Teensy 4.1 digitalWrite square waves first duty cycle too long

mnissov

Well-known member
I'm using this teensy for a larger project, but one problem I noticed is that when I use elapsedMicro to create square pulses, the very first one is always longer. Can't really figure out why, this is for simple baseline so a solution like "use IntervalTimers instead" doesn't really make sense.

image documenting problem: Screenshot from 2023-02-01 16-13-15.jpg

minimal working example:
Code:
#include <Arduino.h>

const uint8_t _pin_out_ = 2;
void setup()
{
  Serial.begin(9600);
  while (!Serial) {
  }

  pinMode(_pin_out_, OUTPUT);
}

elapsedMicros g_rising_edge_timer;
elapsedMicros g_falling_edge_timer;
bool g_is_high{false};
uint32_t g_interval{5000};
uint32_t g_width{10};

void loop()
{
  if (g_rising_edge_timer >= g_interval) {
    digitalWriteFast(_pin_out_, HIGH);
    g_rising_edge_timer -= g_interval;
    g_falling_edge_timer = 0;
    g_is_high = true;
  }

  if (g_is_high && (g_falling_edge_timer >= g_width)) {
    digitalWriteFast(_pin_out_, LOW);
    g_is_high = false;
  }
}


Edit: using platformio with teensy4.1
 
IIRC, you are using the TimerTool in your project? If so, you could as well use the TCK timers for this application. They are cheap, are implemented in software only (use the cycle counters) and have the same API as the hardware timers. E.g. you can use them in periodic and one shot mode which seems to be what you want to achieve in your example.

one problem I noticed is that when I use elapsedMicro to create square pulses, the very first one is always longer.
the elapsedMicros variables are initialized long before setup is called. I'd zero both at the end of setup to have defined conditions.
 
Last edited:
Try defining your output pin using a macro instead of a variable, which will allow the inline function digitalWriteFast() to do more optimization, and add these two lines after call to pinMode() in setup(), which ensures that the first LOW time will be correct. I haven't tried this, so it's a guess.

Code:
  digitalWriteFast( _pin_out_, LOW );  // set LOW
  g_rising_edge_timer = 0;                // start first low time now
 
try defining your output pin using a macro instead of a variable, which will allow the inline function digitalWriteFast() to do more optimization,

Code:
const uint8_t _pin_out_ = 2;

Is perfectly fine for the compiler to identify it as compile time constant. If you want to give the compliler even more hints for optimization you can use

Code:
constepxr uint8_t _pin_out_ = 2
which explicitely marks pin_out as compile time constant.
 
Cheaper than GPT? Probably no right? Because GPT uses hardware right?


Anyway, the main question was more about understanding than fixing, I couldn't really find a reason for this to be happening so consistently, because it happens extremely consistently.
 
Code:
const uint8_t _pin_out_ = 2;

Is perfectly fine for the compiler to identify it as compile time constant. If you want to give the compliler even more hints for optimization you can use

Code:
constepxr uint8_t _pin_out_ = 2
which explicitely marks pin_out as compile time constant.

Thanks, luni. Good to know. I still think he should add the initialization of g_rising_edge_timer before leaving setup(), just to be sure that the conditions on first pass through loop() are he same as at the end of a high time.
 
Cheaper than GPT? Probably no right? Because GPT uses hardware right?
Yes, you only have 2 GPTs but 20 TCKs. So I consider GPT as quite valuable :)

Anyway, the main question was more about understanding than fixing, I couldn't really find a reason for this to be happening so consistently, because it happens extremely consistently.

Sorry added my explanation to #4 which you probably didn't see (my bad)

Edit: Crosspost with joepasquariello
 
Yeah you're right I didn't see it

the elapsedMicros variables are initialized long before setup is called. I'd zero both at the end of setup to have defined conditions.
This shouldn't make a difference here though right? The problem is the first iteration is too long, which means the if statement if (g_is_high && (g_falling_edge_timer >= g_width)) is happening relatively late.

The g_falling_edge_timer is zeroed when the output is set HIGH, so changing this value in setup shouldn't make a difference. You're right, setting g_rising_edge_timer to 0 in the setup means more defined rising edge time, but I don't think that's where the problem is. Or am I misunderstanding something?
 
This shouldn't make a difference here though right? The problem is the first iteration is too long, which means the if statement if (g_is_high && (g_falling_edge_timer >= g_width)) is happening relatively late.

The g_falling_edge_timer is zeroed when the output is set HIGH, so changing this value in setup shouldn't make a difference. You're right, setting g_rising_edge_timer to 0 in the setup means more defined rising edge time, but I don't think that's where the problem is. Or am I misunderstanding something?

You're right, the symptom doesn't match the logic of the fix, but I'd still do it, just to be eliminate any uncertainty in conditions on first execution of loop(). After that, I would use ARM_DWT_CYCCNT to measure the time between calls to digitalWriteFast().
 
I can't explain it yet, but initializing g_rising_edge_timer at the end of setup() does fix the problem. I'm using Arduino 1 IDE, TD 1.58b3. Try it.

Code:
#include <Arduino.h>

const uint8_t _pin_out_ = 2;

elapsedMicros g_rising_edge_timer;
elapsedMicros g_falling_edge_timer;
bool g_is_high{false};
uint32_t g_interval{5000};
uint32_t g_width{10};

void setup()
{
  Serial.begin(9600);
  while (!Serial) {
  }

  pinMode(_pin_out_, OUTPUT);
  g_rising_edge_timer = 0;                // start first low time now}
}

void loop()
{
  if (g_rising_edge_timer >= g_interval) {
    digitalWriteFast(_pin_out_, HIGH);
    g_rising_edge_timer -= g_interval;
    g_falling_edge_timer = 0;
    g_is_high = true;
  }

  if (g_is_high && (g_falling_edge_timer >= g_width)) {
    digitalWriteFast(_pin_out_, LOW);
    g_is_high = false;
  }
}
 
I can't explain it yet, but initializing g_rising_edge_timer at the end of setup() does fix the problem. I'm using Arduino 1 IDE, TD 1.58b3. Try it

This is simple. g_rising_edge_timer leaves setup with a high value (here 120000). You do not set it to zero in loop but subtract the interval g_interval (which is good). So, the first condition will be true for a couple of times until the subtraction gives a value below g_interval which prolongs the HIGH time of the output pin. You can check by adding a second output pin which toggles whenever the first condition is true:

Screenshot 2023-02-01 190100.jpg

Setting g_interval to zero in setup fixes it as expected.
 
You do not set it to zero in loop but subtract the interval g_interval (which is good). So, the first condition will be true for a couple of times until the subtraction gives a value below g_interval which prolongs the HIGH time of the output pin.

Ah, yes. That's it. It takes multiple passes of loop to "burn off" the accumulated microseconds since initialization of global variables prior to execution of setup().

Another "fix" is to define the elapsedMicros as static within loop(), so they get initialized on the first entry to loop().

Code:
void loop()
{
  static elapsedMicros g_rising_edge_timer;
  static elapsedMicros g_falling_edge_timer;
  
  if (g_rising_edge_timer >= g_interval) {
...
 
Back
Top