本系列为基于百问网AvaotaF1 V821开发套件的全志V821芯片音视频学习笔记,据统计该芯片目前广泛应用于智能眼镜系统。
V821开发学习笔记(二)—— SDK固件烧录及SmartIPC Demo演示
本章主要内容为移植SPI LCD显示驱动LCD_FB,驱动1.8' 128x160 RGB TFT LCD,屏幕驱动IC为ST7735。
先看一眼最终效果:

本文大概预览:
前序概念
LCD_FB驱动介绍、时序与信号
驱动移植
源码结构、编写驱动、对接驱动框架、配置设备树、编译测试
LVGL
彩蛋
LCD_FB 驱动是 SDK 中基于 SPI(MIPI DBI Type-C)接口的显示驱动方案,其上层对接到 Linux framebuffer驱动,开辟一块内存区域用于存储显示内容的数据,屏幕的每个像素对应于内存中的一个值。下端对接到 SPI 驱动,通过 SPI 与显示屏通讯。
LCD_FB 所驱动的屏幕由于没有硬件格式转换,需要输入的图像数据格式符合 SPI 传输要求,即 RGB 数据格式。不支持直接显示 YUV 数据,需要应用层或中间层进行 YUV 到 RGB 的转换。
SPI LCD 一般用于小分辨率的 SPI 屏显示,该驱动支持使用 SPI 协议软件控制 DC 方式驱动 SPI 屏幕。
SPI LCD 驱动架构如下:

SPI 接口就是俗称的 4 线 SPI 模式,这是因为发送数据时需要额外借助DC线来区分命令和数据,与 SCLK,CS和SDA共四线。只支持 RGB565 模式。
Notes:
SPI 接口协议与 DBI 的 L4I1和L4I2四线模式时序一样,完全兼容,区别是 DBI 下 DC 脚的控制是硬件自动化控制,而 SPI 模式下需要 CPU 软件控制 DC 脚,由于是非硬件操作,所以刷图性能平均较低。一般 SPI 屏幕配置 DBI L4I1 模式即可。由硬件控制 DC 脚即可。有一些特殊的屏幕需要专门控制 DC 时序,此时才使用 SPI 模式,手动控制 DC 时序。
关于SPI时序这里不再展开介绍,时序图如下图所示:

RGB565是一种 16 位色深的像素格式,主要用于内存和带宽受限的嵌入式系统,它将红、绿、蓝三个通道打包进 16 个比特中:
总计 5 + 6 + 5 = 16 位,即 2 字节/像素。
因为人眼对绿色最敏感,所以绿色多 1 位,6 位绿色可以在有限空间内保留更多亮度细节。
送图数据如下时序图所示:

SPI LCD 驱动在内核中叫做 LCD FB 驱动。其源码如下所示:
v821@tina:~/Documents/tina-v821-v1.3/kernel/linux-5.4-ansc/bsp/drivers/video/sunxi/lcd_fb$ tree.├── dev_fb.c├── dev_fb.h├── dev_lcd_fb.c├── dev_lcd_fb.h├── disp_display.c├── disp_display.h├── disp_lcd.c├── disp_lcd.h├── include.h├── Kconfig├── lcd_fb_feature.h├── lcd_fb_intf.c├── lcd_fb_intf.h├── lcd_fb_ion_mem.c├── lcd_fb_ion_mem.h├── logo.c├── logo.h├── Makefile└── panels ├── ch13613.c ├── ch13613.h ├── Kconfig ├── kld2844b.c ├── kld2844b.h ├── kld35512.c ├── kld35512.h ├── kld39501.c ├── kld39501.h ├── lcd_source.h ├── Makefile ├── nv3029s.c ├── nv3029s.h ├── panels.c ├── panels.h ├── spi_panel.c ├── st7735.c ├── st7735.h ├── st77916.c └── st77916.hSPI LCD 源码包括驱动源码与 SPI 屏幕驱动。其中屏幕驱动位于 panels 文件夹内。新增一款屏幕需要在 panels 文件夹中添加新屏幕驱动。(这里我们st7735.c/h就是我们新增的驱动文件)
ST7735是比较常用的一款驱动IC,网上有很多开源的驱动文件,也可以问屏幕厂商获取,主要是要配置正确的屏幕初始化序列。(篇幅问题,这里就只贴出部分源码,有需要的可以联系分享)/* 初始化序列 */staticvoidLCD_panel_init(unsignedint sel){structsunxi_lcd_fb_disp_panel_parainfo;memset(&info, 0, sizeof(info));if (sunxi_lcd_fb_bsp_disp_get_panel_info(sel, &info)) { LCDFB_WRN("get panel info fail!\n");return; } else { pr_info("st7735s %d*%d: Init start!\n", info.lcd_x, info.lcd_y); }/* 硬件复位 */ RESET(sel, 0); sunxi_lcd_delay_ms(100); RESET(sel, 1); sunxi_lcd_delay_ms(100);/* 0x11: Sleep out *//* 0xB1: Frame Rate Control 1 *//* 0xB2: Frame Rate Control 2 *//* 0xB3: Frame Rate Control 3 *//* 0xB4: Dot inversion *//* 0xC0: Power Control 1 *//* 0xC1: Power Control 2 *//* 0xC2: Power Control 3 *//* 0xC3: Power Control 4 *//* 0xC4: Power Control 5 *//* 0xC5: VCOM *//* 0x36: MADCTL *//* 0xE0: Gamma + *//* 0xE1: Gamma - *//* 0x3A: 65k mode *//* 设置全屏窗口 */ address(sel, 0, 0, info.lcd_x - 1, info.lcd_y - 1);/* 0x29: Display on */ sunxi_lcd_cmd_write_dc(sel, 0x29); sunxi_lcd_cmd_write_dc(sel, 0x2c); /* Display ON */ printk(KERN_INFO "ST7735S: Init done!\n"); }选择一个现成的 SPI LCD 改写即可,这里选择 nv3029s.c 驱动来修改。复制这两个驱动,重命名为 st7735.c
#include"st7735.h"#include<linux/gpio.h>#include<linux/of_gpio.h>staticvoidLCD_power_on(u32 sel);staticvoidLCD_power_off(u32 sel);staticvoidLCD_bl_open(u32 sel);staticvoidLCD_bl_close(u32 sel);staticvoidLCD_panel_init(u32 sel);staticvoidLCD_panel_exit(u32 sel);staticvoidaddress(unsignedint sel, int x, int y, int width, int height){ sunxi_lcd_cmd_write_dc(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_dc(sel, 0x2A); /* Set column 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);}...staticintlcd_set_addr_win(unsignedint sel, int x, int y, int width,int height){ address(sel, x, y, width, height); sunxi_lcd_cmd_write_dc(sel, 0x2c); /* Display ON */return0;}struct __lcd_panelst7735_panel = { .name = "st7735", .func = { .cfg_open_flow = LCD_open_flow, .cfg_close_flow = LCD_close_flow, .blank = lcd_blank, .set_var = lcd_set_var, .set_addr_win = lcd_set_addr_win, },};#ifndef __ST7735_PANEL_H__#define __ST7735_PANEL_H__#include"panels.h"externstruct __lcd_panelst7735_panel;#endif修改源码同层Kconfig文件,增加st7735的配置:
config LCD_SUPPORT_ST7735 bool "LCD support ST7735 panel" default nhelp If you want to support ST7735 panel for display driver, select it.修改源码同层panels.c文件,在panel_array里增加st7735驱动的引用:
#if IS_ENABLED(CONFIG_LCD_SUPPORT_ST7735) &st7735_panel,#endif修改源码同层panels.h文件,增加st7735驱动的引用:
#if IS_ENABLED(CONFIG_LCD_SUPPORT_ST7735)externstruct __lcd_panelst7735_panel;#endif修改源码同层Makefile文件,增加st7735编译选项:
lcd_fb-${CONFIG_LCD_SUPPORT_ST7735} += panels/st7735.o进入内核驱动配置界面,勾选新增的st7735驱动:
Allwinner BSP ---> Device Drivers ---> Video Drivers ---> SPI LCD Panel Drivers ---> <*> LCD FB Driver Support (SPI LCD) LCD FB Panels select ---> [*] LCD support st7735 panel
开发板SPI引脚示意图:

开发板与LCD连线示意图:

设备树配置如下:
&pio { pwm5_pins_active: pwm5@0 { pins = "PD6"; function = "pwm0_5"; }; pwm5_pins_sleep: pwm5@1 { pins = "PD6"; function = "gpio_in"; bias-pull-down; }; spi2_pins_default: spi2@0 { pins = "PD1", "PD2", "PD3"; /* CS, SCK, SDA */ function = "spi2"; allwinner,drive = <3>; }; spi2_pins_sleep: spi2@1 { 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_st7735_spi2>; }; };};&spi2 { pinctrl-0 = <&spi2_pins_default>; pinctrl-1 = <&spi2_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_st7735_spi2: slave@0 { device_type = "spi-panel"; compatible = "allwinner,spi-panel"; reg = <0x0>; spi-max-frequency = <100000000>; lcd_used = <1>; lcd_driver_name = "st7735"; lcd_if = <0>; lcd_dbi_if = <0>; lcd_data_speed = <48>; lcd_x = <128>; lcd_y = <160>; lcd_pixel_fmt = <10>; lcd_dbi_fmt = <2>; lcd_rgb_order = <0>; lcd_width = <28>; lcd_height = <35>; lcd_pwm_used = <1>; lcd_pwm_ch = <5>; lcd_pwm_freq = <5000>; lcd_pwm_pol = <0>; 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"; };};lcd_fb 节点用于配置和管理 LCD 屏幕的显示功能。该节点通过 port 属性配置了两款不同的 SPI 接口屏幕,分别为 spi_panel0 和 spi_panel1。每个屏幕的配置都通过 endpoint 和 remote-endpoint 关联,指定了屏幕的接口和驱动信息。
spi 节点定义了 SPI 控制器使用的引脚,SPI 驱动方式、最大频率,其中定义了 Panel 的节点标识该 LCD 显示面板位于该 SPI 节点下。
panel_st7735_spi2节点定义了屏幕的物理尺寸,时序,接口,背光 PWM 等。
使用mp -j4编译打包,并使用烧录工具烧录到开发板,上电后可以看到屏幕亮起,使用如下命令测试屏幕,输出彩条测试:
echo 0 > /sys/class/lcd_fb/lcd_fb/attr/colorbar 显示黑屏,清零 FB 数据echo 1 > /sys/class/lcd_fb/lcd_fb/attr/colorbar 显示彩条,用于点屏测试LVGL是一个免费的开源图形库,提供了创建嵌入式 GUI 所需的一切,具有易于使用的图形元素,美观的视觉效果和低内存占用。Tina SDK 中移植了 LVGL 8.3.2 核心组件与 Demo,可以快速测试使用:
执行make menuconfig命令,配置如下:
Gui ---> Littlevgl ---> <*> lv_examples (lvgl官方demo) -*- lvgl-8.3.2 use sunxifb double buffer (使能双缓冲,解决撕裂问题) [ ] lvgl-8.3.2 use sunxifb cache (V821 不支持此选项) [ ] lvgl-8.3.2 use sunxifb g2d (V821 不支持此选项) [ ] lvgl-8.3.2 use sunxifb g2d rotate (使能g2d旋转功能,需要开启g2d) [ ] lvgl-8.3.2 use freetype (自动链接freetype) [ ] lvgl-8.3.2 use sunxifb direct mode (V821 不支持此选项)LVGL 源码位于 platform/thirdparty/gui/lvgl-8, 其框架如图:
由于我们的LCD不支持触摸功能,而默认代码中会初始化evdev导致lvgl初始化失败,所以这里需要注释掉platform/thirdparty/gui/lvgl-8/lv_examples/src/main.c如下代码:
// evdev_init();// static lv_indev_drv_t indev_drv;// lv_indev_drv_init(&indev_drv); /*Basic initialization*/// indev_drv.type =LV_INDEV_TYPE_POINTER; /*See below.*/// indev_drv.read_cb = evdev_read; /*See below.*/// /*Register the driver in LVGL and save the created input device object*/// lv_indev_t * evdev_indev = lv_indev_drv_register(&indev_drv);/*Create a Demo*/switch(atoi(argv[1])) {case0: lv_demo_widgets();break;case1: lv_demo_music();break;case2: lv_demo_benchmark();break;case3: lv_demo_keypad_encoder();break;case4: lv_demo_stress();break;default:free(buf); sunxifb_exit();return0; }可以看到,目前示例代码中支持0-4等5个测试demo,使用命令mp -j4编译打包并将镜像烧录至开发板,运行命令测试:
lv_examples 1
接下来,把Linux小企鹅和启动日志也显示到LCD吧,关于这个LCD屏幕,硬件上背光引脚竟然是上拉的,也就是说只要接了3V3的电源,屏幕就会亮起白色,这就导致上电后在boot阶段一直是白屏,这里也不想再去修改boot的配置了。
进入内核配置页面:
使能虚拟终端:

使能Bootup logo





修改初始化脚本/etc/init.d/rcS,直接进入LVGL应用:
echo '/usr/bin/lv_examples 3 &' >> /etc/init.d/rcS后面会介绍音频功能并添加其他外设,计划移植小智AI到V821开发板,感兴趣的可以继续关注。
参考:全志在线、百问网