Introduction

Ultrasonic ranging module HC - SR04 provides 2cm - 400cm non-contact measurement function, the ranging accuracy can reach to 3mm. The modules include ultrasonic transmitters, receiver and control circuit. The basic principle of work:

  • (1) Using IO trigger for at least 10us high-level signal,
  • (2) The Module automatically sends eight 40 kHz and detect whether there is a pulse signal back.
  • (3) IF the signal back, through high level, time of high output IO duration is the time from sending ultrasonic to returning.
  • Test distance = (high level time×velocity of sound (340M/S) / 2

While all of this does sound simple, I didn’t find a lot on the best way to implement this on Zephyr since precise timing is involved. So I decided to make one on my own.

Hardware

  • 96Boards Carbon: Since the sensor has a HIGH value of 5v, Carbon was the closest I could get with 3v3. 3v3 is enough to signal the Trigger pin without any level shifting but we’d still need a voltage divider on the Echo pin.
  • HC-SR04: For obvious reasons.
  • Voltage Divider: it’s basically a combination of resistors to lower the voltage from 5v to 3v3 on the Echo Pin so we do not end up blowing our Carbon board. Here is a diagram showing how to set it up:

HCSR04 5v to 3v3 Voltage Divider

  • Wiring it up is pretty simple, 5v goes to the 5v pin, GND goes to the ground pin, Trigger is connected to “PA1” on the carbon and the other end of the voltage divider for Echo pin is connected to “PA3” on the carbon.

Counting the cycles

  • OK, so lets get to the code
#include <zephyr.h>
#include <misc/printk.h>
#include <device.h>
#include <gpio.h>
#include <sys_clock.h>
#include <misc/util.h>
#include <limits.h>

#define GPIO_OUT_PIN		1
#define GPIO_INT_PIN		3
#define PORT		"GPIOA"

void main(void)
{
  uint32_t cycles_spent;
  uint32_t nanseconds_spent;
  uint32_t val;
  uint32_t cm;
  uint32_t stop_time;
  uint32_t start_time;
  struct device *dev;
  dev = device_get_binding(PORT);
  gpio_pin_configure(dev, GPIO_OUT_PIN, GPIO_DIR_OUT);
  gpio_pin_configure(dev, GPIO_INT_PIN, (GPIO_DIR_IN | GPIO_INT_EDGE| GPIO_INT_ACTIVE_HIGH | GPIO_INT_DEBOUNCE));

  while (1) {
    gpio_pin_write(dev, GPIO_OUT_PIN, 1);
    k_sleep(K_MSEC(10));
    gpio_pin_write(dev, GPIO_OUT_PIN, 0);
    do {
			gpio_pin_read(dev, GPIO_INT_PIN, &val);
		} while (val == 0);
		start_time = k_cycle_get_32();

		do {
			gpio_pin_read(dev, GPIO_INT_PIN, &val);
      stop_time = k_cycle_get_32();
      cycles_spent = stop_time - start_time;
      if (cycles_spent > 1266720) //260cm for 84MHz (((MAX_RANGE * 58000) / 1000000000) * (CLOCK * 1000000))
      {
        break;
      }
		} while (val == 1);
    nanseconds_spent = SYS_CLOCK_HW_CYCLES_TO_NS(cycles_spent);
    cm = nanseconds_spent / 58000;
    printk("%d\n", cm);
    k_sleep(100);
}
}

1) Initialization

  • Header files
    #include <zephyr.h>
    #include <misc/printk.h>
    #include <device.h>
    #include <gpio.h>
    #include <sys_clock.h>
    #include <misc/util.h>
    #include <limits.h>
    
  • Pin Definition
    #define GPIO_OUT_PIN		1
    #define GPIO_INT_PIN		3
    #define PORT		"GPIOA"
    
  • Variable Initialization
    void main(void)
    {
    uint32_t cycles_spent;
    uint32_t nanseconds_spent;
    uint32_t val;
    uint32_t cm;
    uint32_t stop_time;
    uint32_t start_time;
    
  • Initializing the pins and setting IO direction
    struct device *dev;
    dev = device_get_binding(PORT);
    gpio_pin_configure(dev, GPIO_OUT_PIN, GPIO_DIR_OUT);
    gpio_pin_configure(dev, GPIO_INT_PIN, (GPIO_DIR_IN | GPIO_INT_EDGE| GPIO_INT_ACTIVE_HIGH | GPIO_INT_DEBOUNCE));
    

2) The Main Logic

  • First, there is the main while loop to get things running infinitely
  • Timing Method: To time the ECHO pin HIGH state, we could have used the Normal Precision Method which uses the system clock to determine how much time has elapsed between two points in time. But, we will be using the High Precision Method which counts the cycles passed between two points in time, since our logic is basically to count the exact time duration.
  • First we set the trigger pin high for 10 micro-seconds
    gpio_pin_write(dev, GPIO_OUT_PIN, 1);
    k_sleep(K_MSEC(10));
    gpio_pin_write(dev, GPIO_OUT_PIN, 0);
    
  • Next our first do while loop runs till the Echo Pin is LOW, and breaks as soon as it gets HIGH, following that we start counting the cycles
    do {
          gpio_pin_read(dev, GPIO_INT_PIN, &val);
      } while (val == 0);
      start_time = k_cycle_get_32();
    
  • Our next do while loop runs till the ECHO pin remains HIGH: Inside this do while block, we calculate the number of cycles spent and constantly check a value using if to limit the max range. We use the formula (((MAX_RANGE * 58000) / 1000000000) * (CLOCK * 1000000)) and replace MAX_RANGE with the needed value in cm and replace CLOCK with the clock speed of the Microprocessor.
    do {
      gpio_pin_read(dev, GPIO_INT_PIN, &val);
    stop_time = k_cycle_get_32();
    cycles_spent = stop_time - start_time;
    if (cycles_spent > 1266720) //260cm for 84MHz (((MAX_RANGE * 58000) / 1000000000) * (CLOCK * 1000000))
    {
    break;
    }
    } while (val == 1);
    
  • Finally we use the SYS_CLOCK_HW_CYCLES_TO_NS function to calculate the duration while ECHO pin was HIGH, it returns the result in nanoseconds. and then device the value by 58000 to get the result in cm, keeping in mind the speed of sound in air is 340m/sec
    nanseconds_spent = SYS_CLOCK_HW_CYCLES_TO_NS(cycles_spent);
    cm = nanseconds_spent / 58000;
    printk("%d\n", cm);
    

References