PSoC5LP RTOS
Lab 09 — Mutex & Priority Inversion

FreeRTOS on PSoC 5LP · Lab Series. Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11. Shared resource: Character LCD 20×4. Pairs with Chapter 5 — Mutexes (and Chapter 19 — priority inversion).


1. Introduction & Objectives

A critical section (Chapter 4) protects a few lines by turning interrupts off — too blunt for a long operation like writing an LCD. A mutex gives mutual exclusion without disabling interrupts, and adds priority inheritance. This lab shares one LCD between two tasks (garbled without a lock, clean with a mutex), then reproduces the classic priority inversion and shows how a mutex's inheritance fixes it.

After this lab you can:

  • Create and use a mutex (xSemaphoreCreateMutex, Take/Give) to guard a shared resource.
  • Protect a long operation (a full LCD line) without disabling interrupts.
  • Explain priority inversion and how priority inheritance bounds it.
  • Choose between a mutex, a critical section, and a binary semaphore.
  • Avoid mutex deadlock by locking in a consistent order.

2. Prerequisites

Hardware: CY8CKIT-059 PSoC 5LP Prototyping Kit; RobotSoC Kit Character LCD 20×4; optional 3 timing pins + logic analyzer for the inversion demo.

Software: PSoC Creator 4.4 with the FreeRTOS V11 BaseProject.

Chapters (sync progress): Required — Chapter 5 (Mutexes); Chapter 19 (priority inversion, deep dive); builds on Chapter 4 (critical sections). Peripheral note — the Character LCD is assumed known (no separate non-FreeRTOS lab); use your existing LCD driver.

3. Reading Materials / References

  • Chapter 5 slides — Mutexes (Take/Give, protect a resource, which lock to use).
  • Chapter 19 slides — Debugging (priority inversion, the two fixes, the free one).
  • FreeRTOS API: xSemaphoreCreateMutex, xSemaphoreTake, xSemaphoreGive, xSemaphoreCreateRecursiveMutex.

4. Background & Theory of Operation

A mutex is a lock with an owner. xSemaphoreCreateMutex() makes one. A task calls xSemaphoreTake(m, wait) before using the resource and xSemaphoreGive(m) after; only one task owns it at a time, and others block until it is free. Unlike a critical section, a mutex leaves interrupts on — so it is the right tool for a long protected operation such as a full LCD write (many position + print calls).

Why the LCD needs it. Two tasks each do LCD_Position(row, 0); LCD_PrintString(...). If a context switch lands between the position and the print, the second task moves the cursor and the first task's text prints in the wrong place — the display garbles. Wrapping the whole position+print sequence in Take/Give makes each line write as one owner, so the lines stay clean.

Priority inversion. A subtle failure of a plain lock. Suppose Low holds the lock, High then blocks waiting for it, and Medium (which does not need the lock) becomes Ready. Medium pre-empts Low, so Low cannot finish and release the lock — and High, a higher-priority task, is now effectively waiting behind Medium. With a plain binary semaphore (no owner, no inheritance) Medium can stretch High's wait without bound.

Priority inheritance — the free fix. A FreeRTOS mutex solves this automatically. While High waits for a mutex that Low holds, the kernel temporarily lifts Low to High's priority. Now Medium cannot pre-empt Low; Low finishes the critical section quickly, gives the mutex, and drops back — and High runs. The wait is short and bounded, and you wrote no extra code. This is why you use a mutex, not a binary semaphore, for mutual exclusion.

Which lock to use.

  • Critical section — a few instructions over a tiny shared variable; masks interrupts; no blocking inside.
  • Mutex — a longer protected operation (a resource/bus); interrupts stay on; has an owner + inheritance; task context only.
  • Binary semaphore — signalling an event, not locking; no owner, no inheritance; can be given from an ISR.

Deadlock. If two tasks take two mutexes in opposite orders, each can hold one and wait for the other forever. The simplest prevention: always lock multiple mutexes in the same order. Also: always give a mutex back, and never take one in an ISR (mutexes are task-only).

5. RTOS Task Architecture

ONE LCD, TWO WRITERS — GUARDED BY A MUTEX Temp Task writes row 0: "TEMP:25C" Take mutex → write → Give Clock Task writes row 1: "TIME:12:00" Take mutex → write → Give lcdMutex one owner at a time + priority inheritance Character LCD 20×4 TEMP:25C TIME:12:00 owner writes Without the mutex: the two tasks interleave their position+print calls and the display garbles. With it: each writes its whole line as one owner. A mutex gives mutual exclusion WITHOUT disabling interrupts, and adds priority inheritance.

The Temp Task (row 0) and Clock Task (row 1) both write the one LCD. Each takes lcdMutex, writes its whole line, and gives it back, so only one task drives the LCD at a time. The mutex adds priority inheritance, which matters the moment three priorities contend for it (next section).

6. Priority Inversion vs. Inheritance — Over Time

Without inheritance (a plain binary-semaphore lock): unbounded inversion:

Priority Inversion - lock WITHOUT inheritance (binary semaphore) Low holds the lock; High blocks on it; Medium preempts Low, so High waits behind Medium (lower than High!) High (p3) Med (p2) Low (p1) 0 100 200 300 400 500 600 time (ms) L takes lock H blocks on lock M preempts Low High BLOCKED - waiting behind Medium (inversion) L gives lock -> H runs Conceptual / idealized. A binary semaphore has no owner and no inheritance, so Medium can stretch High’s wait without bound.

Low holds the lock; High blocks; Medium pre-empts Low, so High waits behind Medium — the inversion.

With a mutex (priority inheritance): the wait is short and bounded:

Priority Inheritance - a MUTEX fixes it while High waits, Low is lifted to High’s priority, so Medium cannot preempt; Low finishes fast and High runs High (p3) Med (p2) Low (p1) 0 100 200 300 400 500 600 time (ms) L takes mutex H blocks -> L inherits p3 L gives mutex Med runs after Conceptual / idealized. A FreeRTOS mutex does this automatically - nothing to code. Medium can no longer delay High.

While High waits, Low is lifted to High's priority, so Medium cannot pre-empt; Low finishes and High runs.

The only change is the lock type. A binary semaphore lets Medium delay High indefinitely; a mutex's inheritance stops it — for free.

7. Schematic Circuit & Pin Configuration

  • LCD 20×4 → its data/control pins (or an I2C backpack) to the configured GPIOs.
  • (Optional) TP_H / TP_M / TP_L → three timing pins for the inversion demo.
  • Assign the pins in <project>.cydwr → Pins.

8. PSoC Creator Configuration

  • A Character LCD component (or your I2C LCD driver). Optional timing pins.
  • FreeRTOSConfig.h (already set): configUSE_MUTEXES = 1, configUSE_PREEMPTION = 1, heap_4.

9. Provided Template

You are given Lab09_Template_main.c:

  • Given: all includes (note #include "FreeRTOS_PSoC.h"), the mutex handle, both task handles, and a complete main() that creates the mutex and both tasks. A lcd_line() helper and the two tasks have placeholders so the project builds and runs (initially unprotected, so you can see the garble first).
  • You write: the Take/Give inside lcd_line(), and the lcd_line calls in each task.
  • Do not change the boot sequence in main().
static void lcd_line(uint8_t row, const char *str)
{
    xSemaphoreTake(lcdMutex, portMAX_DELAY);   /* claim the LCD      */
    LCD_Position(row, 0u);
    LCD_PrintString(str);                       /* whole line as owner */
    xSemaphoreGive(lcdMutex);                   /* release it         */
}

10. Lab Experiments

Exp01 — Basic: share the LCD. First run lcd_line() without the mutex and watch the two rows garble (interleaved text). Then add Take/Give around the whole position+print and confirm each line is written cleanly. Expected: garbled → clean; you can explain why a mutex (not a critical section) fits a long LCD write.

Exp02 — Advanced: reproduce and fix priority inversion. Build a three-task scenario (High/Med/Low) contending for a shared lock, with timing pins. First use a binary semaphore as the lock (no inheritance): a Medium task that does not need the lock extends High's wait — inversion, visible on the analyzer. Then switch the lock to a mutex and show inheritance bounds High's wait. Expected: captured inversion with the binary lock, and a bounded wait with the mutex.

Exp03 — Challenge: deadlock, then order the locks. Give two tasks two mutexes and have them take the pair in opposite orders — produce a deadlock (both stuck). Then impose a single consistent lock order and show it cannot deadlock. Expected: a reproduced deadlock and the lock-ordering fix, with a one-line explanation.

11. Testing & Verification

#Pass condition
Exp01 Without the mutex the display garbles; with it each line is clean.
Exp02 Binary-lock version shows inversion (High delayed by Medium); mutex bounds the wait.
Exp03 Opposite lock orders deadlock; a consistent order prevents it.

Submit: your lcd_line() and tasks, the Exp02 inversion-vs-inheritance captures, and the Exp03 deadlock + fix.

12. Q&A & Troubleshooting

  • Display still garbles with the mutex → only part of the sequence is inside Take/Give; wrap the whole position+print.
  • Everything blocks forever → a task took the mutex and never gave it back; always pair Take with Give.
  • High task is delayed by an unrelated task → you used a binary semaphore (no inheritance); use a mutex.
  • Two tasks freeze together → deadlock from inconsistent lock order; lock multiple mutexes in one fixed order.
  • Assert/fault taking a mutex in an ISR → mutexes are task-only; use a semaphore to signal from an ISR.

13. Analysis Questions & Lab Report

  1. Why is a mutex better than a critical section for a long LCD write?
  2. Describe priority inversion in your own words, and how priority inheritance bounds it.
  3. Why does a binary semaphore not prevent inversion, but a mutex does?
  4. How does consistent lock ordering prevent deadlock?

Your report should include your lcd_line() and tasks, the Exp02 captures, and answers to the four questions.

© 2026 Air Supply Information Center (Air Supply BBS)