Fixing C code for arduino uno board - ultrasonic module HC-SR04

51 Views Asked by At

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
  }
}

1

There are 1 best solutions below

2
emiled On

The problem lies in the interrupt routine: ICR1 is a 16-bit register. But you assign it to an unsigned 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, and duration as unsigned ints. It is best practice to use #include stdint.h so 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.

#include <stdint.h>

volatile uint16_t duration = 0;
volatile uint16_t startTime = 0;
volatile uint16_t endTime = 0;

// 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;
  }
}

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.

// Convert to distance
uint16_t distance = (duration * 17) / 1000 / 2;

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.