Hey there!
I'm working on a custom IR emitter setup for experiments with my NVIDIA 3D Vision glasses.
The IR protocol of these glasses contain pulses of few microseconds (like 23.25 us high -> 46.2 us low -> 30.75 us high as a pulse sequence for opening the LCD shutter of the left eye for example).
I am using the Arduino Micro [SKU A000053] with atmega32u4 and I use PIN 9 for my experiment, which seems to be PB5 according to some pinout I found in the web.
When I measure the pulse widths with a 24 MHz logic analyzer and Saleae Logic2 I find that the timings are not consistent. Sometimes they are quite near my defined timings, sometimes they deviate by 5 or even 10 microseconds.
For example: Instead of the target pulse sequence according to the protocol down below
23.25 high, 21.1 low, 23.75 high, 100 low (FRAME_PAN), 23.25 high, 78.25 low, 39.75 high
the measurements show
19.75 high, 30.375 low, 14.125 high, 107 low (FRAME_PAN), 29.083 high, 71.875 low, 39.583 high
I am sure I have some rounding errors because I use the prescaler 8 and maybe because of multplying the timings with 2 to get the number of ticks, but these deviations seem to have another source of error. Is there anything I can do to fix these timing issues? Here is my code:
I am grateful for every hint! If you need further information, please let me know.
bfi.ino:
#include <Arduino.h>
#include "bfi.h"
volatile uint16_t persistence = DEFAULT_PERSISTENCE;
volatile uint16_t frameDurationMicros = DEFAULT_FRAME_DURATION_MICROS;
void setup() {
IR_Init();
}
void loop() {}
bfi.h:
#ifndef _BFI_H_
#define _BFI_H_
/* Includes: */
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "IREmitter.h"
// Default persistence ("on"-time for glasses)
#define DEFAULT_PERSISTENCE 4000 // 4 ms
// Default flicker frequency
#define DEFAULT_FRAME_DURATION_MICROS 16667UL // 60 Hz
// Time between sync trigger and start of IR token
#define FRAME_PAN 100 // 100 us
// PIN 9
#define IR_EMITTER 5
#define DDR_IR_EMITTER DDRB
#define PORT_IR_EMITTER PORTB
#define PRESCALER 8
#define bitSet(addr,bit) (addr |= (1<<bit))
#define bitClear(addr,bit) (addr &= ~(1<<bit))
#endif /* _BFI_H_ */
IREmitter.cpp:
#include <Arduino.h>
#include "bfi.h"
#include "IRProtocols.h"
#define START_IR_TIMER() (TCCR1B |= _BV(CS11)) // Prescaler /8 → 0.5us Ticks
extern volatile uint16_t persistence;
extern volatile uint16_t frameDurationMicros;
const IR_Protocol_t* IR_CurProtocol = &IRProt_3DVision;
static uint32_t lastFrameMicros = 0;
static volatile uint8_t lastPulse;
static volatile uint8_t curPulse;
static volatile uint8_t curToken;
static volatile bool frameActive = false;
static volatile bool emitterOn = false;
static volatile uint8_t tokenIndex = 0;
void IR_Init(void) {
bitSet(DDR_IR_EMITTER, IR_EMITTER);
// Timer1 Setup: CTC Mode
TCCR1A = 0;
TCCR1B = _BV(WGM12); // CTC mode
TIMSK1 = 0;
TIFR1 = 0xFF;
// OCR1A = Frame trigger every 60 Hz
OCR1A = TICKS(DEFAULT_FRAME_DURATION_MICROS);
TIMSK1 = _BV(OCIE1A); // Enable Compare A interrupt
TCNT1 = 0;
START_IR_TIMER();
}
// Frame start ISR
ISR(TIMER1_COMPA_vect) {
if (!frameActive) {
frameActive = true;
tokenIndex = 0;
SendNextToken();
}
}
ISR(TIMER1_COMPB_vect) {
emitterOn = !emitterOn;
if (emitterOn) {
bitSet(PORT_IR_EMITTER, IR_EMITTER); // HIGH
} else {
bitClear(PORT_IR_EMITTER, IR_EMITTER); // LOW
}
if (curPulse < lastPulse) {
OCR1B += IR_CurProtocol->timings[curPulse];
curPulse++;
} else {
if (tokenIndex < 4) {
SendNextToken();
} else {
bitClear(PORT_IR_LED, IR_LED);
frameActive = false;
}
}
}
static void SendNextToken() {
curPulse = IR_CurProtocol->indices[tokenIndex];
lastPulse = curPulse + IR_CurProtocol->sizes[tokenIndex];
bitClear(PORT_IR_EMITTER, IR_EMITTER);
emitterOn = false;
uint16_t delayTicks;
switch (tokenIndex) {
case 0: // Start opening left eye
case 1: // Start opening right eye
case 3: // Start closing right eye
delayTicks = TICKS(FRAME_PAN)
break;
case 2: // Start closing left eye
delayTicks = TICKS(persistence);
break;
}
OCR1B = TCNT1 + delayTicks; // Start pulses after delay
bitSet(TIMSK1, OCIE1B); // Enable Compare B
tokenIndex++;
}
IREmitter.h:
#ifndef IR_EMITTER_H
#define IR_EMITTER_H
void IR_Init(void);
void SendNextToken(void);
#endif // IR_EMITTER_H
IRPtocols.h:
#ifndef _IRPROTOCOLS_H_
#define _IRPROTOCOLS_H_
// Definitions for IR protocols, indexed in this order:
// [0]: Open left eye [1]: Open right eye
// [2]: Close left eye [3]: Close right eye
// Timer1-Ticks (0,5 µs)
#define TICKS(us) ((uint16_t)((us) * 2.0))
typedef struct {
uint8_t sizes[4];
uint8_t indices[4];
uint16_t timings[];
} IR_Protocol_t;
const IR_Protocol_t IRProt_3DVision = {
.sizes = { 1,3, 3,3 },
.indices = { 0,1, 4,7 },
.timings = {
TICKS(43.25),
TICKS(23.25), TICKS(46.2), TICKS(30.75),
TICKS(23.25), TICKS(21.1), TICKS(23.75),
TICKS(23.25), TICKS(78.25), TICKS(39.75)
}
};
#endif


