OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 24|回复: 0

《ESP32-P4开发指南— V1.0》第二十章 SPILCD实验

[复制链接]

1222

主题

1236

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5260
金钱
5260
注册时间
2019-5-8
在线时间
1335 小时
发表于 10 小时前 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2025-12-24 09:32 编辑

第二十章 SPILCD实验

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


2.jpg

3.png

本章将开始学习ESP_IDF的LCD外设驱动,目前驱动支持I2C、SPI(QSPI)、I80、RGB和MIPI接口,接下来的几个章节都是介绍不同屏幕的驱动。本章用到的是ESP32-P4的Camera-LCD控制器去驱动2.4/1.3寸SPI LCD屏幕,实质是使用到硬件SPI接口,实现和SPILCD屏之间的通信,实现ASCII字符、图形和彩色的显示。
本章分为如下几个小节:
20.1 SPI及LCD介绍
20.2 硬件设计
20.3 程序设计
20.4 下载验证


20.1 SPI及LCD介绍

20.1.1 SPI介绍
SPI,Serial Peripheral interface,顾名思义,就是串行外围设备接口,是由原摩托罗拉公司在其MC68HCXX系列处理器上定义的。SPI是一种高速的全双工、同步、串行的通信总线,已经广泛应用在众多MCU、存储芯片、AD转换器和LCD之间。
SPI通信跟IIC通信一样,通信总线上允许挂载一个主设备和一个或者多个从设备。为了跟从设备进行通信,一个主设备至少需要4跟数据线,分别为:
MOSI(Master Out / Slave In):主数据输出,从数据输入,用于主机向从机发送数据。
MISO(Master In / Slave Out):主数据输入,从数据输出,用于从机向主机发送数据。
SCLK(Serial Clock):时钟信号,由主设备产生,决定通信的速率。
CS(Chip Select):从设备片选信号,由主设备产生,低电平时选中从设备。
多从机SPI通信网络连接如下图所示。

第二十章 SPILCD实验688.png
图20.1.1.1 多从机SPI通信网络图

从上图可以知道,MOSI、MISO、SCLK引脚连接SPI总线上每一个设备,如果CS引脚为低电平,则从设备只侦听主机并与主机通信。SPI主设备一次只能和一个从设备进行通信。如果主设备要和另外一个从设备通信,必须先终止和当前从设备通信,否则不能通信。
SPI通信有4种不同的模式,不同的从机可能在出厂时就配置为某种模式,这是不能改变的。通信双方必须工作在同一模式下,才能正常进行通信,所以可以对主机的SPI模式进行配置。SPI通信模式是通过配置CPOL(时钟极性)和CPHA(时钟相位)来选择的。
CPOL,详称Clock Polarity,就是时钟极性,当主从机没有数据传输的时候即空闲状态,SCL线的电平状态,假如空闲状态是高电平,CPOL=1;若空闲状态时低电平,那么CPOL = 0。
CPHA,详称Clock Phase,就是时钟相位,实质指的是数据的采样时刻。CPHA = 0表示数据的采样是从第1个边沿信号上即奇数边沿,具体是上升沿还是下降沿的问题,是由CPOL决定的。CPHA=1表示数据采样是从第2个边沿即偶数边沿。
SPI的4种模式对比图,如下图所示。


第二十章 SPILCD实验1199.png
图20.1.1.2 SPI的4种模式对比图

1)模式0,CPOL=0,CPHA=0;空闲时,SCL处于低电平,数据采样在第1个边沿,即SCL由低电平到高电平的跳变,数据采样在上升沿,数据发送在下降沿。
2)模式1,CPOL=0,CPHA=1;空闲时,SCL处于低电平,数据采样在第2个边沿,即SCL由高电平到低电平的跳变,数据采样在下升沿,数据发送在上降沿。
3)模式2,CPOL=1,CPHA=0;空闲时,SCL处于高电平,数据采样在第1个边沿,即SCL由高电平到低电平的跳变,数据采样在下升沿,数据发送在上降沿。
4)模式3,CPOL=1,CPHA=1;空闲时,SCL处于高电平,数据采样在第2个边沿,即SCL由低电平到高电平的跳变,数据采样在上升沿,数据发送在下降沿。

20.1.2 ESP32-P4的SPI介绍
ESP32-P4有五个SPI控制器,主系统有四个,而低功耗系统有一个。五个SPI控制器分为:
①FLASH SPI,用于连接封装外的flash
②PSRAM SPI,用于连接封装内/封装外的PSRAM
③SPI2,通用SPI控制器
④SPI3,通用SPI控制器
⑤低功耗SPI,简称LP-SPI
FLASH SPI和PSRAM SPI即SPI0和SPI1,可配置为SPI存储器模式。在这个前提下,后续即可实现内存映射,即将外部存储器映射到MCU地址空间被视为内部内存。FLASH SPI最高支持四线SDR读写操作,而PSRAM SPI最高支持十六线DDR读写操作。FLASH SPI SDR模式下支持的最高时钟频率为120MHz,PSRAM SPI DDR模式下支持的最高时钟频率为250MHz。在menuconfig中,FLASH和PSRAM也是满足上述条件。
这五个SPI控制器,在主系统中,我们只能使用SPI2和SPI3。SPI2和SPI3的特性如下:
        支持主机和从机模式
        支持半双工和全双工通信
        支持CPU控制的传输模式和DMA控制的传输模式
        SPI2支持1-bit SPI、2-bit Dual SPI、4-bit Quad SPI、QPI、8-bit Octal SPI和OPI模式        
        SPI3支持1-bit SPI、2-bit Dual SPI、4-bit Quad SPI、QPI模式
        时钟频率可配置:主机模式下,时钟频率可达80MHz;从机模式下,时钟频率可达60MHz
        数据长度可配置
        数据位的读写顺序可配置
        时钟极性和相位可配置
SPI2接口的管脚有两组,一组四线接口通过IO MUX与GPIO6~GPIO11和UART0/1接口的管脚复用,另外一组八线接口通过IO MUX与GPIO28~GPIO38、UART0接口的管脚和EMAC的第一组的RMII接口的管脚复用,如下图所示

第二十章 SPILCD实验2367.png
图20.1.2.1 SPI2接口引脚

在选择使用SPI2时,需要注意管脚使用情况。而SPI3接口通过GPIO交换矩阵可配置使用任意GPIO引脚。

20.1.3 LCD介绍
平常所说的LCD是TFT-LCD(薄膜晶体管液晶显示器)的统称。它是一种常见的数字显示技术,常用于显示图像和文字。LCD使用液晶材料和偏振光技术,当液晶分子受到电场影响时,会改变光的偏振方向,从而控制光线强度,使图像或文字显示在屏幕上。
下面看一下LCD的整体结构,如下图所示:

第二十章 SPILCD实验2596.png
图20.1.3.1 LCD整体结构

背光源(LED backlight):位于LCD面板的背面,负责提供均匀的光源。背光是LCD显示图像的基础,没有背光,LCD屏幕无法显示图像。所以在驱动LCD屏幕,首先确保背光有没有被点亮。
扩散板&导光板(Diffusors&Lightguides):位于背光源和液晶层之间,导光板负责将背光源发出的光线均匀分布在整个屏幕,而扩散板主要让光扩散的面积变大,让光均匀柔和。
可以把背光源、导光板和扩散板归纳为背光部分。背光部分还包含未被体现出来的棱镜膜,其作用是让不符合角度的光线再次被利用。
偏光片A&B(Polarizing film):又称偏振板,起到光线过滤作用。LCD的两侧各贴有一片偏振板,他们的方向通常是互相垂直的。光线进入LCD时,只有特定方向的光(垂直偏光片A)才能通过偏光片A,其他方向的光会被吸收,而偏光片B与偏光片A方向是垂直的,这时候光是不能透过偏光片B,屏幕显示出来就是黑色。想要屏幕显示白色,必定需要光透过偏光片B,这时就需要在两个偏光片之间增加一个液晶层。液晶层的液晶分子的排列变化会影响光线通过偏光片B的能力,从而实现明暗的变化。所以光通过的路径是:通过偏光片A,然后穿过液晶层,最后通过偏光片B。
液晶层:液晶层中有液晶分子和配向膜,在上图中没有体现出来,可以参考下图。


第二十章 SPILCD实验3170.png
图20.1.3.2 LCD硬件结构图

液晶分子在自然状态下,有规律、整齐排列,而液晶层的配向膜把液晶夹在中间,液晶分子就会沿着配向膜的细槽方向排列。最终导致透过偏光片A的光,经过液晶层时,会沿着液晶分子的扭曲产生变化,如上图中红色色块和绿色色块下面的液晶分子,这样光变成水平方向,就可以透光偏光片B,显示出白色,有彩色滤光片,所以显示出颜色。液晶分子在没有电场作用时具有一定的排列顺序,而在电场的影响下,其排列方式会发生变化。所以在配向膜两端加上电极层,只需要改变电极层的电压,即可控制光是否透过偏光片B,进而显示颜色。这个电极层就是TFT的内容。
彩色滤光片(Colour filters):位于一个玻璃基板之上,覆盖有红、绿、蓝三原色的滤色条纹阵列,对应形成屏幕上的每一个像素点。换句话来说,每个像素点都有三个子像素,分别对应的是红色子像素、绿色子像素和蓝色子像素。通过改变红绿蓝的明暗程度,即可混合出各种颜色,为显示色彩提供基础。
TFT层(TFT):由电极、玻璃层和TFT组成。TFT即薄膜晶体管,每个子像素都有一个晶体管,也就是说一个像素点会有三个晶体管。这个晶体管就是每个子像素的亮度调节开关,可以调节子像素范围内的电压,这个子像素范围内的液晶分子自然也会受到电压大小的影响而发生不同程度扭曲。
那是什么设备控制这个电压呢?上图20.1.3.2中描述的是一个像素点,红色和绿色显示出来,而蓝色没有被显示出来,最终该像素点显示出来的是黄色。
玻璃基板(Liquid Cystals):LCD由两片很薄的玻璃基板组成,分别作为顶层和底层,这是显示屏的主体结构。这些基板通常是由钙钠玻璃经过精细抛光和镀膜处理制成,以保证透光性和耐用性。
LCD具有很多优点,比如:能耗低、寿命长、可靠性搞、清晰度高等。因此,它已被广泛应用于各种电子设备,如家电、可穿戴设备、便携设备等。
在上面提出了一个小疑问?是什么设备控制TFT的电压,这里主要是用到LCD屏幕的驱动IC。驱动IC通过特定的接口对外通信,并控制输出电压让液晶扭转,使其发生色阶及明暗的变化。它通常包含控制电路和驱动电路两部分。控制电路负责接收来自主控芯片的信号,以及图像信号的转换与处理,驱动电路负责输出图像信号并显示到面板上。
在这里,我们主要关心的是LCD屏幕的驱动接口,比较常见的有SPI、QSPI、I80、RGB和MIPI-DSI等,他们在占用IO数量、并行数据位数以及数据传输带宽和GRAM位置等方面有不同,如下表所示。


第二十章 SPILCD实验4218.png
表20.1.3.1 LCD屏幕不同驱动接口

SPI接口的数据传输带宽小,比较适用于低分辨率的屏幕。本章节用到的屏幕接口就是SPI接口,而屏幕是正点原子的2.4寸TFTLCD屏幕模块(ATK-MD0240)或1.3寸TFTLCD屏幕模块(ATK-MD0130)。这两款显示模块的LCD分辨率分别为320*240和240*240。
QSPI和I80接口的数据传输带宽更大,所以能够支持较高分辨率的屏幕,但I80接口要求LCD内置GRAM,导致屏幕成本较高,并且难以做到大屏幕。正点原子支持I80接口的屏幕有很多,比如2.8寸、3.5寸、4.3寸以及7寸。
RGB与I80接口类似,但RGB接口无需LCD内置GRAM,因此适用于更高分辨率的屏幕。
MIPI-DSI接口适用于高分辨率、高刷新率的屏幕。
驱动LCD屏幕的核心就是:驱动LCD驱动芯片。第一步就得知道LCD驱动芯片使用的是什么接口,进而编写出基本的读写函数;第二步就需要利用第一步的写函数,把屏厂提供的初始化序列写入到LCD驱动芯片,用于初始化;第三步就是实现画点函数,基于画点函数即可实现各种绘图功能。
正点原子1.3寸和2.4寸TFTLCD屏幕模块,如下图所示。


第二十章 SPILCD实验4737.png 第二十章 SPILCD实验4738.png 第二十章 SPILCD实验4739.png
图20.1.3.3 正点原子1.3寸和2.4寸TFTLCD屏幕模块

ATK-MD0130模块采用ST7789V作为LCD的驱动芯片,而ATK-MD0240模块的LCD驱动芯片为ST7789,这两个芯片都自带RAM,无需外加驱动器或存储器。ATK-MD0240模块支持使用I80-8位并行接口、I80-16位并行接口、2线串行接口以及四线8位串行接口,通过M2、M1和M0引脚的高低电平去决定选择。默认情况下,M2和M1连接高电平,而M0连接低电平,使用的是SPI接口。而ATK-MD0130模块只支持SPI接口。
ESP32-P4与SPI接口显示屏模块的连接图如下图所示。


第二十章 SPILCD实验5028.png
图20.1.3.4 ESP32-P4与SPI接口显示屏模块连接图

下面通过下表说明一下引脚关系。

1.png
表20.1.3.2 引脚说明

以上引脚对应着模块引出的引脚,即图20.1.3.3中的2*4的引脚,只不过上表没有把电源和地描述出来。
上述的两个模块的驱动方式是一样的,只不过是初始化代码存在不同以及分辨率不同而已。
ST7789和ST7789V在使用方法上相差无几,所以下面会以ST7789V作为介绍。
显示模块采用ST7789V作为LCD驱动器,LCD的显存可直接存放在ST7789V的片上RAM中,ST7789V的片上RAM有240*320*3字节,并且ST7789V会在没有外部时钟的情况下,自动将其片上RAM的数据显示至LCD上,以最小化功耗。
ST7789V最高支持18位色深(262K色),但一般在显示模块上使用16位色深(65K色)的RGB565格式,这样可以在16位色深下达到最快的速度。在16位色深模式下,ST7789V采用RGB565格式传输、存储颜色数据,如下图所示。


第二十章 SPILCD实验5628.png
图20.1.3.5 16位色深模式(RGB565)传输颜色数据

如上图所示,是一个传输像素数据的时序过程,D/CX线即前面提及的WR线需要拉高,表示传输的是数据。一个像素的颜色数据需要使用16比特来传输,这16比特数据中,高5比特用于表示红色,低5比特用于表示蓝色,中间的6比特用于表示绿色。数据的数值越大,对应表示的颜色就越深。
ST7789V支持连续读写RAM中存放的LCD上颜色对应的数据,并且连续读写的方向(LCD的扫描方向)是可以通过命令0x36进行配置的,如下图所示。


第二十章 SPILCD实验5871.png
图20.1.3.6 命令0x36

从上图中可以看出,命令0x36可以配置6个参数,但对于配置LCD的扫描方向,仅需关心MY、MX和MV这三个参数,如下表所示。

2.png
表20.1.3.3 命令0x36配置LCD扫描方向

这样,我们在使用ST7789V显示内容的时候,就有很大灵活性了,比如显示BMP图片,BMP解码数据,就是从图片的左下角开始,慢慢显示到右上角,如果设置LCD扫描方向为从左到右,从下到上,那么我们只需要设置一次坐标,然后就不停的往LCD填充颜色数据即可,这样可以大大提高显示速度。
在往ATK-MD0130和ATK-MD0240模块写入颜色数据前,还需要设置地址,以确定随后写入的颜色数据对应LCD上的哪一个像素,通过命令0x2A和命令0x2B可以分别设置ATK-MD0130和ATK-MD0240模块显示颜色数据的列地址和行地址,命令0x2A的描述,如下图所示。


第二十章 SPILCD实验6429.png
图20.1.3.7 命令0x2A

命令0x2B的描述,如下图所示。

第二十章 SPILCD实验6465.png
图20.1.3.8 命令0x2B

以默认的LCD扫描方式(从左到右,从上到下)为例,命令0x2A的参数XS和XE和命令0x2B的参数YS和YE就在LCD上确定了一个区域,在连读读写颜色数据时,ST7789V就会按照从左到右,从上到下的扫描方式读写设个区域的颜色数据。

20.2 硬件设计

20.2.1 例程功能
使用ESP32-P4底板的WIRELESS接口连接正点原子SPILCD模块(仅限SPI接口的显示屏模块),实现SPILCD模块的显示。通过把SPILCD模块插入底板上的WIRELESS接口,按下复位之后,就可以看到SPILCD模块不停地显示一些信息并不断切换底色。LED0闪烁用于提示程序正在运行。

20.2.2 硬件资源
1)LED灯
        LED        0        - IO51
2)XL9555
        IIC_SDA        - IO33
        IIC_SCL        - IO32
        EXIO_5        - SLCD_PWR
        EXIO_7        - SLCD_RST
3)SPILCD
        PWR        - EXIO_5(XL9555芯片IO)
        CS                - IO28
        SCK                - IO30
        SDA        - IO29
        WR                - IO31
        RST                - EXIO_7(XL9555芯片IO)

20.2.3 原理图
SPILCD原理图,如下图所示。

第二十章 SPILCD实验7018.png
图20.2.3.1 SPILCD原理图

从上图可知,SPILCD的CS、SCK、MOSI和MISO信号线是直接连接到ESP32-P4的IO上,而PWR和RST引脚连接到的XL9555的IO上。所以要驱动SPILCD,必定先驱动好XL9555。

20.3 程序设计

20.3.1 LCD的IDF驱动
LCD外设驱动位于ESP-IDF下的components/esp_lcd目录下。esp_lcd驱动程序主要包括两部分:panel驱动和io驱动。panel驱动主要就是对帧缓冲区的一些操作,而io驱动主要是基于控制器对LCD驱动芯片的驱动,包括I2C、SPI(QSPI)、I80、 RGB和MIPI接口。要使用esp_lcd功能,需要导入一下头文件:
  1. <font size="3">#include "esp_lcd_panel_interface.h"        /* LCD面板结构体类型 */</font>
  2. <font size="3">#include "esp_lcd_panel_io.h"                        /* 驱动芯片接收/发送命令,发送颜色数据等函数 */</font>
  3. <font size="3">#include "esp_lcd_panel_vendor.h"                /* 包含LCD外设驱动支持的几款驱动芯片 */</font>
  4. <font size="3">#include "esp_lcd_panel_ops.h"                        /* LCD设备接口函数(reset/init/del等) */</font>
  5. <font size="3">#include "esp_lcd_panel_commands.h"        /* LCD驱动芯片的命令 */</font>
复制代码
SPI LCD 驱动流程可大致分为三个部分:初始化接口设备、移植驱动组件和初始化 LCD 设备。
接下来,作者就按照这三个部分分别介绍用到的函数。
初始化接口设备
初始化接口设备需要先初始化总线,再创建接口设备。
1,初始化SPI总线函数spi_bus_initialize
该函数用于初始化SPI总线,如果有多个设备同时使用同一SPI总线,那么只需要对总线初始化一次,其函数原型如下:
  1. esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan)
复制代码
函数形参:

3.png
表20.3.1.1 spi_bus_initialize函数形参描述

函数返回值:
ESP_OK表示SPI总线初始化成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_INVALID_STATE表示主机已经在使用。
ESP_ERR_NOT_FOUND表示没有空闲的DMA通道。
ESP_ERR_NO_MEM表示内存不足。
host_id为SPI控制器编号,spi_host_device_t是一个枚举类型,如下代码所示:
  1. typedef enum {
  2.     SPI1_HOST=0,    /* SPI1(只有在ESP32中才能使用) */
  3.     SPI2_HOST=1,    /* SPI2 */
  4. #if SOC_SPI_PERIPH_NUM > 2
  5.     SPI3_HOST=2,    /* SPI3 */
  6. #endif
  7.     SPI_HOST_MAX,   /* 非法 */
  8. } spi_host_device_t;
复制代码
spi_host_device_t枚举类型,提供了三个SPI控制器SPI1_HOST/SPI2_HOST/SPI3_HOST,分别对应SPI1/SPI2/SPI3。对于ESP32-P4来说,可以选SPI2_HOST或SPI3_HOST,不能选择SPI1_HOST。SPI1在前面也提及到,是flash和psram使用的。
bus_config为指向spi总线配置结构体的指针,spi_bus_config_t结构体中包含很多成员,如下代码所示。
  1. typedef struct {
  2.     union {
  3.         int mosi_io_num;            /* SPI总线的主机输出从机输入引脚 */
  4.         int data0_io_num;           /* QSPI总线的数据引脚0 */
  5.     };
  6.     union {
  7.         int miso_io_num;            /* SPI总线的主机输入从机输出引脚 */
  8.         int data1_io_num;           /* QSPI总线的数据引脚1 */
  9.     };
  10.     int sclk_io_num;                      /* SPI总线的时钟引脚 */
  11.     union {
  12.         int quadwp_io_num;          /* 写保护引脚 */
  13.         int data2_io_num;           /* QSPI总线的数据引脚2 */
  14.     };
  15.     union {
  16.         int quadhd_io_num;          /* 暂停通信引脚 */
  17.         int data3_io_num;           /* QSPI总线的数据引脚3 */
  18.     };
  19.     int data4_io_num;                     /* OSPI总线的数据引脚4 */
  20.     int data5_io_num;                     /* OSPI总线的数据引脚5 */
  21.     int data6_io_num;                     /* OSPI总线的数据引脚6 */
  22.     int data7_io_num;                     /* OSPI总线的数据引脚7 */
  23.     int max_transfer_sz;                  /* 最大传输长度,字节为单位(启用DMA,默认4092) */
  24.     uint32_t flags;                       /* 驱动检查总线的标志 */
  25.     esp_intr_cpu_affinity_t  isr_cpu_id;    /* 选择中断挂载的内核 */
  26.     int intr_flags;                       /* 设置总线优先级 */
  27. } spi_bus_config_t;
复制代码
spi_bus_config_t结构体的成员在本实验中并非全部都需要配置,只需要对sclk_io_num、mosi_io_num、miso_io_num、quadwp_io_num、quadhd_io_num和max_transfer_sz这六个成员做配置即可。
这里有两点需要注意:① 由于SPILCD是使用1根数据线去传输数据的,并不需要读取数据,所以miso_io_num成员可以赋值为GPIO_NUM_NC。② SPI传输数据前会对输入数据量大小进行判断,若单次传输的字节数超过max_transfer_sz就会报错。由于esp_lcd驱动也会提前判断输入的数据量是否超过限制,如果超过则进行分包处理后才控制SPI进行多次传输,因此max_transfer_sz的设置也不太受限制,参考例程代码设置即可。
dma_chan为SPI的DMA通道,而spi_dma_chan_t其实对应spi_common_dma_t,它是一个枚举类型,如下代码所示:
  1. typedef enum {
  2.     SPI_DMA_DISABLED = 0,     /* SPI不使用DMA */
  3. #if CONFIG_IDF_TARGET_ESP32
  4.     SPI_DMA_CH1      = 1,     /* 启动DMA,选择DMA通道1 */
  5.     SPI_DMA_CH2      = 2,     /* 启动DMA,选择DMA通道2 */
  6. #endif
  7.     SPI_DMA_CH_AUTO  = 3,     /* 启动DMA,自动选择通道 */
  8. } spi_common_dma_t;
复制代码
一般来说,这里直接选择SPI_DMA_CH_AUTO即可。
2,创建接口设备esp_lcd_new_panel_io_spi
该函数用于创建SPI接口LCD IO设备,把SPI和LCD驱动关联起来,其函数原型如下:
  1. esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
复制代码
函数形参:

4.png
表20.3.1.2 esp_lcd_new_panel_io_spi函数形参描述

函数返回值:
ESP_OK表示创建接口设备成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_NO_MEM表示内存不足。
bus为SPI控制器编号,需要与spi_bus_initialize函数中设置的要一致。
io_config为指向SPI总线的LCD IO设备配置结构体的指针,esp_lcd_panel_io_spi_config_t结构体中包含很多成员,如下代码所示。
  1. typedef struct {
  2.     int cs_gpio_num;                                 /* CS信号线的GPIO编号 */
  3.     int dc_gpio_num;                                 /* DC信号线的GPIO编号 */
  4.     int spi_mode;                                    /* SPI模式,参考ST7789手册 */
  5.     unsigned int pclk_hz;                    /* 像素时钟频率 */
  6.     size_t trans_queue_depth;                 /* SPI传输队列的深度 */
  7.     esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /* 传输完成回调 */
  8.     void *user_ctx;                                    /* 传输给on_color_trans_done函数的参数 */
  9.     int lcd_cmd_bits;                           /* LCD驱动芯片可识别的命令位宽 */
  10.     int lcd_param_bits;                         /* LCD驱动芯片可识别的参数位宽 */
  11.     uint8_t cs_ena_pretrans;       /* SPI传输前激活CS信号线的SPI位周期数 */
  12.     uint8_t cs_ena_posttrans;      /* SPI传输前保持激活CS信号线的SPI位周期数 */
  13.     struct {
  14.         unsigned int dc_high_on_cmd: 1;          /* 使能,DC为高电平,表示命令传输 */
  15.         unsigned int dc_low_on_data: 1;          /* 使能,DC为低电平,表示颜色数据传输 */
  16.         unsigned int dc_low_on_param: 1;         /* 使能,DC为低电平,表示参数传输 */
  17.         unsigned int octal_mode: 1;              /* 传输采用8根数据线,用来模拟8080时序 */
  18.         unsigned int quad_mode: 1;               /* QSPI模式传输 */
  19.         unsigned int sio_mode: 1;                /* 单根数据线MOSI进行读写 */
  20.         unsigned int lsb_first: 1;               /* 先传输低位 */
  21.         unsigned int cs_high_active: 1;          /* CS引脚的有效电平 */
  22.     } flags;                                                                 /* SPI设备额外的标志 */
  23. } esp_lcd_panel_io_spi_config_t;
复制代码
esp_lcd_panel_io_spi_config_t结构体的成员在本实验中并非全部都需要配置,只需要对dc_gpio_num、cs_gpio_num、pclk_hz、lcd_cmd_bits、lcd_param_bits、spi_mode和trans_queue_depth这七个成员做配置即可。
这里有一点需要注意:lcd_cmd_bits、lcd_param_bits和spi_mode都需要根据LCD驱动芯片实际情况设置,比如ST7789命令和参数位宽都是8位,而SPI模式是支持模式0和模式3的。
ret_io为指向LCD IO设备句柄的结构体指针,esp_lcd_panel_io_handle_t实际是esp_lcd_panel_io_t,该结构体中包含一些函数接口,用于给LCD驱动芯片发送命令和图像数据,如下代码所示。
  1. struct esp_lcd_panel_io_t {
  2. /* 发送单个LCD命令并接收响应参数 */
  3. esp_err_t (*rx_param)(esp_lcd_panel_io_t *io, int lcd_cmd,
  4. void *param, size_t param_size);                                                
  5. /* 发送单个LCD命令及配套参数 */
  6. esp_err_t (*tx_param)(esp_lcd_panel_io_t *io, int lcd_cmd,
  7. const void *param, size_t param_size);                                
  8. /* 发送单次LCD刷屏命令和图像数据 */
  9. esp_err_t (*tx_color)(esp_lcd_panel_io_t *io, int lcd_cmd,
  10. const void *color, size_t color_size);                                
  11. esp_err_t (*del)(esp_lcd_panel_io_t *io);        /* 卸载LCD IO设备句柄 */
  12. /* 注册LCD IO设备回调 */
  13. esp_err_t (*register_event_callbacks)(esp_lcd_panel_io_t *io,
  14. const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
  15. };
复制代码
在esp_lcd_new_panel_io_spi函数内部就会将SPI底层接口与ret_io绑定起来,后续直接访问ret_io即可调用到SPI底层接口,比如panel_io_spi_rx_param、panel_io_spi_tx_param、panel_io_spi_tx_color、panel_io_spi_del和panel_io_spi_register_event_callbacks。
移植驱动组件
移植SPI LCD驱动组件的基本原理包含以下三点:
1、基于数据类型为esp_lcd_panel_io_handle_t的接口设备句柄发送指定格式的命令及参数
2、实现并创建一个LCD设备,然后通过注册回调函数的方式实现结构体esp_lcd_panel_t中的各项功能
3、实现一个函数用于提供数据类型为esp_lcd_panel_handle_t的LCD设备句柄,使得应用程序能够利用LCD通用API来操作LCD设备
第一点已经在前面创建接口设备时完成了,可以调用SPI底层接口。而第二和第三点需要用到esp_lcd_new_panel_st7789函数去实现。乐鑫已经在LCD驱动组件中支持了st7789芯片,所以esp_lcd_new_panel_st7789不用自己编写代码实现,否则需要自己去实现对应函数。关于st7789的驱动文件,可以从“components/esp_lcd/src”目录下找到,如下图所示。


第二十章 SPILCD实验13685.png
图20.3.1.1 LCD驱动组件中st7789.c文件

esp_lcd_panel_st7789.c文件里面的内容,大家自行查看即可,这里就不做介绍了。
接下来,介绍一下esp_lcd_new_panel_st7789函数。
3,为ST7789创建LCD面板esp_lcd_new_panel_st7789
该函数用于为LCD驱动芯片ST7789创建LCD面板并对LCD设备配置,其函数原型如下:
  1. esp_err_t esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io,
  2. const esp_lcd_panel_dev_config_t *panel_dev_config,
  3. esp_lcd_panel_handle_t *ret_panel)
复制代码
函数形参:

5.png
表20.3.1.3 esp_lcd_new_panel_st7789函数形参描述

函数返回值:
ESP_OK表示创建成功。
其他表示异常。
io为LCD接口句柄结构体esp_lcd_panel_io_t,该结构体在前面已经介绍了,这里不再展开。
panel_dev_config为指向LCD设备配置结构体指针,esp_lcd_panel_dev_config_t结构体中包含几个成员,如下代码所示。
  1. typedef struct {
  2.     int reset_gpio_num;                                                 /* 连接LCD复位信号的引脚 */
  3.     union {
  4.         esp_lcd_color_space_t color_space;           /* RGB色彩空间,rgb_ele_order代替 */
  5.         lcd_color_rgb_endian_t rgb_endian;           /* 设置数据端序,rgb_ele_order代替 */
  6.         lcd_rgb_element_order_t rgb_ele_order;        /* 像素色彩的元素顺序(RGB/BGR) */
  7.     };
  8.     lcd_rgb_data_endian_t data_endian;                /* 设置>1字节的颜色数据的数据端序 */
  9.     uint32_t bits_per_pixel;                            /* 色彩格式的位数 */
  10.     struct {
  11.         uint32_t reset_active_high: 1;                 /* 复位引脚有效电平 */
  12.     } flags;                           
  13.     void *vendor_config;                                                 /* 用于替换驱动组件的初始化序列 */
  14. } esp_lcd_panel_dev_config_t;
复制代码
esp_lcd_panel_dev_config_t结构体需要注意以下几点:
①当发生颜色显示不对,比如想显示红色,最终是蓝色,这是像素色彩的元素顺序问题,通过调整rgb_ele_order的赋值。
②颜色显示异常,并非①中描述的情况,除了白色、黑色显示出来,这是由于颜色数据的发送顺序错误,通过调整data_endian的赋值
③若手上的屏幕驱动起来,色彩有点偏差,可通过vendor_config把厂家提供的初始化序列填充进去
④若不是通过芯片的IO作为复位引脚,reset_gpio_num直接赋值GPIO_NUM_NC即可,硬件复位就得自己去实现
ret_panel为指向LCD设备句柄结构体指针,esp_lcd_panel_handle_t其实是esp_lcd_panel_t,如下代码所示。
  1. struct esp_lcd_panel_t {
  2. esp_err_t (*reset)(esp_lcd_panel_t *panel);        /* LCD屏幕复位 */
  3. esp_err_t (*init)(esp_lcd_panel_t *panel);                /* LCD屏幕初始化 */
  4. esp_err_t (*del)(esp_lcd_panel_t *panel);                /* 卸载LCD屏幕 */
  5. esp_err_t (*draw_bitmap)(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);                /* LCD屏幕绘画函数 */
  6. /* LCD屏幕镜像X轴和Y轴 */
  7. esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
  8. /* LCD屏幕交换X轴和Y轴 */
  9. esp_err_t (*swap_xy)(esp_lcd_panel_t *panel, bool swap_axes);
  10. /* LCD屏幕设置画图的起始坐标 */
  11. esp_err_t (*set_gap)(esp_lcd_panel_t *panel, int x_gap, int y_gap);
  12. /* LCD屏幕像素颜色数据按位取反(0xF0F0->0x0F0F),即反显功能 */
  13. esp_err_t (*invert_color)(esp_lcd_panel_t *panel, bool invert_color_data);
  14. /* LCD屏幕显示开关 */
  15. esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
  16. /* LCD屏幕休眠开关 */
  17.     esp_err_t (*disp_sleep)(esp_lcd_panel_t *panel, bool sleep);
  18. void *user_data;    /* 用户数据,用于存储外部自定义数据 */
  19. };
复制代码
在esp_lcd_new_panel_st7789函数内部就会将ST7789实现的接口绑定到ret_panel,这样子通过ret_panel结构体就能调用到ST7789功能接口,进而实现对ST7789初始化,进行画点。
假如在乐鑫的LCD驱动组件中找不到要驱动的LCD驱动芯片,请借鉴以下步骤完成移植:
①在LCD驱动组件中选择一个型号相似的SPI LCD驱动组件,比如要驱动ST7796,这时候就需要选择名字相似的,比如ST7789,这样子很多命令都能适用。
②查阅目标LCD驱动芯片的数据手册(重要!!!),比对一下所选组件中的各功能使用到的命令及参数是否一致,若不一致就需要修改相关代码。
③即使LCD驱动芯片型号相同,不同厂家对屏幕的调试参数都存在差别,一般来说需要用厂家提供的初始化序列进行配置。
④使用搜索和替换功能,把组件中的LCD驱动芯片名称修改为目标名称,比如ST7789修改为ST7796。
简单来说,不要着急,对着借鉴的驱动代码,看着需要适配芯片的数据手册,涉及到命令及参数,查看是否一致即可。
初始化LCD设备
前面已经完成了准备工作,这里就可以调用LCD设备的函数,对ST7789驱动芯片进行初始化,如下代码所示。
  1.     /* 复位屏幕 */
  2. ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
  3.     /* 打开显示反显功能 */
  4. ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
  5.     /* 初始化LCD设备 */
  6.     ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
  7.     /* 打开屏幕显示 */
  8.     ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
复制代码
通过以上流程便可以把ST7789驱动起来,特别注意:
①esp_lcd_panel_disp_on_off函数的第二个参数要传递true,不然是不会显示的。
②esp_lcd_panel_reset函数会进行复位操作,若复位引脚不存在就通过软件指令复位。
③使用esp_lcd_panel_draw_bitmap函数刷新SPI LCD图像,传入颜色数据的字节数可以大于max_transfer_sz,esp_lcd驱动会进行分包处理。函数内部使用DMA方式传输数据,调用函数后,数据仍在通过DMA搬运,此时就不能修改正在使用的缓存区域,所以可以通过esp_lcd_panel_io_register_event_callbacks注册回调函数来判断上次传输是否完成。
初始化LCD设备完成之后,就得编写一些LCD操作的函数,比如设置横竖屏、画点、画线、画图形、显示字符等。
LCD绘图函数
这里简单介绍基础但非常重要的绘图函数esp_lcd_panel_draw_bitmap,其函数原型如下:
  1. esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
复制代码
函数形参:

6.png
表20.3.1.4 esp_lcd_panel_draw_bitmap函数形参描述

函数返回值:
ESP_OK表示操作成功。
其他表示异常。

20.3.2 程序流程图

第二十章 SPILCD实验17849.png
图20.3.2.1 SPILCD实验程序流程图

20.3.3 程序解析
在10_spilcd例程中,作者在10_spilcd\components\BSP路径下新建SPILCD文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. SPILCD驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。SPILCD驱动源码包括三个文件:spilcd.c、spilcd.h和spilcdfont.h。
spilcd.c文件存放的是SPILCD的驱动函数,而spilcd.h存放的是引脚接口宏定义以及函数声明等,spilcdfont.h存放的是4种字体大小不一样的ASCII字符集(12*12、16*16、24*24和32*32)。
下面先解析spilcd.h的程序。对SPILCD相关引脚以及SPI控制器编号做了定义。
  1. <font size="3">#define LCD_HOST                  SPI2_HOST                /* SPI控制器编号 */</font>
  2. <font size="3">#define LCD_SCLK_PIN             GPIO_NUM_30                /* SPILCD的时钟线 */</font>
  3. <font size="3">#define LCD_MOSI_PIN             GPIO_NUM_29                /* SPILCD的数据线 */</font>
  4. <font size="3">#define LCD_MISO_PIN             GPIO_NUM_NC                /* 不使用 */</font>
  5. <font size="3">#define LCD_RST_PIN              GPIO_NUM_NC                /* 不使用 */</font>
  6. <font size="3">#define LCD_PWR_PIN              GPIO_NUM_NC                /* 不使用 */</font>
  7. <font size="3">#define LCD_DC_PIN              GPIO_NUM_31                /* SPILCD的命令数据选择线 */</font>
  8. <font size="3">#define LCD_CS_PIN               GPIO_NUM_28                /* SPILCD的片选线 */</font>
复制代码
SPILCD的背光控制引脚以及复位引脚均不是使用ESP32-P4的引脚,而连接到XL9555的引脚。由于在xl9555_init函数中已经对PWR和RST设置为输出模式并默认输出高电平,所以在spilcd_init函数中,可以不对这两个引脚进行操作。
下面介绍一下在spilcd.h里面定义的一个重要结构体,方便存储一些LCD参数信息:
  1. <font size="3">typedef struct  </font>
  2. <font size="3">{</font>
  3. <font size="3">    uint32_t pwidth;            /* 临时设定值(宽度) */</font>
  4. <font size="3">    uint32_t pheight;           /* 临时设定值(高度) */</font>
  5. <font size="3">    uint8_t dir;                /* 屏幕方向 */</font>
  6. <font size="3">    uint16_t width;             /* 宽度 */</font>
  7. <font size="3">    uint16_t height;            /* 高度 */</font>
  8. <font size="3">} _spilcd_dev;</font>
复制代码
通过该结构体保存屏幕的宽度、高度和屏幕方向,利于进行屏幕管理。声明_spilcd_dev结构体类型变量spilcddev在spilcd.c中实现。
在spilcd.c文件中,为了兼容两种分辨率的SPI屏幕也定义了一些宏和全局变量,代码如下:
  1. <font size="3">#define SPI_LCD_TYPE    1         /* SPI接口屏幕类型(1:2.4寸SPILCD  0:1.3寸SPILCD) */ </font>

  2. <font size="3">/* LCD的宽和高定义 */</font>
  3. <font size="3">#if SPI_LCD_TYPE                    /* 2.4寸SPI_LCD屏幕 */</font>
  4. <font size="3">uint16_t spilcd_width  = 320;       /* 屏幕的宽度 320(横屏) */</font>
  5. <font size="3">uint16_t spilcd_height = 240;       /* 屏幕的宽度 240(横屏) */</font>
  6. <font size="3">#else</font>
  7. <font size="3">uint16_t spilcd_width  = 240;       /* 屏幕的宽度 240(横屏) */</font>
  8. <font size="3">uint16_t spilcd_height = 240;       /* 屏幕的宽度 240(横屏) */</font>
  9. <font size="3">#endif                              /* 1.3寸SPI_LCD屏幕 */</font>
复制代码
大家需要根据屏幕情况,设置好SPI_LCD_TYPE宏,当屏幕是2.4寸时,该宏要设为1;若为1.3寸时,该宏要设为0。
下面,首先看一下初始化函数spilcd_init,代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief            spilcd初始化</font>
  3. <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url]            无</font>
  4. <font size="3"> * @retval            ESP_OK:初始化成功</font>
  5. <font size="3"> */</font>
  6. <font size="3">esp_err_t spilcd_init(void)</font>
  7. <font size="3">{</font>
  8. <font size="3">    spi_bus_config_t buscfg = {</font>
  9. <font size="3">        .sclk_io_num     = LCD_SCLK_PIN,        /* 时钟引脚 */</font>
  10. <font size="3">        .mosi_io_num     = LCD_MOSI_PIN,        /* 主机输出从机输入引脚 */</font>
  11. <font size="3">        .miso_io_num     = LCD_MISO_PIN,         /* 主机输入从机输出引脚 */</font>
  12. <font size="3">        .quadwp_io_num   = -1,                 /* 未使用时设置为-1 */</font>
  13. <font size="3">        .quadhd_io_num   = -1,                 /* 未使用时设置为-1 */</font>
  14. <font size="3">                /* 最大传输大小 */</font>
  15. <font size="3">        .max_transfer_sz = spilcd_width * spilcd_height * sizeof(uint16_t),        </font>
  16. <font size="3">    };</font>
  17. <font size="3">    /* 初始化SPI总线 */</font>
  18. <font size="3">    ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));</font>

  19. <font size="3">    esp_lcd_panel_io_handle_t io_handle = NULL;             /* LCD IO设备句柄 */</font>
  20. <font size="3">    esp_lcd_panel_io_spi_config_t io_config = {                /* spi接口的LCD配置 */</font>
  21. <font size="3">        .dc_gpio_num         = LCD_DC_PIN,                  /* DC IO */</font>
  22. <font size="3">        .cs_gpio_num         = LCD_CS_PIN,                  /* CS IO */</font>
  23. <font size="3">        .pclk_hz             = 80 * 1000 * 1000,                  /* PCLK为80MHz */</font>
  24. <font size="3">        .lcd_cmd_bits        = 8,                           /* 命令位宽 */</font>
  25. <font size="3">        .lcd_param_bits      = 8,                           /* LCD参数位宽 */</font>
  26. <font size="3">        .spi_mode            = 0,                           /* SPI模式 */</font>
  27. <font size="3">        .trans_queue_depth   = 7,                           /* 传输队列 */</font>
  28. <font size="3">    };</font>
  29. <font size="3">    /* 将LCD设备挂载至SPI总线上 */</font>
  30. <font size="3">    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));</font>

  31. <font size="3">    spilcddev.pheight = spilcd_height;  /* 高度 */</font>
  32. <font size="3">    spilcddev.pwidth  = spilcd_width;   /* 宽度 */</font>

  33. <font size="3">    /* LCD设备配置 */</font>
  34. <font size="3">    esp_lcd_panel_dev_config_t panel_config = {</font>
  35. <font size="3">        .reset_gpio_num = LCD_RST_PIN,                          /* RTS IO */</font>
  36. <font size="3">        .rgb_ele_order  = COLOR_RGB_ELEMENT_ORDER_RGB,          /* RGB颜色格式 */</font>
  37. <font size="3">        .bits_per_pixel = 16,                                  /* 颜色深度 */</font>
  38. <font size="3">        .data_endian    = LCD_RGB_DATA_ENDIAN_LITTLE,                  /* 颜色数据的端序 */</font>
  39. <font size="3">    };</font>
  40. <font size="3">    /* 为ST7789创建LCD面板句柄,并指定SPI IO设备句柄 */</font>
  41. <font size="3">    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));</font>
  42. <font size="3">    /* 复位LCD */</font>
  43. <font size="3">ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));</font>

  44. <font size="3">    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));</font>

  45. <font size="3">    /* 初始化LCD句柄 */</font>
  46. <font size="3">    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));</font>

  47. <font size="3">    /* 打开屏幕 */</font>
  48. <font size="3">    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));</font>

  49. <font size="3">    spilcd_display_dir(1);      /* 横屏显示 */</font>
  50. <font size="3">    </font>
  51. <font size="3">    spilcd_clear(WHITE);        /* 清屏 */</font>

  52. <font size="3">    return ESP_OK;</font>
  53. <font size="3">}</font>
复制代码
在spilcd_init函数中,首先调用spi_bus_initialize函数对SPI总线进行初始化,再通过调用esp_lcd_new_panel_io_spi函数创建SPI接口的LCD设备,继续调用 esp_lcd_new_panel_st7789函数创建ST7789的LCD设备,最后通过esp_lcd_panel_init函数对LCD进行初始化。
下面介绍设置屏幕方向的函数,该函数代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief            设置屏幕方向</font>
  3. <font size="3"> * @param             dir: 0为竖屏,1为横屏</font>
  4. <font size="3"> * @retval           无</font>
  5. <font size="3"> */</font>
  6. <font size="3">void spilcd_display_dir(uint8_t dir)</font>
  7. <font size="3">{</font>
  8. <font size="3">    spilcddev.dir = dir;</font>

  9. <font size="3">    if (spilcddev.dir == 0)         /* 竖屏 */</font>
  10. <font size="3">    {</font>
  11. <font size="3">        spilcddev.width = spilcddev.pheight;</font>
  12. <font size="3">        spilcddev.height = spilcddev.pwidth;</font>
  13. <font size="3">        esp_lcd_panel_swap_xy(panel_handle, false);</font>
  14. <font size="3">        esp_lcd_panel_mirror(panel_handle, true, true);</font>

  15. <font size="3">        if (SPI_LCD_TYPE == 0)</font>
  16. <font size="3">        {</font>
  17. <font size="3">            esp_lcd_panel_set_gap(panel_handle, 0, 80);</font>
  18. <font size="3">        }</font>
  19. <font size="3">    }</font>
  20. <font size="3">    else if (spilcddev.dir == 1)    /* 横屏 */</font>
  21. <font size="3">    {</font>
  22. <font size="3">        spilcddev.width = spilcddev.pwidth;</font>
  23. <font size="3">        spilcddev.height = spilcddev.pheight;</font>
  24. <font size="3">        esp_lcd_panel_swap_xy(panel_handle, true);</font>
  25. <font size="3">        esp_lcd_panel_mirror(panel_handle, false, true);</font>

  26. <font size="3">        if (SPI_LCD_TYPE == 0)</font>
  27. <font size="3">        {</font>
  28. <font size="3">            esp_lcd_panel_set_gap(panel_handle, 80, 0);</font>
  29. <font size="3">        }</font>
  30. <font size="3">    }</font>
  31. <font size="3">}</font>
复制代码
该函数主要就是通过调用esp_lcd_panel_swap_xy函数和esp_lcd_panel_mirror函数去实现,前者将X轴和Y轴交换,而后者就是对X轴或Y轴进行镜像。特别注意:1.3寸屏需要通过esp_lcd_panel_set_gap函数进行偏移设置。
接下来介绍画点函数,该函数代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief            绘画一个像素点</font>
  3. <font size="3"> * @param            x,y  : 坐标</font>
  4. <font size="3">* @param            color: 颜色值</font>
  5. <font size="3"> * @retval                 无</font>
  6. <font size="3"> */</font>
  7. <font size="3">void spilcd_draw_point(uint16_t x, uint16_t y, uint16_t color)</font>
  8. <font size="3">{</font>
  9. <font size="3">    esp_lcd_panel_draw_bitmap(panel_handle, x, y, x + 1, y + 1, &color);</font>
  10. <font size="3">}</font>
复制代码
该函数直接调用esp_lcd_panel_draw_bitmap函数进行实现。该函数只能画一个像素点,假如向一片区域填充同样的颜色,调用这个函数的填充效果是非常差的,效率很低。
继续介绍区域填充函数spilcd_fill,该函数代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief            在指定区域内填充单个颜色</font>
  3. <font size="3"> * @param           (sx,sy),(ex,ey):填充矩形对角坐标,区域大小为<img src="static/image/smiley/default/sad.gif" border="0" smilieid="2" alt=":(">ex-sx+1) * (ey-sy+1)</font>
  4. <font size="3"> * @param           color:要填充的颜色</font>
  5. <font size="3"> * @retval          无</font>
  6. <font size="3"> */</font>
  7. <font size="3">void spilcd_fill(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, </font>
  8. <font size="3">uint16_t color)</font>
  9. <font size="3">{</font>
  10. <font size="3">    uint16_t width = ex - sx + 1;</font>
  11. <font size="3">    uint16_t height = ey - sy + 1;</font>

  12. <font size="3">    uint16_t *buffer = heap_caps_malloc(spilcddev.width * sizeof(uint16_t), MALLOC_CAP_INTERNAL);</font>
  13. <font size="3">    if (NULL == buffer)</font>
  14. <font size="3">    {</font>
  15. <font size="3">        ESP_LOGE("TAG", "Memory for bitmap is not enough");</font>
  16. <font size="3">    }</font>
  17. <font size="3">    else</font>
  18. <font size="3">    {</font>
  19. <font size="3">        for (uint16_t i = 0; i < width; i++)</font>
  20. <font size="3">        {</font>
  21. <font size="3">            buffer</font><span style="font-size: medium;"> = color;
  22.         }
  23.         for (uint16_t y_index = sy; y_index < height; y_index++)
  24.         {
  25.             esp_lcd_panel_draw_bitmap(panel_handle, sx, y_index, ex + 1,
  26. y_index + 1, buffer);
  27.         }
  28. }
  29. heap_caps_free(buffer);
  30. }</span>
复制代码
该函数的实现同样也是调用esp_lcd_panel_draw_bitmap函数,只不过在调用该函数之前,事先申请一个buf,buf的大小就是这片区域一行像素的字节数,那每次就会填充一行,效率会比spilcd_draw_point要高。最后,记得申请了内存,用完就要释放内存,不然造成内存浪费。
SPILCD屏幕的清屏函数spilcd_clear与spilcd_fill函数实现的思路是一样的,清屏函数直接会把X轴和Y轴的起始、结束地址都确定,接着申请内存,内存的大小可根据实际内存情况决定,例程中直接申请40行像素点大小,然后填充数据,调用esp_lcd_panel_draw_bitmap函数进行区域填充,实测帧率大概在60帧。
最后介绍一下字符显示函数spilcd_show_char,该函数代码如下:
  1. /**
  2. * @brief            在指定位置显示一个字符
  3. * @param            x,y  : 坐标
  4. * @param           chr  : 要显示的字符:" "--->"~"
  5. * @param             size : 字体大小 12/16/24/32
  6. * @param             mode : 叠加方式(1); 非叠加方式(0);
  7. * @param             color : 字符的颜色;
  8. * @retval            无
  9. */
  10. void spilcd_show_char(uint16_t x, uint16_t y, uint8_t chr, uint8_t size,
  11. uint8_t mode, uint16_t color)
  12. {
  13.     const uint8_t *ch_code;     /* 存放chr字符对应数组的首地址 */
  14.     uint8_t ch_width;           /* 字符的宽度 */
  15.     uint8_t ch_height;          /* 字符的高度 */
  16.     uint8_t ch_size;            /* 字符的大小(字节) */
  17.     uint8_t ch_offset;          /* 字符在字库的相对位置 */
  18.     uint8_t byte_index;         /* 字符对应数据的索引值 */
  19.     uint8_t byte_code;          /* 字符对应数据 */
  20.     uint8_t bit_index;          /* 字符对应字节数据的位索引 */
  21.     uint16_t colortemp = 0;     /* 颜色数据 */
  22.     uint16_t pix_index = 0;
  23.     uint16_t *pcolor = NULL;

  24.     /* 字体大小(字节) =       字体宽度占用字体大小              * 字体高度 */
  25.     ch_size = ((size / 2) / 8 +  (((size / 2) % 8) ? 1 : 0)) * size;        

  26.     pcolor = heap_caps_malloc(size * size * 2, MALLOC_CAP_INTERNAL);        
  27.     if (NULL == pcolor)
  28.     {
  29.         ESP_LOGE("TAG", "Memory for bitmap is not enough");
  30. }

  31. /* 得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库) */
  32.     ch_offset = chr - ' ';        
  33.    
  34.     switch (size)
  35.     {
  36.         case 12:
  37.             ch_code = (uint8_t *)asc2_1206[ch_offset];                /* 调用1206字体 */
  38.             ch_width = 6;
  39.             ch_height = 12;
  40.             break;

  41.         case 16:
  42.             ch_code = (uint8_t *)asc2_1608[ch_offset];                /* 调用1608字体 */
  43.             ch_width = 8;
  44.             ch_height = 16;
  45.             break;

  46.         case 24:
  47.             ch_code = (uint8_t *)asc2_2412[ch_offset];                /* 调用2412字体 */
  48.             ch_width = 12;
  49.             ch_height = 24;
  50.             break;

  51.         case 32:
  52.             ch_code = (uint8_t *)asc2_3216[ch_offset];         /* 调用3216字体 */
  53.             ch_width = 16;
  54.             ch_height = 32;
  55.             break;

  56.         default:
  57.             return ;
  58.     }

  59.     if ((x + ch_width > spilcddev.width) || (y + ch_height > spilcddev.height))
  60.     {
  61.         return;
  62.     }

  63.     for (byte_index = 0; byte_index < ch_size; byte_index++)
  64.     {   
  65.         byte_code = ch_code[byte_index];                                          /* 获取字符的点阵数据 */

  66.         for (bit_index = 0; bit_index < 8; bit_index++)         /* 一个字节8个点 */
  67.         {
  68.             if ((byte_code & 0x80) != 0)                         /* 有效点,需要显示 */
  69.             {   
  70.                 colortemp = color;   
  71.             }
  72.             else if (mode == 0)
  73.             {
  74.                 colortemp = 0xFFFF;
  75.             }
  76.             pcolor[pix_index] = colortemp;
  77.             pix_index++;

  78. /* 24号字体比较特殊,奇数字节只有四位有效 */
  79.             if ((size == 24) && (byte_index % 2))   
  80.             {
  81.                 if (bit_index == 3)
  82.                 {
  83.                     break;
  84.                 }
  85.             }
  86.             byte_code <<= 1;        /* 移位, 以便获取下一个位的状态 */
  87.         }
  88.     }

  89.     esp_lcd_panel_draw_bitmap(panel_handle, x, y, x + ch_width, y + ch_height, (uint16_t *)pcolor);
  90.     heap_caps_free(pcolor);
  91. }
复制代码
在lcd_show_char函数里面,我们用到了四个字符集点阵数据数组asc2_1206、asc2_1608、asc2_2412和asc2_3216,通过参数font决定。此外该函数增加以叠加方式显示,或者以非叠加方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显示。为了提高显示效果,也是先把该字体要显示的区域颜色数据准备好,填充到buf里面,通过esp_lcd_panel_draw_bitmap函数一次发送。
这里需要注意:字体的取模方向会影响到点阵数据,要清楚知道是逐行还是逐列,才能知道每个字节的数据对应的是哪些像素点。简单来说,单片机要根据点阵数据按取模方向进行描点还原,才能显示字符。
注意:字符点阵数据的生成是依靠正点原子的XFONT软件,取模方式设置为:阴码+逐行式+顺向+C51格式,具体教程请参考正点原子任何一个《STM32开发指南》的OLED显示实验章节的程序解析处。
spilcd.c的函数比较多,其他函数请大家自行查看源码,都有详细的注释。
2. CMakeLists.txt文件
本例程的功能实现主要依靠SPILCD驱动。要在main函数中,成功调用SPILCD文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
  1. set(src_dirs
  2.                    LED
  3.                    MYIIC
  4.                    XL9555
  5. SPILCD)

  6. set(include_dirs
  7.                    LED
  8.                    MYIIC
  9.                   XL9555
  10. SPILCD)

  11. set(requires
  12.                   driver
  13.                         esp_lcd)

  14. idf_component_register(        SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

  15. component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码
3. main.c驱动代码
在main.c里面编写如下代码。
  1. void app_main(void)
  2. {
  3.     esp_err_t ret;
  4.     uint8_t x = 0;
  5.    
  6.     ret = nvs_flash_init();                /* 初始化NVS */
  7.     if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
  8.     {
  9.         ESP_ERROR_CHECK(nvs_flash_erase());
  10.         ESP_ERROR_CHECK(nvs_flash_init());
  11.     }
  12.     led_init();                         /* LED初始化 */
  13.     myiic_init();                       /* MYIIC初始化 */
  14.     xl9555_init();                      /* XL9555初始化 */
  15.     spilcd_init();                      /* SPILCD初始化 */

  16.     while(1)
  17.     {
  18.         switch (x)
  19.         {
  20.             case 0:
  21.             {
  22.                 spilcd_clear(WHITE);
  23.                 break;
  24.             }
  25.             case 1:
  26.             {
  27.                 spilcd_clear(BLACK);
  28.                 break;
  29.             }
  30.             case 2:
  31.             {
  32.                 spilcd_clear(BLUE);
  33.                 break;
  34.             }
  35.             case 3:
  36.             {
  37.                 spilcd_clear(RED);
  38.                 break;
  39.             }
  40.             case 4:
  41.             {
  42.                 spilcd_clear(MAGENTA);
  43.                 break;
  44.             }
  45.             case 5:
  46.             {
  47.                 spilcd_clear(GREEN);
  48.                 break;
  49.             }
  50.             case 6:
  51.             {
  52.                 spilcd_clear(CYAN);
  53.                 break;
  54.             }
  55.             case 7:
  56.             {
  57.                 spilcd_clear(YELLOW);
  58.                 break;
  59.             }
  60.             case 8:
  61.             {
  62.                 spilcd_clear(BRRED);
  63.                 break;
  64.             }
  65.             case 9:
  66.             {
  67.                 spilcd_clear(GRAY);
  68.                 break;
  69.             }
  70.             case 10:
  71.             {
  72.                 spilcd_clear(LGRAY);
  73.                 break;
  74.             }
  75.             case 11:
  76.             {
  77.                 spilcd_clear(BROWN);
  78.                 break;
  79.             }
  80.         }
  81.         spilcd_show_string(10, 40,  200, 32, 32, "ESP32-P4", RED);
  82.         spilcd_show_string(10, 80,  200, 24, 24, "SPILCD TEST", RED);
  83.         spilcd_show_string(10, 120, 200, 16, 16, "ATOM@ALIENTEK", RED);

  84.         x++;
  85.         if (x == 12)
  86.         {
  87.             x = 0;
  88.         }
  89.         LED0_TOGGLE();
  90.         vTaskDelay(pdMS_TO_TICKS(500));
  91.     }
  92. }
复制代码
app_main函数功能主要是显示一些固定的字符,字体大小包括32、24和16三种,然后不停的切换背景颜色,每500毫秒切换一次。而LED0也会不停地闪烁,指示程序已经在运行了。

20.4 下载验证
下载代码后,LED0不停地闪烁,提示程序已经在运行了。同时可以看到SPILCD屏幕模块显示背景色不停切换,如下图所示。

第二十章 SPILCD实验30999.png
图20.4.1 SPILCD显示效果图
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-12-24 19:33

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表