Motivo
Bueno, la razón es simple: el bloqueo es fácil, y a primera vista parece funcionar. Ay de ti si quieres hacer otra cosa, mientras tanto.
Entonces, sin entrar en muchos detalles, ya que no conozco el STM32, generalmente puede solucionar este problema de dos maneras, dependiendo de sus necesidades.
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));
Convertir a no bloqueo
O implementas un tiempo de espera para todos tus while
loops. Esto significa:
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
static unsigned long start = now();
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT) && now()-start < TIMEOUT);
if (now()-start >= TIMEOUT) { return ERROR_TIMEOUT; }
(Esto es pseudocódigo, por supuesto, se te ocurre la idea. Siéntete libre de optimizar o ajustar tus preferencias de codificación según sea necesario.)
Tienes que revisar los códigos de retorno cuando subes la pila y elegir el lugar correcto donde realizas tu tiempo de espera. Tenga en cuenta que también es útil establecer una variable global i2c_timeout_occured=1
o lo que sea para que pueda abortar rápidamente más llamadas I2C sin tener que pasar demasiados argumentos.
Este cambio es bastante indoloro, con suerte.
De adentro hacia afuera
Si, en cambio, realmente necesita hacer otro procesamiento mientras espera ese evento, entonces necesita deshacerse completamente del bucle interior while. Lo haces así:
void main_loop() {
do_i2c_stuff(); // must never block
do_other_stuff();
...
}
// Must never block. Assuming all I2C_... functions do not block either.
void do_i2c_stuff() {
static int state=...;
if (state==0) {
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
state=1;
} else if (state==1) {
if (I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT))
state=2;
} else ...
}
No es necesariamente muy complicado, dependiendo de tu otra lógica. Puedes hacer mucho con el sangrado / comentario / formateo adecuado para que no pierdas de vista lo que estás programando.
La forma en que funciona es creando una máquina de estado . Si miras tu código original, se ve así:
non-blocking code
while (!nonblocking_function_call1());
non-blocking code
while (!nonblocking_function_call2());
Para transformar eso en una máquina de estado, tienes un estado para cada uno:
state 0: non-blocking code
state 1: nonblocking_function_call1()
state 2: non-blocking code
state 3: nonblocking_function_call2()
Luego, como se muestra en el ejemplo anterior, usted llama a este código en un bucle sin fin (su bucle principal), y solo ejecuta el código que coincide con su estado actual (rastreado en una variable estática state
). El código de no bloqueo es trivial, no ha cambiado desde antes. El código de bloqueo se reemplaza por una variación que no bloquea, pero solo actualiza state
cuando finaliza.
Tenga en cuenta que los while
loops individuales se han ido; los ha reemplazado por el hecho de que tiene su bucle principal de nivel superior de todos modos, lo que llama a su máquina de estado repetidamente.
Esta solución puede ser dolorosa cuando tienes muchos códigos heredados, ya que no puedes simplemente adaptar la función de bloqueo más interna, como en la primera solución. Brilla cuando empiezas a escribir código nuevo y sigues así desde el principio. Combínelo con muchas otras cosas que un µC podría hacer (por ejemplo, esperar a que se presione el botón, etc.); Si te acostumbras a hacerlo de esta manera todo el tiempo, obtienes habilidades multitarea arbitrarias de forma gratuita.
Interrupciones
Francamente, para algo como esto (es decir, simplemente deshacerse del bloqueo infinito) me esforzaría por evitar interrupciones a menos que tenga necesidades extremas de tiempo. Las interrupciones hacen que sea complicado, rápido, es posible que no tengas suficientes de todas formas, y de todos modos se reducirá a un código bastante similar, ya que no quieres hacer mucho más dentro de la interrupción, excepto establecer algunas banderas.