Secciones críticas sobre Cortex-M3

10

Me pregunto un poco sobre la implementación de secciones de código críticas en un Cortex-M3 donde no se permiten excepciones debido a restricciones de tiempo o problemas de concurrencia.

En mi caso, estoy ejecutando un LPC1758 y tengo un transceptor TI CC2500 a bordo. El CC2500 tiene pines que se pueden usar como líneas de interrupción para datos en el búfer RX y espacio libre en el búfer TX.

Como ejemplo, quiero tener un búfer TX en SRAM de mi MCU y cuando hay espacio libre en el búfer TX del transceptor, quiero escribir estos datos allí. Pero la rutina que pone datos en el búfer SRAM obviamente no puede ser interrumpida por la interrupción de espacio libre en TX. Entonces, lo que quiero hacer es deshabilitar temporalmente las interrupciones mientras realizo este procedimiento de llenado de este búfer, pero las interrupciones que se produzcan durante este procedimiento se ejecutarán una vez que finalice.

¿Cómo se hace esto mejor en Cortex-M3?

Emil Eriksson
fuente

Respuestas:

11

El Cortex M3 admite un par de operaciones de operaciones útiles (comunes en muchas otras máquinas también) llamadas "Load-Exclusive" (LDREX) y "Store-Exclusive" (STREX). Conceptualmente, la operación LDREX realiza una carga, también establece un hardware especial para observar si la ubicación que se cargó podría estar escrita por otra cosa. Realizar un STREX en la dirección utilizada por el último LDREX hará que esa dirección se escriba solo si nada más la escribió primero . La instrucción STREX cargará un registro con 0 si la tienda se llevó a cabo, o 1 si se canceló.

Tenga en cuenta que STREX es a menudo pesimista. Hay una variedad de situaciones en las que podría decidir no realizar la tienda, incluso si la ubicación en cuestión no hubiera sido tocada. Por ejemplo, una interrupción entre un LDREX y un STREX hará que el STREX asuma que la ubicación que se está viendo podría haber sido golpeada. Por esta razón, generalmente es una buena idea minimizar la cantidad de código entre LDREX y STREX. Por ejemplo, considere algo como lo siguiente:

inline void safe_increment (uint32_t * addr)
{
  uint32_t new_value;
  hacer
  {
    nuevo_valor = __ldrex (addr) + 1;
  } while (__ strex (nuevo_valor, addr));
}

que compila algo como:

; Suponga que R0 contiene la dirección en cuestión; r1 destrozado
lp:
  ldrex r1, [r0]
  agregue r1, r1, # 1
  strex r1, r1, [r0]
  cmp r1, # 0; Prueba si no es cero
  bne lp
  .. el código continúa

La gran mayoría de las veces que se ejecuta el código, nada sucederá entre LDREX y STREX para "perturbarlos", por lo que STREX tendrá éxito sin más preámbulos. Sin embargo, si ocurre una interrupción inmediatamente después de la instrucción LDREX o ADD, STREX no realizará el almacenamiento, sino que el código volverá a leer el valor (posiblemente actualizado) de [r0] y calculará un nuevo valor incrementado basado en eso.

El uso de LDREX / STREX para formar operaciones como safe_increment hace posible no solo administrar secciones críticas, sino también, en muchos casos, evitar la necesidad de ellas.

Super gato
fuente
Entonces, ¿no hay forma de "bloquear" las interrupciones para que puedan ser atendidas nuevamente una vez que se desbloquean? Me doy cuenta de que esta es probablemente una solución poco elegante, incluso si es posible, pero solo quiero aprender más sobre el manejo de interrupciones ARM.
Emil Eriksson
3
Es posible deshabilitar las interrupciones, y en el Cortex-M0 a menudo no existe una alternativa práctica para hacerlo. Considero que el enfoque LDREX / STREX es más limpio que deshabilitar las interrupciones, aunque es cierto que en muchos casos realmente no importará (creo que habilitar y deshabilitar terminan en un ciclo cada uno, y deshabilitar las interrupciones durante cinco ciclos probablemente no sea gran cosa) . Tenga en cuenta que un enfoque ldrex / strex funcionará si el código se migra a una CPU multinúcleo, mientras que un enfoque que deshabilita las interrupciones no lo hará. Además, algunos códigos de ejecución de RTOS con permisos reducidos que no pueden desactivar interrupciones.
supercat
Probablemente terminaré con FreeRTOS de todos modos, así que no lo haré yo mismo, pero me gustaría aprender de todos modos. ¿Qué método de deshabilitar las interrupciones debo usar para bloquear las interrupciones como se describe en lugar de descartar cualquier interrupción que ocurra durante el procedimiento? ¿Cómo lo haría si quisiera descartarlos?
Emil Eriksson
No se puede confiar en la respuesta anterior porque al código asociado le falta un paréntesis: while(STREXW(new_value, addr); ¿cómo podemos creer que lo que usted dice es correcto si su código ni siquiera se compila?
@Tim: Lo siento, mi escritura no es perfecta; No tengo el código real que he escrito a mano para comparar, por lo que no recuerdo si el sistema que estaba usando utilizaba STREXW o __STREXW, pero la referencia del compilador enumera __strex como intrínseco (a diferencia de STREXW que está limitado a STREX de 32 bits, los usos intrínsecos de __strex generan un STREXB, STREXH o STREX dependiendo del tamaño del puntero suministrado)
supercat
4

Parece que necesita algunos búferes circulares o FIFO en su software MCU. Al rastrear dos índices o punteros en la matriz para lectura y escritura, puede tener acceso tanto en primer plano como en segundo plano al mismo búfer sin interferencia.

El código de primer plano es libre de escribir en el búfer circular en cualquier momento. Inserta datos en el puntero de escritura, luego incrementa el puntero de escritura.

El código de fondo (manejo de interrupciones) consume datos del puntero de lectura e incrementa el puntero de lectura.

Cuando los punteros de lectura y escritura son iguales, el búfer está vacío y el proceso en segundo plano no envía datos. Cuando el búfer está lleno, el proceso en primer plano se niega a escribir más (o puede sobrescribir datos antiguos, según sus necesidades).

El uso de buffers circulares para desacoplar lectores y escritores debería eliminar la necesidad de deshabilitar las interrupciones.

Toby Jaffey
fuente
Sí, obviamente voy a usar memorias intermedias circulares, pero aumentar y disminuir no son operaciones atómicas.
Emil Eriksson
3
@Emil: No tienen que serlo. Para un búfer circular clásico, con dos punteros y una ranura "inutilizable", todo lo que se necesita es que las escrituras de memoria sean atómicas y se apliquen en orden. El lector posee un puntero, el escritor posee el otro y, aunque ambos pueden leer cualquier puntero, solo el propietario del puntero escribe su puntero. En ese punto, todo lo que necesita son escrituras atómicas en orden.
John R. Strohm
2

No puedo recordar la ubicación exacta, pero en las bibliotecas que provienen de ARM (no TI, ARM, debería estar bajo CMSIS o algo así, utilizo ST pero recuerdo haber leído en alguna parte que este archivo proviene de ARM, por lo que también debería tenerlo ) hay una opción de desactivación de interrupción global. Es una llamada a la función. (No estoy en el trabajo pero mañana buscaré la función exacta). Lo terminaría con un buen nombre en su sistema y deshabilitaría las interrupciones, haría lo suyo y habilitaría de nuevo. Dicho esto, la mejor opción sería implementar un semáforo o una estructura de cola en lugar de la desactivación de interrupción global.

Ktc
fuente