As part of my robot project I need a small microcontroller to do som basic task like measuring distance using a HR-SR04 sensor. I plan to use a raspberry pi as main controller for that robot and as is runs a linux kernel is is not realtime and might be inaccurate for this application. The goal is to have the microcontroller send the measured distance over i2c to the raspberry pi.
The Ultrasonic module works as explained in the following diagram:
Lot’s of different examples regarding the HR-SR04 and microcontrollers can be found on the internet. Unfortunately a lot of them are incomplete or contain bad code. So here is yet another example 🙂
Below you can find a very basic schema with a attiny2313, the HR-SR04, an ISP connector and a led with a resistor.
#define F_CPU 8000000UL
uint16_t rising, falling;
if (TCCR1B & (1<<ICES1)) // On rising edge
TCCR1B &= ~(1<<ICES1); // Next time detect falling edge
rising = ICR1; // Save current count
else // On falling edge
TCCR1B |= (1<<ICES1); // Next time detect falling edge
falling = ICR1; // Save current count
counts = (uint32_t)falling - (uint32_t)rising;
dist = (uint32_t)us_per_count * counts * 10 / 58; // useconds * 10 / 58 to get distance in mm
// Is triggered on timer match of OCR1A
// Generate a 12us pulse to trigger the HR-SR04
PORTD ^= (1<<PIN5);
PORTD ^= (1<<PIN5);
DDRB |= (1<<PIN0); // PORTB PIN 0 as output
DDRD |= (1<<PIN5); // PORTD PIN 5 as output
// TIMER1 INIT
TCCR1B |= (1<<ICNC1) | (1<<CS10) | (1<<CS11) | (1<<WGM12); //ICNC1: noise filter, CS10 and CS11: devide clock by 64, WGM12: Clear Timer on Compare (CTC) Mode
TIMSK |= (1<<ICIE1) | (1<<OCIE1A); //TICIE1: Timer 1 Input Capture Interrupt Enable, OCIE1A: Output Compare Interrupt Enable 1 A
TCCR1B |= (1<<ICES1); // Input capture in rising edge
// "we suggest to use over 60ms measurement cycle, in order to prevent trigger signal to the echo signal."
// source: http://www.robosoftsystems.co.in/wikidocs/index.php?title=Ultrasonic_Sensor_(HC-SR04)
// 70ms cycle: 8MHz/64 = 125000 counts/second => 125000/10 = 12500 counts/100ms => 12500/100*70 = 8750 counts/70ms
OCR1A = 8750;
us_per_count = 8; // 8MHz/64 = 125000 counts/second => 1000000/125000
if (dist < 300)
PORTB |= (1<<PIN0); // Set PIN 0 on PORTB
PORTB &= ~(1<<PIN0); // Reset PIN 0 on PORTB
The logic that is executed is straight forward. Distance measurements are done in cycles of 70ms. The cycle is managed using the 16 bit timer of the attiny. See line 43, 44 and 49. This makes the timer go round in an endless loop of 70ms. That’s because the timer is reset (CTC mode on line 43) as soon as it reaches the value of OCR1A line 49.
At the end of each timer loop the method ISR(TIMER1_COMPA_vect) is called. That method sends the pulse to trigger the HR-SR04 module, line 32-34. The pulse is not required to take exactly 10µs. I found that under 10µs is a problem and that 20µs still worked. 12µs is a safe choice.
The echo pin of the HR-SR04 is connected to the ICP (Input Capture Interrupt) pin of the Attiny2313. Line 45 tells the Attiny2313 to call method ISR(TIMER1_CAPT_vect) when the signal on the ICP pin goes from 0 to 1 (raising edge). The method contains a if stament to see if it was called during a raising edge or falling edge. On a raising edge it switches the ICP to falling edge detection and then stores the start time. On a falling edge it switches the ICP to raising edge detection and gets the end time to calculate the distance in millimeters.
Then in the main while loop the dist value is evaluated to turn the led on or off. That’s all! In the next article I will discuss how to connect multiple HR-SR04 sensors but still use one timer.