Newbie having trouble with Input Capture interrupts on Atmega 328P

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

Hi all,

 

I am working on a robot which moves forward while detecting the range of obstacles using an HC-SR04 ultrasonic rangefinder, and reverses and turns when an obstacle is too close. I am using a custom built board with an Atmega 328P microcontroller running at the default clock frequency of 1MHz. The rangefinder sends a pulse pulse to the microcontroller whose width is proportional to the distance to an obstacle, and I am attempting to measure the length of this pulse using the input capture feature on pin B0 in conjunction with input capture interrupts to detect rising and falling edges. Despite having spent hours googling for an answer and inspecting sample code found online, I am unable to get what seems to be a simple feature to work.

 

I have verified the operation of the sensor using an Arduino Uno board, so I know the problem does not lie there. Continuity testing on a multimeter indicates that there is a connection between the sensor and the microcontroller. The problem must therefore be a software one.

 

My research on the operation of the timers and interrupts on the Atmega 328P have lead me to the following algorithm:

 

  • Set up Timer 1 for input capture mode
  • Enable Timer 1 input capture and overflow interrupts
  • volatile unsigned int riseTime = 0; // time when rising edge takes place
  • volatile unsigned int fallTime = 0; // time when falling edge takes place
  • volatile int pulseWidth = 0; // width of sonar signal pulse - translate to distance
  • volatile float range = 0.00;

 

  • While(1)
    • ​set range, pulseWidth, riseTime, fallTime = 0
    • Send 10us pulse to rangefinder
    • (Re)Start timer with no prescaling // each tick will be 1us
    • Wait for return pulse rising edge
    • Input capture interrupt executes
      • riseTime = ICR1
      • fallTime = 0 // reset in case overflow happens before a rising edge
      • set ICES1 = 0 // to record falling edge time
    • Wait for falling edge // This is implemented as a while loop in code - I get stuck here
    • Input capture interrupt executes
      • fallTime = ICR1
      • set ICES1 = 0 // to record rising edge time
      • Turn Timer 1 off
      • reset Timer 1 counter
    • If timer overflows in the meantime, add 216-1 (65535 to falltime)
    • pulseWidth  = fallTime - riseTime //units -> microseconds
    • range = (pulseWidth/1e6)*(V_SOUND/2) // units metres
    • if (range > 0.1)
      • turn on LED on C3
    • else
      • turn off LED C3

 

Unfortunately I do not have access to an oscilloscope, so I am checking signals via an LED on pin C3. If a certain condition is fulfilled (or not), the LED will (or will not) glow, sort of like printf() statements in C programming for regular computers.

 

What is going on?/Evidence

 

Inside the TIMER1_CAPT_vect ISR

I know that the rising edge interrupt is executing because the LED turns on via an instruction in the TIMER1_CAPT_vect ISR.

If I place the same instruction in the falling edge portion of the same ISR, the LED does not turn on.

 

Conclusion - input capture is detecting a rising edge

 

Inside the TIMER1_OVF_vect ISR

The LED never turns on in this ISR

 

Conclusion - the timer never overflows

 

I have a loop in the program where I wait for the falling edge of the pulse. 

The debug LED is always on if the previously mentioned instruction is the only one of its kind in the program and inside this loop

 

Conclusion - the program gets stuck in an infinite loop here

 

I have attached my code below, with definitions and comment preamble removed to save on lines. Please excuse any egregious errors or deviations from standard practise as I am very new and figuring it all out as I go along while learning from examples and reconnecting with knowledge from my second year in university (just graduated).

 

If anybody has any advice or help to offer, I would greatly appreciate it. 

 

KnjazNik

 

N.B.  I was implementing calculation of pulse width etc inside the input capture ISR, but removed it in favour of polling just in case the problem was that the ISR was taking too many clock cycles.


#define F_CPU 1000000 // CPU frequency is 1.00 MHz (8.00 MHz / 8)

volatile unsigned int riseTime = 0; // time when rising edge takes place
volatile unsigned int fallTime = 0; // time when falling edge takes place
volatile int pulseWidth = 0; // width of sonar signal pulse - translate to distance
volatile float range = 0.00;

int main(void)
{

	DDRB = 0x00; // configure all B ports for input - ECHO
	DDRC = 0xFF;  //configure all C ports for output - TRIGGER
	DDRD = 0xFF;  //configure all D ports for output
	//PORTB = 0xFF; //PORTB pull-up resistors on

	TCCR1A = 0x00; // initialise High byte to zero
	TCCR1B = 0x00; // initialise Mid byte to zero
	TCCR1C = 0x00; // initialise Low byte to zero
	TCCR1B = (1 << ICES1); // enable rising edge capture
	TIMSK1 = (1 << ICIE1) | (1 << TOIE1); // enable input capture interrupt and overflow interrupt

	PORTD = 0x00; // initialise output to Port D as 0
	PORTD |= (1 << 0); // set the motor enable on D0

	sei(); // interrupts on.

    while (1)
    {
		range = 0; // reset range
		pulseWidth = 0; // reset pulsewidth to 0.
		riseTime = 0;	//reset risetime to 0
		fallTime = 0; // reset falltime to 0

		//PORTD |= (1<<1)|(1<<3); // move forward

		PORTC &= ~(1 << 4); // stop trigger pulse on PC5
		_delay_us(2);
		PORTC = (1 << 4); // send trigger pulse on PC5
		_delay_us(10); // keep high for 10us
		PORTC &= ~(1 << 4); // stop trigger pulse on PC5
		_delay_us(2);

		//start the timer

		TCCR1B = (1 << CS00); // use clock frequency as-is

		// wait for pulsewidth and range to update, then calculate distance
		while(fallTime == 0) //
		{
			//PORTC |= (1<<3);
		}
		pulseWidth = fallTime - riseTime; // calculate pulsewidth in timer ticks - 1 tick = 1us
		range = ((pulseWidth/1000000) * (V_SOUND / 2));

		// TODO:
		// if obstacle is too close, reverse and turn
		// below is placeholder to verify sensor operation
		// motor on if object too far/ off if too close

		if (range > 0.1)
		{
			//PORTD |= (1<<1)|(1<<3); // move forward on both motors
		}
		else
		{
			//PORTD |= ~(1<<1)|~(1<<3); // turn off motors otherwise
		}
    }

}

ISR(TIMER1_CAPT_vect) // input capture interrupt for measuring SONAR pulse width
{
	if(ICES1) // rising edge detected
	{
		riseTime = ICR1; // record time at which rising edge occurs
		fallTime = 0; // in case overflow happened before rising edge of Ultrasound return pulse
		TCCR1B = ~(1 << ICES1); // toggle edge capture to detect falling edge on next pass
		//PORTC |= (1<<3); // when uncommented, LED is flickering and dim - this line must be executing.
	}
	else if(!ICES1) // falling edge detected
	{
		fallTime = ICR1; // record time at which falling edge occurs
		TCCR1B |= (1 << ICES1); // toggle edge capture to detect rising edge on next pass
		TCCR1B &= ~(1 << CS00); // stop timer
		TCNT1 = 0; // clear the timer counter
		//PORTC |= (1<<3); // when only such line in code, the LED is never on - this line never executes.
	}
}

ISR(TIMER1_OVF_vect) // If timer overflows, add another 2^16 - 1 to the falling edge count
{
	fallTime += 65535; // 16bits minus 1
	//PORTC |= (1<<3);
}

 

This topic has a solution.
Last Edited: Tue. Oct 31, 2017 - 09:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1
	if(ICES1) // rising edge detected
...
	else if(!ICES1) // falling edge detected

Your naive tries to read ICES1 are a little surprising, because you seem to have no problem to correctly write bits.

 

And there are other problems, like accidentally changing the timer configuration here:

		TCCR1B = ~(1 << ICES1); // toggle edge capture to detect falling edge on next pass

and here:

		TCCR1B = (1 << CS00); // use clock frequency as-is

 

	fallTime += 65535; // 16bits minus 1

Why "minus 1"? And how should that work with a 16 bit fallTime anyway?

 

volatile unsigned int riseTime = 0; // time when rising edge takes place
volatile unsigned int fallTime = 0; // time when falling edge takes place
volatile int pulseWidth = 0; // width of sonar signal pulse - translate to distance

Do you expect to get pulses with a negative width?

 

		range = ((pulseWidth/1000000) * (V_SOUND / 2));

Won't work, result will be always zero (because of the integer division).

Stefan Ernst

Last Edited: Tue. Oct 31, 2017 - 06:55 AM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

If the (16-bit) TC is running at 1MHz, then it will overflow after 65.536ms, which is well beyond the recommended 60ms sample interval.  You don't need to check for overflow.

 

If you clear the counter on the rising edge, then the count on the falling edge is the pulse width in microseconds:

ISR(TIMER1_CAPT_vect)
{
    uint16_t count = TIMER1.TCNT;

    if (edge)	// rising edge
    {
	// set Edge Sense set to falling edge, clear counter
        TIMER1.TCCRB &= ~(1 << ICES1);
        TIMER1.TCNT = 0;
    }    
    else        // falling edge
    {
        // set Edge Sense set to rising edge
        TIMER1.TCCRB |= (1 << ICES1);
	    width = count;
    }
    edge = !edge;	// toggle edge
}

 

Greg Muth

Portland, OR, US

Atmel Studio 7.0 on Windows 10

Xplained/Pro/Mini Boards mostly

 

 

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

Thank you for your guidance sternst and Greg! I have updated my code as follows with your suggestions and comments. I am now successfully measuring a range, or in this case, turning the LED on when the distance to a target is below a certain threshold. Further calibration/investigation into the distance calculation will follow. The main lesson I have learned is to use flags - it would appear every example has them for a reason. 

 

volatile float riseTime = 0; // time when rising edge takes place
volatile float fallTime = 0; // time when falling edge takes place
volatile float pulseWidth = 0; // width of sonar signal pulse - translate to distance
volatile float range = 0.00;
volatile int edgeFlag; // flag for whether input capture is rising or falling edge - 1 for rising edge, 0 for falling edge

....
....

    while (1)
    {

		//PORTD |= (1<<1)|(1<<3); // move forward

		PORTC &= ~(1 << 4); // stop trigger pulse on PC5
		_delay_us(2);
		PORTC = (1 << 4); // send trigger pulse on PC5
		_delay_us(10); // keep high for 10us
		PORTC &= ~(1 << 4); // stop trigger pulse on PC5
		_delay_us(2);

		//start the timer

		TCCR1B = (1 << CS00); // use clock frequency as-is

		// TODO:
		// if obstacle is too close, reverse and turn
		// below is placeholder to verify sensor operation
		// motor on if object too far/ off if too close

		if (range < 0.4)
		{
			PORTC |= (1<<3); // move forward on both motors
		}
		else
		{
			PORTC &= ~(1<<3); // turn off motors otherwise
		}
    }

}

ISR(TIMER1_CAPT_vect) // input capture interrupt for measuring SONAR pulse width
{
	if(edgeFlag) // rising edge detected
	{
		riseTime = ICR1; // record time at which rising edge occurs
		fallTime = 0; // in case overflow happened before rising edge of Ultrasound return pulse
		edgeFlag = 0; // toggle edge capture to detect falling edge on next pass
		TCCR1B &= ~(1 << ICES1); // detect falling edge on next pass
		//PORTC |= (1<<3); // when uncommented, LED is flickering and dim - this line must be executing.
	}
	else if(!edgeFlag) // falling edge detected
	{
		fallTime = ICR1; // record time at which falling edge occurs
		edgeFlag = 1; // toggle edge capture to detect rising edge on next pass
		pulseWidth = fallTime - riseTime; // calculate pulsewidth in timer ticks - 1 tick = 1us
		range = ((pulseWidth/1000000) * (V_SOUND / 2));
		TCCR1B |= (1 << ICES1); // detect rising edge on next pass
		TCCR1B &= ~(1 << CS00); // stop timer
		TCNT1 = 0; // clear the timer counter
		//PORTC |= (1<<3); // when only such line in code, the LED is never on - this line never executes.
	}
}

ISR(TIMER1_OVF_vect) // If timer overflows, add another full timer value to the falling edge count
{
	fallTime += 0xFFFF;
	//PORTC |= (1<<3);
}

I believe I have some timing issues with the interrupts and main loop since the LED is not on at full brightness. I will post later with my findings and hopefully a correction.

 

KnjazNik

 

P.S. Also turns out I'm no good at reading forums and didn't notice Greg's advice on the first pass. Apologies!

Last Edited: Tue. Oct 31, 2017 - 09:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

UPDATE: It all works splendidly now. Thank you Greg for your suggested code segment.

 

Just one question. Is the declaration of the count variable inside the ISR standard practise for such situations? Matters of convention as such evade me completely.

 

When my robot is finished I'll make a project page about it. 

 

Thanks,

 

KnjazNik

 

 

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

Is the declaration of the count variable inside the ISR standard practise for such situations?

I don't know about standard practice...  The key things are to get/set whatever register(s) need attention as early as possible in the ISR, and to keep them as short (in terms of speed) as possible.

 

Greg Muth

Portland, OR, US

Atmel Studio 7.0 on Windows 10

Xplained/Pro/Mini Boards mostly

 

 

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

I would avoid using floats on an 8-bit device.  ALL the arithmetic has to be done in software.  I would use an unsigned int that represents either centimeters or tenths of centimeters.

Greg Muth

Portland, OR, US

Atmel Studio 7.0 on Windows 10

Xplained/Pro/Mini Boards mostly

 

 

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

Thanks! I've learned several useful things today.