2

I am trying to use one of the hardware timers on the arduino uno to generate a short pulse some number of microseconds after receiving a pulse on an input.

Currently my code looks like this:

uint16_t pulse_delay = 12000; //half-microseconds
uint16_t pulse_length = 20;
void setup(){
 pinMode(8, INPUT);
 pinMode(9, OUTPUT);
 TCCR1A = 0;
 TCCR1B = _BV(ICNC1) //input capture noise cancel
 | _BV(ICES1) //positive edge
 | _BV(CS11); // /8 prescaler
 TIMSK1 = _BV(ICIE1); //enable input capture interrupt
}
void loop(){}
ISR(TIMER1_CAPT_vect){
 TCCR1A = _BV(COM1A0) | _BV(COM1A1); //set OC1A on match
 TIMSK1 |= _BV(OCIE1A); //enable match interrupt
 OCR1A = pulse_delay; //pulse begin time
 TCNT1 = TCNT1 - ICR1; //TCNT1 now contains time since input pulse, even if 
 //the interrupt isn't run immediately
}
ISR(TIMER1_COMPA_vect){
 TIMSK1 &=~ _BV(OCIE1A); //disable match interrupt
 TCCR1A = _BV(COM1A1); //clear OC1A on match
 OCR1A = pulse_delay + pulse_length;
}

This code should theoretically do the task, but it doesn't produce any output at all - to my oscilloscope it looks like the output pin just stays low.

However, if I replace the last line (OCR1A = pulse_delay + pulse_length;) of the compare match interrupt with the following two lines, it outputs a pulse just fine. The issue with this is that it uses significantly more CPU time, and it can only count time from when the interrupt starts so if the interrupt is executed late the pulse will be longer.

 delayMicroseconds(pulse_length);
 TCCR1C = _BV(FOC1A); //manually trigger match event

All that the first version is doing differently is triggering the match event via an 'alarm' set on the timer, rather than waiting to trigger the match manually.

Why does the first version not work, and how can I make it work??

asked Feb 23, 2017 at 0:47
3
  • Just a quick guess, which I may develop into a complete answer when I have time, a little bit later: do TIFR1 = _BV(OCF1A); right before TIMSK1 |= _BV(OCIE1A);. Commented Feb 23, 2017 at 10:49
  • Also just a guess. Instead of OCR1A = pulse_delay + pulse_length I'd just try OCR1A = TCNT1 + pulse_length; Commented Feb 23, 2017 at 11:49
  • @6v6gt I have tried that and it also fails. It also has the problem that it lengthens the pulse by the amount of time spent waiting for interrupts to be re-enabled in case the interrupt is triggered during a noInterrupts() block. Commented Feb 23, 2017 at 16:46

1 Answer 1

1

Expanding on my comment, after checking experimentally that this was indeed the issue.

Short answer

You should do

TIFR1 = _BV(OCF1A); // clear interrutp flag

right before

TIMSK1 |= _BV(OCIE1A); // enable match interrupt

Explanation

Whenever the timer reaches the value stored in one of its "compare match" registers, this triggers a "compare match" event, which has the effect of raising the corresponding interrupt flag (OCF1A in your case). If the associated interrupt is enabled, that fires an interrupt request, and the flag is automatically cleared when the CPU starts executing the interrupt vector.

However, if the interrupt is not enabled, the flag just stays set. If you later enable the interrupt when the flag is already set, the interrupt request is sent immediately. This is not what you want, you want the interrupt to fire only on the next match. For this to happen, you have to clear the interrupt flag prior to enabling the interrupt.

The interrupt flags have a very non-intuitive behavior: you clear the flag by writing a logic one to it. Thus the line:

TIFR1 = _BV(OCF1A); // clear interrutp flag

For completeness, I would also clear ICF1 before enabling ICIE1, although this seems to not be critical for the working of your program.

Also, as a side note, be aware that the timer can increment while the CPU is in the middle of executing TCNT1 = TCNT1 - ICR1;. If you want the cleanest possible timings, leave the timer free-running and just set OCR1A to the appropriate values: ICR1+pulse_delay, then ICR1+(pulse_delay+pulse_length).

answered Feb 23, 2017 at 13:09
2
  • Thank you! I've been pulling my hair out over this, but this finally works! Commented Feb 23, 2017 at 16:47
  • 1
    In case you are interested, I've finished packaging the library this was for: github.com/AJMansfield/TriacDimmer Commented Mar 6, 2017 at 15:10

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.