¿Existe una buena manera de implementar la comunicación entre un ISR y el resto del programa para un sistema integrado que evite las variables globales?
Parece que el patrón general es tener una variable global que se comparte entre el ISR y el resto del programa y se usa como indicador, pero este uso de variables globales va en contra de mí. He incluido un ejemplo simple con ISR de estilo avr-libc:
volatile uint8_t flag;
int main() {
...
if (flag == 1) {
...
}
...
}
ISR(...) {
...
flag = 1;
...
}
No puedo ver lo que es esencialmente un problema de alcance; ¿alguna variable accesible tanto por el ISR como por el resto del programa debe ser inherentemente global, seguramente? A pesar de esto, a menudo he visto a personas decir cosas como "las variables globales son una forma de implementar la comunicación entre los ISR y el resto del programa" (énfasis mío), lo que parece implicar que existen otros métodos; Si hay otros métodos, ¿cuáles son?
Respuestas:
Hay una forma estándar de facto de hacer esto (suponiendo que la programación C):
extern
palabras clave o simplemente por error.static
. Dicha variable no es global, sino que está restringida al archivo donde se declara.volatile
. Nota: ¡esto no da acceso atómico ni resuelve el encanto!fuente
inline
está volviendo obsoleto, ya que los compiladores se vuelven cada vez más inteligentes al optimizar el código. Diría que preocuparse por la sobrecarga es la "optimización pre-madura"; en la mayoría de los casos, la sobrecarga no importa, si es que incluso está presente en el código de la máquina.Este es el verdadero problema. Superalo.
Ahora, antes de que los que se arrodillan inmediatamente griten sobre cómo esto es inmundo, déjenme calificarlo un poco. Ciertamente existe el peligro de utilizar variables globales en exceso. Pero también pueden aumentar la eficiencia, lo que a veces es importante en sistemas pequeños con recursos limitados.
La clave es pensar cuándo puede usarlos razonablemente y es poco probable que se meta en problemas, frente a un error que está esperando a suceder. Siempre hay compensaciones. Mientras que en general evitar las variables globales para la comunicación entre el código de interrupción y el primer plano es una pauta comprensible, llevarlo, como la mayoría de las pautas, al extremo de las religiones es contraproducente.
Algunos ejemplos en los que a veces uso variables globales para pasar información entre la interrupción y el código de primer plano son:
Me aseguro de que los ticks de 1 ms, 10 ms y 100 ms tengan un tamaño de palabra que se pueda leer en una sola operación atómica. Si usa un lenguaje de alto nivel, asegúrese de decirle al compilador que estas variables pueden cambiar de forma asincrónica. En C, los declaras volátiles externos , por ejemplo. Por supuesto, esto es algo que se incluye en un archivo de inclusión enlatado, por lo que no necesita recordarlo para cada proyecto.
A veces hago que el contador de tics de 1 s sea el contador de tiempo transcurrido total, así que haga que 32 bits de ancho. Eso no se puede leer en una sola operación atómica en muchos de los pequeños micro que uso, por lo que no se hace global. En cambio, se proporciona una rutina que lee el valor de varias palabras, se ocupa de posibles actualizaciones entre lecturas y devuelve el resultado.
Por supuesto que podría haber habido rutinas para obtener los contadores de ticks más pequeños de 1 ms, 10 ms, etc. Sin embargo, eso realmente hace muy poco por usted, agrega muchas instrucciones en lugar de leer una sola palabra y usa otra ubicación de la pila de llamadas.
¿Cuál es el inconveniente? Supongo que alguien podría hacer un error tipográfico que accidentalmente escribe en uno de los contadores, lo que podría estropear otros tiempos en el sistema. Escribir en un mostrador deliberadamente no tendría sentido, por lo que este tipo de error tendría que ser algo involuntario como un error tipográfico. Parece muy improbable. No recuerdo que eso haya sucedido en más de 100 pequeños proyectos de microcontroladores.
Por ejemplo, el A / D puede estar leyendo la salida de 0 a 3 V de un divisor de voltaje para medir el suministro de 24 V. Las muchas lecturas se ejecutan a través de algún filtrado, luego se escalan para que el valor final esté en milivoltios. Si el suministro está a 24.015 V, entonces el valor final es 24015.
El resto del sistema solo ve un valor actualizado en vivo que indica el voltaje de suministro. No sabe ni debe preocuparse cuándo se actualiza exactamente eso, especialmente porque se actualiza con mucha más frecuencia que el tiempo de establecimiento del filtro de paso bajo.
Nuevamente, se podría usar una rutina de interfaz , pero obtienes muy poco beneficio de eso. Simplemente usar la variable global siempre que necesite el voltaje de la fuente de alimentación es mucho más simple. Recuerde que la simplicidad no es solo para la máquina, sino que eso también significa menos posibilidades de error humano.
fuente
extern int ticks10ms
coninline int getTicks10ms()
harán absolutamente ninguna diferencia en el ensamblado compilado, mientras que por otro lado se le hará difícil cambiar accidentalmente su valor en otras partes del programa, y también le permitirá a una forma de "enganchar" a esta llamada (por ejemplo, burlarse del tiempo durante la prueba de la unidad, registrar el acceso a esta variable o lo que sea). Incluso si argumenta que la posibilidad de que un programador de san cambie esta variable a cero, no hay costo de un captador en línea.Cualquier interrupción particular será un recurso global. A veces, sin embargo, puede ser útil que varias interrupciones compartan el mismo código. Por ejemplo, un sistema podría tener varios UART, todos los cuales deberían usar una lógica de envío / recepción similar.
Un buen enfoque para manejar eso es colocar las cosas utilizadas por el controlador de interrupción, o los punteros a ellas, en un objeto de estructura, y luego hacer que los controladores de interrupción de hardware real sean algo como:
Los objetos
uart1_info
,uart2_info
etc. serían variables globales, pero serían las únicas variables globales utilizadas por los manejadores de interrupciones. Todo lo demás que los manipuladores van a tocar se manejaría dentro de ellos.Tenga en cuenta que cualquier cosa a la que acceda tanto el controlador de interrupciones como el código de la línea principal debe estar calificado
volatile
. Puede ser más simple declarar comovolatile
todo lo que será utilizado por el controlador de interrupciones, pero si el rendimiento es importante, es posible que desee escribir código que copie información a valores temporales, los opere y luego los escriba de nuevo. Por ejemplo, en lugar de escribir:escribir:
El primer enfoque puede ser más fácil de leer y comprender, pero será menos eficiente que el último. Si eso es una preocupación dependerá de la aplicación.
fuente
Aquí hay tres ideas:
Declare la variable de marca como estática para limitar el alcance a un solo archivo.
Haga que la variable de marca sea privada y use las funciones getter y setter para acceder al valor de marca.
Use un objeto de señalización como un semáforo en lugar de una variable de bandera. El ISR establecería / publicaría el semáforo.
fuente
Una interrupción (es decir, el vector que apunta a su controlador) es un recurso global. Entonces, incluso si usa alguna variable en la pila o en el montón:
o código orientado a objetos con una función 'virtual':
... el primer paso debe involucrar una variable global real (o al menos estática) para alcanzar esos otros datos.
Todos estos mecanismos agregan una indirección, por lo que esto generalmente no se hace si desea exprimir el último ciclo del controlador de interrupciones.
fuente
Estoy codificando Cortex M0 / M4 en este momento y el enfoque que estamos utilizando en C ++ (no hay una etiqueta C ++, por lo que esta respuesta puede estar fuera del tema) es la siguiente:
Usamos una clase
CInterruptVectorTable
que contiene todas las rutinas de servicio de interrupción que se almacenan en el vector de interrupción real del controlador:La clase
CInterruptVectorTable
implementa una abstracción de los vectores de interrupción, por lo que puede vincular diferentes funciones a los vectores de interrupción durante el tiempo de ejecución.La interfaz de esa clase se ve así:
Debe realizar las funciones que están almacenadas en la tabla de vectores
static
porque el controlador no puede proporcionar unthis
puntero ya que la tabla de vectores no es un objeto. Entonces, para solucionar ese problema, tenemos elpThis
puntero estático dentro delCInterruptVectorTable
. Al ingresar a una de las funciones de interrupción estática, puede acceder alpThis
puntero para obtener acceso a los miembros del único objeto deCInterruptVectorTable
.Ahora en el programa, puede usar el
SetIsrCallbackfunction
para proporcionar un puntero de función a unastatic
función que se llamará cuando ocurra una interrupción. Los punteros se almacenan en elInterruptVectorTable_t virtualVectorTable
.Y la implementación de una función de interrupción se ve así:
Entonces eso llamará a un
static
método de otra clase (que puede serprivate
), que luego puede contener otrostatic
this
puntero para obtener acceso a las variables miembro de ese objeto (solo una).Supongo que podría construir e interactuar
IInterruptHandler
y almacenar punteros en los objetos, por lo que no necesita elstatic
this
puntero en todas esas clases. (tal vez lo intentemos en la próxima iteración de nuestra arquitectura)El otro enfoque funciona bien para nosotros, ya que los únicos objetos permitidos para implementar un controlador de interrupciones son aquellos dentro de la capa de abstracción de hardware, y generalmente solo tenemos un objeto para cada bloque de hardware, por lo que está bien trabajar con
static
this
punteros. Y la capa de abstracción de hardware proporciona otra abstracción a las interrupciones, llamadaICallback
que luego se implementa en la capa de dispositivo sobre el hardware.¿Accede a datos globales? Claro que sí, pero puede hacer que la mayoría de los datos globales necesarios sean privados, como los
this
punteros y las funciones de interrupción.No es a prueba de balas, y agrega gastos generales. Tendrá dificultades para implementar una pila IO-Link con este enfoque. Pero si no está extremadamente apretado con los tiempos, esto funciona bastante bien para obtener una abstracción flexible de las interrupciones y la comunicación en los módulos sin usar variables globales que sean accesibles desde cualquier lugar.
fuente