Files
AutoFilm-ESP32/components/hal_display/hal_display_nv3041a.c
gronod 729dcfec3d
ci / test (pull_request) Canceled after 0s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (pull_request) Canceled after 0s
ci / firmware (wroom, sdkconfig.wroom, esp32) (pull_request) Canceled after 0s
ci / test (push) Successful in 57s
ci / firmware (jc4827w543, sdkconfig.s3, esp32s3) (push) Successful in 3m30s
ci / firmware (wroom, sdkconfig.wroom, esp32) (push) Successful in 5m40s
Fix display chunk flushing window reset
2026-09-29 17:01:56 +01:00

317 lines
8.0 KiB
C

#include "hal_display.h"
#include "font5x7.h"
#include "board.h"
#include <string.h>
#include <stdlib.h>
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_commands.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define LCD_HOST SPI2_HOST
#define LCD_H HAL_DISPLAY_PX_H
#define LCD_W HAL_DISPLAY_PX_W
#define COL_BG 0x18E3
#define COL_FG 0xFFFF
#define COL_ACC 0xF800
#define COL_BTN 0x39E7
#define COL_BTNT 0xFFFF
static const char *TAG = "disp-s3";
static esp_lcd_panel_io_handle_t s_io;
static uint16_t *s_fb;
static bool s_inited;
static bool s_dirty;
static uint16_t swap565(uint16_t c)
{
return (uint16_t)((c >> 8) | (c << 8));
}
static void fb_fill(uint16_t color)
{
if (s_fb == NULL) {
return;
}
uint16_t c = swap565(color);
for (int i = 0; i < LCD_W * LCD_H; i++) {
s_fb[i] = c;
}
s_dirty = true;
}
static void fb_pixel(int x, int y, uint16_t color)
{
if (s_fb == NULL || x < 0 || y < 0 || x >= LCD_W || y >= LCD_H) {
return;
}
s_fb[y * LCD_W + x] = swap565(color);
}
static void fb_fill_rect(int x, int y, int w, int h, uint16_t color)
{
if (s_fb == NULL) {
return;
}
if (x < 0) {
w += x;
x = 0;
}
if (y < 0) {
h += y;
y = 0;
}
if (x + w > LCD_W) {
w = LCD_W - x;
}
if (y + h > LCD_H) {
h = LCD_H - y;
}
uint16_t c = swap565(color);
for (int yy = 0; yy < h; yy++) {
uint16_t *row = &s_fb[(y + yy) * LCD_W + x];
for (int xx = 0; xx < w; xx++) {
row[xx] = c;
}
}
s_dirty = true;
}
static void fb_char(int x, int y, char ch, uint16_t fg, int scale)
{
if (ch < 32 || ch > 126) {
ch = '?';
}
const uint8_t *g = font5x7[ch - 32];
for (int row = 0; row < 7; row++) {
uint8_t bits = g[row];
for (int col = 0; col < 5; col++) {
if (bits & (0x10 >> col)) {
fb_fill_rect(x + col * scale, y + row * scale, scale, scale, fg);
}
}
}
}
static void fb_str(int x, int y, const char *s, uint16_t fg, int scale)
{
if (s == NULL) {
return;
}
int cx = x;
while (*s) {
fb_char(cx, y, *s, fg, scale);
cx += 6 * scale;
s++;
}
}
static void lcd_cmd(uint8_t cmd, const uint8_t *data, size_t len)
{
if (s_io == NULL) {
return;
}
uint32_t packed = ((uint32_t)0x02 << 24) | ((uint32_t)cmd << 8);
esp_lcd_panel_io_tx_param(s_io, packed, data, len);
}
static void lcd_window(int x0, int y0, int x1, int y1)
{
uint8_t cas[4] = { (uint8_t)(x0 >> 8), (uint8_t)x0, (uint8_t)(x1 >> 8), (uint8_t)x1 };
uint8_t ras[4] = { (uint8_t)(y0 >> 8), (uint8_t)y0, (uint8_t)(y1 >> 8), (uint8_t)y1 };
lcd_cmd(LCD_CMD_CASET, cas, 4);
lcd_cmd(LCD_CMD_RASET, ras, 4);
}
static void lcd_init_panel(void)
{
/* Vendor bring-up for NV3041A-01 480x272. Commands are public datasheet/community. */
static const uint8_t seq[][3] = {
{0xFF, 0xA5, 1},
{0x36, 0x00, 1},
{0x3A, 0x01, 1},
{0x41, 0x03, 1},
{0x44, 0x15, 1},
{0x45, 0x15, 1},
{0x7D, 0x03, 1},
{0xC1, 0xBB, 1},
{0xC2, 0x05, 1},
{0xC3, 0x10, 1},
{0xC6, 0x3E, 1},
{0xC7, 0x25, 1},
{0xC8, 0x11, 1},
{0xC9, 0x20, 1},
{0x7A, 0x51, 1},
{0x6F, 0x2D, 1},
{0x78, 0x4B, 1},
{0x70, 0x07, 1},
{0x71, 0x05, 1},
};
lcd_cmd(LCD_CMD_SWRESET, NULL, 0);
vTaskDelay(pdMS_TO_TICKS(120));
lcd_cmd(LCD_CMD_SLPOUT, NULL, 0);
vTaskDelay(pdMS_TO_TICKS(120));
for (size_t i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) {
uint8_t d = seq[i][1];
lcd_cmd(seq[i][0], &d, seq[i][2]);
}
lcd_cmd(LCD_CMD_DISPON, NULL, 0);
vTaskDelay(pdMS_TO_TICKS(20));
}
void hal_display_init(void)
{
if (s_inited) {
return;
}
gpio_config_t bl = {
.pin_bit_mask = 1ULL << board_pin_lcd_bl(),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&bl);
gpio_set_level(board_pin_lcd_bl(), 1);
spi_bus_config_t buscfg = {
.sclk_io_num = board_pin_lcd_sck(),
.data0_io_num = board_pin_lcd_d0(),
.data1_io_num = board_pin_lcd_d1(),
.data2_io_num = board_pin_lcd_d2(),
.data3_io_num = board_pin_lcd_d3(),
.max_transfer_sz = LCD_W * 40 * sizeof(uint16_t),
};
esp_err_t err = spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (err != ESP_OK) {
ESP_LOGE(TAG, "spi_bus_initialize %s", esp_err_to_name(err));
}
esp_lcd_panel_io_spi_config_t io_config = {
.cs_gpio_num = board_pin_lcd_cs(),
.dc_gpio_num = -1,
.spi_mode = 0,
.pclk_hz = 40 * 1000 * 1000,
.trans_queue_depth = 4,
.lcd_cmd_bits = 32,
.lcd_param_bits = 8,
.flags = {
.quad_mode = 1,
},
};
err = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &s_io);
if (err != ESP_OK) {
ESP_LOGE(TAG, "panel_io %s", esp_err_to_name(err));
s_io = NULL;
} else {
lcd_init_panel();
}
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t),
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (s_fb == NULL) {
s_fb = heap_caps_malloc((size_t)LCD_W * LCD_H * sizeof(uint16_t), MALLOC_CAP_8BIT);
}
if (s_fb == NULL) {
ESP_LOGE(TAG, "no framebuffer");
}
fb_fill(COL_BG);
s_inited = true;
ESP_LOGI(TAG, "NV3041A QSPI %dx%d CS=%d SCK=%d D0=%d",
LCD_W, LCD_H, (int)board_pin_lcd_cs(), (int)board_pin_lcd_sck(),
(int)board_pin_lcd_d0());
}
void hal_display_clear(void)
{
fb_fill(COL_BG);
}
void hal_display_text(int col, int row, const char *s)
{
if (s == NULL) {
return;
}
if (col < 0) {
col = 0;
}
if (row < 0 || row >= HAL_DISPLAY_ROWS) {
return;
}
int x = col * HAL_DISPLAY_CELL_W + 4;
int y = 8 + row * HAL_DISPLAY_CELL_H;
/* clip 20 columns */
char buf[HAL_DISPLAY_COLS + 1];
int n = 0;
while (s[n] && n < HAL_DISPLAY_COLS - col && n < HAL_DISPLAY_COLS) {
buf[n] = s[n];
n++;
}
buf[n] = 0;
fb_fill_rect(x, y, n * HAL_DISPLAY_CELL_W, HAL_DISPLAY_CELL_H - 4, COL_BG);
fb_str(x, y + 6, buf, COL_FG, 3);
}
void hal_display_glyph_thermo(int col, int row)
{
int x = col * HAL_DISPLAY_CELL_W + 8;
int y = 8 + row * HAL_DISPLAY_CELL_H + 4;
fb_fill_rect(x + 6, y, 6, 14, COL_ACC);
fb_fill_rect(x + 3, y + 14, 12, 10, COL_ACC);
}
void hal_display_softkey(int slot, int slots, const char *label)
{
if (slots < 1) {
slots = 1;
}
if (slot < 0 || slot >= slots) {
return;
}
int w = LCD_W / slots;
int x = slot * w + 4;
int y = LCD_H - HAL_DISPLAY_SOFTKEY_H + 4;
int bw = w - 8;
int bh = HAL_DISPLAY_SOFTKEY_H - 8;
fb_fill_rect(x, y, bw, bh, COL_BTN);
if (label != NULL) {
int scale = 3;
int tw = (int)strlen(label) * 6 * scale;
int tx = x + (bw - tw) / 2;
int ty = y + (bh - 7 * scale) / 2;
if (tx < x) {
tx = x + 4;
}
fb_str(tx, ty, label, COL_BTNT, scale);
}
}
void hal_display_flush(void)
{
if (!s_dirty || s_fb == NULL || s_io == NULL) {
s_dirty = false;
return;
}
const int chunk_rows = 16;
for (int y = 0; y < LCD_H; y += chunk_rows) {
int rows = chunk_rows;
if (y + rows > LCD_H) {
rows = LCD_H - y;
}
lcd_window(0, y, LCD_W - 1, y + rows - 1);
uint32_t packed = ((uint32_t)0x32 << 24) | ((uint32_t)LCD_CMD_RAMWR << 8);
esp_lcd_panel_io_tx_color(s_io, packed, &s_fb[y * LCD_W],
(size_t)rows * LCD_W * sizeof(uint16_t));
}
s_dirty = false;
}