Electronic – Critical sections on Cortex-M3

cortex-m3interrupts

I'm wondering a bit about implementing critical code sections on a Cortex-M3 where exceptions are not allowed due to timing constraints or concurrency issues.

In my case, I'm running an LPC1758 and I have a TI CC2500 transceiver on board. The CC2500 has pins which can be used as interrupt lines for data in the RX buffer and free space in the TX buffer.

As an example, I want to have a TX buffer in SRAM of my MCU and when there is free space in the TX buffer of the transceiver, I want to write this data in there. But the routine which puts data in SRAM buffer obviously cannot be interrupted by the free-space-in-TX interrupt. So what I want to do is to temporarily disable interrupts while doing this procedure of filling this buffer but have any interrupts occurring during this procedure execute after it finishes.

How is this done best on Cortex-M3?

Best Answer

The Cortex M3 supports a useful pair of operations of operations (common in many other machines as well) called "Load-Exclusive" (LDREX) and "Store-Exclusive" (STREX). Conceptually, the LDREX operation performs a load, also sets some special hardware to observe whether the location that got loaded might be written by something else. Performing a STREX to the address used by the last LDREX will cause that address to be written only if nothing else wrote it first. The STREX instruction will load a register with 0 if the store took place, or 1 if it was aborted.

Note that STREX is often pessimistic. There are a variety of situations where it might decide not to perform the store even if the location in question had not in fact been touched. For example, an interrupt between an LDREX and STREX will cause the STREX to assume the location being watched might have been hit. For this reason, it's usually a good idea to minimize the amount of code between the LDREX and STREX. For example, consider something like the following:

inline void safe_increment(uint32_t *addr)
{
  uint32_t new_value;
  do
  {
    new_value = __ldrex(addr) + 1;
  } while(__strex(new_value, addr));
}

which compiles to something like:

; Assume R0 holds the address in question; r1 trashed
lp:
  ldrex r1,[r0]
  add   r1,r1,#1
  strex r1,r1,[r0]
  cmp   r1,#0  ; Test if non-zero
  bne   lp
  .. code continues

The vast majority of the time the code executes, nothing will happen between the LDREX and STREX to "disturb" them, so the STREX will succeed without further ado. If, however, an interrupt happens to occur immediately following the LDREX or ADD instruction, the STREX will not perform the store, but instead the code will go back to read the (possibly updated) value of [r0] and compute a new incremented value based upon that.

Using LDREX/STREX to form operations like safe_increment makes it possible to not only manage critical sectionsm, but also in many cases to avoid the need for them.