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.
- Is the problem in my code?
- Or is it due to hardware limitations of the ESP-12e?
- 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();
}
3 Answers 3
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 definitionsproto– proto2016年10月31日 12:46:28 +00:00Commented Oct 31, 2016 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– proto2016年10月31日 22:56:49 +00:00Commented Oct 31, 2016 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 answerJames Waldby - jwpat7– James Waldby - jwpat72016年11月01日 05:26:25 +00:00Commented Nov 1, 2016 at 5:26 -
upvoted already but because I have under 15 rep it isn't viewed in public ;)proto– proto2016年11月01日 17:14:36 +00:00Commented Nov 1, 2016 at 17:14
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 thesetSyncInterval(3000)
statement sets the interval to 3000 seconds, or 50 minutes. The default sync interval is 5 minutes. Between syncs, TimeLib depends onmillis()
.James Waldby - jwpat7– James Waldby - jwpat72016年10月31日 15:56:31 +00:00Commented Oct 31, 2016 at 15:56
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();
}