From 2c0f00389898f29f2dcb40840dce4b680a5509b7 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Tue, 5 Jul 2022 00:09:03 +0200 Subject: [PATCH] Add initial Sonoff POWR3xxD display support Add initial Sonoff POWR3xxD display support (#15856) --- .../xdrv_87_tm1621_sonoff.ino | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_87_tm1621_sonoff.ino diff --git a/tasmota/tasmota_xdrv_driver/xdrv_87_tm1621_sonoff.ino b/tasmota/tasmota_xdrv_driver/xdrv_87_tm1621_sonoff.ino new file mode 100644 index 000000000..eb9c3228e --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_87_tm1621_sonoff.ino @@ -0,0 +1,265 @@ +/* + xdrv_87_tm1621_sonoff.ino - Sonoff POWR3xxD and THR3xxD display support for Tasmota + + SPDX-FileCopyrightText: 2022 Theo Arends + + SPDX-License-Identifier: GPL-3.0-only +*/ + +#ifdef USE_DISPLAY_TM1621_SONOFF +/*********************************************************************************************\ + * Sonoff POWR3xxD and THR3xxD LCD support + * + * {"NAME":"Sonoff POWR316D","GPIO":[32,0,0,0,0,576,0,0,0,224,9280,0,3104,0,320,0,0,0,0,0,0,9184,9248,9216,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} + * {"NAME":"Sonoff POWR320D","GPIO":[32,0,224,0,225,576,0,0,0,0,9280,0,3104,0,320,0,0,0,0,0,0,9184,9248,9216,0,0,0,0,0,0,0,0,0,0,0,0],"FLAG":0,"BASE":1} +\*********************************************************************************************/ + +#define XDRV_87 87 + +#define TM1621_PULSE_WIDTH 10 // microseconds (Sonoff = 100) + +#define TM1621_SYS_EN 0x01 // 0b00000001 +#define TM1621_LCD_ON 0x03 // 0b00000011 +#define TM1621_TIMER_DIS 0x04 // 0b00000100 +#define TM1621_WDT_DIS 0x05 // 0b00000101 +#define TM1621_TONE_OFF 0x08 // 0b00001000 +#define TM1621_BIAS 0x29 // 0b00101001 = LCD 1/3 bias 4 commons option +#define TM1621_IRQ_DIS 0x80 // 0b100x0xxx + +const uint8_t tm1621_commands[] = { TM1621_SYS_EN, TM1621_LCD_ON, TM1621_BIAS, TM1621_TIMER_DIS, TM1621_WDT_DIS, TM1621_TONE_OFF, TM1621_IRQ_DIS }; + +const char tm1621_kchar[] PROGMEM = { "0|1|2|3|4|5|6|7|8|9|-| " }; +// 0 1 2 3 4 5 6 7 8 9 - off +const uint8_t tm1621_digit_row[2][12] = {{ 0x5F, 0x50, 0x3D, 0x79, 0x72, 0x6B, 0x6F, 0x51, 0x7F, 0x7B, 0x20, 0x00 }, + { 0xF5, 0x05, 0xB6, 0x97, 0x47, 0xD3, 0xF3, 0x85, 0xF7, 0xD7, 0x02, 0x00 }}; + +struct Tm1621 { + uint8_t buffer[8]; + char row[2][12]; + uint8_t pin_da; + uint8_t pin_cs; + uint8_t pin_rd; + uint8_t pin_wr; + uint8_t state; + bool celsius; + bool fahrenheit; + bool humidity; + bool voltage; + bool kwh; + bool present; +} Tm1621; + +void TM1621StopSequence(void) { + digitalWrite(Tm1621.pin_cs, 1); // Stop command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + digitalWrite(Tm1621.pin_da, 1); // Reset data +} + +void TM1621SendCmnd(uint16_t command) { + uint16_t full_command = (0x0400 | command) << 5; // 0b100cccccccc00000 + digitalWrite(Tm1621.pin_cs, 0); // Start command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + for (uint32_t i = 0; i < 12; i++) { + digitalWrite(Tm1621.pin_wr, 0); // Start write sequence + if (full_command & 0x8000) { + digitalWrite(Tm1621.pin_da, 1); // Set data + } else { + digitalWrite(Tm1621.pin_da, 0); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + digitalWrite(Tm1621.pin_wr, 1); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + full_command <<= 1; + } + TM1621StopSequence(); +} + +void TM1621SendAddress(uint16_t address) { + uint16_t full_address = (address | 0x0140) << 7; // 0b101aaaaaa0000000 + digitalWrite(Tm1621.pin_cs, 0); // Start command sequence + delayMicroseconds(TM1621_PULSE_WIDTH / 2); + for (uint32_t i = 0; i < 9; i++) { + digitalWrite(Tm1621.pin_wr, 0); // Start write sequence + if (full_address & 0x8000) { + digitalWrite(Tm1621.pin_da, 1); // Set data + } else { + digitalWrite(Tm1621.pin_da, 0); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + digitalWrite(Tm1621.pin_wr, 1); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + full_address <<= 1; + } +} + +void TM1621SendCommon(uint8_t common) { + for (uint32_t i = 0; i < 8; i++) { + digitalWrite(Tm1621.pin_wr, 0); // Start write sequence + if (common & 1) { + digitalWrite(Tm1621.pin_da, 1); // Set data + } else { + digitalWrite(Tm1621.pin_da, 0); // Set data + } + delayMicroseconds(TM1621_PULSE_WIDTH); + digitalWrite(Tm1621.pin_wr, 1); // Read data + delayMicroseconds(TM1621_PULSE_WIDTH); + common >>= 1; + } +} + +void TM1621SendRows(void) { +// AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Row1 '%s', Row2 '%s'"), Tm1621.row[0], Tm1621.row[1]); + + uint8_t buffer[8] = { 0 }; // TM1621 16-segment 4-bit common buffer + char row[4]; + for (uint32_t j = 0; j < 2; j++) { + // 0.4V => " 04", 0.0A => " ", 1234.5V => "1234" + uint32_t len = strlen(Tm1621.row[j]); + char *dp = nullptr; + int row_idx = len -3; + if (len <= 5) { + dp = strchr(Tm1621.row[j], '.'); + row_idx = len -1; + } + row[3] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' '; + if ((row_idx >= 0) && dp) { row_idx--; } + row[2] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' '; + row[1] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' '; + row[0] = (row_idx >= 0) ? Tm1621.row[j][row_idx--] : ' '; + +// AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump%d %4_H"), j +1, row); + + char command[10]; + char needle[2] = { 0 }; + for (uint32_t i = 0; i < 4; i++) { + needle[0] = row[i]; + int index = GetCommandCode(command, sizeof(command), (const char*)needle, tm1621_kchar); + if (-1 == index) { index = 11; } + uint32_t bidx = (0 == j) ? i : 7 -i; + buffer[bidx] = tm1621_digit_row[j][index]; + } + if (dp) { + if (0 == j) { + buffer[2] |= 0x80; // Row 1 decimal point + } else { + buffer[5] |= 0x08; // Row 2 decimal point + } + } + } + + if (Tm1621.fahrenheit) { buffer[1] |= 0x80; } + if (Tm1621.celsius) { buffer[3] |= 0x80; } + if (Tm1621.kwh) { buffer[4] |= 0x08; } + if (Tm1621.humidity) { buffer[6] |= 0x08; } + if (Tm1621.voltage) { buffer[7] |= 0x08; } + +// AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump3 %8_H"), buffer); + + TM1621SendAddress(0x10); // Sonoff only uses the upper 16 Segments + for (uint32_t i = 0; i < 8; i++) { + TM1621SendCommon(buffer[i]); + } + TM1621StopSequence(); +} + +void TM1621PreInit(void) { + if (!PinUsed(GPIO_TM1621_CS) || !PinUsed(GPIO_TM1621_WR) || !PinUsed(GPIO_TM1621_RD) || !PinUsed(GPIO_TM1621_DAT)) { return; } + + Tm1621.present = true; + Tm1621.pin_da = Pin(GPIO_TM1621_DAT); + Tm1621.pin_cs = Pin(GPIO_TM1621_CS); + Tm1621.pin_rd = Pin(GPIO_TM1621_RD); + Tm1621.pin_wr = Pin(GPIO_TM1621_WR); + pinMode(Tm1621.pin_da, OUTPUT); + digitalWrite(Tm1621.pin_da, 1); + pinMode(Tm1621.pin_cs, OUTPUT); + digitalWrite(Tm1621.pin_cs, 1); + pinMode(Tm1621.pin_rd, OUTPUT); + digitalWrite(Tm1621.pin_rd, 1); + pinMode(Tm1621.pin_wr, OUTPUT); + digitalWrite(Tm1621.pin_wr, 1); + + Tm1621.state = 100; + + AddLog(LOG_LEVEL_INFO, PSTR("DSP: TM1621")); +} + +void TM1621Init(void) { + digitalWrite(Tm1621.pin_cs, 0); + delayMicroseconds(80); + digitalWrite(Tm1621.pin_rd, 0); + delayMicroseconds(15); + digitalWrite(Tm1621.pin_wr, 0); + delayMicroseconds(25); + digitalWrite(Tm1621.pin_da, 0); + delayMicroseconds(TM1621_PULSE_WIDTH); + digitalWrite(Tm1621.pin_da, 1); + + for (uint32_t command = 0; command < sizeof(tm1621_commands); command++) { + TM1621SendCmnd(tm1621_commands[command]); + } + + TM1621SendAddress(0x00); + for (uint32_t segment = 0; segment < 16; segment++) { + TM1621SendCommon(0); + } + TM1621StopSequence(); + + snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("----")); + snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("----")); + TM1621SendRows(); +} + +void TM1621Show(void) { + static uint32_t display = 0; + + if (0 == display) { + Tm1621.kwh = false; + ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &Energy.voltage[0]); + ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &Energy.current[0]); + Tm1621.voltage = true; + display = 1; + } else { + Tm1621.voltage = false; + ext_snprintf_P(Tm1621.row[0], sizeof(Tm1621.row[0]), PSTR("%1_f"), &Energy.total[0]); + ext_snprintf_P(Tm1621.row[1], sizeof(Tm1621.row[1]), PSTR("%1_f"), &Energy.active_power[0]); + Tm1621.kwh = true; + display = 0; + } + + TM1621SendRows(); +} + +void TM1621EverySecond(void) { + Tm1621.state++; + if (5 == Tm1621.state) { + TM1621Show(); + Tm1621.state = 0; + } + if (102 == Tm1621.state) { + TM1621Init(); + Tm1621.state = 0; + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv87(uint8_t function) { + bool result = false; + + if (FUNC_INIT == function) { + TM1621PreInit(); + } + else if (Tm1621.present) { + switch (function) { + case FUNC_EVERY_SECOND: + TM1621EverySecond(); + break; + } + } + return result; +} + +#endif // USE_DISPLAY_TM1621_SONOFF