feat(i2c-display): driver HT16K33 robusto + fix inversao TOP + I2C estabilizado

This commit is contained in:
master 2025-12-28 18:29:40 +00:00
parent c5aeddc653
commit 09e5be3233
6 changed files with 192 additions and 62 deletions

View File

@ -1,5 +1,13 @@
// display.c (HT16K33 14-seg / 4 dígitos)
// TOP = 0x71 | BOTTOM = 0x70
// I2C_PORT = I2C_NUM_0
//
// NOTA: agora o código NÃO engole erros de I2C.
// display_init() devolve esp_err_t e podes usar ESP_ERROR_CHECK(display_init()).
#include "driver/i2c.h"
#include "esp_log.h"
#include "esp_err.h"
#include "display.h"
#include <stdint.h>
@ -8,12 +16,14 @@
// 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";
static uint16_t rotate180(uint16_t m);
static uint16_t charset(char c);
static const char *TAG = "DISPLAY";
// =======================================================
// MAPA DE SEGMENTOS (mantido igual ao teu)
// MAPA DE SEGMENTOS (igual ao teu)
// =======================================================
#define SEG_A (1 << 0)
@ -40,31 +50,31 @@ const char *TAG = "DISPLAY";
// =======================================================
// FUNÇÕES GENÉRICAS PARA QUALQUER DISPLAY
// FUNÇÕES GENÉRICAS PARA QUALQUER DISPLAY (AGORA COM ERRO)
// =======================================================
static void disp_send_cmd(uint8_t addr, uint8_t cmd)
static esp_err_t disp_send_cmd(uint8_t addr, uint8_t cmd)
{
i2c_master_write_to_device(I2C_PORT, addr, &cmd, 1, 20 / portTICK_PERIOD_MS);
return i2c_master_write_to_device(I2C_PORT, addr, &cmd, 1, pdMS_TO_TICKS(50));
}
static void disp_raw(uint8_t addr, int pos, uint16_t mask)
static esp_err_t disp_raw(uint8_t addr, int pos, uint16_t mask)
{
if (pos < 0 || pos > 3) return;
if (pos < 0 || pos > 3) return ESP_ERR_INVALID_ARG;
uint8_t buf[3];
buf[0] = pos * 2;
buf[1] = mask & 0xFF;
buf[2] = (mask >> 8) & 0xFF;
buf[0] = (uint8_t)(pos * 2);
buf[1] = (uint8_t)(mask & 0xFF);
buf[2] = (uint8_t)((mask >> 8) & 0xFF);
i2c_master_write_to_device(I2C_PORT, addr, buf, 3, 20 / portTICK_PERIOD_MS);
return i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), pdMS_TO_TICKS(50));
}
static void disp_clear(uint8_t addr)
static esp_err_t 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);
return i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), pdMS_TO_TICKS(50));
}
@ -72,25 +82,42 @@ static void disp_clear(uint8_t addr)
// INIT PARA OS DOIS DISPLAYS (TOP 0x71 | BOTTOM 0x70)
// =======================================================
void display_init(void)
esp_err_t display_init(void)
{
uint8_t cmd1 = 0x21; // oscillator ON
uint8_t cmd2 = 0x81; // display ON, blink OFF
uint8_t cmd3 = 0xEF; // brightness MAX
const uint8_t cmd1 = 0x21; // oscillator ON
const uint8_t cmd2 = 0x81; // display ON, blink OFF
const uint8_t cmd3 = 0xEF; // brightness MAX
esp_err_t err;
// 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);
err = disp_send_cmd(DISP_TOP_ADDR, cmd1);
if (err != ESP_OK) { ESP_LOGE(TAG, "TOP 0x%02X cmd1 falhou: %s", DISP_TOP_ADDR, esp_err_to_name(err)); return err; }
err = disp_send_cmd(DISP_TOP_ADDR, cmd2);
if (err != ESP_OK) { ESP_LOGE(TAG, "TOP 0x%02X cmd2 falhou: %s", DISP_TOP_ADDR, esp_err_to_name(err)); return err; }
err = disp_send_cmd(DISP_TOP_ADDR, cmd3);
if (err != ESP_OK) { ESP_LOGE(TAG, "TOP 0x%02X cmd3 falhou: %s", DISP_TOP_ADDR, esp_err_to_name(err)); return err; }
err = disp_clear(DISP_TOP_ADDR);
if (err != ESP_OK) { ESP_LOGE(TAG, "TOP 0x%02X clear falhou: %s", DISP_TOP_ADDR, esp_err_to_name(err)); return err; }
// 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);
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd1);
if (err != ESP_OK) { ESP_LOGE(TAG, "BOTTOM 0x%02X cmd1 falhou: %s", DISP_BOTTOM_ADDR, esp_err_to_name(err)); return err; }
ESP_LOGI(TAG, "📟 Displays inicializados: TOP=0x71 BOTTOM=0x70");
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd2);
if (err != ESP_OK) { ESP_LOGE(TAG, "BOTTOM 0x%02X cmd2 falhou: %s", DISP_BOTTOM_ADDR, esp_err_to_name(err)); return err; }
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd3);
if (err != ESP_OK) { ESP_LOGE(TAG, "BOTTOM 0x%02X cmd3 falhou: %s", DISP_BOTTOM_ADDR, esp_err_to_name(err)); return err; }
err = disp_clear(DISP_BOTTOM_ADDR);
if (err != ESP_OK) { ESP_LOGE(TAG, "BOTTOM 0x%02X clear falhou: %s", DISP_BOTTOM_ADDR, esp_err_to_name(err)); return err; }
ESP_LOGI(TAG, "📟 Displays OK: TOP=0x%02X BOTTOM=0x%02X", DISP_TOP_ADDR, DISP_BOTTOM_ADDR);
return ESP_OK;
}
@ -99,27 +126,34 @@ void display_init(void)
// =======================================================
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);
esp_err_t err = disp_raw(DISP_TOP_ADDR, pos, mask);
if (err != ESP_OK) {
ESP_LOGE(TAG, "raw_top falhou (pos=%d addr=0x%02X): %s", pos, DISP_TOP_ADDR, esp_err_to_name(err));
}
}
void display_raw_bottom(int pos, uint16_t mask)
{
disp_raw(DISP_BOTTOM_ADDR, pos, mask);
esp_err_t err = disp_raw(DISP_BOTTOM_ADDR, pos, mask);
if (err != ESP_OK) {
ESP_LOGE(TAG, "raw_bottom falhou (pos=%d addr=0x%02X): %s", pos, DISP_BOTTOM_ADDR, esp_err_to_name(err));
}
}
void display_clear_top(void)
{
disp_clear(DISP_TOP_ADDR);
esp_err_t err = disp_clear(DISP_TOP_ADDR);
if (err != ESP_OK) ESP_LOGE(TAG, "clear_top falhou: %s", esp_err_to_name(err));
}
void display_clear_bottom(void)
{
disp_clear(DISP_BOTTOM_ADDR);
esp_err_t err = disp_clear(DISP_BOTTOM_ADDR);
if (err != ESP_OK) ESP_LOGE(TAG, "clear_bottom falhou: %s", esp_err_to_name(err));
}
// =====================
// ROTATE 180 para 14 segmentos REAL
// (mapa correto para o teu HT16K33)
@ -231,22 +265,21 @@ void display_char_top(int pos, char c)
void display_char_bottom(int pos, char c)
{
disp_raw(DISP_BOTTOM_ADDR, pos, charset(c));
display_raw_bottom(pos, charset(c));
}
void display_text_top(const char *txt)
{
for (int i = 0; i < 4; i++) {
char c = txt[i] ? txt[i] : ' ';
char c = (txt && 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] : ' ';
char c = (txt && txt[i]) ? txt[i] : ' ';
display_char_bottom(i, c);
}
}
@ -276,11 +309,10 @@ void display_digit_top(int pos, uint8_t val)
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]);
display_raw_bottom(pos, digit_mask[val]);
}
void display_number_top(int num)
@ -294,21 +326,20 @@ void display_number_top(int num)
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_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
// display_set_time_top() — mantém SEG_DP no sítio certo
// =======================================================
void display_set_time_top(int horas, int minutos)
{
@ -324,8 +355,8 @@ void display_set_time_top(int horas, int minutos)
uint16_t mid = digit_mask[h2] | SEG_DP;
display_digit_top(0, h1);
display_digit_top(0, (uint8_t)h1);
display_raw_top(1, mid);
display_digit_top(2, m1);
display_digit_top(3, m2);
display_digit_top(2, (uint8_t)m1);
display_digit_top(3, (uint8_t)m2);
}

View File

@ -1,20 +1,86 @@
#include "driver/i2c.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_rom_sys.h"
#define SDA_PIN 21
#define SCL_PIN 22
#define I2C_PORT I2C_NUM_0
void i2c_init(void)
static const char *TAG_I2C = "I2C";
static void i2c_bus_reset(void)
{
// Solta o bus: gera 9 clocks em SCL para libertar SDA se algum escravo ficou preso
gpio_config_t io = {
.pin_bit_mask = (1ULL << SDA_PIN) | (1ULL << SCL_PIN),
.mode = GPIO_MODE_OUTPUT_OD,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io);
gpio_set_level(SDA_PIN, 1);
gpio_set_level(SCL_PIN, 1);
esp_rom_delay_us(5);
for (int i = 0; i < 9; i++) {
gpio_set_level(SCL_PIN, 0);
esp_rom_delay_us(5);
gpio_set_level(SCL_PIN, 1);
esp_rom_delay_us(5);
}
// STOP
gpio_set_level(SDA_PIN, 0);
esp_rom_delay_us(5);
gpio_set_level(SCL_PIN, 1);
esp_rom_delay_us(5);
gpio_set_level(SDA_PIN, 1);
esp_rom_delay_us(5);
// devolve os pinos ao I2C driver depois
}
esp_err_t i2c_init(void)
{
// se já estava instalado, limpa e volta a instalar
i2c_driver_delete(I2C_PORT);
i2c_bus_reset();
i2c_config_t cfg = {
.mode = I2C_MODE_MASTER,
.sda_io_num = SDA_PIN,
.scl_io_num = SCL_PIN,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 400000
.master.clk_speed = 100000, // começa seguro
.clk_flags = 0
};
i2c_param_config(I2C_PORT, &cfg);
i2c_driver_install(I2C_PORT, cfg.mode, 0, 0, 0);
esp_err_t err;
err = i2c_param_config(I2C_PORT, &cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG_I2C, "i2c_param_config falhou: %s", esp_err_to_name(err));
return err;
}
err = i2c_driver_install(I2C_PORT, cfg.mode, 0, 0, 0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG_I2C, "i2c_driver_install falhou: %s", esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG_I2C, "I2C OK (SDA=%d SCL=%d) freq=100kHz", SDA_PIN, SCL_PIN);
return ESP_OK;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <stdint.h>
#include "esp_err.h"
// =======================================================
// ENDEREÇOS I2C (definidos no .c)
@ -8,7 +9,7 @@
// BOTTOM = 0x70
// Inicialização dos dois displays
void display_init(void);
esp_err_t display_init(void);
// =======================================================
// RAW ACCESS (usa 16 bits de segmentos)

View File

@ -1,3 +1,4 @@
#pragma once
#include "esp_err.h"
void i2c_init(void);
esp_err_t i2c_init(void);

View File

@ -32,6 +32,11 @@
#include "display.h"
#include <stdbool.h>
esp_err_t i2c_init(void);
esp_err_t display_init(void);
bool modo_bloqueado = false; // definição oficial
#define SDA_PIN 21
@ -136,25 +141,37 @@ void ht16_test()
i2c_master_write_to_device(I2C_PORT, 0x70, buf, sizeof(buf), 20 / portTICK_PERIOD_MS);
}
void i2c_scan()
//************************************************************** */
void i2c_scan(void)
{
printf("\n--- A fazer scan ao I2C ---\n");
// --- SCAN I2C ---
for (uint8_t addr = 1; addr < 127; addr++) {
int found = 0;
for (uint8_t addr = 1; addr < 0x7F; addr++) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_stop(cmd);
esp_err_t r = i2c_master_cmd_begin(I2C_PORT, cmd, 20 / portTICK_PERIOD_MS);
esp_err_t r = i2c_master_cmd_begin(I2C_PORT, cmd, pdMS_TO_TICKS(50));
i2c_cmd_link_delete(cmd);
if (r == ESP_OK) {
printf("ENCONTRADO I2C: 0x%02X\n", addr);
printf("✅ I2C encontrado: 0x%02X\n", addr);
found++;
} else if (r == ESP_ERR_TIMEOUT) {
printf("⏱️ TIMEOUT no addr 0x%02X (bus preso?)\n", addr);
break; // não vale a pena continuar
}
}
if (!found) {
printf("❌ Nenhum dispositivo I2C encontrado\n");
}
}
// ============================
// MAIN
// ============================
@ -185,11 +202,22 @@ void app_main(void) {
i2c_init(); // <- o helper entra aqui
display_init(); // <- inicializa o HT16K33
// 1) Inicializa I2C (não aborta)
esp_err_t ei = i2c_init();
if (ei != ESP_OK) {
printf("i2c_init falhou: %s\n", esp_err_to_name(ei));
}
// 2) Scan I2C (ver o que existe no barramento)
i2c_scan();
display_text_top("INIT");
// 3) Inicializa displays SEM abortar (para não rebootar em loop)
esp_err_t ed = display_init();
printf("display_init = %s\n", esp_err_to_name(ed));
// 4) Teste simples no display (se existir)
display_text_top("INIT");

3
tatus Normal file
View File

@ -0,0 +1,3 @@
c5aeddc (HEAD -> main, tag: portal-top, origin/main, origin/HEAD) chore: ignore vscode settings
0d828ca wifi: portal Wi-Fi top com scan, clear_wifi MQTT e fluxo limpo STA/AP
4d28d62 commit final pc2