Criando seu próprio shell para sistemas embarcados
Neste artigo usaremos o Kit NUCLEO-F446RE da ST para demonstrar o uso da lib micro-shell. Mostraremos como criar, receber e tratar comandos vindos pela porta serial.
Usaremos o CubeMX para gerar o código de inicialização e a IDE Atollic para programar nosso Kit com o suporte ao sistema operacional FreeRTOS.
A biblioteca micro-shell usada neste artigo foi baseada na lib-shell criada originalmente pelo Felipe Neves.
O Problema
Muitas vezes durante o processo de desenvolvimento, chegamos ao instante em que precisamos enviar comandos ao nosso microcontrolador.
A primeira opção seria enviar caracteres simples para controle on/off. Se precisarmos indicar o valor de um set point acabamos combinando letras e números como “S:135\n”. Existem várias formas de fazer isso, porém, muitos desses métodos são bem simples e muitas vezes precisamos enviar várias sequências de comandos para realizar uma única configuração.
A segunda opção seria usar um protocolo industrial como o modbus. Porém não basta implementar o protocolo do lado do micro, também é preciso implementar o protocolo do lado do software. Isso pode levar muito tempo e dependendo da aplicação é inviável.
A Solução
A solução parece óbvia: por que não implementar uma lib que conseguisse replicar o que o shell do linux faz em minha aplicação embarcada?
No mundo Unix, o termo Shell é mais usualmente utilizado para se referir aos programas de sistemas do tipo Unix que podem ser utilizados como meio de interação entre interface de usuário para o acesso a serviços do kernel no sistema operacional. Este é um programa que recebe, interpreta e executa os comandos de usuário, aparecendo na tela como uma linha de comandos, representada por um interpretador de comandos, que aguarda na tela os comandos do usuário. (Fonte: wikipedia)
Conversando com o Felipe descobri a biblioteca que ele tinha montado. Acabei alterando a biblioteca original dele para usá-la com FreeRTOS, e esta é a solução que está sendo compartilhada neste artigo.
Principais Funções da Lib Micro-Shell
Abaixo descreveremos um pouco as funções da lib micro-shell.
Macro SHELL_PRINTF: esta macro é usada para indicar qual função será usada como output das respostas do micro-shell. Em nosso exemplo usaremos uma saída serial, mas nada o impede de usar outro tipo de saída, como por exemplo ethernet, usb, etc.
vTaskMicroShell_init: Função usada para criar a task que trata os comandos vindos pela serial e verificar se eles são válidos ou não.
Obs: Ocasionalmente pode acontecer Stack Overflow ao criar a task; para corrigir isso será preciso aumentar o tamanho da stack usada pela task, que é feito através do parâmetro de entrada multi_stack_size.
vTaskShell: Task da aplicação micro-shell, é usado um semáforo binário para acordar e sinalizar a task, para informar que existe um pacote e ele precisa ser tratado.
shell_parser: Função que realiza toda a mágica de parse do pacote recebido. Obs: dedique um tempo para entender bem esta função, ela pode lhe agregar bastante conhecimento.
shell_getc: Função usada para armazenar o byte recebido pela serial, e internamente controla a montagem do pacote recebido. Ao receber o caractere “\n” ou “\r” ele sinaliza a task principal que chegou um comando e que ele precisa ser tratado.
shell_isr_getc: Função parecida com a shell_getc, esta função deve ser usada somente quando a recepção do byte é feita dentro de uma interrupção.
shell_callback: Esta função deve ser implementada por sua aplicação. Após a lib micro-shell tratar e separar as sintaxes do comando recebido, esta função é chamada para você poder tratar cada dado individualmente.
Rodando o Exemplo
No código de exemplo, implementamos um caso de uso onde esta biblioteca pode ser bem útil. O exemplo consiste em informar a sua aplicação IoT, a rede, e o host que deseja se conectar.
O código abaixo mostra a implementação de nossa aplicação dentro da callback ‘shell_callback”, a variável cmd é a string com o comando principal. A variável argc contém o número de argumentos recebidos e a variável argv contém a string dos argumentos já separados na forma de vetor.
void shell_callback(uint8_t *cmd, uint16_t argc, uint8_t **argv)
{
HAL_StatusTypeDef err = HAL_ERROR;
if (strcmp((const char *)"help", (const char *)cmd) == 0)
{
SHELL_PRINTF("Supported commands:");
SHELL_PRINTF("-> rede -w WIFI_NAME -pwd PASSWORD");
SHELL_PRINTF("-> connect -h HOST -p PORT\n");
err = HAL_OK;
}
else if (strcmp((const char *)"rede", (const char *)cmd) == 0)
{
if (argc == 4)
{
if (strcmp((const char *)"-w", (const char *)argv[0]) == 0 &&
strcmp((const char *)"-pwd", (const char *)argv[2]) == 0)
{
snprintf((char *)config.wifi_name, 20, "%s", argv[1]);
snprintf((char *)config.passwd, 20, "%s", argv[3]);
err = HAL_OK;
}
}
}
else if (strcmp((const char *)"connect", (const char *)cmd) == 0)
{
if (argc == 4)
{
if (strcmp((const char *)"-h", (const char *)argv[0]) == 0 &&
strcmp((const char *)"-p", (const char *)argv[2]) == 0)
{
snprintf((char *)config.host, 20, "%s", argv[1]);
config.port = atoi((const char *)argv[3]);
err = HAL_OK;
}
}
}
else if (strcmp((const char *)"show", (const char *)cmd) == 0)
{
if (argc == 1)
{
if (strcmp((const char *)"-cfg", (const char *)argv[0]) == 0)
{
SHELL_PRINTF("======== CONFIGS ========");
SHELL_PRINTF("wifi name: %s", config.wifi_name);
SHELL_PRINTF("password: %s", config.passwd);
SHELL_PRINTF("host: %s", config.host);
SHELL_PRINTF("port: %d", config.port);
err = HAL_OK;
}
}
}
else
{
SHELL_PRINTF("(X) command not found");
}
if (err == HAL_ERROR)
{
SHELL_PRINTF("(X) Sintax error");
}
}
A tabela abaixo mostra como o comando é recebido, armazenado e disponibilizado nas variáveis cmd e argv. Para este exemplo, o número de argumentos da variável argc é 4 (argv[0-3]).
|
cmd |
argv[0] |
argv[1] |
argv[2] |
argv[3] |
Caractere de fim de pacote |
|
rede |
-w |
@Casa |
-pwd |
123456789 |
\n ou \r |
A figura abaixo mostra a troca de mensagens entre o microcontrolador e o usuário.
Ao enviar um comando com sintaxe errada podemos ver a aplicação informando a mensagens “(X) Sintax error”.
Conclusão
Neste artigo vimos como usar a lib micro-shell, suas principais vantagens e como usar suas principais funções para aquisitar e tratar diferentes tipos de comandos. Uma segunda opção a esta biblioteca é a lib CLI do FreeRTOS.
Se você gostou do artigo ou tem algo a acrescentar deixe seus comentários.
Código Fonte
No link abaixo segue o código fonte do exemplo mostrado em este artigo.
Referências
Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.



Parabéns pelo artigo!
Um dos problemas muito peculiares em comunicação UART no uso de microcontroladores, perda de dados. Uma boa dica, parabéns .