Incorporar con Elliot: interrupciones, el feo

Alana Herrero
Alana Herrero

Bienvenidos a una tercera parte de “Interrupciones: lo bueno, lo malo y lo feo”. Ya hemos confesado nuestro amor por las interrupciones, mostrando cómo sirven para resolver múltiples tareas comunes de microcontroladores con confianza. Eso fue ciertamente bueno. Y luego nos sumergimos en algunos de los problemas de planificación y prioridad que pueden surgir, especialmente si sus rutinas de servicio disruptivas (ISR) se están ejecutando demasiado tiempo o están haciendo más de lo que deberían. Eso fue malo, pero podemos combatir esos problemas escribiendo ISR ligeros.

Esta transmisión, para bien o para mal, descubre nuestro efecto secundario menos favorito de ejecutar interrupciones con un pequeño microcontrolador, y es que sus suposiciones sobre lo que hace su código pueden ser incorrectas y, a veces, con consecuencias desastrosas. Se volverá feo

TL; DR: Una vez que ha comenzado a cambiar las variables de interrupción internas, ya no puede confiar en que sus valores permanecerán constantes; ¡nunca se sabe cuándo ocurrirá la interrupción! La ley de Murphy dice que afectará en los peores momentos. La solución es deshabilitar temporalmente las interrupciones para bloques de código críticos para que sus propios ISR no puedan tirar de la alfombra debajo de sus pies. (Suena fácil, ¡pero sigue leyendo!)

En esta entrega, cubriremos dos caras de esencialmente el mismo problema y mostraremos una solución y media. Pero no se preocupe. Al final de este artículo debes tener la fe para escribir interrupciones sin miedo, porque conocerás al enemigo.

Condiciones de carrera

Si recuerdas de nuestra columna en el volatile palabra clave, cuando desee compartir datos entre ISR y su cuerpo principal, debe utilizar una variable global porque el ISR no puede aceptar argumentos. Dado que el compilador no puede ver que las variables globales cambian en cualquier lugar, las marcamos con el volatile palabra clave para decirle al compilador que no optimice esas variables. Debemos prestar atención a nuestros propios consejos: las variables a las que accede el ISR pueden cambiar en cualquier momento sin previo aviso.

La desventaja “obvia” al compartir variables con ISR es la condición racial: su código establece el valor de la variable aquí y luego lo usa nuevamente allí. La “carrera” en cuestión es si su código puede acelerar lo suficiente desde el punto A al punto B sin que ocurra una interrupción en el medio.

Comencemos con un cuestionario escrito para el microcontrolador AVR:

 
volatile uint16_t raw_accelerometer;
volatile enum {NO, YES} has_new_accelerometer_reading;

ISR(INT1_vector){
	raw_accelerometer = read_accelerometer_z();
	has_new_accelerometer_reading = YES;
}

...

int main(void){
	while (1){

		if (has_new_accelerometer_reading == YES){
			if (raw_accelerometer != 0){
				display(raw_accelerometer);
			}
			// Flag that we've handled this sample
			has_new_accelerometer_reading = NO;
		}
	}
}

Aparentemente, ya hemos leído la última versión de Embed with Elliot, ya que el ISR aquí es corto y hace lo mínimo que necesita. Toda la lógica y el comando de impresión están en el bucle principal donde no causarán problemas por otras posibles interrupciones. ¡Ahora estrella!

El problema, sin embargo, es que vemos valores cero ocasionales impresos en nuestra pantalla. ¿Como puede ser? Solo mostramos el valor del acelerómetro después de demostrar explícitamente que no es cero.

La palabra clave en la oración anterior es “después”. Piense en el peor momento posible para una interrupción de la actualización del acelerómetro raw_accelerometer ¿No es cero, pero antes de imprimir el valor? Sí, eso lo haría.

Por supuesto, la posibilidad de que una interrupción se inicie correctamente durante la última instrucción en la declaración de prueba es relativamente pequeña. No sucederá tan a menudo, ¿verdad? Recordando que su código pasa por el main() bucle muchas veces, si el acelerómetro se actualiza con suficiente frecuencia, puede estar seguro de que el problema vuelo okazi. Solo sucederá con poca frecuencia, y ese es el peor tipo de error que se puede rastrear y crack.

Variables de sombra: solución de prueba

Aquí hay una solución de prueba:

 
int main(void){
	uint16_t local_accelerometer_data = 0;
	while (1){

		if (has_new_accelerometer_reading == YES){
			local_accelerometer_data = raw_accelerometer;
			if (local_accelerometer_data != 0){
				display(local_accelerometer_data);
			}
			// Flag that we've handled this sample
			has_new_accelerometer_reading = NO;
		}
	}
}

¿Ves lo que pasó allí? Agregamos una variable que no se comparte con el ISR, local_accelerometer_data, y luego trabajamos en esa copia en lugar de en la variable que el ISR puede cambiar. Ahora, tanto la prueba de nulidad como la display Se garantiza que la reclamación se basa en el mismo valor.

Esta es solo una solución a medias porque el raw_accelerometer una variable aún puede cambiar mientras estamos trabajando en su instantánea local. Aquí no es un problema, pero si mantenemos nuestra copia local en la variable compartida, correríamos el riesgo de que el raw_accelerometer La variable cambió mientras estábamos trabajando en nuestra copia local, y al escribir nuestra copia local, reemplazaríamos el valor recientemente cambiado. Esto no es trivial, porque perderíamos un punto de datos, pero al menos el trabajo realizado en el local_accelerometer_data es correcto para los datos que teníamos a mano cuando lo copiamos.

Si está trabajando en una fantástica máquina ARM con sus lujosos enteros de 32 bits, esta solución mediana podría funcionar para usted. Para aquellos de nosotros que accedemos a variables comunes de 16 bits en máquinas de 8 bits, hay una complicación más con la que tendremos que lidiar antes de presentar la solución milagrosa a todo esto.

Atomicidad y blues de 8 bits

El atomismo es la propiedad de ser indivisible. (Como lo fueron los átomos hasta principios del siglo XX). Cuando se escribe una sola operación en un lenguaje más avanzado, lata termine de compilar en una instrucción de código de máquina, pero más a menudo se necesitarán algunas instrucciones. Si estas instrucciones se pueden interrumpir para que cambien el resultado, no son atómicas.

La mala noticia: casi nada es atómico. Por ejemplo, incluso algo tan simple como ++i; se puede traducir a tres instrucciones de máquina: una para tomar el valor de i de la memoria a un registro temporal, uno para agregar al registro y un tercero para volver a escribir el resultado en la memoria.

La no atomicidad en ++i; es bastante amable porque el registro temporal funciona igual que la variable de sombra anterior. sí i se cambia en la memoria, mientras que la operación adicional tiene lugar en los registros de la CPU, la memoria será reemplazada por la operación adicional. Pero al menos el valor retenido en i vuelo ser el valor al inicio de la operación más uno.

En una máquina de 8 bits, que maneja variables divididas de 16 bits o más, pueden suceder cosas mucho más extrañas. El acceso a variables de 16 bits es fundamentalmente diferente porque incluso la creación de la versión de variable sombra de la variable no es atómica, toma una instrucción por byte y, por lo tanto, es interrumpible. Para ver esto, tendremos que recurrir a un lenguaje ensamblador y considerar hacer una copia local de nuestro lenguaje común de 16 bits. raw_accelerometer variable.

Para este ejemplo usamos avr-gcc y viene con un montón de herramientas. Una de esas herramientas es avr-objdump que toma el código de máquina compilado y lo desmonta de nuevo a un lenguaje ensamblador “legible”. Específicamente, si compiló el código usando las banderas de depuración (-g) luego la salida de algo como avr-objdump -S myCode.elf > myCode.lst devuelve su código original intercalado con la versión de lenguaje de conjunto del mismo.

 
     local_accelerometer_data = raw_accelerometer;
b8:	80 91 00 01 	lds	r24, 0x0100
bc:	90 91 01 01 	lds	r25, 0x0101

De todos modos, no es necesario ser un gurú del lenguaje ensamblador para ver que lo que pensamos que era una operación necesita dos instrucciones de lenguaje ensamblador diferentes. La primera instrucción copia los ocho bits inferiores nuestros raw_accelerometer datos en el r24 registro de trabajo, y el segundo los ocho bits superiores.

Ahora, ¿cuál es el peor lugar en el que podría llegar el ISR y cambiar la división? raw_accelerometer apreciar de debajo de nuestros pies? Entre los dos lds instrucciones. Cuando el acceso o manipulación de variables requiere más de una máquina / instrucción de ensamblaje, existe otra posibilidad de falla de atomicidad y problemas terribles causados ​​por variables compartidas con ISR. Y especialmente eso rompe nuestra “solución” anterior al problema del atomismo.

Y fíjate lo feo que es esto. Cuando la interrupción golpea entre los dos lds comandos, la copia de la variable incluye los bits bajos de una lectura y los bits altos de un valor completamente diferente. Es un híbrido de los dos valores, un número que nunca se pensó que fuera. Y puede surgir siempre que utilice una variable de 16 bits que se comparte con un ISR.

Hemos preparado un código de muestra para demostrarle el fenómeno. Funcionará con cualquier microcontrolador AVR, incluido un Arduino basado en AVR. El código está escrito para hacer parpadear un LED cada vez que se descubre un valor defectuoso, y termina pareciendo una convención parpadeante, aunque hemos reducido la velocidad de interrupción a una interrupción cada 65,536 ciclos de CPU. ¡Tendremos que arreglar esto!

Flanken de Arduino

El peor fallo de atomicidad anterior se produce al compartir una variable de 16 bits con el ISR en una máquina de 8 bits. Si solo tuviéramos que leer ocho bits del acelerómetro, la solución de “variable de sombra” funcionaría bien. Repetimos esto aquí porque vemos una gran cantidad de 16 bits ints utilizado en el código Arduino cuando un tipo de datos más corto sería suficiente.

Y no culpamos a los usuarios de Arduino: la mayoría de los códigos de ejemplo integrados de Arduino utilizan int para todo, incluidos los números de PIN. El de 16 bits int en AVR Arduino tiene un rango de -32 768 a 32 767. Seguramente es un buen futuro para cuando Arduinos tiene más de 255 pines, pero no podemos imaginar la necesidad de acceder al pin -32,000, lo que sea que eso signifique. La razón fundamental detrás del uso exclusivo int porque todo lo que asumimos es que aprender sobre diferentes tipos de datos es difícil.

Si fuera solo una cuestión de desperdiciar RAM o ciclos de CPU, estaría bien. Pero usando ints significa implícitamente que introduce todo tipo de no átomos en su código sin saberlo. Tan pronto como agregue interrupciones a la mezcla, como puede ver aquí, es una receta para el desastre: no use números de 16 bits en una máquina de 8 bits a menos que sea necesario.

Entonces, ¿cómo funciona algo en Arduino? Las bibliotecas (en su mayor parte) ya no están escritas de manera tan tosca / ingenua (ya). De hecho separaremos el millis() funcionar una vez que hayamos resuelto nuestros problemas atómicos de una vez por todas. Verá que hace exactamente lo correcto.

Finalmente, el verdadero atomismo

En resumen: las variables que se comparten con un ISR pueden cambiar sin previo aviso, y hacer una copia local de la variable solo resuelve la mitad del problema, porque las copias locales solo funcionan cuando la operación para hacer la copia es atómica. ¿Cómo podemos asegurarnos de que las interrupciones no cambien nuestras variables mientras no estamos mirando? La respuesta es sorprendentemente simple: apague las interrupciones.

De hecho, ni siquiera necesitábamos preocuparnos por hacer de la sombra una variable local si solo queríamos activar y desactivar las interrupciones.

 
int main(void){
	while (1){

		if (has_new_accelerometer_reading == YES){
			
			cli();  // clears the global interrupt flag
		
			if (raw_accelerometer != 0){
				display(raw_accelerometer);
			}
			// Flag that we've handled this sample
			has_new_accelerometer_reading = NO;

			sei();  // re-enables interrupts
		}
	}
}

Bastante simple y funciona. Diablos, en el AVR, los comandos de activación / desactivación de interrupciones se traducen directamente en código de máquina que solo dura un ciclo a la vez. Difícil de superar eso. A menos que desactive las interrupciones durante un tiempo relativamente largo.

Pero luego podríamos usar el truco de copia de variable local:

 
int main(void){
	uint16_t local_accelerometer_data = 0;
	while (1){
		if (has_new_accelerometer_reading == YES){
			
			cli();  // clears the global interrupt flag
			local_accelerometer_data = raw_accelerometer;
			sei();  // re-enables interrupts
			
			if (local_accelerometer_data != 0){
				display(local_accelerometer_data);
			}
			// Flag that we've handled this sample
			has_new_accelerometer_reading = NO;
		}
	}
}

Ahora la asignación de la variable local solo necesita protección, por lo que la interrupción solo se deshabilita durante unos pocos ciclos. Eso da bastante miedo. (Una vez más, recuerde la advertencia sobre los datos “sin procesar” a continuación inactivos con la copia local).

Millones de Arduino

Una cosa que nuestro código anterior no hizo fue verificar si las interrupciones se configuraron primero. Simplemente asumimos que las interrupciones funcionaron, y después de terminar nuestra sección crítica, las reemplazamos. Pero si no funcionaron primero, cambiamos algo sin querer. La solución es registrar el valor del registro de estado, SREG, que contiene el bit de habilitación de interrupción global y lo restaura después del final de la sección crítica.

Como se prometió, esta es la rutina de suma de milisegundos de la biblioteca Arduino. (Está en “cableado.c” si está interesado).

 
unsigned long millis()
{
        unsigned long m;
        uint8_t oldSREG = SREG;

        // disable interrupts while we read timer0_millis or we might get an
        // inconsistent value (e.g. in the middle of a write to timer0_millis)
        cli();
        m = timer0_millis;
        SREG = oldSREG;

        return m;
}

Tenga en cuenta que hace las cosas correctas por las razones correctas, eso es suyo El código de tiempo de Arduino realmente funciona. Primero, copia el valor de SREG que contiene el antiguo bit de habilitación disruptiva global. Luego crea una copia local de las calculadoras de milisegundos actuales (ISR-compartido). Finalmente, el SREG y devuelve el número de milisegundos. Bien hecho.

Bloques atómicos

Hacer una solución de propósito general para proteger secciones tan críticas resulta un poco difícil. Como mínimo, nos gustaría copiar con el SREG contenido como en millis(). La biblioteca AVR se titula “util / atomic.h”, que define la ATOMIC_BLOCK un sobre que básicamente hace eso por nosotros.

Hay dos posibles argumentos, ATOMIC_RESTORESTATE y ATOMIC_FORCEON, y responden a los dos casos cuando el bloque comprende primero si el vector de interrupción global fue previamente o simplemente asume que lo fue y lo activa respectivamente.

Y ATOMIC_BLOCK es en realidad más inteligente de lo que hemos comentado hasta ahora. Incluye un truco de código que te permite usar return o break sentencias dentro del bloque, y todavía se encarga de restaurar la bandera de interrupción global por usted.

Por ejemplo, aquí está Arduino millis() reescrito para aprovechar la biblioteca estándar GCC AVR:

 
unsigned long millis(){
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
	    return timer0_millis;
    }
}

Eso es mucho más claro que el original, en nuestra opinión. Usando ATOMIC_BLOCK le ahorra muchas molestias y facilita la lectura del código para comenzar. En realidad, si desea mantener la llamada, puede usar timer0_millis usted mismo de su rutina principal siempre y cuando lo envuelva en ATOMIC_BLOCK:

 
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
    if (timer0_millis > alarm_time){
        do_whatever();
    }
}

El último refinamiento posible

Si usted es una de esas personas de optimización, notará que solo un ISR en particular comparte nuestra variable con la sección crítica. En lugar de deshabilitar todas las interrupciones, podría imaginar deshabilitar solo la interrupción particular que está causando el problema.

Esto también nos volverá atómicos, y no podemos encontrar ninguna razón para no hacer eso que no sea la complejidad del código. Significa renunciar al único ATOMIC_BLOCKmanipulaciones de interrupciones globales de estilo, pero probablemente valga la pena si tiene límites en tiempo real para otras interrupciones que no desea bloquear. Sin embargo, prácticamente hemos visto muchas disrupciones globales. ¿A algún lector le importaría llamar con algunos ejemplos del mundo real en los que solo se desactivaron interrupciones específicas para proteger una sección nuclear crítica?

Conclusión

Este fue un viaje épico a través del tema de las interrupciones, y esperamos que lo haya disfrutado. Las interrupciones son la herramienta más poderosa en el arsenal de microcontroladores porque responden directamente al problema único de las aplicaciones de microcontroladores: la interacción con periféricos del mundo real. Las interrupciones maestras lo llevan de un principiante a un microcontrolador completamente al campo medio, por lo que vale la pena.

Las interrupciones también introducen algunas tareas múltiples, lo que trae consigo problemas como la planificación y las prioridades (lo malo) y las fallas nucleares y las condiciones raciales (lo feo). Conocer estas cosas es la mitad de la batalla. ¿Existen otras grandes dificultades con el uso de interrupciones que no hemos detectado? ¿Cuáles son tus historias de terror disruptivas favoritas? Publica en los comentarios.

  • El gran dice:

    ¡Gracias! Anteriormente había encontrado variables de 16 bits en ISR (y las resolví de varias maneras según el caso), pero no había encontrado previamente la macro ATOMIC_BLOCK. ¡Eso parece bastante útil!

    Hola

  • jaromirs dice:

    ¿Dónde está como un botón para artículos? Elliot hace un buen trabajo con sus artículos.

    • El gran dice:

      +1 – ¡Siempre espero con ansias sus artículos!

    • Cuéntalo dice:

      De acuerdo, estos son tan importantes para mí que ahora dan miedo … Solo necesito uno para los temporizadores * tos, tos Elliot * :). Ecuación de cómo encontrar el tiempo es 1 /[ (sysclock frequency)/(prescaler)/(if8bit->256, if16bit->65536) ]. Me tomó una eternidad conseguir eso jajaja.

  • Iván dice:

    ¡Estas publicaciones son realmente buenas! Gracias Elliot.

  • AussieLauren dice:

    ¿Es la razón principal por la que atomic_block no se usa para toda la longitud de instrucción variable común de 16 bits? (En algún lugar llama alrededor de 4 códigos de ensamblaje adicionales para leer y restablecer el registro de interrupciones)

    ¿Hay alguna manera de hacer esto automáticamente en el compilador si solo se produce la garganta de variables compartidas y tanto el interior como el exterior de los ISR se ven afectados?

  • hephaisto dice:

    has_new_accelerometer_reading = NE;
    Esto probablemente debería hacerse directamente antes / después de copiar el valor, para evitar perder más muestras durante el paso de la pantalla …

  • Jacques1956 dice:

    En su último códice debe mover: ‘has_new_accelerometer_reading = NO;’ antes de ‘sei;’ de lo contrario, podría faltar un punto de fecha.

    • Elliot Williams dice:

      No hice ningún intento aquí para evitar la falta de un punto de datos de acelerómetro; este código solo elimina las observaciones si llegan demasiado rápido para ser manejadas.

      La forma “correcta” de resolver este problema nos lleva a otro tema: el búfer. Otras personas en los comentarios también captaron esto. Escribiré eso la semana que viene.

  • Radu Motisan dice:

    Artículo bien compuesto.

  • Benji Wiebe dice:

    Artículo muy interesante y útil. Pero, ¿le importaría eliminar el> en ‘if (timer0_millis> alarm_time) {‘ en su último ejemplo de código?

    • Benji Wiebe dice:

      Oh, quise decir que necesitas arreglar el> a>

  • rasz_pl dice:

    meh, solo usa mutex, problema resuelto

    • Rud Merriam dice:

      Mutex requiere un sistema operativo.

      • rasz_pl dice:

        no, es solo un semáforo, construye el tuyo

    • Dan Collins dice:

      Si deja que la interrupción esté habilitada y “simplemente usa mutex”, es probable que se encuentre con una situación de bloqueo. Si la aplicación toma el mutex y luego se interrumpe, el ISR intentará sin éxito obtener el mutex. En este punto, el ISR puede descartar los datos y regresar o puede esperar el mutex. Esperar a que la aplicación (que está “en pausa” durante el ISR) devuelva el mutex bloqueará el sistema.

    • tekkieneet dice:

      El flujo serial del búfer en IRQ son problemas resueltos con soluciones conocidas que utilizan secuencias circulares.
      Si necesita funciones RTOS más avanzadas, use o aprenda a usar el RTOS apropiado. De cualquier manera, necesitará aprender algo nuevo.

      • Elliot Williams dice:

        ¡Esta!

        La próxima entrega será en búferes (circulares), así que tenga cuidado.

  • Rud Merriam dice:

    Como otro ejercicio para los lectores, considere cómo manejar la entrada de un giroscopio. Los datos son el cambio de orientación y hay que acumular los cambios para obtener la orientación. No se puede perder un punto de datos. ¿Cómo ayuda la solución final aquí con esa situación?

    • Quinto dice:

      Supongo que primero pensé que haría el ISR algunas matemáticas x + = giroscopio (x); y + = giroscopio (y) etc. donde x, y, z, k … son los más grandes accesibles atómicamente; luego permita que el código principal acceda a los globales en modo de solo lectura. Si el bucle principal necesitaba alguna copia local, puede mantener sus propias copias actualizadas de los ISR utilizados en todo el mundo. Sin intentar o extraer mis notas del libro del dragón, esto debería evitar que las copias del bucle principal reciban valores erróneos. Además, el bucle principal podría verificar razonablemente los valores si fuera necesario, ya que las lecturas breves de int-title no deben estar fuera de ningún rango de tipo 0-360.

      • asdf dice:

        ¿El “libro del dragón”? ¿Te importa hacer ejercicio?

        • Andrés dice:

          ¿Google abajo de nuevo?
          https://en.wikipedia.org/wiki/Dragon_Book

          (Lo dejo como ejercicio para que el lector decida de quién estamos hablando aquí).

      • rasz_pl dice:

        > si el ISR hace los pequeños cálculos

        BZZ, nunca hagas eso

    • tekkieneet dice:

      El ISR no tiene que hacer matemáticas en absoluto. Como mucho, debería almacenar en búfer los datos sin procesar y dejar que su código regular maneje las matemáticas. Parece un búfer de datos en serie.

      También se ha abierto a otro número con bibliotecas matemáticas o cualquier biblioteca a la que llame por irq y su código principal. Las bibliotecas deberían compilarse mejor si utilizan algún tipo de almacenamiento interno.

    • Miguel dice:

      simplemente use el enfoque de variable simulada, asumiendo que el código principal funciona con bastante rapidez, de lo contrario, ¿es un búfer Fifo?

    • Guenthert dice:

      Olvidé las interrupciones y use la votación, de manera más elegante con una CPU de varios núcleos (por ejemplo, Helix, XMOS).

      • tekkieneet dice:

        que funciona hasta que se acaba …

      • Elliot Williams dice:

        Nunca usé un Helix, y una vez debería. Realmente suenan divertidos.

        ¿Cómo tratan varios núcleos los recursos comunes, como los puertos de E / S comunes?

  • Tomás dice:

    Para aplicaciones simples como leer un valor de ADC o acelerómetro y mostrarlo, existe otra alternativa en contra de su consejo sobre una “pequeña rutina de servicio de interrupciones”: lo hace todo con interrupciones.
    p.ej. ADC tiene un nuevo valor, genera una interrupción que retiene el valor. De forma asincrónica, un temporizador de hardware de ejecución libre expira, lo que genera una interrupción y muestra el valor actual. El bucle principal está vacío, las interrupciones no tienen prioridad. Bueno … lo son, si solo tiene un vector ISR. Depende del orden de prueba para el que se produjo una interrupción al comienzo del ISR.

  • madscifi dice:

    Hay dos variables compartidas entre los contextos principal y de interrupción en el ejemplo principal anterior: has_new_accelerometer_reading también debe ser inestable.

    La otra cosa a tener en cuenta es que los tipos de enumeración también son de 16 bits en este entorno (a menos que se usen indicadores de compilador específicos) y cómo tales cargas y almacenes de / a tipos de enumeración no son atómicas. Siempre que uno de los bytes sea * siempre * un valor fijo, como en los ejemplos anteriores, esto funcionará, pero no es seguro tratar los tipos enumerados como atómicos en el caso general.

  • madscifi dice:

    De hecho, hay dos variables compartidas entre los contextos principal y de interrupción en el ejemplo principal anterior: has_new_accelerometer_reading también debe ser inestable.

    La otra cosa a tener en cuenta es que los tipos de enumeración suelen ser de 16 bits en este entorno y, como tal, los tipos get / sets de tipos de enumeración no son atómicos. Si uno de los bytes es * siempre * fijo, esto puede funcionar, pero no es seguro en el caso general.

    • Moryc dice:

      Nunca usé enumeraciones para eso. Si necesito banderas, simplemente hago una señal o la acorto y uso sus piezas como banderas que se pueden nombrar, configurar o vaciar tal como se usan las banderas de registro. Entonces, si necesito 8 indicadores diferentes, no necesito 8 variables, sino solo una. Descubrí este método en microPascal mientras programaba un microcontrolador de recursos limitados.

      • Mj dice:

        ¡Oh, sobre las banderas de bits! En los micrófonos AVR8 hay una forma elegante de obtener algunas optimizaciones realmente efectivas del juego de bits. Si puede encontrar un registro HW disponible y no utilizado en una dirección inferior a 0x20, usted (y / o el compilador) pueden usar una declaración CBI / SBI para establecer y borrar banderas individuales; enfoque nuclear instruccional único, sin cambios ni enmascaramiento.

        Esto puede ahorrar suficiente espacio de código y ejecutarse si agita muchas banderas entre ISR o ISRmain.

        Algunos de los ATmegas más nuevos han agregado algunos “Gobiernos generales” en el espacio SFR para tales fines, pero incluso con MCU más antiguos, generalmente puede encontrar algún registro que no se utiliza en su aplicación particular (por ejemplo, UART o I2C-índice de bud o registros de datos , dirección eeprom o registro de datos, etc.). Solo asegúrese de que sea una unidad periférica sin usar, y que no se debe usar, que tenga acceso completo de R / W a los bits y que no se produzcan efectos de HW no deseados al usarlos.

    • Elliot Williams dice:

      La otra variable común: Verdadero. UPS. Refacción.

      Enumeraciones: siempre compilo con el indicador de enumeración de 8 bits en AVR-GCC, pero tiene razón en general en que las enumeraciones suelen tener el tamaño de int en C, entonces 16 bits en AVR.

      CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums FTW.

    • tekkieneet dice:

      Especifico explícitamente el tamaño de mis variables, p. Ej. Uint8_t para 8 bits sin nombre en lugar de intentar calcular qué tamaño tiene el enumerador en un determinado compilador para evitar sorpresas. Declarar las variables usando el tipo de enumeración “agradable” abstrae la implementación y puede causar problemas aquí si el compilador decide usar 16 bits …

  • Charles dice:

    ¿Puede ISR cambiar SREG? Si es así, ¿qué pasa si en la función millis () el ISR cambia el contenido de SREG después de “oldSREG = SREG”?

    • Garbz dice:

      Los ISR no solo pueden cambiar el SREG, sino que a menudo lo hacen. SREG contiene toda la información sobre transporte y excedentes en funcionamiento. La idea de que ISR no debería estropear el código principal es exactamente la razón por la que el compilador generará un prólogo y un epílogo cuando llamas a ISR, lo que guarda SREG y luego deshabilita las interrupciones globales y lo invierte eventualmente. De lo contrario, muchas operaciones tendrían un resultado que afecta a SREG y, a su vez, puede afectar el código principal, no solo el valor, sino también cosas como los bucles que a menudo dependen de instrucciones que controlan si la bandera de cero está activada o no.

      Existe cierto control sobre este comportamiento. Puede declarar un ISR (interrupción, ISR_NOBLOCK) cuando se define una interrupción y no vaciará la bandera de interrupción global permitiendo que su interrupción sea anulada por una interrupción de mayor prioridad. O puede declarar un ISR (interrupción, ISR_NAKED) y el compilador no generará un prólogo ni un código de epílogo. Este es un comportamiento bastante peligroso y conduciría exactamente al problema que está describiendo. Pero, en general, eso no debería hacerse a menos que esté realmente seguro de lo que está haciendo.

      • Charles dice:

        Gracias !! ¡Me acabas de ahorrar muchos problemas! Honestamente, me olvidé por completo de las banderas aritméticas establecidas en ISR contra los bucles en el código principal. Las hojas de datos de AVR advierten sobre el mantenimiento de un registro de estado en general, pero este es un caso especial para errores terribles.

  • roliop dice:

    ¿El algoritmo de Dekker ayuda en algo?

  • enfermedad dice:

    Para las sociedades que hemos creado en el pasado, se agrega un registro llamado Irq_suppress. Escribe un recuento de ciclos en él y para N ciclos posteriores, las interrupciones se bloquean. Después de la cuenta regresiva de N, regresan a su estado anterior desbloqueado. Útil para pequeñas manipulaciones de controles críticos.

  • Gerben dice:

    No creo que el último refinamiento posible siempre funcione. CLI evita las interrupciones, pero, no obstante, las “alineará”. Una vez que se llama a SEI, se siguen llamando a los ISR correspondientes.

    La desactivación de una interrupción específica causará interrupciones perdidas si la interrupción (ocurre) entre la desactivación y la reactivación de la interrupción específica.

    En algunos casos esto no importa, mientras que en otros podría ser un problema.

    • dr bob bob dice:

      Depende de la arquitectura del procesador. No me ocupé demasiado de las interrupciones de AVR, pero pasé bastante tiempo jugando con las interrupciones de pizza.

      En la imagen 18 tiene una habilitación global y una bandera global junto con una habilitación y una bandera para las fuentes de interrupción individuales. Cuando se apaga la bandera de habilitación para una fuente de interrupción, la bandera activada seguirá en pie. Cuando se activa la bandera de habilitación si hay una interrupción pendiente para esa fuente, lanzará el ISR. Debe tener un poco de cuidado al habilitar la interrupción si usó un dispositivo en modo de votación antes de cambiar a interrupciones.

  • Miguel Colina dice:

    Kara Elliot,

    Muchas gracias por el articulo La desactivación de todas las interrupciones puede distorsionar otros procesos importantes. Un simple semáforo puede ayudar. Revise las modificaciones agregadas.

    Más amigable,

    Miguel.

    *****

    volátil uint16_t raw_accelerometer;
    enum {NO, YES} has_ ​​a new_accelerometer_reading;

    ISR (INT1_vector) {
    if (has_new_accelerometer_reading == NO) {// se permite el muestreo (luz verde)
    raw_celcelometer = read_accelerometer_z (); // leer una muestra
    has_new_accelerometer_reading = SÍ; // el muestreo debe esperar (luz roja)
    }
    }

    int main {
    has_new_accelerometer_reading = NE; // permitir muestreo (luz verde)
    dum (1) {
    if (has_new_accelerometer_reading == YES) {// muestra disponible
    if (raw_cellometer! = 0) {
    display (raw_accelerometer);
    }
    // Tenga en cuenta que manejamos esta muestra
    has_new_accelerometer_reading = NE; // permitir más muestreo (luz verde)
    }
    }
    }

  • Jason Cerundolo dice:

    Esta es una gran escritura. Tengo casi todo aquí en un momento u otro. Me alegra tener esto como un medio para señalar cuando mis amigos tienen problemas similares.

  • Leonard dice:

    los bucles principales son, por lo tanto, 2077

  • rewolff dice:

    Nota: Elliot parece tener mucha experiencia integrada. Hay muchas formas de escribir estos pequeños fragmentos de código de tal manera que corrompan la estructura del búfer cuando, por ejemplo, Las interrupciones llenan el búfer y el código de usuario lo vacía. Elliot simplemente lo escribe de tal manera que no se necesita ningún candado.

  • Eugenio dice:

    Gracias por esta reseña, Elliot.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *