TimerOne: interval is half the value after start() on Teensy 3.6

shirriff

Member
I'm trying to change the period for TimerOne, but the interval after start() is half the specified period.

In the following code, I ask for a period of 1000 microseconds, but get pulses every 500 microseconds. The problem goes away if I remove the stop/setPeriod/start from the handler, and exists if I just have the start.

Expected behavior: the start() call in timerInterrupt should start the interval at the beginning.

Do I need to do something different to start the timer from the beginning of the interval?

Code:
#include <TimerOne.h>

#define PIN_DEBUG 6

void timerInterrupt() {
  Timer1.stop();
  Timer1.setPeriod(1000);
  Timer1.start();
  digitalWrite(PIN_DEBUG, HIGH);
  digitalWrite(PIN_DEBUG, LOW);
}

void setup() {
  pinMode(PIN_DEBUG, OUTPUT);
  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerInterrupt);
  Timer1.start();
}

void loop() {
}

(My ultimate goal is to read serial data from an input, using timer interrupts to sample the data in the middle of a pulse, and restarting the timing on a signal edge. But the time intervals are half of what I specify. The code above is simplified to show the problem, so it's not actually useful.)
 
I did some investigation, and here's a slightly simpler version that illustrates my problem.
Summary: The first timer interrupt after start() happens at half the specified interval.
Expected behavior: Timer interrupts every 1000 microseconds as specified. Pulses on pin 6 every 1000 microseconds
Observed behavior: Looking at the oscilloscope, the first gap between pulses is 500 microseconds, but the following are 1000 microseconds.
Underlying cause: the timer uses CPWMS mode, which counts the timer up and down. The first interrupt fires after going up, while subsequent cycles go down and then up which takes twice as long.

This seems like a bug to me. Is there an alternative way to get a timer interrupt after 1000 microseconds?

Code:
#include <TimerOne.h>

#define PIN_DEBUG 6

void timerInterrupt() {
  digitalWrite(PIN_DEBUG, HIGH);
  delayMicroseconds(50); // Small delay so the pulse shows up on the oscilloscope
  digitalWrite(PIN_DEBUG, LOW);
}

void setup() {
  pinMode(PIN_DEBUG, OUTPUT);
  Timer1.initialize(1000); // Timer should fire every 1000 microseconds
  Timer1.attachInterrupt(timerInterrupt);
  digitalWrite(PIN_DEBUG, HIGH); // Pulse to indicate the timer start time.
  delayMicroseconds(50);
  digitalWrite(PIN_DEBUG, LOW);
  Timer1.start();
}

void loop() {}
 
TimerOne is a compatibility wrapper around the native IntervalTimer. So, unless you need to write code compatible to AVR you'd be better of with an IntervalTimer. This code generates a 50µs pulse every 1000µs:

Code:
constexpr int PIN_DEBUG = 6;

void timerInterrupt()
{
    digitalWriteFast(PIN_DEBUG, HIGH);
    delayMicroseconds(50); // Small delay so the pulse shows up on the oscilloscope
    digitalWriteFast(PIN_DEBUG, LOW);
}

IntervalTimer t;

void setup()
{
    pinMode(PIN_DEBUG, OUTPUT);


    digitalWriteFast(PIN_DEBUG, HIGH); // Pulse to indicate the timer start time.
    delayMicroseconds(50);
    digitalWriteFast(PIN_DEBUG, LOW);

    t.begin(timerInterrupt, 1000);
}

void loop() {}

(My ultimate goal is to read serial data from an input, using timer interrupts to sample the data in the middle of a pulse, and restarting the timing on a signal edge. But the time intervals are half of what I specify. The code above is simplified to show the problem, so it's not actually useful.)

You might be interested to use the TeensyTimerTool for this. Here an example which generates a 50µs pulse 1ms after a rising edge on the signal input pin. For testing it generates a 100Hz signal on pin 0. I connected this pin to the signal pin.

Code:
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

constexpr unsigned pin_debug  = 6;
constexpr unsigned pin_signal = 14;

OneShotTimer timer(TCK); // use a software timer (TCK) here, to avoid the usual interrupt difficulties.

void onTimer()
{
    digitalWriteFast(pin_debug, HIGH);
    delayMicroseconds(50); // Small delay so the pulse shows up on the oscilloscope
    digitalWriteFast(pin_debug, LOW);

   // do your stuff here
}

void onEdgeReceived()
{
    timer.trigger(1000); // trigger the one shot timer whenever you detect an edge on pin_signal
}

void setup()
{
    pinMode(pin_debug, OUTPUT);
    pinMode(pin_signal, INPUT_PULLDOWN);
    pinMode(0, OUTPUT);

    timer.begin(onTimer);                               // intialize timer and attach the onTimer callback to it
    attachInterrupt(pin_signal, onEdgeReceived, RISING); // call onEdgeReceived on a rising edge on pin_signal

    // for testing only
    analogWriteFrequency(0, 100); // generate a 10Hz signal on pin 0, connect to pin_signal for testing
    analogWrite(0, 128);
}

void loop()
{
}

LA Output:

Screenshot 2022-04-05 082054.jpg

Hope I understood your goal correctly....
 
TimerOne is a compatibility wrapper around the native IntervalTimer. So, unless you need to write code compatible to AVR you'd be better of with an IntervalTimer. This code generates a 50µs pulse every 1000µs:

....

Good point @luni - that explains why the code looks as it does.

Plugged this in to the StaticTrace (posted on another thread) versus the TimerOne version and it is much more accurate using the same cycle counter tracking.

Timer1 indeed had a bad first interval and all after that were 16 cycles too long.

Swapping in the IntervalTimer most always hits right on 600,000 cycles. The first is 5 or 6 cycles short - but the init setting of 'last cycle count' for diff at the start could account for that - and rare instances showing - or + 6 cycles on the count.
 
I had a look at TimerOne, actually it uses an FTM Timer, not an IntervalTimer under the hood. Anyway, the Intervaltimer seems to be a better fit.
 
Back
Top