366 lines
13 KiB
C
366 lines
13 KiB
C
/**
|
||
* @file st7305_rlcd.c
|
||
* @brief ST7305 反射式 LCD 驱动实现(纯 C)
|
||
*
|
||
* 移植自微雪官方 custom_lcd_display.cc。
|
||
* 关键逻辑(初始化序列、横屏 LUT、像素打包)100% 保留,
|
||
* 只是把 C++ class 改成模块级全局变量 + 函数。
|
||
*/
|
||
|
||
#include "st7305_rlcd.h"
|
||
|
||
#include <string.h>
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "esp_log.h"
|
||
#include "esp_heap_caps.h"
|
||
#include "driver/gpio.h"
|
||
|
||
static const char *TAG = "st7305";
|
||
|
||
/* ===========================================================
|
||
* 模块内部状态(等价于原来 C++ 类的成员变量)
|
||
* =========================================================== */
|
||
|
||
/* SPI 设备句柄:用 ESP-IDF 的 esp_lcd panel io 框架来管 SPI 通信 */
|
||
static esp_lcd_panel_io_handle_t s_io_handle = NULL;
|
||
|
||
/* 显存:每 8 像素打包成 1 字节,所以显存大小 = W*H/8 = 400*300/8 = 15000 字节。
|
||
* 这个 buffer 放在 PSRAM 里(8MB PSRAM 完全够)。 */
|
||
#define DISPLAY_BUFFER_LEN ((ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT) / 8)
|
||
static uint8_t *s_disp_buffer = NULL;
|
||
|
||
/* 横屏 LUT:把 (x,y) 像素坐标映射到 (字节索引, 位掩码)。
|
||
* ST7305 的显存布局很奇怪(2列×4行打包),没有 LUT 的话
|
||
* 每次画一个像素都要重新算一遍位运算,LUT 让它变成 O(1) 查表。
|
||
* 索引方式:pixel_index = x * HEIGHT + y(跟官方一致) */
|
||
static uint16_t *s_pixel_index_lut = NULL; /* 大小: W*H 个 uint16 */
|
||
static uint8_t *s_pixel_bit_lut = NULL; /* 大小: W*H 个 uint8 */
|
||
|
||
/* ===========================================================
|
||
* SPI 底层:发命令 / 发数据
|
||
* 用 esp_lcd panel io 框架,它会自动管 DC 引脚
|
||
* (DC=0 表示命令, DC=1 表示数据)
|
||
* =========================================================== */
|
||
static void send_command(uint8_t cmd)
|
||
{
|
||
/* 用 -1 作为 dis_flags 表示"不带参数的命令" */
|
||
esp_lcd_panel_io_tx_param(s_io_handle, cmd, NULL, 0);
|
||
}
|
||
|
||
static void send_data(uint8_t data)
|
||
{
|
||
esp_lcd_panel_io_tx_param(s_io_handle, -1, &data, 1);
|
||
}
|
||
|
||
static void send_buffer(uint8_t *data, int len)
|
||
{
|
||
esp_lcd_panel_io_tx_color(s_io_handle, -1, data, len);
|
||
}
|
||
|
||
/* ===========================================================
|
||
* 硬件复位(官方 RLCD_Reset 翻译)
|
||
* =========================================================== */
|
||
static void hardware_reset(void)
|
||
{
|
||
/* RST 引脚高 → 低 → 高 的脉冲,跟官方时序一致 */
|
||
gpio_set_level(ST7305_RLCD_PIN_RST, 1);
|
||
vTaskDelay(pdMS_TO_TICKS(50));
|
||
gpio_set_level(ST7305_RLCD_PIN_RST, 0);
|
||
vTaskDelay(pdMS_TO_TICKS(20));
|
||
gpio_set_level(ST7305_RLCD_PIN_RST, 1);
|
||
vTaskDelay(pdMS_TO_TICKS(50));
|
||
}
|
||
|
||
/* ===========================================================
|
||
* ST7305 初始化序列(官方 RLCD_Init 完整翻译)
|
||
* 这 50+ 条寄存器命令是从微雪官方代码原样复制来的,
|
||
* 不要随便改顺序或值 —— 它们是微雪调好的参数。
|
||
* 每条命令的中文注释参考 ST7305 datasheet 和官方命名。
|
||
* =========================================================== */
|
||
static void init_display(void)
|
||
{
|
||
/* NVM Load Control - 从非易失存储加载设置 */
|
||
send_command(0xD6);
|
||
send_data(0x17);
|
||
send_data(0x02);
|
||
|
||
/* Booster Enable - 使能电荷泵 */
|
||
send_command(0xD1);
|
||
send_data(0x01);
|
||
|
||
/* Gate Voltage Control - VGH/VGL 电压 */
|
||
send_command(0xC0);
|
||
send_data(0x11);
|
||
send_data(0x04);
|
||
|
||
/* VSHP Setting - 正向源电压(高功耗模式) */
|
||
send_command(0xC1);
|
||
send_data(0x69); send_data(0x69); send_data(0x69); send_data(0x69);
|
||
|
||
/* VSLP Setting - 正向源电压(低功耗模式) */
|
||
send_command(0xC2);
|
||
send_data(0x19); send_data(0x19); send_data(0x19); send_data(0x19);
|
||
|
||
/* VSHN Setting - 负向源电压(高功耗模式) */
|
||
send_command(0xC4);
|
||
send_data(0x4B); send_data(0x4B); send_data(0x4B); send_data(0x4B);
|
||
|
||
/* VSLN Setting - 负向源电压(低功耗模式) */
|
||
send_command(0xC5);
|
||
send_data(0x19); send_data(0x19); send_data(0x19); send_data(0x19);
|
||
|
||
/* OSC Setting - 振荡器频率控制 */
|
||
send_command(0xD8);
|
||
send_data(0x80);
|
||
send_data(0xE9);
|
||
|
||
/* Frame Rate Control - 帧率 */
|
||
send_command(0xB2);
|
||
send_data(0x02);
|
||
|
||
/* Gate EQ Control (High Power Mode) - 更新周期时序 */
|
||
send_command(0xB3);
|
||
send_data(0xE5); send_data(0xF6); send_data(0x05); send_data(0x46);
|
||
send_data(0x77); send_data(0x77); send_data(0x77); send_data(0x77);
|
||
send_data(0x76); send_data(0x45);
|
||
|
||
/* Gate EQ Control (Low Power Mode) - 更新周期时序 */
|
||
send_command(0xB4);
|
||
send_data(0x05); send_data(0x46); send_data(0x77); send_data(0x77);
|
||
send_data(0x77); send_data(0x77); send_data(0x76); send_data(0x45);
|
||
|
||
/* Gate Timing Control */
|
||
send_command(0x62);
|
||
send_data(0x32); send_data(0x03); send_data(0x1F);
|
||
|
||
/* Source EQ Enable */
|
||
send_command(0xB7);
|
||
send_data(0x13);
|
||
|
||
/* Gate Line Setting - 门极线数(板子相关)
|
||
* 0x64 = 100,100*3 = 300 行,对应 300 高 */
|
||
send_command(0xB0);
|
||
send_data(0x64);
|
||
|
||
/* Sleep Out - 退出睡眠(datasheet 要求 200ms 延时) */
|
||
send_command(0x11);
|
||
vTaskDelay(pdMS_TO_TICKS(200));
|
||
|
||
/* Source Voltage Select - 选 VSHP1/VSLP1/VSHN1/VSLN1 */
|
||
send_command(0xC9);
|
||
send_data(0x00);
|
||
|
||
/* MADCTL (Memory Data Access Control) - MX=1, DO=1 */
|
||
send_command(0x36);
|
||
send_data(0x48);
|
||
|
||
/* 1-bit 单色模式 */
|
||
send_command(0x3A);
|
||
send_data(0x11);
|
||
|
||
/* Gamma Mode - 单色 */
|
||
send_command(0xB9);
|
||
send_data(0x20);
|
||
|
||
/* Panel Setting - 1-dot 反转,帧反转,隔行 */
|
||
send_command(0xB8);
|
||
send_data(0x29);
|
||
|
||
/* Display Inversion On */
|
||
send_command(0x21);
|
||
|
||
/* Column Address Set - 板子相关 */
|
||
send_command(0x2A);
|
||
send_data(0x12);
|
||
send_data(0x2A);
|
||
|
||
/* Row Address Set - 板子相关 */
|
||
send_command(0x2B);
|
||
send_data(0x00);
|
||
send_data(0xC7);
|
||
|
||
/* Tearing Effect Line On */
|
||
send_command(0x35);
|
||
send_data(0x00);
|
||
|
||
/* Auto Power Down Control */
|
||
send_command(0xD0);
|
||
send_data(0xFF);
|
||
|
||
/* High Power Mode On */
|
||
send_command(0x38);
|
||
|
||
/* Display On */
|
||
send_command(0x29);
|
||
}
|
||
|
||
/* ===========================================================
|
||
* 横屏像素 LUT 构造(官方 InitLandscapeLUT 翻译)
|
||
*
|
||
* ST7305 横屏显存布局:每字节装 8 个像素,排列是 2列×4行 的块
|
||
*
|
||
* 一个字节(8 bit)的物理含义:
|
||
* Bit 7: (row 0, col 0) Bit 6: (row 0, col 1)
|
||
* Bit 5: (row 1, col 0) Bit 4: (row 1, col 1)
|
||
* Bit 3: (row 2, col 0) Bit 2: (row 2, col 1)
|
||
* Bit 1: (row 3, col 0) Bit 0: (row 3, col 1)
|
||
*
|
||
* 所以给一个逻辑坐标 (x, y),要算出:
|
||
* 1. 它在显存里的字节索引
|
||
* 2. 在该字节里的位掩码
|
||
* 这就是 LUT 干的事,预计算一次,后面画像素就 O(1) 查表。
|
||
* =========================================================== */
|
||
static void init_landscape_lut(void)
|
||
{
|
||
const uint16_t H4 = ST7305_RLCD_HEIGHT >> 2; /* 高度方向块数 = 300/4 = 75 */
|
||
|
||
for (uint16_t y = 0; y < ST7305_RLCD_HEIGHT; y++) {
|
||
/* y 方向:ST7305 是从下往上扫的,所以要反转 */
|
||
uint16_t inv_y = ST7305_RLCD_HEIGHT - 1 - y;
|
||
uint16_t block_y = inv_y >> 2; /* 在哪个 4 行块里 */
|
||
uint8_t local_y = inv_y & 3; /* 块内第几行 */
|
||
|
||
for (uint16_t x = 0; x < ST7305_RLCD_WIDTH; x++) {
|
||
uint16_t byte_x = x >> 1; /* 2 列共用 1 字节 */
|
||
uint8_t local_x = x & 1; /* 块内第几列 */
|
||
|
||
/* 字节索引 = 列块号 * 高度块数 + 行块号 */
|
||
uint32_t buffer_idx = (uint32_t)byte_x * H4 + block_y;
|
||
/* 位掩码:bit 位置 = 7 - (local_y * 2 + local_x) */
|
||
uint8_t bit_pos = 7 - ((local_y << 1) | local_x);
|
||
uint8_t bit_mask = (uint8_t)(1u << bit_pos);
|
||
|
||
/* LUT 索引方式:x * HEIGHT + y(跟官方一致) */
|
||
uint32_t pixel_idx = (uint32_t)x * ST7305_RLCD_HEIGHT + y;
|
||
s_pixel_index_lut[pixel_idx] = (uint16_t)buffer_idx;
|
||
s_pixel_bit_lut[pixel_idx] = bit_mask;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ===========================================================
|
||
* 对外 API 实现
|
||
* =========================================================== */
|
||
|
||
esp_err_t st7305_rlcd_init(void)
|
||
{
|
||
esp_err_t ret;
|
||
|
||
ESP_LOGI(TAG, "Step 1: 配置 RST 引脚为输出");
|
||
gpio_config_t gpio_conf = {
|
||
.pin_bit_mask = (1ULL << ST7305_RLCD_PIN_RST),
|
||
.mode = GPIO_MODE_OUTPUT,
|
||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||
.intr_type = GPIO_INTR_DISABLE,
|
||
};
|
||
gpio_config(&gpio_conf);
|
||
|
||
ESP_LOGI(TAG, "Step 2: 初始化 SPI 总线 (SPI3, MOSI=%d, SCK=%d)",
|
||
ST7305_RLCD_PIN_MOSI, ST7305_RLCD_PIN_SCK);
|
||
spi_bus_config_t buscfg = {0};
|
||
buscfg.mosi_io_num = ST7305_RLCD_PIN_MOSI;
|
||
buscfg.miso_io_num = -1; /* 屏幕只写不读,不需要 MISO */
|
||
buscfg.sclk_io_num = ST7305_RLCD_PIN_SCK;
|
||
buscfg.quadwp_io_num = -1;
|
||
buscfg.quadhd_io_num = -1;
|
||
buscfg.max_transfer_sz = ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT; /* 一次传整屏 */
|
||
ret = spi_bus_initialize(ST7305_RLCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "SPI 总线初始化失败: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Step 3: 把屏幕挂到 SPI 总线上(用 esp_lcd panel io 框架)");
|
||
esp_lcd_panel_io_spi_config_t io_config = {0};
|
||
io_config.dc_gpio_num = ST7305_RLCD_PIN_DC;
|
||
io_config.cs_gpio_num = ST7305_RLCD_PIN_CS;
|
||
io_config.pclk_hz = ST7305_RLCD_SPI_HZ;
|
||
io_config.lcd_cmd_bits = 8;
|
||
io_config.lcd_param_bits = 8;
|
||
io_config.spi_mode = 0;
|
||
io_config.trans_queue_depth = 7;
|
||
ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)ST7305_RLCD_SPI_HOST,
|
||
&io_config, &s_io_handle);
|
||
if (ret != ESP_OK) {
|
||
ESP_LOGE(TAG, "panel io 创建失败: %s", esp_err_to_name(ret));
|
||
return ret;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Step 4: 分配显存到 PSRAM (%d 字节)", DISPLAY_BUFFER_LEN);
|
||
s_disp_buffer = (uint8_t *)heap_caps_malloc(DISPLAY_BUFFER_LEN, MALLOC_CAP_SPIRAM);
|
||
if (s_disp_buffer == NULL) {
|
||
ESP_LOGE(TAG, "显存分配失败(PSRAM?)");
|
||
return ESP_ERR_NO_MEM;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Step 5: 分配像素 LUT (index=%dKB, bit=%dKB)",
|
||
(ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT * 2) / 1024,
|
||
(ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT) / 1024);
|
||
uint32_t total_pixels = (uint32_t)ST7305_RLCD_WIDTH * ST7305_RLCD_HEIGHT;
|
||
s_pixel_index_lut = (uint16_t *)heap_caps_malloc(total_pixels * sizeof(uint16_t),
|
||
MALLOC_CAP_SPIRAM);
|
||
s_pixel_bit_lut = (uint8_t *)heap_caps_malloc(total_pixels * sizeof(uint8_t),
|
||
MALLOC_CAP_SPIRAM);
|
||
if (!s_pixel_index_lut || !s_pixel_bit_lut) {
|
||
ESP_LOGE(TAG, "LUT 分配失败(PSRAM?)");
|
||
return ESP_ERR_NO_MEM;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "Step 6: 构建横屏 LUT");
|
||
init_landscape_lut();
|
||
|
||
ESP_LOGI(TAG, "Step 7: 硬件复位 + 发送 ST7305 初始化序列");
|
||
hardware_reset();
|
||
init_display();
|
||
|
||
ESP_LOGI(TAG, "Step 8: 清屏(全白)");
|
||
st7305_rlcd_clear(ST7305_COLOR_WHITE);
|
||
st7305_rlcd_flush();
|
||
|
||
ESP_LOGI(TAG, "屏幕初始化完成 ✓");
|
||
return ESP_OK;
|
||
}
|
||
|
||
void st7305_rlcd_set_pixel(int x, int y, uint8_t color)
|
||
{
|
||
if (x < 0 || x >= ST7305_RLCD_WIDTH) return;
|
||
if (y < 0 || y >= ST7305_RLCD_HEIGHT) return;
|
||
|
||
/* O(1) 查 LUT */
|
||
uint32_t pixel_idx = (uint32_t)x * ST7305_RLCD_HEIGHT + y;
|
||
uint16_t buffer_idx = s_pixel_index_lut[pixel_idx];
|
||
uint8_t bit_mask = s_pixel_bit_lut[pixel_idx];
|
||
|
||
uint8_t *p = &s_disp_buffer[buffer_idx];
|
||
if (color == ST7305_COLOR_BLACK) {
|
||
*p &= ~bit_mask; /* 该位清零 = 像素亮(黑) */
|
||
} else {
|
||
*p |= bit_mask; /* 该位置 1 = 像素灭(白) */
|
||
}
|
||
}
|
||
|
||
void st7305_rlcd_flush(void)
|
||
{
|
||
/* 官方 RLCD_Display:每次刷新都重新设置地址窗口,然后发整屏数据 */
|
||
send_command(0x2A); /* Column Address Set */
|
||
send_data(0x12);
|
||
send_data(0x2A);
|
||
|
||
send_command(0x2B); /* Page/Row Address Set */
|
||
send_data(0x00);
|
||
send_data(0xC7);
|
||
|
||
send_command(0x2C); /* Memory Write */
|
||
send_buffer(s_disp_buffer, DISPLAY_BUFFER_LEN);
|
||
}
|
||
|
||
void st7305_rlcd_clear(uint8_t color)
|
||
{
|
||
memset(s_disp_buffer, color, DISPLAY_BUFFER_LEN);
|
||
}
|
||
|
||
int st7305_rlcd_get_width(void) { return ST7305_RLCD_WIDTH; }
|
||
int st7305_rlcd_get_height(void) { return ST7305_RLCD_HEIGHT; }
|