Hai una CPU single-core, single-threaded. Ciò significa che in qualsiasi momento sta facendo esattamente una cosa. Se l'applicazione richiede che esegua diverse operazioni, il codice deve essere progettato per passare da una all'altra. Può essere banale o complesso a piacere.
Normalmente, la CPU gira intorno al ciclo principale, facendo tutto quello che c'è dentro, e va bene fino a quando non si verifica un interrupt. L'hardware di interrupt fondamentalmente forza una chiamata di funzione all'ISR appropriato, anche se non ci sono istruzioni di chiamata per esso nel ciclo principale. Questa imprevedibilità è l'origine della maggior parte delle regole per scrivere ISR.
Il tempo che trascorri in un ISR è il tempo in cui il ciclo principale viene messo in pausa, in attesa del ritorno dell'ISR. Se il ciclo principale è l'unico a reimpostare un timer watchdog attivo (ottima pratica), il watchdog non verrà reimpostato durante quel periodo. Se il watchdog va in timeout, ti dà un hard reset. Proprio come il reset esterno, ma con flag diversi che puoi controllare all'avvio. Questo è probabilmente il "crash" di cui hai sentito parlare.
È una buona pratica utilizzare il watchdog e reimpostarlo solo una volta per ogni viaggio attorno al loop principale. Questo ti costringe a scrivere codice che rimanga reattivo. Se hai bisogno di aspettare qualcosa, puoi impostare un evento (timer finito, personaggio successivo ricevuto, ecc.) E andare avanti. Controlla periodicamente quell'evento o imposta un altro interrupt per il suo completamento, quindi torna ad esso. Nel frattempo, continui con qualsiasi altra cosa stavi facendo.
La mia struttura principale è tipicamente qualcosa del genere:
#include "module1.h"
#include "module2.h"
void main (void)
{
//complessivamente
//patata fritta
//impostare
mod1_init ();
mod2_init ();
// cancella i flag di interrupt
// abilitazione dell'interrupt globale
mentre (1)
{
// clear watchdog
mod1_run ();
mod2_run ();
}
}
E i miei moduli sono così:
void modX_init (void)
{
// inizializzazione hardware e variabile solo per questo modulo
// non usare interrupt se il polling è abbastanza buono
}
void modX_run (void)
{
if (POLLED_INTERRUPT_FLAG)
{
POLLED_INTERRUPT_FLAG = 0;
// codice "ISR" non bloccante
}
}
void ISR modX_ISR (void)
{
// ok, questo richiede una risposta * immediata *
// trascorri qui il tempo minimo assoluto e vattene
}
Le firme delle funzioni non devono essere void
, ma la maggior parte di esse lo sono. A volte avrò una tempistica ampia in un modulo che è usato anche da un altro, ed è utile usare il valore di ritorno di un modX_run ()
e gli argomenti di un altro (o una logica di base) per fare quella connessione. Ad esempio:
if (DMX_run ()) // include la propria temporizzazione e restituisce true all'inizio di ogni intervallo di 30 Hz, altrimenti false
{
I2C_start (); // I frame I2C vengono sincronizzati con DMX
}
I2C_run (); // una volta avviato, un frame I2C gira liberamente fino al termine
Se studi la scheda tecnica, potresti anche scoprire che le periferiche hardware possono essere massaggiate per fare ciò che desideri senza alcun intervento da parte della CPU.
La generazione di impulsi in uscita, ad esempio, è comune. Accendilo, imposta la periferica per spegnerlo qualche tempo dopo e dimenticalo. Di solito si trova nella stessa area generale del PWM.