超级版主
 
- 积分
- 5268
- 金钱
- 5268
- 注册时间
- 2019-5-8
- 在线时间
- 1340 小时
|
|
第二十一章 RGBLCD实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章将继续学习ESP_IDF的LCD外设驱动,主要学习RGB接口屏幕的驱动方法。本章用到的是ESP32-P4的 Camera-LCD控制器驱动RGBLCD屏幕,实现和RGBLCD屏之间的通信,实现ASCII字符、图形和彩色的显示。
本章分为如下几个小节:
21.1 RGBLCD和ESP32-P4的LCD模块介绍
21.2 硬件设计
21.3 程序设计
21.4 下载验证
21.1 RGBLCD和ESP32-P4的LCD模块介绍
21.1.1 RGBLCD简介
在第20章,我们已经介绍过TFTLCD液晶屏了,实际上RGBLCD也是TFTLCD,只是接口不同而已。接下来我们简单介绍一下RGBLCD的驱动。
(1)RGBLCD的信号线
RGBLCD的信号线如表21.1.1.1所示:
表21.1.1.1 RGBLCD信号线
一般RGB屏都有如表21.1.1.1所示的信号线,有24根颜色数据线(RGB各8根,即RGB888格式),这样可以表示最多1600W色,DE、VS、HS和CLK,用于控制数据传输。
像素同步时钟信号线LCD_CLK:液晶屏与外部使用同步通讯方式,以CLK信号作为同步时钟,在同步时钟的驱动下,每个时钟传输一个像素点数据。
水平同步信号线LCD_HSYNC:有时也被称为行同步信号,用于表示液晶屏一行像素数据的传输结束,每传输完成液晶屏的一行像素数据时,LCD_HSYNC发生电平跳变,如分辨率为800x480的显示屏(800列,480行),传输一帧的图像LCD_HSYNC的电平会跳变480次。
垂直同步信号线LCD_VSYNC:有时也被称为场同步信号,用于表示液晶屏一帧像素数据的传输结束,每传输完成一帧像素时,LCD_VSYNC会发生电平跳变。其中“帧”是图像的单位,一幅图像称为一帧,在液晶屏中,一帧指一个完整屏液晶像素点。
数据使能信号线DE:用于表示数据的有效性,当DE信号线为高电平时,RGB信号线表示的数据有效。
RGB数据线:用来传输颜色数据。
(2)RGBLCD的驱动模式
RGB屏一般有2种驱动模式:DE模式和HV模式。DE模式使用DE信号来确定有效数据(DE为高/低时,数据有效),而HV模式,则需要行同步信号HSYNC和场同步信号VSYNC,来表示扫描的行和列。
DE模式和HV模式的行扫描时序图(以800*480的LCD面板为例),如图21.1.1.1所示:
图21.1.1.1 DE/HV模式行扫描时序图
从图中可以看出,DE和HV模式,时序基本一样,DE模式需要提供DE信号(DEN),而HV模式,则无需DE信号。图中的HSD即HSYNC信号,用于行同步,注意:在DE模式下面,是可以不用HSYNC信号和VSYNC信号,即可以不接,液晶照样可以正常工作。在引脚不是太充足的情况下,可以选择使用DE模式。
图中的thpw为水平同步有效信号脉宽,用于表示一行数据的开始;thb为水平后廊,表示从水平有效信号开始,到有效数据输出之间的像素时钟个数;thfp为水平前廊,表示一行数据结束后,到下一个水平同步信号开始之前的像素时钟个数。
图21.1.1.1仅是一行数据的扫描,输出800个像素点数据,而液晶面板总共有480行,这就还需要一个垂直扫描时序图,如图21.1.1.2所示:
图21.1.1.2 垂直扫描时序图
图中的VSD就是垂直同步信号LCD_VSYNC,HSD就是水平同步信号LCD_HSYNC,DE为数据使能信号。如图可知,一个垂直扫描,刚好就是480个有效的DE脉冲信号,每一个DE时钟周期,扫描一行,总共扫描480行,完成一帧数据的显示。这就是800*480的LCD面板扫描时序,其他分辨率的LCD面板,时序类似。
图中的tvpw为垂直同步有效信号脉宽,用于表示一帧数据的开始;tvb为垂直后廊,表示垂直同步信号以后的无效行数,tvfp为垂直前廊,表示一帧数据输出结束后,到下一个垂直同步信号开始之前的无效行数;这几个时间在配置RGBLCD设备时序时,需要进行设置。
(3)正点原子 RGBLCD模块
正点原子目前提供五款 RGBLCD 模块:ATK-MD0430R-480272(4.3寸,480*272)、ATK-MD0430R-800480(4.3寸,800*480) ATK-MD0700R-800480(7寸,800*480)、 ATK-MD0700R-1024600(7 寸,1024*600)和ATK-MD1010R(10.1 寸,1280*800),这里我们以 ATK-MD0430R-800480为例,给大家介绍。该模块的接口原理图如图21.1.1.3 所示:
图21.1.1.3 RGBLCD模块对外接口原理图
图中 J1就是对外接口,是一个 40PIN 的 FPC 座( 0.5mm 间距),通过 FPC 线,可以连接到ESP32-P4开发板底板的RGB接口上面,从而实现和ESP32-P4的连接。该模块的接口十分完善,采用 RGB888 格式,并支持 DE&HV 模式,还支持触摸屏(电阻/电容)和背光控制。右侧的几个电阻,并不是都焊接的,而是可以用户自己选择。LCD_R7/G7/B7是用来设置RGBLCD的ID的, 由于 RGBLCD 没有读写寄存器,也就没有所谓的 ID, 所以在这里我们通过在模块上面,控制 R7/G7/B7 的上/下拉,来自定义 RGBLCD 模块的 ID,帮助主控芯片去判断当前 LCD 面板的分辨率和相关参数,以提高程序兼容性。这几个位的设置关系如表21.1.1.2 所示:
表21.1.1.2正点原子 RGBLCD模块ID对应关系
这样我们在程序里面,读取 LCD_R7/G7/B7,得到 M0:M2 的值,从而判断 RGBLCD 模块的型号,并执行不同的配置,即可实现不同 LCD模块的兼容。
更详细的RGBLCD相关内容,可以参考正点原子提供的《ATK-MD0430R模块用户手册_V1.0》和《ATK-MD0430R模块使用说明_V1.0》,在这两个手册中,会有LCD相关参数信息。
这里还要说明一下,市面上有一些RGBLCD屏幕,是带有三线SPI接口,需要通过SPI接口对其进行配置操作,才能让屏幕正常显示。而正点原子的RGBLCD屏幕是不需要通过SPI接口配置的,使用起来更加方便。
21.1.2 ESP32-P4的LCD模块简介
ESP32-P4的LCD_CAM控制器包括独立的LCD控制模块和Camera(摄像头)控制模块。其中LCD模块用于发送并行视频数据信号,其总线支持RGB、MOTO6800和I8080等接口时序。Camera模块用于接收并行视频数据信号,其总线支持DVP 8/16位并行输入模式。
本小节主要介绍LCD_CAM控制器的LCD模块-RGB接口部分内容。
首先我们通过分析LCD_CAM控制器的结构框图,了解一下其工作过程。结构框图如下图所示。
图21.1.2.1 LCD_CAM控制器结构框图
LCD控制模块(红色虚线以下)包含发送控制单元(LCD_Ctrl)、发送异步FIFO(Async TX FIFO)、LCD时钟生成模块(LCD_Clock Generator)和格式转换模块(RGB/YUV Converter)。
发送控制单元:用于控制LCD数据的发送。LCD数据从上图可知,可由GDMA取自内部或外部存储器。
发送异步FIFO:用于与外部设备进行交互。
LCD时钟生成模块:用于生成LCD_PCLK时钟。时钟源经过时钟生成模块处理后,生成LCD模块所需要的时钟LCD_PCLK,这一过程如下图所示。
图21.1.2.2 LCD模块时钟生成过程
LCD模块时钟由三个时钟源提供,分别是XTAL_CLK、PLL_F160M_CLK和APLL_CLK。可由HP_SYS_CLKRST_PERI_CLK_CTRL19_REG的HP_SYS_CLKRST_LCD_CLK_SRC_SEL位决定,0选择使用XTAL_CLK,1选择使用PLL_F160M_CLK,2选择使用APLL_CLK。
选择好时钟源后(即得到LCD_CLK_SRC),经过降频得到LCD_CLK,后面再经过分频最终得到LCD_PCLK,这三者的关系如下:
Note:
① 第一条公式中的N、b和a都是通过HP_SYS_CLKRST_PERI_CLK_CTRL110_REG寄存器进行配置的。N的取值范围为2≤N≤256,通过HP_SYS_CLKRST_LCD_CLK_DIV_NUM位进行配置。b是通过HP_SYS_CLKRST_LCD_CLK_DIV_NUMERATOR位进行配置,而a是通过HP_SYS_CLKRST_LCD_CLK_DIV_DENONMINATOR位进行配置。
② 第二条公式中的MO是通过LCD_CAM_LCD_CLOCK_REG寄存器进行配置的。MO的数值主要是由LCD_CAM_LCD_CLK_EQU_SYSCLK和LCD_CAM_LCD_CLKCNT_N去决定,当LCD_CAM_LCD_CLK_EQU_SYSCLK为1,MO为1;当LCD_CAM_LCD_CLK_EQU_SYSCLK为0时,MO为LCD_CAM_LCD_CLKCNT_N+1。
格式转换模块:用于各种格式的视频数据互相转换。在上图中,该部分是虚线框,也就是可不使用该模块。
Camera-LCD控制器的CAM和LCD接口都可通过GPIO交换矩阵灵活配置使用任意GPIO引脚。对于LCD模块的信号,即图21.1.2.1右下角部分,在这里也简单介绍一下,如下表所示。
表21.1.2.1 LCD模块信号描述
使用不同接口,使用的数据线会有差异。通过RGB接口去驱动正点原子的RGBLCD屏幕,没有用到LCD_CD和LCD_CS引脚,且数据总线引脚只用到16根(RGB565格式)。
LCD模块还支持数据格式控制,比如:
① 反转数据位顺序,LCD_DATA_out[x:0]变为LCD_DATA_out[0:x],即MSB与LSB切换。
② 反转数据字节顺序,在16bit模式下有效,{Byte1,Byte0}变为{Byte0,Byte1}
③ 两个字节反转位置,在8bit模式下有效,{Byte1}{Byte0}变为{Byte0}{Byte1}
假如一款LCD模块RGB565像素数据是先发送低位后发送高位,若硬件不支持反转数据字节顺序,那么要显示正常颜色,就得通过代码切换高低字节发送;若硬件支持反转数据字节顺序的功能,便可以提高LCD的显示效率。在SPILCD例程中,就有用到这个功能配置。
21.2 硬件设计
21.2.1 例程功能
使用ESP32-P4开发板的RGB接口来驱动RGB屏,RGBLCD模块的接口底板上,通过40P的FPC排线连接RGBLCD模块,实现RGBLCD模块的驱动和显示,下载成功后,按下复位之后,就可以看到RGBLCD模块不停的显示一些信息并不断切换底色。同时,屏幕上会显示LCD的ID。注意:若想支持正点原子的7寸1024*600 RGBLCD和10.1寸 1280*800 RGBLCD,则需要修改ESP-IDF v5.4版本的lcd_hal.c文件,该文件位于esp-idf-v5.4\components\hal目录下,然后找到lcd_hal_cal_pclk_freq函数屏蔽一下代码段。
21.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)RGBLCD
LCD_R3 - IO18 LCD_R4 - IO17 LCD_R5 - IO16
LCD_R6 - IO15 LCD_R7 - IO14
LCD_G2 - IO13 LCD_G3 - IO12 LCD_G4 - IO11
LCD_G5 - IO10 LCD_G6 - IO9 LCD_G7 - IO8
LCD_B3 - IO7 LCD_B4 - IO6 LCD_B5 - IO5
LCD_B6 - IO4 LCD_B7 - IO3
LCD_CLK - IO20 LCD_DE - IO22 LCD_BL - IO53
CT_RST - IO45 CT_INT - IO21
IIC_SCL - IO32 IIC_SDA - IO33
21.2.3 原理图
RGBLCD原理图,如下图所示。
图21.2.3.1 RGBLCD原理图
21.3 程序设计
21.3.1 LCD的IDF驱动
LCD外设驱动位于ESP-IDF下的components/esp_lcd目录下。要使用esp_lcd功能,需要导入一下头文件:
- <font size="3">#include "esp_lcd_panel_interface.h" /* LCD面板结构体类型 */</font>
- <font size="3">#include "esp_lcd_panel_io.h" /* 驱动芯片接收/发送命令,发送颜色数据等函数 */</font>
- <font size="3">#include "esp_lcd_panel_vendor.h" /* 包含LCD外设驱动支持的几款驱动芯片 */</font>
- <font size="3">#include "esp_lcd_panel_ops.h" /* LCD设备接口函数(reset/init/del等) */</font>
- <font size="3">#include "esp_lcd_panel_commands.h" /* LCD驱动芯片的命令 */</font>
复制代码 RGB LCD 驱动流程可大致分为三个部分:初始化接口设备、移植驱动组件和初始化 LCD 设备。
初始化接口设备
初始化接口设备需要先初始化总线,再创建接口设备。
对于仅有RGB接口的LCD,不支持传输命令及参数,所以不需要初始化接口设备。
对于具备SPI接口和RGB接口的LCD,这里就需要创建SPI接口设备,如第20章操作一样,这里不做展开。
初始化接口设备,主要就是为了上层接口能够调用到底层接口函数进行数据发送。
移植驱动组件
对于仅有RGB接口的LCD,RGB接口驱动已经通过注册回调函数的方式实现了结构体esp_lcd_panel_t中的各项功能,即后续通过结构体esp_lcd_panel_t的调用即可访问到RGB底层接口函数。在LCD驱动组件中还提供esp_lcd_new_rgb_panel函数,该函数用于创建数据类型为esp_lcd_panel_handle_t的LCD设备,esp_lcd_panel_handle_t和esp_lcd_panel_t实际上是一样的。通过esp_lcd_new_rgb_panel函数,便可以使得应用程序能够使用LCD通用API函数(初始化/画点/打开显示)去操作LCD设备。因此,这种LCD不需要移植驱动组件,直接可以跳到初始化LCD设备步骤。
对于具备SPI接口和RGB接口的LCD,初始化接口后,还需要通过SPI接口发送命令以及参数,如第20章操作一样,这里不做展开。
初始化LCD设备
1,为RGBLCD创建LCD面板esp_lcd_new_rgb_panel
该函数用于为RGBLCD创建LCD面板并对LCD设备配置,其函数原型如下:
- esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel)
复制代码 函数形参:
表21.3.1.1 esp_lcd_new_rgb_panel函数形参描述
函数返回值:
ESP_OK表示创建成功。
ESP_ERR_INVALID_ARG表示参数有误。
ESP_ERR_NO_MEM表示内存不足。
ESP_ERR_NOT_FOUND表示找不到硬件资源。
esp_panel_config为指向LCD设备配置结构体指针,esp_lcd_rgb_panel_config_t结构体中包含非常多成员,如下代码所示。
- typedef struct {
- lcd_clock_source_t clk_src; /* LCD模块时钟源 */
- esp_lcd_rgb_timing_t timings; /* RGBLCD时序参数*/
- size_t data_width; /* 数据位宽(RGB565格式即16位) */
- size_t bits_per_pixel; /* 帧缓冲区颜色深度(0时默认为data_width) */
- size_t num_fbs; /* 整屏帧缓冲区数目(0/1表示1个,最多3个) */
- size_t bounce_buffer_size_px; /* DRAM缓冲区供DMA使用(速度比PSRAM快) */
- size_t sram_trans_align; /* SRAM申请的缓冲区对齐 */
- size_t psram_trans_align; /* PSRAM申请的缓冲区对齐 */
- int hsync_gpio_num; /* LCD_HSYNC信号引脚 */
- int vsync_gpio_num; /* LCD_VSYNC信号引脚 */
- int de_gpio_num; /* LCD_DE信号引脚 */
- int pclk_gpio_num; /* LCD_PCLK信号引脚 */
- int disp_gpio_num; /* LCD_BL信号引脚 */
- int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; /* 数据线信号引脚(16个IO) */
- struct {
- uint32_t disp_active_low: 1; /* disp信号引脚的有效电平 */
- uint32_t refresh_on_demand: 1; /* 1时调用draw_bitmap函数刷新帧缓存区 */
- uint32_t fb_in_psram: 1; /* 帧缓冲区优先从PSRAM中分配 */
- uint32_t double_fb: 1; /* 分配两个整屏缓存区,与num_fbs一致效果 */
- uint32_t no_fb: 1; /* 不分配帧缓冲区,需手动在回调函数中处理 */
- uint32_t bb_invalidate_cache: 1; /* 对读取数据执行缓存无效操作,释放缓存 */
- } flags; /* RGBLCD配置标志 */
- } esp_lcd_rgb_panel_config_t;
复制代码 esp_lcd_rgb_panel_config_t结构体中涉及到一个比较重要的结构体esp_lcd_rgb_timing_t,该结构体主要就是对RGBLCD的时序参数做配置,如下代码所示。
- typedef struct {
- uint32_t pclk_hz; /* LCD_PCLK时钟频率 */
- uint32_t h_res; /* 水平分辨率,即一行中的像素数 */
- uint32_t v_res; /* 垂直分辨率,即帧中的行数 */
- uint32_t hsync_pulse_width; /* 水平同步宽度,单位为PCLK周期 */
- uint32_t hsync_back_porch; /* 水平后廊 */
- uint32_t hsync_front_porch; /* 水平前廊 */
- uint32_t vsync_pulse_width; /* 垂直同步宽度,单位为行数 */
- uint32_t vsync_back_porch; /* 垂直后廊 */
- uint32_t vsync_front_porch; /* 垂直前廊 */
- struct {
- uint32_t hsync_idle_low: 1; /* 空闲状态下,LCD_HSYNC低电平 */
- uint32_t vsync_idle_low: 1; /* 空闲状态下,LCD_VSYNC低电平 */
- uint32_t de_idle_high: 1; /* 空闲状态下,LCD_DE高电平 */
- uint32_t pclk_active_neg: 1; /* 显示数据是否在LCD_PCLK下降沿上发送 */
- uint32_t pclk_idle_high: 1; /* 空闲状态下,LCD_PCLK高电平 */
- } flags; /* RGBLCD时序参数 */
- } esp_lcd_rgb_timing_t;
复制代码 创建好RGBLCD屏幕后,就可以调用LCD设备的函数,对RGBLCD进行初始化以及做一些配置工作,如下代码所示。
- /* 复位屏幕 */
- ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
- /* 初始化RGB */
- ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
复制代码 初始化LCD设备完成之后,就得编写一些LCD操作的函数,比如设置横竖屏、画点、画线、画图形、显示字符等。
21.3.2 程序流程图
图21.3.2.1 RGBLCD实验程序流程图
21.3.3 程序解析
在11_rgblcd例程中,作者在11_rgblcd\components\BSP路径下新建LCD文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. RGBLCD驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。LCD驱动源码包括五个文件:lcd.c、lcd.h、rgblcd.c、rgblcd.h和lcdfont.h。
rgblcd.c文件存放的是RGBLCD的驱动函数,而rgblcd.h存放的是RGBLCD接口相关引脚宏以及管理RGBLCD屏幕的重要结构体类型。lcd.c文件主要lcd的一些绘图函数,而lcd.h则存放的是引脚接口宏定义以及函数声明。lcdfont.h存放的是4种字体大小不一样的ASCII字符集(12*12、16*16、24*24和32*32)。
这里主要给大家介绍一下RGBLCD的初始化函数rgblcd_init,如下代码所示:
- <font size="3">/**</font>
- <font size="3"> * @brief 初始化RGBLCD</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] 无</font>
- <font size="3"> * @retval RGBLCD句柄</font>
- <font size="3"> */</font>
- <font size="3">esp_lcd_panel_handle_t rgblcd_init(void)</font>
- <font size="3">{</font>
- <font size="3"> rgbdev.id = lcddev.id; /* 读取LCD面板ID */</font>
- <font size="3"> /* 配置VDDPST_1管理的IO电压 */</font>
- <font size="3"> esp_ldo_channel_handle_t ldo_rgblcd_phy = NULL;</font>
- <font size="3"> esp_ldo_channel_config_t ldo_rgblcd_phy_config = {</font>
- <font size="3"> .chan_id = 4, /* 选择内存LDO */</font>
- <font size="3"> .voltage_mv = 3300, /* 输出标准电压提供VDD_RGBLCD_DPHY */</font>
- <font size="3"> };</font>
- <font size="3">ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_rgblcd_phy_config, </font>
- <font size="3">&ldo_rgblcd_phy));</font>
- <font size="3"> if (rgbdev.id == 0X4342) /* 4.3寸屏, 480*272 RGB屏 */</font>
- <font size="3"> {</font>
- <font size="3"> rgbdev.pwidth = 480; /* 面板宽度,单位:像素 */</font>
- <font size="3"> rgbdev.pheight = 272; /* 面板高度,单位:像素 */</font>
- <font size="3"> rgbdev.hsw = 4; /* 水平同步宽度 */</font>
- <font size="3"> rgbdev.hbp = 43; /* 水平后廊 */</font>
- <font size="3"> rgbdev.hfp = 8; /* 水平前廊 */</font>
- <font size="3"> rgbdev.vsw = 4; /* 垂直同步宽度 */</font>
- <font size="3"> rgbdev.vbp = 12; /* 垂直后廊 */</font>
- <font size="3"> rgbdev.vfp = 8; /* 垂直前廊 */</font>
- <font size="3"> rgbdev.pclk_hz = 9 * 1000 * 1000; /* 设置像素时钟 9Mhz */</font>
- <font size="3"> }</font>
- <font size="3"> else if (rgbdev.id == 0X4384)</font>
- <font size="3"> {</font>
- <font size="3"> rgbdev.pwidth = 800; /* 面板宽度,单位:像素 */</font>
- <font size="3"> rgbdev.pheight = 480; /* 面板高度,单位:像素 */</font>
- <font size="3"> rgbdev.hsw = 48; /* 水平同步宽度 */</font>
- <font size="3"> rgbdev.hbp = 88; /* 水平后廊 */</font>
- <font size="3"> rgbdev.hfp = 40; /* 水平前廊 */</font>
- <font size="3"> rgbdev.vsw = 3; /* 垂直同步宽度 */</font>
- <font size="3"> rgbdev.vbp = 32; /* 垂直后廊 */</font>
- <font size="3"> rgbdev.vfp = 13; /* 垂直前廊 */</font>
- <font size="3"> rgbdev.pclk_hz = 30 * 1000 * 1000; /* 设置像素时钟 30Mhz */</font>
- <font size="3"> }</font>
- <font size="3"> else if (rgbdev.id == 0x7084) /* ATK-MD0700R-800480 */</font>
- <font size="3"> {</font>
- <font size="3"> rgbdev.pwidth = 800; /* LCD面板的宽度 */</font>
- <font size="3"> rgbdev.pheight = 480; /* LCD面板的高度 */</font>
- <font size="3"> rgbdev.hsw = 1; /* 水平同步宽度 */</font>
- <font size="3"> rgbdev.hbp = 46; /* 水平后廊 */</font>
- <font size="3"> rgbdev.hfp = 210; /* 水平前廊 */</font>
- <font size="3"> rgbdev.vsw = 1; /* 垂直同步宽度 */</font>
- <font size="3"> rgbdev.vbp = 23; /* 垂直后廊 */</font>
- <font size="3"> rgbdev.vfp = 22; /* 垂直前廊 */</font>
- <font size="3"> rgbdev.pclk_hz = 33 * 1000 * 1000; /* 设置像素时钟 33Mhz */</font>
- <font size="3"> }</font>
- <font size="3"> else if (rgbdev.id == 0x7016) /* ATK-MD0700R-1024600 */</font>
- <font size="3"> {</font>
- <font size="3"> rgbdev.pwidth = 1024; /* LCD面板的宽度 */</font>
- <font size="3"> rgbdev.pheight = 600; /* LCD面板的高度 */</font>
- <font size="3"> rgbdev.hsw = 20; /* 水平同步宽度 */</font>
- <font size="3"> rgbdev.hbp = 140; /* 水平后廊 */</font>
- <font size="3"> rgbdev.hfp = 160; /* 水平前廊 */</font>
- <font size="3"> rgbdev.vsw = 3; /* 垂直同步宽度 */</font>
- <font size="3"> rgbdev.vbp = 20; /* 垂直后廊 */</font>
- <font size="3"> rgbdev.vfp = 12; /* 垂直前廊 */</font>
- <font size="3"> rgbdev.pclk_hz = 48 * 1000 * 1000; /* 设置像素时钟 48Mhz */</font>
- <font size="3"> }</font>
- <font size="3"> else if (rgbdev.id == 0x1018) /* ATK-MD1018R-1280800 */</font>
- <font size="3"> {</font>
- <font size="3"> rgbdev.pwidth = 1280; /* LCD面板的宽度 */</font>
- <font size="3"> rgbdev.pheight = 800; /* LCD面板的高度 */</font>
- <font size="3"> rgbdev.hsw = 10; /* 水平同步宽度 */</font>
- <font size="3"> rgbdev.hbp = 140; /* 水平后廊 */</font>
- <font size="3"> rgbdev.hfp = 10; /* 水平前廊 */</font>
- <font size="3"> rgbdev.vsw = 3; /* 垂直同步宽度 */</font>
- <font size="3"> rgbdev.vbp = 23; /* 垂直后廊 */</font>
- <font size="3"> rgbdev.vfp = 10; /* 垂直前廊 */</font>
- <font size="3"> rgbdev.pclk_hz = 48 * 1000 * 1000; /* 设置像素时钟 48Mhz */</font>
- <font size="3"> }</font>
- <font size="3"> /* 配置RGB参数 */</font>
- <font size="3"> esp_lcd_rgb_panel_config_t panel_config = { /* RGBLCD配置结构体 */</font>
- <font size="3"> .num_fbs = 2, /* 缓存区数量 */</font>
- <font size="3"> .data_width = 16, /* 数据宽度为16位 */</font>
- <font size="3"> .psram_trans_align = 64, /* 在PSRAM分配的缓冲区的对齐 */</font>
- <font size="3"> .clk_src = LCD_CLK_SRC_DEFAULT, /* RGBLCD外设时钟源 */</font>
- <font size="3"> .disp_gpio_num = GPIO_NUM_NC, /* 用于显示控制信号,不用设-1 */</font>
- <font size="3"> .pclk_gpio_num = GPIO_LCD_PCLK, /* PCLK信号引脚 */</font>
- <font size="3"> .hsync_gpio_num = GPIO_NUM_NC, /* HSYNC信号引脚,DE模式不用 */</font>
- <font size="3"> .vsync_gpio_num = GPIO_NUM_NC, /* VSYNC信号引脚,DE模式不用 */</font>
- <font size="3"> .de_gpio_num = GPIO_LCD_DE, /* DE信号引脚 */</font>
- <font size="3"> .data_gpio_nums = { /* 数据线引脚 */</font>
- <font size="3"> GPIO_LCD_B3, GPIO_LCD_B4, GPIO_LCD_B5, GPIO_LCD_B6, GPIO_LCD_B7,</font>
- <font size="3"> GPIO_LCD_G2, GPIO_LCD_G3, GPIO_LCD_G4, GPIO_LCD_G5, GPIO_LCD_G6, </font>
- <font size="3">GPIO_LCD_G7,</font>
- <font size="3"> GPIO_LCD_R3, GPIO_LCD_R4, GPIO_LCD_R5, GPIO_LCD_R6, GPIO_LCD_R7,</font>
- <font size="3"> },</font>
- <font size="3"> .timings = { /* RGBLCD时序参数 */</font>
- <font size="3"> .pclk_hz = rgbdev.pclk_hz, /* 像素时钟频率 */</font>
- <font size="3"> .h_res = rgbdev.pwidth, /* 水平分辨率,即一行中的像素数 */</font>
- <font size="3"> .v_res = rgbdev.pheight, /* 垂直分辨率,即帧中的行数 */</font>
- <font size="3"> .hsync_back_porch = rgbdev.hbp, /* 水平后廊 */</font>
- <font size="3"> .hsync_front_porch = rgbdev.hfp, /* 水平前廊 */</font>
- <font size="3"> .hsync_pulse_width = rgbdev.vsw, /* 垂直同步宽度,单位:行数 */</font>
- <font size="3"> .vsync_back_porch = rgbdev.vbp, /* 垂直后廊 */</font>
- <font size="3"> .vsync_front_porch = rgbdev.vfp, /* 垂直前廊 */</font>
- <font size="3"> .vsync_pulse_width = rgbdev.hsw, /* 水平同步宽度 */</font>
- <font size="3"> .flags = {</font>
- <font size="3"> .pclk_active_neg = true, /* RGB数据在下降沿计时 */</font>
- <font size="3"> },</font>
- <font size="3"> },</font>
- <font size="3"> .flags.fb_in_psram = true, /* 在PSRAM中分配帧缓冲区 */</font>
- <font size="3"> };</font>
- <font size="3"> esp_lcd_new_rgb_panel(&panel_config, &panel_handle);/* 创建RGB对象 */</font>
-
- <font size="3"> ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); /* 复位RGB屏 */</font>
- <font size="3"> </font>
- <font size="3"> ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); /* 初始化RGB */</font>
- <font size="3"> </font>
- <font size="3"> rgblcd_display_dir(1); /* 设置横屏 */</font>
- <font size="3"> return panel_handle; /* RGBLCD句柄 */</font>
- <font size="3">}</font>
复制代码 该函数就是对RGBLCD进行初始化,整个过程就围绕着21.3.1小节中描述的驱动流程实现。通过esp_lcd_new_rgb_panel函数创建RGBLCD屏对象,后续通过LCD通用API接口对LCD进行配置,比如esp_lcd_panel_reset复位LCD屏,esp_lcd_panel_init初始化LCD屏。最后通过rgblcd_display_dir函数设置横屏显示。函数中还涉及到配置VDDPST_1管理的IO电压,通过调用esp_ldo_acquire_channel函数设置LDO_VO4输出3.3V。
上面RGBLCD对应的时序参数,可到对应的裸屏规格书中查找到,一般来说都是以典型值作为配置值。
接下来,介绍一下设置RGBLCD横竖屏的函数rgblcd_display_dir,如下代码所示:
- <font size="3">/**</font>
- <font size="3"> * @brief RGBLCD显示方向设置</font>
- <font size="3"> * @param dir:0,竖屏;1,横屏</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void rgblcd_display_dir(uint8_t dir)</font>
- <font size="3">{</font>
- <font size="3"> rgbdev.dir = dir; /* 显示方向 */</font>
- <font size="3"> if (rgbdev.dir == 0) /* 竖屏 */</font>
- <font size="3"> {</font>
- <font size="3"> rgbdev.width = rgbdev.pheight;</font>
- <font size="3"> rgbdev.height = rgbdev.pwidth;</font>
- <font size="3"> esp_lcd_panel_swap_xy(panel_handle, true); /* 交换X和Y轴 */ </font>
- <font size="3"> esp_lcd_panel_mirror(panel_handle, false, true); /* Y轴进行镜像处理 */</font>
- <font size="3"> }</font>
- <font size="3"> else if (rgbdev.dir == 1) /* 横屏 */</font>
- <font size="3"> {</font>
- <font size="3"> rgbdev.width = rgbdev.pwidth;</font>
- <font size="3"> rgbdev.height = rgbdev.pheight;</font>
- <font size="3"> esp_lcd_panel_swap_xy(panel_handle, false); /* 不需要交换XY轴 */</font>
- <font size="3"> esp_lcd_panel_mirror(panel_handle, false, false); /* XY轴不镜像处理 */</font>
- <font size="3"> }</font>
- <font size="3"> lcddev.width = rgbdev.width; /* 宽度 */</font>
- <font size="3"> lcddev.height = rgbdev.height; /* 高度 */</font>
- <font size="3">}</font>
复制代码 该函数主要就是通过调用esp_lcd_panel_swap_xy函数和esp_lcd_panel_mirror函数去实现,前者将X轴和Y轴交换,而后者就是对X轴或Y轴进行镜像。
下面介绍在rgblcd.h文件定义的重要结构体:
- <font size="3">/* LCD RGBLCD重要参数集 */</font>
- <font size="3">typedef struct </font>
- <font size="3">{</font>
- <font size="3"> uint32_t pwidth; /* RGBLCD面板的宽度,固定参数,不随显示方向改变 */</font>
- <font size="3"> uint32_t pheight; /* RGBLCD面板的高度,固定参数,不随显示方向改变 */</font>
- <font size="3"> uint16_t hsw; /* 水平同步宽度 */</font>
- <font size="3"> uint16_t vsw; /* 垂直同步宽度 */</font>
- <font size="3"> uint16_t hbp; /* 水平后廊 */</font>
- <font size="3"> uint16_t vbp; /* 垂直后廊 */</font>
- <font size="3"> uint16_t hfp; /* 水平前廊 */</font>
- <font size="3"> uint16_t vfp; /* 垂直前廊 */</font>
- <font size="3"> uint8_t activelayer; /* 当前层编号:0/1 */</font>
- <font size="3"> uint8_t dir; /* 0,竖屏;1,横屏; */</font>
- <font size="3"> uint16_t id; /* RGBLCD ID */</font>
- <font size="3"> uint32_t pclk_hz; /* 设置像素时钟 */</font>
- <font size="3"> uint16_t width; /* RGBLCD宽度 */</font>
- <font size="3"> uint16_t height; /* RGBLCD高度 */</font>
- <font size="3">} _rgblcd_dev;</font>
复制代码 _rgblcd_dev结构体用于保存一些RGBLCD重要参数信息,比如RGBLCD的ID、RGBLCD的长宽、RGBLCD的时序参数等。最后声明_rgblcd_dev结构体类型变量rgblcd_dev,rgblcd_dev在rgblcd.c中定义。
在lcd.h文件中也定义了一个重要参数结构体,如下代码所示。
- <font size="3">/* LCD重要参数集 */</font>
- <font size="3">typedef struct </font>
- <font size="3">{</font>
- <font size="3"> uint16_t id; /* 读取ID */</font>
- <font size="3"> uint32_t width; /* 面板宽度,固定参数,不随显示方向改变 */</font>
- <font size="3"> uint32_t height; /* 面板高度,固定参数,不随显示方向改变 */</font>
- <font size="3"> uint8_t dir; /* 0,竖屏(MIPI只能竖屏);1,横屏; */</font>
- <font size="3"> uint8_t color_byte; /* 颜色格式 */</font>
- <font size="3"> esp_lcd_panel_handle_t lcd_panel_handle; /* LCD控制句柄 */</font>
- <font size="3"> struct</font>
- <font size="3"> {</font>
- <font size="3"> int lcd_rst; /* 复位引脚 */</font>
- <font size="3"> int lcd_bl; /* 背光引脚 */</font>
- <font size="3"> } ctrl;</font>
- <font size="3">} _lcd_dev;</font>
复制代码 _lcd_dev结构体用于保存一些LCD重要参数信息,比如LCD的ID(RGBLCD)、LCD的长宽、LCD控制句柄等。最后声明_lcd_dev结构体类型变量lcddev,lcddev在lcd.c中定义。
下面我们再解析lcd.c的程序,看一下初始化函数lcd_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief 初始化LCD</font>
- <font size="3"> * @param 无</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void lcd_init(void)</font>
- <font size="3">{</font>
- <font size="3"> lcddev.id = lcd_panelid_read(); /* 读取RGB LCD面板ID */</font>
- <font size="3"> lcddev.ctrl.lcd_rst = LCD_RST_PIN; /* 复位管脚 */</font>
- <font size="3"> lcddev.ctrl.lcd_bl = LCD_BL_PIN; /* 背光管脚 */</font>
- <font size="3"> gpio_config_t gpio_init_struct = {0};</font>
- <font size="3"> gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引脚中断 */</font>
- <font size="3"> gpio_init_struct.mode = GPIO_MODE_OUTPUT; /* 输出模式 */</font>
- <font size="3"> gpio_init_struct.pull_up_en = GPIO_PULLUP_DISABLE; /* 失能上拉 */</font>
- <font size="3"> gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */</font>
- <font size="3"> gpio_init_struct.pin_bit_mask = 1ull << lcddev.ctrl.lcd_bl; /* 设置的引脚 */</font>
- <font size="3"> ESP_ERROR_CHECK(gpio_config(&gpio_init_struct)); /* 配置GPIO */</font>
- <font size="3"> LCD_BL(0); /* 背光关闭 */</font>
- <font size="3"> lcddev.lcd_panel_handle = rgblcd_init(); /* 初始化RGB LCD */</font>
- <font size="3">ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(lcddev.lcd_panel_handle,</font>
- <font size="3">2, &lcd_buffer[0], &lcd_buffer[1])); /* 获取帧缓冲区 */</font>
- <font size="3"> </font>
- <font size="3">/* 内部缓冲区刷新完成回调函数 */</font>
- <font size="3">const esp_lcd_rgb_panel_event_callbacks_t rgb_cbs = {</font>
- <font size="3"> .on_bounce_frame_finish = lcd_rgb_panel_refresh_done_callback, </font>
- <font size="3"> };</font>
- <font size="3"> ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(</font>
- <font size="3">lcddev.lcd_panel_handle, &rgb_cbs, NULL));</font>
- <font size="3"> lcd_clear(WHITE);</font>
- <font size="3"> LCD_BL(1); /* 打开背光 */</font>
- <font size="3">}</font>
复制代码 在lcd_init函数中,首先通过调用lcd_panelid_read函数读取RGBLCD的ID,把ID赋值给lcddev.id成员,然后对LCD背光控制引脚配置,后面再调用rgblcd_init函数初始化RGBLCD,紧接着通过esp_lcd_rgb_panel_get_frame_buffer函数接口把数据获取到。为了防止屏幕撕裂,这里还注册了刷新完成回调函数,在进行清屏函数中,需要等待一帧刷新完成,再进行下一帧刷新。最后调用lcd_clear函数清屏,拉高背光控制引脚,打开背光。
现在,来看一下读取RGBLCD的ID函数lcd_panelid_read,如下代码所示。
- <font size="3">/**</font>
- <font size="3"> * @brief 读取RGB LCD ID</font>
- <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url] 利用LCD RGB线的最高位(R7,G7,B7)来识别面板ID</font>
- <font size="3"> * IO14 = R7(M0); IO8 = G7(M1); IO3 = B7(M2);</font>
- <font size="3"> * M2:M1:M0</font>
- <font size="3"> * 0 :0 :0 4.3 寸480*272 RGB屏,ID = 0X4342</font>
- <font size="3"> * 0 :0 :1 7 寸800*480 RGB屏,ID = 0X7084</font>
- <font size="3"> * 0 :1 :0 7 寸1024*600 RGB屏,ID = 0X7016</font>
- <font size="3"> * 0 :1 :1 7 寸1280*800 RGB屏,ID = 0X7018</font>
- <font size="3"> * 1 :0 :0 4.3 寸800*480 RGB屏,ID = 0X4348</font>
- <font size="3"> * 1 :0 :1 10.1寸1280*800 RGB屏,ID = 0X1018</font>
- <font size="3"> * </font>
- <font size="3"> * @param 无</font>
- <font size="3"> * @retval 0, 非法; </font>
- <font size="3"> * 其他, LCD ID</font>
- <font size="3"> */</font>
- <font size="3">uint16_t lcd_panelid_read(void)</font>
- <font size="3">{</font>
- <font size="3"> uint8_t idx = 0;</font>
- <font size="3"> gpio_config_t gpio_init_struct = {0};</font>
- <font size="3"> gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引脚中断 */</font>
- <font size="3"> gpio_init_struct.mode = GPIO_MODE_INPUT; /* 输入输出模式 */</font>
- <font size="3"> gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; /* 使能上拉 */</font>
- <font size="3"> gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */</font>
- <font size="3">gpio_init_struct.pin_bit_mask = 1ull << GPIO_LCD_R7 | 1ull << GPIO_LCD_G7 </font>
- <font size="3">| 1ull << GPIO_LCD_B7;</font>
- <font size="3"> gpio_config(&gpio_init_struct); /* 配置GPIO */</font>
- <font size="3"> idx = (uint8_t)gpio_get_level(GPIO_LCD_R7); /* 读取M0 */</font>
- <font size="3"> idx |= (uint8_t)gpio_get_level(GPIO_LCD_G7) << 1; /* 读取M1 */</font>
- <font size="3"> idx |= (uint8_t)gpio_get_level(GPIO_LCD_B7) << 2; /* 读取M2 */</font>
- <font size="3"> /* 正点原子其他的RGB LCD自行匹配 */</font>
- <font size="3"> switch (idx)</font>
- <font size="3"> {</font>
- <font size="3"> case 0:</font>
- <font size="3"> {</font>
- <font size="3"> return 0x4342; /* ATK-MD0430R-480272 */</font>
- <font size="3"> }</font>
- <font size="3"> case 1:</font>
- <font size="3"> {</font>
- <font size="3"> return 0x7084; /* ATK-MD0700R-800480 */</font>
- <font size="3"> }</font>
- <font size="3"> case 2:</font>
- <font size="3"> {</font>
- <font size="3"> return 0x7016; /* ATK-MD0700R-1024600 */</font>
- <font size="3"> }</font>
- <font size="3"> case 3:</font>
- <font size="3"> {</font>
- <font size="3"> return 0x7018; /* ATK-MD0700R-1280800 */</font>
- <font size="3"> }</font>
- <font size="3"> case 4:</font>
- <font size="3"> {</font>
- <font size="3"> return 0x4384; /* ATK-MD0430R-800480 */</font>
- <font size="3"> }</font>
- <font size="3"> case 5:</font>
- <font size="3"> {</font>
- <font size="3"> return 0x1018; /* ATK-MD1018R-1280800 */</font>
- <font size="3"> }</font>
- <font size="3"> default:</font>
- <font size="3"> {</font>
- <font size="3"> return 0;</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 在前面也说到,RGBLCD屏并没有读功能,但是在正点原子的RGBLCD模块上,利用数据线(R7/G7/B7)做了一个巧妙的设计,可让主控芯片读到RGBLCD模块的ID,从而执行不同的初始化,实现不同分辨率的RGBLCD模块的兼容。
接下来,再看看这个回调函数里面的实现,如下代码所示。
- <font size="3">/**</font>
- <font size="3"> * @brief 内部缓存刷新完成回调函数</font>
- <font size="3"> * @param panel_io: RGBLCD IO的句柄</font>
- <font size="3"> * @param edata: 事件数据类型</font>
- <font size="3"> * @param user_ctx: 传入参数</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">IRAM_ATTR static bool lcd_rgb_panel_refresh_done_callback(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)</font>
- <font size="3">{</font>
- <font size="3"> refresh_done_flag = 1;</font>
- <font size="3"> return false;</font>
- <font size="3">}</font>
复制代码 当发送一帧完成之后,就回进入到回调函数中,把refresh_done_flag标志置1。
现在看一下LCD的清屏函数lcd_clear,如下代码所示:
- <font size="3">/**</font>
- <font size="3"> * @brief 清屏</font>
- <font size="3"> * @param color :清屏颜色</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">IRAM_ATTR void lcd_clear(uint16_t color)</font>
- <font size="3">{</font>
- <font size="3">/* 将 void* 转换为 uint16_t* */</font>
- <font size="3"> uint16_t *buffer = (uint16_t *)lcd_buffer[buffer_sw]; </font>
- <font size="3"> /* 制定缓存区填充颜色值 */</font>
- <font size="3"> for (uint32_t i = 0; i < lcddev.width * lcddev.height; i++)</font>
- <font size="3"> {</font>
- <font size="3"> buffer</font><span style="font-size: medium;"> = color;
- }
- esp_lcd_panel_draw_bitmap(lcddev.lcd_panel_handle, 0, 0, lcddev.width,
- lcddev.height, buffer);
- refresh_done_flag = 0;
- do
- {
- vTaskDelay(1); /* 等待内部缓存刷新完成 */
- }
- while (refresh_done_flag != 1);
- /* 使用异或操作在 0 和 1 之间切换,目的是为了切换另一个缓冲区 */
- buffer_sw ^= 1;
- }</span>
复制代码 在清屏函数里面,按照熟悉的流程,先申请内存,然后往内存存入颜色数据,再调用esp_lcd_panel_draw_bitmap进行区域画点。只不过这里没有申请内存,而是直接使用LCD初始化时候申请到的整屏缓存,即esp_lcd_rgb_panel_config_t结构体中的num_fbs成员。在mipi_lcd_init函数里面,设置该成员为2,所以看到lcd_buffer有两个元素。当发送完esp_lcd_panel_draw_bitmap函数,这时候dma就开始搬运数据,这个过程需要一点时间,所以利用refresh_done_flag变量进行等待完成。完成一帧数据的显示,最后切换另一个buffer,避免出现LCD撕裂现象。
最后介绍一下字符显示函数lcd_show_char,该函数代码如下:
- /**
- * @brief 在指定位置显示一个字符
- * @param x,y :坐标
- * @param chr :要显示的字符:" "--->"~"
- * @param size :字体大小 12/16/24/32
- * @param mode :叠加方式(1); 非叠加方式(0);
- * @param color:字体颜色
- * @retval 无
- */
- void lcd_show_char(uint16_t x, uint16_t y, char chr, uint8_t size, uint8_t mode, uint16_t color)
- {
- uint8_t temp, t1, t;
- uint16_t y0 = y;
- uint8_t csize = 0;
- uint8_t *pfont = 0;
- csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); /* 得到字体大小 */
- chr = (char)chr - ' '; /* 得到偏移后的值*/
- switch (size)
- {
- case 12:
- pfont = (uint8_t *)asc2_1206[(uint8_t)chr]; /* 调用1206字体 */
- break;
- case 16:
- pfont = (uint8_t *)asc2_1608[(uint8_t)chr]; /* 调用1608字体 */
- break;
- case 24:
- pfont = (uint8_t *)asc2_2412[(uint8_t)chr]; /* 调用2412字体 */
- break;
- case 32:
- pfont = (uint8_t *)asc2_3216[(uint8_t)chr]; /* 调用3216字体 */
- break;
- default:
- return ;
- }
- for (t = 0; t < csize; t++)
- {
- temp = pfont[t]; /* 获取字符的点阵数据 */
- for (t1 = 0; t1 < 8; t1++) /* 一个字节8个点 */
- {
- if (temp & 0x80) /* 有效点,需要显示 */
- {
- lcd_draw_point(x, y, color); /* 画点出来,要显示这个点 */
- }
- else if (mode == 0) /* 无效点,不显示 */
- {
- lcd_draw_point(x, y, g_back_color); /* 画背景色 */
- }
- temp <<= 1; /* 移位, 以便获取下一个位的状态 */
- y++;
- if (y >= lcddev.height) return; /* 超区域了 */
- if ((y - y0) == size) /* 显示完一列了? */
- {
- y = y0; /* y坐标复位 */
- x++; /* x坐标递增 */
-
- if (x >= lcddev.width)
- {
- return; /* x坐标超区域了 */
- }
- break;
- }
- }
- }
- }
复制代码 在lcd_show_char函数里面,我们用到了四个字符集点阵数据数组asc2_1206、asc2_1608、asc2_2412和asc2_3216,通过参数font决定。此外该函数增加以叠加方式显示,或者以非叠加方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显示。
注意:字符点阵数据的生成是依靠正点原子的XFONT软件,取模方式设置为:阴码+逐列式+顺向+C51格式,具体教程请参考正点原子任何一个《STM32开发指南》的OLED显示实验章节的程序解析处。
lcd.c的函数比较多,其他函数请大家自行查看源码,都有详细的注释。
2. CMakeLists.txt文件
本例程的功能实现主要依靠LCD驱动。要在main函数中,成功调用LCD文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- set(src_dirs
- LED
- LCD)
- set(include_dirs
- LED
- LCD)
- set(requires
- driver
- esp_lcd
- esp_common)
- idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
- component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码 3. main.c驱动代码
在main.c里面编写如下代码。
- void app_main(void)
- {
- esp_err_t ret;
- uint8_t x = 0;
- ret = nvs_flash_init(); /* 初始化NVS */
- if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
- {
- ESP_ERROR_CHECK(nvs_flash_erase());
- ESP_ERROR_CHECK(nvs_flash_init());
- }
- led_init(); /* LED初始化 */
- lcd_init(); /* LCD屏初始化 */
- while (1)
- {
- switch (x)
- {
- case 0:
- {
- lcd_clear(WHITE);
- break;
- }
- case 1:
- {
- lcd_clear(BLACK);
- break;
- }
- case 2:
- {
- lcd_clear(BLUE);
- break;
- }
- case 3:
- {
- lcd_clear(RED);
- break;
- }
- case 4:
- {
- lcd_clear(MAGENTA);
- break;
- }
- case 5:
- {
- lcd_clear(GREEN);
- break;
- }
- case 6:
- {
- lcd_clear(CYAN);
- break;
- }
- case 7:
- {
- lcd_clear(YELLOW);
- break;
- }
- case 8:
- {
- lcd_clear(BRRED);
- break;
- }
- case 9:
- {
- lcd_clear(GRAY);
- break;
- }
- case 10:
- {
- lcd_clear(LGRAY);
- break;
- }
- case 11:
- {
- lcd_clear(BROWN);
- break;
- }
- }
- lcd_show_string(10, 40, 240, 32, 32, "ESP32-P4", RED);
- lcd_show_string(10, 80, 240, 24, 24, "RGBLCD TEST", RED);
- lcd_show_string(10, 110, 240, 16, 16, "ATOM@ALIENTEK", RED);
- x++;
- if (x == 12)
- {
- x = 0;
- }
- LED0_TOGGLE();
- vTaskDelay(pdMS_TO_TICKS(1000));
- }
- }
复制代码 app_main函数功能主要是显示一些固定的字符,字体大小包括32、24和16三种,然后不停的切换背景颜色,每500毫秒切换一次。而LED0也会不停地闪烁,指示程序已经在运行了。
21.4 下载验证
下载代码后,LED0不停地闪烁,提示程序已经在运行了。同时可以看到RGBLCD屏幕模块显示背景色不停切换,如下图所示。
图21.4.1 RGBLCD显示效果图 |
|