Help controlling LED panel with SM16169SH + INCD1018 over HUB75 on ESP32

Description:
I am trying to control an LED panel using an ESP32.
The panel has a HUB75 connection and uses the following chips:

  • SM16169SH
  • INCD1018

Currently, I cannot even get the chip to turn on or display anything on the panel.

What I’ve tried:

  • Basic ESP32 HUB75 libraries
  • Attempted to send clock/data signals manually
  • Checked wiring between ESP32 and HUB75
  • Tried with 3V signals from the ESP32 and with the signals shifted to 5V with a SN74HCT245N
  • I tried setting OE and CLK and LAT cycles, to get something running on the board, but didnt manage to

Problem:

  • No LEDs light up, no output on the panel at all.

My questions:

  1. Does anyone have experience with the SM16169SH driver IC?
  2. Is there an initialization sequence required before the panel can display anything?
  3. Do I also need to configure the INCD1018 chip, and if so, how?
  4. Are there known working libraries for ESP32 + SM16169SH panels?

Hardware/Setup:

  • ESP32
  • LED Panel with HUB75 interface (80x40)
  • SM16169SH + INCD1018 Chips
    LED panel is powered with 5V and I am also using a SN74HCT245N to shift the 3V of the esp to 5V so that the panel gets the correct high/low signals.
    I checked the Voltage after the SN74HCT245N, this was as expected (shifting between 0 and 5 Volts)

Code
As I did not manage to run anything i dont have any code except the mapping of the pins, which corresponds to my current wireing.


#define R1_PIN 14
#define G1_PIN 26
#define B1_PIN 27
#define R2_PIN 25
#define G2_PIN 33
#define B2_PIN 13
#define A_PIN 23
#define B_PIN 19
#define C_PIN 32
#define D_PIN 12
#define LAT_PIN 18
#define OE_PIN 21
#define CLK_PIN 22
#define PANEL_WIDTH   80
#define PANEL_HEIGHT  40

I also have this script here

#include <WiFi.h>
#include <time.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "ESP32-VirtualMatrixPanel-I2S-DMA.h"
#include <Adafruit_GFX.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <WiFiClientSecure.h>
// —————— Wi‑Fi & Timezone ——————
const char* ssid       = "";
const char* password   = "";
const char* ntpServer  = "";
const char* tzInfo     = "";

// —————— Shelly BLE‑Gateway params ——————
const char* gatewayIp  = "";
const char* sensorMac  = "";

// —————— Panel geometry & pins ——————
#define PANEL_RES_X 80
#define PANEL_RES_Y 40
#define NUM_ROWS     1
#define NUM_COLS     1

#define R1_PIN 14
#define G1_PIN 26
#define B1_PIN 27
#define R2_PIN 25
#define G2_PIN 33
#define B2_PIN 13
#define A_PIN   23
#define B_PIN   19
#define C_PIN   32
#define D_PIN   12
#define E_PIN   -1
#define CLK_PIN 22
#define LAT_PIN 18
#define OE_PIN  21

#define VIRTUAL_MATRIX_CHAIN_TYPE CHAIN_TOP_LEFT_DOWN_ZZ
#define TEXT_SZ 1
#define CHAR_W  (6*TEXT_SZ)
#define CHAR_H  (8*TEXT_SZ)

// —————— VirtualPanel remap for 1/4‑scan ——————
class TenScanPanel : public VirtualMatrixPanel {
public:
  using VirtualMatrixPanel::VirtualMatrixPanel;
protected:
  VirtualCoords getCoords(int16_t x, int16_t y) override;
};
inline VirtualCoords TenScanPanel::getCoords(int16_t x, int16_t y) {
  coords = VirtualMatrixPanel::getCoords(x, y);
  if (coords.x < 0 || coords.y < 0) return coords;
  const uint16_t subH = PANEL_RES_Y / 4;
  coords.x += 16 * (1 + coords.x / 16);
  if (((coords.y / subH) % 2) == 1) {
    coords.x -= 16;
    coords.y -= subH;
  }
  if (coords.y >= PANEL_RES_Y / 2) {
    coords.y -= subH;
  }
  return coords;
}

// —————— Globals ——————
MatrixPanel_I2S_DMA *dma_display = nullptr;
TenScanPanel        *tenPanel    = nullptr;

// shared readings
volatile float currentTemp  = NAN;
volatile float currentHum   = NAN;
volatile float currentPrice = NAN;  // cents per kWh
static StaticJsonDocument<1024> doc;    // for Shelly JSON
static StaticJsonDocument<2048> adoc;   // for aWattar JSON
static std::vector<uint8_t> dataBuf;
// —————— Base64 decoder ——————
static const char B64_CHARS[] =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
bool base64Decode(const String &in, std::vector<uint8_t> &out) {
  int val = 0, valb = -8;
  for (char c : in) {
    if (isspace(c))      continue;
    if (c == '=')        break;
    const char *p = strchr(B64_CHARS, c);
    if (!p)              return false;
    val = (val << 6) + (p - B64_CHARS);
    valb += 6;
    if (valb >= 0) {
      out.push_back(uint8_t((val >> valb) & 0xFF));
      valb -= 8;
    }
  }
  return true;
}


// —————— Display Task (every 1 s on Core 1) ——————
void displayTask(void *pvParameters) {
  char timeBuf[9], buf[8];
  for (;;) {
    // local time
    time_t now = time(nullptr);
    struct tm ts;
    localtime_r(&now, &ts);
    snprintf(timeBuf, sizeof(timeBuf),
             "%02d:%02d:%02d", ts.tm_hour, ts.tm_min, ts.tm_sec);

    tenPanel->startWrite();
      // — time centered —
      {
        uint16_t w = strlen(timeBuf)*CHAR_W;
        uint16_t x = (PANEL_RES_X - w)/2;
        uint16_t y = (PANEL_RES_Y - CHAR_H)/2;
        tenPanel->fillRect(x, y, w, CHAR_H,
                           tenPanel->color565(0,0,0));
        tenPanel->setCursor(x, y);
        tenPanel->print(timeBuf);
      }

      

    tenPanel->endWrite();

    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void setup() {
  Serial.begin(115200);

  // Wi‑Fi & NTP
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print('.');
  }
  Serial.println(" WiFi OK");
  configTzTime(tzInfo, ntpServer);

  // HUB75 DMA init
  HUB75_I2S_CFG::i2s_pins pins = {
    R1_PIN,G1_PIN,B1_PIN,
    R2_PIN,G2_PIN,B2_PIN,
    A_PIN,B_PIN,C_PIN,
    D_PIN,E_PIN,
    LAT_PIN,OE_PIN,CLK_PIN
  };
  HUB75_I2S_CFG cfg(PANEL_RES_X*2, PANEL_RES_Y/2, NUM_ROWS*NUM_COLS);
  cfg.clkphase = false;
  cfg.driver   = HUB75_I2S_CFG::FM6126A;
  dma_display = new MatrixPanel_I2S_DMA(cfg);
  dma_display->setBrightness8(10);
  if (!dma_display->begin()) {
    Serial.println("DMA init failed!");
    while (1) delay(1000);
  }

  // virtual panel
  tenPanel = new TenScanPanel(*dma_display,
                              NUM_ROWS, NUM_COLS,
                              PANEL_RES_X, PANEL_RES_Y,
                              VIRTUAL_MATRIX_CHAIN_TYPE);
  tenPanel->setTextColor(tenPanel->color565(50,50,50));
  tenPanel->setTextSize(TEXT_SZ);
  tenPanel->fillScreen(tenPanel->color565(0,0,0));

  // start tasks
  xTaskCreatePinnedToCore(displayTask, "Display",    4096, NULL, 1, NULL, 0);
}

void loop() {
  // nothing here – all work in tasks
}

Which was working on another panel, that was using the ICN2037BP+ RUC7258D. This is not running on my panel with the SM16169SH.

Hi and welcome to the forum!

Please read the forum guide in the sticky post at the top of most forum categories. This will tell you what you need to post. Without those things, you prevent others from helping you.

You can spend weeks spinning your wheels, or you might get lucky and solve your problem quickly. To avoid unnecessary delays, it’s crucial to provide an annotated schematic of your circuit as you have it wired, showing all connections, including power, ground, and power supplies. I recommend it be in English, you can translate before posting if needed.

Why Detailed Information Matters:

Annotated Schematics: These are essential because they show exactly how your circuit is set up. Without them, it's difficult for anyone to understand what you’ve done, which makes troubleshooting nearly impossible. Fritzing diagrams or unclear pictures are not enough.

Technical Information: Many modules look similar and may even have the same name, but they can function differently. This is why we always ask for links to detailed technical information—not just sales pages like those on Amazon, which often lack the specifics we need.

Post your Software Without that we do not have a clue as to how it is expected to operate. Be sure to use code tags.

Show All Connections: It’s important to include every connection, especially power, ground and power sources in your schematic. Missing these details makes it hard to determine if a setup issue might be causing your problem.

My Process:

When I see a question, I spend a moment assessing it. If it’s missing critical information, I might ask for it. However, if it's repeatedly lacking important details, I may assume the questioner is not serious and move on to another query.

What You Need to Consider:

We don’t know your skill level or what resources you have available. If you’re missing key technical details or seem unprepared, it may indicate that you need to spend more time learning the basics before starting your project.

Providing the right information upfront will help you get the best possible assistance and avoid the frustration of running into dead ends. Let us help you by sharing what you have clearly and completely!

Hello, ı have the same problem did you solve the problem ? Please let me know. I thing driving method is different on this chip.

Yes, the SM16169SH is a PWM-type driver that not supported by ESP32-HUB75-MatrixPanel-DMA . You won't be able to run it with this or (AFAIK) any other Arduino library.

See this discussion for details:

Actually ı have used myown library and it is on stm32 mcu. I just want to learn what are differents with pwm type driver. I am driving with timer interrupt in firmware actually. I will check it deeply. I do not know actually main difference between SM16169SH and ICN2037BP (as an example which is perfectly worked for my firmware )for example. Do you know any major difference already ? please let me know.

The main difference is that with these drivers, you don't need to constantly scan, loading matrix lines at a high frequency. The driver does this automatically. It's equipped with a memory buffer large enough to store 12 to 16 color bits per panel pixel. Therefore, working with these drivers means loading the entire image into the panel and then controlling the scanning by switching channels by ABCDE lines, and sending clock pulses through the OE & CLK channels.
The problem is that this type of driver is relatively new, and there's no established standard yet. Therefore, almost every driver has its own protocol, which, moreover, isn't described in the datasheet to protect commercial confidentiality.
You can find a quick description of some PWM driver protocols in the thread linked in post #5 above. There's also data for SM16169, though I haven't tested it since I don't have such a panel.

I agree that the STM32, with its powerful flexible timers, is best suited for this. I also use STM32 for working with LED panels. I've added support for two types of PWM drivers to my library:

Dear Friend;
Thank you so much for your valuable support. I will check it, since ı have no option to change hardware because there is no free pin, ı can not change the OE pin into any timer to generate pwm. So probably, ı will skip this module probably.
Regards;
Regards;

If you look at my library code, you'll see that the OE pin should be a timer output for almost any driver, not just SM16169. Without it, you'll never get a sufficient refresh rate, and your panel brightness control options will be very limited.
I think that even if you don't have any free pins, you can always move a less important signal to another pin.

And a general note: it's not a good idea to lay out a board before you have the code ready.

This is the timer interrupt triggered panel scanning function.

void panelScaning(void)
{
  
  HAL_GPIO_WritePin(CPU_PORT, CPU_PIN, (GPIO_PinState)(1 - HAL_GPIO_ReadPin(CPU_PORT, CPU_PIN)));
  
  if (leds_on==1){
    OE(0); //topkarag it was 0 before
    if(brightness == 0){
      OE(1);
    }
    leds_on = 0;
    TIM_SetCounter(TIM2, timer_period-brightness);
  }else{
  
    Lock_ShowFrameBuffer =1;
    write_data(int_count_AB);
    leds_on = 1;
    
    if(int_count_AB == panelScaningType){
      Lock_ShowFrameBuffer = 0;
    }
    int_count_AB++;
    
    if(int_count_AB>panelScaningType){
      int_count_AB=0;
      
    }
    //TIM_SetCounter(TIM2, timer_period);
    OE(1);
    LAT(1);
    //  
    LAT(0); 
    //LAT(1);
    //SleepUs(50);
    AB();
    
    TIM_SetCounter(TIM2, brightness);
    //TIM_SetCounter(TIM2, 100);
  }
  
}

'''
This panelscanning function runs every timer interrupt is triggered. And OE() function is this

#define OE(x)   HAL_GPIO_WritePin(OE_PORT, OE_PIN,        x == 0 ? GPIO_PIN_RESET : GPIO_PIN_SET)

so i will check only changing the presecelar parameters for timer configuration is enough or not for driving SM16169. I will try it.
Also, for the layout, we have already produced this led panel product. Almost 1000 device is in field right now. The problem is that our led module supplier is sent different P8 module than previous one and it did not work. We have been looking for the new led module supplier and new supplier sent the module with SM16169 driver ic. That is the problem.

What was the driver on the "different P8 module" that first supplier sent? The SM16169 is not an easy thing, may be the another option was a better.

When you looking for a new modules, It's a good idea to have a list of drivers that are compatible with your library.

The worked driver ic is ICN2037BP, as you know this is a classic method of driving, not require pwm generation on OE pin. This kind of ic is worked for us.

I used a PWM on OE with a classic drivers like 2037 too.

But I asked about another driver, as you talked about in that sentence:

I'm asking because it might be easier to run that driver than SM16169. Tell me the chip type, maybe I can help you.