66x22 WS2812B matrix blackout

I am making a project with a big led matrix that will show time and current weekday, I have a problem with drawing dots between the hours and minutes, each second when dots update matrix just goes black for a bit then turns back on. It's a really small detail and everyone I showed my matrix to said that they didn't notice anything wrong but it's really annoying for me to look at.


#include <FastLED.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <WiFi.h>
#include <time.h>
#include "esp_system.h"

#include "drawings.h"

#define CurrentWeekDayXs 1
#define CurrentWeekDayYs 1
#define CurrentWeekDayXf 66
#define CurrentWeekDayYf 5

#define CurrentNumberXs 10
#define CurrentNumberYs 14
#define ClkAlign 1


#define PirPin 13

#define Width 66
#define Height 21

#define LED_TYPE WS2812B
#define LED_PIN 23
#define NUM_LEDS 1452
#define COLOR_ORDER GRB

CRGB leds[NUM_LEDS];

struct tm timeinfo;


// time things //
const char* ssid = "DNA-WLAN-2G-CB18";
const char* password = "44995489940";

const char* ntpServer = "pool.ntp.org";
const int gmtOffset = 7200;
const int daylight = 3600;

const char* WEEKDAYS[] = {
  "Sunday", "Monday", "Tuesday", "Wednesday",
  "Thursday", "Friday", "Saturday"
};
int previousWeekday = -1;
int currentWeekday = -1;
int lastDrawnMinutes = -1;
int currentMinutes = 0;
int lastDrawnHours = -1;
int currentHours = 0;

unsigned long lastTimeCheck = 0;
unsigned long timeCheckInterval = 1000;


unsigned long lastPresenceDetected = 0;
unsigned long PresenceDetectionInterval = 20000;

bool SleepMode = false;

int TimeDrawingStartPosX = 1;
int TimeDrawingStartPosY = 7;

bool AreDotsOn = false;


void setup() {
  Serial.begin(9600);
  delay(1000);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  configTime(gmtOffset, daylight, ntpServer);

  if (!getLocalTime(&timeinfo)) {
    return;
  }

  pinMode(PirPin, INPUT);

  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.clear();
  FastLED.setBrightness(20);

  currentHours = timeinfo.tm_hour;
  currentMinutes = timeinfo.tm_min;

  currentWeekday = timeinfo.tm_wday;
  previousWeekday = currentWeekday;
  DrawWeekday(currentWeekday, 1, 1);
  DrawFrame();
  delay(1000);
}

void loop() {
  if (!SleepMode) {
    if (millis() - lastTimeCheck >= timeCheckInterval) {
      dots();
      if (getLocalTime(&timeinfo)) {
        lastTimeCheck = millis();
        currentHours = timeinfo.tm_hour;
        currentMinutes = timeinfo.tm_min;

        if (previousWeekday != currentWeekday) {
          currentWeekday = timeinfo.tm_wday;
          previousWeekday = currentWeekday;
          DrawWeekday(currentWeekday, 1, 1);
        }

        if (lastDrawnHours != currentHours) {
          if (currentHours < 10) {
            DrawNumber(0, CalcAlignNumb(47, 1), TimeDrawingStartPosY);
            DrawNumber(currentHours, CalcAlignNumb(47, 1) + 11, TimeDrawingStartPosY);
          } else {
            DrawNumber(currentHours / 10, CalcAlignNumb(47, 1), TimeDrawingStartPosY);
            DrawNumber(currentHours % 10, CalcAlignNumb(47, 1) + 11, TimeDrawingStartPosY);
          }

          lastDrawnHours = currentHours;
        }
        if (lastDrawnMinutes != currentMinutes) {
          if (currentMinutes < 10) {
            DrawNumber(0, CalcAlignNumb(47, 1) + 27, TimeDrawingStartPosY);
            DrawNumber(currentMinutes, CalcAlignNumb(47, 1) + 39, TimeDrawingStartPosY);
          } else {
            DrawNumber(currentMinutes / 10, CalcAlignNumb(47, 1) + 27, TimeDrawingStartPosY);
            DrawNumber(currentMinutes % 10, CalcAlignNumb(47, 1) + 39, TimeDrawingStartPosY);
          }

          lastDrawnMinutes = currentMinutes;
        }
      }
    }

    if (millis() - lastPresenceDetected >= PresenceDetectionInterval) {
      WiFi.disconnect();
      SleepMode = true;
      CleanScreenCord(0, 0, Width, Height + 1);
      for (int i = 23; i > -14; i--) {
        delay(20);
        CleanScreenCord(0, 0, Width, Height + 1);
        DrawDrawing(GoingDrawing, CalcAlign(GoingDrawing, 1), i);
      }
      for (int i = 23; i > -14; i--) {
        delay(20);
        CleanScreenCord(0, 0, Width, Height + 1);
        DrawDrawing(ToDrawing, CalcAlign(ToDrawing, 1), i);
      }
      DrawSleepDrawing();
    }
  }

  if (digitalRead(PirPin) == 1) {
    lastPresenceDetected = millis();
    if (SleepMode) {
      previousWeekday = -1;
      lastDrawnHours = -1;
      lastDrawnMinutes = -1;
      SleepMode = false;
      reconnectWiFi();
      DrawFrame();
    }
  }

  delay(10);
}

void dots() {
  AreDotsOn = !AreDotsOn;
  if (!AreDotsOn) {
    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 3)] = CRGB::Purple;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 3)] = CRGB::Purple;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 4)] = CRGB::Purple;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 4)] = CRGB::Purple;

    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 8)] = CRGB::Purple;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 8)] = CRGB::Purple;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 9)] = CRGB::Purple;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 9)] = CRGB::Purple;
    FastLED.show();
  } else {
    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 3)] = CRGB::Black;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 3)] = CRGB::Black;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 4)] = CRGB::Black;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 4)] = CRGB::Black;

    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 8)] = CRGB::Black;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 8)] = CRGB::Black;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 23, TimeDrawingStartPosY + 9)] = CRGB::Black;
    leds[getLedIndex(CalcAlignNumb(47, 1) + 24, TimeDrawingStartPosY + 9)] = CRGB::Black;
    FastLED.show();
  }
};

int getLedIndex(int x, int y) {
  if (y % 2 == 1) {
    return y * Width + x;
  } else {
    return y * Width + (Width - 1 - x);
  }
}


void DrawDrawing(const Drawing& drw, int8_t DrwX, int8_t DrwY) {
  int arrayIndex = 0;
  for (int8_t x = 0; x < drw.width; x++) {
    for (int8_t y = 0; y < drw.height; y++) {
      int8_t pixel = drw.drawing[arrayIndex];
      if (pixel) {
        int16_t matrixX = DrwX + x;
        int16_t matrixY = DrwY + y;

        if (matrixX < Width && matrixY < Height && matrixX >= 0 && matrixY >= 0) {
          leds[getLedIndex(matrixX, matrixY)] = drw.color;
        }
      }
      arrayIndex++;
    }
  }
  FastLED.show();
}


void DrawWeekday(int theWeekDay, uint8_t WeekDayY, uint8_t Align) {
  CleanScreenCord(CurrentWeekDayXs, CurrentWeekDayYs, CurrentWeekDayXf, CurrentWeekDayYf);
  switch (theWeekDay) {
    case 0:
      DrawDrawing(SundayDrawing, CalcAlign(SundayDrawing, Align), WeekDayY);
      break;
    case 1:
      DrawDrawing(MondayDrawing, CalcAlign(MondayDrawing, Align), WeekDayY);
      break;
    case 2:
      DrawDrawing(TuesdayDrawing, CalcAlign(TuesdayDrawing, Align), WeekDayY);
      break;
    case 3:
      DrawDrawing(WednesdayDrawing, CalcAlign(WednesdayDrawing, Align), WeekDayY);
      break;
    case 4:
      DrawDrawing(ThursdayDrawing, CalcAlign(ThursdayDrawing, Align), WeekDayY);
      break;
    case 5:
      DrawDrawing(FridayDrawing, CalcAlign(FridayDrawing, Align), WeekDayY);
      break;
    case 6:
      DrawDrawing(SaturdayDrawing, CalcAlign(SaturdayDrawing, Align), WeekDayY);
      break;
  }
}

void DrawNumber(uint8_t TheNumber, uint8_t NumberX, uint8_t NumberY) {
  CleanScreenCord(NumberX, NumberY, NumberX + CurrentNumberXs + 1, NumberY + CurrentNumberYs);
  switch (TheNumber) {
    case 0:
      DrawDrawing(ZeroDrawing, NumberX, NumberY);
      break;
    case 1:
      DrawDrawing(OneDrawing, NumberX, NumberY);
      break;
    case 2:
      DrawDrawing(TwoDrawing, NumberX, NumberY);
      break;
    case 3:
      DrawDrawing(ThreeDrawing, NumberX, NumberY);
      break;
    case 4:
      DrawDrawing(FourDrawing, NumberX, NumberY);
      break;
    case 5:
      DrawDrawing(FiveDrawing, NumberX, NumberY);
      break;
    case 6:
      DrawDrawing(SixDrawing, NumberX, NumberY);
      break;
    case 7:
      DrawDrawing(SevenDrawing, NumberX, NumberY);
      break;
    case 8:
      DrawDrawing(EightDrawing, NumberX, NumberY);
      break;
    case 9:
      DrawDrawing(NineDrawing, NumberX, NumberY);
      break;
  }
}

uint8_t CalcAlign(const Drawing& drw, uint8_t Align) {
  int TheX;
  switch (Align) {
    case 0:
      TheX = 0;
      return TheX;
      break;
    case 1:
      TheX = (Width - drw.width) / 2;
      return TheX;
      break;
    case 2:
      TheX = Width - drw.width;
      return TheX;
      break;
  }
}

uint8_t CalcAlignNumb(uint8_t Numb, uint8_t Align) {
  int TheX;
  switch (Align) {
    case 0:
      TheX = 0;
      return TheX;
      break;
    case 1:
      TheX = (Width - Numb) / 2;
      return TheX;
      break;
    case 2:
      TheX = Width - Numb;
      return TheX;
      break;
  }
}

void CleanScreenCord(uint8_t Xs, uint8_t Ys, uint8_t Xf, uint8_t Yf) {
  for (int y = Ys; y < Yf; y++) {
    for (int x = Xs; x < Xf; x++) {
      leds[getLedIndex(x, y)] = CRGB::Black;
    }
  }
}

void DrawFrame() {
  for (int y = 0; y < 22; y++) {
    leds[getLedIndex(65, y)] = CRGB::DarkBlue;
    leds[getLedIndex(65, y)].nscale8(30);
  };
  for (int y = 21; y >= 0; y--) {
    leds[getLedIndex(0, y)] = CRGB::DarkBlue;
    leds[getLedIndex(0, y)].nscale8(30);
  };
  for (int x = 0; x < Width; x++) {
    leds[getLedIndex(x, 0)] = CRGB::DarkBlue;
    leds[getLedIndex(x, 0)].nscale8(30);
  }
  for (int x = 0; x < Width; x++) {
    leds[getLedIndex(x, Height)] = CRGB::DarkBlue;
    leds[getLedIndex(x, Height)].nscale8(30);
  }

  FastLED.show();
}


void GradientXY(uint8_t x, uint8_t y, bool isVertical, uint8_t dir, uint8_t length, CRGB ColorA, CRGB ColorB) {
  if (length == 0) return;

  if (!isVertical) {
    if (dir == 0) {
      for (int i = 0; i < length; i++) {
        int xi = x + i;
        if (xi >= 0 && xi < Width && y >= 0 && y < Height) {
          leds[getLedIndex(xi, y)] = blend(ColorA, ColorB, (uint8_t)((i * 255) / (length - 1)));
        }
      }
    } else if (dir == 1) {
      for (int i = 0; i < length; i++) {
        int xi = x - i;
        if (xi >= 0 && xi < Width && y >= 0 && y < Height) {
          leds[getLedIndex(xi, y)] = blend(ColorA, ColorB, (uint8_t)((i * 255) / (length - 1)));
        }
      }
    }
  } else {
    if (dir == 0) {
      for (int i = 0; i < length; i++) {
        int yi = y + i;
        if (x >= 0 && x < Width && yi >= 0 && yi < Height) {
          leds[getLedIndex(x, yi)] = blend(ColorA, ColorB, (uint8_t)((i * 255) / (length - 1)));
        }
      }
    } else if (dir == 1) {
      for (int i = 0; i < length; i++) {
        int yi = y - i;
        if (x >= 0 && x < Width && yi >= 0 && yi < Height) {
          leds[getLedIndex(x, yi)] = blend(ColorA, ColorB, (uint8_t)((i * 255) / (length - 1)));
        }
      }
    }
  }
}

void DrawSleepDrawing() {
  for (int i = 37; i > 0; i--) {
    uint8_t scale = constrain(i * 20, 50, 255);

    CleanScreenCord(0, 0, Width, Height);

    int drawX = CalcAlign(SleepDrawing, 1);
    int drawY = i - 14;

    int arrayIndex = 0;
    for (int8_t x = 0; x < SleepDrawing.width; x++) {
      for (int8_t y = 0; y < SleepDrawing.height; y++) {
        int8_t pixel = SleepDrawing.drawing[arrayIndex++];
        if (!pixel) continue;

        int16_t matrixX = drawX + x;
        int16_t matrixY = drawY + y;

        if (matrixX >= 0 && matrixX < Width && matrixY >= 0 && matrixY < Height) {
          int ledIndex = getLedIndex(matrixX, matrixY);
          if (ledIndex >= 0 && ledIndex < NUM_LEDS) {
            leds[ledIndex] = blend(SleepDrawing.color, CRGB::Black, 255-scale);
          }
        }
      }
    }

    FastLED.show();
    delay(20);
  }

  FastLED.clear();
  FastLED.show();
}



void reconnectWiFi() {
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}


Separate drawings file just contains arrays with the drawings and noting else that matters to the problem.
Also if someone notices any flaws in any other parts of my code feel free to tell me about them.

I use esp32 220 ohm resistor for data line and 1000uf capacitor for power

Well you should use something to shift the logic level from 3.3v to 5v for starters, (something like a 74hct14 powered with 5v,and running through 2 of the gates) and i guess you have 'added' a capacitor, but you don't say how you are actually powering the leds. These are 66 x 22 = 1452 leds, even at a low brightness that is still going to require quite a lot of power. (50 Amps at full brightness !) So my bet is that is the cause.

I focussed on the dot part of your code (loop() is a bit to long to check on everything) but it looks fine. Basically you overwrite the led buffer part that you change and then call show();

I thought it was 22 ?

How have you wired this ?

So i do suggest that you also put the sections in loop() into separate functions to make sure that you keep an overview of the logic of when which section is executed so sleepmode is not entered when it shouldn't be. (it's a bit hard to keep overview without loading your code into the IDE)

All of these sort of 'magic numbers' referring to the location in the matrix, should be constants that you ddeclare somewhere, or you run the chance of getting it wrong just a little.

Then the signal will be only 3.3V and may not control ws2812 reliably.

You could try a signal level shifter to increase the data signal to the ws2812 to 5V. But don't use one of those commonly available level shifter boards intended for i2c bus. They are not fast enough for ws2812 signals.

I usually use a 74hc14 chip and wire 2 of the inverter gates in series. (Don't forget to connect the inputs if the other 4 gates to ground and connect a 0.1uF bypass cap close to the chip.)

I thought about that chip before starting but then I tried esp32 simply with 3.3v and it looked like that worked correctly untill I noticed flickering, I just simply can't find the chip I need to order, when I just search it in google it mostly gives some random ones that are completely different from what I need or the correct ones that are from some random online shop that nobody has ever used before.

I fixed all the flaws you said, thanks for pointing them out.

what would be your preferred source and your country to deliver to ?

74HCT14

a 74HCT04 would also do the trick,

More or less any 74HCTxx would work, depending on how you wire it up. I tend to use 74HCT02 (NOR gates) and use 1 of the input to block the output until the processor is ready.

Still no word on your PSU !

I use Lidl HG06338 usb output, max output per port is up to 2.4A, I use at max 400 leds at once and with overall brightness always set to 20.

In that case i do suggest you use a logic gate that can be blocked by a different pin. All your leds may accidentally receive an 'all HIGH' signal, particularly during boot.

So a 74HCT02 would be the best. run the signal through 2 gates, connect the 2nd pin of the first gate to a 'Physical' 10K pullup and to an output pin, which you switch to LOW programmatically within your sketch just before the First call to show(); The pins of the 2nd gate (which comes from the first) can either be tied together or one of them can be pulled to GND.

That way you know for sure that your LEDs will not accidentally light up.

Still that thing is not really a proper PSU, and the WS2812's do also draw some current (i think it can be near 2mA) when they are in idle. (check the datasheet, hopefully it's less) if it's 2mA, that would already result in nearly 3A current draw. You need a bigger PSU !

I don't know what you searched for but if you search for 74hc14 you will find they are readily available.

74hct chips are best but they are a bit more difficult to find on eBay/Amazon etc. You will find them from major electronics components suppliers but you will need to place a larger order and pay for shipping. But 74hc chips are easier to find on eBay/Amazon.

Almost any 74cht will work. But if you want to follow @Deva_Rishi 's advice, you will need one with gates that have 2 inputs.

However, most normal 74hc chips will not work. Only certain ones will work for this, such as 74hc14 and maybe a few others. The 74hc chips that work for this purpose must have "Schmitt trigger" inputs, which have different characteristics to normal 74hc chips.

If you can find 74hc132 easier than 74hct chips, you can use that with @Deva_Rishi 's suggestion.

I think we may have had this discussion before, but HC version will work if powered with 5v even LS versions work. We are boosting an actual digital signal here, close to the source, it will be a true square wave, so the Schmitt-trigger is really not required.

I don't know why I didn't think about that first, after I connected it to 80W charger the flickering was gone

I will still get the chip later since you all have told me that it's necessary

At 5V, the minimum input level (to register as HIGH) of most 74HC chips is 0.7 x 5 = 3.5V, which is higher than the 3.3V signal from an ESP chip's output pin. So you would just be moving the problem from the WS2812's input pin to the 74HC chip's input pin.

For 74HC14, because it has Schmitt trigger inputs, the signal only needs to be 2V to register as HIGH. We don't actually need it to be a Schmitt trigger input, but we do need something that will recognise 3.3V as HIGH.

EDIT: reading the data sheet again more carefully, the threshold for HIGH could be as low as 2V but might be as high 3.5V. So 74HC14 isn't guaranteed to work either. I would say that you would be lucky if most 74HC chips would work in this situation. You would be unlucky if 74HC14 didn't work.