PSoC5LP RTOS
Lab 08 — Binary & Counting Semaphores
FreeRTOS on PSoC 5LP · Lab Series. Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11. Events: Rotary Encoder (bursts) + Button (single). Pairs with Chapter 6 — Binary Semaphores & Chapter 7 — Counting Semaphores.
1. Introduction & Objectives
A binary semaphore remembers that at least one event happened. A counting semaphore remembers how many. This lab makes the difference physical: a rotary encoder turned fast fires a burst of pulses. A binary semaphore collapses that burst to one — losing counts; a counting semaphore keeps the whole tally. A button, firing one event at a time, shows where a binary semaphore is exactly enough.
After this lab you can:
- Create and use a binary semaphore (xSemaphoreCreateBinary) for a single event.
- Create and use a counting semaphore (xSemaphoreCreateCounting) to count events without loss.
- Give a semaphore from an ISR (xSemaphoreGiveFromISR) and take it in a task.
- Drain a counting semaphore (take with timeout 0) so a burst is fully counted.
- Explain when a burst is lost by a binary semaphore, and use a counting semaphore as a resource pool.
2. Prerequisites
Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit; RobotSoC Kit Rotary Encoder module, 1× push button, 1× LED + 220 Ω.
Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject.
Chapters (sync progress): Required — Chapter 6 (Binary Semaphores: the *_FromISR rules, ISR priority 2–7) and Chapter 7 (Counting Semaphores); Peripheral prerequisite — the rotary encoder is covered in the separate (non-FreeRTOS) Lab 16 (Rotary Encoder).
3. Reading Materials / References
- Chapter 6 slides — Binary Semaphores (ISR gives, task takes; portYIELD_FROM_ISR; ISR priority 2–7).
- Chapter 7 slides — Counting Semaphores (event tally vs resource pool; drain a burst).
- FreeRTOS API: xSemaphoreCreateBinary, xSemaphoreCreateCounting, xSemaphoreTake, xSemaphoreGiveFromISR, uxSemaphoreGetCount.
4. Background & Theory of Operation
A binary semaphore is a one-bit flag. It is empty or full. An ISR "gives" it (fills it) and a task "takes" it (empties it, blocking until it is full). It is perfect for a single "it happened" signal — a button press, a data-ready interrupt. But it saturates at one: if a second event arrives before the task takes the first, the extra give is simply dropped. A burst collapses to one.
A counting semaphore keeps a tally. Created with xSemaphoreCreateCounting(max, initial), it holds a count from 0 to max. Each give increments the count; each take decrements it. So a fast burst of N events (if N ≤ max) leaves the count at N, and the task can process all N. The two classic uses: count events — create with initial 0; the ISR gives once per event; the task takes once per event. Resource pool — create with initial N (all free); take to claim a permit, give to return it; models N identical resources.
Draining a burst. The task blocks on the first event with xSemaphoreTake(sem, portMAX_DELAY), then drains the rest with xSemaphoreTake(sem, 0) in a loop until it returns pdFALSE. Take once per loop and the count outruns you; drain and nothing is lost. uxSemaphoreGetCount reads how many are pending.
Why the encoder shows it best. A rotary encoder turned quickly fires pulses faster than a task can wake per pulse. With a binary semaphore the task sees "moved" but under-counts the steps. With a counting semaphore, every pulse increments the count and the drain loop recovers all of them — the step count is exact.
The ISR rules still apply (Chapter 6). Use xSemaphoreGiveFromISR (never the plain give) inside an ISR, clear the source first, end with portYIELD_FROM_ISR, and keep the ISR at priority 2–7.
Binary semaphore vs task notification. Lab 06 used a notification for the same ISR→task signal. A binary semaphore is the heavier equivalent — but it is a real object, so several tasks can wait on it, which a notification cannot do. Choose a notification for one waiter, a semaphore when more than one task waits or you need a counting tally.
5. RTOS Task Architecture
Two event sources. The Rotary Encoder fires bursts into a counting semaphore; the Encoder Task blocks for the first pulse then drains the rest, so every pulse is counted. The Button fires single events into a binary semaphore; the Button Task reacts once per press. Turn the encoder fast and a binary semaphore would saturate at 1 and lose pulses — the counting semaphore does not.
6. Binary vs. Counting — Over a Burst
Binary: the burst collapses to one (events lost):
Five pulses arrive fast; the binary sem caps at 1, so four gives are dropped. The task counts 1.
Counting: every pulse is kept:
Each give increments the count to 5; the task drains all five with take-timeout-0 and counts 5.
The only difference is the semaphore type. The same ISR, the same burst — one loses four events, the other loses none.
7. Schematic Circuit & Pin Configuration
- Encoder A/B → interrupt-capable pin(s); an Interrupt component on the pulse edge (isr_Enc).
- BTN → push button to GND on an interrupt pin (isr_Btn), internal pull-up (active-low).
- LED → GPIO + 220 Ω → GND.
- Assign the pins in <project>.cydwr → Pins.
8. PSoC Creator Configuration
- Encoder input + Interrupt component; button input + Interrupt component; an LED output.
- Set both interrupt priorities to 3 (within 2–7), or call isr_*_SetPriority(3) at startup (the template does this).
- FreeRTOSConfig.h (already set): configUSE_COUNTING_SEMAPHORES = 1, configMAX_SYSCALL_INTERRUPT_PRIORITY = (2<<5), heap_4.
9. Provided Template
You are given Lab08_Template_main.c:
- Given: all includes (note #include "FreeRTOS_PSoC.h"), both semaphore handles, both task handles, and a complete main() that creates the counting and binary semaphores, sets both ISR priorities into 2–7, starts the ISRs, and creates both tasks. Placeholders keep the project building and running.
- You write: Enc_ISR() / Btn_ISR() (clear → give → yield) and the two task bodies — the encoder task must drain with take-timeout-0.
- Do not change the boot sequence in main().
static void vEncoderTask(void *arg)
{
for (;;) {
xSemaphoreTake(xEncSem, portMAX_DELAY); /* wait for first */
do { g_steps++; } /* count it */
while (xSemaphoreTake(xEncSem, 0) == pdTRUE); /* drain the rest */
/* show g_steps */
}
}
10. Lab Experiments
Exp01 — Basic: count every pulse, react to every press. Write both ISRs and tasks. Turn the encoder: the counting semaphore + drain loop count every step. Press the button: the binary semaphore triggers one reaction. Expected: the step count matches the physical detents; each press gives exactly one reaction.
Exp02 — Advanced: make the binary semaphore lose the burst. Temporarily route the encoder pulses into a binary semaphore. Turn the encoder fast. Expected: the binary version under-counts — a fast turn registers far fewer steps than detents, because the burst saturated at 1. Compare side by side with the counting version and quantify the loss.
Exp03 — Challenge: counting semaphore as a resource pool. Create a counting semaphore with initial N (e.g. 3 permits). Have several tasks Take a permit before using a shared "resource" and Give it back after. Expected: at most N tasks hold the resource at once; a task blocks when all permits are out, and proceeds when one is returned. Read uxSemaphoreGetCount to show how many are free.
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | Encoder step count is exact (drained); each button press = one reaction. |
| Exp02 | Binary version under-counts a fast burst; you can quantify the loss vs counting. |
| Exp03 | At most N tasks hold the pooled resource; blocking/release works; free count shown. |
Submit: your ISRs and tasks, the Exp02 counting-vs-binary comparison (numbers), and the Exp03 pool behaviour.
12. Q&A & Troubleshooting
- Encoder under-counts even with the counting sem → you took once per loop instead of draining; add the take-timeout-0 loop.
- Count saturates / stops rising → the counting sem hit max; raise ENC_MAX_COUNT or drain faster.
- ISR re-fires forever → the interrupt source was not cleared first.
- HardFault / assert in an ISR → ISR priority is 0 or 1; set it to 2–7, and use only *_FromISR calls.
- Button gives several reactions → contact bounce; add a short debounce guard.
- Pool never blocks → you gave back more permits than you took; balance Take/Give.
13. Analysis Questions & Lab Report
- Why does a binary semaphore lose events in a burst, while a counting semaphore does not?
- What does "draining" mean, and why is take-with-timeout-0 the right tool for it?
- Give the two classic uses of a counting semaphore and how the initial count differs between them.
- When would you choose a binary semaphore over a task notification (Lab 06)?
Your report should include your ISRs and tasks, the Exp02 numbers, and answers to the four questions.