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??
1 Answer 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)
.
-
Thank you! I've been pulling my hair out over this, but this finally works!AJMansfield– AJMansfield02/23/2017 16:47:59Commented Feb 23, 2017 at 16:47
-
1In case you are interested, I've finished packaging the library this was for: github.com/AJMansfield/TriacDimmerAJMansfield– AJMansfield03/06/2017 15:10:04Commented Mar 6, 2017 at 15:10
TIFR1 = _BV(OCF1A);
right beforeTIMSK1 |= _BV(OCIE1A);
.OCR1A = pulse_delay + pulse_length
I'd just tryOCR1A = TCNT1 + pulse_length;