Trying to use Timer1 to capture Pulse width without interrupt(s) ATmega328p

Arduino Pro Mini

I planned on writing a simple program that would use Timer1 to measure the Pulse of a HC-SR04. I thought it would be simple but I can't get the rising edge (and maybe the falling) edge to work.

Using a logic analyzer and the C0 to C3 outputs to monitor the software path I find at
if ((TIFR1 & (1 << ICF1)) == 1); digitalWrite(C0,1); ICF1 is already high.

So my question is; can the capture function of Timer1 be used without an interrupt? Or am I missing something else.

/*
V06a  only rising capture

 Pin Assignments:
   UART:   TXO PORTD1
           RXI PORTD0
  Trigger Pulse out:        Pin  9  PORTB1
  Echo Pulse In:      ICP1  Pin  8  PORTB0
  Internal LED              Pin 13  PORTB5
 */


//#include <Arduino.h>

#define trigger 9
#define echo 8
#define LED 13
#define C0 14
#define C1 15
#define C2 16
#define C3 17

// *** Main *******************************************************
// ****************************************************************

void setup(void)
{
// Variables
  pinMode(echo,INPUT);
  pinMode(trigger,OUTPUT); // PB1 Trigger
  pinMode(LED,OUTPUT); // LED
  PORTB = 0;

  pinMode(C0,OUTPUT); //C0
  pinMode(C1,OUTPUT);
  pinMode(C2,OUTPUT);
  pinMode(C3,OUTPUT); //C3
  digitalWrite(trigger,0);

  //sei();      /* Enable global interrupt */

 // TCCR1A = 0;   /* Set all bit to zero Normal operation */
  //TIMSK1 = 0b00100001;  // enable ICIE1 and TOIE1
}
// *** Start LOOP ****************************************
// ****************************************************************
 void loop () {

    digitalWrite(C0,0);
    digitalWrite(C1,0);
    digitalWrite(C2,0);
    digitalWrite(C3,0);
    delay(5);
    TCNT1 = 0;            // Clear Timer counter
    TCCR1B = 0b01000001;  // Capture on rising edge, prescale 1
    TIFR1 = 0;

   /* 10us trigger pulse*/
    digitalWrite(trigger,1);
    delayMicroseconds(8);
    digitalWrite(trigger,0);


/* ------------------------------------------------------------- */
/* ---- Capture rising edge ----------------------------------- */

    if ((TIFR1 & (1 << ICF1)) == 1);    digitalWrite(C0,1);  // C0

    while ((TIFR1 & (1 << ICF1)) == 0){   //  <<<<<<<<<<<<<<<<<<<<<<<<<<<< wait rising edge
      digitalWrite(C1, !digitalRead(C1)); // toggle C1
      TCNT1 = 0;              // Clear Timer counter
      }
   digitalWrite(C2,1);  // C2

   delay(2000);
  }

// --- eof --------------------------------------------------------------------

This is a Timer1 input capture sketch I have used with an HC SR04

volatile unsigned int Ticks;// holds time as .5 us ticks
volatile boolean pulseCaptured = false;
//int icpPin = 8;

boolean triggerFinished;

const byte trigPin = 7;
const byte  echoPin = 8; //icp pin

void setup() {
  //Timer1 Setup
  TCCR1A = 0;   //initialize register
  TCCR1B = 0;  //initialize register
  TCCR1B = (1 << CS11);  // prescaler 8 for .5us tick
  TCCR1B |= (1 << ICES1); //enable input capture edge select rising
  //TCCR1B |= (1<< ICNC1);//enable noise cancellation
  TIMSK1 = _BV(ICIE1);   // enable input capture interrupt for timer 1

  //pinMode(icpPin, INPUT);

  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

  Serial.begin (115200);
  Serial.println("starting...");
}



void loop() {

  if (triggerFinished == false)
  {
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    triggerFinished = true;
  }

  if (pulseCaptured == true && triggerFinished == true)
  {
    pulseCaptured = false; //reset
    triggerFinished = false;
    Serial.print(getTick() / 2 / 29.1, 1);
    Serial.print('\t');
    //Serial.print((LastPulseTime/2) / 29.1,1);
    Serial.println("cm");
    //Serial.print('\t');
    //Serial.println();
    delay(1000);
  }
}

ISR(TIMER1_CAPT_vect) {
  //if ( bit_is_set(TCCR1B , ICES1)) // was rising edge detected ?
  if (bitRead(TCCR1B, ICES1)) //bit is set and rising edge selected
    //if(! bit_is_set(TCCR1B ,ICES1))// was falling edge detected ?
  {
    TCNT1 = 0;       // reset the counter
    ICR1 = 0;
    Ticks = 0;
  }
  else {                                // falling edge was detected
    Ticks = ICR1;//pulse length
    ICR1 = 0;
    TCNT1 = 0;
    pulseCaptured = true;
  }
  // TCCR1B ^= _BV(ICES1);// toggle bit value to trigger on the other edge
  TCCR1B ^= 1 << (ICES1); // toggle bit value to trigger on the other edge
}

unsigned int getTick() {
  unsigned int akaTick = 0;       // holds a copy of the tick count so we can return it after re-enabling interrupts
  cli();             //disable interrupts
  akaTick = Ticks;
  Ticks = 0;
  sei();             // enable interrupts
  return akaTick / 2; //microseconds
}

Thanks, I'll look it over :slight_smile:

If you are not using the interrupt, you have to manually reset the ICF1 bit of TIFR1 after use by writing 1 to it.

TIFR1
Bit 5 – ICF1: Timer/Counter1, Input Capture Flag
This flag is set when a capture event occurs on the ICP1 pin. When the Input Capture Register (ICR1) is set by
the WGM13:0 to be used as the TOP value, the ICF1 Flag is set when the counter reaches the TOP value.
ICF1 is automatically cleared when the Input Capture Interrupt Vector is executed. Alternatively, ICF1 can be
cleared by writing a logic one to its bit location

@6v6gt Thank for the reply :slight_smile:

I think I'm confused. Its entirely possible I'm missing something critical and just haven't been able to see it.

Bit 5 - ICF1: Timer/Counter1, Input Capture Flag.
This flag is set when a capture event occurs on the ICP1 pin.

I was working under the assumption that I would clear the ICF1 bit, then when a edge (rising or falling depending on the ICES1 bit, the timer count TCNT1 would be copied to OCR1 register.

In the code I posted, I'm using C0 to C3 outputs for debug purposes.

In my test code loop:

  1. I reset C0 to C3 to 0
  2. clear the TCNT1
  3. Set the capture for the rising edge
  4. Reset the whole TIFR1 register
  5. Generate a trigger pulse
  6. test the ICF1 bit, finding it Set then set Port C0 output to High.

The logic trace indicates that the C0 output goes high 4.6µs after the pulse ends suggesting the ICF1 bit is already set making the processor think a rising edge has occurred on the Echo (which it hasn't).

So I'm baffled..

Just before this statement in the loop() :

TCCR1B = 0b01000001; // Capture on rising edge, prescale 1

try this :

TIFR1 = bit (ICF1) ; // clear flag with 1 (https://www.gammon.com.au/forum/?id=11504&reply=12#reply12 )

I was working under the assumption that I would clear the ICF1 bit, then when a edge (rising or falling depending on the ICES1 bit, the timer count TCNT1 would be copied to OCR1 register.

On a capture event, the TCNT1 value is written to ICR1.

From the data sheet

When a change of the logic level (an event) occurs on the Input Capture pin (ICP1), or alternatively on the
Analog Comparator output (ACO), and this change confirms to the setting of the edge detector, a capture
will be triggered: the 16-bit value of the counter (TCNT1) is written to the Input Capture Register (ICR1).
The Input Capture Flag (ICF) is set at the same system clock cycle as the TCNT1 value is copied into the
ICR1 Register. If enabled (TIMSK1.ICIE=1), the Input Capture Flag generates an Input Capture interrupt.
The ICF1 Flag is automatically cleared when the interrupt is executed. Alternatively the ICF Flag can be
cleared by software by writing '1' to its I/O bit location.

My earlier posting used an input capture interrupt, and I now realize that you are trying to do this without the interrupt but by monitoring the flag. I apologize for my misunderstanding.

The logic trace indicates that the C0 output goes high 4.6µs after the pulse ends suggesting the ICF1 bit is already set making the processor think a rising edge has occurred on the Echo (which it hasn't).

if ((TIFR1 & (1 << ICF1)) == 1);    digitalWrite(C0,1);   ICF1 is already high.

The semicolon after the conditional is wrong, and the digitalWrite() happens without the flag set. The 4.6 us is correct for the time to digitalWrite() a pin HIGH.

I think to capture the lead edge after the trigger you want something like

while(ICF1==0){};//wait here
digitalWrite(CO,1);//mark lead edge
//clear flag by writing 1 to the register
//reset edge select

With a prescale of 1, the 16-bit counter is only good for 4095 microseconds or about 70 cm. I'd recommend using a prescale of 8 to get a max distance of 560 cm (5.6 meters).

This works for me:

// Measure the HC-SR04 echo pulse on Arduino UNO Pin 8 (ICP1 pin) and calculate the distance


const byte TriggerPin = 9;
const byte EchoPin = 8; // MUST be 8 (ICP1)


// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().


// 343 meters per second == 0.002915452 seconds per meter
const float MicrosecondsPerCM = 29.15452;
const float MicrosecondsPerRoundTripCM = MicrosecondsPerCM * 2;  // ~58.3
const float HalfMicrosecondsPerRoundTripCM = MicrosecondsPerRoundTripCM * 2; // ~116.6


// TCCR1B values to start Timer 1, prescale = 8 (2 MHz count rate)
// Input Capture Edge Select (1=Rising, 0=Falling)
const uint8_t TCCR1BCaptureRisingEdge  = _BV(CS11) |  _BV(ICES1);
const uint8_t TCCR1BCaptureFallingEdge = _BV(CS11);


uint16_t RisingEdgeTime = 0;
unsigned long TimeOfLastPing = 0;


void setup()
{
  Serial.begin(115200);
  while (!Serial);


  Serial.println("Ready");


  digitalWrite(TriggerPin, LOW);

  pinMode(TriggerPin, OUTPUT);
  pinMode(EchoPin, INPUT);


  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;


  TIFR1 = _BV(ICF1); // clear Input Capture Flag
  TCCR1B = TCCR1BCaptureRisingEdge;
  interrupts ();
}


void loop()
{
  // Is the Input Capture Flag set?
  if (TIFR1 & _BV(ICF1))
  {
    uint16_t edgeTime = ICR1;
    TIFR1 = _BV(ICF1); // Clear the Input Capture Flag (Yes, by setting it to 1)


    if (RisingEdgeTime)
    {
      // The RisingEdgeTime is already set so this must be the falling edge
      uint16_t elapsed = edgeTime - RisingEdgeTime;


      // Look for the next rising edge
      RisingEdgeTime = 0;
      TCCR1B = TCCR1BCaptureRisingEdge;


      // Now calculate distance:
      float cm = elapsed / HalfMicrosecondsPerRoundTripCM;
      Serial.println(cm, 3);
    }
    else // !RisingEdgeTime
    {
      // RisingEdgeTime is 0 so this must be the rising edge
      RisingEdgeTime = edgeTime;


      // Now look for a falling edge
      TCCR1B = TCCR1BCaptureFallingEdge;
    }
  }


  // Be sure to leave 20 to 30 milliseconds between pings so 
  // the sensor doesn't catch old echoes from the previous ping.
  if (millis() - TimeOfLastPing >= 30)
  {
    TimeOfLastPing = millis();
    digitalWrite(TriggerPin, HIGH);
    digitalWrite(TriggerPin, LOW);
  }
}

@johnwasser

Thanks, this even more than I was looking for but it will help my understand were I went astray.

John