Consider the Arduino code at the end of this post for full reference that makes LED13 blink in a 0.5Hz rhythm driven by timers. It is just a proof of concept, I know it can be improved for various aspects, but lets focus on the following issue. Although in the PoC below I only use bit0, the goal of my PoC is to find a robust way to access the full 32-bit integer outside the ISR. The source sniplet discussed is this:
void loop() {
digitalWrite( led , secondCounter & 1 );
}
The ISR increases the secondsCounter
every second and the led follows bit0 of that counter, making it blink.
The resulting disassembly listing for the loop looks like this:
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>
Notice that in the background Timer event interrupts are fired all the time and there is a possibility that an interrupt is serviced while r22-r25 are being read from memory. This may result in a corrupted integer value being read from memory, but it can easily be fixed by disabling interrupts while the variable is read from memory:
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
}
Which results in:
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
But now the entire digitalWrite routine (which takes a relatively long time) is executed while interrupts are disabled and pending interrupts have to wait a long time before being serviced.
A solution seems to be the use of a dummy variable:
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 );
}
Which results in nice and clean assembly:
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>
Question: The use of an extra temporary variable seems cumbersome, is there a smarter and tidier solution to this?
Failed attempt: I tried creating a function like:
uint32_t atomic_int32( uint32 var ) {
noInterrupts();
uint32_t tmp = var;
interrupts();
return tmp;
}
Combined with:
digitalWrite( led , atomic_int32( secondCounter ) & 1 );
But the compiler 'optimizes' the reading of the variable from memory outside the cli
and sei
instructions. Actually there is nothing in between those instructions at all:
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>
Full code
/*
* (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 );
}