I’ve put together this input capture sketch.
// input capture from Arduino.cc http://forum.arduino.cc/index.php?topic=40748.msg297609#msg297609
// one-shot code https://wp.josh.com/2015/03/05/the-perfect-pulse-some-tricks-for-generating-precise-one-shots-on-avr8/
/******* Sketch to test Input Capture interrupt handling *********************************
functionality: measure length of pulses on the ICP pin
Pulses provided by the one-shot code from Josh
Version of 3/25/19 appears to work and resolved pulse lengths as short as 41 clock cycles - woot!
**************************************************************************************************************/
#define icpPin 8 // ICP input pin on arduino
// some variables to help see that something is happening in the interrupt handlers
volatile unsigned int Value; // this stores the current ICR1 value (legacy)
volatile unsigned int eVal; // negative transition ICR1 value
volatile unsigned int sVal; // positive transition ICR1 value
unsigned int index;
uint8_t Overflows;
/* Overflow interrupt vector */
ISR(TIMER1_OVF_vect) { // here if no input pulse detected
Overflows++; // increment overflow count
}
//-------------------------------------------------------------------
// Interrupt Capture vector
ISR(TIMER1_CAPT_vect) {
if ( bit_is_set(TCCR1B , ICES1)) { // was rising edge detected ?
sVal = ICR1; // save the start time
}
else { // falling edge was detected
eVal = ICR1; // save the end time
}
TCCR1B ^= _BV(ICES1); // toggle bit value to trigger on the other edge
}
//--------------------------------------------------------------------
// One shot generator vars / setup
#define OSP_SET_WIDTH(cycles) (OCR2B = 0xff-(cycles-1))
unsigned char o = 42; // generate a pulse of 'o' clock cycles
// 42 seems to be the absolute minimum resolution
unsigned int t; // measured pulse time.
// Setup the one-shot pulse generator and initialize with a pulse width that is (cycles) clock counts long
void osp_setup(uint8_t cycles) {
TCCR2B = 0; // Halt counter by setting clock select bits to 0 (No clock source).
// This keeps anyhting from happening while we get set up
TCNT2 = 0x00; // Start counting at bottom.
OCR2A = 0; // Set TOP to 0. This effectively keeps us from counting becuase the counter just keeps reseting back to 0.
// We break out of this by manually setting the TCNT higher than 0, in which case it will count all the way up to
// MAX and then overflow back to 0 and get locked up again.
OSP_SET_WIDTH(cycles); // This also makes new OCR values get loaded from the buffer on every clock cycle.
TCCR2A = _BV(COM2B0) | _BV(COM2B1) | _BV(WGM20) | _BV(WGM21); // OC2B=Set on Match, clear on BOTTOM. Mode 7 Fast PWM.
TCCR2B = _BV(WGM22) | _BV(CS20); // Start counting now. WGM22=1 to select Fast PWM mode 7
DDRD |= _BV(3); // Set pin to output (Note that OC2B = GPIO port PD3 = Arduino Digital Pin 3)
}
// Setup the one-shot pulse generator
void osp_setup() {
osp_setup(1);
}
// Fire a one-shot pulse. Use the most recently set width.
#define OSP_FIRE() (TCNT2 = OCR2B - 1)
// Test there is currently a pulse still in progress
#define OSP_INPROGRESS() (TCNT2>0)
// Fire a one-shot pulse with the specified width.
// Order of operations in calculating m must avoid overflow of the uint8_t.
// TCNT2 starts one count lower than the match value becuase the chip will block any compare on the cycle after setting a TCNT.
#define OSP_SET_AND_FIRE(cycles) {uint8_t m=0xff-(cycles-1); OCR2B=m;TCNT2 =m-1;}
//---------------------------------------------------------------
void setup() {
Serial.begin (115200);
pinMode(icpPin, INPUT); // ICP pin (digital pin 8 on arduino) as input
pinMode(7, OUTPUT); // telltele LED
TCCR1A = 0 ; // this register set to 0!
TCCR1B = _BV(CS10); // NORMAL MODE!!, prescaler 1, rising edge ICP1 - this works (prescaler was 2)
TCCR1B |= _BV(ICES1); // enable input capture
TIMSK1 = _BV(ICIE1); // enable input capture interrupt for timer 1
TIMSK1 |= _BV(TOIE1); // enable overflow interrupt to detect missing input pulses
// one-shot generator
osp_setup(); // Initialize one-shot code
// setup TC1 for input capture
TCCR1A = 0;
TIFR1 = (1 << ICF1); /* clear input capture flag */
TCCR1B = 0x41; /* capture on rising edge, enable clock with no prescaling */
Serial.print("Finished setup\r\n");
}
void loop() {
OSP_SET_AND_FIRE(o); // Generate a one-shot with the width, in cycles, specified by o.
// Serial.print("\t");
// Serial.print("index ");
// Serial.print(index);
index += 1; // commenting/uncommenting print statements messes with the reported value
// Serial.print("\t\t");
// Serial.print(eVal);
// Serial.print("\t");
Serial.println(eVal - sVal);
// }
// Serial.println(" clocks registered");
delay(1000); // wait 1 second for next update
}
The pulses for measurement are generated by TCC2 using this code and have a claimed resolution down to one clock cycle width. Though I can’t verify width I set up a hardware S-R latch to verify pulses are produced. It’s working after a fashion because the code shown reports pulse widths equal to the value set, at least down to 41 clocks.
However, setting the pulse width to less than ~42 clocks breaks the thing, the reported pulse width goes wildly large and random. I’m guessing this is an execution time issue? Commenting/uncommenting various of the print statements preceding [color=blue]Serial.println(eVal - sVal);[/color] also results in random values reported.
Keeping in mind my interpretation may be faulty, according to an assembly dump it should take about 50 and 45 clocks respectively for rising edge/falling edge interrupt processing to complete (not an in depth examination). I don’t know if it’s significant but, this caught my attention. Based on the instruction cycle times, it’s 21 clocks into the capture ISR before the capture register is read for either rising or falling. Is this where the magical 42 (21+21) clock boundary is set?
Am I missing something about now interrupt processing relates to the main line code?
What might be the minimum resolvable pulse width?
The capture code, just in case:
// Interrupt Capture vector
ISR(TIMER1_CAPT_vect) {
4d0: 1f 92 push r1
4d2: 0f 92 push r0
4d4: 0f b6 in r0, 0x3f ; 63
4d6: 0f 92 push r0
4d8: 11 24 eor r1, r1
4da: 8f 93 push r24
4dc: 9f 93 push r25
if ( bit_is_set(TCCR1B , ICES1)) { // was rising edge detected ?
4de: 80 91 81 00 lds r24, 0x0081 ; 0x800081 <__TEXT_REGION_LENGTH__+0x7e0081>
4e2: 86 ff sbrs r24, 6
4e4: 09 c0 rjmp .+18 ; 0x4f8 <__vector_10+0x28>
sVal = ICR1; // save the start time
4e6: 80 91 86 00 lds r24, 0x0086 ; 0x800086 <__TEXT_REGION_LENGTH__+0x7e0086>
4ea: 90 91 87 00 lds r25, 0x0087 ; 0x800087 <__TEXT_REGION_LENGTH__+0x7e0087>
4ee: 90 93 31 01 sts 0x0131, r25 ; 0x800131 <sVal+0x1>
4f2: 80 93 30 01 sts 0x0130, r24 ; 0x800130 <sVal>
4f6: 08 c0 rjmp .+16 ; 0x508 <__vector_10+0x38>
}
else { // falling edge was detected
eVal = ICR1; // save the end time
4f8: 80 91 86 00 lds r24, 0x0086 ; 0x800086 <__TEXT_REGION_LENGTH__+0x7e0086>
4fc: 90 91 87 00 lds r25, 0x0087 ; 0x800087 <__TEXT_REGION_LENGTH__+0x7e0087>
500: 90 93 33 01 sts 0x0133, r25 ; 0x800133 <eVal+0x1>
504: 80 93 32 01 sts 0x0132, r24 ; 0x800132 <eVal>
}
TCCR1B ^= _BV(ICES1); // toggle bit value to trigger on the other edge
508: 90 91 81 00 lds r25, 0x0081 ; 0x800081 <__TEXT_REGION_LENGTH__+0x7e0081>
50c: 80 e4 ldi r24, 0x40 ; 64
50e: 89 27 eor r24, r25
510: 80 93 81 00 sts 0x0081, r24 ; 0x800081 <__TEXT_REGION_LENGTH__+0x7e0081>
}
514: 9f 91 pop r25
516: 8f 91 pop r24
518: 0f 90 pop r0
51a: 0f be out 0x3f, r0 ; 63
51c: 0f 90 pop r0
51e: 1f 90 pop r1
520: 18 95 reti
