Bare-metal firmware, real-time systems, and hardware–software boundaries.
ECE undergraduate @ PES University Bangalore.
libopencm3 — Merged Upstream Contribution
Production firmware library · 200+ contributors · Used in drones, satellites, and industrial sensors worldwide
- Implemented HSI clock presets (2 MHz, 16 MHz, 32 MHz) for the STM32L0 family by reading the RM0377 reference manual and calculating PLL multipliers, dividers, and flash wait-states from first principles
- Fixed RCC symbol visibility regressions affecting clock-tree configuration across STM32L0-based products
- Contribution is live in the mainline library — not a fork, not a patch, a merged upstream change
Hardware: STM32L072CZ (Cortex-M0+, 32 MHz) · MPU6050 IMU · B-L072Z-LRWAN1
The core problem: acquire IMU data continuously at full rate on a low-power MCU without dropping samples, while keeping CPU headroom for compute tasks.
Architecture:
- DMA-driven I2C acquisition — CPU completely removed from the data path
- Ping-pong double buffering (
buffer[2][N]) — DMA writes one buffer while CPU processes the other, zero sample loss - FreeRTOS binary semaphores — compute task blocks until DMA completion ISR signals via
xSemaphoreGiveFromISR(), deterministic wake-up - Result: sustained acquisition at full sample rate with < 40% CPU utilization
Host: Fedora Linux · i686-elf-gcc cross-compiler · QEMU + GDB
Target: x86 (i686) bare metal
Built from a blank 512-byte MBR bootloader upward — no tutorials, no starter code.
Implemented subsystems:
- Custom bootloader: real mode → protected mode transition via CR0, GDT segment descriptors for kernel/user rings
- Interrupt subsystem: IDT gates, dual 8259 PIC remapped to 0x20 to isolate hardware IRQs from CPU exceptions
- PS/2 keyboard driver via port-mapped I/O with assembly → C ISR handshake
- Custom kernel heap allocator built on an explicit block-entry table — hand-crafted allocator with no libc dependency
# Build and run
./build.sh
qemu-system-x86_64 -hda ./bin/os.binHardware: ESP32 · MPU6050 · Custom MOSFET motor drivers (IRLZ44N) · Custom Lark1 PCB (KiCad)
The real engineering here is the architectural evolution — not just the final version.
| Version | Architecture | Problem it solved |
|---|---|---|
| LOS 1.0 | Superloop | Validated sensor reads, BT parsing, motor PWM |
| LOS 1.1 | Superloop + full IMU + PID | Failed — unacceptable latency from blocking IO |
| LOS 2.0 | FreeRTOS, 2 tasks | Separated BT IO from IMU+PID, eliminated starvation |
| LOS 2.1 | FreeRTOS, 4 tasks + queues | BT read → IMU read → complementary filter → PID, fully decoupled via RTOS queues |
Key insight from this project: a superloop that handles Bluetooth IO, IMU reads, and a PID loop in one thread produces unacceptable and non-deterministic control latency. Partitioning into priority-assigned RTOS tasks with queue-based IPC bounds worst-case latency and allows the control loop to preempt IO tasks.
Custom driver built from the MPU6050 datasheet — register-level I2C, START/STOP/ACK handling, 16-bit signed integer reconstruction from raw 8-bit registers, and direct integration with DMA pipelines used in Project Black Box.
Custom bootloader for STM32L0 with vector table relocation, clock initialization, and clean jump_to_main handover to application firmware. Uses libopencm3 as submodule with local clock fixes — the same fixes later upstreamed to mainline.
| Domain | Details |
|---|---|
| Languages | C, x86 Assembly, ARM Assembly |
| RTOS | FreeRTOS — tasks, queues, semaphores, timers, priority assignment |
| MCUs | STM32L0/F4, ESP32, x86 (i686) |
| Peripherals | DMA, I2C, SPI, UART, NVIC, PIC 8259, PS/2, PWM |
| Low-level | Custom bootloaders, linker scripts, memory layout, custom allocators, clock trees |
| Tools | GCC cross-toolchain (i686-elf, arm-none-eabi), QEMU, GDB, OpenOCD, STM32CubeIDE, KiCad, Git |
| Hardware | PCB design in KiCad, perfboard prototyping, soldering |
- DMA over polling — remove the CPU from data paths wherever timing matters
- Partition, don't cram — RTOS task boundaries exist to bound jitter, not just organize code
- Push upstream — fix libraries at the source instead of maintaining local forks
- Measure CPU headroom — a system at 90% utilization has no margin; design for < 50% on critical paths
