public
Last active

Arduino input capture example

  • Download Gist
InputCapture.ino
Arduino
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
/***
InputCapture.ino
 
Timer 1 high-resolution timing facility.
 
Copyright (C) 2008-2012 Bill Roy
 
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
 
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
 
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
***/
#include "Arduino.h"
#include <avr/interrupt.h>
 
/*
TO DO
analog threshold-based tick timer
set comparator to use internal reference
*/
 
// rising/falling edge tracking
// per '168 data sheet page 121, must clear ICF1 after edge direction change
//
volatile uint8_t rising;
//#define catchRisingEdge() (TCCR1B |= (1<<ICES1); TIFR1 |= (1<<ICF1); rising = 1;);
//#define catchFallingEdge() (TCCR1B &= ~(1<<ICES1); TIFR1 |= (1<<ICF1); rising = 0;);
 
 
// Event queue
#define MAX_EVENT_BUFF 32
unsigned long long last_event;
volatile byte event_buffer_head;
byte event_buffer_tail;
unsigned long long event_buffer[MAX_EVENT_BUFF];
 
unsigned long long eventRead(void) {
unsigned long long event;
if (event_buffer_tail == event_buffer_head) {
Serial.println("Event buffer underrun");
return -1;
}
event = event_buffer[event_buffer_tail] - last_event;
last_event = event_buffer[event_buffer_tail];
byte oldsreg = SREG;
cli();
if (++event_buffer_tail >= MAX_EVENT_BUFF) event_buffer_tail = 0;
SREG = oldsreg;
return event;
}
 
byte eventAvailable(void) {
byte head;
byte oldsreg = SREG;
cli();
head = event_buffer_head;
SREG = oldsreg;
return event_buffer_tail != head;
}
 
// Our virtual timer counts 2^64 clocks (provided sizeof(unsigned long long) == 8)
// The low order 16 bits are maintained by timer1 and are snapshotted by the
// TIMER1_CAPT input capture interrupt below.
//
// We maintain the high order 48 bits here by incrementing the virtual timer.
//
volatile unsigned long long t1_virtual_count;
ISR(TIMER1_OVF_vect) {
t1_virtual_count += 0x10000ULL;
}
 
 
unsigned long lost_event_count;
 
// Interrupt capture handler
//
ISR(TIMER1_CAPT_vect) {
 
byte l = ICR1L; // grab captured timer value
byte h = ICR1H;
 
// watch for the other edge to catch the half-pulse width
//rising ? catchFallingEdge() : catchRisingEdge();
if (rising) { TCCR1B &= ~(1<<ICES1); TIFR1 |= (1<<ICF1); rising = 0; }
else {TCCR1B |= (1<<ICES1); TIFR1 |= (1<<ICF1); rising = 1;}
// push the timestamp into the buffer
byte newhead = event_buffer_head + 1;
if (newhead >= MAX_EVENT_BUFF) newhead = 0;
if (newhead != event_buffer_tail) {
event_buffer[event_buffer_head] = t1_virtual_count + (h << 8) + l;
event_buffer_head = newhead;
}
else ++lost_event_count;
}
 
 
void printEvent(unsigned long long n, unsigned long base) {
char buf[32]; // stack for the digits
char *ptr = buf;
if (n == 0ULL) {
Serial.write('0');
return;
}
while (n > 0) {
*ptr++ = n % base;
n /= base;
}
while (--ptr >= buf) Serial.write((*ptr < 10) ? (*ptr + '0') : (*ptr - 10 + 'A'));
}
 
 
void initTimer(void) {
 
// Input Capture setup
// ICNC1: Enable Input Capture Noise Canceler
// ICES1: =1 for trigger on rising edge
// CS10: =1 set prescaler to 1x system clock (F_CPU)
TCCR1A = 0;
TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<CS10);
TCCR1C = 0;
 
//catchFallingEdge(); // initialize to catch
{ TCCR1B &= ~(1<<ICES1); TIFR1 |= (1<<ICF1); rising = 0; }
 
// Interrupt setup
// ICIE1: Input capture
// TOIE1: Timer1 overflow
TIFR1 = (1<<ICF1) | (1<<TOV1); // clear pending
TIMSK1 = (1<<ICIE1) | (1<<TOIE1); // and enable
 
// Set up the Input Capture pin, ICP1, which corresponds to Arduino D8
pinMode(8, INPUT);
digitalWrite(8, 0); // leave floating to count 60 Hz etc.
//digitalWrite(8, 1); // or enable the pullup
}
 
 
void setup(void) {
pinMode(13, OUTPUT);
 
// Power up the light sensor
//pinMode(9, OUTPUT); digitalWrite(9, 0);
//pinMode(10, OUTPUT); digitalWrite(10, 1);
 
Serial.begin(57600);
initTimer();
Serial.println("timing...");
}
 
long count, sumt, bogon_count;
unsigned long updatetime;
 
void loop60hz(void) {
long t;
long dt;
 
while (eventAvailable()) {
t = (long) eventRead();
dt = t - (F_CPU/60);
if (abs(dt) < 2000L) {
count = count + 1;
sumt += dt;
}
else ++bogon_count;
 
if (millis() > updatetime) {
Serial.println("");
Serial.print("t="); Serial.print(t);
//Serial.print(" dt="); Serial.print(dt);
Serial.print(" sdt="); Serial.print(sumt);
Serial.print(" n="); Serial.print(count);
Serial.print(" a="); Serial.print(sumt/count);
Serial.print(" e="); Serial.print(lost_event_count);
Serial.print(" b="); Serial.print(bogon_count);
updatetime = millis() + 1000L;
}
Serial.print(" "); Serial.print(dt);
}
}
 
 
void loop(void) {
while (eventAvailable()) {
Serial.print(count++); Serial.write(':');
printEvent(eventRead(), 10); Serial.write(' '); Serial.println(lost_event_count);
}
}

Nice. If I read it correctly it nicely implements the ICT and supporting concepts of overrun and with buffering for the main loop in a Arduino way of Available().
A macro description would be insightful. I think it is printing the time stamps the ICT received into a 32 deep storage buffer?
And not sure where the loop60Hz is used. Seems like it would be used to dump stats every 1/60 of a second.

I am trying to use it on a IR remote control. bumped up the buffer and added some delays as remove the lost events. however there appears to be a (likely) signed problem. as there are occasional irrationally large values. A logic scope and toggle of LED13 shows its not skipping.

I have fixed the problem in my fork. https://gist.github.com/4404996
The (h << 8) carries the sign of the shift in to the equation. I have used a union here to cleanly present the values as there component bytes and no need to shift bits.

Hope this helps. I suspect I will incorporate this into a few projects. perhaps merge it with https://github.com/shirriff/Arduino-IRremote
Thanks.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.