I recently wrote code that is meant to measure the range detected by the arduino uno ultrasonic module HC-SR04. However, the range value printed is constantly wrong. The values for range are never accurate and frequently jump 3000cm to 5cm.
I would greatly appreciate some help with this DIY project. Thank you everyone! :D
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
// Define macros for bit manipulation
#define bitSet(reg, n) (reg |= 1 << n)
#define bitRead(reg, n) (reg & (1 << n))
#define bitClear(reg, n) (reg &= ~(1 << n))
// Define constants for clock frequency and baud rate
#define FOSC 16000000
#define BAUD 9600
#define MYUBRR FOSC / 16 / BAUD - 1
// Define pin assignments
#define TRIG_PIN PB1
#define ECHO_PIN PB2
// Declare volatile variables for storing time and distance
volatile unsigned long startTime = 0;
volatile unsigned long endTime = 0;
volatile unsigned long duration = 0;
volatile int distance = 0;
// Function prototypes
void USART_Init(unsigned int ubrr);
void USART_Transmit(unsigned char data);
void txString(const char *pStr);
// Function to initialize USART communication
void USART_Init(unsigned int ubrr)
{
// Set baud rate
UBRR0H = (unsigned char)(ubrr >> 8);
UBRR0L = (unsigned char)ubrr;
// Enable receiver and transmitter
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
// Set frame format: 8 data bits, 1 stop bit
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
// Function to transmit a single character over USART
void USART_Transmit(unsigned char data)
{
// Wait for empty transmit buffer
while (!(UCSR0A & (1 << UDRE0)))
;
// Put data into buffer, sends the data
UDR0 = data;
}
// Function to transmit a string of characters over USART
void txString(const char *pStr)
{
while (*pStr != '\0')
{
USART_Transmit(*pStr);
pStr++;
}
}
// Interrupt service routine for Timer 1 capture event
ISR(TIMER1_CAPT_vect)
{
if (bitRead(PINB, ECHO_PIN)) {
startTime = ICR1;
} else {
endTime = ICR1;
duration = endTime - startTime;
distance = duration * 0.017 / 2;
}
}
int main(void)
{
// Initialize USART communication
USART_Init(MYUBRR);
// Set trigger pin as output and echo pin as input
DDRB |= (1 << TRIG_PIN);
DDRB &= ~(1 << ECHO_PIN);
// Set timer 1 for input capture mode with noise canceler and prescaler of 8
TCCR1B |= (1 << ICNC1) | (1 << ICES1) | (1 << CS11);
// Enable input capture interrupt
TIMSK1 |= (1 << ICIE1);
// Enable global interrupts
sei();
// Main loop
while (1)
{
// Trigger ultrasonic sensor
PORTB |= (1 << TRIG_PIN);
_delay_us(10);
PORTB &= ~(1 << TRIG_PIN);
// Transmit distance over USART
char formattedDistance[20];
sprintf(formattedDistance, "Distance: %d cm\n", distance);
txString(formattedDistance);
_delay_ms(500); // Wait for 1 second
}
}
The problem lies in the interrupt routine:
ICR1is a 16-bit register. But you assign it to anunsigned long, which is 32-bit. If you have a timer overflow, there is no natural overflow of your duration measurement.You have to declare
startTime,endTime, anddurationasunsigned ints. It is best practice to use#include stdint.hso you can specify the size of your integers, which may vary from one platform to the other.As Lundin pointed out, keep your ISR as short as possible, e.g.
And then convert to a distance in your main loop. Usually on MCUs without a floating point unit (FPU), you would want to make the calculations in integer arithmetics.
By the way, I would also explicitely reset, start, and stop the timer when you want to measure, so you are sure that the time is measured at the right moment.