PSoC5LP RTOS
Lab 21 — Waiting on Many Sources: Queue Sets & Advanced Notifications
FreeRTOS on PSoC 5LP · Lab Series. Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11. Topics: Queue Sets (xQueueSelectFromSet) + the full Task Notification API (bits / counter / mailbox). Pairs with Chapter 8 — Queues & Chapter 7 — Task Notifications.
1. Introduction & Objectives
So far every task has blocked on one thing — a single queue, a single semaphore. But real firmware often has a task that must react to whichever of several sources fires first: a UART command, a new sensor reading, a button press. Polling each in turn wastes CPU and adds latency; spawning one task per source wastes RAM and scatters the logic. FreeRTOS gives two better tools. A queue set lets one task block on a group of queues and semaphores at once. And the task notification — a 32-bit word every task already owns — can act as event bits, a counter, or a one-deep mailbox, a channel that costs no extra RAM.
After this lab you can:
- Block one task on several sources with a queue set (xQueueCreateSet, xQueueAddToSet, xQueueSelectFromSet).
- Size a queue set correctly and obey its rules (receive from the returned handle; one set per member; no mutexes).
- Use the notification value three ways: eSetBits (event group), eIncrement (counter), eSetValueWithOverwrite (mailbox).
- Receive a notification value with xTaskNotifyWait and clear bits on entry/exit.
- Choose between a queue set, a notification, and a plain queue by the shape of the problem.
2. Prerequisites
Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit; RobotSoC Kit a UART (commands), an analog input (sensor), a button; an LED for feedback.
Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject.
Chapters (sync progress): Required — Chapter 8 (Queues) and Chapter 7 (Task Notifications); Related — Lab 08/14/15 (queues and semaphores), Lab 10 (event-group bits), Lab 20 (sync patterns).
3. Reading Materials / References
- Chapter 8 slides — Queues (blocking receive; queue vs semaphore).
- Chapter 7 slides — Task Notifications (the per-task value, actions, xTaskNotifyWait).
- FreeRTOS API: xQueueCreateSet, xQueueAddToSet, xQueueRemoveFromSet, xQueueSelectFromSet, xTaskNotify, xTaskNotifyWait, ulTaskNotifyTake.
4. Background & Theory of Operation
The problem: one waiter, many sources. A dispatcher that serves a command queue and a sensor queue and a button needs to block until any of them is ready, then handle exactly that one. Blocking on each in sequence cannot work (blocking on the command queue ignores a waiting sensor reading); polling with zero timeouts burns CPU and adds jitter; a task per source triples the stacks and spreads the logic out.
Queue sets. A queue set solves this. xQueueCreateSet(len) makes a set; xQueueAddToSet(member, set) adds a queue or semaphore to it. A task then blocks on the whole group with xQueueSelectFromSet(set, timeout), which returns the handle of a member that is ready. Crucially, the select does not read the item — you must then xQueueReceive/xSemaphoreTake from that exact handle, which consumes it. The rules that keep this correct:
- Size it right: the set length must be ≥ the sum of all member queue lengths plus one for each binary semaphore. Too small and an add fails silently at runtime.
- One set per member: a queue or semaphore can belong to only one set at a time.
- Read the returned handle: always receive from the handle xQueueSelectFromSet gave you, with a zero block time (it is guaranteed ready).
- No mutexes: do not add a mutex to a set — its priority-inheritance semantics do not fit. Queues, binary and counting semaphores are fine.
- Needs configUSE_QUEUE_SETS = 1.
Task notifications — a zero-cost channel. Every task has a built-in 32-bit notification value and a state, with no separate object to create. xTaskNotify(handle, value, action) writes it, and the action decides the meaning:
- eSetBits — OR the value's bits into the word. The receiver reads a bit-set, so the notification behaves like a lightweight event group (for one receiver).
- eIncrement — add one. The word becomes a counter, so the notification behaves like a counting semaphore.
- eSetValueWithOverwrite — replace the word with the value. The receiver always sees the latest value — a one-deep mailbox. (eSetValueWithoutOverwrite instead fails if the previous value is unread.)
The receiver calls xTaskNotifyWait(bitsToClearOnEntry, bitsToClearOnExit, &value, timeout) to block until notified and read the word, clearing bits as asked; or ulTaskNotifyTake for the counter/binary style (as in Lab 07/12). Notifications are faster and lighter than a queue or semaphore — but they target one receiver and cannot queue several values (except the counter's running total), so they are for signalling and single-value hand-off, not buffered data.
Indexed notifications (optional). Newer FreeRTOS gives each task an array of notification slots (xTaskNotifyIndexed, configTASK_NOTIFICATION_ARRAY_ENTRIES), so one task can have independent notification channels — useful when two unrelated signals would otherwise collide in the single word.
Choosing the tool. Many sources, one waiter → a queue set. A single value, bit-set, or count to one task → a notification. Buffered data that decouples producer and consumer rates → a queue. Matching the mechanism to the shape keeps the design small and the latency low.
5. RTOS Task Architecture
Three producers feed a command queue, a sensor queue, and a button semaphore, all added to one queue set. The Dispatcher task blocks once on the set with xQueueSelectFromSet, finds which member fired, receives from that handle, and acts — in arrival order, with a single blocking point. The panels give the queue-set rules and the three notification actions (event bits / counter / mailbox).
6. Mechanisms Over Time
Queue set — one task wakes for whichever source fires, in arrival order:
xQueueSelectFromSet returns the ready handle; the dispatcher reads that one and handles the event — no polling, no task-per-source.
One notification value, three ways to use it:
The same 32-bit word accumulates bits (eSetBits), counts up (eIncrement), or keeps the latest value (eSetValueWithOverwrite) — chosen by the action.
The two diagrams carry the lesson: pick the primitive that matches the shape of the waiting. A set collapses many sources into one wait; a notification turns a task's own word into the exact channel you need.
7. Schematic Circuit & Pin Configuration
- UART → the command source (feeds the command queue).
- Analog in → the sensor source (feeds the sensor queue).
- Button → a GPIO/ISR that gives the button semaphore.
- LED → optional feedback for the handled event.
- Assign the pins in <project>.cydwr → Pins.
8. PSoC Creator Configuration
- A UART, an ADC, and a button input; an optional LED.
- FreeRTOSConfig.h (already set): configUSE_QUEUE_SETS = 1, configUSE_TASK_NOTIFICATIONS = 1, configUSE_COUNTING_SEMAPHORES = 1, heap_4, configUSE_TIME_SLICING = 1 (course default).
- For indexed notifications (optional), set configTASK_NOTIFICATION_ARRAY_ENTRIES ≥ 2.
9. Provided Template
You are given Lab21_Template_main.c:
- Given: all includes (note #include "FreeRTOS_PSoC.h"), the two queues, the button semaphore, the queue set (already sized and with all members added), the worker handle, and a complete main(); plus producer stubs.
- You write: the xQueueSelectFromSet loop in vDispatcherTask() (receive from the returned handle), the xTaskNotifyWait decode in vWorkerTask(), and the producer's xTaskNotify calls.
- Do not change the boot sequence in main().
/* dispatcher: block on the whole set, serve whoever fired */
QueueSetMemberHandle_t who = xQueueSelectFromSet(qset, portMAX_DELAY);
if (who == qCmd) { xQueueReceive(qCmd, &cmd, 0); /* handle command */ }
else if (who == qSensor) { xQueueReceive(qSensor, &reading, 0); /* handle reading */ }
else if (who == sBtn) { xSemaphoreTake(sBtn, 0); /* handle press */ }
/* worker: one notification value, decoded */
if (xTaskNotifyWait(0, 0xFFFFFFFFu, &val, portMAX_DELAY) == pdTRUE) { /* use val */ }
10. Lab Experiments
Exp01 — Basic: the multi-source dispatcher. Complete the dispatcher so one task serves the command queue, the sensor queue, and the button via xQueueSelectFromSet, receiving from the returned handle each time. Drive all three sources at once. Expected: every event is handled exactly once, in arrival order, from a single blocking point — no polling and no lost events.
Exp02 — Advanced: the notification three ways. Make the producer notify the worker and the worker decode it, in three variants: eSetBits (set a FAULT/READY bit and have the worker test bits), eIncrement (the worker reads how many events happened since it last ran), and eSetValueWithOverwrite (the worker sees only the latest value). Expected: a working event-group-style, counter-style, and mailbox-style channel from the same notification word, and a clear statement of when each fits.
Exp03 — Challenge: set sizing and the pitfalls. Deliberately under-size the queue set and show the failure; fix it with the correct ≥ Σ lengths + 1 per semaphore rule. Then show what happens if you forget to receive from the returned handle (the select keeps returning the same ready member). (Optional: give the worker two independent signals with indexed notifications instead of sharing one word.) Expected: you can size a set correctly, explain why the handle must be read, and justify indexed notifications.
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | One dispatcher serves all three sources via the set; each event handled once, in order. |
| Exp02 | The notification works as bits, counter, and mailbox; each decoded correctly. |
| Exp03 | Under-sizing the set is shown and fixed; the read-the-handle rule is demonstrated. |
Submit: your dispatcher and worker code, your three notification variants, and your Exp03 sizing demonstration.
12. Q&A & Troubleshooting
- xQueueAddToSet fails → the set is too small; size it ≥ Σ queue lengths + 1 per binary semaphore.
- Select keeps returning the same member → you did not receive from the returned handle; always read it (zero timeout).
- An event is missed → you received from a different queue than the one the select returned; read the exact handle given back.
- xQueueSelectFromSet link/compile error → configUSE_QUEUE_SETS = 0; enable it.
- Notification bits pile up unexpectedly → you did not clear them; use the bitsToClearOnExit argument of xTaskNotifyWait.
- A second signal clobbers the first → two unrelated signals share the one notification word; use indexed notifications or a queue.
- Mutex in a set misbehaves → mutexes must not be added to a queue set; protect the resource separately.
13. Analysis Questions & Lab Report
- What does xQueueSelectFromSet return, and why must you then receive from that exact handle?
- How do you size a queue set, and what happens if it is too small?
- Show how one notification word can act as an event group, a counting semaphore, and a mailbox — which action gives which?
- When would you choose a queue set, a notification, or a plain queue? Give an example of each.
Your report should include your dispatcher and worker code, your notification variants, and answers to the four questions.