Uma coisa que achei útil em várias máquinas é um simples comutador de pilhas. Na verdade, eu não escrevi um para o PIC, mas espero que a abordagem funcione bem no PIC18 se ambos / todos os segmentos usarem um total de 31 ou menos níveis de pilha. No 8051, a rotina principal é:
_taskswitch:
xch a, SP
xch a, _altSP
xch a, SP
ret
No PIC, esqueço o nome do ponteiro da pilha, mas a rotina seria algo como:
_taskswitch:
movlb _altSP >> 8
movf _altSP, w, b
movff _STKPTR, altSP
movwf _STKPTR, c
Retorna
No início do seu programa, chame uma rotina task2 () que carregue altSP com o endereço da pilha alternativa (16 provavelmente funcionaria bem para um PIC18Fxx) e execute o loop task2; essa rotina nunca deve retornar, senão as coisas sofrerão uma morte dolorosa. Em vez disso, deve chamar _taskswitch sempre que desejar gerar controle para a tarefa principal; a tarefa principal deve chamar _taskswitch sempre que desejar render à tarefa secundária. Muitas vezes, você terá rotinas fofinhas como:
void delay_t1 (valor curto não assinado)
{
Faz
comutador de tarefas ();
while ((sem sinal abreviado) (milissegundo_clock - val)> 0xFF00);
}
Observe que o alternador de tarefas não tem meios de executar qualquer 'espera pela condição'; tudo o que suporta é um spinwait. Por outro lado, a alternância de tarefas é tão rápida que uma tentativa de alternar tarefas () enquanto a outra tarefa aguarda a expiração do cronômetro mudará para a outra tarefa, verificará o cronômetro e voltará mais rapidamente do que um alternador de tarefas determinaria que ele não precisa alternar entre tarefas.
Observe que a multitarefa cooperativa tem algumas limitações, mas evita a necessidade de muitos códigos relacionados a mutex e bloqueio nos casos em que invariantes temporariamente perturbados podem ser restabelecidos rapidamente.
(Editar): Algumas advertências sobre variáveis automáticas e outras:
- se uma rotina que usa alternância de tarefas for chamada de ambos os threads, geralmente será necessário compilar duas cópias da rotina (possivelmente #incluindo o mesmo arquivo de origem duas vezes, com instruções #define diferentes). Qualquer arquivo de origem contém código para apenas um segmento ou outro código que será compilado duas vezes - uma vez para cada segmento - para que eu possa usar macros como "#define delay (x) delay_t1 (x)" ou #define delay (x) delay_tx (x) "dependendo do segmento que estou usando.
- Acredito que os compiladores PIC que não podem "ver" uma função sendo chamada assumirão que essa função pode eliminar todos e quaisquer registros de CPU, evitando assim a necessidade de salvar quaisquer registros na rotina de troca de tarefas [um bom benefício em comparação com multitarefa preemptiva]. Qualquer pessoa que considere um alternador de tarefas semelhante para qualquer outra CPU precisa estar ciente das convenções de registro em uso. Pressionar os registros antes de uma tarefa alternar e pop-los depois é uma maneira fácil de cuidar das coisas, assumindo que existe um espaço de pilha adequado.
A multitarefa cooperativa não permite escapar completamente de questões de bloqueio, mas realmente simplifica bastante as coisas. Em um RTOS preventivo com um coletor de lixo compactador, por exemplo, é necessário permitir que os objetos sejam fixados. Ao usar um comutador cooperativo, isso não é necessário, desde que o código pressuponha que os objetos do GC possam se mover a qualquer momento em que o taskwitch () for chamado. Um coletor de compactação que não precisa se preocupar com objetos fixados pode ser muito mais simples do que um que faz.