Considere el código Arduino al final de este post para obtener una referencia completa que hace que LED13 parpadee en un ritmo de 0.5Hz impulsado por los temporizadores. Es solo una prueba de concepto, sé que se puede mejorar en varios aspectos, pero nos centramos en el siguiente problema. Aunque en el PoC a continuación solo uso bit0, el objetivo de mi PoC es encontrar una forma sólida para acceder al entero entero de 32 bits fuera del ISR. El sniplet de fuente discutido es este:
void loop() {
digitalWrite( led , secondCounter & 1 );
}
El ISR aumenta el secondsCounter
cada segundo y el led sigue bit0 de ese contador, haciéndolo parpadear.
El listado de desmontaje resultante para el bucle se ve así:
000002ae <loop>:
2ae: 60 91 00 02 lds r22, 0x0200 ; Read secondCounter from memory
2b2: 70 91 01 02 lds r23, 0x0201 ; 4 bytes, 32-vit integer
2b6: 80 91 02 02 lds r24, 0x0202
2ba: 90 91 03 02 lds r25, 0x0203
2be: 61 70 andi r22, 0x01 ; 1
2c0: 77 27 eor r23, r23
2c2: 88 27 eor r24, r24
2c4: 99 27 eor r25, r25
2c6: 8d e0 ldi r24, 0x0D ; 13
2c8: 0c 94 0a 02 jmp 0x414 ; 0x414 <digitalWrite>
Observe que, en segundo plano, las interrupciones de eventos del temporizador se activan todo el tiempo y existe la posibilidad de que una interrupción se repare mientras se lee de la memoria r22-r25. Esto puede dar como resultado que un valor entero dañado se lea de la memoria, pero se puede arreglar fácilmente deshabilitando las interrupciones mientras se lee la variable de la memoria:
void loop() {
noInterrupts(); // Prevent secondCounter from being updated in ISR while being read from memory in main loop.
digitalWrite( led , secondCounter & 1 );
interrupts(); // Enable interrupts
}
Lo que resulta en:
000002ae <loop>:
2ae: f8 94 cli ; Disable interrupts
2b0: 60 91 00 02 lds r22, 0x0200
2b4: 70 91 01 02 lds r23, 0x0201
2b8: 80 91 02 02 lds r24, 0x0202
2bc: 90 91 03 02 lds r25, 0x0203
2c0: 61 70 andi r22, 0x01 ; 1
2c2: 77 27 eor r23, r23
2c4: 88 27 eor r24, r24
2c6: 99 27 eor r25, r25
2c8: 8d e0 ldi r24, 0x0D ; 13
2ca: 0e 94 0d 02 call 0x41a ; 0x41a <digitalWrite>
2ce: 78 94 sei ; Enable interrupts
2d0: 08 95 ret
Pero ahora toda la rutina digitalWrite (que lleva un tiempo relativamente largo) se ejecuta mientras las interrupciones están deshabilitadas y las interrupciones pendientes tienen que esperar mucho tiempo antes de ser reparadas.
Una solución parece ser el uso de una variable ficticia:
void loop() {
noInterrupts(); // Prevent secondCounter from being updated in ISR while being read from memory in main loop.
uint32_t seconds = secondCounter;
interrupts(); // Enable interrupts
digitalWrite( led , seconds & 1 );
}
Lo que resulta en un ensamblaje agradable y limpio:
000002ae <loop>:
2ae: f8 94 cli ; Disable interrupts
2b0: 60 91 00 02 lds r22, 0x0200 ; Read variable from memory
2b4: 70 91 01 02 lds r23, 0x0201
2b8: 80 91 02 02 lds r24, 0x0202
2bc: 90 91 03 02 lds r25, 0x0203
2c0: 78 94 sei ; Enable interrupts
2c2: 61 70 andi r22, 0x01 ; Do other stuff
2c4: 77 27 eor r23, r23
2c6: 88 27 eor r24, r24
2c8: 99 27 eor r25, r25
2ca: 8d e0 ldi r24, 0x0D ; 13
2cc: 0c 94 0c 02 jmp 0x418 ; 0x418 <digitalWrite>
Pregunta : el uso de una variable temporal adicional parece engorroso, ¿existe una solución más inteligente y ordenada para esto?
Intento fallido : intenté crear una función como:
uint32_t atomic_int32( uint32 var ) {
noInterrupts();
uint32_t tmp = var;
interrupts();
return tmp;
}
Combinado con:
digitalWrite( led , atomic_int32( secondCounter ) & 1 );
Pero el compilador 'optimiza' la lectura de la variable de la memoria fuera de las instrucciones cli
y sei
. En realidad, no hay nada entre esas instrucciones:
000002ae <loop>:
2ae: 80 91 00 02 lds r24, 0x0200 ; Read variable from memory
2b2: 90 91 01 02 lds r25, 0x0201
2b6: a0 91 02 02 lds r26, 0x0202
2ba: b0 91 03 02 lds r27, 0x0203
2be: f8 94 cli ; Disable interrupts
2c0: 78 94 sei ; Enable interrupts
2c2: 60 e0 ldi r22, 0x00 ; 0
2c4: 8d e0 ldi r24, 0x0D ; 13
2c6: 0c 94 09 02 jmp 0x412 ; 0x412 <digitalWrite>
Código completo
/*
* (c) J.P. Hendrix
*
* http://blog.linformatronics.nl/213/electronics/timed-1-millisecond-interrupt-routine-for-arduino
*
* Timed interrupt using Timer2
* ISR is called every 1ms
*
* The LED on pin13 will blink in a 0.5Hz rhythm
*/
#define _BC(bit) ( 0 << ( bit ) )
#define _BS(bit) ( 1 << ( bit ) )
// Pin 13 has an LED connected on most Arduino boards.
const uint8_t led = 13;
volatile uint16_t millisecondCounter = 0;
volatile uint32_t secondCounter = 0;
void setup() {
// initialize the digital pin as an output.
pinMode( led , OUTPUT);
// Set up Timer2 for 1 ms interrupts
// - Arduino is clocked at 16MHz
// - The timer is clocked through a /128 prescaler, thus 125kHz (TCCR2)
// - The interrupt is generated when the counter hits 125 (OCR2A)
// at which moment the Timer is reset to 0, resulting in 1kHz intervals
TCCR2A = _BC( COM2A1 ) | _BC( COM2A0 ) | // Normal port operation, OC2A disconnected
_BC( COM2B1 ) | _BC( COM2B0 ) | // Normal port operation, OC2B disconnected
_BS( WGM21 ) | _BC( WGM20 ); // Clear timer on compare match
TCCR2B = _BC( FOC2A ) | _BC( FOC2B ) |
_BC( WGM22 ) | // Clear timer on compare match
_BS( CS22 ) | _BC( CS21 ) | _BS( CS20 ); // prescaler f = clk2 / 128
OCR2A = 125 - 1; // 16MHz / 128 = 125kHz => 125kHz/125 = 1kHz
TCNT2 = 0;
TIMSK2 = _BC( OCIE2B ) | _BS( OCIE2A ) | _BC( TOIE2 );// Enable compare match A interrupts
sei(); // Enable global interrupts
}
// Attach interrupt routine to the Timer Compare Interrupt
ISR( TIMER2_COMPA_vect ) {
millisecondCounter += 1;
if ( millisecondCounter == 1000 ) { // 1000 milliseconds equals 1 second
secondCounter += 1;
millisecondCounter = 0;
}
};
void loop() {
digitalWrite( led , secondCounter & 1 );
}