What it does: This project consists of a small autonomous vehicle capable of maintaining a constant safe distance from a target object or vehicle in front of it.
Its purpose: The main goal is to implement a real-time closed-loop control system (Adaptive Cruise Control) on an 8-bit microcontroller. It demonstrates bare-metal embedded programming by utilizing hardware interrupts, hardware timers, and custom serial protocols, completely avoiding high-level abstractions like Arduino libraries.
The starting idea: The idea is inspired by modern Advanced Driver Assistance Systems (ADAS) used in the automotive industry to prevent rear-end collisions and improve driving comfort.
Why it is useful: It serves as a highly practical demonstration of low-level hardware control, bridging the gap between theoretical micro-processor architecture and real-world physical applications.
The system uses an ultrasonic sensor mounted on the front to continuously measure the distance to the obstacle ahead.
Based on the calculated distance, a proportional control algorithm calculates the necessary speed adjustments to maintain a safe gap. The system smoothly interpolates the motor speed using hardware PWM based on the target's distance. If the target is too close (under 25 cm), the vehicle applies the brakes. If the target is within the tracking range (25 cm - 85 cm), it adjusts the speed dynamically to follow it.
Block Diagram:
List of components:
Electrical Schematic:
#include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> #include <stdio.h> #define PM_BAUD 9600 #define TRIG_PIN PC0 #define ECHO_PIN PC1 #define LATCH_PIN PB4 #define CLK_PIN PD4 #define EN_PIN PD7 #define DATA_PIN PB0 #define M1_A 2 #define M1_B 3 #define M2_A 1 #define M2_B 4 volatile uint16_t timer_val = 0; volatile uint8_t echo_done = 0; static int _usart0_putchar(char c, FILE *stream) { if (c == '\n') _usart0_putchar('\r', stream); while (!(UCSR0A & (1<<UDRE0))); UDR0 = c; return 0; } static FILE USART0_stdout = FDEV_SETUP_STREAM(_usart0_putchar, NULL, _FDEV_SETUP_WRITE); void usart_init() { UBRR0H = (unsigned char)(103>>8); //br9600 UBRR0L = (unsigned char)103; UCSR0B = (1<<RXEN0) | (1<<TXEN0); //act transmisia UCSR0C = (1<<USBS0) | (3<<UCSZ00); //8b stdout = &USART0_stdout; } void shift_out_data(uint8_t data) { PORTB &= ~(1<<LATCH_PIN); for(int i=0; i<8; i++) { //pt fiecare bit PORTD &= ~(1<<CLK_PIN); if(data & (1 << (7-i))) { PORTB |= (1<<DATA_PIN); //punem val d } else { PORTB &= ~(1<<DATA_PIN); } PORTD |= (1<<CLK_PIN); //ridicam si coboram pin clock } PORTB |= (1<<LATCH_PIN); } void motors_init() { DDRB |= (1<<LATCH_PIN) | (1<<DATA_PIN) | (1<<PB3); DDRD |= (1<<CLK_PIN) | (1<<EN_PIN) | (1<<PD3); PORTD &= ~(1<<EN_PIN); TCCR2A = (1<<COM2A1) | (1<<COM2B1) | (1<<WGM21) | (1<<WGM20); TCCR2B = (1<<CS22); OCR2A = 0; // punem viteza in registrii OCR2B = 0; shift_out_data(0); } void set_motor_dir(int m1_fwd, int m1_rev, int m2_fwd, int m2_rev) { uint8_t shift_data = 0; if (m1_fwd) shift_data |= (1<<M1_A); if (m1_rev) shift_data |= (1<<M1_B); if (m2_fwd) shift_data |= (1<<M2_A); if (m2_rev) shift_data |= (1<<M2_B); shift_out_data(shift_data); } void sonar_init() { DDRC |= (1<<TRIG_PIN); //pc0 output DDRC &= ~(1<<ECHO_PIN); TCCR1A = 0; TCCR1B = (1<<CS11); PCICR |= (1<<PCIE1); PCMSK1 |= (1<<PCINT9); } ISR(PCINT1_vect) { if (PINC & (1<<ECHO_PIN)) { TCNT1 = 0; } else { timer_val = TCNT1; echo_done = 1; } } int get_distance() { echo_done = 0; PORTC &= ~(1<<TRIG_PIN); _delay_us(2); PORTC |= (1<<TRIG_PIN); _delay_us(10); PORTC &= ~(1<<TRIG_PIN); uint32_t timeout = 60000; while(!echo_done && timeout > 0) { timeout--; } if(timeout == 0) return 400; return timer_val / 116; } int map_speed(int x, int in_min, int in_max, int out_min, int out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } int main() { usart_init(); motors_init(); sonar_init(); sei(); printf("ACC System Bare Metal Started\n"); int safe_dist = 25; int max_dist = 85; while(1) { int dist = get_distance(); if(dist == 0 || dist > 400) dist = 400; printf("Dist: %d cm\n", dist); if (dist > max_dist) { set_motor_dir(0, 0, 0, 0); OCR2A = 0; OCR2B = 0; } else if (dist > safe_dist) { int speed = map_speed(dist, safe_dist, max_dist, 115, 220); set_motor_dir(1, 0, 1, 0); OCR2A = speed; OCR2B = speed; } else { set_motor_dir(0, 0, 0, 0); OCR2A = 0; OCR2B = 0; } _delay_ms(50); } return 0; }
Development Environment:
Libraries and 3rd-party sources:
Algorithms and Hardware Features utilized:
The system reacts accurately and smoothly to dynamic environments.
Final Project Photo:
Implementing this project in a bare-metal C environment provided profound insights into the internal workings of the ATmega328P microcontroller. Stripping away the Arduino framework forced a deeper understanding of memory mapping, the crucial difference between polling and hardware interrupts, and the complexity of generating custom serial signals (bit-banging). The project successfully replicates an industrial ADAS concept on an 8-bit architecture.
Hardware Resources:
Software Resources: