I habitually compile my own C code with
-Wall -O2, sometimes using
-Wall -Wextra -O2. I never, ever need to "disable optimizations".
I sometimes do use
unsafe optimizations that let the compiler generate faster code but no longer follow the C standards exactly, but the
-O2 (or
-Os or
-Og) options never enable such by themselves.
I want to set up a timer interrupt on the 8051, using keil ,I’m trying to understand the proper use of the volatile keyword in embedded C.
As explained above,
volatile tells the compiler it cannot infer the value of the
volatile object, and therefore must do the actual access.
Another similar issue wrt. optimization is the precision and range of numerical expressions. If you want to calculate
x * y, but limit it to the range and precision of
unsigned char, all you need to do is a cast:
(unsigned char)(x * y). You do not need a temporary variable at all.
The third issue, common on 8-bit architectures, is non-atomic accesses. As detailed above by explained above by HwAoRrDk, it occurs because the object is read from or written to in smaller parts, and the interrupt can occur mid-way during the write. The solution is to temporarily disable interrupts, do the access, and then re-enable interrupts. Typical code implements that as
saving the interrupt state, then restoring it, so that if you already have interrupts disabled, you don't enable them too early. On 8051, you should have a function that saves the Interrupt Enable register, then disables interrupts globally, and another that restores interrupts.
I don't use Keil, so I don't know what they actually are for OP. If we assume they are, for simplicity,
unsigned char save_interrupts_disable(void); // Returns current Interrupt Enable register, disabling interrupts globally void restore_interrupts(unsigned char); // Set Interrupt Enable register to a previously saved valuethen it would be best to use
accessor functions to manipulate the variables (larger than 8 bits) used in both the interrupt and other code:
static volatile TYPE shared_variable; static inline TYPE get_shared_variable(void) { unsigned char state; TYPE value; state = save_interrupts_disable(); value = shared_variable; restore_interrupts(state); return value; } static inline void set_shared_variable(const TYPE value) { unsigned char state; state = save_interrupts_disable(); shared_variable = value; restore_interrupts(state); } static inline TYPE replace_shared_variable(const TYPE newvalue) { unsigned char state; TYPE oldvalue; state = save_interrupts_disable(); oldvalue = shared_variable; shared_variable = newvalue; restore_interrupts(state); return oldvalue; }The variable is declared
volatile exactly because it can be changed from an interrupt context at any time, so the compiler must not infer its value at any point, and must do explicit accesses to it.
The
save_interrupts_disable() function is used to stop interrupts from occurring during the access, so that even if the variable is copied piecewise, it will not be interrupted, and will thus be
effectively atomic.
The
restore_interrupts() restores the interrupt enable register, so that these functions can be used from both normal code, as well as from within an interrupt handler.
You could open-code the interrupt blocking for the access duration, but that is error-prone.
Finally, the
static inline designation tells the compiler that this function will only be used in the current compilation unit (.c source file), and that the compiler can inline the code (copying it) instead of generating a function and calls to that function. Because the accessor functions are only available in the current compilation unit, it makes sense to limit the shared variable to the current compilation unit, therefore the shared variable is declared
static.
C11 and later did introduce
atomic types via
#include <stdatomic.h>, but I do not know if Keil supports those, implements them internally via implicit interrupt disable/restore like above, or does something else.