Creating a HC-SR04 driver for NuttX

In this post I will explain how I did the HC-SR04 driver for NuttX.

I think this is the first time I try to explain how to create a driver for NuttX, oh wait! It is not! The first time was this post: NuttX driver to control a single LED

But this time it will be different, instead of just posting the code here, I will try to explain how I came with the driver. So, let to get started.

First I searched for the HC-SR04 datasheet, it was easy to find:

Click to access HCSR04.pdf

Reading it I discovered that I need to send a pulse of 10uS in the pin Trig to start the conversion, pretty easy:

static int hcsr04_start_measuring(FAR struct hcsr04_dev_s *priv)
{
  /* Configure the interruption */

  priv->rising = true;
  priv->config->irq_setmode(priv->config, priv->rising);
  priv->config->irq_enable(priv->config, true);

  /* Send to 10uS trigger pulse */

  priv->config->set_trigger(priv->config, true);
  usleep(10);
  priv->config->set_trigger(priv->config, false);

  return 0;
}

After sending the Trigger pulse we will receive a pulse in the Echo pin with the width encoding the measured distance.

Because we need to measure width of a pulse the first idea that came to my mind was to use the Input Capture of STM32 Timer (I’m using the STM32F103-Minimum board), but I want this driver to be generic, then I decided to use an ordinary GPIO interrupt pin of STM32.

I can setup STM32 to detect level changing (rising edge and falling edge), but some microcontrollers don’t support it, you need to select rising edge or falling edge, not both at same time. We need to work around it because I need this driver to be generic enough to work on these “poor man” MCUs.

Then to get it working I need to implement a ping-pong approach: first setup the GPIO pin to detect rising edge of signal and inside the ISR (Interrupt Service Routine or just interrupt handler) it needs to change the pin configuration to detect interruption in the falling edge.

So at hcsr04_int_handler I did it:

if (priv->rising)
{
  /* Get the clock ticks from the free running timer */

  priv->time_start_pulse = priv->config->get_clock(priv->config);

  /* Now we need to wait for the falling edge interruption */

  priv->rising = false;
  priv->config->irq_setmode(priv->config, priv->rising);
  priv->config->irq_enable(priv->config, true);
}
else
{
  /* Get the clock ticks from the free running timer */

  priv->time_finish_pulse = priv->config->get_clock(priv->config);

  /* Disable interruptions */

  priv->config->irq_enable(priv->config, false);

  /* Convertion is done */

  sem_post(&priv->conv_donesem);
}

So now you got the idea how it works, we just need to understand the magic under the hood. These functions irq_enable(), irq_setmode(), get_clock(), etc, are in fact “function pointers” to board specific functions:

/* Interrupt configuration data structure */

struct hcsr04_config_s
{
  CODE int (*irq_attach)(FAR struct hcsr04_config_s * state, xcpt_t isr,
                         FAR void *arg);
  CODE void (*irq_enable)(FAR const struct hcsr04_config_s *state,
                          bool enable);
  CODE void (*irq_clear)(FAR const struct hcsr04_config_s *state);
  CODE void (*irq_setmode)(FAR struct hcsr04_config_s *state, bool risemode);
  CODE void (*set_trigger)(FAR const struct hcsr04_config_s *state, bool on);
  CODE int64_t (*get_clock)(FAR const struct hcsr04_config_s *state);
};

The real functions are at “boards/arm/stm32/common/src/stm32_hcsr04.c”

This way we can create a generic abstraction and let it work with any board/microcontroller.

As you probably already figured-out, it is the irq_setmode() function that defines if the GPIO pin (connected to Echo pin of HC-SR04 module) will detect rising edge signal or falling edge signal:

/* Setup the interruption mode: Rising or Falling */

static void hcsr04_irq_setmode(FAR struct hcsr04_config_s *state, bool rise_mode)
{
  FAR struct stm32_hcsr04config_s *priv =
  (FAR struct stm32_hcsr04config_s *)state;

  if (rise_mode)
    {
      priv->rising = true;
      priv->falling = false;
    }
  else
    {
      priv->rising = false;
      priv->falling = true;
    }
}

Ok, we just need the “rising” variable to store the current edge mode, because the “falling” variable always will be the inverse. But let us to use both for didactic reason. Now irq_enable() just call stm32_gpiosetevent() passing these edge parameters and enabling it (passing the driver’s interrupt handler) or disabling it (passing a NULL).

/* Enable or disable the GPIO interrupt */

static void hcsr04_irq_enable(FAR const struct hcsr04_config_s *state, bool enable)
{
  FAR struct stm32_hcsr04config_s *priv =
                                   (FAR struct stm32_hcsr04config_s *)state;

  sinfo("%d\n", enable);

  (void)stm32_gpiosetevent(GPIO_HCSR04_INT, priv->rising, priv->falling, true,
                           enable ? priv->isr : NULL, priv->arg);
}

Finally the get_clock() just returns the current clock tick of a free running timer configured to run at 1 microsecond resolution. I just need to convert the value of returned “timespec” variable “ts” to microseconds, multiplying tv_sec by 1 million (1 second has 1000000 us) and dividing tv_nsec by 1000 (1us = 1000 ns), see:

/* Return the current Free Running clock tick */

static int64_t hcsr04_get_clock(FAR const struct hcsr04_config_s *state)
{
  /* Get the time from free running timer */

  stm32_freerun_counter(&g_freerun, &ts);

  /* Return time in microseconds */

  return ((ts.tv_sec * 1000000) + (ts.tv_nsec / 1000));
}

So, I think you got the idea how this driver works. It is was easy to implement. You can read the complete code here: “nuttx/drivers/sensors/hc_sr04.c

You can see this driver working on this video tutorial:

One thought on “Creating a HC-SR04 driver for NuttX

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s