Anleitung: JumboUhr mit Noiasca_NeopixelDisplay, WS2815 und ESP32

Zweck: Ein Bibliotheksbeispiel mit einer komplizierten Verlegung der LED-Streifen, um Löten und Kabel zu sparen. Jedwede Anzeige wäre auf diese Art möglich, die Uhrzeit ist nur ein Beispiel.

Anregung: Im Thema Scoreboard mit BLE über Android wurde geraten, möglichst alle Ziffern bei der Streifenverlegung gleich zu gestalten. Das führte zu einem einfacheren Programm, aber mehr Aufwand beim Löten und Kabelverlegen.

In diesem Thema zeige ich, wie man den Aufwand in die Software verlagern kann.

Die Schultern, auf denen ich stehe:

Hardware:

  • gedrucktes Gehäuse
  • Streifen WS2815 mit 60 Pixeln pro Meter, Versorgungspannung 12 V, sonst wie WS2812.
  • Wemos D1 mini ESP32
  • Netzteil 12 V 2,5 A
  • StepDown DC-DC-Wandler auf 3,3 V
  • Kabel und Kleinteile

Streifenverlegung:

Foto

  • Das erste Pixel befindet sich in der roten Markierung. Die ersten Pixel der Zahlen dienen nur dem leichteren Löten und bleiben dunkel.
  • Beim µC werden drei Pins verwendet, je einer für eine Zahl und für den Doppelpunkt.
  • Alle Ziffern haben ein abweichendes Layout.

Programm: Das Programm verteilt sich auf mehrere ino-Dateien im Verzeichnis ESP_JumboUhr, die in der IDE als Tabs angezeigt werden. Das dient der Übersichtlichkeit.

ESP_JumboUhr.ino
const char* ssid = STA_SSID;            // << kann bis zu 32 Zeichen haben
const char* password = STA_PASSWORD;    // << mindestens 8 Zeichen jedoch nicht länger als 64 Zeichen

#include <WebServer.h>
#include <ArduinoOTA.h>
#include <ESP32Time.h>
ESP32Time rtc;  // Realtime-Clock
#include "DebugClass.h"  // Debug mit Telnet https://www.arduinoforum.de/arduino-Thread-Debug-mit-Telnet-ESP8266-und-ESP32-ungetestet?pid=101897#pid101897

//#define DEBUGGING                     // Einkommentieren für die Serielle Ausgabe

#ifdef DEBUGGING
#define DEBUG_B(...) Serial.begin(__VA_ARGS__)
#define DEBUG_P(...) Serial.print(__VA_ARGS__)
#define DEBUG_L(...) Serial.println(__VA_ARGS__)
#define DEBUG_F(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_B(...)
#define DEBUG_P(...)
#define DEBUG_L(...)
#define DEBUG_F(...)
#endif

WebServer server(80);

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);     // OnBoardLed Esp32
  DEBUG_B(115200);
  DEBUG_F("\nSketchname: %s\nBuild: %s\t\tIDE: %d.%d.%d\n\n", __FILE__, __TIMESTAMP__, ARDUINO / 10000, ARDUINO % 10000 / 100, ARDUINO % 100 / 10 ? ARDUINO % 100 : ARDUINO % 10);
  routerVerbindung();
  setupAnzeige();
  setupZeit();
  
  ArduinoOTA.begin();

  server.begin();
  DEBUG_L("HTTP Server gestartet\n\n");
  Debug.begin("esp32-JumboUhr");
}

void loop() {
  routerVerbindung();
  ArduinoOTA.handle();
  Debug.handle();
  anzeige();
}
Anzeige.ino
//
//  Die Segmente entsprechen der Schreibung für 7-Segmentanzeigen!
//                Ziffer A          Ziffer B           Ziffer C          Ziffer D
//
//    A    |      14 15 16    |     06 07 08    ||     08 07 06    |     16 15 14
//         |     13      17   |    05      09   49    09      05   |    17      13
//  F   B  |     12      18   |    04      10   50    10      04   |    18      12
//         |  10 11      19   |    03      11 12|| 12 11      03   |    19      11 10
//    G    |      22 21 20  24|23   02 01 00    ||     00 01 02  23|24   20 21 22
//         |  09 08      00 25|    22      14 13|| 13 14      22   |25  00      08 09
//  E   C  |     07      01   |    21      15   51    15      21   |    01      07
//         |     06      02   |    20      16   52    16      20   |    02      06
//    D    |      05 04 03    |     19 18 17    53     17 18 19    |     03 04 05
//
typedef uint32_t segsize_t;                               // fit variable size to your needed pixels. uint16_t --> max 16 Pixel per digit
const segsize_t zifferA[8] {
  bit(14) | bit(15) | bit(16),  // SEG_A
  bit(17) | bit(18) | bit(19),  // SEG_B
  bit( 0) | bit( 1) | bit( 2),  // SEG_C
  bit( 5) | bit( 4) | bit( 3),  // SEG_D
  bit( 6) | bit( 7) | bit( 8),  // SEG_E
  bit(11) | bit(12) | bit(13),  // SEG_F
  bit(22) | bit(21) | bit(20),  // SEG_G
  0                             // SEG_DP
};
const segsize_t zifferB[8] {
  bit( 6) | bit( 7) | bit( 8),  // SEG_A
  bit( 9) | bit(10) | bit(11),  // SEG_B
  bit(14) | bit(15) | bit(16),  // SEG_C
  bit(19) | bit(18) | bit(17),  // SEG_D
  bit(20) | bit(21) | bit(22),  // SEG_E
  bit( 3) | bit( 4) | bit( 5),  // SEG_F
  bit( 2) | bit( 1) | bit( 0),  // SEG_G
  0                             // SEG_DP
};
const segsize_t zifferC[8] {
  bit( 8) | bit( 7) | bit( 6),  // SEG_A
  bit( 5) | bit( 4) | bit( 3),  // SEG_B
  bit(22) | bit(21) | bit(20),  // SEG_C
  bit(19) | bit(18) | bit(17),  // SEG_D
  bit(16) | bit(15) | bit(14),  // SEG_E
  bit(11) | bit(10) | bit( 9),  // SEG_F
  bit( 0) | bit( 1) | bit( 2),  // SEG_G
  0                             // SEG_DP
};
const segsize_t zifferD[8] {
  bit(16) | bit(15) | bit(14),  // SEG_A
  bit(13) | bit(12) | bit(11),  // SEG_B
  bit( 8) | bit( 7) | bit( 6),  // SEG_C
  bit( 5) | bit( 4) | bit( 3),  // SEG_D
  bit( 2) | bit( 1) | bit( 0),  // SEG_E
  bit(19) | bit(18) | bit(17),  // SEG_F
  bit(20) | bit(21) | bit(22),  // SEG_G
  0                             // SEG_DP
};
const byte neoPixelPinAB = 33;         // linke Zahl mit Display A und B
const byte neoPixelPinCD = 14;         // linke Zahl mit Display C und D
const byte neoPixelPinDP = 26;         // Doppelpunkt

const byte numDigits = 1;              // How many digits (numbers) are available on each display
const byte pixelPerDigit = 23;         // all pixels, including decimal point pixels if available at each digit
const byte addPixels = 5;              // unregular additional pixels to be added to the strip
const uint16_t ledCount = pixelPerDigit * 2 + addPixels;
const uint16_t ledCountDP = 5;

const byte startPixelA = 27;           // start pixel of display A
const byte startPixelB =  1;           // start pixel of display B
const byte startPixelC =  1;           // start pixel of display C
const byte startPixelD = 27;           // start pixel of display D

#include <Noiasca_NeopixelDisplay.h>  // download library from: http://werner.rotschopf.net/202005_arduino_neopixel_display.htm
Adafruit_NeoPixel stripAB(ledCount, neoPixelPinAB, NEO_GRB + NEO_KHZ800);     // linke Zahl als Adafruit Neopixel Object
Adafruit_NeoPixel stripCD(ledCount, neoPixelPinCD, NEO_GRB + NEO_KHZ800);     // rechte Zahl als Adafruit Neopixel Object
Adafruit_NeoPixel stripDP(ledCountDP, neoPixelPinDP, NEO_GRB + NEO_KHZ800);   // Doppelpunkt als Adafruit Neopixel Object
Noiasca_NeopixelDisplay displayA(stripAB, zifferA, numDigits, pixelPerDigit, startPixelA);  // erste Ziffer als Display Object
Noiasca_NeopixelDisplay displayB(stripAB, zifferB, numDigits, pixelPerDigit, startPixelB);  // zweite Ziffer als Display Object
Noiasca_NeopixelDisplay displayC(stripCD, zifferC, numDigits, pixelPerDigit, startPixelC);  // dritte Ziffer als Display Object
Noiasca_NeopixelDisplay displayD(stripCD, zifferD, numDigits, pixelPerDigit, startPixelD);  // vierte Ziffer als Display Object

void setupAnzeige()
{
  stripAB.begin();                       // INITIALIZE NeoPixel strip object (REQUIRED)
  stripCD.begin();                       // INITIALIZE NeoPixel strip object (REQUIRED)
  stripDP.begin();                       // INITIALIZE NeoPixel strip object (REQUIRED)
  stripAB.show();                        // Turn OFF all pixels ASAP
  stripCD.show();                        // Turn OFF all pixels ASAP
  stripDP.show();                        // Turn OFF all pixels ASAP
  stripAB.setBrightness(255);             // Set BRIGHTNESS to about 1/5 (max = 255)
  stripCD.setBrightness(255);             // Set BRIGHTNESS to about 1/5 (max = 255)
  stripDP.setBrightness(255);             // Set BRIGHTNESS to about 1/5 (max = 255)
  displayA.setColorFont(0xFFFF00);     // each display gets its own color, e.g. corresponding to the button color
  displayB.setColorFont(0xFF0000);
  displayC.setColorFont(0x00FF00);     // each display gets its own color, e.g. corresponding to the button color
  displayD.setColorFont(0xFF00FF);
  displayA.print(" ");
  displayB.print("0");
  displayC.print("0");
  displayD.print("0");
}

void anzeige()
{
  uint32_t aktMillis = millis();
  static uint32_t lastMillis;
  const uint32_t INTERVALL = 1000;
  if (aktMillis - lastMillis >= INTERVALL) {
    lastMillis = aktMillis;
    debPrintln("Aktuelle Zeit: " + rtc.getTime("%A, %d. %B %Y %H:%M:%S  Woche %W"));   // (String) returns time with specified format
    byte stunde = rtc.getHour(1);
    byte minute = rtc.getMinute();
    byte sekunde = rtc.getSecond();  // für das sekündliche Blinken des Doppelpunktes
    //displayA.setCursor(0); if (minute < 10) displayA.print(" "); else displayA.print(minute / 10); displayB.setCursor(0); displayB.print(minute % 10);
    //displayC.setCursor(0); if (sekunde < 10) displayC.print("0"); else displayC.print(sekunde / 10); displayD.setCursor(0); displayD.print(sekunde % 10);

    displayA.setCursor(0);
    if (stunde < 10) displayA.print(" "); else displayA.print(stunde / 10);
    displayB.setCursor(0);
    displayB.print(stunde % 10);

    displayC.setCursor(0);
    if (minute < 10) displayC.print("0"); else displayC.print(minute / 10);
    displayD.setCursor(0);
    displayD.print(minute % 10);

    uint32_t farbe = 0x0000FF;
    if (sekunde & 0x01) farbe = 0;
    stripDP.setPixelColor(0, farbe);
    stripDP.setPixelColor(1, farbe);
    stripDP.setPixelColor(3, farbe);
    stripDP.setPixelColor(4, farbe);
    stripDP.show();
  }
}
Connect.ino
void routerVerbindung()
{
  unsigned long connDauer = 20000;
  unsigned long connTimer = millis();

  if (WiFi.status() != WL_CONNECTED)
  {
    DEBUG_F("Verbindung zum Router ");
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    WiFi.begin(STA_SSID, STA_PASSWORD);
    while (WiFi.status() != WL_CONNECTED && (millis() - connTimer < connDauer)) {
      digitalWrite(LED_BUILTIN, 1);
      delay(250);
      digitalWrite(LED_BUILTIN, 0);
      delay(250);
      DEBUG_F(".");
    }
    if (WiFi.status() == WL_CONNECTED)
    {
      WiFi.setAutoReconnect(true);
      WiFi.persistent(false);  // true = default, bei reboot werden ssid+pass in den Flash geschrieben, killt den freien Speicher!
      DEBUG_L("\nVerbunden mit: " + WiFi.SSID());
      DEBUG_L("Esp32 IP: " + WiFi.localIP().toString() + "\n");
    } else {
      DEBUG_L(" nicht hergestellt");
    }
  }
}
Zeit.ino
const char * NTP_SERVER = "fritz.box";
//const char * NTP_SERVER = "de.pool.ntp.org";
const char * TZ_INFO = "WEST-1DWEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"; // Western European Time
const uint32_t intervallUpdateZeit = 22 * 60000UL;
uint32_t letzteUpdateZeit = -intervallUpdateZeit;  // Sofortstart im Setup
boolean zeitGesetzt = false;  // Zeit wurde seit Reset gesetzt
boolean online = false;   // Zeit konnte beim letzten Versuch mit NTP-Server synchronisiert werden

void setupZeit() {
  setenv("TZ", TZ_INFO, 1);
  tzset();

  DEBUG_P("Hole NTP Zeit von ");
  DEBUG_L(NTP_SERVER);
  configTzTime(TZ_INFO, NTP_SERVER); // ESP32 Systemzeit mit NTP Synchronisieren
  if ( holeZeit(10000) )             // Synchronisationszeit mit NTP-Server in ms
  {
    DEBUG_L("Aktuelle Zeit: " + rtc.getTime("%A, %d. %B %Y %H:%M:%S  Woche %W"));   // (String) returns time with specified format
  } else {
    DEBUG_L("Synchronisation mit NTP-Server fehlgeschlagen!");
  }
}

bool holeZeit(uint32_t syncZeit)
{
  struct tm timeinfo;
  if (millis() - letzteUpdateZeit >= intervallUpdateZeit)  // Zeit mit NTP-Server abgleichen
  {
    letzteUpdateZeit = millis();
    if (WiFi.status() == WL_CONNECTED) 
    {
      if (getLocalTime(&timeinfo, syncZeit))    // Versuche mit NTP-Server zu synchronisieren
      {
        rtc.setTimeStruct(timeinfo);
        zeitGesetzt = true;
        online = true;
      }
    } else {
      online = false;
    }
  }
  return zeitGesetzt;
}

Die Kommentare sind Englisch aus dem Bibliotheksbeispiel und Deutsch von mir. Trotz Überprüfung können sich da noch Ungereimtheiten verstecken.

Foto der Rückseite mit Verkabelung

Ein Lob an @noiasca für die durchdachte Bibliothek, es hat Spaß gemacht, damit zu programmieren!

3 Likes

das Aufteilen der Zehner/Einer ist nur wegen den Farben oder weshalb hast du dich dafür entschieden?

Was macht die ESP32Time.h besser als die time.h?

Die Felder zifferA bis zifferD sind alle unterschiedlich, da fiel mir keine andere Lösung ein. Geht das etwa auch einfacher?

Über die Antwort zu ESP32Time muß ich erstmal nachgrübeln, die Entscheidung liegt schon länger zurück.

hast recht, wenn man nicht schneiden/verkabeln oder überkreuzen will komm ich auch auf nichts besseres. du hättest nur noch links und rechts gleich machen können (also A wie C und B und D) Aber außer zwei const segsizet_t Arrays hättest sonst auch nichts gespart...

Ja, hatte ich erst vor, aber die Kabeldurchführung der linken Zahl ist bei der rechten Ziffer. Erste Idee war, den Streifen von hinten mit Strom zu versorgen und nur die Datenleitung zur linken Ziffer zu führen. Aber dann kam mir die Idee, den Streifen in der rechten Ziffer der linken Zahl beginnen zu lassen. Und siehe da, es ist mit Deiner Bibliothek möglich :slightly_smiling_face:


Inzwischen habe ich auch einen sich ändernden Farbverlauf probiert:
grafik

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.