/* Producer–Consumer, ISR VARIANT #2 — deferring with a TASK NOTIFICATION (the light way).
* Wokwi, ST Nucleo-C031C6. Library Manager -> + Add -> "STM32duino FreeRTOS".
*
* A take-off on the queue version. Same three-beat ISR handshake — capture, defer, yield — but
* when the task only needs to know "it happened" (not receive a struct of data), a direct
* TASK NOTIFICATION is faster and uses less RAM than a queue. FreeRTOS calls it the lightweight
* way to unblock a task from an ISR.
*
* THE THREE ISR -> TASK HAND-OFF TOOLS, AND WHEN TO REACH FOR EACH:
* - Queue : copies a full struct of DATA. Use when the task needs the payload.
* - Task notification : a "doorbell" (or ONE 32-bit value). Fastest + lightest. Use to SIGNAL.
* - Binary semaphore : the classic signal-an-event; same idea, heavier than a notification.
* This sketch shows the task notification.
*
* Faked trigger : a LOW-priority IsrSource task stands in for the hardware IRQ, so the
* woken HIGH-priority worker visibly preempts it via the yield.
*/
#include <STM32FreeRTOS.h>
static TaskHandle_t workerHandle = NULL; // notifications are sent to a SPECIFIC task handle
/* ---- WORKER: blocks on its notification, then does the heavy work. HIGH priority. ---- */
static void WorkerTask(void *pv) {
(void)pv;
for (;;) {
/* Block until notified. pdTRUE = clear the count to 0 on return (acts binary/latching).
* The return value is how many notifications had arrived — a free event counter. */
uint32_t events = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
Serial.print(" [WORKER] woke ("); Serial.print(events);
Serial.println(" event(s) since last wake) -> processing");
vTaskDelay(pdMS_TO_TICKS(120)); // slow work, in TASK context (never in the ISR)
Serial.println(" [WORKER] done");
}
}
/* ---- THE ISR BODY: capture -> notify -> yield. No queue, no data copy. ---- */
static void SimulatedISR(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 1) start pdFALSE
vTaskNotifyGiveFromISR(workerHandle, &xHigherPriorityTaskWoken); // 2) FromISR "doorbell" to the worker
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 3) higher-prio task woke -> switch NOW
}
/* ---- FAKE TRIGGER: stands in for the hardware firing the IRQ (LOW priority on purpose). ---- */
static void IsrSourceTask(void *pv) {
(void)pv; uint32_t seq = 0;
for (;;) {
vTaskDelay(pdMS_TO_TICKS(1000)); // ~1 Hz "interrupt"
seq++;
Serial.print("[IRQ] fired seq="); Serial.print(seq);
Serial.println(" (ISR: notify + return fast)");
SimulatedISR(); // on real HW: EXTIx_IRQHandler() / TIMx_IRQHandler()
Serial.print("[IRQ] handler returned seq="); Serial.println(seq);
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(WorkerTask, "WORKER", 256, NULL, 3, &workerHandle); // capture the handle to notify
xTaskCreate(IsrSourceTask, "IRQSRC", 256, NULL, 1, NULL);
vTaskStartScheduler();
}
void loop() {}
/* ================= NOTIFICATION vs QUEUE — the trade-off =================
* A notification is faster and needs no queue RAM, but it carries no per-event payload: you get a
* COUNT of how many fired (great — a queue would have to DROP when full; a notification just counts),
* but not the data of each one. Need the data? Use the queue version. Need one number? Use
* xTaskNotifyFromISR(handle, value, eSetValueWithOverwrite, &xHTW) — but a burst overwrites, so it
* holds at most one value. Rule: notification to SIGNAL, queue to carry a STREAM of data.
*
* TRY IT: in IsrSourceTask, fire SimulatedISR() 3x in quick succession (10 ms apart) before the
* 1 s delay. Because the worker is slow, watch it wake and report "3 event(s)" — no queue, no drops. */