Embedded · 4 months · CMU

Embedded Vehicle

An embedded car powered by a custom-built real-time operating system.

STM32 Cortex-M4RMS scheduler<10% steady-state error

Overview

I created a real-time embedded vehicle from the bare metal up, combining custom hardware design, low-level firmware, a custom operating system, and closed-loop motor control. A preemptive RTOS guarantees strict execution deadlines for the PID loop, UART telemetry, and LCD updates.

The system is built around an STM32 Cortex-M4 microcontroller on a custom two-layer PCB that routes power and signals to L298 H-bridge motor drivers, LM1084 voltage regulators, quadrature encoders, servos, and an I2C LCD.

Assembled embedded vehicle with STM32 carrier PCB, motor drivers, and encoder wheels on the chassis
Vehicle assembly

PCB Design

I designed the board in Autodesk Fusion to interface the STM32 Nucleo with the vehicle's peripherals. The PCB handles 5V and 3.3V power distribution, motor-control routing, encoder inputs, and user I/O.

Vehicle carrier board PCB layout with STM32 Nucleo, L298 motor driver, encoder and LCD headers
Carrier board layout, STM32 Nucleo

Firmware

I wrote the system bootloader in ARMv7-M assembly to initialize the exception vector table and stack pointers, then built bare-metal C drivers using MMIO for PWM, ADC, I2C, and interrupt-driven UART.

Preemptive multitasking runs on a dual-stack context-switching architecture built on the ARM Cortex-M exception model: each thread executes in user mode on its own stack (PSP), while the kernel runs in handler mode on a shared stack (MSP). SysTick tracks thread budgets and pends PendSV; the handler below saves the running thread's context, consults the thread control block (TCB) list to select the next runnable thread by priority, and restores it.

Context switch flow from SysTick through the PendSV handler Thread A runs on the user stack until SysTick pends PendSV. The PendSV handler, running in kernel mode on the main stack, saves Thread A's context, consults the thread control block list, selects the next runnable thread by priority, and restores its context so Thread B resumes on the user stack. USER MODE HANDLER MODE Thread A running on PSP Thread B resumes on PSP PendSV HANDLER 1. Save PSP context 2. Push state, update TCB 3. Select highest-priority thread 4. Restore new context SysTick tick TCB list sorted by priority Context switch flow from SysTick through the PendSV handler Thread A runs on the user stack until SysTick pends PendSV. The PendSV handler, running in kernel mode on the main stack, saves Thread A's context, consults the thread control block list, selects the next runnable thread by priority, and restores its context so Thread B resumes on the user stack. USER MODE Thread A running on PSP HANDLER MODE SysTick tick PendSV HANDLER 1. Save PSP context 2. Push state, update TCB 3. Select highest-priority thread 4. Restore new context TCB list sorted by priority USER MODE Thread B resumes on PSP
Context switch

Task priorities are assigned by a rate-monotonic scheduler validated against the utilization bound, with an SVC path bridging user and kernel space, newlib support for printf and malloc, and priority-inheritance mutexes (HLP/IPCP) to bound priority inversion.

Motor Control

The vehicle decodes wheel position through EXTI quadrature encoders and drives the motors through hardware-timer PWM. I tuned a closed-loop PID controller to maintain wheel speed with under 10% steady-state error, while UART telemetry and I2C LCD updates ran as concurrent real-time tasks without missing critical execution deadlines.

Results

The scheduler kept every task on time: the PID loop held under 10% steady-state error while UART telemetry and LCD updates shared the CPU without a single missed deadline. The simulator below reconstructs that same three-task schedule (PID, UART, and LCD at their real periods and compute budgets) so you can explore why it stays schedulable, and how close it runs to the theoretical limit.

Steady-state error
<10% on closed-loop PID wheel speed control
Task utilization
71.25% of CPU time across PID, UART, and LCD threads
UB schedulability bound
77.98% for 3 tasks, comfortably met
Missed deadlines
None observed during operation
Use the steppers to change task timing