10
\$\begingroup\$

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?

\$\endgroup\$
11
\$\begingroup\$

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.

\$\endgroup\$
5
  • \$\begingroup\$ So there is no way of "blocking" interrupts so that they can be served again once they are unblocked? I realize that this is probably an inelegant solution even if possible but I just want to learn more about ARM interrupt handling. \$\endgroup\$ – Emil Eriksson Jan 26 '12 at 16:38
  • 3
    \$\begingroup\$ It is possible to disable interrupts, and on the Cortex-M0 there is often no practical alternative to doing so. I consider the LDREX/STREX approach to be cleaner than disabling interrupts, though admittedly in many cases it won't really matter (I think enable and disable end up being one cycle each, and disabling interrupts for five cycles is probably no big deal). Note that an ldrex/strex approach will work if code is migrated to a multi-core CPU, while an approach that disables interrupts won't. Also, some RTOS's run code at reduced permissions which aren't allowed to disable interrupts. \$\endgroup\$ – supercat Jan 26 '12 at 16:54
  • \$\begingroup\$ I will probably end up going with FreeRTOS anyway so I won't be doing this myself but I'd like to learn anyway. What method of disabling interrupts should I use to block the interrupts as described as opposed to discard any interrupts occurring during the procedure? How would I do it if I want to discard them? \$\endgroup\$ – Emil Eriksson Jan 26 '12 at 16:57
  • \$\begingroup\$ The one answer above cannot be trusted because the associated code is missing a parenthesis: while(STREXW(new_value, addr); How can we beleive what you say is correct if your code will not even compile? \$\endgroup\$ – user62159 Dec 26 '14 at 20:22
  • \$\begingroup\$ @Tim: Sorry my typing isn't perfect; I don't have the actual code I've written handy for comparison, so I don't remember whether the system I was using used STREXW or __STREXW, but a the compiler reference lists __strex as an intrinsic (unlike STREXW which is limited to 32-bit STREX, the __strex intrinsic uses generates a STREXB, STREXH, or STREX depending upon the supplied pointer size) \$\endgroup\$ – supercat Dec 26 '14 at 21:31
4
\$\begingroup\$

It sounds like you need some circular buffers or FIFOs in your MCU software. By tracking two indices or pointers into the array for read and write, you can have both foreground and background accessing the same buffer without interference.

The foreground code is free to write to the circular buffer at any time. It inserts data at the write pointer, then increments the write pointer.

The background (interrupt handling) code consumes data from the read pointer and increments the read pointer.

When the read and write pointers are equal, the buffer is empty and the background process sends no data. When the buffer is full, the foreground process refuses to write any more (or can overwrite old data, depending on your needs).

Using circular buffers to decouple readers and writers should remove the need to disable interrupts.

\$\endgroup\$
2
  • \$\begingroup\$ Yes, I'm obviously going to use circular buffers but increment and decrement are not atomic operations. \$\endgroup\$ – Emil Eriksson Jan 26 '12 at 16:36
  • 3
    \$\begingroup\$ @Emil: They don't have to be. For a classic circular buffer, with two pointers and one "unusable" slot, all that is necessary is that memory writes are atomic and enforced in-order. The reader owns one pointer, the writer owns the other, and, although both of them can read either pointer, only the pointer's owner writes his pointer. At that point, all you need are atomic in-order writes. \$\endgroup\$ – John R. Strohm Jan 26 '12 at 18:01
2
\$\begingroup\$

I cannot remember exact location but in the libraries that comes from ARM (Not TI, ARM, it should be under CMSIS or something like that, I use ST but I remember reading somewhere that this file came from ARM so you should have it as well) there is a global interrupt disable option. It is a function call. (I am not at work but I will look up tomorrow the exact function). I would wrap that up with a nice name in your system and disable the interrupts, do your thing and enable again. Having said that, the better option would be implementing a semaphore or a queue structure instead of global interrupt disable.

\$\endgroup\$
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.