Motivo
Bene, il motivo è semplice: bloccare è semplicemente facile e a prima vista sembra funzionare. Guai a te se intanto vuoi fare qualcos'altro.
Quindi, senza entrare in molti dettagli, poiché non conosco l'STM32, in genere puoi risolvere questo problema in due modi, a seconda delle tue esigenze.
I2C_GenerateSTART (HMC5883L_I2C, ENABLE);
/ * attendi conferma * /
while (! I2C_CheckEvent (HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));
Converti in non bloccante
O implementi un timeout per tutti i tuoi cicli while
. Ciò significa:
I2C_GenerateSTART (HMC5883L_I2C, ENABLE);
/ * attendi conferma * /
static unsigned long start = now ();
while (! I2C_CheckEvent (HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT) && now () - avvia < TIMEOUT);
if (now () - start > = TIMEOUT) {return ERROR_TIMEOUT; }
(Questo è ovviamente uno pseudocodice, hai un'idea. Sentiti libero di ottimizzare o adattare le tue preferenze di codifica, se necessario.)
Devi controllare i codici di ritorno quando sali sulla pila e scegliere il punto corretto in cui eseguire la gestione del timeout. Nota che aiuta anche impostare una variabile globale i2c_timeout_occured = 1
o qualsiasi altra cosa in modo da poter interrompere rapidamente ulteriori chiamate I2C senza dover passare troppi argomenti.
Questo cambiamento è piuttosto indolore, si spera.
Dentro e fuori
Se, invece, hai davvero bisogno di fare altre elaborazioni mentre aspetti quell'evento, allora devi sbarazzarti completamente del ciclo while interno. Lo fai in questo modo:
void main_loop () {
do_i2c_stuff (); // non deve mai bloccare
do_other_stuff ();
...
}
// Non deve mai bloccare. Supponendo che nemmeno tutte le funzioni I2C _... si blocchino.
void do_i2c_stuff () {
stato int statico = ...;
if (state == 0) {
I2C_GenerateSTART (HMC5883L_I2C, ENABLE);
stato = 1;
} else if (state == 1) {
if (I2C_CheckEvent (HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT))
stato = 2;
} altro ...
}
Non è necessariamente così complicato, dipende dalla tua altra logica. Puoi fare molto con il corretto rientro / commento / formattazione in modo da non perdere traccia di ciò che stai programmando.
Il modo in cui funziona è creare una macchina a stati . Se guardi il tuo codice originale, sembra questo:
codice non bloccante
while (! nonblocking_function_call1 ());
codice non bloccante
while (! nonblocking_function_call2 ());
Per trasformarlo in una macchina a stati, hai uno stato per ciascuno:
stato 0: codice non bloccante
stato 1: nonblocking_function_call1 ()
stato 2: codice non bloccante
stato 3: nonblocking_function_call2 ()
Quindi, come mostrato nell'esempio sopra, chiami questo codice in un ciclo infinito (il tuo ciclo principale) ed esegui solo il codice che corrisponde al tuo stato corrente (tracciato in una variabile state
statica) . Il codice non bloccante è banale, non è cambiato rispetto a prima. Il codice di blocco è sostituito da una variante che non blocca, ma aggiorna solo lo state
al termine.
Nota che i singoli cicli while
sono spariti; li hai sostituiti dal fatto che hai comunque il tuo ciclo principale di livello superiore, che chiama ripetutamente la tua macchina a stati.
Questa soluzione può essere dolorosa quando si dispone di molto codice legacy poiché non è possibile adattare semplicemente la funzione di blocco più interna, come nella prima soluzione. Risplende quando inizi a scrivere codice nuovo e vai in questo modo dall'inizio. Combinalo con molte altre cose che un µC potrebbe fare (ad esempio, attendere la pressione dei pulsanti, ecc.); se ti abitui a farlo in questo modo tutto il tempo, ottieni gratuitamente abilità multitasking arbitrarie.
Interruzioni
Francamente, per qualcosa di simile (cioè, sbarazzarmi del blocco infinito) farei del mio meglio per stare lontano dalle interruzioni a meno che tu non abbia esigenze di tempismo estreme.Gli interrupt lo rendono complicato, veloce, potresti non averne abbastanza comunque, e si ridurrà comunque a codice abbastanza simile, poiché non vuoi fare molto di più all'interno dell'interrupt se non impostare alcuni flag.