Перехват данных с драйвера дисплея TM1628 с помощью ESP8266
Привет Хабр! Как-то так случилось, что кто-то очень хитрый, в одном НИИ, подключил обычный бытовой увлажнитель BALLU UHB-1000 к фитотрону(ака гроубоксу). Вот и встала задача добавить управление этим устройством. Проблема изначально стояла в том, что при подаче напряжения он автоматически не включался и состояние его было неизвестно. В комплекте присутствует пульт ДУ и можно было бы просто эмулировать сигнал включения, но почитав отзывы на увлажнитель с этим пультом стало ясно что это ни к чему хорошему не приведет. Хорошо что там уже валялся такой же неработающий увлажнитель и можно смело его мучать.
Анализ
Разбираем подставку с датчиками и отделяем переднюю панель, плата управления живет в ней и соединяется шлейфом с платой датчиков в донной части. На обратной стороне платы управления видим антенны-пружинки тачкнопок.

Все микросхемы залиты лаком, но с трудом идентифицируем драйвер дисплея TM1628.

Далее качаем датащит, берем мультиметр и ищем откуда брать питание на все это богатство. Слава фиксикам, все это звонится с входов VDD и GND TM -ки напрямую, значит можно подавать 5 вольт не боясь спалить. Если смотреть с тыльной стороны первый контакт +5в, последний GND.
Вуаля! Включился и даже показывает! НО… при нажатии на вкл, мигает красным иконка пустого бака, а это значит надо прозванивать донную часть и искать на какой пин приходит с него сигнал. Находим что 3 пин в шлейфе это пин датчика идущий через резистор 4,7ком, значит можно просто воткнуть резистор в шлейф и плата будет думать что в баке есть вода и даст нам включить устройство!

Припаиваемся к антеннке нужной кнопки и вешаем на пин нашей ESP-хи через конденсатор и резистор 10ком. Будем имитировать нажатие тачкнопки просто меняя состояние пина. У меня это 0.1мкф из того что было под рукой. На просторах интернета добрые люди писали что можно и меньше, главное тестировать. Все работает, казалось бы, можно было бы остановиться на этом и запустить в работу, но черт дернул ломать его до конца, тем более что нам желательно получить устройство с обратной связью.
подключаемся к пинам -

DIO, SCLK, STB драйвера TM1628 чтобы перехватывать то что рассказывает дисплею контроллер платы. И тут была самая трудоемкая часть работы - наблюдать за состоянием экрана при нажатиях, и собирать показания. Много было времени потрачено на оптимизацию связи и фильтрацию данных. Но это был первый такой опыт, и он тем интереснее. В итоге получилось собрать весь дисплей со всеми статусами, если кому-то понадобится то пользуйтесь на здоровье, теперь легко можно прикрутить что угодно для управления, благо на борту ESP8266 есть вайфай.
#define DIO_PIN D0
#define CLK_PIN D2
#define STB_PIN D1
const uint8_t digitPatterns[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F, // 9
0xBF, //10
0xDB, // 12
0x86, // 11
0x40, // -
};
const int butPin1 = D3;
const int butPin2 = D4;
const int butPin3 = D5;
void butclick(int pin) {
pinMode(pin, INPUT_PULLUP);
pinMode(pin, INPUT);
digitalWrite(pin, LOW);
delay(40);
pinMode(pin, INPUT_PULLUP);
Serial.println("Button pressed on pin: " + String(pin));
}
void setup() {
Serial.begin(115200);
pinMode(STB_PIN, INPUT);
pinMode(CLK_PIN, INPUT);
pinMode(DIO_PIN, INPUT);
pinMode(butPin1, INPUT_PULLUP);
digitalWrite(butPin1, LOW);
pinMode(butPin2, INPUT_PULLUP);
digitalWrite(butPin2, LOW);
pinMode(butPin3, INPUT_PULLUP);
digitalWrite(butPin3, LOW);
Serial.println("TM1628 HUMIDIFIER DISPLAY DECODER");
}
void loop() {
static uint8_t lastSTB = HIGH;
uint8_t currentSTB = digitalRead(STB_PIN);
if (lastSTB == HIGH && currentSTB == LOW) {
decodeHumidifierDisplay();
}
lastSTB = currentSTB;
serialEvent();
}
void decodeHumidifierDisplay() {
uint8_t bytes[20];
int byteCount = 0;
// Capture transmission with timeout
unsigned long startTime = micros();
while (digitalRead(STB_PIN) == LOW && byteCount < 20 && (micros() - startTime) < 10000) {
bytes[byteCount++] = readByte();
}
if (byteCount >= 10) {
// Check if this is the display data packet pattern
if (bytes[0] == 0x00 || bytes[0] == 0x01 || bytes[0] == 0x03) {
if (bytes[1] == 0x40 || bytes[1] == 0x20) {
if (bytes[2] == 0xC0) {
processDisplayData(bytes, byteCount);
return;
}
}
}
}
}
uint8_t readByte() {
uint8_t data = 0;
for (int i = 0; i < 8; i++) {
// Wait for clock LOW
unsigned long waitStart = micros();
while (digitalRead(CLK_PIN) == HIGH) {
if (micros() - waitStart > 1000 || digitalRead(STB_PIN) == HIGH)
return data;
}
// Wait for clock HIGH
waitStart = micros();
while (digitalRead(CLK_PIN) == LOW) {
if (micros() - waitStart > 1000 || digitalRead(STB_PIN) == HIGH)
return data;
}
// Read bit
if (digitalRead(DIO_PIN)) {
data |= (1 << i);
}
}
return data;
}
void processDisplayData(uint8_t* bytes, int count) {
// display data format:
//
// info leds on b3 - ION, Steam, NoWater, TIMER icon
// 0x04 - Ion icon
// 0x02 - warm steam icon
// 0x01 - no water icon
// 0xF0 - TIMER icon
// b11 && b13 - Hum power scale-
// 0x00 0x00 device OFF
// 0x00 0x38 - 25%
// 0x00 0x3F - 50%
// 0xC0 && 0x3F - 75%
// 0xF8 0x3F - 100%
// 0xC7 0x3F - AUTO
static unsigned long lastUnknown = 0;
if (count < 10) return;
uint8_t leds = bytes[3];
uint8_t digit1 = bytes[7]; // first digit
uint8_t digit2 = bytes[9]; // second digit
uint8_t extra = (count > 10) ? bytes[10] : 0; // % led (C led comes with temperature readings)
uint8_t pow1 = bytes[11]; // pow scale up
uint8_t ison = bytes[12]; // 0x01 when device ON
uint8_t pow2 = bytes[13]; // pow scale down
// Decode digits
char d1 = decodeDigit(digit1);
char d2 = decodeDigit(digit2);
// Determine what's being displayed based on patterns
String displayType = "Unknown";
String fullDisplay = "";
if (d1 != '?' && d2 != '?') {
displayType = "Value";
fullDisplay = String(d1) + String(d2);
// Check for special characters
if (extra == 0x01) {
fullDisplay += " %"; // Percentage
displayType = "Humidity";
} else {
fullDisplay += " C"; // Celsius
displayType = "Temperature";
}
}
// Only print if we have proper data
if (d1 != '?' || d2 != '?' || displayType != "Unknown" ) {
Serial.print("DISPLAY: ");
if (leds % 2 != 0 ) Serial.print (" NO WATER " );
if (ison == 0 ) {
Serial.print (" DEVICE IS OFF ");
} else {
if (pow1 == 0xC7 && pow2 == 0x3F) Serial.print ("AUTO " );
else if (pow1 == 0x00 && pow2 == 0x38) printf("Power 25%%\n");
else if (pow1 == 0x00 && pow2 == 0x3F) printf("Power 50%%\n");
else if (pow1 == 0xC0 && pow2 == 0x3F) printf("Power 75%%\n");
else if (pow1 == 0xF8 && pow2 == 0x3F) printf("Power 100%%\n");
if (leds > 0x01 ) {
printf("ICONS: 0x%02X\n", leds);
printf("ION: %s\n", (leds & 0x04) ? "ON" : "OFF");
printf("Steam: %s\n", (leds & 0x02) ? "ON" : "OFF");
printf("TIMER: %s\n", (leds & 0xF0) ? "ON" : "OFF");
}
}
Serial.print(displayType);
Serial.print(" -> ");
Serial.print(fullDisplay);
Serial.print(" [Data: ");
Serial.println("]");
}
}
char decodeDigit(uint8_t byte) {
// main digit patterns
for (int i = 0; i < 10; i++) {
if (byte == digitPatterns[i]) {
return '0' + i;
}
}
// temperature patterns
switch (byte) {
case 0xBF: return '0'; // 0 with something else?
case 0x86: return '1'; // 1 with something else?
case 0xDB: return '2'; // 3 with something else?
case 0xCF: return '3'; // 3 with something else?
case 0xE6: return '4'; // 4 with something else?
case 0xED: return '5'; // 5 with something else?
case 0xFD: return '6'; // 6 with something else?
case 0x87: return '7'; // 7 with something else?
case 0xFF: return '8'; // 7 with something else?
case 0xEF: return '9'; // 9 with something else?
default: return '?';
}
}
void serialEvent() {
while (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input.startsWith("SEND ")) {
float value = input.substring(5).toFloat();
if (value == 1) butclick(butPin1);
if (value == 2) butclick(butPin2);
if (value == 3) butclick(butPin3);
}
}
}
Ниже пример с прикрученной вебмордой

Как-то так, всем успехов :-)
Комментарии (5)

AlexMiller001
23.10.2025 15:39Я в замешательстве. В даташите не оказалось необходимой информации о протоколах?

HardWrMan
23.10.2025 15:39Дело не в протоколах. Он как раз простой. Дело в соответствии сегментов подключенного экранчика и кнопочек к матрице сканирования чипом. Эта схема вызванивается за полчаса. А потом ещё минут 20 на сопоставление соответствия сегментов битам памяти чипа. Чип универсальный а матрицы могут быть разными. Например, у этих ребят уже лет 20 применяется подобный (или даже такой же) чипс TMxxxx (SMxxxx/HTxxxx...):


SM1628 по сути близнец TM1628 но другой фирмы И лет 15 назад я его сам применял в одной своей поделке.

CyberexTech
23.10.2025 15:39Классно! Делал что-то подобное для интеграции увлажнителя в Home Assistant, данные с дисплея брал с помощью оптронов, которые подключались параллельно сегментам.
HardWrMan
Конечно трудно, если не читать буквари, в которых всё рассказано.
trinitrotimotron Автор
Все так, все испортили символы единиц при показаниях температуры) они почему-то совмещены с еще одним сегментом и получалась каша которая меня смущала)