Multitarefa em microcontroladores PIC

17

Multitarefa é importante nos dias de hoje. Gostaria de saber como podemos alcançá-lo em microcontroladores e programação incorporada. Estou projetando um sistema baseado em um microcontrolador PIC. Eu projetei seu firmware no MplabX IDE usando C e depois criei um aplicativo para ele no Visual Studio usando C #.

Desde que me acostumei a usar threads na programação C # na área de trabalho para implementar tarefas paralelas, existe uma maneira de fazer o mesmo no meu código de microcontrolador? O MplabX IDE fornece, pthreads.hmas é apenas um esboço sem implementação. Eu sei que há suporte para o FreeRTOS, mas usá-lo torna seu código mais complexo. Alguns fóruns dizem que as interrupções também podem ser usadas como multitarefa, mas não acho que interrupções sejam equivalentes a threads.

Estou projetando um sistema que envia alguns dados para um UART e, ao mesmo tempo, ele precisa enviar dados para um site via Ethernet (com fio). Um usuário pode controlar a saída pelo site, mas a saída é LIGADA / DESLIGADA com um atraso de 2-3 segundos. Então esse é o problema que estou enfrentando. Existe alguma solução para multitarefa em microcontroladores?

Aeronaves
fonte
Os threads podem ser usados ​​apenas em processadores que executam um sistema operacional, porque os threads fazem parte do processo e os processos são usados ​​apenas em sistemas operacionais.
TicTacToe
@ Zola sim, você está certo. Mas e no caso de controladores?
Aeronave
2
Possível duplicado de RTOS para Sistemas Embarcados
Roger Rowland
11
Você pode explicar por que precisa de verdadeira multitarefa e não pode implementar razoavelmente seu software com base em uma abordagem de tarefa round-robin ou em um loop select () ou similar?
Whatsisname
2
Bem, como eu já disse, estou enviando e recebendo dados para uart e ao mesmo tempo enviando e recebendo dados para ethernet. Além disso, eu também preciso salvar dados no cartão SD junto com o tempo, portanto, o DS1307 RTC está envolvido e a EEPROM também está envolvida. Até agora, tenho apenas 1 UART, mas talvez alguns dias depois eu esteja enviando e recebendo dados de 3 módulos UART. O site também receberá dados de 5 sistemas diferentes instalados em local remoto. Tudo isso tem que ser paralelo, mas certo, não é paralelo, mas com um atraso de alguns segundos. !
Aeronaves

Respostas:

20

Existem dois tipos principais de sistemas operacionais multitarefa, preventivo e cooperativo. Ambos permitem que várias tarefas sejam definidas no sistema, a diferença é como a alternância de tarefas funciona. Obviamente, com um único processador principal, apenas uma tarefa está sendo executada por vez.

Ambos os tipos de sistemas operacionais multitarefa requerem uma pilha separada para cada tarefa. Portanto, isso implica duas coisas: primeiro, que o processador permita que as pilhas sejam colocadas em qualquer lugar da RAM e, portanto, tenha instruções para mover o ponteiro da pilha (SP) - ou seja, não há uma pilha de hardware para fins especiais, como existe na gama baixa PIC's. Isso deixa de fora as séries PIC10, 12 e 16.

Você pode escrever um sistema operacional quase inteiramente em C, mas o alternador de tarefas, onde o SP se move, precisa estar em montagem. Em vários momentos, escrevi alternadores de tarefas para o PIC24, PIC32, 8051 e 80x86. As tripas são bem diferentes, dependendo da arquitetura do processador.

O segundo requisito é que haja RAM suficiente para fornecer várias pilhas. Normalmente, você gostaria de pelo menos algumas centenas de bytes para uma pilha; mas mesmo com apenas 128 bytes por tarefa, oito pilhas exigirão 1K bytes de RAM - você não precisa alocar a mesma pilha de tamanho para cada tarefa. Lembre-se de que você precisa de pilha suficiente para lidar com a tarefa atual e todas as chamadas para suas sub-rotinas aninhadas, mas também empilhar espaço para uma chamada de interrupção, pois você nunca sabe quando uma ocorrerá.

Existem métodos bastante simples para determinar quanta pilha você está usando para cada tarefa; por exemplo, você pode inicializar todas as pilhas para um valor específico, por exemplo, 0x55, executar o sistema por um tempo e parar e examinar a memória.

Você não diz que tipo de PIC deseja usar. A maioria dos PIC24 e PIC32 terá muito espaço para executar um sistema operacional multitarefa; o PIC18 (o único PIC de 8 bits a ter pilhas na RAM) tem um tamanho máximo de RAM de 4K. Então, isso é bastante duvidoso.

Com a multitarefa cooperativa (a mais simples das duas), a troca de tarefas só é feita quando a tarefa "desiste" de seu controle de volta ao sistema operacional. Isso acontece sempre que a tarefa precisa chamar uma rotina do SO para executar algumas funções pelas quais aguardará, como uma solicitação de E / S ou uma chamada de timer. Isso torna mais fácil para o sistema operacional alternar pilhas, pois não é necessário salvar todos os registros e informações de estado, o SP pode ser alternado para outra tarefa (se não houver outras tarefas prontas para execução, uma pilha inativa será dado controle). Se a tarefa atual não precisar fazer uma chamada do sistema operacional, mas já estiver em execução há algum tempo, precisará abrir mão do controle voluntariamente para manter o sistema responsivo.

O problema da multitarefa cooperativa é que, se a tarefa nunca abrir mão do controle, ela pode monopolizar o sistema. Somente ele e todas as rotinas de interrupção que recebem controle podem ser executados; portanto, o sistema operacional parece travar. Este é o aspecto "cooperativo" desses sistemas. Se um cronômetro de vigilância for implementado e redefinido apenas quando uma alternância de tarefas for executada, é possível capturar essas tarefas erradas.

O Windows 3.1 e versões anteriores eram sistemas operacionais cooperativos, razão pela qual em parte o desempenho deles não foi tão bom.

A multitarefa preemptiva é mais difícil de implementar. Aqui, as tarefas não são necessárias para abrir mão do controle manualmente, mas, em vez disso, cada tarefa pode ter um tempo máximo para execução (por exemplo, 10 ms) e, em seguida, uma alternância de tarefas é executada para a próxima tarefa executável, se houver uma. Isso requer interromper arbitrariamente uma tarefa, salvar todas as informações de estado e, em seguida, alternar o SP para outra tarefa e iniciá-la. Isso torna o alternador de tarefas mais complicado, requer mais pilha e torna o sistema um pouco mais lento.

Para multitarefa cooperativa e preventiva, podem ocorrer interrupções a qualquer momento, o que antecipará temporariamente a tarefa em execução.

Como a supercat aponta em um comentário, uma vantagem da multitarefa cooperativa é que é mais fácil compartilhar recursos (por exemplo, hardware como um ADC multicanal ou software como modificar uma lista vinculada). Às vezes, duas tarefas desejam acessar o mesmo recurso ao mesmo tempo. Com o planejamento preventivo, seria possível que o sistema operacional alternasse tarefas no meio de uma tarefa usando um recurso. Portanto, os bloqueios são necessários para impedir que outra tarefa entre e acesse o mesmo recurso. Com a multitarefa cooperativa, isso não é necessário porque a tarefa controla quando será lançada de volta ao sistema operacional.

tcrosley
fonte
3
Uma vantagem da multitarefa cooperativa é que na maioria dos casos não é necessário usar bloqueios para coordenar o acesso aos recursos. Será suficiente garantir que as tarefas sempre deixem os recursos em um estado compartilhável sempre que eles abandonarem o controle. A multitarefa preemptiva é muito mais complicada se uma tarefa pode ser desativada enquanto mantém um bloqueio em um recurso necessário para outra tarefa. Em alguns casos, a segunda tarefa pode acabar sendo bloqueada por mais tempo do que seria em um sistema cooperativo, uma vez que a tarefa que mantinha a trava estaria dedicando o sistema ...
supercat
11
... recursos completos para concluir a ação que (no sistema preventivo) exigiria o bloqueio, disponibilizando assim o objeto protegido para a segunda tarefa.
Supercat 7/15
11
Embora as multitarefas cooperativas exijam disciplina, garantir que os requisitos de tempo sejam atendidos às vezes pode ser mais fácil em uma multitarefa cooperativa do que em uma preventiva. Como são necessários muito poucos bloqueios em uma alternância de tarefas, um sistema de alternância de tarefas de cinco tarefas round robin, em que as tarefas são obrigadas a não exceder 10 ms sem produzir, combinadas com uma pequena lógica que diz "Se a tarefa X urgentemente precisa ser executado, execute-o em seguida ", garantirá que a tarefa X nunca precise esperar mais de 10 ms, uma vez que ela sinalize antes de começar a ser executada. Por outro lado, se uma tarefa requer um bloqueio, a tarefa X ...
supercat
11
... vai precisar, mas é trocado por um alternador preventivo antes de liberá-lo, o X pode não conseguir fazer nada útil até que o agendador da CPU comece a executar a primeira tarefa. A menos que o planejador inclua lógica para reconhecer e manipular a inversão de prioridade, pode levar algum tempo até que a primeira tarefa conclua seus negócios e libere a trava. Tais problemas não são solucionáveis, mas resolvê-los requer muita complexidade que poderia ter sido evitada em um sistema cooperativo. Sistemas cooperativos grande trabalho, exceto por uma pegadinha: ...
supercat
3
você não precisa de várias pilhas na cooperativa se codificar em continuações. Em essência, seu código é dividido em funções: void foo(void* context)a lógica do controlador (kernel) puxa um ponteiro e o par de ponteiros de funções da fila e o chama um de cada vez. Essa função usa o contexto para armazenar suas variáveis ​​e tal e, em seguida, pode adicionar enviar uma continuação à fila. Essas funções devem retornar rapidamente para permitir que outras tarefas sejam o momento na CPU. Este é um método baseado em eventos que requer apenas uma única pilha.
catraca aberração
16

A segmentação é fornecida por um sistema operacional. No mundo incorporado, geralmente não temos um sistema operacional ("bare metal"). Portanto, isso deixa as seguintes opções:

  • O loop principal de votação clássico. Sua função principal tem um tempo (1) que executa a tarefa 1 e depois a tarefa 2 ...
  • Loop principal + sinalizadores ISR: você tem um ISR que executa a função de tempo crítico e, em seguida, alerta o loop principal por meio de uma variável flag que a tarefa precisa de serviço. Talvez o ISR coloque um novo caractere em um buffer circular e, em seguida, diga ao loop principal para manipular os dados quando estiver pronto para fazê-lo.
  • All ISR: Grande parte da lógica aqui é executada a partir do ISR. Em um controlador moderno, como um ARM, com vários níveis de prioridade. Isso pode fornecer um poderoso esquema "semelhante a um encadeamento", mas também pode ser confuso para depuração, portanto deve ser reservado apenas para restrições críticas de tempo.
  • RTOS: um núcleo RTOS (facilitado por um ISR de timer) pode permitir alternar entre vários threads de execução. Você mencionou o FreeRTOS.

Aconselho que você use o mais simples dos esquemas acima que funcionará para o seu aplicativo. Pelo que você descreve, eu teria o loop principal gerando pacotes e colocando-os em buffers circulares. Em seguida, tenha um driver baseado no UART ISR que seja acionado sempre que o byte anterior terminar de enviar até que o buffer seja enviado e aguarde mais conteúdo do buffer. Abordagem semelhante para a Ethernet.

Houston Fortney
fonte
3
Essa é uma resposta muito útil, pois aborda a raiz do problema (como realizar multitarefas em um pequeno sistema incorporado, em vez de threads como solução). Um parágrafo sobre como isso poderia se aplicar à pergunta original seria excelente, talvez incluindo os prós e contras de cada um para o cenário.
David
8

Como em qualquer processador single-core, não é possível realizar multitarefa de software real. Portanto, você deve alternar entre várias tarefas de uma maneira. Os diferentes RTOS estão cuidando disso. Eles têm um agendador e, com base em uma marca do sistema, alternam entre diferentes tarefas para oferecer a você uma capacidade de multitarefa.

Os conceitos envolvidos nesse processo (economia e restauração de contexto) são bastante complicados; portanto, fazer isso manualmente provavelmente será difícil e tornará seu código mais complexo e, como você nunca fez isso antes, haverá erros nele. Meu conselho aqui seria usar um RTOS testado como o FreeRTOS.

Você mencionou que as interrupções fornecem um nível de multitarefa. Isso é meio que verdade. A interrupção interromperá seu programa atual a qualquer momento e executará o código, é comparável a um sistema de duas tarefas em que você tem 1 tarefa com baixa prioridade e outra com alta prioridade que termina dentro de um intervalo de tempo do agendador.

Assim, você pode escrever um manipulador de interrupção para um timer recorrente que enviará alguns pacotes pelo UART, e então o resto do seu programa será executado por alguns milissegundos e os próximos bytes. Dessa forma, você obtém um recurso limitado de multitarefa. Mas você também terá uma interrupção bastante longa, o que pode ser uma coisa ruim.

A única maneira real de realizar várias tarefas ao mesmo tempo em um MCU de núcleo único é usar o DMA e os periféricos, pois eles trabalham independentemente do núcleo (o DMA e o MCU compartilham o mesmo barramento, para que trabalhem um pouco mais devagar quando ambos estão ativos). Assim, enquanto o DMA embaralha os bytes para o UART, seu núcleo fica livre para enviar as coisas para a Ethernet.

Arsenal
fonte
2
obrigado, DMA parece interessante. Definitivamente vou procurar.
Aeronaves
Nem todas as séries de PICs têm DMA.
Matt Young
11
Estou usando o PIC32;)
Aeronave
6

As outras respostas já descreviam as opções mais utilizadas (loop principal, ISR, RTOS). Aqui está outra opção como compromisso: Protothreads . É basicamente uma biblioteca muito leve para threads, que usa o loop principal e algumas macros C para "emular" um RTOS. Claro que não é um sistema operacional completo, mas para threads "simples", pode ser útil.

erebos
fonte
de onde posso baixar o código fonte do windows? Eu acho que está disponível apenas para Linux.!
Aircraft
@CZAbhinav Deve ser independente do SO e você pode obter o download mais recente aqui .
21715 erebos
Estou no windows agora e usando MplabX, eu não acho que é útil aqui. De qualquer forma obrigado.!
Aircraft
Não ouvi falar de protothreads, parece uma técnica interessante.
Arsenal
@CZAbhinav Do que você está falando? É um código C e não tem nada a ver com o seu sistema operacional.
Matt Young
3

Meu design básico para um RTOS fatiado com o mínimo de tempo não mudou muito em várias micro famílias. É basicamente uma interrupção do temporizador ao dirigir uma máquina de estado. A rotina de serviço de interrupção é o kernel do sistema operacional, enquanto a instrução switch no loop principal são as tarefas do usuário. Drivers de dispositivo são rotinas de serviço de interrupção para interrupções de E / S.

A estrutura básica é a seguinte:

unsigned char tick;

void interrupt HANDLER(void) {
    device_driver_A();
    device_driver_B();
    if(T0IF)
    {
        TMR0 = TICK_1MS;
        T0IF = 0;   // reset timer interrupt
        tick ++;
    }
}

void main(void)
{
    init();

    while (1) {
        // periodic tasks:
        if (tick % 10 == 0) { // roughly every 10 ms
            task_A();
            task_B();    
        }
        if (tick % 55 == 0) { // roughly every 55 ms
            task_C();
            task_D();    
        }

        // tasks that need to run every loop:
        task_E();
        task_F();
    }
}

Este é basicamente um sistema multitarefa cooperativo. As tarefas são gravadas para nunca inserir um loop infinito, mas não nos importamos porque as tarefas são executadas em um loop de eventos, portanto o loop infinito está implícito. Esse é um estilo semelhante de programação para linguagens orientadas a eventos / sem bloqueio, como javascript ou go.

Você pode ver um exemplo desse estilo de arquitetura no meu software de transmissor RC (sim, na verdade, eu o uso para pilotar aviões RC, por isso é um pouco crítico em termos de segurança me impedir de bater nos aviões e potencialmente matar pessoas): https://github.com / slebetman / pic-txmod . Ele possui basicamente 3 tarefas - 2 tarefas em tempo real implementadas como drivers de dispositivo com estado (consulte o material do ppmio) e 1 tarefa em segundo plano implementando a lógica de mixagem. Então, basicamente, é semelhante ao seu servidor web, pois possui 2 threads de E / S.

slebetman
fonte
11
Eu realmente não chamaria isso de 'multitarefa cooperativa', pois isso realmente não é substancialmente diferente de qualquer outro programa de microcontrolador que precise fazer várias coisas.
whatsisname
2

Embora eu aprecie que a pergunta indique especificamente sobre o uso de um RTOS incorporado, me ocorre que a pergunta mais ampla que está sendo feita é "como obter multitarefa em uma plataforma incorporada".

Eu recomendo fortemente que você esqueça de usar um RTOS incorporado, pelo menos por enquanto. Aconselho isso porque acho que é essencial primeiro aprender sobre como obter 'simultaneidade de tarefas' por meio de técnicas de programação extremamente simples que consistem em agendadores de tarefas simples e máquinas de estado.

Para explicar de maneira extremamente resumida o conceito, cada módulo de trabalho que precisa ser realizado (ou seja, cada 'tarefa') possui uma função específica que deve ser chamada ('marcada') periodicamente para que esse módulo faça algumas coisas. O módulo mantém seu próprio estado atual. Você tem um loop infinito principal (o agendador) que chama as funções do módulo.

Ilustração bruta:

for(;;)
{
    main_lcd_ui_tick();
    networking_tick();
}


...

// In your LCD UI module:
void main_lcd_ui_tick(void)
{
    check_for_key_presses();
    update_lcd();
}

...

// In your networking module:
void networking_tick(void)
{
    //'Tick' the TCP/IP library. In this example, I'm periodically
    //calling the main function for Keil's TCP/IP library.
    main_TcpNet();
}

Uma estrutura de programação de encadeamento único como esta, na qual você chama periodicamente as funções da máquina de estado principal a partir de um loop do agendador principal, é onipresente na programação incorporada, e é por isso que eu encorajo fortemente o OP a se familiarizar e se familiarizar com ele primeiro, antes de mergulhar diretamente no uso Tarefas / threads do RTOS.

Trabalho em um tipo de dispositivo incorporado que possui uma interface LCD de hardware, servidor web interno, cliente de email, cliente DDNS, VOIP e muitos outros recursos. Embora utilizemos um RTOS (Keil RTX), o número de threads individuais (tarefas) usados ​​é muito pequeno e a maior parte da 'multitarefa' é alcançada como descrito acima.

Para dar alguns exemplos de bibliotecas que demonstram esse conceito:

  1. A biblioteca de redes Keil. Toda a pilha TCP / IP pode ser executada em thread único; você chama periodicamente main_TcpNet (), que itera a pilha TCP / IP e qualquer outra opção de rede que você tenha compilado da biblioteca (por exemplo, o servidor da web). Consulte http://www.keil.com/support/man/docs/rlarm/rlarm_main_tcpnet.htm . É certo que em algumas situações (possivelmente fora do escopo desta resposta) você chega a um ponto em que começa a se tornar benéfico ou necessário o uso de threads (principalmente se estiver usando soquetes BSD de bloqueio). (Observação: o novo V5 MDK-ARM na verdade gera um encadeamento Ethernet dedicado - mas estou apenas tentando fornecer uma ilustração.)

  2. A biblioteca VOIP do Linphone. A biblioteca linphone em si é de thread único. Você chama a iterate()função em um intervalo suficiente. Consulte http://www.linphone.org/docs/liblinphone-javadoc/org/linphone/core/LinphoneCore.html#iterate () . (Um exemplo ruim, porque usei isso em uma plataforma Linux incorporada e as bibliotecas de dependência do linphone, sem dúvida, geram threads, mas novamente é para ilustrar um ponto.)

Voltando ao problema específico descrito pelo OP, o problema parece ser o fato de que a comunicação UART deve ocorrer ao mesmo tempo que algumas redes (transmissão de pacotes via TCP / IP). Não sei qual biblioteca de rede você está realmente usando, mas eu suponho que ela tenha uma função principal que precisa ser chamada com frequência. Você precisaria escrever seu código que lida com a transmissão / recepção de dados UART para ser estruturado de maneira semelhante, como uma máquina de estado que pode ser iterada por chamadas periódicas para uma função principal.

Trevor Page
fonte
2
Obrigado por esta boa explicação, estou usando a biblioteca TCP / IP fornecida pelo microchip e é um código complexo muito grande. De alguma forma, consegui dividi-lo em partes e torná-lo utilizável de acordo com meus requisitos. Definitivamente vou tentar uma das suas abordagens.!
Aeronaves
Divirta-se :) O uso de um RTOS definitivamente facilita a vida em muitas situações. Na minha opinião, o uso de um thread (tarefa) facilita muito o esforço de programação em um sentido, pois você pode evitar ter que dividir sua tarefa em uma máquina de estado. Em vez disso, basta escrever o código da tarefa como faria nos programas C #, com o código da tarefa criado como se fosse a única coisa que existe. É essencial explorar as duas abordagens e, à medida que você faz mais programação incorporada, começa a sentir qual é a melhor abordagem para cada situação.
Trevor Página
Também prefiro usar a opção de segmentação. :)
Aeronave