← Back to Embedded C series
🔌
Applications
Applied · Prereq: lesson 23

24. Capstone — Control LED·Timer via UART Commands

The final capstone. We weave together the pieces — USART (19)·interrupts (18)·ring buffer (22)·SysTick (16)·GPIO (14)·command parsing/modularity (22·23) — into one small firmware. Type on/off/blink/help in a PC terminal and the board parses it to control the LED and timer. The design key is role separation — the interrupt only receives, main only processes, the parser only parses. Each module is independently testable, and the command parser is verified on PC.

capstoneintegrationcommand parserring bufferproducer-consumerfirmware
Duration
~2–3 hours
Level
📊 Advanced
Prerequisite
🎯 Lesson 23
OUTCOME
Integrate USART·interrupt·ring buffer·SysTick·GPIO·parser into one firmware, completing a cleanly structured command-control firmware via producer/consumer separation and pure-module unit tests.

What you'll learn

  • 1Integrate several peripherals·modules into one firmware
  • 2Separate the roles of interrupt (receive) and main (process) (producer/consumer)
  • 3Gather UART input with the ring buffer and assemble lines
  • 4Parse commands and connect them to LED/timer actions
  • 5Split the parser into a module and unit-test it on PC

Introduction

From lesson 1's Hello, Embedded C! through 8051·STM32 registers, interrupts·comms·timers, and debounce·ring buffer·HAL patterns — this caps the journey. After it, you can write firmware that is "direct-register yet cleanly structured".

Key concepts

1) Overall architecture and modules

c
[PC terminal] --UART--> RX interrupt --push--> [ring buffer] --pop--> main loop
                                                          │
                          assemble line → command_parse() → handle_command()
                                                          │
                          LED(PC13) / blink mode / UART response
          SysTick(1ms) ──(toggle every 500ms if blink mode)──> LED

Pure modules (ringbuffer·command) are chip-independent and test on PC directly. Only main.c handles hardware setup·integration.

2) Producer/consumer split and command protocol

CommandActionResponse
onLED on, blink offLED ON
offLED off, blink offLED OFF
blinkauto-blink every 500msBLINK mode
helpcommand listcmds: ...
ℹ️

The RX interrupt (producer) only rb_push (short); main (consumer) rb_pop and confirms a line at \n/\r to parse·execute. blink_mode·g_ms are ISR·main shared, so volatile.

Core example

c
/* command.c — pure parser (hardware-independent, PC-testable) */
command_t command_parse(const char *line) {
    if (line[0] == '\0')      return CMD_NONE;
    if (streq(line, "on"))    return CMD_ON;
    if (streq(line, "off"))   return CMD_OFF;
    if (streq(line, "blink")) return CMD_BLINK;
    if (streq(line, "help"))  return CMD_HELP;
    return CMD_UNKNOWN;
}
c
/* main.c — integration (excerpt) */
while (1) {
    if (rb_pop(&rx_rb, &c)) {
        if (c == '\n' || c == '\r') {
            if (idx > 0u) { line[idx]='\0'; handle_command(command_parse(line)); idx=0u; }
        } else if (idx < LINE_MAX-1u) line[idx++] = (char)c;
    }
}

Parser check: "on"→ON, "blink"→BLINK, "xyz"→UNKNOWN, ""→NONE. Build: gcc pc_test.c command.c -o pc_test. Type on/off/blink/help in the simulator's UART #1 to verify LED·blink·responses.

Common mistakes

Q. Commands get no response.

A. A line is confirmed only at a line end (\n/\r). Check the terminal sends a newline on Enter, and that RXNEIE and NVIC_EnableIRQ(USART1_IRQn) are on.

Q. Fast typing drops characters.

A. Check main calls rb_pop often enough and the ring buffer is big enough. Heavy work in the ISR misses the next byte (ISR push only).

Q. Testing the parser on the board is tedious.

A. That's why the parser is a pure module. Build command.c with pc_test.c via gcc to verify logic first, then flash the board.

Summary

  • The capstone weaves USART·interrupt·ring buffer·SysTick·GPIO·parser into one
  • The interrupt only receives (push); main does pop·assemble·parse·execute (split)
  • Pure modules (ringbuffer/command) are unit-tested on PC, then flashed
  • A command protocol lets you control·extend the firmware externally
  • Direct register control and clean structure coexist — 24 lessons complete!

Exercises

  1. Add "fast"/"slow" commands that change the blink rate in the parser and handler
  2. Extend parsing to commands with arguments (e.g. "pwm 50")
  3. Sketch how to attach a next topic (I2C/SPI sensor·RTOS·DMA) to this capstone
Example code / lecture materials

All lecture materials and example code (with homework and answers) are openly available on GitHub.

View on GitHub ↗