#include "st7789v2.h" #include "app_config.h" #include "asm/gpio.h" #include "device/device.h" #include "system/init.h" #include "app_log.h" #define LCD_SPI_NAME "spi2" #define LCD_RES_PIN IO_PORTA_01 #define LCD_CS_PIN IO_PORTA_02 #define LCD_DC_PIN IO_PORTA_05 #define LCD_BL_PIN IO_PORTA_07 /* PA6 是 TE 引脚,但当前版本暂未启用 TE 同步 */ #define LCD_WIDTH 240 #define LCD_HEIGHT 320 #define LCD_PIXEL_BYTES 3 #define LCD_LINE_BYTES (LCD_WIDTH * LCD_PIXEL_BYTES) #define LCD_CMD_SWRESET 0x01 #define LCD_CMD_SLPIN 0x10 #define LCD_CMD_SLPOUT 0x11 #define LCD_CMD_INVON 0x21 #define LCD_CMD_DISPOFF 0x28 #define LCD_CMD_DISPON 0x29 #define LCD_CMD_CASET 0x2A #define LCD_CMD_RASET 0x2B #define LCD_CMD_RAMWR 0x2C static void *lcd_spi_hdl; static u8 lcd_line_buf[LCD_LINE_BYTES]; static OS_MUTEX lcd_mutex; struct lcd_init_entry { u8 cmd; u8 len; u8 data[14]; }; /* * B7、BB、C0、C3、C4、E0、E1 与具体玻璃面板有关。 * 这里保留原工程参数,量产前必须与模组厂初始化表核对。 * * D6/A1 未列入公开 ST7789V2 命令表,因此不放入该序列。 */ static const struct lcd_init_entry lcd_init_seq[] = { {0x36, 1, {0x00}}, {0x3A, 1, {0x06}}, /* RGB666 */ {0xB2, 5, {0x0C, 0x0C, 0x00, 0x33, 0x33}}, {0xB7, 1, {0x46}}, {0xBB, 1, {0x1B}}, {0xC0, 1, {0x2C}}, {0xC2, 1, {0x01}}, {0xC3, 1, {0x0F}}, {0xC4, 1, {0x20}}, {0xC6, 1, {0x0F}}, {0xD0, 2, {0xA4, 0xA1}}, {0xE0, 14, { 0xD0, 0x06, 0x0B, 0x09, 0x08, 0x30, 0x30, 0x5B, 0x4B, 0x18, 0x14, 0x14, 0x2B, 0x31 }}, {0xE1, 14, { 0xD0, 0x05, 0x0A, 0x0A, 0x07, 0x28, 0x32, 0x2C, 0x49, 0x18, 0x13, 0x13, 0x2C, 0x33 }}, /* * Display inversion:面板行列驱动反相。 * 它不是简单的软件颜色取反。 */ {LCD_CMD_INVON, 0, {0x00}}, }; static int st7789v2_mutex_init(void) { return os_mutex_create(&lcd_mutex); } early_initcall(st7789v2_mutex_init); static int lcd_lock(void) { if (!os_mutex_valid(&lcd_mutex)) { return -EIO; } return os_mutex_pend(&lcd_mutex, 0); } static void lcd_unlock(void) { os_mutex_post(&lcd_mutex); } static void lcd_delay_ms(u32 ms) { u32 ticks; ticks = (ms * OS_TICKS_PER_SEC + 999U) / 1000U; if (ticks == 0) { ticks = 1; } os_time_dly(ticks); } static void lcd_gpio_output(u32 pin, int value) { gpio_direction_output(pin, value); } static void lcd_cs(int value) { lcd_gpio_output(LCD_CS_PIN, value); } static void lcd_dc(int value) { lcd_gpio_output(LCD_DC_PIN, value); } static void lcd_reset(int value) { lcd_gpio_output(LCD_RES_PIN, value); } static void lcd_backlight_on(void) { lcd_gpio_output(LCD_BL_PIN, 1); } static void lcd_backlight_off(void) { lcd_gpio_output(LCD_BL_PIN, 0); } static int lcd_spi_write_locked(const void *buf, u32 len) { int ret; if (!lcd_spi_hdl) { return -ENODEV; } if (len == 0) { return 0; } if (!buf) { return -EINVAL; } /* * 本 SDK 的 SPI dev_write() 使用 0 表示成功, * 非 0 表示失败。 */ ret = dev_write(lcd_spi_hdl, (void *)buf, len); return ret; } static int lcd_write_cmd_raw_locked(u8 cmd) { lcd_dc(0); return lcd_spi_write_locked(&cmd, 1); } static int lcd_write_data_raw_locked(const void *data, u32 len) { lcd_dc(1); return lcd_spi_write_locked(data, len); } /* * 一个完整命令事务: * CS 低 -> 命令 -> 可选参数 -> CS 高 */ static int lcd_send_command_locked(u8 cmd, const u8 *data, u32 len) { int ret; lcd_cs(0); ret = lcd_write_cmd_raw_locked(cmd); if (ret == 0 && len != 0) { ret = lcd_write_data_raw_locked(data, len); } lcd_cs(1); if (ret != 0) { APP_LOG("st7789v2: command 0x%02x failed, ret=%d\n", cmd, ret); } return ret; } static void lcd_hardware_reset_locked(void) { lcd_cs(1); lcd_dc(1); lcd_reset(1); lcd_delay_ms(10); lcd_reset(0); lcd_delay_ms(20); lcd_reset(1); lcd_delay_ms(120); } static int lcd_controller_init_locked(void) { int ret; u32 i; lcd_hardware_reset_locked(); ret = lcd_send_command_locked(LCD_CMD_SLPOUT, NULL, 0); if (ret != 0) { return ret; } lcd_delay_ms(120); for (i = 0; i < sizeof(lcd_init_seq) / sizeof(lcd_init_seq[0]); i++) { ret = lcd_send_command_locked( lcd_init_seq[i].cmd, lcd_init_seq[i].data, lcd_init_seq[i].len); if (ret != 0) { return ret; } } ret = lcd_send_command_locked(LCD_CMD_DISPON, NULL, 0); if (ret != 0) { return ret; } lcd_delay_ms(20); return 0; } /* * 调用本函数前 CS 必须已经拉低。 */ static int lcd_set_window_raw_locked(u16 x1, u16 y1, u16 x2, u16 y2) { int ret; u8 data[4]; data[0] = x1 >> 8; data[1] = x1 & 0xff; data[2] = x2 >> 8; data[3] = x2 & 0xff; ret = lcd_write_cmd_raw_locked(LCD_CMD_CASET); if (ret != 0) { return ret; } ret = lcd_write_data_raw_locked(data, sizeof(data)); if (ret != 0) { return ret; } data[0] = y1 >> 8; data[1] = y1 & 0xff; data[2] = y2 >> 8; data[3] = y2 & 0xff; ret = lcd_write_cmd_raw_locked(LCD_CMD_RASET); if (ret != 0) { return ret; } ret = lcd_write_data_raw_locked(data, sizeof(data)); if (ret != 0) { return ret; } return lcd_write_cmd_raw_locked(LCD_CMD_RAMWR); } static int lcd_fill_rgb_locked(u8 red, u8 green, u8 blue) { int ret; u32 x; u32 y; if (!lcd_spi_hdl) { return -ENODEV; } /* * COLMOD=0x06 时为 RGB666。 * 每个颜色分量只有高 6 bit 有效。 */ red &= 0xfc; green &= 0xfc; blue &= 0xfc; for (x = 0; x < LCD_WIDTH; x++) { lcd_line_buf[x * 3 + 0] = red; lcd_line_buf[x * 3 + 1] = green; lcd_line_buf[x * 3 + 2] = blue; } /* * 窗口设置和所有像素属于同一个 SPI 事务。 * 任意错误发生时,也必须恢复 CS 高电平。 */ lcd_cs(0); ret = lcd_set_window_raw_locked( 0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); if (ret == 0) { lcd_dc(1); for (y = 0; y < LCD_HEIGHT; y++) { ret = lcd_spi_write_locked( lcd_line_buf, sizeof(lcd_line_buf)); if (ret != 0) { break; } } } lcd_cs(1); return ret; } int st7789v2_open(void) { int ret; ret = lcd_lock(); if (ret != 0) { return ret; } if (lcd_spi_hdl) { lcd_unlock(); return 0; } lcd_backlight_off(); lcd_cs(1); lcd_dc(1); lcd_reset(1); lcd_spi_hdl = dev_open(LCD_SPI_NAME, NULL); if (!lcd_spi_hdl) { APP_LOG("st7789v2: dev_open(%s) failed\n", LCD_SPI_NAME); lcd_unlock(); return -ENODEV; } ret = lcd_controller_init_locked(); if (ret != 0) { goto open_failed; } /* * 背光打开前先清成黑色,避免显示未初始化显存。 * 1 MHz 下该操作约需要 1.84 秒。 */ ret = lcd_fill_rgb_locked(0, 0, 0); if (ret != 0) { goto open_failed; } lcd_backlight_on(); lcd_unlock(); APP_LOG("st7789v2: open success\n"); return 0; open_failed: lcd_backlight_off(); lcd_cs(1); dev_close(lcd_spi_hdl); lcd_spi_hdl = NULL; lcd_unlock(); APP_LOG("st7789v2: initialization failed, ret=%d\n", ret); return ret; } int st7789v2_close(void) { int ret; int tmp; ret = lcd_lock(); if (ret != 0) { return ret; } lcd_backlight_off(); if (!lcd_spi_hdl) { lcd_cs(1); lcd_unlock(); return 0; } ret = lcd_send_command_locked(LCD_CMD_DISPOFF, NULL, 0); lcd_delay_ms(20); tmp = lcd_send_command_locked(LCD_CMD_SLPIN, NULL, 0); if (ret == 0 && tmp != 0) { ret = tmp; } /* * 使用与 SDK 官方 SleepIn 类似的保守等待时间。 */ lcd_delay_ms(120); lcd_cs(1); tmp = dev_close(lcd_spi_hdl); lcd_spi_hdl = NULL; if (ret == 0 && tmp != 0) { ret = tmp; } lcd_unlock(); return ret; } int st7789v2_fill_rgb(u8 red, u8 green, u8 blue) { int ret; ret = lcd_lock(); if (ret != 0) { return ret; } ret = lcd_fill_rgb_locked(red, green, blue); lcd_unlock(); if (ret != 0) { APP_LOG("st7789v2: fill failed, ret=%d\n", ret); } return ret; }