1

I'm using the ESP-12e with Arduino IDE. With that I'm trying to build a clock with a WS2812B LED strip matrix that I've build. When I try to display the seconds on my matrix there are strange timing problems. It shows me the numbers how I should, the LEDs work OK but ..it misses seconds spontaneously. sometime it jumps the numbers like 00 01 03 06, sometimes 00 02 03 05.. sometimes it hangs at one number for a short time.

  1. Is the problem in my code?
  2. Or is it due to hardware limitations of the ESP-12e?
  3. How can I do it better?

Here is my sketch (I removed some number definitions [...] for better overview):

#include <ESP8266WiFi.h> //WiFi
#include <Adafruit_NeoPixel.h> //LEDs
#include <TimeLib.h> //Time
#include <WiFiUdp.h> //Synchronize Time

const char* ssid = "XXXXX";
const char* password = "XXXXXXX";

WiFiClient wifiClient;

//NTP Servers
  static const char ntpServerName[] = "us.pool.ntp.org";
  const int timeZone = 1; // Central European Time
  WiFiUDP Udp;
  unsigned int localPort = 8888;  // local port to listen for UDP packets
  time_t getNtpTime();
  void digitalClockDisplay();
  void printDigits(int digits);
  void sendNTPpacket(IPAddress &address);

  // Setup Wifi
void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}


/*-------- NTP code ----------*/
// ====================================

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

// NEO PIXELS
// ====================================

// Setup
#define PIN 0
#define NUMPIXELS 224

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int individualPixels[NUMPIXELS];

// Time variables
int hs;
int m;
int s;

// For Loops
int x;

// RGB color variables
int red;
int green;
int blue;

void colorBlue(){
  red = 0;
  green = 0;
  blue = 100;
} 

/* IN LOOP Light pixels corresponding to current time */
void neoPixler(){
  for (x=0; x<sizeof(individualPixels); x++){
    if (individualPixels[x]==1){
      pixels.setPixelColor(x, pixels.Color(red,green,blue)); //Set Neopixel color
    }
    else{
      pixels.setPixelColor(x,pixels.Color(0,0,0));
    }
  }
  pixels.show(); //Display Neopixel color
} 

// ALL OFF
void turnOff(){
  for(x=0; x<pixels.numPixels(); x++) {
    individualPixels[x]=0;
    pixels.show();
  }
}

//Numbers
void dl_one(){
  individualPixels[160]=1;
  individualPixels[161]=1;
  individualPixels[146]=1;
  individualPixels[147]=1;
  individualPixels[116]=1;
  individualPixels[117]=1;
  individualPixels[102]=1;
  individualPixels[103]=1;
  individualPixels[72]=1;
  individualPixels[73]=1;
  individualPixels[58]=1;
  individualPixels[59]=1;
  individualPixels[28]=1;
  individualPixels[29]=1;
  individualPixels[148]=1;
  individualPixels[149]=1;
  individualPixels[112]=1;
  individualPixels[113]=1;
}
void dr_one(){
  individualPixels[172]=1;
  individualPixels[173]=1;
  individualPixels[134]=1;
  individualPixels[135]=1;
  individualPixels[128]=1;
  individualPixels[129]=1;
  individualPixels[90]=1;
  individualPixels[91]=1;
  individualPixels[84]=1;
  individualPixels[85]=1;
  individualPixels[46]=1;
  individualPixels[47]=1;
  individualPixels[40]=1;
  individualPixels[41]=1;
  individualPixels[136]=1;
  individualPixels[137]=1;
  individualPixels[124]=1;
  individualPixels[125]=1;
}
void dl_two(){
  individualPixels[152]=1;
  individualPixels[153]=1;
  individualPixels[156]=1;
  individualPixels[157]=1;
  individualPixels[158]=1;
  individualPixels[159]=1;
  individualPixels[160]=1;
  individualPixels[161]=1;
  individualPixels[144]=1;
  individualPixels[145]=1;
  individualPixels[118]=1;
  individualPixels[119]=1;
  individualPixels[104]=1;
  individualPixels[105]=1;
  individualPixels[102]=1;
  individualPixels[103]=1;  
  individualPixels[68]=1;  
  individualPixels[69]=1; 
  individualPixels[64]=1; 
  individualPixels[65]=1; 
  individualPixels[22]=1; 
  individualPixels[23]=1; 
  individualPixels[24]=1; 
  individualPixels[25]=1; 
  individualPixels[26]=1; 
  individualPixels[27]=1; 
  individualPixels[28]=1; 
  individualPixels[29]=1; 
  individualPixels[30]=1; 
  individualPixels[31]=1; 
}
void dr_two(){
  individualPixels[34]=1;
  individualPixels[35]=1;
  individualPixels[36]=1;
  individualPixels[37]=1;
  individualPixels[38]=1;
  individualPixels[39]=1;
  individualPixels[40]=1;
  individualPixels[41]=1;
  individualPixels[42]=1;
  individualPixels[43]=1;
  individualPixels[52]=1;
  individualPixels[53]=1;
  individualPixels[80]=1;
  individualPixels[81]=1;
  individualPixels[92]=1;
  individualPixels[93]=1;
  individualPixels[90]=1;
  individualPixels[91]=1;
  individualPixels[132]=1;
  individualPixels[133]=1;
  individualPixels[130]=1;
  individualPixels[131]=1;
  individualPixels[168]=1;
  individualPixels[169]=1;
  individualPixels[170]=1;
  individualPixels[171]=1;
  individualPixels[172]=1;
  individualPixels[173]=1;
  individualPixels[140]=1;
  individualPixels[141]=1;
}

[......]

// Seconds
// ====================================
void print_numbers(byte num){   
  byte num10;
  byte num1;
  num1=num%10;
  num10=(num-num1)/10;
  switch (num10){     //10...                                     
  case 0:
    dl_null();
    break;
  case 1:
    dl_one();
    break;
  case 2:
    dl_two();
    break;
  case 3:
    dl_three();
    break;
  case 4:
    dl_four();
    break;
  case 5:
    dl_five();
    break;
  }
  switch (num1){      //1...
  case 0:
    dr_null();
    break;
  case 1:
    dr_one();
    break;
  case 2:
    dr_two();
    break;
  case 3:
    dr_three();
    break;
  case 4:
    dr_four();
    break;
  case 5:
    dr_five();
    break;
  case 6:
    dr_six();
    break;
  case 7:
    dr_seven();
    break;
  case 8:
    dr_eight();
    break;
  case 9:
    dr_nine();
    break;
  }

}
void seconds(){
/* Get current time */
s=second();    
turnOff();
print_numbers(s);  
neoPixler();      // Lights
}

void setup() {

  // Debug info
  Serial.begin(115200);  
  Serial.println(F("Booting"));

  setup_wifi();

  // Setup UDP Time + Sync Time
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
  setSyncInterval(3000);

/* NEO PIXELS ------------------------------------------------------------------------------------------------------*/
  colorBlue(); //Standard Color
  pixels.begin(); //Begin Neopixel string
  pixels.show(); // Initialize all pixels to 'off'
}

void loop() {
  seconds(); 
}
1

It's likely the problems are in your code somewhere; but not enough information is available to say for sure.


Your first step toward debugging the problem probably should be taking any ESP-12e issues out of the mix. At the beginning of getNtpTime() temporarily add something like return 1477897101UL + millis()/1000; to get a sync-time that advances at the correct rate and is repeatable for testing. If the display suddenly begins updating smoothly, look for problems in your time code, else in the display code.


The technique of duplicating a whole bunch of individualPixels[116]=1; lines (with different pixel numbers) is crude, tedious, verbose, error-prone, inflexible, etc. You might instead create some arrays and use a loop to set pixel data. For example:

const byte L1[] = {160, 161, 146, 147, 116, 117, 102, 103, 72, 73, 58, 59, 28, 29, 148,
  149, 112, 113, 0};
...
const byte R2[] = {34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 52, 53, 80, 81, 92, 93, 90,
  91, 132, 133, 130, 131, 168, 169, 170, 171, 172, 173, 140, 141, 0};

void loadDigit(byte *pixelList) {
  for (byte j=0; pixelList[j]; ++j)
    individualPixels[pixelList[j]] = 1;
}
...
   loadDigit(L1);  // Write a left 1
...
   loadDigit(R2);  // Write a right 2

You could also create an array of addresses of number-data arrays, to get rid of the crude, tedious (etc.) switch/case statements in print_numbers(). For example:

const byte *digitsL[] = {L0, L1, L2, L3, L4, L5, L6, L7, L8, L9};
const byte *digitsR[] = {R0, R1, R2, R3, R4, R5, R6, R7, R8, R9};
...
if (num>9)
  loadDigit(digitsL[num/10]);  // Write tens digit
loadDigit(digitsR[num%10]);    // Write units digit

If your matrix is logically organized, all of the 1's in R0 are at constant offsets from the 1's in L0 (for example, 12 more), and similarly for other digits. If that's the case, you could modify loadDigit() to take a second parameter, offset, which indicates where to place the digit in the matrix, ie, tells loadDigit() how much to add via a statement like individualPixels[offset+pixelList[j]] = 1;. Then you could leave out the R0 ... R9 arrays (and digitsR[] as well) and use code like the following:

...
if (num>9)
  loadDigit(digitsL[num/10], 0);  // Write tens digit at left
loadDigit(digitsL[num%10], 12);   // Write units digit at right   

A drawback to using const byte arrays like L0 ... L9 and R0 ... R9 is that when your program starts they get copied from PROGMEM into RAM for access. It would make sense to declare them in PROGMEM, and use them directly from PROGMEM by appropriate changes. [Edit: See example program, below]


Note, a string of 224 WS2812B units takes about 6.7 ms to update: 224 units x 24 bits x 1.25 μs/bit. Each time you update your matrix of lights, the millis() counter, timer0_millis, will lose 5 or 6 ms because pixels.show() runs with interrupts turned off. To compensate, you could add 5 to timer0_millis after each update. To make the loss always be 5 ms instead of sometimes 5 and sometimes 6, add the following code just before pixels.show(): unsigned long now=millis(); while (now==millis());, which will stall until the end of the current millisecond. To see how to change timer0_millis, see the “Better Alternative” section of my answer to “How to keep accurate millis() while using ADC_sleep mode?”.


I'd remove the pixels.show() call from turnOff() because you will (I think) always be calling pixels.show() shortly after that, after loading number images, to turn on a bunch of other pixels. That is, clear the array, fill the necessary 1's, then show, without a show between clearing and filling.


Following is an example program that illustrates storing a constant array of bytes in PROGMEM, and accessing its entries via pgm_read_byte() calls.

// Sketch that accepts a number k via serial input, and responds with
// the k-th set of numbers from among some sets of numbers stored in
// PROGMEM.  This avoids copying the array data into RAM.  jiw 31 Oct 2016

#include <Streaming.h>  // Ref: http://arduiniana.org/libraries/streaming/

// Set up datatext in PROGMEM
const byte L1[] PROGMEM = { 102, 103, 112, 113, 116, 117, 146, 147, 148, 
                149, 160, 161, 28, 29, 58, 59, 72, 73, 0};

const byte R1[] PROGMEM = { 124, 125, 128, 129, 134, 135, 136, 137, 172, 
                173, 40, 41, 46, 47, 84, 85, 90, 91, 0};

const byte L2[] PROGMEM = { 152, 153, 156, 157, 158, 159, 160, 161, 144, 
                145, 118, 119, 104, 105, 102, 103, 68, 69, 64,
                65, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0};

const byte R2[] PROGMEM = { 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 52, 
                53, 80, 81, 92, 93, 90, 91, 132, 133, 130,
                131, 168, 169, 170, 171, 172, 173, 140, 141, 0};
const  void* Addrs[] = { L1, R1, L2, R2 };
enum { nAddrs = sizeof Addrs / sizeof Addrs[0] };

void prompt() {
  Serial << endl << "Please enter item number from 1 to " << _DEC(nAddrs) << ": ";
}

void setup() {
  Serial.begin(115200);         // init serial port
  prompt();         // Issue initial prompt
}

void loop() {
  char c, d;
  while (Serial.available()) { // Get characters
    c = Serial.read();
    if (c >= '0' && c <= '9') {
      Serial << c << endl;  // Echo the entered digit
      d = c - '0' - 1;
      if (d>=0 && d < nAddrs) {
        byte k=0, t = pgm_read_byte(Addrs[d]);
        Serial << "Set #" << _DEC(d+1) << ": ";
        while (t) {
          Serial << _HEX(t) << ' ';
          t = pgm_read_byte(++k + Addrs[d]);
        }
        Serial << endl;
      }
      prompt();         // Issue prompt after any digit
    }
  }
}
  • Thank you very much for your extensive answer. You just fixed my problems. "pixels.show(): unsigned long now=millis(); while (now==millis());" did the thing. Also thanks for the suggentions regarding the pixel definitions – proto Oct 31 '16 at 12:46
  • with "const byte L1[] = (160, 161, 146, 147, 116, 117, 102, 103, 72, 73, 58, 59, 28, 29, 148, 149, 112, 113, 0);" I get "initializer fails to determine size of "L1". I also don't really get the PROGMEM thing. – proto Oct 31 '16 at 22:56
  • @proto, those (...) initializers should have been {...}. (Now corrected in answer.) See example program now at end of answer for use of pgm_read_byte() to get data from a PROGMEM array. It ran ok on a Nano. Note, feel free to upvote answer – James Waldby - jwpat7 Nov 1 '16 at 5:26
  • upvoted already but because I have under 15 rep it isn't viewed in public ;) – proto Nov 1 '16 at 17:14
0

NTP is a protocol implemented over UDP which is a connectionless protocol. If packets are lost then neither the client nor the server will be aware of their loss. So on top of everything else that might be wrong your data source may also be injecting additional errors. You should take the time from a timer on the board and update it from NTP when you receive data.

  • That's what TimeLib.h does. It syncs at intervals, and the setSyncInterval(3000) statement sets the interval to 3000 seconds, or 50 minutes. The default sync interval is 5 minutes. Between syncs, TimeLib depends on millis(). – James Waldby - jwpat7 Oct 31 '16 at 15:56
0

When I'm trying it like this I get the error:

invalid conversion from 'const byte* {aka const unsigned char*}' to 'byte* {aka unsigned char*}' [-fpermissive]

    #include <ESP8266WiFi.h> //WiFi
    #include <Adafruit_NeoPixel.h> //LEDs

    // 1
    const byte L1[] = {160, 161, 146, 147, 116, 117, 102, 103, 72, 73, 58, 59, 28, 29, 148,  149, 112, 113, 0};
    const byte R1[] = {172, 173, 134, 135, 128, 129, 90, 91, 84, 85, 46, 47, 40, 41, 136, 137, 124, 125, 0};
...    
    const byte *digitsL[] = {L0, L1, L2, L3, L4, L5, L6, L7, L8, L9};
    const byte *digitsR[] = {R0, R1, R2, R3, R4, R5, R6, R7, R8, R9};

    void loadDigit(byte *pixelList) {
      for (byte j=0; pixelList[j]; ++j)
        individualPixels[pixelList[j]] = 1;
    }

    void print_numbers(byte num){  
      if (num>9){
        loadDigit(digitsL[num/10]); // Write tens digit
      }
      else {
      loadDigit(digitsR[num%10]);  // Write units digit
      }
    }

    void seconds(){
    turnOff();
    print_numbers(byte second());  
    //print_numbers(second());  
    neoPixler();      
    }

EDIT: It works when I add "const" to the loadDigit

    //0
    const byte L0[] = {156, 157, 158, 159, 160, 161, 152, 153, 110, 111, 108, 109, 66, 67, 64, 65, 144, 145, 118, 119, 100, 101, 74, 75, 56, 57, 24, 25, 26, 27, 28, 29, 0};
    const byte R0[] = {168, 169, 170, 171, 172, 173, 140, 141, 122, 123, 96, 97, 78, 79, 52, 53, 132, 133, 130, 131, 88, 89, 86, 87, 44, 45, 36, 37, 38, 39, 40, 41, 0};
    //1
    const byte L1[] = {160, 161, 146, 147, 116, 117, 102, 103, 72, 73, 58, 59, 28, 29, 148,  149, 112, 113, 0};
    const byte R1[] = {172, 173, 134, 135, 128, 129, 90, 91, 84, 85, 46, 47, 40, 41, 136, 137, 124, 125, 0};

  void loadDigit(const byte *pixelList) {
      for (byte j=0; pixelList[j]; ++j)
        individualPixels[pixelList[j]] = 1;
    }
    void print_numbers(byte num){   
      if (num>9){
        loadDigit(digitsL[num/10]); 
        loadDigit(digitsR[num%10]);
        }
      else {
        loadDigit(digitsR[num%10]);  
      }
    }
    void seconds(){
      s=second();    
      turnOff();
      print_numbers(s);  
      neoPixler();
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.