SPI 驱动 ST7789V 1.3 寸 LCD
SPI 接口协议与 DBI 的 L4I1
和 L4I2
四线模式时序一样,完全兼容,区别是 DBI 下 DC 脚的控制是硬件自动化控制,而 SPI 模式下需要 CPU 软件控制 DC 脚,由于是非硬件操作,所以刷图性能平均较低。一般 SPI 屏幕配置 DBI L4I1 模式即可。由硬件控制 DC 脚即可。
此次适配的SPI屏为 ZJY130S0800TG01
,使用的是 SPI 进行驱动。
引脚配置如下:
V821 | TFT 模块 |
---|---|
PD1 | CS |
PD2 | SCL |
PD3 | SDA |
3V3 | BLK |
PD4 | RES |
PD5 | DC |
3V3 | VCC |
GND | GND |
驱动配置
设置 SPI 驱动
勾选以下驱动:
Allwinner BSP --->
Device Drivers --->
SPI NG Drivers --->
<*> SPI NG Driver Support for Allwinner SoCs
[*] Support driver bit-aligned feature
[*] Support driver dbi feature
[*] Support driver camera feature
[*] Support atomic xfer function
编写 SPI LCD 显示屏驱动
获取屏幕初始化序列
首先询问屏厂提供驱动源码
找到 LCD 的初始化序列代码
找到屏幕初始化的源码
整理后的初始化代码如下:
LCD_WR_REG(0x11); // Sleep out
delay_ms(120); // Delay 120ms
//************* Start Initial Sequence **********//
LCD_WR_REG(0x36);
LCD_WR_DATA8(0x00);
LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);
LCD_WR_REG(0xB2);
LCD_WR_DATA8(0x1F);
LCD_WR_DATA8(0x1F);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x33);
LCD_WR_REG(0xB7);
LCD_WR_DATA8(0x35);
LCD_WR_REG(0xBB);
LCD_WR_DATA8(0x20); // 2b
LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x2C);
LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x01);
LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x01);
LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x18); // VDV, 0x20:0v
LCD_WR_REG(0xC6);
LCD_WR_DATA8(0x13); // 0x13:60Hz
LCD_WR_REG(0xD0);
LCD_WR_DATA8(0xA4);
LCD_WR_DATA8(0xA1);
LCD_WR_REG(0xD6);
LCD_WR_DATA8(0xA1); // sleep in后,gate输出为GND
LCD_WR_REG(0xE0);
LCD_WR_DATA8(0xF0);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x3C);
LCD_WR_DATA8(0x36);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x29);
LCD_WR_DATA8(0x30);
LCD_WR_REG(0xE1);
LCD_WR_DATA8(0xF0);
LCD_WR_DATA8(0x02);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x21);
LCD_WR_DATA8(0x25);
LCD_WR_DATA8(0x32);
LCD_WR_DATA8(0x3B);
LCD_WR_DATA8(0x38);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x27);
LCD_WR_DATA8(0x31);
LCD_WR_REG(0xE4);
LCD_WR_DATA8(0x1D); // 使用240根gate (N+1)*8
LCD_WR_DATA8(0x00); // 设定gate起点位置
LCD_WR_DATA8(0x00); // 当gate没有用完时,bit4(TMG)设为0
LCD_WR_REG(0x21);
LCD_WR_REG(0x29);
用现成驱动改写 SPI LCD 驱动
选择一个现成的 SPI LCD 改写即可,这里选择 nv3029s.c
驱动来修改。复制这两个驱动,重命名为 st7789v.c
先编辑 st7789v.h
将 nv3029s
改成 st7789v
#ifndef _ST7789V_H
#define _ST7789V_H
#include "panels.h"
struct __lcd_panel st7789v_panel;
#endif /*End of file*/
编辑 st7789v.c
将 nv3029s
改成 st7789v
编写初始化序列
先删除 static void LCD_panel_init(unsigned int sel)
中的初始化函数。
然后将屏厂提供的初始化序列复制进来
然后按照 spi_lcd
框架的接口改写驱动接口,具体接口如下
屏厂函数 | SPILCD框架接口 |
---|---|
LCD_WR_REG | sunxi_lcd_cmd_write |
LCD_WR_DATA8 | sunxi_lcd_para_write |
delay_ms | sunxi_lcd_delay_ms |
可以直接进行替换
完成后如下
然后对照屏厂提供的驱动修改 address
函数
做如下修改
static void address(unsigned int sel, int x, int y, int width, int height)
{
sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */
sunxi_lcd_para_write(sel, (y >> 8) & 0xff);
sunxi_lcd_para_write(sel, y & 0xff);
sunxi_lcd_para_write(sel, (height >> 8) & 0xff);
sunxi_lcd_para_write(sel, height & 0xff);
sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */
sunxi_lcd_para_write(sel, (x >> 8) & 0xff);
sunxi_lcd_para_write(sel, x & 0xff);
sunxi_lcd_para_write(sel, (width >> 8) & 0xff);
sunxi_lcd_para_write(sel, width & 0xff);
sunxi_lcd_cmd_write(sel, 0x2c);
}
完成驱动如下
#include "st7789v.h"
static void LCD_power_on(u32 sel);
static void LCD_power_off(u32 sel);
static void LCD_bl_open(u32 sel);
static void LCD_bl_close(u32 sel);
static void LCD_panel_init(u32 sel);
static void LCD_panel_exit(u32 sel);
#define RESET(s, v) sunxi_lcd_gpio_set_value(s, 0, v)
#define power_en(sel, val) sunxi_lcd_gpio_set_value(sel, 0, val)
static struct disp_panel_para info[LCD_FB_MAX];
static void address(unsigned int sel, int x, int y, int width, int height)
{
sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */
sunxi_lcd_para_write(sel, (y >> 8) & 0xff);
sunxi_lcd_para_write(sel, y & 0xff);
sunxi_lcd_para_write(sel, (height >> 8) & 0xff);
sunxi_lcd_para_write(sel, height & 0xff);
sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */
sunxi_lcd_para_write(sel, (x >> 8) & 0xff);
sunxi_lcd_para_write(sel, x & 0xff);
sunxi_lcd_para_write(sel, (width >> 8) & 0xff);
sunxi_lcd_para_write(sel, width & 0xff);
sunxi_lcd_cmd_write(sel, 0x2c);
}
static void LCD_panel_init(unsigned int sel)
{
if (bsp_disp_get_panel_info(sel, &info[sel])) {
lcd_fb_wrn("get panel info fail!\n");
return;
}
sunxi_lcd_cmd_write(sel, 0x11); // Sleep out
sunxi_lcd_delay_ms(120); // Delay 120ms
//************* Start Initial Sequence **********//
sunxi_lcd_cmd_write(sel, 0x36);
sunxi_lcd_para_write(sel, 0x00);
sunxi_lcd_cmd_write(sel, 0x3A);
sunxi_lcd_para_write(sel, 0x05);
sunxi_lcd_cmd_write(sel, 0xB2);
sunxi_lcd_para_write(sel, 0x1F);
sunxi_lcd_para_write(sel, 0x1F);
sunxi_lcd_para_write(sel, 0x00);
sunxi_lcd_para_write(sel, 0x33);
sunxi_lcd_para_write(sel, 0x33);
sunxi_lcd_cmd_write(sel, 0xB7);
sunxi_lcd_para_write(sel, 0x35);
sunxi_lcd_cmd_write(sel, 0xBB);
sunxi_lcd_para_write(sel, 0x20); // 2b
sunxi_lcd_cmd_write(sel, 0xC0);
sunxi_lcd_para_write(sel, 0x2C);
sunxi_lcd_cmd_write(sel, 0xC2);
sunxi_lcd_para_write(sel, 0x01);
sunxi_lcd_cmd_write(sel, 0xC3);
sunxi_lcd_para_write(sel, 0x01);
sunxi_lcd_cmd_write(sel, 0xC4);
sunxi_lcd_para_write(sel, 0x18); // VDV, 0x20:0v
sunxi_lcd_cmd_write(sel, 0xC6);
sunxi_lcd_para_write(sel, 0x13); // 0x13:60Hz
sunxi_lcd_cmd_write(sel, 0xD0);
sunxi_lcd_para_write(sel, 0xA4);
sunxi_lcd_para_write(sel, 0xA1);
sunxi_lcd_cmd_write(sel, 0xD6);
sunxi_lcd_para_write(sel, 0xA1); // sleep in后,gate输出为GND
sunxi_lcd_cmd_write(sel, 0xE0);
sunxi_lcd_para_write(sel, 0xF0);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x07);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x25);
sunxi_lcd_para_write(sel, 0x33);
sunxi_lcd_para_write(sel, 0x3C);
sunxi_lcd_para_write(sel, 0x36);
sunxi_lcd_para_write(sel, 0x14);
sunxi_lcd_para_write(sel, 0x12);
sunxi_lcd_para_write(sel, 0x29);
sunxi_lcd_para_write(sel, 0x30);
sunxi_lcd_cmd_write(sel, 0xE1);
sunxi_lcd_para_write(sel, 0xF0);
sunxi_lcd_para_write(sel, 0x02);
sunxi_lcd_para_write(sel, 0x04);
sunxi_lcd_para_write(sel, 0x05);
sunxi_lcd_para_write(sel, 0x05);
sunxi_lcd_para_write(sel, 0x21);
sunxi_lcd_para_write(sel, 0x25);
sunxi_lcd_para_write(sel, 0x32);
sunxi_lcd_para_write(sel, 0x3B);
sunxi_lcd_para_write(sel, 0x38);
sunxi_lcd_para_write(sel, 0x12);
sunxi_lcd_para_write(sel, 0x14);
sunxi_lcd_para_write(sel, 0x27);
sunxi_lcd_para_write(sel, 0x31);
sunxi_lcd_cmd_write(sel, 0xE4);
sunxi_lcd_para_write(sel, 0x1D); // 使用240根gate (N+1)*8
sunxi_lcd_para_write(sel, 0x00); // 设定gate起点位置
sunxi_lcd_para_write(sel, 0x00); // 当gate没有用完时,bit4(TMG)设为0
sunxi_lcd_cmd_write(sel, 0x21);
sunxi_lcd_cmd_write(sel, 0x29);
if (info[sel].lcd_x < info[sel].lcd_y)
address(sel, 0, 0, info[sel].lcd_x - 1, info[sel].lcd_y - 1);
else
address(sel, 0, 0, info[sel].lcd_y - 1, info[sel].lcd_x - 1);
}
static void LCD_panel_exit(unsigned int sel)
{
sunxi_lcd_cmd_write(sel, 0x28);
sunxi_lcd_delay_ms(20);
sunxi_lcd_cmd_write(sel, 0x10);
sunxi_lcd_delay_ms(20);
sunxi_lcd_pin_cfg(sel, 0);
}
static s32 LCD_open_flow(u32 sel)
{
lcd_fb_here;
/* open lcd power, and delay 50ms */
LCD_OPEN_FUNC(sel, LCD_power_on, 50);
/* open lcd power, than delay 200ms */
LCD_OPEN_FUNC(sel, LCD_panel_init, 200);
LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 50);
/* open lcd backlight, and delay 0ms */
LCD_OPEN_FUNC(sel, LCD_bl_open, 0);
return 0;
}
static s32 LCD_close_flow(u32 sel)
{
lcd_fb_here;
/* close lcd backlight, and delay 0ms */
LCD_CLOSE_FUNC(sel, LCD_bl_close, 50);
/* open lcd power, than delay 200ms */
LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10);
/* close lcd power, and delay 500ms */
LCD_CLOSE_FUNC(sel, LCD_power_off, 10);
return 0;
}
static void LCD_power_on(u32 sel)
{
/* config lcd_power pin to open lcd power0 */
lcd_fb_here;
power_en(sel, 1);
sunxi_lcd_power_enable(sel, 0);
sunxi_lcd_pin_cfg(sel, 1);
RESET(sel, 1);
sunxi_lcd_delay_ms(100);
RESET(sel, 0);
sunxi_lcd_delay_ms(100);
RESET(sel, 1);
}
static void LCD_power_off(u32 sel)
{
lcd_fb_here;
/* config lcd_power pin to close lcd power0 */
sunxi_lcd_power_disable(sel, 0);
power_en(sel, 0);
}
static void LCD_bl_open(u32 sel)
{
sunxi_lcd_pwm_enable(sel);
/* config lcd_bl_en pin to open lcd backlight */
sunxi_lcd_backlight_enable(sel);
lcd_fb_here;
}
static void LCD_bl_close(u32 sel)
{
/* config lcd_bl_en pin to close lcd backlight */
sunxi_lcd_backlight_disable(sel);
sunxi_lcd_pwm_disable(sel);
lcd_fb_here;
}
/* sel: 0:lcd0; 1:lcd1 */
static s32 LCD_user_defined_func(u32 sel, u32 para1, u32 para2, u32 para3)
{
lcd_fb_here;
return 0;
}
static int lcd_set_var(unsigned int sel, struct fb_info *p_info)
{
return 0;
}
static int lcd_set_addr_win(unsigned int sel, int x, int y, int width, int height)
{
address(sel, x, y, width, height);
return 0;
}
static int lcd_blank(unsigned int sel, unsigned int en)
{
return 0;
}
struct __lcd_panel st7789v_panel = {
/* panel driver name, must mach the name of lcd_drv_name in sys_config.fex
*/
.name = "st7789v",
.func = {
.cfg_open_flow = LCD_open_flow,
.cfg_close_flow = LCD_close_flow,
.lcd_user_defined_func = LCD_user_defined_func,
.blank = lcd_blank,
.set_var = lcd_set_var,
.set_addr_win = lcd_set_addr_win,
},
};
对接驱动框架
完成了屏幕驱动的编写,接下来需要对接到 SPILCD 驱动框架。首先编辑 Kconfig
,增加 st7789v
的配置
config LCD_SUPPORT_ST7789V
bool "LCD support st7789v panel"
default n
---help---
If you want to support st7789v panel for display driver, select it.
然后编辑 panels.c
在 panel_array
里增加 st7789
驱动的引用
如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V
&st7789v_panel,
#endif
之后编辑 panels.h
同样增加引用
如下图
#ifdef CONFIG_LCD_SUPPORT_ST7789V
extern struct __lcd_panel st7789v_panel;
#endif
最后编辑外层的 Makefile
增加编译选项,如下所示
obj-${CONFIG_LCD_SUPPORT_ST7789V} += panels/st7789v.o
选择驱动
进入内核驱动配置界面,勾选新增的驱动
Allwinner BSP --->
Device Drivers --->
Video Drivers --->
<*> Framebuffer implementaion without display hardware of AW
LCD fb panels select --->
[*] LCD support st7789v panel
配置设备树
&pio {
spi1_pins_default: spi1@0 {
pins = "PD1", "PD2", "PD3"; /* CS, SCK, SDA */
function = "spi1";
allwinner,drive = <3>;
};
spi1_pins_sleep: spi1@2 {
pins = "PD1", "PD2", "PD3";
function = "io_disabled";
};
};
&lcd_fb {
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
spi_panel0: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_st7789v_spi1>;
};
};
};
&spi1 {
pinctrl-0 = <&spi1_pins_default>;
pinctrl-1 = <&spi1_pins_sleep>;
pinctrl-names = "default", "sleep";
clock-frequency = <100000000>;
sunxi,spi-bus-mode = <SUNXI_SPI_BUS_MASTER>;
sunxi,spi-cs-mode = <SUNXI_SPI_CS_AUTO>;
status = "okay";
panel_st7789v_spi1: slave@0 {
device_type = "spi-panel";
compatible = "allwinner,spi-panel";
reg = <0x0>;
spi-max-frequency = <100000000>;
lcd_used = <1>;
lcd_driver_name = "st7789v";
lcd_if = <0>;
lcd_dbi_if = <0>;
lcd_data_speed = <48>;
lcd_x = <240>;
lcd_y = <240>;
lcd_pixel_fmt = <10>;
lcd_dbi_fmt = <2>;
lcd_rgb_order = <0>;
lcd_width = <60>;
lcd_height = <60>;
lcd_pwm_used = <0>;
lcd_pwm_ch = <6>;
lcd_pwm_freq = <5000>;
lcd_pwm_pol = <1>;
lcd_frm = <1>;
lcd_gamma_en = <1>;
fb_buffer_num = <2>;
lcd_backlight = <100>;
lcd_fps = <60>;
lcd_dbi_te = <0>;
lcd_dbi_clk_mode = <0>;
lcd_spi_dc_pin = <&pio PD 5 GPIO_ACTIVE_LOW>;
lcd_gpio_0 = <&pio PD 4 GPIO_ACTIVE_LOW>;
status = "okay";
};
};
功能测试
勾选 LVGL,运行测试
lv_examples 1
屏幕可以正常显示,帧率正确即可。
LVGL 颜色不对的时候记得配置
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 1