#include "tm1621.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { namespace tm1621 { static const char *const TAG = "tm1621"; const uint8_t TM1621_PULSE_WIDTH = 10; // microseconds (Sonoff = 100) const uint8_t TM1621_SYS_EN = 0x01; // 0b00000001 const uint8_t TM1621_LCD_ON = 0x03; // 0b00000011 const uint8_t TM1621_TIMER_DIS = 0x04; // 0b00000100 const uint8_t TM1621_WDT_DIS = 0x05; // 0b00000101 const uint8_t TM1621_TONE_OFF = 0x08; // 0b00001000 const uint8_t TM1621_BIAS = 0x29; // 0b00101001 = LCD 1/3 bias 4 commons option const uint8_t TM1621_IRQ_DIS = 0x80; // 0b100x0xxx enum Tm1621Device { TM1621_USER, TM1621_POWR316D, TM1621_THR316D }; 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}}; void TM1621Display::setup() { ESP_LOGCONFIG(TAG, "Running setup"); this->cs_pin_->setup(); // OUTPUT this->cs_pin_->digital_write(true); this->data_pin_->setup(); // OUTPUT this->data_pin_->digital_write(true); this->read_pin_->setup(); // OUTPUT this->read_pin_->digital_write(true); this->write_pin_->setup(); // OUTPUT this->write_pin_->digital_write(true); this->state_ = 100; this->cs_pin_->digital_write(false); delayMicroseconds(80); this->read_pin_->digital_write(false); delayMicroseconds(15); this->write_pin_->digital_write(false); delayMicroseconds(25); this->data_pin_->digital_write(false); delayMicroseconds(TM1621_PULSE_WIDTH); this->data_pin_->digital_write(true); for (uint8_t tm1621_command : TM1621_COMMANDS) { this->send_command_(tm1621_command); } this->send_address_(0x00); for (uint32_t segment = 0; segment < 16; segment++) { this->send_common_(0); } this->stop_(); snprintf(this->row_[0], sizeof(this->row_[0]), "----"); snprintf(this->row_[1], sizeof(this->row_[1]), "----"); this->display(); } void TM1621Display::dump_config() { ESP_LOGCONFIG(TAG, "TM1621:"); LOG_PIN(" CS Pin: ", this->cs_pin_); LOG_PIN(" DATA Pin: ", this->data_pin_); LOG_PIN(" READ Pin: ", this->read_pin_); LOG_PIN(" WRITE Pin: ", this->write_pin_); LOG_UPDATE_INTERVAL(this); } void TM1621Display::update() { // memset(this->row, 0, sizeof(this->row)); if (this->writer_.has_value()) (*this->writer_)(*this); this->display(); } float TM1621Display::get_setup_priority() const { return setup_priority::PROCESSOR; } void TM1621Display::bit_delay_() { delayMicroseconds(100); } void TM1621Display::stop_() { this->cs_pin_->digital_write(true); // Stop command sequence delayMicroseconds(TM1621_PULSE_WIDTH / 2); this->data_pin_->digital_write(true); // Reset data } void TM1621Display::display() { // Tm1621.row[x] = "text", "----", " " or a number with one decimal like "0.4", "237.5", "123456.7" // "123456.7" will be shown as "9999" being a four digit overflow // 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(this->row_[j]); char *dp = nullptr; // Expect number larger than "123" int row_idx = len - 3; // "1234.5" if (len <= 5) { // "----", " ", "0.4", "237.5" dp = strchr(this->row_[j], '.'); row_idx = len - 1; } else if (len > 6) { // "12345.6" snprintf(this->row_[j], sizeof(this->row_[j]), "9999"); row_idx = 3; } row[3] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; if ((row_idx >= 0) && dp) { row_idx--; } row[2] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; row[1] = (row_idx >= 0) ? this->row_[j][row_idx--] : ' '; row[0] = (row_idx >= 0) ? this->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 = this->get_command_code_(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 (this->fahrenheit_) { buffer[1] |= 0x80; } if (this->celsius_) { buffer[3] |= 0x80; } if (this->kwh_) { buffer[4] |= 0x08; } if (this->humidity_) { buffer[6] |= 0x08; } if (this->voltage_) { buffer[7] |= 0x08; } // AddLog(LOG_LEVEL_DEBUG, PSTR("TM1: Dump3 %8_H"), buffer); this->send_address_(0x10); // Sonoff only uses the upper 16 Segments for (uint8_t i : buffer) { this->send_common_(i); } this->stop_(); } bool TM1621Display::send_command_(uint16_t command) { uint16_t full_command = (0x0400 | command) << 5; // 0b100cccccccc00000 this->cs_pin_->digital_write(false); // Start command sequence delayMicroseconds(TM1621_PULSE_WIDTH / 2); for (uint32_t i = 0; i < 12; i++) { this->write_pin_->digital_write(false); // Start write sequence if (full_command & 0x8000) { this->data_pin_->digital_write(true); // Set data } else { this->data_pin_->digital_write(false); // Set data } delayMicroseconds(TM1621_PULSE_WIDTH); this->write_pin_->digital_write(true); // Read data delayMicroseconds(TM1621_PULSE_WIDTH); full_command <<= 1; } this->stop_(); return true; } bool TM1621Display::send_common_(uint8_t common) { for (uint32_t i = 0; i < 8; i++) { this->write_pin_->digital_write(false); // Start write sequence if (common & 1) { this->data_pin_->digital_write(true); // Set data } else { this->data_pin_->digital_write(false); // Set data } delayMicroseconds(TM1621_PULSE_WIDTH); this->write_pin_->digital_write(true); // Read data delayMicroseconds(TM1621_PULSE_WIDTH); common >>= 1; } return true; } bool TM1621Display::send_address_(uint16_t address) { uint16_t full_address = (address | 0x0140) << 7; // 0b101aaaaaa0000000 this->cs_pin_->digital_write(false); // Start command sequence delayMicroseconds(TM1621_PULSE_WIDTH / 2); for (uint32_t i = 0; i < 9; i++) { this->write_pin_->digital_write(false); // Start write sequence if (full_address & 0x8000) { this->data_pin_->digital_write(true); // Set data } else { this->data_pin_->digital_write(false); // Set data } delayMicroseconds(TM1621_PULSE_WIDTH); this->write_pin_->digital_write(true); // Read data delayMicroseconds(TM1621_PULSE_WIDTH); full_address <<= 1; } return true; } uint8_t TM1621Display::print(uint8_t start_pos, const char *str) { // ESP_LOGD(TAG, "Print at %d: %s", start_pos, str); return snprintf(this->row_[start_pos], sizeof(this->row_[start_pos]), "%s", str); } uint8_t TM1621Display::print(const char *str) { return this->print(0, str); } uint8_t TM1621Display::printf(uint8_t pos, const char *format, ...) { va_list arg; va_start(arg, format); char buffer[64]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); if (ret > 0) return this->print(pos, buffer); return 0; } uint8_t TM1621Display::printf(const char *format, ...) { va_list arg; va_start(arg, format); char buffer[64]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); if (ret > 0) return this->print(buffer); return 0; } int TM1621Display::get_command_code_(char *destination, size_t destination_size, const char *needle, const char *haystack) { // Returns -1 of not found // Returns index and command if found int result = -1; const char *read = haystack; char *write = destination; while (true) { result++; size_t size = destination_size - 1; write = destination; char ch = '.'; while ((ch != '\0') && (ch != '|')) { ch = *(read++); if (size && (ch != '|')) { *write++ = ch; size--; } } *write = '\0'; if (!strcasecmp(needle, destination)) { break; } if (0 == ch) { result = -1; break; } } return result; } } // namespace tm1621 } // namespace esphome