15.7.1 Sharing Memory with Mainline Code

Exercise caution when modifying the same variable within a main or low-priority ISR and a high-priority ISR. Higher priority interrupts, when enabled, can interrupt a multiple instruction sequence and yield unexpected results when a low-priority function has created a multiple instruction Read-Modify-Write sequence accessing that same variable. Therefore, embedded systems must implement an “atomic” operation to ensure that the intervening high-priority ISR will not write to the variable from which the low-priority ISR has just read, but not yet completed its write.

An atomic operation is one that cannot be broken down into its constituent parts – it cannot be interrupted. Not all C expressions translate into an atomic operation. On dsPIC DSC devices, these expressions mainly fall into the following categories: 32-bit expressions, floating point arithmetic, division, operations on multi-bit bit-fields, and fixed point operations. Other factors will determine whether or not an atomic operation will be generated, such as memory model settings, optimization level and resource availability. In other words, C does not guarantee atomicity of operations.

Consider the general expression:

foo = bar op baz;

The operator (op) may or may not be atomic, based on the architecture of the device. In any event, the compiler may not be able to generate the atomic operation in all instances, depending on factors that may include the following:

  • availability of an appropriate atomic machine instruction
  • resource availability - special registers or other constraints
  • optimization level, and other options that affect data/code placement

Without knowledge of the architecture, it is reasonable to assume that the general expression requires two reads, one for each operand and one write to store the result. Several difficulties may arise in the presence of interrupt sequences, depending on the particular application.

Development Issues

Here are some examples of the issues that should be considered:

bar Must Match baz

When it is required that bar and baz match (i.e., are updated synchronously with each other), there is a possible hazard if either bar or baz can be updated within a higher priority interrupt expression. Here are some sample flow sequences:

  1. Safe:


    read bar

    read baz

    perform operation


    write back result to foo
  2. Unsafe:


    read bar


    interrupt modifies baz

    read baz

    perform operation


    write back result to foo
  3. Safe:


    read bar

    read baz

    interrupt modifies bar or baz

    perform operation


    write back result to foo

The first is safe because any interrupt falls outside the boundaries of the expression. The second is unsafe because the application demands that bar and baz be updated synchronously with each other. The third is probably safe; foo will possibly have an old value, but the value will be consistent with the data that was available at the start of the expression.

Type of foo, bar and baz

Another variation depends upon the type of foo, bar and baz. The operations “read bar,” “read baz,” or “write back result to foo,” may not be atomic depending upon the architecture of the target processor. For example, dsPIC DSC devices can read or write an 8-bit, 16-bit, or 32-bit quantity in 1 (atomic) instruction. But a 32-bit quantity may require two instructions depending upon instruction selection (which in turn will depend upon optimization and memory model settings). Assume that the types are long and the compiler is unable to choose atomic operations for accessing the data. Then the access becomes:

read lsw bar

read msw bar

read lsw baz

read msw baz

perform operation (on lsw and on msw)


perform operation


write back lsw result to foo

write back msw result to foo

Now there are more possibilities for an update of bar or baz to cause unexpected data.

Bit-fields

A third cause for concern are bit-fields. C allows memory to be allocated at the bit level, but does not define any bit operations. In the purest sense, any operation on a bit will be treated as an operation on the underlying type of the bit-field and will usually require some operations to extract the field from bar and baz or to insert the field into foo. The important consideration to note is that (again depending upon instruction architecture, optimization levels and memory settings) an interrupted routine that writes to any portion of the bit-field where foo resides may be corruptible. This is particularly apparent in the case where one of the operands is also the destination.

The dsPIC DSC instruction set can operate on 1 bit atomically. The compiler may select these instructions depending upon optimization level, memory settings and resource availability.

Cached Memory Values in Registers

Finally, the compiler may choose to cache memory values in registers. These are often referred to as register variables and are particularly prone to interrupt corruption, even when an operation involving the variable is not being interrupted. Ensure that memory resources shared between an ISR and an interruptible function are designated as volatile. This will inform the compiler that the memory location may be updated out-of-line from the serial code sequence. This will not protect against the effect of non-atomic operations, but is never-the-less important.

Development Solutions

Here are some strategies to remove potential hazards:

  • Design the software system such that the conflicting event cannot occur. Do not share memory between ISRs and other functions. Make ISRs as simple as possible and move the real work to main code.
  • Use care when sharing memory and, if possible, avoid sharing bit-fields which contain multiple bits.
  • Protect non-atomic updates of shared memory from interrupts as you would protect critical sections of code. The following macro can be used for this purpose:
      #define INTERRUPT_PROTECT(x) {  \
         char saved_ipl;                       \
                                               \
         SET_AND_SAVE_CPU_IPL(saved_ipl,7);    \
         x;                                    \
         RESTORE_CPU_IPL(saved_ipl); } (void) 0;

    This macro disables interrupts by increasing the current priority level to 7, performing the desired statement and then restoring the previous priority level.

Application Example

The following example highlights some of the points discussed in this section:

void __attribute__((interrupt))
             HigherPriorityInterrupt(void) {
              /* User Code Here */
              LATGbits.LATG15 = 1; /* Set LATG bit 15 */
              IPC0bits.INT0IP = 2; /* Set Interrupt 0
                                      priority (multiple
                                      bits involved) to 2 */
              }

int main(void) {
    /* More User Code */
    LATGbits.LATG10 ^= 1; /* Potential HAZARD -
                             First reads LATG into a W reg,
                             implements XOR operation,
                             then writes result to LATG */

    LATG = 0x1238;        /* No problem, this is a write
                             only assignment operation */

    LATGbits.LATG5 = 1;   /* No problem likely,
                             this is an assignment of a
                             single bit and will use a single
                             instruction bit set operation */

    LATGbits.LATG2 = 0;   /* No problem likely,
                             single instruction bit clear
                             operation probably used */

    LATG += 0x0001;       /* Potential HAZARD -
                             First reads LATG into a W reg,
                             implements add operation,
                             then writes result to LATG */

    IPC0bits.T1IP = 5;    /* HAZARD -
                             Assigning a multiple bitfield
                             can generate a multiple
                             instruction sequence */
}

A statement can be protected from interrupt using the INTERRUPT_PROTECT macro provided above. For this example:

INTERRUPT_PROTECT(LATGbits.LATG15 ^= 1); /* Not interruptible by
                                                    level 1-7 interrupt
                                                    requests and safe
                                                    at any optimization
                                                    level */