As an aside, I read somewhere that any globals that are within spitting distance of interrupts should be volatile. Is this true? Is there a downside?
It's always important to understand what's really happening in order to predict and avoid rare situations in which code doesn't quite do what you expected.
'Volatile' implies that a variable cannot be relied upon to retain the same value unless it's changed by the C code being executed at the time. Hardware registers, for example, should be 'volatile' because their values might change independently of the code.
Suppose your hardware includes a memory mapped status register that indicates whether or not some operation has completed. You might, quite reasonably, include some code along the lines of:
do {
result = HW_STATUS_REGISTER;
} while ((result & STATUS_COMPLETE) == 0);
The intent of the code is, of course, to poll the status register until a status bit gets set.
But: the compiler could, quite reasonably, determine that the value of 'result'
always equals whatever value is in HW_STATUS_REGISTER, and only read it once. Not reading it each time round the loop saves time, and means the loop executes faster. It doesn't particularly matter in this example, but if the loop also included other interesting things for the CPU to be doing whilst waiting for the hardware to complete its operation, then that would be a good thing.
Not re-reading the value each time is completely valid if HW_STATUS_REGISTER isn't declared as volatile. If it is declared volatile, then the assumption that its value doesn't change is false, and the compiler will ensure it gets re-read each time round the loop.
It's exactly the same if the value of a variable is being changed by an ISR rather than by hardware, hence the rule-of-thumb about declaring variables that are used by an ISR as volatile. (Note, that's variables which are used both inside
and outside the ISR. You don't need to declare as volatile every variable used inside the ISR. Typically they should be local in scope and not externally visible at all anyway).
'Volatile' won't get you out of every problem, though. You still need to consider cases like:
- read, modify, write cycles can break if the value being modified gets changed by an ISR in between the read and the write.
- reads of variables which are wider than the CPU data bus will break if they're changed by an ISR in between different bytes being read. For example, if you're on an 8 bit CPU, and you read a uint32_t, then you might get three bytes of 'old' data and one byte of 'new' data.
The general rule is, if an ISR can happen to execute at exactly the wrong time, then it will. Always consider whether you need to disable interrupts around any set of operations which access data generated by an ISR.