How to implement ESP8266 5 kHz PWM?

ARF picture ARF · Feb 8, 2017 · Viewed 9.2k times · Source

I need to realise a PWM output with 5 kHz +/- 5%. (Presumably due to a filter circuit into which this feeds over which I have no control.)

Is this realisable with a ESP8266 (ideally with NodeMCU)?

I realise that the software PWM of the ESP8266 has a maximum frequency of 1 kHz while the sigma-delta can be used to implement a PWM with a fixed frequency of about 300 kHz.

So is there a reliable way to achieve 5 kHz? I know some people experimented with the I2S peripheral for waveform output but I am unsure whether it can be used for 5kHz output.

Has anybody looked at into a similar problem before?

Answer

ekim picture ekim · Feb 14, 2017

The essential code is:

#define  qap      2       //  Quick As Possible ... Duty cycle only 0, 50, 100%
#define  HFreq    5150
#define  pPulse   D2      // a NodeMCU/ESP8266 GPIO PWM pin
analogWriteRange(qap);    analogWriteFreq( HFreq );  analogWrite(pPulse, 1);    // start PWM

TL;DR Just did some crude benchamrks

//     had HFreq=126400   with 75 KHz pulse at 80 MHz ESP clock, every 1 sec but proof of evidence lost 

nominally with 80 MHz clock got

// 72.24 KHz  361178  2015061929
// 72.23 KHz  361163  2415062390
// 72.23 KHz  361133  2815062824

and

// 141.52 KHz  353809  2009395076
// 141.54 KHz  353846  2409395627
// 141.52 KHz  353806  2809395946

with 160 MHz clock

CAVEATS!!!
1. The duty cycle range is 2!
2. As well the system had no other conditioning, so other timing artifacts were still present from Serial IO etc. In particular instability due to the WDT and WiFi penultimately appeared. (Anyhow, presumably this is an issue only when the ESP8266 is under stress and duress.)

// 141.50 KHz  353754  619466806
// 141.52 KHz  353810  1019467038
//     ...
// ad infinitum cum tempore finitum, infinitus est ad nauseum?
//     ...
// 141.54 KHz  353857  735996888
//
// ets Jan  8 2013,rst cause:4, boot mode:(1,7)
//
//wdt reset

When using the following code to generate a 5 KHz square wave signal, the considerations above are not an issue and do not occur.

// ------------  test results   for 80 MHz clock  --------------
//
//
//       PWM pulse test      
//
// F_CPU:          80000000L
// ESP8266_CLOCK:  80000000UL
// PWM "freq.":    5150
//
//
//   connect D1 to D2
//
//
//             raw     MPU     
// frequency  count   cycle  
// 0.00 KHz  1  407976267
// 4.74 KHz  9482  567976702
// 5.00 KHz  10007  727977137
// 5.00 KHz  10006  887977572
// 5.00 KHz  10006  1047978007
// 5.00 KHz  10007  1207978442
// 5.00 KHz  10006  1367978877
// 5.00 KHz  10006  1527979312
// 5.00 KHz  10007  1687979747
// 5.00 KHz  10006  1847980182
// 5.00 KHz  10006  2007980617
// 5.00 KHz  10007  2167981052
// 5.00 KHz  10006  2327981487
// 5.00 KHz  10006  2487981922
// 5.00 KHz  10007  2647982357  ...
//
//    crude testing for 5KHz signal
//          extracted from:
//                highest frequency / shortest period pin pulse generate / detect test
//
//     uses raw ESP8266 / NodeMCU V1.0 hardware primitive interface of Arduino IDE (no included libraries)
//
//     timing dependencies: WDT, WiFi, I2S, I2C, one wire, UART, SPI, ...
//
//  Arduino GPIO   16    5    4    0    2   14   12   13   15    3    1      0  1  2  3  4  5 ... 12 13 14 15 16 
//  NodeMCU D pin   0    1    2    3    4    5    6    7    8    9   10      3 10  4  9  2  1 ...  6  7  5  8  0 
//                  |    |    |    |    |    |    |    |    |    |    |                      
//    a           WAKE   |    |   F    Tx1   |    |   Rx2  Tx2  Rx0  Tx0             
//     k      (NO PWM or |    |    L   blue  |    |    |    |                               
//      a'    interrupt) |  S       A   *  H |  H |  H |    |                     * led's
//       s         red  S    D       S      S    M    S    H       
//                  *    C    A       H      C    I    I    C                    
//                        L    T              L    S    M    S   
//                         K    A              K    O    O              
//                                       └  -  -  -  - └----UART's----┘         
//                      └--I2C--┘            └-----SPI------┘
//
//  rules of engagement are obscure and vague for effects of argument values for the paramters of these functions:
//      analogWriteRange(qap);     analogWriteFreq( HFreq );              analogWrite(pPulse, 1);  
//
//    http://stackoverflow.com/questions/42112357/how-to-implement-esp8266-5-khz-pwm  
//
//   system #defines:  F_CPU  ESP8266_CLOCK
#define  pInt     D1          // HWI pin: NOT D0 ie. GPIO16 is not hardwared interrupt or PWM pin
#define  pPulse   D2          // PWM pulsed frequency source   ...  ditto D0  (note: D4 = blue LED)
#define  countFor 160000000UL 

#define  gmv(p)   #p          //  get macro value
#define  em(p)    gmv(p)      //  evaluate macro
#define  qap      2           //  minimal number of duty cycle levels (0, 50, 100% ) Quick As Possible ...
#define  HFreq    5150 //((long int) F_CPU==80000000L ? 125000 : 250000)        // ... to minimize time of a cycle period
                                     //   max values      ^   and   ^   found empirically
//     had HFreq=126400   with 75 KHz pulse at 80 MHz ESP clock, every 1 sec but proof of evidence lost
#define  infoTxt (String)                                                  \
                  "\n\n\t    PWM pulse test        "                        \   
                  "\n F_CPU:          " em(F_CPU)                          \
                  "\n ESP8266_CLOCK:  " em(ESP8266_CLOCK)                  \
                  "\n PWM \"freq.\":    " + HFreq + "\n"                   \
                  "\n\n   connect " em(pInt) " to " em(pPulse) "\n"        \  
                  "\n\n             raw     MPU   "                        \
                  "  \n frequency  count   cycle  "

long int oc=1, cntr=1;  
unsigned long int tc=0;
void hwISR(){ cntr++; }                                              // can count pulses if pInt <---> to pPulse
void anISR(){  tc=ESP.getCycleCount(); timer0_write( countFor + tc ); oc=cntr; cntr=1; }

void setup() {                                     // need to still confirm duty cycle=50%  (scope it or ...)
   noInterrupts(); 
      Serial.begin(115200); Serial.println(infoTxt);  delay(10);  // Serial.flush(); Serial.end(); // Serial timing?
      analogWriteRange(qap);     analogWriteFreq( HFreq );              analogWrite(pPulse, 1);    // start PWM
      pinMode( pInt,  INPUT );   attachInterrupt(pInt, hwISR, RISING);                             // count pulses
      timer0_isr_init();         timer0_attachInterrupt( anISR );       anISR();                   // 
   interrupts();
}

void loop() {  delay(10);     if (oc==0) return;  
               Serial.println((String)" "+(oc/1000.0*F_CPU/countFor)+" KHz  "+oc+"  "+tc);   oc=0;    }  
//