317 lines
8.0 KiB
C
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;
|
|
}
|
|
lcd_window(0, 0, LCD_W - 1, LCD_H - 1);
|
|
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;
|
|
}
|
|
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;
|
|
}
|