Documentation:AVR8 Tutorials--Atomic 16-Bit Transfers

From AVRFreaks Wiki

Jump to: navigation, search

This tutorial is a brief discussion of the pitfalls of non-atomic variable modification in the AVR8.

[edit] Problem

One of the surprises programmers of other 8-bit microcontrollers experience when they begin using the AVR8 is the lack of atomic 16-bit register-memory transfer capability.

Specifically, when operating on a 16-bit variable in memory, the AVR8 is forced to perform the operation with many successive instructions rather than just one. This is not a problem unless the following two things happen:

  1. The processor receives an interrupt between those instructions, and
    • Either the interrupt service routine modifies the location which was being operated on, or
    • that location changed its value because it was a peripheral register.

The problem is often encountered when using on-chip peripheral registers whose contents change over time, such as those involved with the timers.

[edit] Solution

The way around this problem is to prevent the transfer from being interrupted, through the use of the global interrupt flag. For example, take this code fragment:

uint16_t TimerValue;

TimerValue = TCNT;

which gets compiled to something like

in    r16,TCNTL
sts   TimerValue, r16
in    r17,TCNTH
sts   TimerValue+1, r17

Obviously, if this transfer gets interrupted, TCNT will likely be something quite different upon return from the interrupt. The way to prevent this is to save the state of the global interrupt flag (which is in the status register SREG), then disable interrupts before performing the transfer. After the transfer, it is sufficient to simply restore the saved status register value:

uint8_t TempSREG;
uint16_t TimerValue;

TempSREG = SREG;
cli();
TimerValue = TCNT;
SREG = TempSREG;

[edit] Doesn't the specifier volatile fix that for me?

The C keyword volatile doesn't enforce atomic data transfers. All it does is tell the compiler that the affected variable is subject to change by background tasks, and so should be loaded every time it is referenced. By contrast, when optimization is permitted, the compiler is normally free to hold a variable's value in registers and use it repeatedly across the execution of several source lines.

The necessity of disabling interrupts when reading or writing larger-than-native data types is peculiar to many RISC processors. But the volatile keyword pretty much assumes that atomic memory transfers are possible, whether or not the processor truly can support them.

Personal tools