Using std::queue in an interrupt handler?

Imagine a toy app with a button that turns an LED on when pressed and off when released. The on/off events are captured in an interrupt handler. Because the interrupt code should be short, I wanted to queue up the interrupts and process them in the app’s loop().

Is it possible to use std::queue variables in an interrupt handler? My code is below… a bunch of compiler errors due to use of volatile as queue type and container type.


#include "Particle.h"
#include <queue>

#define DEBOUNCE_TIME_MS    10

uint16_t btn = D4;
uint16_t led = D6;

volatile uint8_t ledState = LOW;
volatile system_tick_t lastBtnTime = 0;

volatile std::queue <volatile uint8_t> btnEvents;

void buttonChange(void);

void setup () {
    pinMode(btn, INPUT);
    attachInterrupt(btn, buttonChange, CHANGE);
    pinMode(led, OUTPUT);
    digitalWrite(led, ledState);
}

void loop() {
    while ( ! btnEvents.empty() ) {
        digitalWrite(led, btnEvents.front());
        btnEvents.pop();
    }
}

void buttonChange(void) {
    if (millis() - lastBtnTime >= DEBOUNCE_TIME_MS) {
		lastBtnTime = millis();
		ledState = (ledState == LOW) ? HIGH : LOW;
        btnEvents.push(ledState);
	}
}

I don’t think that dynamic memory allocation flies well in an ISR.
I’d rather go with a static circular buffer.

Got it… is there an implementation you’d suggest or should I roll my own?

I think there is a library somewhere but circular buffers are so simple (one array, two indices) I’d do my own - any library would just add extra overhead IMO.

1 Like

I’ll get right to it! Thanks again, as always.

1 Like

Is this all you’re doing in loop()? If your loop is quick enough I am wondering why you would need to queue these events. Of course if you’re doing long operations and need to replay events in between I understand.

Eventually I’ll be sending those events over the mesh network… def something you don’t want to do in the handler!

Ok, well that makes a lot of sense then!

1 Like

If your goal is just passing small messages around, you can also use the “os_queue_t” wrapper from DeviceOS.

It uses the underline FreeRTOS queue and is safe to use from ISR.

os_queue_t test_queue = nullptr;

void setup()
{
        os_queue_create(&test_queue, sizeof(int), 5, nullptr))
}

void loop()
{
    if(nullptr != test_queue)
    {
        os_queue_take(test_queue, &ptr, 0, nullptr);
    }
}

void ISRHandler()
{
    int message = 1;
    if(nullptr != test_queue)
    {
        os_queue_put(test_queue, &message, 0, nullptr);
    }
}

*pseudo-code

1 Like

That sounds like a great idea! I'd much prefer an existing implementation than rolling my own if I can help it. Do you know where any documentation is for the DeviceOS wrappers for the FreeRTOS queue functions?

Oh, and in the FreeRTOS docs, I see this:

Note that interrupts must NOT use API functions that do not end in “FromISR”.

Do the DeviceOS functions have "FromISR" variants?

I'm not sure, but @ScruffR may know more about it.

That's true! But the guys at particle knew that and they handle it by checking if the call was made from a ISR or not:

int os_queue_put(os_queue_t queue, const void* item, system_tick_t delay, void*)
{
    if (HAL_IsISR()) {
        BaseType_t woken = pdFALSE;
        int res = xQueueSendFromISR(queue, item, &woken) != pdTRUE;
        portYIELD_FROM_ISR(woken);
        return res;
    } else {
        return xQueueSend(queue, item, delay)!=pdTRUE;
}

*from concurrent_hal.cpp

1 Like

Awesome, I’m going to swap out my implementation for os queues. Thank you for this solution!