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.
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.
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
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