Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 13 of 13

Thread: Teensy 4.1 digitalWrite square waves first duty cycle too long

  1. #1

    Teensy 4.1 digitalWrite square waves first duty cycle too long

    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: Click image for larger version. 

Name:	Screenshot from 2023-02-01 16-13-15.jpg 
Views:	22 
Size:	10.7 KB 
ID:	30291

    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

  2. #2
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    2,106
    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 by luni; 02-01-2023 at 03:17 PM.

  3. #3
    Senior Member
    Join Date
    Oct 2016
    Posts
    1,222
    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

  4. #4
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    2,106
    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.

  5. #5
    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.

  6. #6
    Senior Member
    Join Date
    Oct 2016
    Posts
    1,222
    Quote Originally Posted by luni View Post
    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.

  7. #7
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    2,106
    Quote Originally Posted by mnissov View Post
    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

  8. #8
    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?

  9. #9
    Senior Member
    Join Date
    Oct 2016
    Posts
    1,222
    Quote Originally Posted by mnissov View Post
    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().

  10. #10
    Senior Member
    Join Date
    Oct 2016
    Posts
    1,222
    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;
      }
    }

  11. #11
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    2,106
    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:

    Click image for larger version. 

Name:	Screenshot 2023-02-01 190100.jpg 
Views:	17 
Size:	28.7 KB 
ID:	30292

    Setting g_interval to zero in setup fixes it as expected.

  12. #12
    Senior Member
    Join Date
    Oct 2016
    Posts
    1,222
    Quote Originally Posted by luni View Post
    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) {
    ...

  13. #13
    @luni, okay yes I see. Thanks for walking me through the troubleshooting.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •