Introduction

In this project, we will talk to the popular ultrasonic distance sensor HC-SR04 using the Nordic nRF51-DK board, and transmit the distance information over BLE using the NUS (Nordic UART Service). We will be utilizing GPIO and Timer1 for this purpose.

Prerequisite

Before you read further, please take a look at my previous article on nRF51-DK programming using GCC, since we’re going to use the same development setup here.

Communicating with HC-SR04

I have written about this sensor before, interfacing it with Arduino as well as ATtiny84. Here’s how the HC-SR04 works:

Send a 10us HIGH pulse on the Trigger pin.

The sensor sends out a “sonic burst” of 8 cycles.

Listen to the Echo pin, and the duration of the next HIGH signal will give you the time taken by the sound to go back and forth from sensor to target.

So we need (a) a way to send the trigger pulse and (b) a method to measure the echo pulse.

Connections

Here is how the nRF51-DK is hooked up to the HC-SR04:

HC-SR04 nRF51-DK VCC 5V GND GND Trig P0.01 Echo P0.02 via R DIV

The HC-SR04 runs on 5V, but our nRF51422 runs on 3.3V logic. It’s OK to send data to the HC-SR04 on 3.3 V TTL, but it’s not OK to receive a 5V pulse from the sensor. Hence, we route the Echo through a resistor divider as shown below.

Now let’s look at how to send and receive data.

Trigger Pulse

You can send the trigger pulse using the GPIO feature of the nRF51. The code looks like this:

// send 12us trigger pulse // _ // __| |__ nrf_gpio_pin_clear(pinTrig); nrf_delay_us(20); nrf_gpio_pin_set(pinTrig); nrf_delay_us(12); nrf_gpio_pin_clear(pinTrig); nrf_delay_us(20);

What you are doing above is toggling a GPIO pin HIGH and LOW, creating a pulse, and sure enough, you get a response from the Echo pin of the HC-SR04.

Echo Pulse

The distance is encoded in the width of the HIGH pulse returned by the HC-SR04. To measure this width, we will make use of the nRF51 Timer1 peripheral on the nRF51822. The Timer1 is a 8/16 bit timer which runs at 16 MHz.

In their SDK, Nordic provides higher level APIs (which start with nrf_drv_timer) to set up these timers. But I found their documentation and API to be confusing, so I decided to use the registers directly. I think this is a better approach in any case, as it corresponds directly to what you read in the nRF51 reference manual. Here is the code that sets up the timer and the interrupt routine:

// set up and start Timer1 void start_timer(void) { NRF_TIMER1->MODE = TIMER_MODE_MODE_Timer; NRF_TIMER1->TASKS_CLEAR = 1; // set prescalar n // f = 16 MHz / 2^(n) uint8_t prescaler = 0; NRF_TIMER1->PRESCALER = prescaler; NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_16Bit; // 16 MHz clock generates timer tick every 1/(16000000) s = 62.5 nano s // With compare enabled, the interrupt is fired every: 62.5 * comp1 nano s // = 0.0625*comp1 micro seconds // multiply this by 2^(prescalar) uint16_t comp1 = 500; // set compare NRF_TIMER1->CC[1] = comp1; // set conversion factor countToUs = 0.0625*comp1*(1 << prescaler); printf("timer tick = %f us

", countToUs); // enable compare 1 NRF_TIMER1->INTENSET = (TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos); // use the shorts register to clear compare 1 NRF_TIMER1->SHORTS = (TIMER_SHORTS_COMPARE1_CLEAR_Enabled << TIMER_SHORTS_COMPARE1_CLEAR_Pos); // enable IRQ NVIC_EnableIRQ(TIMER1_IRQn); // start timer NRF_TIMER1->TASKS_START = 1; } // Timer 1 IRQ handler // just increment count void TIMER1_IRQHandler(void) { if (NRF_TIMER1->EVENTS_COMPARE[1] && NRF_TIMER1->INTENSET & TIMER_INTENSET_COMPARE1_Msk) { // clear compare register event NRF_TIMER1->EVENTS_COMPARE[1] = 0; tCount++; } }

In the start_timer() method above, we start by setting the prescaler of the timer to 0. The timer frequency is given by $$f_{TIMER} = \frac{16 MHz}{2^{PRESCALER}}$$. The 16 MHz clock generates timer tick every 1/(16000000) s = 62.5 nano seconds. We then set the compare1 register to a value of 500. This means that the timer generates an interrup every $$62.5500$$ nano seconds or 31.25 microseconds. We set up the flags for *compare1, enable the interrupt, and set the SHORTS register to clear the task when the interrupt fires. (This is like a shortcut so that you don’t need to manually do this in the interrupt routine.)

In the interrupt routine, we increment a global counter tCount and clear the compare1 event. If you don’t do the latter, you will not get an interrupt the next time around. It’s best to have minimal amount of code TIMER1_IRQHandler() as otherwise, it will introduce latency into the timer, and that will mess up the accurancy of your timing.

Computing the Distance

Now that we are counting every 31.25 microseconds, let’s see how we put that use to compute the distance. Here is the relevant portion from the distance computation method:

// listen for echo and time it // ____________ // _____| |___ // wait till Echo pin goes high while(!nrf_gpio_pin_read(pinEcho)); // reset counter tCount = 0; // wait till Echo pin goes low while(nrf_gpio_pin_read(pinEcho)); // calculate duration in us float duration = countToUs*tCount; // dist = duration * speed of sound * 1/2 // dist in cm = duration in us * 10^-6 * 340.29 * 100 * 1/2 float distance = duration*0.017;

As soon as we send the trigger pulse, we loop around pinEcho till it goes high. Then we reset tCount so that the timer can start counting it, and loop around till pinEcho goes low. At this point tCount will be proportional to the pulse width, and we convert it to microseconds using the factor computed earlier. The distance can then be computed using the good old physics equation $$x = vt$$. The factor $$1/2$$ above comes in because the sound is travelling back and forth to the sensor – so you just need to consider half the time measured by the sensor.

The BLE Part

This project uses the S110 softdevice. Once we have the distance information, we send it over BLE via the Nordic UART Service (NUS). You can view it on your BLE device (phone/tablet) using the Nordic nRFtoolbox app.

Downloads

You can get the complete source code for this project here:

https://github.com/electronut/nRF51-hcsr04-test

Acknowledgments

I’d like to thank Aryan from Nordic Developer Zone for clearing my doubts on Timer1 usage.

References