Interfacing HC-SR04 Ultrasonic Sensor with PIC16F877A

jpanhalt

Joined Jan 18, 2008
11,087
Unfortunately, I can only code in Assembly (MPASM). I have read that the conversion form Arduino to C is not too hard. I suspect a lot of the examples on the Internet are copies of copies. It will take some weeding to find something that is not Arduino.
 

atferrari

Joined Jan 6, 2004
5,015
After rereading you OP:

the module does not detect distance (nonsense).

Your display seems to show some period measured. What seems to have failed is the calculation of the distanced based on that period. Even if the time measuremente is wrong, make sure the calculation gives a result.

Simulate with a certain period and check if the calculation gives the correct result.

You are trying to use something without understanding how it works.

Had I to use that module I would have drawn my flow chart and write the code. Much easier to tweak, test, adjust or correct.
 

Thread Starter

Lambo Av

Joined Apr 24, 2019
63
the module does not detect distance (nonsense).

Your display seems to show some period measured. What seems to have failed is the calculation of the distanced based on that period. Even if the time measuremente is wrong, make sure the calculation gives a result.
Yes, it shows the time taken for the receiver to receive the sound waves which is emitted by the transmitter. From there, it calculates the distance. The issue seems to be the period

Simulate with a certain period and check if the calculation gives the correct result.
I don't quite follow you here. Do you mean that I leave my sensor ON for some time and see if the calculation gives the correct result?

Had I to use that module I would have drawn my flow chart and write the code. Much easier to tweak, test, adjust or correct.
That is a good recommendation, thank you
 

djsfantasi

Joined Apr 11, 2010
9,237
Unfortunately, I can only code in Assembly (MPASM). I have read that the conversion form Arduino to C is not too hard. I suspect a lot of the examples on the Internet are copies of copies. It will take some weeding to find something that is not Arduino.
The Arduino IDE is for writing sketches in Arduino C++. So an Arduino program will work, with changes required for the particular hardware.

Bottom Line, if you can code in C or C++, you should be able to use a sketch developed in the Arduino IDE.
 

Thread Starter

Lambo Av

Joined Apr 24, 2019
63
1) Have you set the detector up by itself and observed a change, like the video showed?

2) With a 20 MHz crystal as used in the project, each instruction cycle is 0.2 us. Assuming Timer1 runs at the instruction cycle (that's a setting you can change) with a 1:4 prescale, each count = 0.8 us. So multiplying Timer1 counts by 0.8 seconds/count will give the correct time.
1) Yes, and I observed a change like the video showed

2) Yes, I am using a 20 MHz crystal
 

ericgibbs

Joined Jan 29, 2010
21,462
hi Lambo,
It is always a good idea to check that the signals are being generated/detected when dealing with TX and RX sensors.

What is the current stage of the project.?
E
 

Thread Starter

Lambo Av

Joined Apr 24, 2019
63
hi Lambo,
It is always a good idea to check that the signals are being generated/detected when dealing with TX and RX sensors.

What is the current stage of the project.?
E
Yes, I agree

The only stage left is running the sensor accurately. Once my problem is solved, everything is done.
 

ericgibbs

Joined Jan 29, 2010
21,462
hi,
Watched the video.
What is the repetition rate of the TX pulse and the number 40kHz cycles in the pulse.?
What is the Time in mSec from the TX pulse start to the RX echo being received, for say 0.33 mtrs ?

E
 

jpanhalt

Joined Jan 18, 2008
11,087
It appears your ultrasonic detector is working and you simply are not capturing time/counts for the relevant events.

A few years ago, I did a project using a thermal accelerometer (MXD 2020E, Memsic.com) to measure inclination. Its output was similar to what you are seeing. I cannot find the applicable application notes on the current manufacturer's site, so have up loaded only one of them (#6) from my records.

The PWM square wave is like you are seeing on the 'scope. Its duty cycle varies with tilt instead of distance. Here's an example:
1572525455468.png

The frequency for the MXD 2020E chip was 100 Hz, which is close enough to your 40 Hz that the same approach may apply with a 2:1 or 4:1 prescale. In my code, I used the PIC12F683 or PIC12F1840, let TMR1 run continuously, used capture (CCP module), and took care of TMR1's rollover in the subtraction algorithm.

Unfortunately for your case, it is all in Assembly, but the same principle should apply, assuming no more than one rollover. If more than one per measurement cycle, you can track the number of rollovers too. Basically, since the frequency is fixed, you only need to capture two events, e.g., times at 1 and 2 or times at 2 and 3. Note that 3 and 1 are both rising edges separated by 1/40 seconds in your case. Also, since the frequency is so low, there is plenty of processor time to do calculations between events so every cycle is captured.

The application note I have attached gives more detail and a code in C snippet (not Arduino C). It starts and stops the timer (Timer 1), however. Hope that helps. I am more than willing to share the raw code I wrote but that probably won't help.
 

Attachments

atferrari

Joined Jan 6, 2004
5,015
I don't quite follow you here. Do you mean that I leave my sensor ON for some time and see if the calculation gives the correct result?
Don't you have in your IDE the chance to simulate? If so, you could inject a measurement (time) and verify if the calculation gives a result at least (if correct, better). Your display is showing NO result. I would focus on that first.
 

JohnInTX

Joined Jun 26, 2012
4,787
It appears your ultrasonic detector is working and you simply are not capturing time/counts for the relevant events.
In my code, I used the PIC12F683 or PIC12F1840, let TMR1 run continuously, used capture (CCP module), and took care of TMR1's rollover in the subtraction algorithm.
Thinking on that, you can do that in the '877, too, and maybe not even need fancy subtraction adjustments.

The sequence for one ping is
Pulse the trigger 10uS
Measure the time between the rising edge and the falling edge of the return signal.
Calculate distance from that time.

You could use both CCPs in the 877 with TIMER1 as the timebase.
Connect the return signal to both CCP1 and CCP2 on the PIC.
Configure CCP1 to capture every rising edge (CCP1CON = 00000101) to capture TIMER1's value when the echo signal starts.
Configure CCP2 to capture every falling edge (CCP1CON = 00000100) to capture TIMER1's value when the echo signal ends.
Configure TIMER1 to free run and set its maximum time longer than the time corresponding to the maximum distance you expect to measure.

The ping/echo sequence is:
Trigger the ranger by emitting a 10us pulse i.e.
Trigger = 1;
delay_10uS;
Trigger = 0;

There will be some delay between the trigger and the echo as the ranger transmits. During that time:
Clear TIMER1
Clear CCP1IF
Clear CCP2IF
Clear TMR1IF

Now you wait for the echo:
while((CCP1IF == 0) && (CCP2IF == 0)) ; // wait for BOTH captures

Stop TIMER1
StartCount = (CCPR1H << 8) | CCPR1L // read CCP1 into 16 bit unsigned int
StopCount = (CCPR2H << 8) | CCPR2L // read CCP2 into 16 bit unsigned int
DistanceCount = StopCount - StartCount; // guaranteed positive since TIMER1 is configured not to overflow from 0000 during one ping.
Compute distance from DistanceCount

Here's the best part.
The original code will lock up if it does not get an echo - it waits forever for the signal to go first high then low. Never do that when interfacing with the outside world - especially with sonar..

BUT the fix is in:
As noted above, configure TIMER1's period to fully cover the expected (or desired) maximum range. When that is true, not only is the arithmetic simple (end count > start count always) but you can guard the whole sequence by watching for timer 1 overflow i.e. range is greater than maximum or you didn't get an echo at all. To do it just poll for TMR1IF along with CCP1IF and CCP2IF:

while( ((CCP1IF == 0) && (CCP2IF == 0)) || TMR1IF==1); // wait for BOTH captures OR Timer 1 overflow then see what happened
Stop TIMER1;
if(TMR1IF == 1){
Bad Ping i.e. the timer overflowed before the echo
}
else {
Process the capture values as before
}

Repeat for each ping.


As the others have indicated, once you get the count corresponding to the range, it's just a matter of arithmetic to convert the count to some distance. The code you posted takes liberties with mixing data types so you might want to explicitly cast to avoid problems i.e.
float distance;
distance = (float)DistanceCount;

Also, consider combining constants to simplify the math. For example from the original code:
distance = (0.0272 * time_taken)/2;
Can be:
distance = 0.0136 * time_taken; // yes? The compiler should optimize it for you but it's not a bad idea.

But your original problem seems to be in the original code:
d1 = (distance_int/100)%10;
d2 = (distance_int/10)%10;
d3 = (distance_int/1)%10;
is used to show the distance but distance_int is never loaded with anything so it will always be 0.

Also in Lcd_Start:
for(int i=1065244; i<=0; i--) NOP();
That number will not fit into a 16bit integer. I don't know how many nops you get but it's not that. Besides, it's a terrible way to generate a delay - use the compiler's delay functions.

You might consider using sprintf to format your displays. In the later versions of XC8 it's pretty efficient. It may be OK for your stuff. Note that if you CAN use sprintf to a buffer, you don't need the LCD to test it - just print to the buffer and examine the contents at a breakpoint.

Finally, consider dumping the 16F877 in favor of 16F1xxx or PIC18Fxxxx. The midrange stuff is seriously obsolete and should be avoided if possible.

FWIW: at 25C the ranging value is 146.75 uS/inch. Temperature will affect that value e.g. At 40C it's 142.89 uS/inch, at 0C it's 153us/inch. Not a bit deal at 40KHz but something to keep in mind.
The wavelength of a 40KHz sonar pulse at 25C is 0.34". That will affect the precision of your measurements.

Good luck!
 
Last edited:

jpanhalt

Joined Jan 18, 2008
11,087
@JohnInTX
Fancy subtraction?
Code:
     movf      CCP_T1L,w      ;SourceL,w
     subwf     CCP_T2L        ;DestL
     movf      CCP_T1H,w      ;SourceH,w
     btfss     STATUS,C       ;STATUS,0
     incfsz    CCP_T1H,w      ;SourceH,w
     subwf     CCP_T2H        ;DestH
Just ordinary double-precision with a mid-range (12F683) chip. Go enhanced mid-range and you can avoid use "subwfb." ;)[/code]
 

JohnInTX

Joined Jun 26, 2012
4,787
Hey at my age the bar for 'fancy' is pretty low there, pard'ner ;)
But I was referring to the wrap around issues of two captures with free-running timers. Not difficult, just more of the simple. And you have to like the overflow as guard-timer..
 

jpanhalt

Joined Jan 18, 2008
11,087
Hi John,

I do like your addition to detect bad echo that is built into knowing your starting count. That would be a little more difficult, but not particularly hard to do without zeroing the count.

What I wanted to point out to the TS, however, (see my earlier posts) is that calculation of periods with a free running 16-bit timer is no more complicated than with one that is started close to 0 counts. At least, if you limit your calculation size to 16 bits. Here's an example:
1) Assume starting count for a 16-bit counter is d.39000 (9858h)
2) Ending count is d.464 (1D0h)
3) Expected period is d.27000 (6978h)
4) 1D0h - 9858h = FFFF6978h
5) However, with just 16 bits to deal with, only the 4 least significant digits are retained = 6978h (d.27000)

A decimal example may be easier for the TS to see. Say you have a 3-digit counter, i.e., 0-999 that rollsover. Now your first count is 900 and your end cont is 200. By inspection, the interval was 300 counts. Simple subtraction gives -700 as the difference, but that is not the question. The question is interval, so "200" can be viewed as "1200." Alternatively, since the interval cannot be negative, one could calculate the 10's complement of 700 = 300.
 

JohnInTX

Joined Jun 26, 2012
4,787
That is a good explanation of how to handle the wrap-around. I've used both methods for ranging. I think we agree that the key is to let the peripherals do the measuring rather than manually counting, especially in C.
Thanks for the writeup.
 
Last edited:
Top