Una grande varietà di risposte qui ... principalmente affrontando il problema in vari modi.
Scrivo software e firmware integrati di basso livello da oltre 25 anni in una varietà di lingue, principalmente C (ma con deviazioni in Ada, Occam2, PL / M e vari assemblatori lungo la strada).
Dopo un lungo periodo di riflessione, tentativi ed errori, ho optato per un metodo che ottiene risultati abbastanza rapidamente ed è abbastanza facile creare involucri di test e imbracature (dove AGGIUNGONO VALORE!)
Il metodo va in questo modo:
Scrivi un driver o un'unità di codice di astrazione hardware per ogni periferica principale che desideri utilizzare. Scrivere anche uno per inizializzare il processore e ottenere tutto impostato (questo rende l'ambiente amichevole). In genere su piccoli processori integrati, ad esempio il tuo AVR, potrebbero esserci 10-20 unità di questo tipo, tutte piccole. Potrebbero essere unità per l'inizializzazione, conversione A / D in buffer di memoria non scalati, uscita bit a bit, ingresso pulsante (nessun debounce appena campionato), driver di modulazione della larghezza di impulso, UART / driver seriali semplici l'uso interrompe e piccoli buffer I / O. Potrebbero essercene alcuni altri, ad esempio driver I2C o SPI per EEPROM, EPROM o altri dispositivi I2C / SPI.
Per ciascuna unità di astrazione hardware (HAL) / driver, scrivo quindi un programma di test. Questo si basa su una porta seriale (UART) e sull'iniziatore del processore, quindi il primo programma di test utilizza solo quelle 2 unità e fa solo input e output di base. Questo mi permette di provare che posso avviare il processore e che ho un I / O seriale di supporto per il debug funzionante. Una volta che funziona (e solo allora) sviluppo gli altri programmi di test HAL, costruendoli sopra le note unità UART e INIT. Quindi potrei avere programmi di test per leggere gli input bit per bit e visualizzarli in una bella forma (esadecimale, decimale, qualunque cosa) sul mio terminale di debug seriale. Posso quindi passare a cose più grandi e complesse come i programmi di test EEPROM o EPROM - faccio guidare la maggior parte di questi menu in modo da poter selezionare un test da eseguire, eseguirlo e vedere il risultato. Non riesco a scriverlo ma di solito non lo faccio
Una volta che ho tutti i miei HAL in esecuzione, trovo quindi un modo per ottenere un normale tick del timer. Questo è in genere a una velocità compresa tra 4 e 20 ms. Questo deve essere regolare, generato in un interrupt. Il rollover / overflow dei contatori è di solito come si può fare. Il gestore di interrupt quindi INCREMENTA una dimensione di byte "semaforo". A questo punto puoi anche giocherellare con la gestione dell'alimentazione, se necessario. L'idea del semaforo è che se il suo valore è> 0 è necessario eseguire il "ciclo principale".
EXECUTIVE esegue il loop principale. Praticamente aspetta solo che quel semaforo diventi diverso da 0 (l'astratto questo dettaglio è lontano). A questo punto, puoi giocare con i contatori per contare questi tick (perché conosci la frequenza di tick) e quindi puoi impostare flag che mostrano se l'attuale tick esecutivo è per un intervallo di 1 secondo, 1 minuto e altri intervalli comuni potrebbe voler usare. Una volta che il dirigente sa che il semaforo è> 0, esegue un singolo passaggio attraverso ogni funzione di "aggiornamento" dei processi "applicazione".
I processi dell'applicazione siedono efficacemente uno accanto all'altro e vengono eseguiti regolarmente da un segno di spunta "aggiornamento". Questa è solo una funzione chiamata dall'esecutivo. Questo è in realtà un multi-tasking per le persone povere con un RTOS casalingo molto semplice che si basa su tutte le applicazioni che entrano, fanno un piccolo lavoro ed escono. Le applicazioni devono mantenere le proprie variabili di stato e non possono eseguire calcoli a lungo termine poiché non esiste un sistema operativo preventivo per forzare l'equità. OVVIAMENTE il tempo di esecuzione delle applicazioni (cumulativamente) dovrebbe essere inferiore al periodo di tick maggiore.
L'approccio sopra è facilmente esteso in modo da poter aggiungere elementi come stack di comunicazione che vengono eseguiti in modo asincrono e che i messaggi di comunicazione possono quindi essere recapitati alle applicazioni (si aggiunge una nuova funzione a ciascuna che è il "rx_message_handler" e si scrive un dispatcher di messaggi che figure a quale domanda spedire).
Questo approccio funziona praticamente per qualsiasi sistema di comunicazione che ti interessa nominare: può (e ha fatto) funzionare per molti sistemi proprietari, sistemi di comunicazione standard aperti, funziona anche per stack TCP / IP.
Ha anche il vantaggio di essere costruito in pezzi modulari con interfacce ben definite. Puoi estrarre e estrarre pezzi in qualsiasi momento, sostituire pezzi diversi. Ad ogni punto lungo il percorso è possibile aggiungere imbracature di prova o manipolatori che si basano sulle parti dello strato inferiore buone note (le cose di seguito). Ho scoperto che all'incirca dal 30% al 50% di un progetto può trarre vantaggio dall'aggiunta di test di unità appositamente scritti che di solito sono abbastanza facilmente aggiunti.
Ho fatto un ulteriore passo avanti (un'idea che ho preso da qualcun altro che lo ha fatto) e ho sostituito il livello HAL con un equivalente per PC. Ad esempio, è possibile utilizzare C / C ++ e winforms o simili su un PC e scrivendo ATTENTAMENTE il codice è possibile emulare ciascuna interfaccia (ad esempio EEPROM = un file del disco letto nella memoria del PC) e quindi eseguire l'intera applicazione integrata su un PC. La possibilità di utilizzare un ambiente di debug intuitivo può far risparmiare molto tempo e fatica. Solitamente solo progetti veramente grandi possono giustificare questo sforzo.
La descrizione sopra è qualcosa che non è unico nel modo in cui faccio le cose su piattaforme incorporate - mi sono imbattuto in numerose organizzazioni commerciali che fanno simili. Il modo in cui è fatto è di solito molto diverso nell'attuazione, ma i principi sono spesso più o meno gli stessi.
Spero che quanto sopra dia un po 'di sapore ... questo approccio funziona per piccoli sistemi embedded che funzionano in pochi kB con una gestione aggressiva della batteria fino a mostri di 100K o più linee sorgente che funzionano permanentemente. Se si esegue "incorporato" su un sistema operativo di grandi dimensioni come Windows CE o simili, tutto quanto sopra è completamente irrilevante. Ma questa non è una vera programmazione embedded, comunque.