Alternativas de thread para sistemas embarcados
Atualmente estou estudando engenharia elétrica. Devido às pandemias, minhas aulas foram suspensas e estou aproveitando esse tempo para aprender mais sobre eletrônica e programação.
No momento, estou tentando usar um Pic16f628a
display digital genérico para construir um relógio digital com alguns recursos. Acontece que tive que acessar um menu apertando um botão em tempo de execução, enquanto o relógio estava sendo exibido. Normalmente eu chamaria um thread para a exibição do relógio e o thread principal estaria observando as entradas, mas devido à simplicidade do controlador de imagem, não posso usar o recurso.
Então, meu código C (ainda não implementado especificamente para pic) é algo assim:
void display_timer(){
static struct current_time timer;
static int is_time_set = 0;
set_current_time(&timer, &is_time_set);
while (is_time_set){
system("clear");
printf("########\n");
printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
printf("########\n");
sleep(1);
update_timer(&timer, &is_time_set);
}
}
int main ()
{
while (1){
display_menu();
}
}
Durante o sono (), o controlador teria que ser capaz de observar novas entradas e agir de forma correspondente.
Uma alternativa que eu pensei era usar uma máquina de estado para armazenar um pressionamento de botão, dividindo a função de hibernação em 4 ou 8 intervalos, algo assim:
while (is_time_set){
system("clear");
printf("########\n");
printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
printf("########\n");
for (int i = 0; i<8; i++){
if (state_machine_input == 1){state_machine_input = 0; break;}
sleep(1/8);
}
update_timer(&timer, &is_time_set);
Poderia ser feito, mas eu agradeceria se não tivesse que adicionar mais complexidade ao projeto, adicionando outra máquina de estado, por exemplo. O que eu poderia fazer em termos de software para implementar essa funcionalidade?
Respostas
Threading é um conceito de nível mais alto do que a programação de microcontroladores. Simplificando, os threads são implementados como um agendador que usa interrupções do cronômetro, que por sua vez salva o contador do programa + ponteiro da pilha, etc. e os define em locais diferentes. Portanto, é bem possível e fácil implementar um conceito semelhante usando interrupções - com a vantagem de obter interrupções especializadas em vez de multi-threading genérico.
Essa é a única maneira sensata de fazer isso com um legado 8 amargo restrito como o PIC, que é extremamente limitado quando se trata de uso de pilha. Esqueça o uso de bibliotecas de thread, mesmo aquelas escritas para microcontroladores. Isso apenas adicionará inchaço e complexidade excessivos, para nada ganhar. Em geral, é uma má ideia arrastar conceitos de programação de PC para o mundo incorporado.
O que você deve fazer é colocar a digitalização de botão dentro de uma interrupção de cronômetro cíclico que é executada uma vez a cada 10ms ou mais. De dentro da interrupção, você consulta os botões e compara o botão lido com o anterior uma vez, para fins de depuração. O resultado disso é armazenado em uma variável compartilhada com o programa principal, declarada como volatile
e protegida de condições de corrida. Como você só grava na variável de dentro das interrupções, pode ser proteção suficiente para garantir que as leituras sejam de 8 bits, mas você deve desmontar para ter certeza. Mais informações sobre isso aqui: Usando volátil no desenvolvimento C embutido .
Use interrupções
Você quer executar algum código ao pressionar um botão? Use uma interrupção de mudança de pino
Você quer fazer algo em um intervalo fixo? Use uma interrupção temporizada
De certa forma, o hardware do microcontrolador executa um 'thread' que monitora as fontes de interrupção e executa um 'callback' ou rotina de interrupção para cada evento.
O programa principal é pausado automaticamente durante a execução da interrupção.
Uma maneira comum de compartilhar dados entre as interrupções e o código principal é por meio de volatile
variáveis globais e desabilitar temporariamente as interrupções ao ler dados desses globais quando eles forem maiores do que o tamanho da palavra do controlador (quase sempre em um controlador de 8 bits)
Eu provavelmente sugeriria uma biblioteca multitarefa cooperativa. Um que usei no passado é Protothreads:http://www.dunkels.com/adam/pt/
Qualquer biblioteca multitarefa cooperativa decente ajudará a abstrair a máquina de estado implícita necessária para manter o controle das coisas.
Boa sorte.
Em geral, existem diferentes abordagens com multitarefa quando se trata de sistema embarcado:
- Polling ou Multitarefa Cooperativa : Tudo é feito em um loop infinito e as tarefas são projetadas para levar o mínimo de tempo possível e retornar à execução principal o mais rápido possível, para evitar atrasos. Observe que as tarefas adequadas para esta arquitetura podem não ser o que você pensaria em termos de conceito de nível superior, por exemplo, em seu aplicativo, uma tarefa poderia ser
update_display
e outra tarefa poderia sercheck_button
e você criaria um loop como:
while(1){
check_buttons();
update_display();
sleep(0.1); //seconds
}
Interrupções : Todas as entradas possíveis conectadas a interrupções de hardware e a execução principal são deixadas para coisas que não podem ser interrompidas (pode ser nada, caso em que normalmente o microcontrolador é colocado em modo de espera para reduzir o consumo de energia. Detalhes de como isso é feito geralmente depende do microcontrolador e compilador específico usado.
RTOS : dependendo de quanta energia o microcontrolador fornece, pode ser possível executar um Sistema Operacional em Tempo Real (RTOS), que pode fornecer API para criar tarefas ou até mesmo threads. Isso depende da aplicação e das capacidades do hardware, e para exemplos educacionais não deve ser necessário (ou aconselhável, imo)
Considere também que outra parte importante ao decidir a arquitetura geral do aplicativo é a divisão em tarefas e como elas cooperam. Um dos paradigmas usados é o State Machines (o link é para a página geral da wikipedia que pode ser opressora, recursos mais simples específicos para programação embarcada podem ser encontrados em seu livro ou no google).
Principalmente, em dispositivos de 8 bits têm origem limitada. Acho que uma solução mais simples é a melhor solução em PICs de 8 bits.
Você pode criar temporizadores de hardware para 2 tarefas diferentes. Defina uma bandeira e verifique a bandeira em seu loop infinito, faça a tarefa e reinicie a bandeira. Não use atrasos. Este método garante a realização de suas tarefas em seu loop infinito, caso sua falha aconteça.
Mas, você tem que saber que as tarefas não são executadas no momento exato em que o sinaliza. Se houver dois sinalizadores definidos ao mesmo tempo, você não pode saber qual deles é executado primeiro. Porque você não sabe onde no loop infinito. Mas, geralmente é bom para aplicativos de interface não críticos de tempo.