HC-SR04 code with timer based trigger.

Go To Last Post
21 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am trying to implement a measuring device as a parking aid using the HC-SR04. I have found a lot of good examples. I have used the ideas from different versions and put them together to try and create what I am looking for. I want a sensor to light up some LEDs to indicate how close the car is to the desired distance. I want the user to be able to select the desired distance by pushing a button when the car is parked where they want it to be. I started with the basics and then add functions once I get one part working. The basic code is to put a pin high, delay 10us, set the pin low then delay 60ms and to run this in a loop. My concern is that when I add the debounce code (Dani's of course) that with the delay of 60ms for the trigger pulse, I will not get the proper polling of the key press and not get desired results. I am using an Atmega328P so I only have one 16 bit timer and that has to be used for the reading of the pulse. (I will modify this code later for a microcontroller with 2 16 bit counters, but I want to see if I can get this to work) So I thought I could create a pseudo PWM timer to create the pulse and required pause. The pulse is 10us and need a 60ms delay between pulses. I set up a 10us CTC timer and in that timer, I set up a counter to see how many times the counter overflowed and some logic to turn the trigger pin on and off accordingly. Everything seems to be working but the function that turns the Leds off doesn't work as expected. When I use the loop in the main to fire the trigger, Pin on, delay 10us, Pin Off, delay 60ms the Leds will turn off after 500 readings of the same distance which is approximately 30 seconds. 60ms * 500=30sec. When I use the pseudo PWM, the module seems to be reacting to distance changes the same as before but the leds don't turn off until after approximately 3 minutes, which is 6 times longer than expected. If the trigger is pulsing at the same rate as the loop, then the time should be the same? I do not know why there is such a difference. Maybe my math on the PWM is off? I could just change the number in the program to get what I want, but I want to understand why I am getting such a huge difference. Here is my code and I have a few questions about the code I have used:

 

1. If you have a variable that is just used in an interrupt, do you have to declare it globally and volatile? Or just declare it globally and manage it in the interrupt?

2. While in an interrupt, I have read that the code for disabling interrupts/saving the SREG and restoring the SREG after completion is redundant because as a function of interrupts, they are already blocked.

3. One code I saw used PRR &= ~(1<<PRTIM1)​ to start the timer (as stated in the data sheet) but most of the code on timers does not use that. What is the purpose of that code?

 * The ultrasonic module is triggered every 60 ms by setting pin PC4 (the trigger) high for 10 us. A change in state on pin PC5 (the echo)
 * causes a interrupt. The interrupt checks if PC5 is high. If it is a timer is started that increments every micro second. Once PC5 triggers a
 * interrupt again and is low the result is calculated and displayed in centimeters on the NewHaven display. Distance in cm is calculated
 * by the amount of microseconds PC5 (the echo) is active high divided by 58.
 *
 * Pin placement of ATMega328p:
 * Pin PC3 HC-SR04 Trig using pseudo PWM
 * Pin PC4 HC-SR04 Trig using delay in main loop
 * Pin PC5 HC-SR04 Echo
 * Port B[0-3] LEDs
 */
 #define F_CPU 1000000UL
 
 #include <stdio.h>
 #include <avr/io.h>
 #include <util/delay.h>
 #include <avr/interrupt.h>
 
 int TempDistance = 36; //temp distance to check if parked
 uint16_t TimesStill = 0;  //counter to see if parked
 int Margin =2;  // Margin of error for parking
 int SavedDistance; //to hold the saved parking distance
 volatile int PWMCounter=0;
 //volatile int status=0;
 
 /*******************************************INITALIZE PORTS, TIMER, AND INTURRUPTS*******************************************/
 void init() {
  DDRB = 0xFF; //Port B all output LEDs
  PORTB =0x00; //set all off
  DDRD = 0xFF; // Port D all output. 
  DDRC = 0xFF; // Port C all output. 
  DDRC &= ~(1<<DDC5); // Set Pin C5 as input to read Echo
  PORTC |= (1<<PORTC5); // Enable pull up on C5
  PORTC &= ~(1<<PC4); // Init C4 as low (trigger)
 
  //PRR &= ~(1<<PRTIM1); // To activate timer1 module
  TCNT1 = 0; // Initial timer value
  TCCR1B |= (1<<CS10); // Timer without prescaller. Since default clock for atmega328p is 1Mhz period is 1uS
  TCCR1B |= (1<<ICES1); // First capture on rising edge
  
  //Use Timer0for pseudo PWM
  
  TCCR0A |= (1<<COM0A1) | (1<<WGM01); //Clear OC0A on Compare Match
  TCCR0B |= (1<<CS00); //CTC Mode no prescaler
  TIMSK0 |= (1<<OCIE0A);
  OCR0A = 0xA; //0x9 == 10us 1000000/(1/.00001)-1
  
  //end of new code
  
  PCICR = (1<<PCIE1); // Enable PCINT[14:8] we use pin C5 which is PCINT13
  PCMSK1 = (1<<PCINT13); // Enable C5 interrupt
  sei(); // Enable Global Interrupts
 }
 
 /*******************************************Set LEDs*******************************************/

 void SetLeds (uint16_t Dist_Inches){
  if( Dist_Inches >= (TempDistance - Margin) && Dist_Inches <= (TempDistance + Margin)){ // No change in distance with in Margin of error
   TimesStill ++;  // Number of cycles that car was still
  }
  else { //Reset because car is moving
   TimesStill = 0;
   TempDistance=Dist_Inches;
  }
  if ( TimesStill >= 500){ //Car has not moved for approx 30 seconds
   PORTB = 0x00;  //Turn off leds, car is parked
   TimesStill =500; //don't let counter overflow
  }
  else {
   switch (Dist_Inches) {
    
    case 109 ... 144: // 9 to 12 feet
    PORTB= 0x01; //LED on PB0 is on (green)
    break;
    
    case 73 ... 108: //6 to 9 feet
    PORTB = 0x03; //LED on PB0 and PB1 is on (green/green)
    break;
    
    case 49 ... 72: // 4 to 6 feet
    PORTB = 0x07; //LED on PB0, PB1 and PB2 is on (green/green/yellow)
    break;
    
    case 36 ... 48: //3 to 4 feet
    PORTB = 0x0F; //LED on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
    break;
    
    case 1 ... 35: //Closer than 3 feet-DANGER
    PORTB = 0x0F; //LEDs flash on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
    _delay_ms(250);
    PORTB = 0x00;
    _delay_ms(250);
    break;
    
    default: //Out of range or error
    PORTB = 0x00; // All LEDs off
    break;
    
   }
  }
 }

 /*******************************************MAIN PROGRAM*******************************************/
 int main() {
  init();
  while (1) {
   //This code will work properly*******************************
   
   //_delay_ms(60); // To allow sufficient time between queries (60ms min)
 
   //PORTC |= (1<<PC4); // Set trigger high
   //_delay_us(10); // for 10uS
   //PORTC &= ~(1<<PC4); // to trigger the ultrasonic module
  }
 }
 /*******************************************INTURRUPT PCINT1 FOR PIN C5*******************************************/
 ISR(PCINT1_vect) {
  if (bit_is_set(PINC,PC5)) { // Checks if echo is high
   TCNT1 = 0; // Reset Timer
   
  } 
  else {
   uint16_t numuS = TCNT1; // Save Timer value
   uint8_t oldSREG = SREG;
   cli(); // Disable Global interrupts
   SetLeds(numuS/148);
   SREG = oldSREG; // Enable interrupts
   
  }
 }

 ISR(TIMER0_COMPA_vect) {
  PWMCounter++;
  if (PWMCounter>=5999){
   PORTC |= (1<<PC3);
   PWMCounter=0;
  }
  if (PWMCounter==1){
   PORTC &= ~(1<<PC3);
  }
   
 }

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No need for the sei(), cli() within the ISR, as the compiler does that for you! 

I would also remove the saving/restoring of SREG as well.   I think your messing up your interrupts and that is why your timing is off.

 

Jim

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In stead of calling setleds() in the ISR, set a flag and call from main when flag is set. 

Be sure to set shared variables as volatile....

 

Jim

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ki0bk wrote:

No need for the sei(), cli() within the ISR, as the compiler does that for you! 

Gee, that ISR looked familiar when I searched for past threads...

ISR(PCINT2_vect){
	unsigned char sreg;
	
	sreg = SREG;
	cli();
	if(PIND & (1<<PIND2)){
		
		TCNT1 = TCNT1_START_HCSR04;
		TCCR1B |= (1<<CS10);
	}else{
		PORTD |= (1<<PIND4);
		TCCR1B &= ~(1<<CS10);
		echoInches = (TCNT1 - TCNT1_START_HCSR04)/148;
		pingState = 2;
	}
	
	SREG = sreg;
	sei();
}

And in that thread, the poster was talked out of the structure planned here, and moved on to a state machine:

http://www.avrfreaks.net/forum/a...

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't understand what distance is being measured.  Is it between the car's fender and the curb?  What if there is no curb?  Is it between the fender and a wall?   Can you measure 9 to 12 feet and still have the Echo reflected accurately?

 

The PRR is the Power Reduction Register.  It stops the clock to a timer, which effectively stops the timer. I've never seen it used to turn a peripheral off/on, but section 14.10 infers that it could be done that using the PRR.  This is probably useful in ultra-low power situations.  But since the AVR and HC-SR04 is on an automobile, power saving isn't an issue.

 

If you're using a Mega328P, then I suggest parallel developing with an Arduino Nano as a second hardware platform.  It also uses the Mega328P and runs at 16MHz.  They cost about $3, have a USB programming interface and several good working libraries for the HC-SR04 that you can study and adapt to your application.  This platform can be used to quickly develop working software and compare the test results to your original Mega328P platform.

 

Are you sure that the timing is OK on the Timer0 interrupt?  Is it triggering an interrupt every 10 uS?  With a 1MHz clock and no prescaler on the timer?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@ ki0bk That is what I thought. I will try that and see what happens. I saw in several code examples the use of storing and restting the SREG but I wanted to me sure. Sometimes you can remove something and it appears to work correctly but it will have adverse affests down the road. When you mean to set flags in the interrupt, I want to make sure I understand, are you saying to create a volatile variable and then set it in the interrupt to indict the state the program is in and continously test that in the main while loop?

 

@theusch LOL I did say that I looked at several examples, I did see that one, but it wasn't where I got the code... I think you and ki0bk are esentially stating the same thing about setting a flag in the ISR? I will make those changes. I think this should work for the pseudo PWM ISR as well. Keep track of the overflow clicks in the main and just change the state when it is tim to fire again.

 

@Simonetta This device will be mounted on the wall of the garage and measure the distance from the car to the sensor and based on the distance, LEDs will light up to indicate when you are parked in the right spot. I did several tests and so far it seemed to measure accureately out to 13 feet. At that distance, it really isn't imoprtant that the measurement is super accureate. But when you get closer it is. But so far all the test have seemed accureate. It is still on a breadboard so as soon as I get a little closer I will do some real world testing. In regards to Timer0, that is one of the questions I have. I tried to measure the frequency on an o-scope but because the pulse is so small in comparison to the pause, I couldn't get a reading that is why I asked to see if the way I wrote the ISR my calculations were correct and if they were, why such a difference in timing. The lights seem to indicate that it is working correctly. You asked if I was sure that the timer was firing as expected so let me see if I am calculating things correctly

 

1MHZ clock with no prescaler = each clock tick is 1us? Correct? So if the timer cycle 10 times, that should be 10us?

 

Or using this using this formula, this is what I get:

 

Timer clicks= (Clock Freq in HZ/Prescaler)/(1/desired time period) -1 (timer starts at 0

this gives me (1000000/1)/(1/.00001)-1 = 9

 

I set the compare to 9 so that should get me the 10us pulse.

 

Then I need the pause of 60ms. If the timer overflows every 10us then 6000 cycles should be 60ms?

 

Are my calculations off? Or my logic in the ISR? Why such a difference?

 

 

 

Last Edited: Wed. Sep 20, 2017 - 12:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dusteater wrote:
@ ki0bk ...... are you saying to create a volatile variable and then set it in the interrupt to indict the state the program is in and continously test that in the main while loop?

 

 Bingo! You will need to reset the flag in main when you act on it too. 

Also, investigate using a state machine as well.  

Good luck.

 

Jim

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

If the Output Compare interrupt happens when Timer0 reaches the value 0x0a, and the system clock is 1MHz, then the interrupt routine is longer than the interval allocated for it. 

 

There are 10 instruction cycles between the time that the timer counts from 0 to 0x0A.  The interrupt overhead takes about 6 cycles from the Output Compare flag being set.  It probably takes longer because several registers are probably loaded onto the stack. The ISR does a 16-bit increment, a couple of 16-bit compares, and a Port I/O.  Then it removes data registers and addresses from the stack, and returns to the main code.  I estimate that the ISR takes about 30 system clock cycles to completely run, so it can't fire each time the timer0 reaches 0x0A.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I will make those changes as soon as I get a chance and let you know how it goes. I am reading about the Machine State and let me know if this code looks better, especially with the new information that Simonetta brought up I think I understand it more.

 

Here are my thoughts.

I think I would need 4 states:

ReadyToPing

PingSent

WaitingForPing

PingReceived

 

In the ISRs I would have the following code: I don't see how I can minimize the code any more than this.

ISR(PCINT1_vect) { //checks for signal change on pin C5
       if (bit_is_set(PINC,PC5)) { // Checks if echo is high
              TCNT1 = 0; // Reset Timer
        }
       else {
              uint16_t numuS = TCNT1; // Save Timer value
              PingState=PingReceived
       }
}

ISR(TIMER0_COMPA_vect) { //Fires every 10us
       PWMCounter++;  // if I do this process in the Main loop, will I miss some of the interrupts and therefore the number be inaccurate and get inaccurate timing??
}

 

 And the Main loop:

while (1) {
    switch (PingState) {
        case ReadyToPing:
            PORTC |= (1<<PC3);
            PingState = PingSent;
            break;
        case PingSent:
            if (PWMCounter >=1{   //not sure if it will make it through this case before the next ISR interrupt
                PORTC &= ~(1<<PC3); //turn off trigger
                PingState=WaitingForPing;
             }
            break;
        case WaitingForPing: //do nothing until ping has been received
            break;    
        case PingReceived:
            SetLeds (numuS/148)  //I could add another state so that the Set LEDs would only run once through each state
            if (PWMCounter >= 5999){ //60ms has transpired
                PingState = ReadyToPing;
                PWMCounter =0
            }
    }
}

And because I am using the PingState in the ISR I will have to declare it like this?

 

volatile enum{ ReadyToPing; PingSent; WaitingForPing; PingReceived}

 

And would it be recommended to add an additional State so that I am not running the SetLeds function every time until 60ms has elapsed? What benefits or problems would adding this state cause? The switch function would have additional comparisons to do causing additional overhead, but that might be less task consuming than the SetLEDs function?

 

And just so I understand, when I was running the code for the trigger and pause in the main loop with the delays, the timing would only be marginally off because the _delay_us and _delay_ms might only be off by a clock cycle or two due to overhead?

 

But when I moved the timing into the ISR, I was performing more than 10us of instructions so the next 10us interrupt would be triggered in the middle of the first ISR (any subsequent ones would be lost until this new one got processed?) So if I take Simonetta's guess of 30 system clock ticks then my PWMCounter would only be incrementing once every 30us? And if it was just slightly larger it would be once every 40us? This gets me closer to understanding why the timing was so far off. So with the time difference between the _delay_ms version and the ISR version being a factor of 6, maybe the ISR overhead might be 60 system clock ticks? Or maybe there might be additional overhead in between the processing of the ISRs that makes the whole thing off by a factor of 6?

 

And with that thought, if any other ISR triggers in the middle of this, then it will cause the PWMCcounter to be off as well? When the PCINT_vect gets triggered, it has to do the few comparisons and other tasks but then have to perform the whole SetLeds function before returning and completing this ISR before the PWM ISR can start running so this will cause some additional delays every 60ms or so.

 

I am going to add a button press function using Danni's debounce code, so I want to stay away of _delay_ms in order to properly capture the pin value. Am I heading in the right direction?

 

Thanks for your help, I really appreciate it.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

dusteater wrote:
I have found a lot of good examples

But discounted the really simple ones; eg,

 

  • A mark on the garage side wall that you align with, say, the door mirror;
  • A tennis ball hung from the roof - when it touches the rear screen, you are done ...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:

dusteater wrote:
I have found a lot of good examples

But discounted the really simple ones; eg,

 

  • A mark on the garage side wall that you align with, say, the door mirror;
  • A tennis ball hung from the roof - when it touches the rear screen, you are done ...

I didn't discount them, I thought about those. I am surprised that you didn't mention the sandbags on the floor method, it is easier to calibrate than the methods you suggested and there is no training required. I didn't want the tennis ball because it looks tacky and in the way when you don't have a car in the garage. And what if you are driving a different car, then you have to "re-calibrate" the tennis ball. A mark on the wall will have to be "re-calibrated" as well if you are driving a different car. What if someone else is driving the car? Then there is the learning curve in training them in the proper parking procedures. And what if you aren't available to properly train them? Besides, an electronic parking gauge is far cooler than these redneck versions. LOL

 

 

Last Edited: Fri. Sep 22, 2017 - 02:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So I thought the State Machine would make things work better or there is something wrong with my logic???? Here is the revised code and now the LEDs do not react like they are supposed to. It doesn't seem like it is measuring things. They come on in an unpredictable pattern. Am I expecting too much to run a 10us pulse this way? Would it be better if I were running at 8Mhz?

The biggest reason I wanted to use the timer was to avoid the _delay_ms (60) in the while loop. I am going to add a button and I will need to debounce it and I wasn't sure how the debouncing routine would behave with the long delay in the while loop. Would this delay in the while loop cause problems using Dannis debounce routine?

 

Is this code salvageable? What are your suggestions?

#define F_CPU 1000000UL

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

int TempDistance = 36; //temp distance to check if parked
uint16_t TimesStill = 0;//counter to see if parked
int Margin =3; //Margin of error for parking
int SavedDistance; //to hold the saved parking distance
volatile int PWMCounter=0;
volatile char status=0;
enum PingState {ReadyToPing,PingSent,WaitingForPing,PingReceived};
volatile char currentState=0;
uint16_t numuS;

/*******************************************INITALIZE PORTS, TIMER, AND INTURRUPTS*******************************************/
void init() {
 DDRB = 0xFF; //Port B all output LEDs
 PORTB =0x00; //set all off
 DDRD = 0xFF; // Port D all output.
 DDRC = 0xFF; // Port C all output.
 DDRC &= ~(1<<DDC5); // Set Pin C5 as input to read Echo
 PORTC |= (1<<PORTC5); // Enable pull up on C5
 PORTC &= ~(1<<PC4); // Init C4 as low (trigger)
 
 TCNT1 = 0; // Initial timer value
 TCCR1B |= (1<<CS10); // Timer without prescaller. Since default clock for atmega328p is 1Mhz period is 1uS
 TCCR1B |= (1<<ICES1); // First capture on rising edge
 
 //Use Timer0for pseudo PWM
 
 TCCR0A |= (1<<COM0A1) | (1<<WGM01); //Clear OC0A on Compare Match
 TCCR0B |= (1<<CS00); //CTC Mode no prescaler
 TIMSK0 |= (1<<OCIE0A);
 OCR0A = 0xA; //0x9 == 10us 1000000/(1/.00001)-1
 
 //end of new code
 
 PCICR = (1<<PCIE1); // Enable PCINT[14:8] we use pin C5 which is PCINT13
 PCMSK1 = (1<<PCINT13); // Enable C5 interrupt
 sei(); // Enable Global Interrupts
}

/*******************************************Set LEDs*******************************************/

void SetLeds (uint16_t Dist_Inches){
 if( Dist_Inches >= (TempDistance - Margin) && Dist_Inches <= (TempDistance + Margin)){ // No change in distance with in Margin of error
  TimesStill ++; // Number of cycles that car was still
 }
 else {//Reset because car is moving
  TimesStill = 0;
  TempDistance=Dist_Inches;
 }
 if ( TimesStill >= 500){ //Car has not moved for approx 30 seconds
  PORTB = 0x00;//Turn off leds, car is parked
  TimesStill =500;//don't let counter overflow
 }
 else {
  switch (Dist_Inches) {
   
   case 109 ... 144: // 9 to 12 feet
   PORTB= 0x01; //LED on PB0 is on (green)
   break;
   
   case 73 ... 108: //6 to 9 feet
   PORTB = 0x03;//LED on PB0 and PB1 is on (green/green)
   break;
   
   case 49 ... 72: // 4 to 6 feet
   PORTB = 0x07;//LED on PB0, PB1 and PB2 is on (green/green/yellow)
   break;
   
   case 36 ... 48: //3 to 4 feet
   PORTB = 0x0F;//LED on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   break;
   
   case 1 ... 35: //Closer than 3 feet-DANGER
   PORTB = 0x0F; //LEDs flash on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   _delay_ms(250);
   PORTB = 0x00;
   _delay_ms(250);
   break;
   
   default: //Out of range or error
   PORTB = 0x00; // All LEDs off
   break;
   
  }
 }
}

/*******************************************MAIN PROGRAM*******************************************/
int main() {
 init();
 while (1) {
  switch (currentState) {
   case ReadyToPing
   
   
   
    PORTC |= (1<<PORTC3); //turn on pulse
    currentState =PingSent;
    break;
   case PingSent:
    if (PWMCounter>=1){ //turn off pulse, might be slightly longer than 1us
     PORTC &= ~(1<<PORTC3);
     currentState=WaitingForPing;
    }
    break;
   case WaitingForPing: //do nothing until 60ms has expired
    break;
   case PingReceived:
    if (PWMCounter >= 5999){
     SetLeds(numuS/148);
     currentState=ReadyToPing;
     PWMCounter=0; 
    }
  } 

 }
}
/*******************************************INTURRUPT PCINT1 FOR PIN C5*******************************************/
ISR(PCINT1_vect) {
 if (bit_is_set(PINC,PC5)) { // Checks if echo is high
  TCNT1 = 0; // Reset Timer
 }
 else {
  numuS = TCNT1; // Save Timer value
  currentState=PingReceived;
 }
}

ISR(TIMER0_COMPA_vect) { //pseudo PWM Counter 10us duration
 PWMCounter++;
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You're running the cpu at 1MHz and you have a 10us interrupt. That's just 10 cpu cycles. You probably need around 30 or more cpu cycles for the isr to execute. You've run out of time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see a big problem: your main loop does not access PWMCounter, numuS or currentState atomically. At least the two first variables MUST be atomically accessed since they are manipulated by ISR and are more than 8-bit in size.

 

Fix that and post your new code.

/Jakob Selbing

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 @Kartman, After Simonetta explained the ISR issue, I had concerns about that and that is why I asked if changing the clock to 8MHz would resolve that. She estimated that the ISR was taking around 30 cpu cycles before I stripped it down to just one 16 bit increment. So I wasn't sure if reducing it this far would give enough time to process. I tried unsetting the div/8 fuse and make the appropriate timing adjustments, and it seems to be working properly With the Atomic Block (see code below in case I am missing something) Now to add another timer for the Debounce routine.

 

@Jaksel That seemed to fix part of the problem. Things seem to be operating correctly as far as the measurement. But the test for the idle state is now not working. It should be approximately 30 seconds of the same measurement but it isn't turning off at all. This leads me to believe that the Atomic block is interrupting the Pin interrupt and so in the 500 tests there will get an interrupted echo reading that will give a distance out of range? Maybe I am not applying the Atomic macro correctly? See code below.

//8Mhz Version
#define F_CPU 8000000UL

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

int TempDistance = 36; //temp distance to check if parked
uint16_t TimesStill = 0;//counter to see if parked
int Margin =3; //Margin of error for parking
int SavedDistance; //to hold the saved parking distance
volatile int PWMCounter=0;
volatile char status=0;
enum {ReadyToPing,PingSent,WaitingForPing,PingReceived};
volatile char currentState=0;
uint16_t numuS;

/*******************************************INITALIZE PORTS, TIMER, AND INTURRUPTS*******************************************/
void init() {
 DDRB = 0xFF; //Port B all output LEDs
 PORTB =0x00; //set all off
 DDRD = 0xFF; // Port D all output.
 DDRC = 0xFF; // Port C all output.
 DDRC &= ~(1<<DDC5); // Set Pin C5 as input to read Echo
 PORTC |= (1<<PORTC5); // Enable pull up on C5
 PORTC &= ~(1<<PC4); // Init C4 as low (trigger)
 
 TCNT1 = 0; // Initial timer value
 TCCR1B |= (1<<CS11); // Timer with /8 prescaller. 8 MHZ Since default clock for atmega328p is 1Mhz period is 1uS
 TCCR1B |= (1<<ICES1); // First capture on rising edge
 
 //Use Timer0for pseudo PWM
 
 TCCR0A |= (1<<COM0A1) | (1<<WGM01); //Clear OC0A on Compare Match
 TCCR0B |= (1<<CS00); //CTC Mode no prescaler 8Mhz
 TIMSK0 |= (1<<OCIE0A);
 OCR0A = 0x4F; //0x9 == 10us 8000000/(1/.00001)-1
 
 //end of new code
 
 PCICR = (1<<PCIE1); // Enable PCINT[14:8] we use pin C5 which is PCINT13
 PCMSK1 = (1<<PCINT13); // Enable C5 interrupt
 sei(); // Enable Global Interrupts
}

/*******************************************Set LEDs*******************************************/

void SetLeds (uint16_t Dist_Inches){
 if( Dist_Inches >= (TempDistance - Margin) && Dist_Inches <= (TempDistance + Margin)){ // No change in distance with in Margin of error
  TimesStill ++; // Number of cycles that car was still
 }
 else {//Reset because car is moving
  TimesStill = 0;
  TempDistance=Dist_Inches;
 }
 if ( TimesStill >= 500){ //Car has not moved for approx 30 seconds
  PORTB = 0x00;//Turn off leds, car is parked
  TimesStill =500;//don't let counter overflow
 }
 else {
  switch (Dist_Inches) {
   
   case 109 ... 144: // 9 to 12 feet
   PORTB= 0x01; //LED on PB0 is on (green)
   break;
   
   case 73 ... 108: //6 to 9 feet
   PORTB = 0x03;//LED on PB0 and PB1 is on (green/green)
   break;
   
   case 49 ... 72: // 4 to 6 feet
   PORTB = 0x07;//LED on PB0, PB1 and PB2 is on (green/green/yellow)
   break;
   
   case 36 ... 48: //3 to 4 feet
   PORTB = 0x0F;//LED on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   break;
   
   case 1 ... 35: //Closer than 3 feet-DANGER
   PORTB = 0x0F; //LEDs flash on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   _delay_ms(250);
   PORTB = 0x00;
   _delay_ms(250);
   break;
   
   default: //Out of range or error
   PORTB = 0x00; // All LEDs off
   break;
   
  }
 }
}

/*******************************************MAIN PROGRAM*******************************************/
int main() {
 init();
 while (1) {
  switch (currentState) {
   case ReadyToPing:
   PORTC |= (1<<PORTC3); //turn on pulse
   currentState =PingSent;
   break;
   case PingSent:
   ATOMIC_BLOCK(ATOMIC_FORCEON){
    if (PWMCounter>=1){ //turn off pulse, might be slightly longer than 1us
     PORTC &= ~(1<<PORTC3);
     currentState=WaitingForPing;
    }
   }
   break;
   case WaitingForPing: //do nothing until 60ms has expired
   break;
   case PingReceived:
   ATOMIC_BLOCK(ATOMIC_FORCEON){
    if (PWMCounter >= 5999){
     SetLeds(numuS/148);
     currentState=ReadyToPing;
     PWMCounter=0;
    }
   }
  }
 }
}
/*******************************************INTURRUPT PCINT1 FOR PIN C5*******************************************/
ISR(PCINT1_vect) {
 if (bit_is_set(PINC,PC5)) { // Checks if echo is high
  TCNT1 = 0; // Reset Timer
 }
 else {
  numuS = TCNT1; // Save Timer value
  currentState=PingReceived;
 }
}

ISR(TIMER0_COMPA_vect) { //pseudo PWM Counter 10us duration
 PWMCounter++;
}

Do you think it would be better to not use the PWM method and put the pulse routine in the while loop? Will the delays cause a problem with the debounce routine?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Now that it seems that the pseudo PWM is working, I need to set up a timer for the debounce routine. I added the code for timer 2 that I think would work, but for some reason when I add the timer code, nothing seems to be working now? What is wrong with my timer 2 code?

/*
* The ultrasonic module is triggered every 60 ms by setting pin PC4 (the trigger) high for 10 us. A change in state on pin PC5 (the echo)
* causes a interrupt. The interrupt checks if PC5 is high. If it is a timer is started that increments every micro second. Once PC5 triggers a
* interrupt again and is low the result is calculated and displayed in centimeters on the NewHaven display. Distance in cm is calculated
* by the amount of microseconds PC5 (the echo) is active high divided by 58.
*
* Pin placement of ATMega328p:
* Pin PC3 HC-SR04 Trig using pseudo PWM
* Pin PC4 HC-SR04 Trig using delay in main loop
* Pin PC5 HC-SR04 Echo
* Port B[0-3] LEDs
*/
#define F_CPU 8000000UL

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

int TempDistance = 36; //temp distance to check if parked
uint16_t TimesStill = 0;//counter to see if parked
int Margin =3; //Margin of error for parking
int SavedDistance; //to hold the saved parking distance
volatile int PWMCounter=0;
volatile char status=0;
enum PingState {ReadyToPing,PingSent,WaitingForPing,PingReceived};
volatile char currentState=0;
uint16_t numuS;

/*******************************************INITALIZE PORTS, TIMER, AND INTURRUPTS*******************************************/
void init() {
 DDRB = 0xFF; //Port B all output LEDs
 PORTB =0x00; //set all off
 DDRD = 0xFF; // Port D all output.
 DDRC = 0xFF; // Port C all output.
 DDRC &= ~(1<<DDC5); // Set Pin C5 as input to read Echo
 PORTC |= (1<<PORTC5); // Enable pull up on C5
 PORTC &= ~(1<<PC4); // Init C4 as low (trigger)
 
 //Timer interupt for echo 1us per cpu cycle
 
 TCCR1B |= (1<<CS11); // Timer with prescaller of 8. 
 TCCR1B |= (1<<ICES1); // First capture on rising edge
 
 //Use Timer0 for pseudo PWM 10us
 
 TCCR0A |= (1<<COM0A1) | (1<<WGM01); //Clear OC0A on Compare Match 10us
 TCCR0B |= (1<<CS00); //CTC Mode no prescaler
 TIMSK0 |= (1<<OCIE0A);
 OCR0A = 0x4F; //0x4F 79 == 10us 8000000/(1/.00001)-1
 
 //Timer 2 for debounce need 10ms
 
 TCCR2A |= (1<<COM2A1) | (1<<WGM21); //Clear OC0A on Compare Match 
 TCCR2B |= (1<<CS20) | (1<<CS21) | (1<<CS22); //CTC Mode prescaler 1024
 TIMSK2 |= (1<<OCIE2A); //
 OCR2A = 0x4D; //0x4D == (8000000/1024)/(1/.00001)-1 =77
 
 PCICR = (1<<PCIE1); // Enable PCINT[14:8] we use pin C5 which is PCINT13
 PCMSK1 = (1<<PCINT13); // Enable C5 interrupt
 sei(); // Enable Global Interrupts
}

/*******************************************Set LEDs*******************************************/

void SetLeds (uint16_t Dist_Inches){
 if( Dist_Inches >= (TempDistance - Margin) && Dist_Inches <= (TempDistance + Margin)){ // No change in distance with in Margin of error
  TimesStill ++; // Number of cycles that car was still
 }
 else {//Reset because car is moving
  TimesStill = 0;
  TempDistance=Dist_Inches;
 }
 if ( TimesStill >= 500){ //Car has not moved for approx 30 seconds
  PORTB = 0x00;//Turn off leds, car is parked
  TimesStill =500;//don't let counter overflow
 }
 else {
  switch (Dist_Inches) {
   
   case 109 ... 144: // 9 to 12 feet
   PORTB= 0x01; //LED on PB0 is on (green)
   break;
   
   case 73 ... 108: //6 to 9 feet
   PORTB = 0x03;//LED on PB0 and PB1 is on (green/green)
   break;
   
   case 49 ... 72: // 4 to 6 feet
   PORTB = 0x07;//LED on PB0, PB1 and PB2 is on (green/green/yellow)
   break;
   
   case 36 ... 48: //3 to 4 feet
   PORTB = 0x0F;//LED on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   break;
   
   case 1 ... 35: //Closer than 3 feet-DANGER
   PORTB = 0x0F; //LEDs flash on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   _delay_ms(250);
   PORTB = 0x00;
   _delay_ms(250);
   break;
   
   default: //Out of range or error
   PORTB = 0x00; // All LEDs off
   break;
   
  }
 }
}

/*******************************************MAIN PROGRAM*******************************************/
int main() {
 init();
 while (1) {
  switch (currentState) {
   case ReadyToPing:
   PORTC |= (1<<PORTC3); //turn on pulse
   currentState =PingSent;
   break;
   case PingSent:
   ATOMIC_BLOCK(ATOMIC_FORCEON){
   if (PWMCounter>=1){ //turn off pulse, might be slightly longer than 1us
    PORTC &= ~(1<<PORTC3);
    currentState=WaitingForPing;
   }
   }
   break;
   case WaitingForPing: //do nothing until 60ms has expired
   break;
   case PingReceived:
   ATOMIC_BLOCK(ATOMIC_FORCEON){}
   if (PWMCounter >= 5999){
    SetLeds(numuS/148);
    currentState=ReadyToPing;
    PWMCounter=0;
   }
   
   }

 }
}
/*******************************************INTURRUPT PCINT1 FOR PIN C5*******************************************/
ISR(PCINT1_vect) {
 if (bit_is_set(PINC,PC5)) { // Checks if echo is high
  TCNT1 = 0; // Reset Timer
 }
 else {
  numuS = TCNT1; // Save Timer value
  currentState=PingReceived;
 }
}

ISR(TIMER0_COMPA_vect) { //pseudo PWM Counter 10us duration
 PWMCounter++;
}

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you're making what should be a fairly simple process into some rather complex. Your pwm counter is a 'bad idea' - it is sucking cpu resources for little gain. You really only need one timer. Consider this:

use the compare feature of the timer (not CTC).

Load the compare value with the timer you require.

When the compare happens you cycle your machine then load your next time or stop.

your first state might be set trigger, load 10us into compare.

on compare interrupt: clear trigger, load timestamp with the current timer value and load timeout value.

if you get a pcint (you probably should use the input capture feature as it is more precise), then read the timer value. timer value - timestamp = period

if you get a timeout, then set timeout flag and stop, or set a time to retry.

rinse and repeat.

Pretty well the whole sampling can be done in the background via interrupts. This is probably more sophisticated that the application really needs, but it is simpler than your current method.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why do you need to debounce your button?

 

Think about it. You push the button and it is detected and acted upon and the state is latched. It does not matter that it bounces as the 'button pushed' state will stay until it is reset by some other action.

 

Debouncing is only needed if...

 

1). an input moves between two or more states on each push or

2). increments a variable on each push (although this is really just a sub-case of 1). ).

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Brian- yes I didn't think about that, all I want to capture is if it is pressed. 

@Kartman I rewrote the program based on what I thought you were saying, but I ran into a problem. If I use a 1Mhz clock without a prescaler, then I run into a couple problems, the 60ms pause is larger than an integer 60,000. I was creating this just as an exercise to figure out how to program an AVR and try different things. Here is the code I have (that obviously won't work) but if you have any ideas to make it work, I would appreciate it. 

* Pin placement of ATMega328p:
* Pin PC3 HC-SR04 Trig 
* Pin PC5 HC-SR04 Echo
* Port B[0-3] LEDs
*/
#define F_CPU 1000000UL

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

int TempDistance = 36; //temp distance to check if parked
uint16_t TimesStill = 0;//counter to see if parked
int Margin =3; //Margin of error for parking
int SavedDistance; //to hold the saved parking distance
enum {ReadyToPing,PingSent,WaitingForEcho,PingReceived,Waiting60ms};
volatile char currentState=0;
volatile uint16_t  numuS;

/*******************************************INITALIZE PORTS, TIMER, AND INTURRUPTS*******************************************/
void init() {
 DDRB = 0xFF; //Port B all output LEDs
 PORTB =0x00; //set all off
 DDRD = 0xFF; // Port D all output.
 DDRC = 0xFF; // Port C all output.
 DDRC &= ~(1<<DDC5); // Set Pin C5 as input to read Echo
 PORTC |= (1<<PORTC5); // Enable pull up on C5
 PORTC &= ~(1<<PORTC4); // Init C4 as low (trigger)
 
 TCCR1B |= (1<<CS10) | (1<<WGM10); // CTC Timer with no prescaller  
 TCCR1B |= (1<<ICES1); // First capture on rising edge
 TIMSK1 |= (1<<OCIE1A); //Compare A interrupt
 OCR1A = 0x190; // set to 400-max for HC-SR04 measurement
 PCICR = (1<<PCIE1); // Enable PCINT[14:8] we use pin C5 which is PCINT13
 PCMSK1 = (1<<PCINT13) | (1<<OCIE0A); // Enable C5 interrupt
 
 sei(); // Enable Global Interrupts
}

/*******************************************Set LEDs*******************************************/

void SetLeds (uint16_t Dist_Inches){
 if( Dist_Inches >= (TempDistance - Margin) && Dist_Inches <= (TempDistance + Margin)){ // No change in distance with in Margin of error
  TimesStill ++; // Number of cycles that car was still
 }
 else {//Reset because car is moving
  TimesStill = 0;
  TempDistance=Dist_Inches;
 }
 if ( TimesStill >= 500){ //Car has not moved for approx 30 seconds
  PORTB = 0x00;//Turn off leds, car is parked
  TimesStill =500;//don't let counter overflow
 }
 else {
  switch (Dist_Inches) {
   
   case 109 ... 144: // 9 to 12 feet
   PORTB= 0x01; //LED on PB0 is on (green)
   break;
   
   case 73 ... 108: //6 to 9 feet
   PORTB = 0x03;//LED on PB0 and PB1 is on (green/green)
   break;
   
   case 49 ... 72: // 4 to 6 feet
   PORTB = 0x07;//LED on PB0, PB1 and PB2 is on (green/green/yellow)
   break;
   
   case 36 ... 48: //3 to 4 feet
   PORTB = 0x0F;//LED on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   break;
   
   case 1 ... 35: //Closer than 3 feet-DANGER
   PORTB = 0x0F; //LEDs flash on PB0, PB1, PB2 and PB3 is on (green/green/yellow/red)
   _delay_ms(250);
   PORTB = 0x00;
   _delay_ms(250);
   break;
   
   default: //Out of range or error
   PORTB = 0x00; // All LEDs off
   break;
   
  }
 }
}

/*******************************************MAIN PROGRAM*******************************************/
int main() {
 init();
 while (1) {
  switch (currentState) {
   case ReadyToPing:
    OCR1A=0x10; //Set to 10us for trigger pulse
    PORTC |= (1<<PORTC3); //turn on pulse
    TCNT1 = 0;
    currentState =PingSent;
    break;
   
   case PingReceived:
    ATOMIC_BLOCK (ATOMIC_FORCEON) {
     SetLeds(numuS/18);
    }
    currentState=Waiting60ms;
    OCR1A=0xEA5F; //Set to 60ms for ****trigger pulse TOO BIG!!!!*****
    TCNT1 = 0x0;
    break;
   }
 }
}
/*******************************************INTURRUPT PCINT1 FOR PIN C5*******************************************/
ISR(PCINT1_vect) {
 if (bit_is_set(PINC,PC5)) { // Checks if echo is high
  TCNT1 = 0; // Reset Timer
 }
 else {
  numuS = TCNT1; // Save Timer value
  currentState=PingReceived;
 }
}

ISR(TIMER1_COMPA_vect) { //General timer
 switch (currentState){
  case PingSent: //should be the end of the 10us Pulse
   PORTC &= ~(1<<PORTC3); //turn off pulse
   OCR1A = 0x190; // set to 400-max for HC-SR04 measurement.
   currentState = WaitingForEcho;
   TCNT1=0x0;
   break;
  case WaitingForEcho: //Pulse never received-Start over
   currentState= ReadyToPing;
   break;
  case Waiting60ms:  //60ms elapsed
   currentState=ReadyToPing;
   break;   
 }
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should never have to write to tcnt. If you use unsigned 16 bit variables, the maths works out when you add a value that causes an overflow. The wonders of fixed precision binary math. You can prove it using a pencil and paper.
You can get longer periods by ‘hopping’. You need to keep a 32 bit variable of the period you want. If the wanted period is > 65536, then subtract 65536 from the variable and exit. You’ll get another compare event in 65536 ticks. Next compare do the same thing again until the wanted period is < 65536 and >0. If 0, the requiried period has elapsed. Another slightly different way is to have a variable with the number of hops you need.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I will leave that for my next project, LOL

Thanks for all your help! No to the next problem...