Alternative di thread per sistemi embedded
Attualmente sto studiando ingegneria elettrica. A causa delle pandemie, le mie lezioni sono state sospese e sto usando questo tempo per saperne di più sull'elettronica e sulla programmazione.
Attualmente sto cercando di utilizzare un Pic16f628a
display digitale generico per costruire un orologio digitale con alcune caratteristiche. Il fatto è che avevo dovuto accedere a un menu premendo un pulsante in tempo di esecuzione, mentre l'orologio veniva visualizzato. Normalmente chiamerei un thread per la visualizzazione dell'orologio e il thread principale guarderebbe gli input, ma a causa della semplicità del controller pic non posso usare la risorsa.
Quindi, il mio codice C (non ancora implementato specificamente per pic) è qualcosa del genere:
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 lo sleep (), il controller dovrebbe essere in grado di guardare nuovi input e agire di conseguenza.
Un'alternativa che pensavo fosse quella di utilizzare una macchina a stati per memorizzare la pressione di un pulsante, dividendo la funzione sleep in 4 o 8 intervalli, qualcosa del genere:
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);
Potrebbe essere fatto, ma apprezzerei se non dovessi aggiungere più complessità al progetto, aggiungendo ad esempio un'altra macchina a stati. Cosa posso fare nei therms del software per implementare questa funzionalità?
Risposte
Il threading è un concetto di livello superiore rispetto alla programmazione del microcontrollore. In poche parole, i thread sono implementati come uno scheduler che utilizza interrupt del timer, che a sua volta salva il contatore del programma + il puntatore dello stack, ecc. E li imposta in posizioni diverse. Quindi è abbastanza possibile e facile implementare un concetto simile usando gli interrupt, con il vantaggio di ottenere interrupt specializzati invece del multi-threading generico.
Questo è l'unico modo sensato per farlo con un 8 bitter legacy limitato come PIC, che è estremamente limitato quando si tratta di utilizzare lo stack. Dimentica di usare le librerie di thread, anche quelle scritte per microcontrollori. Ciò aggiungerà solo un eccesso di gonfiore e complessità, per niente guadagnato. In generale, è una cattiva idea trascinare i concetti di programmazione per PC nel mondo embedded.
Quello che dovresti fare è inserire la scansione dei pulsanti all'interno di un interrupt ciclico del timer che viene eseguito una volta ogni 10 ms circa. Dall'interno dell'interrupt, si interrogano i pulsanti e si confronta il pulsante letto con il precedente una volta, per scopi di debouncing. Il risultato di ciò viene memorizzato in una variabile condivisa con il programma principale, dichiarato come volatile
e protetto dalle race condition. Dal momento che scrivi nella variabile solo dall'interno degli interrupt, potrebbe essere una protezione sufficiente per garantire che le letture siano 8 bit, ma devi disassemblare per essere sicuro. Maggiori informazioni al riguardo qui: Utilizzo di volatile nello sviluppo C incorporato .
Usa le interruzioni
Vuoi eseguire del codice quando premi un pulsante? Usa un pin-change-interrupt
Vuoi fare qualcosa a un intervallo fisso? Usa un timer di interruzione
In un certo senso, l'hardware del microcontrollore esegue un "thread" che monitora le sorgenti di interrupt ed esegue una "callback" o routine di interrupt per ogni evento.
Il programma principale viene automaticamente messo in pausa durante l'esecuzione dell'interrupt.
Un modo comune per condividere i dati tra gli interrupt e il codice principale è attraverso le volatile
variabili globali e disabilitare temporaneamente gli interrupt durante la lettura dei dati da questi globali quando sono più della dimensione della parola del controller (quasi sempre su un controller a 8 bit)
Probabilmente suggerirei una libreria multitasking cooperativa. Uno che ho usato in passato è Protothreads:http://www.dunkels.com/adam/pt/
Qualsiasi libreria multitasking cooperativa decente aiuterà ad astrarre la macchina a stati implicita richiesta per tenere traccia delle cose.
In bocca al lupo.
Esistono in generale diversi approcci con il multitasking quando si tratta di sistemi embedded:
- Polling o multitasking cooperativo : tutto viene svolto in un ciclo infinito e le attività sono progettate per richiedere il minor tempo possibile e tornare all'esecuzione principale il più velocemente possibile, per evitare ritardi. Nota che le attività adatte a questa architettura potrebbero non essere ciò a cui penseresti in termini di concetto di livello superiore, ad esempio nella tua applicazione potrebbe essere
update_display
un'attività e un'altra attività potrebbe esserecheck_button
e dovresti costruire un ciclo come:
while(1){
check_buttons();
update_display();
sleep(0.1); //seconds
}
Interrupt : tutti i possibili ingressi collegati agli interrupt hardware e l'esecuzione principale è lasciata per cose che non possono essere messe in interrupt (potrebbe non essere nulla, nel qual caso di solito il microcontrollore viene messo in modalità sleep per ridurre i consumi energetici. Dettagli su come questo di solito dipende dal particolare microcontrollore e compilatore utilizzato.
RTOS : a seconda della potenza fornita dal microcontrollore, potrebbe essere possibile eseguire un sistema operativo in tempo reale (RTOS), che potrebbe fornire API per creare attività o persino thread. Ciò dipende dall'applicazione e dalle capacità hardware e per esempi didattici non dovrebbe essere necessario (o consigliabile, imo)
Considera anche che un'altra parte importante nel decidere l'architettura complessiva dell'applicazione è la divisione in compiti e il modo in cui cooperano. Uno dei paradigmi utilizzati sono le macchine a stati (il collegamento è alla pagina generale di wikipedia che potrebbe essere travolgente, risorse più semplici specifiche per la programmazione incorporata si possono trovare sul tuo libro di testo o su google).
Per lo più, i dispositivi a 8 bit hanno una sorgente limitata. Penso che una soluzione più semplice sia una soluzione migliore nei PIC a 8 bit.
È possibile creare timer hardware per 2 diverse attività. Imposta una bandiera e controlla la bandiera nel tuo ciclo infinito, esegui il compito e ripristina la bandiera. Non usare ritardi. Questo metodo garantisce di svolgere le tue attività nel tuo ciclo infinito, se il tuo flag è attivo.
Ma devi sapere che l'attività non viene eseguita nel momento esatto in cui viene visualizzato il flag. Se sono stati impostati due flag contemporaneamente non è possibile sapere quale viene eseguito per primo. Perché non sai dove nel ciclo infinito. Ma per lo più va bene per applicazioni di interfacciamento non critiche in termini di tempo.