跳到主要内容

I8080 8BIT 点亮 240x320 屏

注意芯片型号!

首先确认芯片型号是否为 V821L2-WXX,其他型号不支持 I8080 显示。

此次适配的 I8080 屏为 D240C2422V0,使用的是 I8080 进行驱动。

image-20250324162421417

引脚配置如下:

V821引脚复用功能TFT 模块
PD1—PD8LCD-D3—LCD-D12D0—D7
PD12LCD-VSYNCTE
PD11LCD-HSYNCRD
PD9LCD-CLKWR
PD10LCD-DEDC
PD14GPIO OUTPUTCS
PD19GPIO OUTPUTRESET

编写 LCD 显示屏驱动

获取屏幕初始化序列

首先询问屏厂提供初始化序列,和屏幕手册。

image-20250324124555630

屏厂会提供一个初始化代码

RES=1;
Delayms(5);
RES=0;
Delayms(10);
RES=1;
Delayms(120);
//************* Start Initial Sequence **********//
Writecom(0x11);
Delayms(120); //ms
Writecom(0x36);
Writedat(0x00);
Writecom(0x3A);
Writedat(0x55);
Writecom(0xB2);
Writedat(0x0C);
Writedat(0x0C);
Writedat(0x00);
Writedat(0x33);
Writedat(0x33);
Writecom(0xB7);
Writedat(0x56);
Writecom(0xBB);
Writedat(0x20);
Writecom(0xC0);
Writedat(0x2C);
Writecom(0xC2);
Writedat(0x01);
Writecom(0xC3);
Writedat(0x0F);
Writecom(0xC4);
Writedat(0x20);
Writecom(0xC6);
Writedat(0x0F);
Writecom(0xD0);
Writedat(0xA4);
Writedat(0xA1);
Writecom(0xD6);
Writedat(0xA1);
Writecom(0xE0);
Writedat(0xF0);
Writedat(0x00);
Writedat(0x06);
Writedat(0x06);
Writedat(0x07);
Writedat(0x05);
Writedat(0x30);
Writedat(0x44);
Writedat(0x48);
Writedat(0x38);
Writedat(0x11);
Writedat(0x10);
Writedat(0x2E);
Writedat(0x34);
Writecom(0xE1);
Writedat(0xF0);
Writedat(0x0A);
Writedat(0x0E);
Writedat(0x0D);
Writedat(0x0B);
Writedat(0x27);
Writedat(0x2F);
Writedat(0x44);
Writedat(0x47);
Writedat(0x35);
Writedat(0x12);
Writedat(0x12);
Writedat(0x2C);
Writedat(0x32);
Writecom(0x21);
Writecom(0x29);

编写屏幕驱动

选择一个现成的 LCD 驱动改写即可,这里选择 st7789v_cpu.c 驱动来修改。

找到初始化序列的部分 static void lcd_panel_st7789v_init(u32 sel, struct disp_panel_para *info)

image-20250324125049987

把原来的初始化序列删除,然后将屏厂提供的初始化序列复制进来。

屏厂函数LCD框架接口
Writecomsunxi_lcd_cpu_write_index
Writedatsunxi_lcd_cpu_write_data
Delaymssunxi_lcd_delay_ms

image-20250324125426100

配置屏幕使用 TE 模式

由于屏幕支持 TE,这里可以配置使用 TE 模式,参考屏幕驱动 IC 手册,查看 TE 启用的寄存器,或者询问屏厂。针对 ST7789V2 IC 驱动,其开启 TE 的寄存器是 35H

image-20250324162737101

配置 TE 时钟的寄存器是 44H

image-20250324162839491

这里配置启用 TE,并且设置 TE 时钟为60Hz。在初始化中写入启用 TE 功能的如下代码

#if defined(CPU_TRI_MODE)
/* enable te, mode 0 */
sunxi_lcd_cpu_write_index(0, 0x35);
sunxi_lcd_cpu_write_data(0, 0x00);

sunxi_lcd_cpu_write_index(0, 0x44);
sunxi_lcd_cpu_write_data(0, 0x00);
sunxi_lcd_cpu_write_data(0, 0x80);
#endif

image-20250324162618638

配置屏幕开启

针对 i8080 屏幕,还需要写入 21H,29H开显示之后,还需要开启 GRAM 写入,对应寄存器是 2CH,一般屏厂会在初始化中提供,但是也有屏厂初始化中不提供这个指令。

image-20250324163125265

如果出现这样屏幕可以亮但是花屏,且送显示没有反应的情况(如下图)

image-20250324163420055

可以尝试增加开启 GRAM 写入的指令到初始化序列最后一行:

sunxi_lcd_cpu_write_index(0, 0x2c);

image-20250324163248222

完整屏幕驱动

完整的屏幕驱动如下:

#include "st7789v_cpu.h"

#define CPU_TRI_MODE

#define DBG_INFO(format, args...) \
(printk("[ST7789V LCD INFO] LINE:%04d-->%s:" format, __LINE__, \
__func__, ##args))
#define DBG_ERR(format, args...) \
(printk("[ST7789V LCD ERR] LINE:%04d-->%s:" format, __LINE__, \
__func__, ##args))
#define panel_reset(val) sunxi_lcd_gpio_set_value(sel, 0, val)
#define lcd_cs(val) sunxi_lcd_gpio_set_value(sel, 1, val)

static void lcd_panel_st7789v_init(u32 sel, struct disp_panel_para *info);
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);

static void LCD_cfg_panel_info(struct panel_extend_para *info)
{
#if defined(__DISP_TEMP_CODE__)
u32 i = 0, j = 0;
u32 items;
u8 lcd_gamma_tbl[][2] = {
/* {input value, corrected value} */
{ 0, 0 }, { 15, 15 }, { 30, 30 }, { 45, 45 },
{ 60, 60 }, { 75, 75 }, { 90, 90 }, { 105, 105 },
{ 120, 120 }, { 135, 135 }, { 150, 150 }, { 165, 165 },
{ 180, 180 }, { 195, 195 }, { 210, 210 }, { 225, 225 },
{ 240, 240 }, { 255, 255 },
};

u32 lcd_cmap_tbl[2][3][4] = {
{
{ LCD_CMAP_G0, LCD_CMAP_B1, LCD_CMAP_G2, LCD_CMAP_B3 },
{ LCD_CMAP_B0, LCD_CMAP_R1, LCD_CMAP_B2, LCD_CMAP_R3 },
{ LCD_CMAP_R0, LCD_CMAP_G1, LCD_CMAP_R2, LCD_CMAP_G3 },
},
{
{ LCD_CMAP_B3, LCD_CMAP_G2, LCD_CMAP_B1, LCD_CMAP_G0 },
{ LCD_CMAP_R3, LCD_CMAP_B2, LCD_CMAP_R1, LCD_CMAP_B0 },
{ LCD_CMAP_G3, LCD_CMAP_R2, LCD_CMAP_G1, LCD_CMAP_R0 },
},
};

items = sizeof(lcd_gamma_tbl) / 2;
for (i = 0; i < items - 1; i++) {
u32 num = lcd_gamma_tbl[i + 1][0] - lcd_gamma_tbl[i][0];

for (j = 0; j < num; j++) {
u32 value = 0;

value = lcd_gamma_tbl[i][1] +
((lcd_gamma_tbl[i + 1][1] -
lcd_gamma_tbl[i][1]) *
j) / num;
info->lcd_gamma_tbl[lcd_gamma_tbl[i][0] + j] =
(value << 16) + (value << 8) + value;
}
}
info->lcd_gamma_tbl[255] = (lcd_gamma_tbl[items - 1][1] << 16) +
(lcd_gamma_tbl[items - 1][1] << 8) +
lcd_gamma_tbl[items - 1][1];

memcpy(info->lcd_cmap_tbl, lcd_cmap_tbl, sizeof(lcd_cmap_tbl));
#endif
}

static s32 LCD_open_flow(u32 sel)
{
LCD_OPEN_FUNC(sel, LCD_power_on, 120);
#ifdef CPU_TRI_MODE
LCD_OPEN_FUNC(sel, LCD_panel_init, 100);
LCD_OPEN_FUNC(sel, sunxi_lcd_tcon_enable, 50);
#else
LCD_OPEN_FUNC(sel, sunxi_lcd_tcon_enable, 100);
LCD_OPEN_FUNC(sel, LCD_panel_init, 50);
#endif
LCD_OPEN_FUNC(sel, LCD_bl_open, 0);

return 0;
}

static s32 LCD_close_flow(u32 sel)
{
LCD_CLOSE_FUNC(sel, LCD_bl_close, 20);
#ifdef CPU_TRI_MODE
LCD_CLOSE_FUNC(sel, sunxi_lcd_tcon_disable, 10);
LCD_CLOSE_FUNC(sel, LCD_panel_exit, 50);
#else
LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10);
LCD_CLOSE_FUNC(sel, sunxi_lcd_tcon_disable, 10);
#endif
LCD_CLOSE_FUNC(sel, LCD_power_off, 0);

return 0;
}

static void LCD_power_on(u32 sel)
{
/* config lcd_power pin to open lcd power0 */
sunxi_lcd_power_enable(sel, 0);
sunxi_lcd_pin_cfg(sel, 1);
}

static void LCD_power_off(u32 sel)
{
/* lcd_cs, active low */
lcd_cs(1);
sunxi_lcd_delay_ms(10);
/* lcd_rst, active hight */
panel_reset(1);
sunxi_lcd_delay_ms(10);

sunxi_lcd_pin_cfg(sel, 0);
/* config lcd_power pin to close lcd power0 */
sunxi_lcd_power_disable(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);
}

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);
}

/* static int bootup_flag = 0; */
static void LCD_panel_init(u32 sel)
{
struct disp_panel_para *info =
kmalloc(sizeof(struct disp_panel_para), GFP_KERNEL);

DBG_INFO("\n");
bsp_disp_get_panel_info(sel, info);
lcd_panel_st7789v_init(sel, info);

kfree(info);
return;
}

static void LCD_panel_exit(u32 sel)
{
sunxi_lcd_cpu_write_index(0, 0x28);
sunxi_lcd_cpu_write_index(0, 0x10);
}

static void lcd_panel_st7789v_init(u32 sel, struct disp_panel_para *info)
{
DBG_INFO("\n");
/* lcd_cs, active low */
lcd_cs(0);
sunxi_lcd_delay_ms(10);
panel_reset(1);
sunxi_lcd_delay_ms(20);
panel_reset(0);
sunxi_lcd_delay_ms(20);
panel_reset(1);
sunxi_lcd_delay_ms(120);
sunxi_lcd_cpu_write_index(0, 0x11);
sunxi_lcd_delay_ms(120);
sunxi_lcd_cpu_write_index(0, 0x36);
sunxi_lcd_cpu_write_data(0, 0x00);

sunxi_lcd_cpu_write_index(0, 0x3A);
sunxi_lcd_cpu_write_data(0, 0x55);

sunxi_lcd_cpu_write_index(0, 0xB2);
sunxi_lcd_cpu_write_data(0, 0x0C);
sunxi_lcd_cpu_write_data(0, 0x0C);
sunxi_lcd_cpu_write_data(0, 0x00);
sunxi_lcd_cpu_write_data(0, 0x33);
sunxi_lcd_cpu_write_data(0, 0x33);

sunxi_lcd_cpu_write_index(0, 0xB7);
sunxi_lcd_cpu_write_data(0, 0x56);

sunxi_lcd_cpu_write_index(0, 0xBB);
sunxi_lcd_cpu_write_data(0, 0x20);

sunxi_lcd_cpu_write_index(0, 0xC0);
sunxi_lcd_cpu_write_data(0, 0x2C);

sunxi_lcd_cpu_write_index(0, 0xC2);
sunxi_lcd_cpu_write_data(0, 0x01);

sunxi_lcd_cpu_write_index(0, 0xC3);
sunxi_lcd_cpu_write_data(0, 0x0F);

sunxi_lcd_cpu_write_index(0, 0xC4);
sunxi_lcd_cpu_write_data(0, 0x20);

sunxi_lcd_cpu_write_index(0, 0xC6);
sunxi_lcd_cpu_write_data(0, 0x0F);

sunxi_lcd_cpu_write_index(0, 0xD0);
sunxi_lcd_cpu_write_data(0, 0xA4);
sunxi_lcd_cpu_write_data(0, 0xA1);

sunxi_lcd_cpu_write_index(0, 0xD6);
sunxi_lcd_cpu_write_data(0, 0xA1);

sunxi_lcd_cpu_write_index(0, 0xE0);
sunxi_lcd_cpu_write_data(0, 0xF0);
sunxi_lcd_cpu_write_data(0, 0x00);
sunxi_lcd_cpu_write_data(0, 0x06);
sunxi_lcd_cpu_write_data(0, 0x06);
sunxi_lcd_cpu_write_data(0, 0x07);
sunxi_lcd_cpu_write_data(0, 0x05);
sunxi_lcd_cpu_write_data(0, 0x30);
sunxi_lcd_cpu_write_data(0, 0x44);
sunxi_lcd_cpu_write_data(0, 0x48);
sunxi_lcd_cpu_write_data(0, 0x38);
sunxi_lcd_cpu_write_data(0, 0x11);
sunxi_lcd_cpu_write_data(0, 0x10);
sunxi_lcd_cpu_write_data(0, 0x2E);
sunxi_lcd_cpu_write_data(0, 0x34);

sunxi_lcd_cpu_write_index(0, 0xE1);
sunxi_lcd_cpu_write_data(0, 0xF0);
sunxi_lcd_cpu_write_data(0, 0x0A);
sunxi_lcd_cpu_write_data(0, 0x0E);
sunxi_lcd_cpu_write_data(0, 0x0D);
sunxi_lcd_cpu_write_data(0, 0x0B);
sunxi_lcd_cpu_write_data(0, 0x27);
sunxi_lcd_cpu_write_data(0, 0x2F);
sunxi_lcd_cpu_write_data(0, 0x44);
sunxi_lcd_cpu_write_data(0, 0x47);
sunxi_lcd_cpu_write_data(0, 0x35);
sunxi_lcd_cpu_write_data(0, 0x12);
sunxi_lcd_cpu_write_data(0, 0x12);
sunxi_lcd_cpu_write_data(0, 0x2C);
sunxi_lcd_cpu_write_data(0, 0x32);

#if defined(CPU_TRI_MODE)
/* enable te, mode 0 */
sunxi_lcd_cpu_write_index(0, 0x35);
sunxi_lcd_cpu_write_data(0, 0x00);

sunxi_lcd_cpu_write_index(0, 0x44);
sunxi_lcd_cpu_write_data(0, 0x00);
sunxi_lcd_cpu_write_data(0, 0x80);
#endif

sunxi_lcd_cpu_write_index(0, 0x21);
sunxi_lcd_cpu_write_index(0, 0x29);
sunxi_lcd_cpu_write_index(0, 0x2c);
}

/* panel driver name, must mach the name of lcd_drv_name in sys_config.fex */
struct __lcd_panel st7789v_cpu_panel = {
.name = "st7789v_cpu",
.func = {
.cfg_panel_info = LCD_cfg_panel_info,
.cfg_open_flow = LCD_open_flow,
.cfg_close_flow = LCD_close_flow,
},
};

配置屏幕驱动

进入内核,勾选 ST7789V_CPU 驱动。

image-20250324123059225

编辑设备树,配置屏幕时序与引脚

&disp {
disp_init_enable = <1>;
disp_mode = <0>;

screen0_output_type = <1>;
screen0_output_mode = <4>;
screen0_to_lcd_index = <0>;

screen1_output_type = <3>;
screen1_output_mode = <10>;
screen1_to_lcd_index = <2>;

screen1_output_format = <0>;
screen1_output_bits = <0>;
screen1_output_eotf = <4>;
screen1_output_cs = <257>;
screen1_output_dvi_hdmi = <2>;
screen1_output_range = <2>;
screen1_output_scan = <0>;
screen1_output_aspect_ratio = <8>;

fb_format = <0>;
fb_num = <1>;
fb_debug = <0>;
/*<disp channel layer zorder>*/
fb0_map = <0 0 0 16>;
fb0_width = <240>;
fb0_height = <320>;
/*<disp channel layer zorder>*/
fb1_map = <0 2 0 16>;
fb1_width = <300>;
fb1_height = <300>;
/*<disp channel layer zorder>*/
fb2_map = <1 0 0 16>;
fb2_width = <1280>;
fb2_height = <720>;
/*<disp channel layer zorder>*/
fb3_map = <1 1 0 16>;
fb3_width = <300>;
fb3_height = <300>;

chn_cfg_mode = <1>;
disp_para_zone = <1>;
};

&lcd0 {
lcd_used = <1>;

lcd_driver_name = "st7789v_cpu";
lcd_if = <1>;

lcd_x = <240>;
lcd_y = <320>;
lcd_width = <43>;
lcd_height = <63>;

lcd_dclk_freq = <20>;

lcd_hbp = <20>;
lcd_ht = <298>;
lcd_hspw = <10>;
lcd_vbp = <8>;
lcd_vt = <336>;
lcd_vspw = <2>;

lcd_backlight = <50>;
lcd_pwm_used = <1>;
lcd_pwm_ch = <4>;
lcd_pwm_freq = <50000>;
lcd_pwm_pol = <1>;
lcd_pwm_max_limit = <255>;
lcd_bright_curve_en = <1>;

lcd_frm = <2>;
lcd_gamma_en = <0>;
lcd_bright_curve_en = <0>;
lcd_cmap_en = <0>;

lcdgamma4iep = <22>;
lcd_cpu_mode = <1>;
lcd_cpu_te = <2>;
lcd_cpu_if = <14>;

/* rst */
lcd_gpio_0 = <&pio PD 19 GPIO_ACTIVE_LOW>;
/* cs */
lcd_gpio_1 = <&pio PD 14 GPIO_ACTIVE_LOW>;

pinctrl-0 = <&rgb8_pins_a>, <&rgb8_pins_ctl_a>;
pinctrl-1 = <&rgb8_pins_b>, <&rgb8_pins_ctl_b>;
};

测试屏幕

使用命令查看 TCON 彩条,这个彩条是由 TCON 发出的,操作更加底层。

echo 1 > /sys/class/disp/disp/attr/colorbar

image-20250324163915645

使用命令查看 DE 彩条,这个彩条是由 DE 合成发出的,如果 DE 出问题则可以看到 TCON 彩条看不到 DE 彩条。

提示

如果刚才开了 TCON 彩条,记得先关一下,TCON 彩条优先级更高

echo 0 > /sys/class/disp/disp/attr/colorbar
echo 8 > /sys/class/disp/disp/attr/colorbar

image-20250324164027198

FAQ

I8080接口显示抖动有花纹

  1. 改大时钟管脚的管脚驱动能力,改大。

黑屏无显示

黑屏,没有屏幕信号输出

注意芯片型号!

首先确认芯片型号是否为 V821L2-WXX,其他型号不支持 I8080 显示。

完全黑屏,背光也没有

  1. 屏驱动添加失败。驱动没有加载屏驱动,导致背光电源相关函数没有运行到。
  2. 屏驱动加载成功,但是没有执行到开背光函数(可以在uboot的屏驱动中加打印确认开屏流程的执行情况)。这时候大概率是屏驱动的开屏函数没有执行完,uboot就执行完毕进入内核了。需要在满足屏手册上电时序要求的情况下,尽量减少延迟。
  3. PWM 配置和背光电路的问题,另外就是直接测量下硬件测量下相关管脚和电压,再检查屏是否初始化成功。

黑屏但是有背光

  1. 没送图层。如果应用没有送任何图层那么表现的现象就是黑屏。
  2. SoC端的显示接口模块没有供电。SoC端模块没有供电自然无法传输视频信号到屏上。
  3. 复位脚没有复位。如果有复位脚,请确保硬件连接正确,确保复位脚的复位操作有放到屏驱动中。
  4. board.dtslcd0有严重错误。第一个是 lcdtiming搞错了,请严格按照屏手册中的提示来写。第二个就是,接口类型搞错。
  5. 屏的初始化命令不对。包括各个步骤先后顺序,延时等,这个时候请找屏厂确认初始化命令。