Timing problems with Timer1 on Arduino Pro Micro

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

Im deutschen Teil des Forums darfst Du Ddutsch schreiben.
Möchtest Du englisch schreiben dann mach es im englischen Teil.
Grüße Uwe

Danke für den Tipp, hab es nun in einen anderen Themenbereich verschoben. LG

maybe worth looking at the ESP32 Remote Control Transceiver (RMT) - an onboard peripheral which was designed to act as an infrared transceiver.

Id like to understand my underlying issue and how interrupts and timings, latencies, jitter etc work on an Arduino. Its not my goal to just have a working emitter, I want to learn.

not used "IRProtocols.h" so cannot help with problems

using the ESP32 RMT can get stable pulse sequence near to your specification, e.g. 23.2uSec High 46.2uSec LOW 30.7uSec HIGH then LOW for 3mSec

// ESP32 RMT pulse sequence 
//    23.2uSec High 46.2uSec LOW 30.7uSec HIGH then LOW for 3mSec

#include "Arduino.h"
#include "driver/rmt_tx.h"

void setup() {
	Serial.begin(115200);
	delay(2000);

	rmt_channel_handle_t txChannel {NULL};
	rmt_tx_channel_config_t tx_chan_config = {
			.gpio_num = GPIO_NUM_14,
			.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
			.resolution_hz = 10000000, // 10MHz resolution,
			.mem_block_symbols = 64,
			.trans_queue_depth = 4, // set the number of transactions that can be pending in the background
			};

	assert(rmt_new_tx_channel(&tx_chan_config, &txChannel)==ESP_OK && "Failed to Create RMT Channel");
	rmt_transmit_config_t transmitConfig = {
			.loop_count = -1
	};
	rmt_encoder_handle_t copyEncoder {NULL};
	rmt_copy_encoder_config_t copyEncoderConfig = {};
	assert(rmt_new_copy_encoder(&copyEncoderConfig, &copyEncoder)==ESP_OK && "Failed to Create Copy Encoder");

  // 23.2uSec High 46.2uSec LOW 30.7uSec HIGH then LOW for 3mSec
	const rmt_symbol_word_t pulsePattern[] = {
			{.duration0 = 232, .level0 = 1, .duration1 = 462, .level1 = 0},
			{.duration0 = 307, .level0 = 1, .duration1 = 30000, .level1 = 0},
		};

	assert(rmt_enable(txChannel)==ESP_OK && "Failed to Enable txChannel");
	Serial.println("Starting Transmitter");
	assert(rmt_transmit(txChannel, copyEncoder, &pulsePattern, sizeof(pulsePattern), &transmitConfig)==ESP_OK && "Failed to begin transmitting");
	Serial.println("Transmitter Running");
}

void loop() {
}

oscilloscope plot of sequence

looking at first pulse

pulse width is 23.2uSec
if you require pulse width to 23.25uSec accuracy you will require a faster processor than the ESP32 or use an FPGA

Looks promising. The accuracy should be good enough I guess but I have to try out.
Do you know why the pulses are more stable than when using a digital pin like I did? I mean the processor has a 16 MHz clock and I do not have any other processes running in my code, so why do I have these deviations? Thanks

function calls, interrupt execution, etc take time

consider the following simple program

// ESP32 pulse sequence using loop() and delayMicroseconds(us)
//    23uSec HIGH 46uSec LOW 30uSec HIGH then LOW for 100uSec

// in practice the sequence is 24uSec HIGH 47uSec LOW 31uSec HIGH then LOW
//  probably due to time to execute function calls

void setup() {
  Serial.begin(115200);
  pinMode(14, OUTPUT);
}

void loop() {
  digitalWrite(14, 1);
  delayMicroseconds(23);
  digitalWrite(14, 0);
  delayMicroseconds(46);
  digitalWrite(14, 1);
  delayMicroseconds(30);
  digitalWrite(14, 0);
  delayMicroseconds(100);
}

the pulse HIGH/LOW times are approximately 1uSec longer than the delayMicroseconds() call parameter, e.g. delayMicroseconds(30)


probably due to the time the function calls take
also one cannot do anything else in loop() as it will corrupt the pulse timing

one could use timer interrupts to generate the sequence but again one has errors introduced by the interrupt execution time plus any function calls within the ISR
however one can then do other operations in loop()

the advantage of using the RMT to generate the pulse sequence (as in program in post 6) is it is a peripheral independent of the processor - accuracy being limited by the RMT clock (10MHz in program in post 6)

I have no idea why your pulse sequences deviate by 5 or even 10 microseconds (stated in post 1)

Thanks for your detailled response. I tried out some things and can confirm that the micro processor is very sensitive to software latencies. When I just define a fixed pulse timing array and use an interrupt and don't use any other function or software delay, I can get very precise timings with deviations less than a half microsecond.

In playing around with interrupts on AVR devices, I found that the interrupts can trigger at quite different times, but there are ways to work around that.

For example the following code can assist, depending on your requirements:

static void inline wait_until(uint8_t time) {
__asm__ __volatile__ (
		"sub	%[time], %[tcnt1l]\n\t" //Subtract the low byte of TCNT1 from our designated time to wait until
                    "brmi   102f\n\t"               //If the result is negative, we already missed it. Branch to 102 and exit
	"100:\n\t"                      
                    "subi	%[time], 3\n\t"         //Subtract 3 from time
		"brcc	100b\n\t"               //Loop until we get a negative
		"subi	%[time], 0-3\n\t"       //Add 3 back to our value
		"breq	101f\n\t"               //If equal to 0, branch to 101 and delay 1/16 of a uS (No OPeration)
		"dec	%[time]\n\t"            //Decrement our value by 1
		"breq	102f\n\t"               //If equal to 0, branch to 102 and don't delay
		"rjmp	102f\n"                 //Else Jump to the end
	"101:\n\t"
		"nop\n"
	"102:\n"
	:                                 //Output variables (none)
	: [time] "a" (time),              //Input variables
	[tcnt1l] "a" (TCNT1L)
);
}

Basically, you call the function wait_until(150); from one of your interrupt routines. Might need to play around with the delay to get the least amount of delay for the least amount of jitter.

This creates a variable delay within the interrupt using assembly code. The delay depends on when the interrupt was triggered in relation to the Timer1 TCNT1 counter in this case.

I used this function for example with NTSC signalling at 8Mhz, and it made a big difference in controlling when exactly the signal starts/stops, removing a lot of the interrupt jitter.

For my example code, you can look at Sketches/NTSC_8Mhz_Example at master · TMRh20/Sketches · GitHub but it is a little complex.