La mayoría de los artículos que puedo encontrar que son esencialmente "I2C para tontos en el microcontrolador XXX" sugieren bloquear la CPU mientras esperan los eventos de confirmación. Por ejemplo , (los comentarios y la descripción están en ruso, pero eso en realidad no importa). Aquí está el ejemplo de "bloqueo" del que estoy hablando:
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));
Estos while
bucles están ahí después de cada operación, esencialmente. Finalmente, mi pregunta es: ¿cómo implementar I2C sin "colgar" MCU con estos bucles while? En un nivel muy alto, entiendo que tengo que usar interrupciones en lugar de whiles, pero no puedo encontrar ningún ejemplo de código. ¿Puedes ayudarme con eso?
Seguro.
Sin embargo, creo que las dos primeras opciones son probablemente sus mejores apuestas. Sin embargo, mucho depende de cuánto depende del código de la biblioteca escrito por otros. Es muy posible que su código de biblioteca no esté diseñado para dividirse en componentes de nivel superior e inferior. Y también es muy posible que no pueda llamarlos según los eventos del temporizador u otros eventos basados en la máquina de estado.
Tiendo a suponer que si no me gusta la forma en que la biblioteca hace el trabajo (solo bucles de espera ocupados), entonces estoy atascado escribiendo mi propio código para hacer el trabajo. Lo que significa que soy libre de usar cualquiera de los métodos anteriores.
Pero debe observar de cerca el código de la biblioteca y ver si ya tiene funciones que le permitan evitar el comportamiento de espera ocupada. Es posible que la biblioteca tenga soporte para algo diferente. Así que ese es el primer lugar para verificar, por si acaso. De lo contrario, juegue con la idea de usar las funciones existentes como parte de una división de controlador superior / inferior o como un proceso impulsado por una máquina de estado. Es posible que puedas resolver eso también.
No tengo ninguna otra sugerencia que me venga rápidamente a la mente, ahora mismo.
Hay ejemplos en las STM32Cube
bibliotecas. Obtenga el apropiado para su familia de controladores (por ejemplo, STM32CubeF4
o STM32CubeL1
) y busque Examples/I2C/I2C_TwoBoards_ComDMA
en el Projects
subdirectorio.
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, como no conozco el STM32, generalmente puede salir de este problema de dos maneras, según sus necesidades.
I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));
O implementa un tiempo de espera para todos sus while
bucles. 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, entiende la idea. Siéntase libre de optimizar o ajustar sus preferencias de codificación según sea necesario).
Debe verificar los códigos de retorno cuando viaje hacia arriba en la pila y elija el lugar correcto donde realiza el manejo del tiempo de espera. Tenga en cuenta que también ayuda establecer una variable global i2c_timeout_occured=1
o lo que sea para que pueda cancelar rápidamente más llamadas I2C sin tener que pasar demasiados argumentos.
Este cambio es bastante indoloro, con suerte.
Si, en cambio, realmente necesita hacer otro procesamiento mientras espera ese evento, entonces necesita deshacerse del ciclo while interno por completo. 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. Puede hacer mucho con la sangría, los comentarios y el formato adecuados para no perder la noción de lo que está programando.
La forma en que esto funciona es mediante la creación de 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, tiene 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, llama a este código en un ciclo sin fin (su ciclo principal) y solo ejecuta el código que coincide con su estado actual (seguido 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, sino que solo se actualiza state
cuando finaliza.
Tenga en cuenta que los while
bucles individuales se han ido; los ha reemplazado por el hecho de que tiene su bucle principal de nivel superior de todos modos, que llama a su máquina de estado repetidamente.
Esta solución puede ser dolorosa cuando tiene mucho código heredado, ya que no puede simplemente adaptar la función de bloqueo más interna, como en la primera solución. Brilla cuando comienzas a escribir código nuevo y sigues este camino desde el principio. Combínelo con muchas otras cosas que podría hacer un µC (por ejemplo, esperar a que se presione un botón, etc.); si te acostumbras a hacerlo de esta manera todo el tiempo, obtienes habilidades multitarea arbitrarias de forma gratuita.
Francamente, para algo como esto (es decir, simplemente deshacerse del bloqueo infinito) me esforzaría por mantenerme alejado de las interrupciones a menos que tenga necesidades de tiempo extremas. Las interrupciones lo hacen complicado, rápido, es posible que no tenga suficientes de todos modos, y de todos modos se reducirá a un código bastante similar, ya que no desea hacer mucho más dentro de la interrupción, excepto establecer algunas banderas.
while
los bucles. Tengo una pregunta sobre su último ejemplo de código; no estoy seguro de qué propone exactamente. El problema es que I2C_CheckEvent
regresa true
después de un tiempo y no se bloquea, lo que significa que, en general, no es suficiente llamarlo una vez, lo cual (como lo obtuve) sugiere. ¿Puedes por favor explicar un poco sobre eso?while
los bucles infinitos. El primero todavía los tiene pero agrega un tiempo de espera. El segundo se deshace de ellos por completo (suponiendo que tiene un bucle de nivel superior que se ejecuta en un bucle perpetuo, de todos modos). He agregado alguna explicación, según lo solicitado. i2C_CheckEvent se llama a menudo, pero su método regresa inmediatamente, sin importar el resultado, por lo que también le da tiempo a las otras partes de su programa para que se ejecuten.Aquí hay una lista de solicitud de interrupción disponible en un STM32. Como no sé el chip exacto que está utilizando, se recomienda consultar el manual de referencia del suyo si hay alguna diferencia, pero dudo que la haya.
Para permanecer en su código de ejemplo, si necesita una versión sin bloqueo, debe habilitar el evento Bit de inicio enviado (maestro)ITEVFEN
con el bit de control y verificar el SB
indicador de evento dentro del ISR.
Teniendo en cuenta el extracto de @BenceKaulics de una hoja de datos, el pseudocódigo para una rutina de servicio de interrupción (ISR) podría verse así:
i2c_event_isr() {
switch( i2c_event ) {
case master_start_bit_sent:
send_address(...);
break;
case master_address_sent:
case data_byte_finished:
if ( has_more_data() ) {
send_next_data_byte();
} else {
send_stop_condition();
}
break;
...
}
}
jimmyb
scttnlsn