Month: August 2017

NuttX Channel #016: Using the HC-SR04 to measure distance

I create a new video explaining how to use the ranging sensor HC-SR04 on NuttX:

Advertisements

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:
https://cdn.sparkfun.com/datasheets/Sensors/Proximity/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 distance measured.

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 “nuttx/configs/stm32f103-minimum/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

Compiling sigrok and pulseview

Install the dependencies:

$ sudo apt-get install automake libudev-dev checkinstall libglib2.0-dev libftdi-dev sdcc python3-dev qt-sdk libboost-system-dev libboost-thread-dev libboost-filesystem-dev libqt5svg5-dev libglibmm-2.4-dev libboost1.58-all-dev

Clone the repositories:

$ git clone git://sigrok.org/libsigrok
$ git clone git://sigrok.org/libsigrokdecode
$ git clone git://sigrok.org/sigrok-cli
$ git clone git://sigrok.org/pulseview

Compile them all:

$ cd libsigrok
$ ./autogen.sh
$ ./configure
$ make
$ sudo checkinstall

$ cd libsigrokdecode
$ ./autogen.sh
$ ./configure
$ make
$ sudo checkinstall

$ cd sigrok-cli
$ ./autogen.sh
$ ./configure
$ make
$ sudo checkinstall

$ cd pulseview
$ cmake .
$ make
$ sudo checkinstall

Reduzing Memory consumption: Smaller Vector Tables

I want to reduce the memory consumption to NuttX in the stm32f103-minimum board to get external SPI NOR Flash with SmartFS and USB Serial Console at same time.

After mount the SmartFS partition I will have only 808 bytes of RAM:

nsh> free
 total used free largest
Mem: 17536 15352 2184 2184
nsh> mksmartfs /dev/smart0p1
nsh> free
 total used free largest
Mem: 17536 15352 2184 2184
nsh> mount -t smartfs /dev/smart0p1 /mnt
nsh> free
 total used free largest
Mem: 17536 16728 808 808
nsh>

Analyzing which features are consumpting more RAM I notice the interruption vector table is the using more than 600 bytes:

$ arm-none-eabi-nm --print-size --size-sort --radix dec -C nuttx | grep ' [DdBb] ' | tail -20
536873088 00000008 B g_sigpendingirqaction
536873096 00000008 B g_sigpendingirqsignal
536873104 00000008 B g_sigpendingsignal
536872252 00000008 B g_waitingforsemaphore
536872280 00000008 B g_waitingforsignal
536873124 00000008 B g_wdactivelist
536873112 00000008 B g_wdfreelist
536872436 00000016 B g_hpwork
536873296 00000028 B g_spawn_parms
536871024 00000028 d g_spi1dev
536871860 00000028 b g_syslog_dev
536870912 00000092 d g_usart1priv
536871764 00000096 b g_wdpool
536872288 00000128 B g_pidhash
536873132 00000164 B g_mmheap
536871564 00000188 b g_idletcb
536871308 00000256 b g_usart1rxbuffer
536871052 00000256 b g_usart1txbuffer
536871888 00000328 b g_usbdev
536872452 00000608 B g_irqvector

Fortunately there is an option to reduce it:

http://www.nuttx.org/doku.php?id=wiki:howtos:smallvectors

Keep tuned!