Monthly archive

Pages

PIC18: Ultrasonic sensor

Submitted by admin on Tue, 10/07/2014 - 10:56

In this article we are going to experiment with an ultrasonic sensor, the HC-SR04; it seems quite known and common, especially in the Arduino community, maybe because it is quite cheap and simple to operate.

Wanna discover how we can use it to measure the distance of an object, by interfacing it to the Freedom II development board? Yes? Coool! So... let's get started!

HC-SR04 operation

The sensor comes with four pins:

+5V Supply

Trigger Pulse Input

Echo Pulse Output

GND

Triggering

The datasheet explains how to activate the Trigger pin and what it produces:

You only need to supply a short 10uS pulse to the trigger input to start the ranging, and then the module will send out an 8 cycle burst of ultrasounds at 40 kHz.

This train of ultrasound bursts travel forwards, until it finds an obstacle, and then backwards to the receiving end of the sensor; the echo pin will stay high for the duration of this travel back and forth.

Distance calculation

Ultrasonic of course means we're dealing with sounds, albeit inaudible to the human ear. The speed of sound is at around 340m/s, which is equivalent to 34,000cm/S or 34cm/mS.
So if the duration of the high pulse on the echo pin is, say, 2 milliseconds, that means that ultrasonic sound waves had to travel 34 centimeters to the obstacle for 1 mS and then 34 centimeters back to the HC-SR04. The length of the entire travel would be 68 centimeters, but the distance of the object will be half that measure:

In 1ms the distance of the obstacle would be 17cm and in 1uS it would be 0.017cm; so which is the magic number that lets us convert the duration in uS, measured on the Echo pin, to centimeters? Easily said:

x = 1uS / 0.017cm = 58

Let's say we capture 1.76mS (1760uS) on the Echo pin (here below the capture shown on the scope):

Distance(cm) = 1760uS / 58 = 30cm

A high 1760uS pulse on Echo pin means that the obstacle is 30cm far away from the sensor.

Capture

Pulses on the Echo pin can be captured by the PIC by configuring its CCP modules in ... well, Capture mode, the other modes being Compare and PWM.

The CCPx pins on the 18F4550 are RC2(CCP1) and RC1(CCP2), which will be connected to the Echo pin of the sensor:

Let's read from the datasheet what happens when a capture event occurs:

15.2 Capture Mode

In Capture mode, the CCPRxH:CCPRxL register pair captures the 16-bit value of the TMR1 or TMR3 registers when an event occurs on the corresponding CCPx pin. An event is defined as one of the following:

every falling edge

every rising edge...

So this is the plan:

CCP1 will be configured to capture a rising edge

whenever that occurs TMR3 will be cleared and start to count

CCP2 instead will listen for falling edge inputs

in which case TMR3 will stop and its value will be saved into CCPR2H:CCPR2L registers

that value will represent the duration of the high signal on pin Echo, which is proportional to the distance to the objrct

The program

The core of the program (main routine) is super simple; we are going to take measurements every second, by sending an input to the Trigger and waiting for the Echo.
To achieve that an interrupt is generated by TMR0 overflow every 1 second, which sends the requested 10uS +5V to the Trigger pin of the sensor; the HC-SR04 will send the ultrasound bursts and the Echo signal back will be captured, via interrupt, by CCP1/CCP2.

Here when a rising edge is captured on CCP1 TMR3 is cleared and starts to count up, to measure the duration of the pulse length; when a falling edge is captured on CCP2 it means that the pulse has ended and we can copy the values of CCPR2x, which contain the values of TMR3x, that is the duration of the pulse.

Going back to main.asmCCPR2High and CCPR2Low are just rough values that need to be converted in order to get the distance of the obstacle.

Calculations

With a 5MHz Fosc/4, there are 5,000,000 instructions per sec or 1 instruction every 0.2uS (1/5,000,000); TMR3 is prescaled by 8 in order to have less increments, which occur every 1.6uS (0.2uS * 8).

We said before that in order to get the distance we should divide the value in uS by the 58; here we don't have uS, but rather 1,6uS increments, so the new magic number is obtained in this way:

x = 58 / 1.6 = 36.25

36.25 is the divisor that should be applied to the value of TMR3H:TMR3L registers in order to get the distance in centimeters.

How can we divide a number by 36.25? With assembly is not as simple as with a calculator.
To start we could divide by 32, which can be easily accomplished with Shift Left Register commands; left-shifting a number is the same as dividing by 2, so if we repeat this last operation 5 times we effectively divide by 32.

ReadEcho
movlw .5 ; divide values by .32
movwf shiftr_count
shiftr_again
bcf STATUS,C ; we want the Carry to be 0 right now
rrcf CCPR2High,F ; shift right CCPR2High with Carry
rrcf CCPR2Low,F ; shift right CCPR2Low with Carry
decf shiftr_count,F ; is this the fith time we rotate right?
bnz shiftr_again ; no: shift right again
; yes: raw data CCPR2High and CCPR2Low should be divided to .32 now

After the last manipulations CCPRHigh is actually 0, at least for distances under 2 meters, which we are going to use in this experiment; so we can skip it and focus only on CCPR2Low.

So far we got a value which is a little overrated and so we use the routine DivisionByX in math.asm to get a more consistent value:

movf CCPR2Low,W ;
call DivisionByX ; We divided by .32, but to get a better result raw data should be divided by .36
movf quotient,W ; So, we remove 13% off CCPR2Low (.36/.32=1.13)
subwf CCPR2Low,F ;

By taking 13% off of it, CCPR2Low should now contain the decimal value, expressed in centimeters, of the distance of the obstacle!