Embedded systems
An autonomous buggy that drives itself.
A line-following robot buggy built on the STM32 NUCLEO-F091RC in bare-metal C: a Kitronik chassis driven through an L298 on PWM, steering from two LDR line sensors, stopping for obstacles on an ultrasonic sensor, and signalling with indicator, brake and headlight LEDs. It was built by a five-person engineering team; our part was testing, debugging, calibration and integration.
- F091RC
- STM32F0 Cortex-M0 host
- Bare-metal C
- Direct register access and CMSIS
- 2× LDR
- ADC line sensing (PA4, PB0)
- HC-SR04
- Ultrasonic hazard stop
- TIM3
- Dual-motor PWM control
- 0.57m/s
- Top speed (calculated)
Watch it follow the line.
How it works
Two downward-facing LDRs are read through the F091’s ADC (PA4 and PB0) and thresholded to digital. The buggy steers by cutting PWM to whichever motor sits over the line, driving differential speed on the L298 through Timer 3. An HC-SR04 ultrasonic sensor (trigger PA0, echo PA1) measures range as echo / 58; inside a set distance the buggy halts, lights its hazard LEDs and crawls until the obstacle clears. Mode changes run off EXTI button interrupts, and a millisecond timer gives a five-second stop-and-resume. Amber corner LEDs flash as indicators, rear LEDs act as brake lights and white LEDs run as headlights.
Our role
In a team of five, we owned testing, debugging and calibration, and the integration that brought the subsystems together.
- Unit-tested the sensor, motor and LED routines in isolation by checking register state
- Integrated hazard detection, line-following and button handling into one main loop
- Calibrated LDR thresholds, PWM base speed and ultrasonic echo timing on the 48 MHz clock
- Field-tested across lighting and track conditions, tuning until the response was smooth
- Brought the front end up on a breadboard before committing to the custom PCB
Line-following & hazard logic.
// Read both LDRs on the ADC, threshold to digital
ldr1 = adc_read(CH4); // PA4
ldr2 = adc_read(CH8); // PB0
uint8_t a = (ldr1/4095.0f >= 0.5f);
uint8_t b = (ldr2/4095.0f >= 0.5f);
uint32_t base = 840; // ~0.2 m/s
if (a && !b) { L = base; R = 0; } // bear left
else if (!a && b) { L = 0; R = base; } // bear right
else { L = base/2; R = base/2; } // on the line
TIM3->CCR1 = L; TIM3->CCR2 = R; // drive the L298TRIG_LOW; delayuS(2);
TRIG_HIGH; delayuS(10); TRIG_LOW; // 10 us pulse
float distance = read_echo(400000) / 58.0f;
if (distance <= 2.5f) { // obstacle ahead
LED_D2_ON; LED_D3_ON; LED_D4_ON; // hazard lights
TIM3->CCR1 = 0; TIM3->CCR2 = 0; // full stop
while (read_echo(400000)/58.0f <= 2.5f) // crawl until clear
{ TIM3->CCR1 = 300; TIM3->CCR2 = 300; delay_ms(50); }
}