Question about DeepSleep-nRF52 example: Potential Race Condition with Double Semaphore?

Hi everyone, and tagging @beegee for his expertise on the SX126x-Arduino library examples!

I am currently developing a complex production tracker using the RAK4631 (nRF52840) based on the DeepSleep-nRF52.ino example. Can be found here too

My architecture has evolved into a multi-interrupt system. I currently have four different wake-up sources:

  1. Periodic Wakeup Timer (KeepAlive).
  2. LoRaWAN Downlink RX events.
  3. Accelerometer hardware interrupt 1.
  4. Accelerometer hardware interrupt 2.

While studying FreeRTOS to make this production-ready (and discussing the architecture with an AI assistant), we noticed a potential logic trap regarding the semaphores in the example code, specifically the xSemaphoreTake(taskEvent, 10); located at the very end of the loop().

The Suspected Race Condition: If my loop() wakes up to process an event (for example, it takes a few seconds to process and send a motion alert via LoRa), and during that processing time a critical interrupt fires (like a LoRa downlink arriving, or a free-fall event triggering), the ISR will correctly update the eventType and give the semaphore.

However, when the loop() finishes its current task and reaches the bottom, the xSemaphoreTake(taskEvent, 10); will “consume” this new ticket from the queue. Since this happens at the end of the loop, the newly arrived event is discarded without being processed. The loop will then wrap around to the top, hit the portMAX_DELAY semaphore, and the MCU will go into deep sleep, effectively missing a critical hardware interrupt.

My Questions:

  1. Is my understanding of this race condition correct for a multi-interrupt environment?
  2. Was that trailing xSemaphoreTake(taskEvent, 10); intentionally placed in the basic example just to act as a “flush” for noisy/bouncing interrupts?
  3. For a production environment where no interrupt can be missed, is the correct approach to completely remove that trailing semaphore and rely solely on the single blocking xSemaphoreTake(taskEvent, portMAX_DELAY) at the top of the loop?

I just want to make sure I am applying the FreeRTOS concepts correctly before deploying this to the field. Thank you very much for your time and for the amazing libraries!

You can remove the xSemaphoreTake at the end of loop().
I checked with my WisBlock API where I use the same semaphore approach, it takes the semaphore only once.

Can’t tell why the library example does it twice, its an 5 year old example.

3 Likes

I was wondering about that one. I thought it was eating some spurious event. :smiley: