/* IR Widget: capture a raw IR signal and dump the timing of the non-demodulated signal
http://www.piclist.com/images/boards/irwidget/index.htm
http://www.hifi-remote.com/forums/dload.php?action=file&file_id=2044
http://www.hifi-remote.com/wiki/index.php?title=IR_Scope_and_IR_Widget_User%27s_Guide
http://www.compendiumarcana.com/irwidget/
Arduino digital pin numbers for the input capture pin (ICP) and the logic analyzer debugging pin (LA Dbg):
Board name / MCU | ICP pin | LA Dbg pin
-------------------------------------------|--------------.-----------|------------------------
Duemilanove/Uno (ATmega328P / ATmega168) | ICP1/PB0, Arduino pin 8 | PD6, Arduino pin 6
Leonardo (ATmega32U4) | ICP1/PD4, Arduino pin 4 | PD6, Arduino pin 12
Arduino Mega 2560 (ATmega2560) | ICP4/PL0, Arduino pin 49 | PL6, Arduino pin 43
see also here:
http://arduino.cc/en/Hacking/PinMapping168 (also for ATmega328P)
http://arduino.cc/en/Hacking/PinMapping32u4
http://arduino.cc/en/Hacking/PinMapping2560
*/
// Copyright (c) 2012 Michael Dreher <michael(at)5dot1.de>
// this code may be distributed under the terms of the General Public License V2 (GPL V2)
//#include <arduino.h>
////////////////////////////////////////////////////////////////////////////////
// User settings: this section must be adapted to your environment
////////////////////////////////////////////////////////////////////////////////
//#define ARDUINO_SERIAL_BAUDRATE 115200 // adapt this to your serial port baud rate
#define ARDUINO_SERIAL_BAUDRATE 1000000 // adapt this to your serial port baud rate
// the amount of RAM consumed by stack and global variables (e.g. serial receive buffer of 64 byte)
#if defined(_AVR_IOM2560_H_)
const uint16_t projectRamUsage = 1200;
#else
const uint16_t projectRamUsage = 380 + 64;
#endif
const uint16_t bufSize ((RAMEND - 0x100 - projectRamUsage) / sizeof(uint16_t)); // use as much RAM as possible
const uint8_t RANGE_EXTENSION_BITS = 4; // factor for upper measurement range = 2^(RANGE_EXTENSION_BITS+1)
#if RANGE_EXTENSION_BITS > 8
typedef uint16_t ovlBitsDataType;
#else
typedef uint8_t ovlBitsDataType;
#endif
// used to debug the timing with a logic analyzer or oszilloscope on a port pin
// Arduino pin numbers of PD6: Duemilanove: pin 6, Leonardo: pin 12
// Arduino pin numbers of PL6: Mega2560: pin 43
// when you don't need debugging, comment out the #define DEBUG_PIN
//#define DEBUG_PIN 6
//#define DEBUG_PORT D
//#define DEBUG_PIN 6
//#define DEBUG_PORT L
////////////////////////////////////////////////////////////////////////////////
// Adaption to different MCUs and clk values
////////////////////////////////////////////////////////////////////////////////
#if defined(_AVR_IOM32U4_H_)
#define CAP_PORT D
#define CAP_PIN 4
#define CAP_TIM 1
#define CAP_TIM_OC A
#else
#if defined(_AVR_IOM2560_H_)
#define CAP_PORT L
#define CAP_PIN 0
#define CAP_TIM 4
#define CAP_TIM_OC A
#else
// the default is the setting for the ATmega328P / ATmega168
#define CAP_PORT B
#define CAP_PIN 0
#define CAP_TIM 1
#define CAP_TIM_OC A
#endif
#endif
// convert number of clocks to number of nanoseconds, try to use integer arithmetic and avoid
// overflow and too much truncation (double arithmetic costs additional 800 byte of code)
uint32_t inline timerValueToNanoSeconds(uint32_t x)
{
#if (F_CPU % 8000000) == 0
return ((x * 125UL) / (F_CPU/8000000UL));
#else
#if (F_CPU % 1000000) == 0
return ((x * 1000UL) / (F_CPU/1000000UL));
#else
#if (F_CPU % 115200) == 0 // serial bps rate compatible cpu clocks, e.g. 7372800 or 14745600
// TODO: this has to be tested, especially the accuracy
return ((((x * 1000UL) / (F_CPU/115200UL)) * 625UL) / 72UL);
#else
// TODO: this has to be tested
return (((double)x * 1.0E9) / (double)F_CPU); // use double precision floating point arithmetic
#endif
#endif
#endif
}
////////////////////////////////////////////////////////////////////////////////
// Helper macros
////////////////////////////////////////////////////////////////////////////////
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // clear bit
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // set bit
#define __CAT2(base, portname) base##portname // internally needed by CAT2
#define CAT2(prefix, num) __CAT2(prefix, num) // build a define name from 2 params
#define __CAT3(prefix, num, postfix) prefix##num##postfix // internally needed by CAT3
#define CAT3(prefix, num, postfix) __CAT3(prefix, num, postfix) // build a define name from 3 params
// these macros are used to debug the timing with an logic analyzer or oszilloscope on a port pin
inline void DEBUG_PIN_TOGGLE(void) {
#if defined(DEBUG_PIN) && defined(DEBUG_PORT)
CAT2(PIN, DEBUG_PORT) = _BV(DEBUG_PIN);
#endif
}
inline void DEBUG_PIN_CLEAR(void) {
#if defined(DEBUG_PIN) && defined(DEBUG_PORT)
cbi(CAT2(PORT, DEBUG_PORT), DEBUG_PIN);
#endif
}
////////////////////////////////////////////////////////////////////////////////
// Main part: Capture function
////////////////////////////////////////////////////////////////////////////////
uint16_t captureData[bufSize]; // the buffer where the catured data is stored
int16_t captureCount; // number of values stored in captureData
inline uint16_t packTimeVal(uint16_t diffVal, ovlBitsDataType ovlCnt)
{
// overflow part is stored in the lower RANGE_EXTENSION_BITS bits and not in
// the upper bits because that makes the code smaller here (less shifting)
return (0x8000 | ((diffVal >> 1) & (0x7fff & ~(_BV(RANGE_EXTENSION_BITS) - 1))) | ovlCnt);
}
inline uint32_t unpackTimeVal(uint32_t val)
{
if(val & 0x8000)
{
val = val & 0x7fff;
uint32_t valOvl = (val & (_BV(RANGE_EXTENSION_BITS) - 1)) << 16;
uint32_t valTim = (val << 1) & (0x7fff & ~(_BV(RANGE_EXTENSION_BITS) - 1));
val = valOvl | valTim;
}
return val;
}
// Wait for a signal on pin ICP1 and store the captured time values in the array 'captureData'
void startCapture(void)
{
unsigned char sreg = SREG;
#ifdef USBCON
// disabling IRQs for a long time will disconnect the USB connection of the ATmega32U4, therefore we
// defer the sbi() instruction until we got the starting edge and only stop the Timer0 in the meanwhile
uint8_t tccr0b = TCCR0B;
TCCR0B &= ~(_BV(CS02) | _BV(CS01) | _BV(CS00)); // stop timer0 (disables timer IRQs)
#else
cli(); // disable IRQs
#endif
register uint8_t icesn_val = _BV(CAT2(ICES, CAP_TIM));
register uint8_t tccrnb = CAT3(TCCR, CAP_TIM, B) | icesn_val; // trigger on rising edge
CAT3(TCCR, CAP_TIM, B) = tccrnb;
OCR1A = CAT2(TCNT, CAP_TIM) - 1;
CAT2(TIFR, CAP_TIM) = _BV(CAT2(ICF, CAP_TIM))
| _BV(CAT3(OCF, CAP_TIM, CAP_TIM_OC)) | _BV(CAT2(TOV, CAP_TIM)); // clear all timer flags
register ovlBitsDataType ovlCnt = 0;
register uint16_t prevVal = 0;
register uint8_t tifr; // cache the result of reading TIFR1 (masked with ICF1 and OCF1A)
register uint16_t *pCapDat; // pointer to current item in captureData[]
DEBUG_PIN_CLEAR();
DEBUG_PIN_TOGGLE();
for(pCapDat = captureData; pCapDat <= &captureData[bufSize - 1]; )
{
DEBUG_PIN_TOGGLE();
// wait for edge or overflow (output compare match)
while(! (tifr =
(CAT2(TIFR, CAP_TIM) & (_BV(CAT2(ICF, CAP_TIM)) | _BV(CAT3(OCF, CAP_TIM, CAP_TIM_OC)))))) {
}
DEBUG_PIN_TOGGLE();
uint16_t val = CAT2(ICR, CAP_TIM);
CAT3(OCR, CAP_TIM, CAP_TIM_OC) = val; // timeout based on previous trigger time
if(tifr & _BV(CAT3(OCF, CAP_TIM, CAP_TIM_OC))) // check for overflow bit
{
if(pCapDat != captureData) // ignore overflow at the beginning of the capture
{
if(ovlCnt >= (_BV(RANGE_EXTENSION_BITS) - 1))
{
// clear input capture and output compare flag bit
CAT2(TIFR, CAP_TIM) = _BV(CAT2(ICF, CAP_TIM)) | _BV(CAT3(OCF, CAP_TIM, CAP_TIM_OC));
break; // maximum value reached, treat this as timeout and abort capture
}
ovlCnt++;
}
// clear input capture and output compare flag bit
CAT2(TIFR, CAP_TIM) = _BV(CAT2(ICF, CAP_TIM)) | _BV(CAT3(OCF, CAP_TIM, CAP_TIM_OC));
continue;
}
#ifdef USBCON
cli();
#endif
tccrnb ^= icesn_val; // toggle the trigger edge
CAT3(TCCR, CAP_TIM, B) = tccrnb;
// clear input capture and output compare flag bit
CAT2(TIFR, CAP_TIM) = _BV(CAT2(ICF, CAP_TIM)) | _BV(CAT3(OCF, CAP_TIM, CAP_TIM_OC));
uint16_t diffVal = val - prevVal;
if(ovlCnt || (diffVal & 0x8000))
{
diffVal = packTimeVal(diffVal, ovlCnt);
ovlCnt = 0;
}
*pCapDat = diffVal;
pCapDat++;
prevVal = val;
}
DEBUG_PIN_CLEAR();
// the first array entry contains only the starting time and no time
// difference, therefore ist is no longer needed and will be removed
captureCount = (pCapDat - captureData) - 1; // correct count
for(int16_t i = 0; i < captureCount; i++)
{
captureData[i] = captureData[i + 1];
}
#ifdef USBCON
TCCR0B = tccr0b; // re-enable Timer0
#endif
SREG = sreg; // enable IRQs
}
////////////////////////////////////////////////////////////////////////////////
// Main loop and output functions
////////////////////////////////////////////////////////////////////////////////
// formatter function: compact format appropraiate for directly reading from screen
inline void dumpBufferCompact()
{
Serial.print(F("capture["));
Serial.print(captureCount, DEC);
Serial.print(F(" values]="));
Serial.println();
for(uint16_t i = 0; i < captureCount; i++)
{
uint32_t val = timerValueToNanoSeconds(unpackTimeVal(captureData[i]));
Serial.write((i & 0x01) ? '-' : '+');
Serial.print(val, DEC);
if(val >= 50000)
Serial.println();
else
Serial.print(' ');
}
Serial.println();
Serial.println();
}
// formatter function: CSV format to use for OpenOffice or Excel charts
inline void dumpBufferCSV()
{
Serial.println(F("\"Time [ns]\";\"Signal\";\"Duration\""));
uint32_t absTime = 0;
for(uint16_t i = 0; i < captureCount; i++)
{
uint32_t val = timerValueToNanoSeconds(unpackTimeVal(captureData[i]));
Serial.print(absTime, DEC);
Serial.write(';');
Serial.write((i & 0x01) ? '1' : '0');
Serial.write(';');
Serial.write('0'); // dummy duration
Serial.println();
Serial.print(absTime + 1, DEC); // lying somewhat to avoid identical timestamps
Serial.write(';');
Serial.write((i & 0x01) ? '0' : '1');
Serial.write(';');
Serial.println(val, DEC);
absTime = absTime + val;
}
Serial.println();
}
// calculate the medium pulse width and the period/carrier frequency
inline void calcCarrierFreq()
{
uint32_t pulseWidthSum = 0;
uint32_t pauseWidthSum = 0;
uint32_t shortestPeriod = 0xffffffff;
for(uint16_t i = 0; i < (captureCount - 1); i += 2)
{
uint32_t period = unpackTimeVal(captureData[i] + captureData[i + 1]);
if(period < shortestPeriod)
shortestPeriod = period;
}
uint16_t sumCount = 0;
for(uint16_t i = 0; i < (captureCount - 1); i += 2)
{
uint32_t valHi = unpackTimeVal(captureData[i]);
uint32_t valLo = unpackTimeVal(captureData[i + 1]);
uint32_t period = valHi + valLo;
// only use complete pulse+pause which are in the range shortestPeriod + 10%
if(period <= ((11*shortestPeriod) / 10))
{
pulseWidthSum += valHi;
pauseWidthSum += valLo;
sumCount++;
}
if((pulseWidthSum >= 0x70000000) || (pauseWidthSum >= 0x70000000))
break;
}
if(sumCount >= 1)
{
uint32_t mediumPeriod = timerValueToNanoSeconds((pulseWidthSum + pauseWidthSum) / sumCount);
uint32_t mediumPulse = timerValueToNanoSeconds(pulseWidthSum / sumCount);
Serial.print(F("Number of sample periods used for average values: "));
Serial.println(sumCount, DEC);
Serial.print(F("Carrier frequency [Hz]: "));
Serial.println(1000000000 / mediumPeriod, DEC);
Serial.print(F("Medium period [ns]: "));
Serial.println(mediumPeriod, DEC);
Serial.print(F("Medium pulse width [ns]: "));
Serial.println(mediumPulse, DEC);
Serial.print(F("Duty cycle [%]: "));
Serial.println(100 * mediumPulse / mediumPeriod, DEC);
Serial.println();
}
}
void loop()
{
startCapture();
if(captureCount > 0)
{
Serial.print(F("bufferSize="));
Serial.println(bufSize, DEC);
// select the format you want
dumpBufferCSV();
dumpBufferCompact();
calcCarrierFreq();
delay(5);
}
}
////////////////////////////////////////////////////////////////////////////////
// Initialization
////////////////////////////////////////////////////////////////////////////////
// initialize Timer and IO pins, needs to be called once before calling startCapture()
void setupCapture()
{
// configure signal capture ICP pin as input
cbi(CAT2(DDR, CAP_PORT), CAP_PIN);
#if defined(DEBUG_PIN) && defined(DEBUG_PORT)
sbi(CAT2(DDR, DEBUG_PORT), DEBUG_PIN); // configure logic analyzer debug pin as output
#endif
// init timer, disable power save mode of timer
#ifdef PRR0 // for ATmega32U4 and ATmega2560
#if PRTIM <= 2
cbi(PRR0, CAT2(PRTIM, CAP_TIM)); // for ATmega32U4 and ATmega2560
#else
cbi(PRR1, CAT2(PRTIM, CAP_TIM)); // for ATmega2560
#endif
#else
cbi(PRR, CAT2(PRTIM, CAP_TIM));
#endif
CAT3(TCCR, CAP_TIM, A) = 0; // Timer mode 0 = normal
CAT3(TCCR, CAP_TIM, B) = _BV(CAT2(ICNC, CAP_TIM)) | _BV(CAT3(CS, CAP_TIM, 0)); // no prescaler, enable noise canceler
}
void setup()
{
Serial.begin(ARDUINO_SERIAL_BAUDRATE); // connect to the serial port
setupCapture();
for(uint16_t i = 0; i < captureCount; i++)
captureData[i] = 0;
}