Skip to content
This repository has been archived by the owner on Jan 29, 2023. It is now read-only.

ESP8266Timer and PWM --> wdt reset #8

Closed
holgerlembke opened this issue Oct 22, 2020 · 5 comments
Closed

ESP8266Timer and PWM --> wdt reset #8

holgerlembke opened this issue Oct 22, 2020 · 5 comments
Labels
documentation Improvements or additions to documentation Support Library support

Comments

@holgerlembke
Copy link

Hi,

I try to create a "glow" at certain intervals. Current design is that I loop through my sinus-table with 60 ms and do a analogWrite(), stop at the end of the sinus-loop, then wait some time and then rinse repeat. Looping the 60 ms does not work soooo well, as you already wrote.

To run more smoothly I thought of using the ESP8266Timer to switch the 60 ms-pwm change. For demonstration the restart is done in a simple ticker-loop().

All I get is nothing near a glow but watchdog resets. "analogWrite();" seems to be the offender.

Any ideas? What did I miss?

#include "ESP8266TimerInterrupt.h"

#ifndef LED_BUILTIN
#define LED_BUILTIN       2         // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266, NodeMCU and WeMoS, control on-board LED
#endif

const int sinus[] = {  0, 20, 45, 89, 134, 178, 221, 265, 308, 350, 391, 432,
                       472, 512, 550, 587, 623, 658, 691, 723, 754, 784,
                       812, 838, 863, 886, 907, 927, 945, 961, 976, 988,
                       999, 1007, 1014, 1019, 1022, 1023, 1023, 1023
                    };
const int sinuslen = sizeof(sinus) / sizeof(sinus[0]);
const int sinusschrittzeit = 60;
const long sinusdauer = sinusschrittzeit * sinuslen;

volatile int step = 0;
volatile int direction = 1;

ESP8266Timer ITimer;

void ICACHE_RAM_ATTR TimerHandler(void)
{
  analogWrite(LED_BUILTIN, sinus[step]);
  step += direction;
  if (step < 0) {
    step = 0;
    direction = 1;
    ITimer.disableTimer();
  }
  if (step > sinuslen - 1) {
    step = sinuslen - 1;
    direction = -1;
  }
}

void setup()
{
  Serial.begin(115200);
  while (!Serial);

  pinMode(LED_BUILTIN, OUTPUT);

  // Interval in microsecs
  if (ITimer.attachInterruptInterval(60L * 1000L, TimerHandler))
  {
    ITimer.disableTimer();
    Serial.println("\n\nStarting  ITimer OK");
  }
  else
    Serial.println("\n\nCan't set ITimer correctly. Select another freq. or interval");
}

void loop()
{
  static unsigned long ticker = 0;

  if  (millis() - ticker > 6000) {
    step = 0;
    direction = 1;
    ticker = millis();
    ITimer.enableTimer();
  }
}
@khoih-prog
Copy link
Owner

You have the big WDT problem because you're using "time-consuming code" inside the ISR.

The best way to do inside the ISR is just to set a flag, then using the flag to run the actual "time-consuming code"

Have a look at the modified code hereafter and modify the time accordingly to your use case. I expand the time to just demonstrate the operation

1.Revised code to use "lean and mean" code inside ISR : setting just a flag

#include "ESP8266TimerInterrupt.h"

#ifndef LED_BUILTIN
#define LED_BUILTIN       2         // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266, NodeMCU and WeMoS, control on-board LED
#endif

const int sinus[] = {  0, 20, 45, 89, 134, 178, 221, 265, 308, 350, 391, 432,
                       472, 512, 550, 587, 623, 658, 691, 723, 754, 784,
                       812, 838, 863, 886, 907, 927, 945, 961, 976, 988,
                       999, 1007, 1014, 1019, 1022, 1023, 1023, 1023
                    };
                                      
const int sinuslen = sizeof(sinus) / sizeof(sinus[0]);
const int sinusschrittzeit = 60;
const long sinusdauer = sinusschrittzeit * sinuslen;

int step = 0;
int direction = 1;

ESP8266Timer ITimer;

volatile bool startRunning  = false;

void runSinus(void)
{
  analogWrite(LED_BUILTIN, 1023   /*sinus[step]*/);
  Serial.print("step = ");
  Serial.print(step);
  Serial.print(", sinus = ");
  Serial.println(sinus[step]);
  //Serial.print(".");
  
  step += direction;
  
  if (step < 0) 
  {
    step = 0;
    direction = 1;
    ITimer.disableTimer();
    
    Serial.println("\nDisable ITimer");
  }
  
  if (step > sinuslen - 1) 
  {
    step = sinuslen - 1;
    direction = -1;
  }

  startRunning = false;
}

void ICACHE_RAM_ATTR TimerHandler(void)
{
  startRunning = true;
}

void setup()
{
  Serial.begin(115200);
  while (!Serial);

  pinMode(LED_BUILTIN, OUTPUT);

  // Interval in microsecs
  if (ITimer.attachInterruptInterval(1000L /*60L*/ * 1000L, TimerHandler))
  {
    ITimer.disableTimer();
    Serial.println("\n\nStarting  ITimer OK");
  }
  else
    Serial.println("\n\nCan't set ITimer correctly. Select another freq. or interval");
}

void loop()
{
  static unsigned long ticker = 0;

  if (startRunning)
  {
    runSinus(); 
  }

  if ( (ticker == 0) || (millis() - ticker > 60000 /*6000*/) )
  {
    step = 0;
    direction = 1;
    ticker = millis();
    ITimer.enableTimer();

    Serial.println("\nRestarting  ITimer");
  }
}

2. Terminal output


Starting  ITimer OK

Restarting  ITimer
step = 0, sinus = 0
step = 1, sinus = 20
step = 2, sinus = 45
step = 3, sinus = 89
step = 4, sinus = 134
step = 5, sinus = 178
step = 6, sinus = 221
step = 7, sinus = 265
step = 8, sinus = 308
step = 9, sinus = 350
step = 10, sinus = 391
step = 11, sinus = 432
step = 12, sinus = 472
step = 13, sinus = 512
step = 14, sinus = 550
step = 15, sinus = 587
step = 16, sinus = 623
step = 17, sinus = 658
step = 18, sinus = 691
step = 19, sinus = 723
step = 20, sinus = 754
step = 21, sinus = 784
step = 22, sinus = 812
step = 23, sinus = 838
step = 24, sinus = 863
step = 25, sinus = 886
step = 26, sinus = 907
step = 27, sinus = 927
step = 28, sinus = 945
step = 29, sinus = 961
step = 30, sinus = 976
step = 31, sinus = 988
step = 32, sinus = 999
step = 33, sinus = 1007
step = 34, sinus = 1014
step = 35, sinus = 1019
step = 36, sinus = 1022
step = 37, sinus = 1023
step = 38, sinus = 1023
step = 39, sinus = 1023
step = 39, sinus = 1023
step = 38, sinus = 1023
step = 37, sinus = 1023
step = 36, sinus = 1022
step = 35, sinus = 1019
step = 34, sinus = 1014
step = 33, sinus = 1007
step = 32, sinus = 999
step = 31, sinus = 988
step = 30, sinus = 976
step = 29, sinus = 961
step = 28, sinus = 945
step = 27, sinus = 927
step = 26, sinus = 907
step = 25, sinus = 886
step = 24, sinus = 863
step = 23, sinus = 838
step = 22, sinus = 812
step = 21, sinus = 784
step = 20, sinus = 754
step = 19, sinus = 723
step = 18, sinus = 691
step = 17, sinus = 658
step = 16, sinus = 623
step = 15, sinus = 587
step = 14, sinus = 550
step = 13, sinus = 512
step = 12, sinus = 472
step = 11, sinus = 432
step = 10, sinus = 391
step = 9, sinus = 350
step = 8, sinus = 308
step = 7, sinus = 265
step = 6, sinus = 221
step = 5, sinus = 178
step = 4, sinus = 134
step = 3, sinus = 89
step = 2, sinus = 45
step = 1, sinus = 20
step = 0, sinus = 0

Disable ITimer

Restarting  ITimer
step = 0, sinus = 0
step = 1, sinus = 20
step = 2, sinus = 45
step = 3, sinus = 89
step = 4, sinus = 134
step = 5, sinus = 178
step = 6, sinus = 221
step = 7, sinus = 265
step = 8, sinus = 308
step = 9, sinus = 350
step = 10, sinus = 391
step = 11, sinus = 432
step = 12, sinus = 472
step = 13, sinus = 512
step = 14, sinus = 550
step = 15, sinus = 587
step = 16, sinus = 623
step = 17, sinus = 658
step = 18, sinus = 691
step = 19, sinus = 723
step = 20, sinus = 754
step = 21, sinus = 784
step = 22, sinus = 812
step = 23, sinus = 838
step = 24, sinus = 863
step = 25, sinus = 886
step = 26, sinus = 907
step = 27, sinus = 927
step = 28, sinus = 945
step = 29, sinus = 961
step = 30, sinus = 976
step = 31, sinus = 988
step = 32, sinus = 999
step = 33, sinus = 1007
step = 34, sinus = 1014
step = 35, sinus = 1019
step = 36, sinus = 1022
step = 37, sinus = 1023
step = 38, sinus = 1023
step = 39, sinus = 1023
step = 39, sinus = 1023
step = 38, sinus = 1023
step = 37, sinus = 1023
step = 36, sinus = 1022
step = 35, sinus = 1019
step = 34, sinus = 1014
step = 33, sinus = 1007
step = 32, sinus = 999
step = 31, sinus = 988
step = 30, sinus = 976
step = 29, sinus = 961
step = 28, sinus = 945
step = 27, sinus = 927
step = 26, sinus = 907
step = 25, sinus = 886
step = 24, sinus = 863
step = 23, sinus = 838
step = 22, sinus = 812
step = 21, sinus = 784
step = 20, sinus = 754
step = 19, sinus = 723
step = 18, sinus = 691
step = 17, sinus = 658
step = 16, sinus = 623
step = 15, sinus = 587
step = 14, sinus = 550
step = 13, sinus = 512
step = 12, sinus = 472
step = 11, sinus = 432
step = 10, sinus = 391
step = 9, sinus = 350
step = 8, sinus = 308
step = 7, sinus = 265
step = 6, sinus = 221
step = 5, sinus = 178
step = 4, sinus = 134
step = 3, sinus = 89
step = 2, sinus = 45
step = 1, sinus = 20
step = 0, sinus = 0

Disable ITimer

....

@khoih-prog khoih-prog added invalid This doesn't seem right Support Library support labels Oct 22, 2020
@holgerlembke
Copy link
Author

Thanks for the very fast support. But your solution is not what I wanted to archive.

I wanted to create a isr-task that sets the pwm so I don't need the loop-stuff any more. As outlined in my example, I only want to start in the loop, then the timer isr shall walk through the sinus, each step lasting 60 ms. My code works fine without the analogWrite...

@khoih-prog
Copy link
Owner

That's just an example to show you why you got WDT reset and what's wrong with your code.

You certainly can write a software timer to take care of the work, instead of relying on loop().

It's up to you now.

@holgerlembke
Copy link
Author

The real reason seems to be that both analogWrite()/PWM and your lib use Timer1. So both together simply don't work.

@khoih-prog
Copy link
Owner

That's correct. But Timer0 is already used by WiFi and can not be used for PWM analogWrite() or creating unexpected WiFi issues and/or both.

One possible workaround, if analogWrite() usage is a must, is

  1. Using external i2C DAC
  2. using software PWM such as mentioned in this :

ESP8266 PWM REVISITED (AND REIMPLEMENTED)

The ESP8266 lacks any hardware support for PWM. Any ATtiny, PIC or any ARM Cortex M0 based SoC fares better in this regard, although the smallest SoCs may have only one or two channels.

As an alternative to hardware PWM it is possible to do PWM purely in software, typically assisted by interrupts from a hardware counter. For the ESP8266 a software PWM implementation is available in the SDK provided by Espressif, but it comes with several strings attached:

  1. It has a quite awkward API, the documentation lacks several important points open
  2. As any interrupt based implementation it is susceptible for glitches
  3. The duty cycle is limited to 90% maximum

THE MISSING MANUAL PARTS

The API has four important functions to control the PWM, as follows:

void pwm_set_duty(uint32 duty, uint8 channel)

Set the duty for a logical channel. One duty unit corresponds to 40ns. The maximum should be period / 40ns, but due to the implementation there is a fixed dead time of 100μs which limits the maximum duty to 90% when using a period of 1ms (i.e. a frequency of 1kHz).

void pwm_set_period(uint32 period)
Set the PWM period to period microseconds.

void pwm_start(void)
Needs to be called before any pwm_set_duty, pwm_set_period calls take any effect. Does some preparatory work needed for the interupts handler to do its job of toggling the GPIOs.

void pwm_init(uint32 period, uint32 * duty, uint32 pwm_channel_num, uint32 (*pin_info_list)[3])

duty points to an array of duty cycles, the number of array elements depends on the number of used channels. From the documentation it is not obvious if this is only needed for initial settings, if this is also accessed after the pwm_init call (e.g. ownership of the array is transfered) and if is save to pass NULL here.

pin_info_list points to an array of arrays. It better had been declared as an array of structs, each struct storing the configuration of a GPIO pin. As is, each 3-tuple stores:

  1. the name of the MUX configuration register as documented in the GPIO chapter of the SDK, see the PIN_FUNC_SELECT macro
  2. the name of the MUX setting, see GPIO SDK documentation
  3. the number of the GPIO from 0 to 15
    One 3-tuple is needed for each PWM channel/GPIO pin.

When the duty cycle > 90% is needed, just use analogWrite(pin,100) or even digitalWrite(pin, 1)

@khoih-prog khoih-prog added documentation Improvements or additions to documentation and removed invalid This doesn't seem right labels Oct 23, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation Support Library support
Projects
None yet
Development

No branches or pull requests

2 participants