SPI sharing between interrupts and main program

PaulStoffregen

Well-known member
More libraries are starting to use SPI from interrupts, which causes a conflict with non-interrupt SPI usage, or SPI-based libraries like Ethernet & SD.

I'm considering publishing SPI usage guidelines for Teensy 3.x. This thread is the place to comment on the technical details.

This system will be voluntary. It will only prevent conflicts if every library & program using SPI follows the guidelines. I'll patch SD and Ethernet and eventually others that come with the Teensyduino installer. A huge challenge involves persuading authors of SPI-using libraries to follow these guidelines.

My thought so far is to declare interrupt priority level 192 to be the threshold for SPI usage. ARM interrupt priorities range from 0 to 256 (or to -2 if you count a couple extra priority system things), where 0 is the highest priority and 256 is non-interrupt priority. The Teensyduino core library assigns priority 128 as a default to most interrupts.

As this idea develops into a guideline, I'll update/edit this message.

If anyone has any comments or suggestions, now's the time to speak up!!
 
The SD library is the same as regular Arduino, which is based on an old version of Bill Grieman's sdfat library.

Teensyduino adds some small patches, to fix 32 bit issues. In the latest, I brought over Bill Grieman's native SPI code from recent sdfat, which makes about a 3X speedup.

When we make the jump to Arduino 1.5.7, I'm considering replacing replacing the underlying (old) code with Bill's latest. Arduino 1.5.x has improvement library management, so it'll be easier to optimize a Teensy-specific copy without worry of breaking things for regular Arduino boards, and also AVR-based Teensy2.
 
It would help users if there was some sort of guidance document on such things as when/why MUTEX by any means is needed. Can be a huge time-waster as the bugs cause obscure symptoms.
 
Teensyduino has that AVR-like SPI library - plus optional use of a better SPI library that takes advantage of the K20 CPU's FIFO'd SPI.
 
My thought so far is to declare interrupt priority level 192 to be the threshold for SPI usage.
Isn't the first-order problem that a non-interrupt program can, today, call a library routine to which does SPI I/O, and that library doesn't disable interrupts as a rudimentary way to get MUTEX with an ISR that would also use the SPI? No matter the interrupt priorities.

I myself made this mindless error - in the radio chip app I have. I read the signal strength (RSSI) via a call to a class method which called the SPI lib to do a register read. Eventually, that conflicted with an ISR around that time, as the library routine had no MUTEX, nor did the higher level class method. This is all within just one SPI device and chip select - not the more complex case of multiple chip-selected devices competing for use of one SPI interface.

The higher-level solution to this was in my app - to read the RSSI only when I knew the radio was is "idle" state and cannot interrupt again until I change its state to receive or transmit. But that's not a general MUTEX solution.
 
Last edited:
Isn't the first-order problem that a non-interrupt program can, today, call a library routine to which does SPI I/O, and that library doesn't disable interrupts as a rudimentary way to get MUTEX with an ISR that would also use the SPI? No matter the interrupt priorities.

I believe that's an overly simplified way of looking at a rather complex issue.

The easy solution is to simply disable interrupts during every SPI transaction (a set of SPI transfers that form one logical operation). This has been suggested for AVR a few times. It does solve the compatibility issue for SPI communication, but it comes at a terrible cost of adding tremendous interrupt latency.

Libraries like Servo create waveforms, where latency in servicing the timer interrupt results in a distorted output waveform, which causes the motors to "jitter". Many people build applications with IntervalTimer, expecting microsecond precision. Fast baud rates on the serial ports, output bound queued USB packets maintaining full 12 Mbit/sec speed, and lots of the other awesome things Teensy 3.1 is capable of doing depend on low interrupt latency.

A mutex, where interrupts only disable briefly to query or modify the mutex, is a wonderful solution when all the actors are threads running under an RTOS. But what is an interrupt routine to do in the case where it needs to perform a SPI transaction, but the main program has the mutex locked? It can't just wait for the mutex, since that would prevent the main program from ever finishing.

In the USB MSD stuff I wrote on Teensy 2.0, I actually tried a complex scheme where the interrupt sets a "I need this when you're done" flag and the main program is responsible for checking the flag and calling the MSD code, which then tries to (much later) do whatever the interrupt had intended to do. That can work in some libraries, but it's far too intrusive to the design of 3rd party Arduino libraries. It also gets into all sorts of other thorny issues, like what happens if another interrupt occurs during the completion handler. Sure, you could disable interrupts during the followup SPI transaction which the previous interrupt wasn't able to do, but the whole point in the first place was to avoid disabling interrupts for so long. That sort of complex design burden just isn't realistic for all SPI-using Arduino libraries, especially if it's only for a platform like Teensy which has a relatively small share of the market.

Interrupt priorities offer better, but far from ideal solution. This would mean interrupts with the same or lower priority than 192 would would be disabled, but interrupts of higher priority would remain enabled during a lengthy SPI transaction. Anyone who decides to use priority 192 or lower could suffer pretty substantial interrupt latency. Maybe that's ok?

I'm not very familiar with how FreeRTOS really works on Teensy 3.1. How does it use interrupts and interrupt priority levels? I should really learn more about how FreeRTOS and the others are actually implemented....
 
I agree about minimizing interrupt latency... but, what can an ISR do if it must use the SPI to read status which clears the interrupt cause. The ISR can't use a MUTEX because if the MUTEX says "busy", the ISR can't just exit and try later, as this creates an interrupt loop. The traditional way to do this is for the non-ISR level code wanting to use the resource (SPI), loops to get MUTEX success. Then it disables interrupts from just the device - either by clearing interrupt enable bits in the device or masking the interrupt via the CPU (NVIC) vector slot, etc. This gets messy if what the non-ISR code does destroys the pending interrupt status.

FreeRTOS for Teensy 3 was a quick and dirty. The only thing it does is rely on the systick that's already in teensyduino, plus FreeRTOS uses a software interrupt (trap) to stack the registers to get from task mode to RTOS kernel mode. Then a decision is made on which register saved-set to restore based on what task is going to run next. I used FreeRTOS with task preemption disabled, to avoid the problem that most of the arduino, much of the C libs, and probably much of Teensyduino is not reentrant. So with preemption off, life is greatly simplified but a task must not loop, it must yield or delay or do a wait on queue messages so the task will block to RTOS and let the RTOS switch to the next ready task. This can work very well if your tasks don't hog the CPU too long. This has nothing to do with interrupt latency.

I feel that if you need to assure interrupt latency of, say no more than 10 microseconds or so, then that app can't share the CPU with other I/O or tasks. It can lessen the latency demand with a dedicated CPU -or- use of well buffered DMA - which is how "big" computers do it. But we need to limit what we should try to do on a $20 computer without an operating system.
 
Last edited:
I really need to learn more about FreeRTOS and spend some time actually using it....

I agree about minimizing interrupt latency... but, what can an ISR do if it must use the SPI to read status which clears the interrupt cause.

That's a good point too. The interrupt code must do a SPI transaction for SPI-based chips which assert an interrupt signal. Other complicated ways, like disabling the interrupt and trying to service it later, just aren't practical.

I believe the priority approach is probably the least-bad way to go. At least only interrupts at priority 192 to 255 will have to suffer the latency of SPI transactions. Priority levels 0 to 191 can interrupt the SPI-using interrupt, because the ARM chip provides nested interrupts with priority levels.
 
I think the non-ISR level code, when it needs to access the port resource (SPI, I2C, etc), needs to disable interrupts from only the intended device. Rather than all devices at or below those of the same priority.

As I recall, the NVIC slot for a specific interrupt source can disable that at the NVIC level. This avoids trying to use the (e.g.) SPI port to disable interrupts from the device by altering the device's own interrupt enable bits. This can be a race condition. In most all devices, one can defer taking and servicing the device's interrupt for quite a while, like 10's of microseconds. Maybe not in a non-DMA based real time thing like D/A or some such. But in the case of SD cards, the radios we speak of, ethernet, such a delay is OK - the device will keep asserting the interrupt, even if the CPU is ignoring it for a while (via the NVIC setting, or master interrupt disable).
 
Last edited:
I think the non-ISR level code, when it needs to access the port resource (SPI, I2C, etc), needs to disable interrupts from only the intended device.

Yes, that would be ideal. But how am I supposed know which interrupt to disable?

When I patch the Ethernet and SD libraries, I have no idea which interrupt another library will use to do SPI transactions. In fact, there could be multiple libraries in play. Perhaps a library services a RFM22 radio using a pin interrupt (which pin is user configurable), and maybe the user's sketch creates an IntervalTimer object that uses SPI to read a sensor at some regular intervals. Ethernet would need to disable 2 other interrupts before using SPI.

Another usage scenario is a non-interrupt library called from the interrupt of another library. For example, the audio library calls the SD library functions while playing WAV files from the SD card.


As I recall, the NVIC slot for a specific interrupt source can disable that at the NVIC level.

Yes, the NVIC definitely has this capability. It very nice and uniform for all interrupts.

I just don't see how libraries like SD and Ethernet can know which NVIC bits to clear. Well, at least not without some sort of scheme where interrupt-using libraries can register their interrupt number on a list. Then to disable only those specific interrupts, you'd have to look at that list and clear all the specified bits. That might be possible, but it's sounding like quite a bit of code to run before every SPI transaction. Libraries like Ethernet do a LOT of small transactions, so whatever mechanism is used to lock and unlock exclusive access really wants to be fast.

Maybe there is some simpler way I'm just not seeing?
 
On a prior ARM7 project, which also as an NVIC.. I had one routine that managed NVIC slot assignments. Devices did not hard-code this. The new device asked for an unused slot and was given one by the manager.

The other way is to have a single #define file for NVIC assignments for all interrupt sources. I did this too, with one hard coded (systick).
An ISR calling a non-reentrant library or a library not intended to run at an ISR level, and not FAST, will usually lead to unreliable code!
I've always made a habit of not calling library code from ISRs. Maybe a C function that I wrote or know is suitable and "ISR-safe".

This is all on the verge of having an operating system.
All these Arduino codes for I/O assume no other devices will conflict or, more usually, there are no other devices.
This is fine for a trivial system.

I hate to say it, but using ethernet, SD card, SPI devices like the radios, need a coherent design, not a collection of autonomously coded things.
At some point in such complexity of I/O, a Linux based SBC like RPi is easier.
 
Last edited:
.... using ethernet, SD card, SPI devices like the radios, need a coherent design, not a collection of autonomously coded things.

I'm optimistic that more active involvement on my part, and perhaps even Arduino (if they're willing to collaborate), can move all these libraries towards a more cohesive design.

Like all technical things, there will be trade-offs. Some highly designs requiring highly coordinated static configuration just won't be possible with so many independent authors using so many different design strategies. But perfection isn't my goal. I "only" want a big improvement...


At some point in such complexity of I/O, a Linux based SBC like RPi is easier.

I agree completely.

I only want to move that "some point" a lot farther out. ;)
 
I'm optimistically following the discussions and trying to wrap my head around how this all would work, but I'm very glad that paul is thinking about how to get these things to work. I've definitely shied away from doing some projects that I'm wanting to do, cause I think I'm going to end up hitting my head against the wall. I really would like to see libraries work better together, without requiring a whole proper OS scheduler. Some sort of standard, or way to check that a library is compliant, or wrapper calls that register the interrupt, etc. I mean a lot of libraries don't need to worry about this, but a few do, as Paul has been noting.

I don't want to go to something like a RPi, cause at that point a lot of interesting projects are eliminated, cause we need something like the teensy for these type of projects. I would definitely run 2 or more teensy3's before I would try a RPi, eventho linux would be where I'm most comfortable.
 
I think a starting point is the SD card - because it dwarfs ethernet and low rate wireless with the volume of data to be exchanged via SPI.
The ethernet interface is far easier - because of IP and its tolerance of delays. And that the Wiznet 5100/5200 off-load the whole stack (ARP/ICMP, IP, TCP), and these modules have a full MTU sized buffer for both TX and RX for each socket.

The low-power radios are the challenge, because the FIFO buffers are small- usually smaller than the application's messages. The RF22 library has a packet structure for up to 255 user payload bytes (with a preamble of a dozen or so bytes). The radio, for both transmit and receive, must interrupt as the FIFOs need service (fill for TX, empty for RX). If the SD card, for example, was doing a 512 byte sector sized transfer and didn't want to give up the SPI port in the middle of those - we have this issue- the radio will underrun (TX) or overrun (RX). And the radio has no way to flow control this- owing to the nature of wireless. But the reliable datagram mode in RF22 lib would error recover via retransmission - though you wouldn't want this to be routine.

What other SPI device challenges are there? Some of these quadcopter gyros? A/Ds?

Another Arduino issue with SPI libraries is:
The author sets up the SPI config once and assumes it never needs repeating. Well, if another device's software reconfigures the SPI port (different format or clock rate), that has to be reestablished prior to each library doing any sort of transfer at all. Doesn't take much time to do so, but it's an example of design philosophy used in "drivers".

I'll bet that the issue above- isn't dealt with properly even in a Linux thing like the RPi - because it is so application specific. As on the Teensy, it would be up to the collaborating authors (or a single author), to establish conventions on this.

With 1-3 collaborating authors for a a few SPI devices, this is all doable - rather simple except for the SD card port-hogging issue. I suppose a universal standard would be hard to achieve. Indeed, I haven't ever seen such. Maybe it's been done for something like CMSIS?
 
Last edited:
Short follow-up.. I searched around the web and quickly found examples of code that uses a classic MUTEX to "own" the SPI port for a transfer session of 1-n bytes or byte-pairs (16 bit xfers).
These all assumed there is a scheduler of some sort... even a simple cooperative scheduler with a SINGLE STACK, will do. More sophisticated is something like an RTOS with a stack per task - which is not AVR friendly but OK on Teensy (RAM size).

So there are examples out there to be read - where after owning the MUTEX semaphore (lock) for the SPI port, the code does indeed reconfigure the SPI port, assuming some other usage may have changed it. That's only 5 or so lines of code.
 
Today I learned about the Cosa project, which is a C++ library for AVR hardware.

As nearly as I can tell, it actually does the "some sort of scheme where interrupt-using libraries can register their interrupt number on a list" approach I theorized (but had no idea anyone actually implemented) in reply #11.

Here's the source. The interrupt disable appears to be at line 108.

http://dl.dropboxusercontent.com/u/993383/Cosa/doc/html/d7/d38/SPI_8cpp_source.html
 
.... found examples of code that uses a classic MUTEX to "own" the SPI port for a transfer session of 1-n bytes or byte-pairs (16 bit xfers).

Could you post links to a few of these? Specific source files and even line numbers would be nice.

I think I understand this RTOS-based approach, but I'd like to take a look at the actual source code.
 
Here is my feeble attempt to make a mutex of sorts with inlined assembly using the exculsive load and store commands. It's not a spinlock but a "try lock" that only executes the code block if the mutex is free. I haven't been able to get the spin lock version working yet where it will wait till a lock is free.

Try 2 Lock:
Code:
static inline uint32_t __tryLock() {
  int result = 0;
  int value = 0;
  asm volatile (
                "ldrex %0, [%1]"         "\n\t"
                "cmp %0, #0"             "\n\t"
                "wfe"                    "\n\t"
                "it eq"                  "\n\t"
                "strexeq %0, %2, [%1]"   "\n\t"
                "DSB"                    "\n\t"
                "ISB"                    "\n\t"
                "it eq"                  "\n\t"
                "cmpeq %0, #0"           "\n\t"
                "dmb"                    "\n"
                 : "=&r" (result) : "r" (&MUTEX), "r" (1) :
                );
  return !result;
}

Free if Lock Acquired
Code:
static inline void __freeLock() {
  asm volatile (
                "dmb"                  "\n\t"
                "str %1, [%0]"         "\n\t"
                : : "r" (&MUTEX), "r" (0) : 
               );
}

Wrap into Code Block Macro:
Code:
#define TRY_LOCK(x) { if(__tryLock()) { x; __freeLock(); } }

Mutex Varaible:
Code:
volatile unsigned long MUTEX = 0;

Here are two examples that show this working on some "unsafe" code inside of the Interval Timers callbacks and loop functions. The first one will not use the "TRY_LOCK" and fail in allocating memory with a fault while the second one that uses the "TRY_LOCK" works.

Unsafe memory allocation: not using the "TRY_LOCK" code block.
Code:
#define TRY_LOCK(x) { if(__tryLock()) { x; __freeLock(); } }

volatile unsigned long MUTEX = 0;

char val = 0x21;
uint8_t *data;

IntervalTimer timer1;
IntervalTimer timer2;
IntervalTimer timer3;
IntervalTimer timer4;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH0, 128);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH1, 128);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH2, 128);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH3, 128);
  while(!Serial);
  delay(100);
  Serial.println("TRY BLOCK MUTEX EXAMPLE");
  timer1.begin(iTimer1, 100000);
  timer2.begin(iTimer2, 100000);
  timer3.begin(iTimer3, 100000);
  timer4.begin(iTimer4, 100000);
}

void loop() {
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  //TRY_LOCK (
    Serial.println("Loop");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delay(100);
    delete [] data;
  //);
  delay(20);
}

void iTimer1() {
  //TRY_LOCK (
    Serial.println("Timer1");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  //);
}

void iTimer2() {
  //TRY_LOCK (
    Serial.println("Timer2");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  //);
}

void iTimer3() {
  //TRY_LOCK (
    Serial.println("Timer3");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  //);
}

void iTimer4() {
  //TRY_LOCK (
    Serial.println("Timer4");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  //);
}

static inline uint32_t __tryLock() {
  int result = 0;
  int value = 0;
  asm volatile (
                "ldrex %0, [%1]"         "\n\t"
                "cmp %0, #0"             "\n\t"
                "wfe"                    "\n\t"
                "it eq"                  "\n\t"
                "strexeq %0, %2, [%1]"   "\n\t"
                "DSB"                    "\n\t"
                "ISB"                    "\n\t"
                "it eq"                  "\n\t"
                "cmpeq %0, #0"           "\n\t"
                "dmb"                    "\n"
                 : "=&r" (result) : "r" (&MUTEX), "r" (1) :
                );
  return !result;
}

static inline void __freeLock() {
  asm volatile (
                "dmb"                  "\n\t"
                "str %1, [%0]"         "\n\t"
                : : "r" (&MUTEX), "r" (0) :
               );
}

Safe memory allocation: using the "TRY_LOCK" code block.
Code:
#define TRY_LOCK(x) { if(__tryLock()) { x; __freeLock(); } }

volatile unsigned long MUTEX = 0;

char val = 0x21;
uint8_t *data;

IntervalTimer timer1;
IntervalTimer timer2;
IntervalTimer timer3;
IntervalTimer timer4;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH0, 128);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH1, 128);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH2, 128);
  //NVIC_SET_PRIORITY(IRQ_PIT_CH3, 128);
  while(!Serial);
  delay(100);
  Serial.println("TRY BLOCK MUTEX EXAMPLE");
  timer1.begin(iTimer1, 100000);
  timer2.begin(iTimer2, 100000);
  timer3.begin(iTimer3, 100000);
  timer4.begin(iTimer4, 100000);
}

void loop() {
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  TRY_LOCK (
    Serial.println("Loop");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delay(100);
    delete [] data;
  );
  delay(20);
}

void iTimer1() {
  TRY_LOCK (
    Serial.println("Timer1");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  );
}

void iTimer2() {
  TRY_LOCK (
    Serial.println("Timer2");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  );
}

void iTimer3() {
  TRY_LOCK (
    Serial.println("Timer3");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  );
}

void iTimer4() {
  TRY_LOCK (
    Serial.println("Timer4");
    data = new uint8_t[2000];
    memset(data, val, 2000);
    delete [] data;
  );
}

static inline uint32_t __tryLock() {
  int result = 0;
  int value = 0;
  asm volatile (
                "ldrex %0, [%1]"         "\n\t"
                "cmp %0, #0"             "\n\t"
                "wfe"                    "\n\t"
                "it eq"                  "\n\t"
                "strexeq %0, %2, [%1]"   "\n\t"
                "DSB"                    "\n\t"
                "ISB"                    "\n\t"
                "it eq"                  "\n\t"
                "cmpeq %0, #0"           "\n\t"
                "dmb"                    "\n"
                 : "=&r" (result) : "r" (&MUTEX), "r" (1) :
                );
  return !result;
}

static inline void __freeLock() {
  asm volatile (
                "dmb"                  "\n\t"
                "str %1, [%0]"         "\n\t"
                : : "r" (&MUTEX), "r" (0) :
               );
}
 
Too bad that IntervalTimer does not have a pause/resume... this way you could pause all other IntervalTimers inside the one you are using, and then resume it without loss of where the timer was at that time plus any expired time... However nothing I am doing is actually that critical time-wise. Afterall, before I released the following code examples, this was done in loop(), which was a huge mess, and would get in your way, and cause you to miss plug-events etc... so without further ado...

As far as memory corruption I do an even better way, I just add in the locking that malloc needs.
You can see it here... https://github.com/xxxajk/xmem2/blob/master/mlock.c

Yup, a big fat cli();/sei(); that actually allows reentrance. :cool:

Here is the other part though, where I can actually share spi between a polled IntervalTimers entry, and code that is elsewhere... perhaps Paul can do something similar...

This is the ISR part: https://github.com/xxxajk/xmemUSB/blob/master/xmemUSB.cpp#L14-L45
...which uses the macro defined here https://github.com/xxxajk/xmemUSB/blob/master/xmemUSB.h#L42

This macro basically stops and starts the timer, and is used wherever I share spi, or in this case, USB code which uses SPI...
... and it is used like this: https://github.com/xxxajk/xmemUSBFS/blob/master/xmemUSBFS.cpp#L1978-L1988

Note that this also prevents the timer from changing any other variables that you may want access to, and it is used for that as well. in other words, kind of like a spinlock, but without any actual locking. ;)

Hope this helps you out. now that you have a working example for yourself that kind-of does what you asked for. ;)
 
As far as memory corruption I do an even better way, I just add in the locking that malloc needs.

isn't this the same thing as the ATOMIC_BLOCK, where you are just disabling all interrupts? How do you deal with priorities for the different ISR's, especially if you don't know what they are? Seems to me like a brute force way of doing things, though I haven't really looked into your implementation that close either.

Another point to take into account is what happens if a ISR with higher priority preempts the current ISR trying to allocate memory and tries to allocate or free the same memory pool. Thats why you need an exclusive load store of the mutex or use bitband to update the mutex. Maybe the your code deals with this i just didn't see it.
 
Could you post links to a few of these? Specific source files and even line numbers would be nice.

I think I understand this RTOS-based approach, but I'd like to take a look at the actual source code.
A MUTEX for the non-ISR (application) doesn't solve the exclusion with the ISR - which is the hard problem. The ISR could be triggered by a pin interrupt which leads to using the SPI port to read and clear the interrupt cause. Gotta do that, else an interrupt loop ensues. The RFM22/69 radios do this; they assert an nIRQ which is a pin interrupt to an ISR that then uses the SPI to read the interrupt cause. A different and easier to deal with is an interrupt from the ARM SPI device itself. Easier because you can MUTEX the SPI access and the interrupt that occurs only if you started the transfer.

A MUTEX among non-ISR code isn't needed for a single threaded environment like an RTOS-less Teensy. With preemptive scheduling, it is needed but is very simple. An RTOS with preemption disabled (cooperative task switching, e.g., calling yield()) doesn't need a MUTEX for I/O and the SPI port (the ISR issue remains though). FreeRTOS can run with or without preemption, as a config choice. I use it without preemption because many C lib and Arduino lib code is not coded to be reentrant.
 
Last edited:
isn't this the same thing as the ATOMIC_BLOCK, where you are just disabling all interrupts? How do you deal with priorities for the different ISR's, especially if you don't know what they are? Seems to me like a brute force way of doing things, though I haven't really looked into your implementation that close either.
Yes, it is exactly ATOMIC_BLOCK, that is the entire idea. Yes, it is brute force, and that is actually the only way to make it ISR safe.
Another point to take into account is what happens if a ISR with higher priority preempts the current ISR trying to allocate memory and tries to allocate or free the same memory pool. Thats why you need an exclusive load store of the mutex or use bitband to update the mutex. Maybe the your code deals with this i just didn't see it.
It will cover all situations except an NMI.
No regular ISR is allowed to interrupt, and there is no actual mutex.
The counter is needed because newlib's memory management is based on reentrant locks.
The counter is there only to unroll the reentrant cli() and sei() when all unlocking is done.
As written, if used in a threaded environment it will not be ISR safe.
 
Back
Top