Ultrasonic Distance Sensing
The HC-SR04 ultrasonic distance sensor measures distance by emitting sound pulses and timing how long they take to bounce back from an obstacle.
Driver Working Principle
- Trigger Pulse: The microcontroller initiates a measurement by sending a High (
1) pulse of at least 10 µs to the sensor’s Trig pin. - Pulse Transmission: The sensor automatically emits eight 40kHz acoustic pulses.
- Echo Measurement: The sensor sets its Echo pin High (
1). It keeps it High until the reflected sound wave is received, then drops it Low (0). - Timeout: If no object is detected, the Echo pin automatically drops Low after approximately 38ms to prevent infinite blocking.
Timing & Prescaler Calculations
To measure the Echo pulse duration, we use a 16-bit hardware timer (such as Timer 4 on the ATmega2560) running at .
1. Timeout Counter Limit
A timeout of 38ms () corresponds to a CPU cycle count of:
A 16-bit timer can only count up to . We must choose a prescaler division factor such that :
The closest standard prescaler factor higher than 9.27 is .
The comparison threshold OCR4A for a 38ms timeout using a prescaler of 64 is:
2. Integer Distance Scaling
Floating-point division is extremely slow on 8-bit microcontrollers and is disabled in standard serial formatting libraries. We pre-calculate a static integer scale factor to convert timer counts directly into millimeters.
- Speed of sound at 20°C: .
- The sound travels to the object and back, so the physical distance is half the total travel time:
3. Overflow Protection Check
Before committing to integer math, verify that the numerator does not overflow the variable container type. The maximum expected count is the timeout limit ():
Since (), this calculation fits inside a standard 32-bit unsigned integer (uint32_t) without risk of overflow.
Driver Implementation
#include <avr/io.h>
#include <stdint.h>
#include <stdbool.h>
#define TRIG_PIN PORTA2
#define ECHO_PIN PINA3
static volatile bool timeout_flag = false;
void distance_init(void) {
DDRA |= (1 << TRIG_PIN); // Set Trig pin as Output
DDRA &= ~(1 << DDA3); // Set Echo pin as Input
// Configure Timer 4 in CTC mode (WGM42=1)
TCCR4A = 0; TCCR4B = 0;
TCCR4B |= (1 << WGM42);
OCR4A = 9500; // Timeout set at 38ms
TIMSK4 |= (1 << OCIE4A); // Enable Compare A interrupt
}
ISR(TIMER4_COMPA_vect) {
timeout_flag = true;
}
uint16_t distance_measure(void) {
timeout_flag = false;
TCNT4 = 0;
// 1. Send 10us Trigger Pulse
PORTA |= (1 << TRIG_PIN);
for (volatile uint8_t i = 0; i < 40; i++); // Short delay loop (~10us)
PORTA &= ~(1 << TRIG_PIN);
// 2. Wait for Echo pin to go High (with timeout check)
while (!(PINA & (1 << ECHO_PIN))) {
if (timeout_flag) return UINT16_MAX;
}
// 3. Start Timer 4 with prescaler 64
TCNT4 = 0;
TCCR4B |= (1 << CS41) | (1 << CS40); // N = 64
// 4. Wait for Echo pin to go Low (with timeout check)
while (PINA & (1 << ECHO_PIN)) {
if (timeout_flag) {
TCCR4B &= ~((1 << CS41) | (1 << CS40)); // Stop timer
return UINT16_MAX;
}
}
// 5. Stop Timer 4
TCCR4B &= ~((1 << CS41) | (1 << CS40));
uint16_t count = TCNT4;
// Convert counts to mm: (Count * 343) / 500
uint32_t distance = ((uint32_t)count * 343) / 500;
return (uint16_t)distance;
}
Why do we pre-calculate the integer fraction (343 / 500) rather than writing 'distance = (count * 64.0 / 16000000.0) * 171500.0;'?
Distance Scaling Equation
// Calculate distance in mm from timer count using integer scaling uint32_t distance = ((uint32_t)count * ) / ;
References & Further Reading
- Cytron Technologies. HC-SR04 Ultrasonic Sensor Product User’s Manual.
- VIA University College: Embedded Software 1 (ESW1) Lecture Notes - Section 11 (Case Study: Distance Sensor).