379 lines
12 KiB
C
379 lines
12 KiB
C
// display.c (HT16K33 14-seg / 4 dígitos)
|
|
// TOP = 0x71 | BOTTOM = 0x70
|
|
// I2C_PORT = I2C_NUM_0
|
|
//
|
|
// MODO "SAFE":
|
|
// - Se I2C não estiver instalado, ou se não existir display, ignora tudo (sem spam)
|
|
// - display_init() NUNCA aborta o firmware (devolve ESP_OK e deixa display disabled)
|
|
|
|
#include "driver/i2c.h"
|
|
#include "esp_log.h"
|
|
#include "esp_err.h"
|
|
#include "display.h"
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
#define I2C_PORT I2C_NUM_0
|
|
|
|
// Endereços reais
|
|
#define DISP_TOP_ADDR 0x71
|
|
#define DISP_BOTTOM_ADDR 0x70
|
|
|
|
static const char *TAG = "DISPLAY";
|
|
|
|
// =======================================================
|
|
// FLAG GLOBAL
|
|
// =======================================================
|
|
static bool s_display_enabled = false;
|
|
|
|
void display_set_enabled(bool en) { s_display_enabled = en; }
|
|
bool display_is_enabled(void) { return s_display_enabled; }
|
|
|
|
// =======================================================
|
|
// MAPA DE SEGMENTOS (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)
|
|
|
|
static uint16_t charset(char c);
|
|
|
|
// =======================================================
|
|
// FUNÇÕES I2C (internas)
|
|
// =======================================================
|
|
|
|
static inline bool i2c_driver_is_installed(void)
|
|
{
|
|
// truque simples: tenta criar um cmd e dar begin "vazio" é overkill.
|
|
// Mais seguro: tenta uma escrita curta e ver se dá INVALID_STATE.
|
|
// Aqui vamos só confiar na primeira escrita do display_init().
|
|
return true;
|
|
}
|
|
|
|
static esp_err_t disp_send_cmd(uint8_t addr, uint8_t cmd)
|
|
{
|
|
return i2c_master_write_to_device(I2C_PORT, addr, &cmd, 1, pdMS_TO_TICKS(50));
|
|
}
|
|
|
|
static esp_err_t disp_raw(uint8_t addr, int pos, uint16_t mask)
|
|
{
|
|
if (pos < 0 || pos > 3) return ESP_ERR_INVALID_ARG;
|
|
|
|
uint8_t buf[3];
|
|
buf[0] = (uint8_t)(pos * 2);
|
|
buf[1] = (uint8_t)(mask & 0xFF);
|
|
buf[2] = (uint8_t)((mask >> 8) & 0xFF);
|
|
|
|
return i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), pdMS_TO_TICKS(50));
|
|
}
|
|
|
|
static esp_err_t disp_clear(uint8_t addr)
|
|
{
|
|
uint8_t buf[17] = {0};
|
|
buf[0] = 0x00;
|
|
return i2c_master_write_to_device(I2C_PORT, addr, buf, sizeof(buf), pdMS_TO_TICKS(50));
|
|
}
|
|
|
|
// =======================================================
|
|
// INIT (SAFE)
|
|
// =======================================================
|
|
|
|
esp_err_t display_init(void)
|
|
{
|
|
// por defeito: desligado
|
|
s_display_enabled = false;
|
|
|
|
// Se o driver I2C não estiver instalado, a primeira chamada vai dar INVALID_STATE.
|
|
// Nós tratamos isso como "não há display nesta board" e seguimos sem logs.
|
|
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
|
|
err = disp_send_cmd(DISP_TOP_ADDR, cmd1);
|
|
if (err == ESP_ERR_INVALID_STATE) return ESP_OK; // I2C não instalado -> ignora
|
|
if (err != ESP_OK) return ESP_OK; // display ausente/cabos -> ignora
|
|
|
|
err = disp_send_cmd(DISP_TOP_ADDR, cmd2);
|
|
if (err != ESP_OK) return ESP_OK;
|
|
|
|
err = disp_send_cmd(DISP_TOP_ADDR, cmd3);
|
|
if (err != ESP_OK) return ESP_OK;
|
|
|
|
err = disp_clear(DISP_TOP_ADDR);
|
|
if (err != ESP_OK) return ESP_OK;
|
|
|
|
// BOTTOM
|
|
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd1);
|
|
if (err != ESP_OK) return ESP_OK;
|
|
|
|
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd2);
|
|
if (err != ESP_OK) return ESP_OK;
|
|
|
|
err = disp_send_cmd(DISP_BOTTOM_ADDR, cmd3);
|
|
if (err != ESP_OK) return ESP_OK;
|
|
|
|
err = disp_clear(DISP_BOTTOM_ADDR);
|
|
if (err != ESP_OK) return ESP_OK;
|
|
|
|
// OK -> ativa display
|
|
s_display_enabled = true;
|
|
ESP_LOGI(TAG, "📟 Displays OK: TOP=0x%02X BOTTOM=0x%02X", DISP_TOP_ADDR, DISP_BOTTOM_ADDR);
|
|
return ESP_OK;
|
|
}
|
|
|
|
// =======================================================
|
|
// RAW / CLEAR (com guard)
|
|
// =======================================================
|
|
|
|
void display_raw_top(int pos, uint16_t mask)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
esp_err_t err = disp_raw(DISP_TOP_ADDR, pos, mask);
|
|
if (err != ESP_OK) {
|
|
// se o driver desaparecer a meio, desativa e cala
|
|
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
// opcional: não logar para evitar spam
|
|
// ESP_LOGE(TAG, "raw_top falhou: %s", esp_err_to_name(err));
|
|
}
|
|
}
|
|
|
|
void display_raw_bottom(int pos, uint16_t mask)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
esp_err_t err = disp_raw(DISP_BOTTOM_ADDR, pos, mask);
|
|
if (err != ESP_OK) {
|
|
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
// opcional: não logar
|
|
}
|
|
}
|
|
|
|
void display_clear_top(void)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
esp_err_t err = disp_clear(DISP_TOP_ADDR);
|
|
if (err != ESP_OK) {
|
|
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
}
|
|
}
|
|
|
|
void display_clear_bottom(void)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
esp_err_t err = disp_clear(DISP_BOTTOM_ADDR);
|
|
if (err != ESP_OK) {
|
|
if (err == ESP_ERR_INVALID_STATE) s_display_enabled = false;
|
|
}
|
|
}
|
|
|
|
// =======================================================
|
|
// TEXTO / CHARS
|
|
// =======================================================
|
|
|
|
void display_char_top(int pos, char c)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
display_raw_top(pos, charset(c));
|
|
}
|
|
|
|
void display_char_bottom(int pos, char c)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
display_raw_bottom(pos, charset(c));
|
|
}
|
|
|
|
void display_text_top(const char *txt)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
char c = (txt && txt[i]) ? txt[i] : ' ';
|
|
display_char_top(i, c);
|
|
}
|
|
}
|
|
|
|
void display_text_bottom(const char *txt)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
char c = (txt && txt[i]) ? txt[i] : ' ';
|
|
display_char_bottom(i, c);
|
|
}
|
|
}
|
|
|
|
// =======================================================
|
|
// NÚMEROS
|
|
// =======================================================
|
|
|
|
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 (!s_display_enabled) return;
|
|
if (val > 9) val = 0;
|
|
display_raw_top(pos, digit_mask[val]);
|
|
}
|
|
|
|
void display_digit_bottom(int pos, uint8_t val)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
if (val > 9) val = 0;
|
|
display_raw_bottom(pos, digit_mask[val]);
|
|
}
|
|
|
|
void display_number_top(int num)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
if (num < 0) num = 0;
|
|
if (num > 9999) num = 9999;
|
|
|
|
display_digit_top(3, (uint8_t)(num % 10));
|
|
display_digit_top(2, (uint8_t)((num / 10) % 10));
|
|
display_digit_top(1, (uint8_t)((num / 100) % 10));
|
|
display_digit_top(0, (uint8_t)((num / 1000) % 10));
|
|
}
|
|
|
|
void display_number_bottom(int num)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
if (num < 0) num = 0;
|
|
if (num > 9999) num = 9999;
|
|
|
|
display_digit_bottom(3, (uint8_t)(num % 10));
|
|
display_digit_bottom(2, (uint8_t)((num / 10) % 10));
|
|
display_digit_bottom(1, (uint8_t)((num / 100) % 10));
|
|
display_digit_bottom(0, (uint8_t)((num / 1000) % 10));
|
|
}
|
|
|
|
// =======================================================
|
|
// RELÓGIO (HH:MM com DP no meio)
|
|
// =======================================================
|
|
|
|
void display_set_time_top(int horas, int minutos)
|
|
{
|
|
if (!s_display_enabled) return;
|
|
|
|
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, (uint8_t)h1);
|
|
display_raw_top(1, mid);
|
|
display_digit_top(2, (uint8_t)m1);
|
|
display_digit_top(3, (uint8_t)m2);
|
|
}
|
|
|
|
// =======================================================
|
|
// CHARSET (igual ao 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
|
|
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;
|
|
}
|
|
}
|
|
|
|
void display_debug_segment(uint16_t bitmask)
|
|
{
|
|
(void)bitmask;
|
|
// opcional: podes mostrar um padrão fixo se display estiver ativo
|
|
// if (!display_is_enabled()) return;
|
|
// display_raw_top(0, bitmask);
|
|
}
|