#include "driver/i2c.h" #include "esp_log.h" #include "display.h" #include #define I2C_PORT I2C_NUM_0 // Endereços reais #define DISP_TOP_ADDR 0x71 // display superior #define DISP_BOTTOM_ADDR 0x70 // display inferior static uint16_t rotate180(uint16_t m); const char *TAG = "DISPLAY"; // ======================================================= // MAPA DE SEGMENTOS (mantido igual ao teu) // ======================================================= #define SEG_A (1 << 0) #define SEG_B (1 << 1) #define SEG_C (1 << 2) #define SEG_D (1 << 3) #define SEG_E (1 << 4) #define SEG_F (1 << 5) #define SEG_ML (1 << 6) #define SEG_MR (1 << 7) #define SEG_TL (1 << 8) #define SEG_TM (1 << 9) #define SEG_TR (1 << 10) #define SEG_BL (1 << 11) #define SEG_BM (1 << 12) #define SEG_BR (1 << 13) #define SEG_DP (1 << 14) #define SEG_G (SEG_ML | SEG_MR) // ======================================================= // FUNÇÕES GENÉRICAS PARA QUALQUER DISPLAY // ======================================================= static void disp_send_cmd(uint8_t addr, uint8_t cmd) { i2c_master_write_to_device(I2C_PORT, addr, &cmd, 1, 20 / portTICK_PERIOD_MS); } static void disp_raw(uint8_t addr, int pos, uint16_t mask) { if (pos < 0 || pos > 3) return; uint8_t buf[3]; buf[0] = pos * 2; buf[1] = mask & 0xFF; buf[2] = (mask >> 8) & 0xFF; i2c_master_write_to_device(I2C_PORT, addr, buf, 3, 20 / portTICK_PERIOD_MS); } static void disp_clear(uint8_t addr) { uint8_t buf[17] = {0}; buf[0] = 0x00; i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), 20 / portTICK_PERIOD_MS); } // ======================================================= // INIT PARA OS DOIS DISPLAYS (TOP 0x71 | BOTTOM 0x70) // ======================================================= void display_init(void) { uint8_t cmd1 = 0x21; // oscillator ON uint8_t cmd2 = 0x81; // display ON, blink OFF uint8_t cmd3 = 0xEF; // brightness MAX // TOP disp_send_cmd(DISP_TOP_ADDR, cmd1); disp_send_cmd(DISP_TOP_ADDR, cmd2); disp_send_cmd(DISP_TOP_ADDR, cmd3); disp_clear(DISP_TOP_ADDR); // BOTTOM disp_send_cmd(DISP_BOTTOM_ADDR, cmd1); disp_send_cmd(DISP_BOTTOM_ADDR, cmd2); disp_send_cmd(DISP_BOTTOM_ADDR, cmd3); disp_clear(DISP_BOTTOM_ADDR); ESP_LOGI(TAG, "📟 Displays inicializados: TOP=0x71 BOTTOM=0x70"); } // ======================================================= // RAW PARA TOP E BOTTOM // ======================================================= void display_raw_top(int pos, uint16_t mask) { int p = 3 - pos; // inverter ordem dos dígitos uint16_t m = rotate180(mask); // rodar segmentos disp_raw(DISP_TOP_ADDR, p, m); } void display_raw_bottom(int pos, uint16_t mask) { disp_raw(DISP_BOTTOM_ADDR, pos, mask); } void display_clear_top(void) { disp_clear(DISP_TOP_ADDR); } void display_clear_bottom(void) { disp_clear(DISP_BOTTOM_ADDR); } // ===================== // ROTATE 180 para 14 segmentos REAL // (mapa correto para o teu HT16K33) // ===================== static uint16_t rotate180(uint16_t m) { uint16_t r = 0; // A (topo) <-> D (baixo) if (m & SEG_A) r |= SEG_D; if (m & SEG_D) r |= SEG_A; // B (top-right) <-> E (bottom-left) if (m & SEG_B) r |= SEG_E; if (m & SEG_E) r |= SEG_B; // C (bottom-right) <-> F (top-left) if (m & SEG_C) r |= SEG_F; if (m & SEG_F) r |= SEG_C; // Meio vertical ML ↔ MR if (m & SEG_ML) r |= SEG_MR; if (m & SEG_MR) r |= SEG_ML; // Alfanuméricos topo TL/TM/TR ↔ BL/BM/BR if (m & SEG_TL) r |= SEG_BL; if (m & SEG_BL) r |= SEG_TL; if (m & SEG_TM) r |= SEG_BM; if (m & SEG_BM) r |= SEG_TM; if (m & SEG_TR) r |= SEG_BR; if (m & SEG_BR) r |= SEG_TR; // DP é DP (ponto) if (m & SEG_DP) r |= SEG_DP; return r; } // ======================================================= // CHARSET — mantido EXACTAMENTE como o teu // ======================================================= static uint16_t charset(char c) { switch (c) { // NÚMEROS case '0': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F; case '1': return SEG_B | SEG_C; case '2': return SEG_A | SEG_B | SEG_G | SEG_E | SEG_D; case '3': return SEG_A | SEG_B | SEG_G | SEG_C | SEG_D; case '4': return SEG_F | SEG_G | SEG_B | SEG_C; case '5': return SEG_A | SEG_F | SEG_G | SEG_C | SEG_D; case '6': return SEG_A | SEG_F | SEG_G | SEG_E | SEG_D | SEG_C; case '7': return SEG_A | SEG_B | SEG_C; case '8': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G; case '9': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G; // LETRAS COMPLETAS (mantidas) case 'A': case 'a': return SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G; case 'B': case 'b': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G; case 'C': case 'c': return SEG_A | SEG_F | SEG_E | SEG_D; case 'D': case 'd': return SEG_B | SEG_C | SEG_D | SEG_E | SEG_G; case 'E': case 'e': return SEG_A | SEG_F | SEG_G | SEG_E | SEG_D; case 'F': case 'f': return SEG_A | SEG_F | SEG_G | SEG_E; case 'G': case 'g': return SEG_A | SEG_F | SEG_E | SEG_D | SEG_C | SEG_G; case 'H': case 'h': return SEG_F | SEG_E | SEG_G | SEG_B | SEG_C; case 'I': case 'i': return SEG_B | SEG_C; case 'J': case 'j': return SEG_B | SEG_C | SEG_D; case 'K': case 'k': return SEG_F | SEG_E | SEG_G | SEG_TR | SEG_BR; case 'L': case 'l': return SEG_F | SEG_E | SEG_D; case 'M': case 'm': return SEG_F | SEG_E | SEG_TL | SEG_TR | SEG_B | SEG_C; case 'N': case 'n': return SEG_F | SEG_E | SEG_TL | SEG_BR | SEG_C | SEG_B; case 'O': case 'o': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F; case 'P': case 'p': return SEG_A | SEG_B | SEG_F | SEG_G | SEG_E; case 'Q': case 'q': return SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_BR; case 'R': case 'r': return SEG_A | SEG_B | SEG_F | SEG_G | SEG_E | SEG_BR; case 'S': case 's': return SEG_A | SEG_F | SEG_G | SEG_C | SEG_D; case 'T': case 't': return SEG_A | SEG_TM | SEG_BR; case 'U': case 'u': return SEG_F | SEG_E | SEG_D | SEG_C | SEG_B; case 'V': case 'v': return SEG_F | SEG_E | SEG_D | SEG_B | SEG_TR; case 'W': case 'w': return SEG_F | SEG_E | SEG_D | SEG_C | SEG_B | SEG_BR | SEG_TR; case 'X': case 'x': return SEG_TL | SEG_TR | SEG_ML | SEG_MR | SEG_BL | SEG_BR; case 'Y': case 'y': return SEG_F | SEG_B | SEG_G | SEG_C | SEG_D; case 'Z': case 'z': return SEG_A | SEG_TR | SEG_G | SEG_BL | SEG_D; // símbolos case '-': return SEG_G; case '.': return SEG_DP; case '_': return SEG_D; case ' ': return 0; default: return 0; } } // ======================================================= // DISPLAY CARACTER / TEXTO // ======================================================= void display_char_top(int pos, char c) { display_raw_top(pos, charset(c)); } void display_char_bottom(int pos, char c) { disp_raw(DISP_BOTTOM_ADDR, pos, charset(c)); } void display_text_top(const char *txt) { for (int i = 0; i < 4; i++) { char c = txt[i] ? txt[i] : ' '; display_char_top(i, c); } } void display_text_bottom(const char *txt) { for (int i = 0; i < 4; i++) { char c = txt[i] ? txt[i] : ' '; display_char_bottom(i, c); } } // ======================================================= // DISPLAY DE NÚMEROS (igual ao teu) // ======================================================= static const uint16_t digit_mask[10] = { [0] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, [1] = SEG_B | SEG_C, [2] = SEG_A | SEG_B | SEG_G | SEG_E | SEG_D, [3] = SEG_A | SEG_B | SEG_G | SEG_C | SEG_D, [4] = SEG_F | SEG_G | SEG_B | SEG_C, [5] = SEG_A | SEG_F | SEG_G | SEG_C | SEG_D, [6] = SEG_A | SEG_F | SEG_G | SEG_E | SEG_D | SEG_C, [7] = SEG_A | SEG_B | SEG_C, [8] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F | SEG_G, [9] = SEG_A | SEG_B | SEG_C | SEG_D | SEG_F | SEG_G, }; void display_digit_top(int pos, uint8_t val) { if (val > 9) val = 0; display_raw_top(pos, digit_mask[val]); } void display_digit_bottom(int pos, uint8_t val) { if (val > 9) val = 0; disp_raw(DISP_BOTTOM_ADDR, pos, digit_mask[val]); } void display_number_top(int num) { if (num < 0) num = 0; if (num > 9999) num = 9999; display_digit_top(3, num % 10); display_digit_top(2, (num / 10) % 10); display_digit_top(1, (num / 100) % 10); display_digit_top(0, (num / 1000) % 10); } void display_number_bottom(int num) { if (num < 0) num = 0; if (num > 9999) num = 9999; display_digit_bottom(3, num % 10); display_digit_bottom(2, (num/10) % 10); display_digit_bottom(1, (num/100) % 10); display_digit_bottom(0, (num/1000) % 10); } // ======================================================= // display_set_time() — mantém SEG_DP no sítio certo // ======================================================= void display_set_time_top(int horas, int minutos) { if (horas < 0) horas = 0; if (horas > 99) horas = 99; if (minutos < 0) minutos = 0; if (minutos > 59) minutos = 59; int h1 = horas / 10; int h2 = horas % 10; int m1 = minutos / 10; int m2 = minutos % 10; uint16_t mid = digit_mask[h2] | SEG_DP; display_digit_top(0, h1); display_raw_top(1, mid); display_digit_top(2, m1); display_digit_top(3, m2); }