NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Sensors, Actuators, and Robotics » HC-SR04 ultrasonic distance sensor with NerdKits

March 30, 2012
by victor
victor's Avatar

I've got the HC-SR04 ultrasonic distance sensor. I looked up online but was only able to find those Arduino tutorials to make it work. Here is one example.

http://www.alt42.com/2011/12/hc-sr04-ultrasonic-distance-sensor/

I'm wondering how to implement this particular function which is available in Arduino ? Basically it's measuring the input pin voltage change, to calculate the distance (d = v*t/2).

//get the time to receive rebound duration = pulseIn(us_echoPin, HIGH);

March 30, 2012
by victor
victor's Avatar

I've got the HC-SR04 ultrasonic distance sensor. I looked up online but was only able to find those Arduino tutorials to make it work. Here is one example.

http://www.alt42.com/2011/12/hc-sr04-ultrasonic-distance-sensor/

I'm wondering how to implement this particular function which is available in Arduino ? Basically it's measuring the input pin voltage change, to calculate the distance (d = v*t/2).

  //get the time to receive rebound

  duration = pulseIn(us_echoPin, HIGH);
March 30, 2012
by pcbolt
pcbolt's Avatar

Victor -

Try here. A similar question.

March 31, 2012
by lukel
lukel's Avatar

That code will work with that. I have one of those and it works with that code just fine. Change the trigger pin to PC5. Here is the complete code.

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

 volatile uint8_t pulse_flag, timing_flag;
 volatile uint32_t pulse_count, pulse_time;
void timer_init(){
// Set up interrupt
TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
TCCR0B |= (1<<CS00);   // set clock divider to 1
OCR0A = 74;            // set "top" for 5 usec
TIMSK0 |= (1<<OCIE0A); // turn interrupt on

pulse_flag = 0;
timing_flag = 0;
}

void send_pulse(){

PORTC |= (1<<PC5);     // start pulse
pulse_flag = 1;
TCNT0 = 0;

}

ISR(TIMER0_COMPA_vect) {

if (pulse_flag == 1){
PORTC &= ~(1<<PC5);    // stop pulse
pulse_flag = 0;
pulse_count = 0;
}
else{
pulse_count++;
timing_flag = 1;
}
}
ISR(PCINT1_vect){
if (timing_flag == 1){
pulse_time = pulse_count;
timing_flag = 0;
}
}

void pin_init(){
 DDRC &= ~(1<<PC4);
 PORTC |= (1<<PC4);
  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT12);
  return;
}
int main() {
 DDRC |= (1<<PC2);
 DDRC |= (1<<PC5);

 // init lcd
 lcd_init();
 FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
 lcd_home();

 // init serial port
 uart_init();
 FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
 stdin = stdout = &uart_stream;

 timer_init();
 pin_init();
 double pulse_distance;
 sei();

 while(1){
    send_pulse();
    pulse_distance = (double)pulse_time * 0.000005 * 47.0 * 100.0;
    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%10.2f inches"), pulse_distance);

}
}
March 31, 2012
by victor
victor's Avatar

Hi Luke! I spent the whole morning trying to reproduce your code in your previous post. I'm able to compile it and fixed some minor bugs in your previous code like line 88, you used "start_pulse()" which should be "send_pulse()". :)

I'm now trying to upload the hex to the board, but no luck so far. weird problem:

> "make.exe" all
avrdude -c avr109 -p m168 -b 115200 -P COM5 -U flash:w:victor_ultrasound.hex:a

Connecting to programmer: .
Found programmer: Id = "?""; type = 

avrdude: error: buffered memory access not supported. Maybe it isn't
a butterfly/AVR109 but a AVR910 device?
make.exe: *** [victor_ultrasound-upload] Error 1

> Process Exit Code: 2
> Time Taken: 01:51
March 31, 2012
by Ralphxyz
Ralphxyz's Avatar

victor, you are not actually making contact with the Nerdkit.

Found programmer: Id = "?""; type =

Pull your USB cable or maybe a reboot, of course check your wiring and battery voltage (under load).

Ralph

March 31, 2012
by victor
victor's Avatar

Hi Ralph, I just figured out that I forgot the switch to the programmable mode. I was able to upload a new hex onto it, to display different words on the LED screen.

I also upload the ultrasound hex onto it, but then I only see a screen filled with black blocks. any clue?

March 31, 2012
by victor
victor's Avatar

Hey guys! Good news! I've successfully made my ultrasonic range sensor working! :)

Luke, I noticed that the error is a bit huge. 10 inches measured as 8.93. Also, there is about 1 second delay, right?

Awesome small project! I'll take a video clip today and share with you guys! thanks! :)

March 31, 2012
by Ralphxyz
Ralphxyz's Avatar

The two rows of black boxes means the LCD is not getting "initialized" so there is either a problem with your wiring or code.

Sorry I cannot be more specific.

Ralph

March 31, 2012
by victor
victor's Avatar

Hi Luke, I've been debugging the accuracy issue for a while. I found this info by the manufactures, that maybe the root of the issue. It says we should trigger at least 10us, but in the code, we said: OCR0A = 74; // set "top" for 5 usec

"Using IO trigger for at least 10us high level signal The Module automatically sends eight 40 kHz and detect whether there is a pulse signal back"

Firstly, my device is HC-SR04, as listed below the Amazon link. http://www.amazon.com/Ultrasonic-Module-HC-SR04-Distance-Arduino/dp/B004U8TOE6/ref=pd_cp_e_0

March 31, 2012
by victor
victor's Avatar

Hey guys, the more I read the datasheets, the more I'm confused. Here is my understanding, please help me out. To measure the distance, 1, send a high voltage from pin PC5 to trigger, Luke used 5us, but the HC-SR04 datasheet says at least 10us. Confusion 1.

2, we set the timer basic unit as 74 cpu cycles, that is 5us. 3, once the PC5 is set to high for that 5us (or 10us), we start measuring the PC4, echo pin. Confusion 2.

4, once the PC5 changes , will trigger an interupt, then we get the "pulse_count", which is proportional to a distance by dist = time * speed/2.

could anyone help me out? thanks!

March 31, 2012
by lukel
lukel's Avatar

It's because this code is for a ping((( ultrasonic sensor. It worked for me, but it also had the delay. I tried to make it as accurate as possible.

March 31, 2012
by victor
victor's Avatar

Ahh i see! Ping))) uses 5us as trigger.

How much of the code needs to be changed to be able to use 10us? I've tried to change 74 to 148, but it looks like it can only measure 1.1 meter far. Beyond 1.1 meter, I got some weird reading... Any clue?

March 31, 2012
by victor
victor's Avatar

Thank you guys! My UltraSonic measuring system is up and running! :) Pretty agile and accurate!

"Victor1"

"Victor"

March 31, 2012
by victor
victor's Avatar

Oops, repost the photos here:

"victor"

March 31, 2012
by victor
victor's Avatar

March 31, 2012
by victor
victor's Avatar

Luke, I think I've fixed the bug you had been suffering. You need to put a delay_ms(200) at the end of your while(1) loop. Otherwise the measuring won't be stable, since sometimes the 5us will mess up the previous execution. Mine is quite agile and accurate now. HTH

March 31, 2012
by victor
victor's Avatar

March 31, 2012
by pcbolt
pcbolt's Avatar

Hi victor -

If you set the timer "top" value to 148 (OCR0A = 148), this will execute the code in the "ISR(TIMER0_COMPA_vect)" routine every 10 uSec. So when you call "send_pulse()", all it does is turn the output pin high, set the timer to 0 and set the pulse_flag. 10 uSec later the timer ISR will execute and since the pulse_flag is on ( == 1) it turns the output pin off, pulse_flag off ( == 0), and sets the pulse_count to 0. Now every 10 uSec, when the ISR gets triggered, the pulse_count variable counts how many times it has been called and it sets the timing_flag to 1.

When a pin change happens on the input pin (when the return sound comes back), the pin change interrupt "ISR(PCINT1_vect)" checks to see if you are in "timing" mode by checking the timing_flag. If it is, it takes a "snapshot" of the current 10 uSec pulse counts.

So I think all you need to do is change line 90 from:

pulse_distance = (double)pulse_time * 0.000005 * 47.0 * 100.0;

To:

pulse_distance = (double)pulse_time * 0.00001 * 47.0 * 100.0;

(I'm not sure why the * 100 is in there since the answer should be in inches).

March 31, 2012
by pcbolt
pcbolt's Avatar

Whoops -

Looks like we were posting at the same time....glad to see it working.

April 01, 2012
by Ralphxyz
Ralphxyz's Avatar

Hey Luke, what Ping))) sensor do you have?

I have the one from Radio Shack (about two years ago) it is from parallax

I corrected your code from above with pcbolts corrections and have the program running but all I see on the LCD is "0.00 inches".

I am using PC1 instead of PC4 (I need PC4 for my I2C LCD).

I am putting out a signal I see approx 8 milivolts and I can see the signal on my scope but I am not receiving anything or not calculating the distance correctly.

Here is my code:

/*
 *  Ultra Sonic Distance Sensor
 *      by  lukel http://www.nerdkits.com/forum/thread/2216/
 *      and http://www.nerdkits.com/forum/thread/2183/
 *  Created by Ralph on 4/1/12.
 *  
 *
 */

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>    
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>    
#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"
//#include "../libnerdkits/lcd.h"
//#include "../libnerdkits/uart.h"
#include "../libnerdkits/i2cmaster.h"
#include "../libnerdkits/I2C_lcd.h"

volatile uint8_t pulse_flag, timing_flag;
volatile uint32_t pulse_count, pulse_time;

void timer_init(){
    // Set up interrupt
    TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
    TCCR0B |= (1<<CS00);   // set clock divider to 1
    OCR0A = 74;            // set "top" for 5 usec
    TIMSK0 |= (1<<OCIE0A); // turn interrupt on     
    pulse_flag = 0;
    timing_flag = 0;
}

void send_pulse(){

    PORTC |= (1<<PC4);     // start pulse
    pulse_flag = 1;
    TCNT0 = 0;      
}

//SIGNAL(SIG_OUTPUT_COMPARE0A) {
ISR(TIMER0_COMPA_vect)
{
    if (pulse_flag == 1){
        PORTC &= ~(1<<PC4);    // stop pulse
        pulse_flag = 0;
        pulse_count = 0;
    }
    else{
        pulse_count++;
        timing_flag = 1;
    }       
}       
    ISR(PCINT1_vect)
    {
        if (timing_flag == 1){
            pulse_time = pulse_count;
            timing_flag = 0;
        }
    }

    void pin_init(){

        //Enable PIN Change Interrupt 1 - This enables interrupts on pins
        //PCINT14...8 see p70 of datasheet
        PCICR |= (1<<PCIE1);

        //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
        //the interrupt. see p71 of datasheet
        //PCMSK1 |= (1<<PCINT12);
        PCMSK1 |= (1<<PCINT12);
        return;
    }

    int main() {

        i2c_init();     // initialize I2C interface
                        // fire up the LCD
        I2C_lcd_init();
        I2C_lcd_home(); 
        FILE I2C_lcd_stream = FDEV_SETUP_STREAM(I2C_lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //DDRC |= (1<<PC4);
        DDRC |= (1<<PC1);

        // init lcd
        //lcd_init();
        //FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //lcd_home();           
        // init serial port
        //uart_init();
        //FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
        //stdin = stdout = &uart_stream;

        void timer_init();
        pin_init();
        double pulse_distance;
        sei();

        while(1){
            void start_pulse();
            delay_ms(500);
            pulse_distance = (double)pulse_time * 0.000005 * 47.0;
            //lcd_home();
            //fprintf_P(&lcd_stream, PSTR("%10.2f inches"), pulse_distance);
            I2C_lcd_home();
            fprintf_P(&I2C_lcd_stream, PSTR("%10.2f inches"), pulse_distance);
        }
    }

Hope someone can see something,

Ralph

April 01, 2012
by victor
victor's Avatar

a quick fix: line 98, remove "void "

April 01, 2012
by victor
victor's Avatar

also, line 104, remove "void".

April 01, 2012
by victor
victor's Avatar

Also, add these 2 functions in your code. You didn't define them correctly.

void timer_init(){
// Set up interrupt
TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
TCCR0B |= (1<<CS00);   // set clock divider to 1
OCR0A = 74;            // set "top" for 5 usec
TIMSK0 |= (1<<OCIE0A); // turn interrupt on

pulse_flag = 0;
timing_flag = 0;
}

void send_pulse(){

PORTC |= (1<<PC5);     // start pulse
pulse_flag = 1;
TCNT0 = 0;

}
April 01, 2012
by Ralphxyz
Ralphxyz's Avatar

Thanks Victor, when I removed the line 104 void I get this error:

PING.c:112: warning: implicit declaration of function 'start_pulse'

Here is the code:

while(1){
            start_pulse();
            delay_ms(500);
            pulse_distance = (double)pulse_time * 0.000005 * 47.0;
            //lcd_home();
            //fprintf_P(&lcd_stream, PSTR("%10.2f inches"), pulse_distance);
            I2C_lcd_home();
            fprintf_P(&I2C_lcd_stream, PSTR("%10.2f inches"), pulse_distance);
        }

start_pulse is not defined anywhere!!

Ralph

April 01, 2012
by victor
victor's Avatar

Hi Ralphxyz, That's one easy fix from Luke's code. Basically just change it to send_pulse().

April 01, 2012
by Ralphxyz
Ralphxyz's Avatar

Well apparently start_pulse should be send_pulse, which victor noted previously.

Made the changes and now I get a constant 325.31 inches.

Ralph

April 01, 2012
by victor
victor's Avatar

Ralph, try printing out at the 2nd line the value of "pulse_time" would help you debug. Also, delay 500ms is not necessary. The sensor can only measure up to 5 meters, which takes the round trip time of 5*2/340 = 29ms. So delay 100ms would be enough.

Check your pins first. did u connect Trig and Echo pins correctly?

April 01, 2012
by Ralphxyz
Ralphxyz's Avatar

I am using PC1 not PC5 or PC4!

Made the corrections but still getting "325.31 inches"

The led flashes every couple of seconds.

Ralph

April 01, 2012
by Ralphxyz
Ralphxyz's Avatar

Thanks for the help victor, I am getting 0.00 for pulse_time!!

re: pins I do not have a Trig or Echo pin I only have tree pins + - and Signal.

That's the reason I asked luke what Ping))) device he was using to make sure we were using the same device.

So with pulse_time 0.00 what does that mean?

Ralph

April 01, 2012
by victor
victor's Avatar

Ralph, that means the device is not powered.... actually, Ping))) is just a simplification of my HC sensor. Ping))) hardwired the Trig pin as a constant triggering module which keeps sending out US waves. take a snapshot so we could help you....

April 02, 2012
by Ralphxyz
Ralphxyz's Avatar

Oh the device definitely is powered the LED flashes every two seconds and I looked at the signal on my scope!

I bet the error is in my switching from PC4 to PC1. I am using Rick's I2C_LCD and I2C (TWI) uses PC4.

Here is my latest code

Ralph

April 02, 2012
by Ralphxyz
Ralphxyz's Avatar

Here is a shot of the face of the device, the yellow wire in the background (SIG) goes to PC1 on the mcu.

I wonder what the purpose of the led (the box in the center label ACT) is for?

Ralph

April 02, 2012
by victor
victor's Avatar

Ralph, Check carefully you code, esp. those parts that are related to setting PC1. , like:

void pin_init(){

ISR(xxx)

Post your code here so we can take a look.

April 02, 2012
by 6ofhalfdozen
6ofhalfdozen's Avatar

Ralph,

I would guess the ACT led is an "activity LED". I would guess that it is there to tell you the ultrasonics are ON. We recently got an outdoor ultrasonic cat repeller (our neighbors cat loves to spray our house and crash into/attack the windows scaring our cats badly). Anyhow, the paperwork mentions that even though you can't hear it, >90db of ultrasonics can do damage to your ears. While I doubt the sensor puts out >90db, I imagine the light lets you know when not to put your head in front of the thing. perhaps.

April 02, 2012
by Ralphxyz
Ralphxyz's Avatar

Here is the whole thing:

Now I am getting 0.00 inches and 0.00 pulse_time

/*
 *  Ultra Sonic Distance Sensor
 *      by  lukel http://www.nerdkits.com/forum/thread/2216/
 *      and http://www.nerdkits.com/forum/thread/2183/
 *  Created by Ralph on 4/1/12.
 *  Copyright 2012 Ralph Hulslander.... All rights reserved.
 *
 */

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"
//#include "../libnerdkits/lcd.h"
//#include "../libnerdkits/uart.h"
#include "../libnerdkits/i2cmaster.h"
#include "../libnerdkits/I2C_lcd.h"

volatile uint8_t pulse_flag, timing_flag;
volatile uint32_t pulse_count, pulse_time;

void timer_init(){
    // Set up interrupt
    TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
    TCCR0B |= (1<<CS00);   // set clock divider to 1
    OCR0A = 74;            // set "top" for 5 usec
    TIMSK0 |= (1<<OCIE0A); // turn interrupt on

    pulse_flag = 0;
    timing_flag = 0;
}

void send_pulse(){

    PORTC |= (1<<PC1);     // start pulse
    pulse_flag = 1;
    TCNT0 = 0;

}

//SIGNAL(SIG_OUTPUT_COMPARE0A) {
ISR(TIMER0_COMPA_vect)
{
    if (pulse_flag == 1){
        PORTC &= ~(1<<PC1);    // stop pulse
        pulse_flag = 0;
        pulse_count = 0;
    }
    else{
        pulse_count++;
        timing_flag = 1;
    }

}

    ISR(PCINT1_vect)
    {
        if (timing_flag == 1){
            pulse_time = pulse_count;
            timing_flag = 0;
        }
    }

    void pin_init(){

        //Enable PIN Change Interrupt 1 - This enables interrupts on pins
        //PCINT14...8 see p70 of datasheet
        PCICR |= (1<<PCIE1);

        //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
        //the interrupt. see p71 of datasheet
        //PCMSK1 |= (1<<PCINT12);
        PCMSK1 |= (1<<PCINT9);
        return;
    }

    int main() {

        i2c_init();     // initialize I2C interface
                        // fire up the LCD
        I2C_lcd_init();
        I2C_lcd_home(); 
        FILE I2C_lcd_stream = FDEV_SETUP_STREAM(I2C_lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //DDRC |= (1<<PC4);
        DDRC |= (1<<PC1);

        // init lcd
        //lcd_init();
        //FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
        //lcd_home();

        // init serial port
        //uart_init();
        //FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
        //stdin = stdout = &uart_stream;

        timer_init();
        pin_init();
        double pulse_distance;
        sei();

        while(1){
            send_pulse();
            delay_ms(500);
            pulse_distance = (double)pulse_time * 0.000005 * 47.0;
            //lcd_home();
            //fprintf_P(&lcd_stream, PSTR("%10.2f inches"), pulse_distance);
            I2C_lcd_home();
            fprintf_P(&I2C_lcd_stream, PSTR("%10.2f inches"), pulse_distance);
            I2C_lcd_line_three();
            fprintf_P(&I2C_lcd_stream, PSTR("%10.2f pulse_time"), pulse_time);          
        }
    }
April 02, 2012
by victor
victor's Avatar
fprintf_P(&I2C_lcd_stream, PSTR("%10.2f pulse_time"), pulse_time);

changed to

fprintf_P(&I2C_lcd_stream, PSTR("%10d pulse_time"), pulse_time);
April 02, 2012
by victor
victor's Avatar

Ralph, I know your problem. You didn't configure the input pin correctly. Hint, you need something like this. I will let you explore and learn instead of spoiling you. Read carefully Luke's code and the ATmel chip datasheet.

 DDRC |= (1<<PC1);
April 02, 2012
by Ralphxyz
Ralphxyz's Avatar

I am confused about Luke's use of PC4 and PC5 (there is even a PC2 mentioned).

I am using the code from this page now but I only have one pin SIG on my Ping))) unit PC1.

So I guess I am at a loss about how this is supposed to work.

Well now I am getting 3318.67/3319.30/3319.33/3319.12 inches with 1750/1753/1763 pulse_time.

It used to be static so I guess I am making progress.

Ralph

April 02, 2012
by pcbolt
pcbolt's Avatar

@ lukel -

I couldn't understand why you added *100 to this:

pulse_distance = (double)pulse_time * 0.000005 * 47.0 * 100.0;

What happened was when computing the speed of sound in air using 1115 ft/sec, I divided that number by 12 to get inches/sec. That was wrong. I should have multiplied by 12 to get 13,380 in/sec. So the final formula should have been...

pulse_distance = (double)pulse_time * 0.000005 * 13380.0 * 0.5;  // *0.5 for one-way

This can simplify down to:

pulse_distance = (double)pulse_time * 0.03345;

Sorry about the mix-up.

April 02, 2012
by pcbolt
pcbolt's Avatar

Ralph -

I checked out the datasheet for the Ping))) sensor and it turns out to be quite a bit different from the sensors lukel and victor are using. The Ping))) needs a 5 uSec trigger pulse to get things started (SIG line high for 5 uSec). Then it waits "about" 200 uSec to send its sound pulse. It will set the SIG line high until it gets the return sound at which time it goes low. So the code above won't really work for you. You'll need to start the timing when the SIG line goes high after a send_pulse() is used. Then stop the timer when the SIG line goes low. The code I posted for lukel right above this post should work to convert to inches.

April 03, 2012
by Ralphxyz
Ralphxyz's Avatar

Thanks pcbolt, that was why I asked Luke when I started trying this.

Hey Luke, what Ping))) sensor do you have?

My need for a Ping))) sensor is a long way off but I thought since I had one and with the ongoing conversation figured I I'd give it a whirl.

Plus I really needed to understand timers and interrupts for a upcoming project so I thought this would be a good way to lean.

I thought I might just have to start from scratch so I Googled "AVR Ping" and found this site.

 *      Pseudo Code: 
 *      Make the I/O pin an Output
 *      Bring LOW the  pin that the PING rangefinder is connected
 *      Bring the  pin HIGH for 5 microseconds
 *      Bring the  pin LOW 
 *      Make the I/O pin an Input
 *      Wait until the pin goes HIGH
 *      Use a timer to see how much longer it takes for the pin to become LOW 
 *      The time it took to become low is now our raw distance ( in microseconds)
 *      Divide Raw Distance by two since it includes the time for a return trip of the sonar
 *      Raw distance * 2257 is our distance in cm
 *      Raw distance * 889 is our distance in inches
 */

Does this Pseudo Code look right?

Also I am using this to finally learn how to use my oscilloscope, so this has been a really good experience so far.

I do not have a lot of time but like I said I need to understand interrupts so here we go.

So thanks victor for your help so far, I'll be back for more.

Ralph

April 03, 2012
by pcbolt
pcbolt's Avatar

Ralph -

It looks pretty good. But on that site they mention the conversions to inches and cm were "clock dependent" so you most likely will have to tweak those numbers. The clock timer lukel used increments the "pulse_count" (and therefore "pulse_time") variable every 5 uSec. So this should work...

 pulse_distance = (double)pulse_time * 0.03345;     // output in inches
April 04, 2012
by lukel
lukel's Avatar

Ralph, I think you need to define an input pin. Replace pin_init with this.

void pin_init(){
 DDRC &= ~(1<<PC4);
 PORTC |= (1<<PC4);
  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT12);
  return;
}
April 04, 2012
by lukel
lukel's Avatar

I mean this.

void pin_init(){
 DDRC &= ~(1<<PC1);
 PORTC |= (1<<PC1);
  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT12);
  return;
}
April 04, 2012
by pcbolt
pcbolt's Avatar

lukel -

Were you able to modify your code with the new conversion factor I listed about 4 post ago on this thread? Again, sorry for the mix up.

April 04, 2012
by Ralphxyz
Ralphxyz's Avatar

lukel, what Ping))) device do you have is it the same one I have from Parallex that I have?

Ralph

April 04, 2012
by lukel
lukel's Avatar

Yes, I have that one. I also have the HC-S904.

April 05, 2012
by Ralphxyz
Ralphxyz's Avatar

Good then that probable explains some of my confusion, some of the discussion/code was about the Ping))) sensor and other was about the HC-S904 and I was mixing them up.

Looking forward to your Nerdkit Community Library Project Ultra Sonic Sensor project.

I see you have gotten started.

Ralph

April 05, 2012
by lukel
lukel's Avatar

Ralph, try this code.

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

volatile uint8_t pulse_flag, timing_flag;
 volatile uint32_t pulse_count, pulse_time;
void timer_init(){
// Set up interrupt
TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
TCCR0B |= (1<<CS00);   // set clock divider to 1
OCR0A = 74;            // set "top" for 5 usec
TIMSK0 |= (1<<OCIE0A); // turn interrupt on
}

void send_pulse(){
PORTC |= (1<<PC4);     // start pulse
delay_us(5);
PORTC &= ~(1<<PC5);
delay_us(200); 
}
void time_it() { 
do {
 pulse_time++;
}
while(PINC & (1<<PC4)); 
}

void pin_init(){

  DDRC &= ~(1<<PC4);
  //turn on pullup resistor
  PORTC |= (1<<PC4);
  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT12);
  return;
}
int main() {
 DDRC |= (1<<PC4);

 // init lcd
 lcd_init();
 FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
 lcd_home();

 // init serial port
 uart_init();
 FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
 stdin = stdout = &uart_stream;

 timer_init();
 pin_init();
 double pulse_distance;
 sei();

 while(1) {
    send_pulse();
    time_it();  
    pulse_distance = (double)pulse_time * 0.03345;
    lcd_home();
    lcd_line_one();
    fprintf_P(&lcd_stream, PSTR("distance %.2f"), pulse_distance);
    }
    return 0;
}
April 05, 2012
by Ralphxyz
Ralphxyz's Avatar

Thanks lukel, i'll have to try it later on got work to do now :-)

Ralph

April 05, 2012
by Ralphxyz
Ralphxyz's Avatar

lukel why are you still using PC4 and PC5?

void send_pulse(){
PORTC |= (1<<PC4);     // start pulse
delay_us(5);
PORTC &= ~(1<<PC5);
delay_us(200);

I can not use PC4 or PC5 so I am using PC1.

Now I am getting a steady pulse the led is on constantly!!

Also I am not getting anything on the LCD!!

Here is my modified code.

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/i2cmaster.h"
#include "../libnerdkits/I2C_lcd.h"

volatile uint8_t pulse_flag, timing_flag;
volatile uint32_t pulse_count, pulse_time;
void timer_init(){
    // Set up interrupt
    TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
    TCCR0B |= (1<<CS00);   // set clock divider to 1
    OCR0A = 74;            // set "top" for 5 usec
    TIMSK0 |= (1<<OCIE0A); // turn interrupt on
}

void send_pulse(){
    PORTC |= (1<<PC1);     // start pulse
    delay_us(5);
    PORTC &= ~(1<<PC1);
    delay_us(200); 
}
void time_it() { 
    do {
        pulse_time++;
    }
    while(PINC & (1<<PC1)); 
}

void pin_init(){

    DDRC &= ~(1<<PC1);
    //turn on pullup resistor
    PORTC |= (1<<PC1);
    //Enable PIN Change Interrupt 1 - This enables interrupts on pins
    //PCINT14...8 see p70 of datasheet
    PCICR |= (1<<PCIE1);

    //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
    //the interrupt. see p71 of datasheet
    PCMSK1 |= (1<<PCINT9);
    return;
}
int main() {
    DDRC |= (1<<PC1);

    i2c_init();     // initialize I2C interface
                    // fire up the LCD
    I2C_lcd_init();
    I2C_lcd_home(); 
    FILE I2C_lcd_stream = FDEV_SETUP_STREAM(I2C_lcd_putchar, 0, _FDEV_SETUP_WRITE);

    // init serial port
    uart_init();
    FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
    stdin = stdout = &uart_stream;

    timer_init();
    pin_init();
    double pulse_distance;
    sei();

    while(1) {
        send_pulse();
        time_it();  
        pulse_distance = (double)pulse_time * 0.03345;
        I2C_lcd_home();
        I2C_lcd_line_three();
        fprintf_P(&I2C_lcd_stream, PSTR("distance %.2f"), pulse_distance);
    }
    return 0;
}
April 05, 2012
by lukel
lukel's Avatar

On line 35, add

delay_us(5);
April 05, 2012
by Ralphxyz
Ralphxyz's Avatar

Made the change but still get the led on constantly and nothing on the LCD!!

Ralph

April 05, 2012
by pcbolt
pcbolt's Avatar

Ralph -

Try using these subroutines:

void send_pulse(){
    DDRC |= (1<<PC1);      // set as output
    PORTC |= (1<<PC1);     // start pulse
    delay_us(5);
    PORTC &= ~(1<<PC1);
    DDRC &= ~(1<<PC1);     // set as input
    pulse_time = 0;
}
void time_it() {
    while !(PINC & (1<<PC1)){ }   // wait until PC1 goes HIGH
    while (PINC & (1<<PC1)){
        delay_us(5);
        pulse_time++;
    }
}

Oh...and I usually like to write a test string to the LCD right after it is initialized. Just as a sanity check. If it does work, you can get rid of all the interrupt code since you are not using them.

April 06, 2012
by Ralphxyz
Ralphxyz's Avatar

Now I need to understand getting rid of the interrupts, which were the reason I was pursuing this.

And yes I like the debug message.

Oh,

while !(PINC & (1<<PC1)){ }   // wait until PC1 goes HIGH

should be

while (!(PINC & (1<<PC1))){ }

The indicator LED is still on constantly and I am not getting anything on the LCD.

I'll look at my I2C_LCD changes for that.

Ralph

April 06, 2012
by Ralphxyz
Ralphxyz's Avatar

Now I setup a regular Nerdkit breadboard using PC4.

I am still seeing the LED on constantly with nothing displayed on the LCD!!

Here is my current code using PC4:

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/io_328p.h"
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

volatile uint8_t pulse_flag, timing_flag;
volatile uint32_t pulse_count, pulse_time;
void timer_init(){
    // Set up interrupt
    TCCR0A |= (1<<WGM01);  // sets compare and reset to "top" mode
    TCCR0B |= (1<<CS00);   // set clock divider to 1
    OCR0A = 74;            // set "top" for 5 usec
    TIMSK0 |= (1<<OCIE0A); // turn interrupt on
}
void send_pulse(){
    DDRC |= (1<<PC4);      // set as output
    PORTC |= (1<<PC4);     // start pulse
    delay_us(5);
    PORTC &= ~(1<<PC4);
    DDRC &= ~(1<<PC4);     // set as input
    pulse_time = 0;
}
void time_it() {
    while (!(PINC & (1<<PC4))){ }   // wait until PC4 goes HIGH
    while (PINC & (1<<PC4)){
        delay_us(5);
        pulse_time++;
    }
}
void pin_init(){

    DDRC &= ~(1<<PC4);
    //turn on pullup resistor
    PORTC |= (1<<PC4);
    //Enable PIN Change Interrupt 1 - This enables interrupts on pins
    //PCINT14...8 see p70 of datasheet
    PCICR |= (1<<PCIE1);

    //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
    //the interrupt. see p71 of datasheet
    PCMSK1 |= (1<<PCINT12);
    return;
}
int main() {
    DDRC |= (1<<PC4);

    // init lcd
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    lcd_home();

    // init serial port
    uart_init();
    FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
    stdin = stdout = &uart_stream;

    timer_init();
    pin_init();
    double pulse_distance;
    sei();

    while(1) {
        send_pulse();
        time_it();  
        pulse_distance = (double)pulse_time * 0.03345;
        lcd_home();
        lcd_line_one();
        lcd_write_string(PSTR("Hello World"));   
        lcd_line_two();
        fprintf_P(&lcd_stream, PSTR("distance %.2f"), pulse_distance);
        lcd_line_four();
        fprintf_P(&lcd_stream, PSTR("pulse_time %.2d"), pulse_time);
    }
    return 0;
}

There are no interrupts but they are still referenced which is confusing.

Ralph

April 06, 2012
by pcbolt
pcbolt's Avatar

Ralph -

If you are not getting even the "Hello World" message it may mean the "time_it()" subroutine is not returning. Try putting lines 77,78,79 above the while(1) statement (after say line 67), just to test the LCD.

You mentioned in one of your posts (another thread) that you have a good O-scope. Might be time to break that bad boy out to test the PC4 pin to see if it is sending the 5 uSec pulse, and if the sensor is reacting the way it should.

April 07, 2012
by Ralphxyz
Ralphxyz's Avatar

I have been trying to use my scope (which I know veryvery little about using) but so far

I have not seen a 5 volt blip. My multi meter registers a steady 5 volts.

I really need to learn how to use the scope.

I am seeing two different things with the two probes on the same pin and both are just miniscule blips but different.

Ralph

April 07, 2012
by lukel
lukel's Avatar
Ralph, add delay_us(200); on line 32
April 07, 2012
by lukel
lukel's Avatar

See if you can find my library post, it has the code for a ping((( sensor.

April 07, 2012
by pcbolt
pcbolt's Avatar

lukel -

I remember you said you have both sensors...did you get both to work? If so, great job.

I had one question though, did you have to change the output trigger pulse to 10 uSec? The reason I ask is the "TOP" value is set as 148 which is 10 uSec instead of 74 which is what you used in the second code list in the library.

April 07, 2012
by lukel
lukel's Avatar

I got "pingy" working, but it just delayed a long time to read.

April 08, 2012
by Ralphxyz
Ralphxyz's Avatar

lukel said:

See if you can find my library post, it has the code for a ping((( sensor.

Thanks lukel for adding your Ping))) Sensor (always nice to have links) code.

But sorry to have to tell you this but it doesn't compile!

First error:

void time_it() {
//while !(PINC & (1<<PC1)){ }   // wait until PC1 goes HIGH
while (!(PINC & (1<<PC1))){ }

You missed the opening ( ).

And then there isn't any pin_init() code!

I added pin_init() code from prior builds:

//* adding in pin_init() code
 void pin_init(){

 DDRC &= ~(1<<PC1); // set Data Direction
 //turn on pullup resistor
 PORTC |= (1<<PC1); // set PC1 high

 //Enable PIN Change Interrupt 1 - This enables interrupts on pins
 //PCINT14...8 see p70 of datasheet
 PCICR |= (1<<PCIE1);

 //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
 //the interrupt. see p71 of datasheet
 PCMSK1 |= (1<<PCINT9); //PC1
 return;
 }

//*/

Now the code compiles, but I am still getting a blank LCD.

Also now the LED only flashes once and then stays off.

Now don't be surprised especially as this was your first post to the Nerdkit Community Library which I really appreciate your effort.

This is just one of life's embarrassing moments (which can happen a lot, through one's life if not careful).

You made certain assumptions before posting your code.

Starting with assuming your code worked especially since you have seen it work.

But you did not test the code you uploaded immediately before uploading.

and then (and this might be the most important step) you did not download your code and test it again.

Now we expect broken code in the forum but when you post to the library the actual uploaded code should be tested and working.

You cannot run code a couple of days before and then just upload that, you have to test it before uploading.

You will find that there are certain Gremlins that get into your code and mess it and components up, they take great delight in doing things like this and are very pernicious unless you have a diligently followed testing plan.

The nice thing about the Library is that if I had been able to fix the code I could have fixed it without making mention but I think there are some important lessons to be learnt here.

I am sorry to do this publicly but if we had a decent forum service we could message each other privately.

Ralph

Post a Reply

Please log in to post a reply.

Did you know that NerdKits has a TV commercial, seen on MythBusters and the Science Channel? Learn more...