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







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 .