PSoC5LP RTOS
Lab 07 — Queues: Producer / Consumer
FreeRTOS on PSoC 5LP · Lab Series. Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11. Sensor: MF52D-103F-3950 NTC thermistor (RobotSoC Kit). Pairs with Chapter 8 — Queues.
1. Introduction & Objectives
A queue is the first FreeRTOS tool that moves data, not just a signal, between two tasks. In this lab, you build a classic producer/consumer pipeline: one task samples the NTC thermistor through the ADC and sends each temperature reading into a queue; a second task receives the readings and prints them over UART. The queue is the only connection between them.
After this lab you can:
- Create a queue of structures with xQueueCreate(length, sizeof(item)).
- Send and receive items by value, and explain why the data is copied, not shared by pointer.
- Use the three blocking timeouts — portMAX_DELAY, 0, and a tick count — on both send and receive.
- Reproduce and explain queue full, queue empty, timeout, and data loss by changing the producer and consumer rates.
- Describe how a queue decouples a producer from a consumer and absorbs bursts.
2. Prerequisites
Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit; RobotSoC Kit MF52D-103F-3950 NTC thermistor + one 10 kΩ resistor (voltage divider); USB-UART (KitProg) to a PC serial terminal. Alternative sensor: CDS-55 photoresistor + 10 kΩ (light level instead of temperature).
Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject; serial terminal (115200 8-N-1).
Chapters (sync progress): Required — Chapter 8 (Queues); Assumed — Chapters 0–2 (tasks, vTaskDelay, priority); Peripheral prerequisite — the ADC and the NTC thermistor are covered in the separate (non-FreeRTOS) lab, non-RTOS Lab 05 (Thermometer), MF52D-103F-3950, which you must have completed.
3. Reading Materials / References
- Chapter 8 slides — Queues (create, send, receive, by-value copy, full/empty).
- RobotSoC non-RTOS Lab 05 (Thermometer) — MF52D-103F-3950 NTC thermistor (divider wiring, Beta conversion).
- FreeRTOS API: xQueueCreate, xQueueSend, xQueueReceive, uxQueueMessagesWaiting.
- PSoC Creator component datasheets: ADC_SAR, UART.
4. Background & Theory of Operation
What a queue is. A FreeRTOS queue is a fixed-length FIFO that holds items of a fixed size. You set both when you create it: xQueueCreate(uxQueueLength, uxItemSize). Items leave in the order they arrived.
By-value copy. xQueueSend copies uxItemSize bytes into the queue; xQueueReceive copies them out into your buffer. The two tasks never share a pointer, so there is no race over the data and no lifetime problem — the sender's local can go out of scope right after the send. The rule that follows: never queue a pointer to a local variable; copy the value. Keep items small, because every send and receive copies the whole item.
Blocking and timeouts. Both calls take a timeout in ticks: portMAX_DELAY waits indefinitely; 0 does not wait and returns a failure code immediately if the queue is empty/full; N ticks waits a bounded time. Receive returns pdTRUE on success; send returns pdTRUE, or errQUEUE_FULL if it could not enqueue in time.
Decoupling and back-pressure. Because the queue buffers items, producer and consumer need not run in lock-step; a short burst is absorbed by the queue depth. But the queue is finite. If the producer is persistently faster, the queue fills and the send timeout decides what happens: send with 0 rejects the item (that reading is lost — gaps in the sequence numbers); send with portMAX_DELAY blocks the producer until a slot frees (no loss, but throttled to the consumer — back-pressure).
How the queue behaves over time. The diagram below traces the balanced case: the producer (green, top) sends a numbered reading every 100 ms, the consumer (blue, bottom) takes one every 100 ms, and the middle line is the queue depth. When the two rates match, the depth stays shallow, and nothing is lost. The overrun cases are shown in the experiments in §10.
Balanced: producer 100 ms, consumer 100 ms — queue stays shallow, no loss.
The sensor (not the focus, but needed). The NTC is wired as a divider — Vdda — NTC(10k) — node — 10k — GND — and the node goes to the ADC. The provided helper ntc_tempC10() turns raw counts into temperature (0.1 °C units) using the Beta equation from non-RTOS Lab 05 (Thermometer) (R0 = 10 kΩ @ 25 °C, Beta = 3950). Your job is the queue, not the thermistor math — just call the helper.
From an ISR. When the producer is an interrupt, use xQueueSendFromISR with portYIELD_FROM_ISR. This lab keeps the producer in a task.
5. RTOS Task Architecture
Two tasks, one queue. The NTC Sampling Task (priority 2) produces a Reading_t every 100 ms and sends it by value. The UART Display Task (priority 1) blocks on the queue and prints each item. This producer-to-consumer shape becomes one branch of the full system you assemble in Lab 18.
6. Schematic Circuit & Pin Configuration
- Divider: Vdda — NTC(10 kΩ) — node — R(10 kΩ) — GND; the node → analog input pin → ADC_SAR input.
- UART TX → KitProg USB-UART.
- Assign the analog and UART pins to your RobotSoC Kit's pins in <project>.cydwr → Pins.
- Photoresistor option: replace the NTC with the CDS-55 in the same divider position.
7. PSoC Creator Configuration
- ADC_SAR_1: 12-bit, single-ended, appropriate Vref, free-running (the template uses free-running).
- UART_1: 115200, 8-N-1, TX enabled.
- FreeRTOSConfig.h (already set in the BaseProject): configUSE_PREEMPTION = 1, heap_4, configMAX_PRIORITIES ≥ 3, configTOTAL_HEAP_SIZE large enough for the queue and two task stacks.
8. FreeRTOS API Reference
| API | Purpose |
|---|---|
| xQueueCreate(len, itemSize) | Create a FIFO of len items, each itemSize bytes. Returns NULL on failure. |
| xQueueSend(q, &item, wait) | Copy item to the back; block if full up to wait. Returns pdTRUE / errQUEUE_FULL. |
| xQueueReceive(q, &buf, wait) | Copy the front item into buf; block if empty up to wait. |
| uxQueueMessagesWaiting(q) | How many items are currently queued. |
| xQueueSendFromISR(q, &item, &woken) | ISR form of send (not used in this lab). |
9. Provided Template
You are given Lab07_Template_main.c:
- Given: all includes — note #include "FreeRTOS_PSoC.h", which defines FreeRTOS_Init() (it builds the PSoC 5LP interrupt vector table). Also the Reading_t struct, the provided ntc_tempC10() conversion helper, the queue and task handles, and a complete main() that starts the ADC and UART, creates the queue, creates both tasks (producer priority 2, consumer priority 1), and starts the scheduler. Both task functions contain a vTaskDelay placeholder so the project builds and runs immediately.
- You write: the bodies of vAdcSamplingTask() and vUartDisplayTask(), following the TODO(Exp01) comments, plus the rate/timeout changes for Exp02 and Exp03.
- Do not change the boot sequence in main().
#include "FreeRTOS.h"
#include "FreeRTOS_PSoC.h" /* FreeRTOS_Init(): builds the PSoC 5LP vector table */
#include "task.h"
#include "queue.h"
typedef struct { uint16_t seq; uint16_t raw; int16_t tC10; } Reading_t;
/* producer - you fill this body */
static void vAdcSamplingTask(void *arg)
{
for (;;)
{
/* TODO(Exp01): sample ADC -> r.tC10 = ntc_tempC10(c)
* -> xQueueSend(xReadingQ, &r, timeout)
* -> vTaskDelay(pdMS_TO_TICKS(100)); */
vTaskDelay(pdMS_TO_TICKS(100)); /* placeholder */
}
}
10. Lab Experiments
Exp01 — Basic: a working pipeline. Implement both task bodies. Producer samples every 100 ms and sends with portMAX_DELAY; consumer receives with portMAX_DELAY and prints #seq raw=.. XX.X C. Pinch the thermistor between your fingers and watch the temperature rise. Expected: readings print in order, sequence numbers with no gaps — the balanced timing diagram in §4 is this case.
Exp02 — Advanced: force the queue full. Slow the consumer to ~300 ms while the producer stays at 100 ms. Run twice: (1) producer send timeout 0 → gaps in the sequence numbers (dropped readings); log uxQueueMessagesWaiting() to see the queue sit full. (2) producer send timeout portMAX_DELAY → no gaps, producer throttled to the consumer (back-pressure). Expected: you can show the difference between loss and back-pressure from the UART log.
Exp02a — send timeout 0: the queue fills, and each send while full is dropped (red ×, gaps in the sequence).
Exp02b — send timeout portMAX_DELAY: the producer blocks (amber) until a slot frees — no loss, throttled to the consumer.
Exp03 — Challenge: measure the trade-off. Use a finite send timeout and a dropped-item counter. Sweep READING_QUEUE_LEN (e.g. 1, 4, 16) at a fixed overrun ratio and record the drop count for each. (Optional: add a second producer — a button-event item — into the same queue and keep the consumer correct.) Expected: a short table of queue length vs drops with a one-line explanation of the latency/loss trade-off.
Exp03 — deeper queue defers loss but does not remove it while the consumer stays slower (producer 100 ms, consumer 300 ms, timeout 0).
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | Readings print in FIFO order, no sequence gaps; °C rises when you warm the thermistor. |
| Exp02a | With timeout 0 under overrun, sequence gaps appear; queue observed full. |
| Exp02b | With portMAX_DELAY, no gaps; producer rate throttled to the consumer. |
| Exp03 | Table of queue length vs drop count submitted, with a short analysis. |
Submit: your two task bodies, UART logs for Exp01/02, and the Exp03 table.
12. Q&A & Troubleshooting
- xReadingQ is NULL / configASSERT fires → heap too small; raise configTOTAL_HEAP_SIZE.
- Garbage values received → you queued a pointer to a local, or the item size is wrong; send the struct by value with sizeof(Reading_t).
- Temperature looks wrong / inverted → check the divider orientation (NTC on the Vdda side) and that ADC_FULL/Vref match your ADC setup.
- Nothing prints → UART not started, wrong baud, or the consumer never runs.
- Consumer starves → the producer never blocks/delays; a higher-priority task that never yields starves lower ones.
13. Analysis Questions & Lab Report
- Why does a queue copy the item instead of sharing a pointer? Give one bug this prevents.
- For a full queue, describe the outcome for each send timeout: 0, N ticks, portMAX_DELAY.
- How does queue length trade latency against data loss under a fixed overrun?
- When would you choose a queue over a semaphore or a task notification?
Your report should include your code, the required logs/table, and answers to the four questions.