Em projetos embarcados, ter um shell interativo é um grande facilitador para depuração e testes. O Letter Shell oferece uma base sólida, mas muitas vezes precisamos ir além do básico: registrar nossas próprias funções como comandos, criar atalhos de teclado ou expor variáveis para leitura e escrita direatmente pelo terminal. A boa notícia é que a arquitetura do Letter Shell foi projetada com extensibilidade em mente, tratando funções, variáveis e teclas de maneira unificada como "itens de comando".
Este artigo mostra como personalizar o shell usando os mecanismos de exportação fornecidos, tomando cuidado com a retenção no linker e fornecendo exemplos práticos.
Configuração do linker
Para que os comandos registrados não sejam removidos pelo linker durante a otimização, é necessário preservar a seção .shellCommand. As configurações variam conforme o compilador:
- Keil MDK: adicione
--keep shellCommand*nas opções de compilação. - GCC: insira no script de linker: ```
_shell_command_start = .;
KEEP (*(shellCommand))
_shell_command_end = .;
Exportando funções como comandos
A macro SHELL_EXPORT_CMD permite transformar uma função C em um comando invocável pelo terminal. A função é registrada na seção shellCommand por meio de uma estrutura de atributos. O exemplo a seguir cria um comando saudar que imprime uma mensagem:
void exibirSaudacao(void) {
Shell* sh = shellGetCurrent();
if (sh) {
shellWriteString(sh, "Ola, shell customizado!");
}
}
SHELL_EXPORT_CMD(
SHELL_CMD_PERMISSION(0) |
SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) |
SHELL_CMD_DISABLE_RETURN,
saudar, exibirSaudacao, "Exibe uma saudacao"
);
Note que a função exportada é convertida para (int (*)()) internamente, o que sacrifica a verificação de tipos. Em aplicações críticas, pode-se envolver a função original em uma função de assinatura compatível para maior segurança.
Variáveis acessíveis pelo terminal
Além de funções, o Letter Shell permite exportar variáveis para leitura e escrita. Os tipos suportados incluem inteiros, strings e ponteiros. A macro SHELL_EXPORT_VAR recebe o endereço da variável (exceto para strings, onde o próprio array já é um ponteiro).
int contador = 0;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_VAR_INT),
contador, &contador, "Contador de eventos");
char mensagem[] = "teste shell";
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_VAR_STRING),
mensagem, mensagem, "Mensagem de status");
struct Dispositivo disp;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0) | SHELL_CMD_TYPE(SHELL_TYPE_VAR_POINT),
disp, &disp, "Estrutura de dispositivo");
Com isso, digitar contador no terminal mostra seu valor atual, e atribuições como contador = 10 atualizam a variável em tempo real. Para variáveis somente leitura, adicione o atributo SHELL_CMD_READ_ONLY.
Atalhos de teclado
Também é possível associar ações a teclas específicas (como Tab, Ctrl+C, ou setas). A macro SHELL_EXPORT_KEY registra um callback para uma sequência de bytes representada em big-endian.
Por exemplo, a tecla Ctrl+C envia o byte 0x03. Para registrá-la:
uint8_t flagInterrupcao = 0;
void aoPressionarCtrlC(Shell* shell) {
flagInterrupcao = 1;
// Reexibe o prompt e reativa a análise de comandos
extern void escreverPrompt(Shell* shell, unsigned char novaLinha);
escreverPrompt(shell, 1);
extern void executarShell(Shell* shell);
executarShell(shell);
}
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 0x03000000, aoPressionarCtrlC, "Ctrl+C");
Para desocbrir a sequência de bytes de outras teclas, utilize um terminal com modo hexadecimal (como o SecureCRT) e pressione a tecla desejada enquanto observa os dados recebidos. Lembre-se de completar com zeros à direita até 4 bytes, pois o valor é tratado como big‑endian de 32 bits.
O mecanismo de exportação unificado, baseado na seção shellCommand e nas macros fornecidas, torna o Letter Shell uma ferramenta de depuração extremamente flexível. Basta garantir que a seção não seja removida pelo linker e você poderá rapidamente transformar suas funções, variáveis e atalhos em comandos de terminal.