第二十二章 SPI_LCD实验

1)实验平台:正点原子DNESP32S3开发板

2)章节摘自【正点原子】ESP32-S3使用指南—IDF版 V1.6

3)购买链接:https://detail.tmall.com/item.htm?&id=768499342659

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32S3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子DNESP32S3开发板技术交流群:132780729

本章,我们将学习ESP32-S3的硬件SPI接口,将会大家如何使用SPI接口去驱动LCD屏。在本章中,实现和LCD屏之间的通信,实现ASCII字符、彩色、图片和图形的显示。
本章分为如下几个小节:
22.1 SPI与LCD简介
22.2 硬件设计
22.3 程序设计
22.4 下载验证

22.1 SPI与LCD简介

22.1.1 SPI介绍

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

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

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

22.1.2 SPI控制器介绍

ESP32-S3芯片集成了四个SPI控制器,分别为SPI0、SPI1、SPI2和SPI3。SPI0和SPI1控制器主要供内部使用以访问外部FLASH和PSRAM,所以只能使用SPI2和SPI3。SPI2又称为HSPI,而SPI3又称为VSPI,这两个属于GP-SPI。
GP-SPI特性:

   1,支持主机模式和从机模式
   2,支持半双工通信和全双工通信
   3,支持多种数据模式:

l SPI2:1-bit SPI模式、2-bit Dual SPI模式、4-bit Quad SPI模式、QPI模式、8-bit Octal模式、OPI模式
l SPI3:1-bit SPI模式、2-bit Dual SPI模式、4-bit Quad SPI模式、QPI模式

   4,时钟频率可配置:

l 在主机模式下:时钟频率可达80MHz
l 在从机模式下:时钟频率可达60MHz
5,数据位的读写顺序可配置
6,时钟极性和相位可配置
7,四种SPI时钟模式:模式0 ~ 模式3
8,在主机模式下,提供多条CS线
l SPI2:CS0 ~ CS5
l SPI3:CS0 ~ CS2
9,支持访问SPI接口的传感器、显示屏控制器、flash或RAM芯片
SPI2和SPI3接口相关信号线可以经过GPIO交换矩阵和IO_MUX实现与芯片引脚的映射,IO使用起来非常灵活。

22.1.3 LCD介绍

本例程仅支持两款屏幕,一款是正点原子的1.3寸显示模块ATK-MD0130,另一款是正点原子2.4寸显示模块ATK-MD0240。这两款显示模块的LCD分辨率分别为240 240和320 240,支持16位真彩色显示。模块采用ST7789V作为LCD的驱动芯片,该芯片自带RAM,无需外加驱动器或存储器。使用外接的主控芯片时,仅需使用SPI接口就可以轻松地驱动这两个显示模块。
屏幕模块通过2 * 4的排针(2.54m间距)同外部相连接,该模块可直接与正点原子DNESP32-S3开发板的WIRELESS接口(SPI 接口)连接,而对于没有板载WIRELESS接口的开发板,可以通过杜邦线连接。
ATK-MD0130模块的外观,如下图所示。

图22.1.3.1 ATK-MD0130模块实物图
模块的原理图,如下图所示。

图22.1.3.2 ATK-MD0130模块原理图
模块通过一个2 * 4的排针(2.54mm间距)同外部电路连接,各引脚的详细描述,如下表所示。

表22.1.3.1 ATK-MD0130和ATK-MD0240模块引脚说明

22.1.4 模块SPI时序介绍

ATK-MD0130和ATK-MD0240模块在四线SPI通讯模式下,最少仅需四根信号线(CS、SCK、SDA、WR(DC))就能够完成与这两个显示模块的通讯,四线SPI接口时序如下图所示。

图22.1.4.1 四线SPI接口时序图
上图中各个时间参数,如下图所示。

图22.1.4.2 四线SPI接口时序时间参数
从上图中可以看出,ATK-MD0130和ATK-MD0240模块四线SPI的写周期是非常快的(TSCYCW = 66ns),而读周期就相对慢了很多(TSCYCR =150ns)。
更详细的时序介绍,可以参考ST7789V的数据手册《ST7789V_SPEC_V1.4.pdf》。

22.1.5 模块驱动说明

ATK-MD0130和ATK-MD0240模块采用ST7789V作为LCD驱动器,LCD的显存可直接存放在ST7789V的片上RAM中,ST7789V的片上RAM有240 320 3字节,并且ST7789V会在没有外部时钟的情况下,自动将其片上RAM的数据显示至LCD上,以最小化功耗。
在每次初始化显示模块之前,必须先通过RST引脚对显示模块进行硬件复位,硬件复位要求RST至少被拉低10微秒,拉高RST结束硬件复位后,须延时120毫秒等待复位完成后,才能够往显示模块传输数据。
PWR引脚用于控制显示模块的LCD背光,该引脚自带下拉电阻,当PWR引脚被拉低或悬空时,ATK-MD0130模块的LCD背光都处于关闭状态,当PWR引脚被拉高时,显示模块的LCD背光才会点亮。
ST7789V最高支持18位色深(262K色),但一般在希纳是模块上使用16位色深(65K色)的RGB565格式,这样可以在16位色深下达到最快的速度。在16位色深模式下,ST7789V采用RGB565格式传输、存储颜色数据,如下图所示。

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

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

表22.1.5.1 命令0x36配置LCD扫描方向
这样一来,就能够大大地提高ATK-MD0130和ATK-MD0240模块在刷屏时的效率,仅需设置一次坐标,然后连续地往ATK-MD0130和ATK-MD0240模块传输颜色数据即可。
在往ATK-MD0130和ATK-MD0240模块写入颜色数据前,还需要设置地址,以确定随后写入的颜色数据对应LCD上的哪一个像素,通过命令0x2A和命令0x2B可以分别设置ATK-MD0130和ATK-MD0240模块显示颜色数据的列地址和行地址,命令0x2A的描述,如下图所示。

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

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

22.2 硬件设计

22.2.1 例程功能

本章实验功能简介:使用开发板的SPI接口连接正点原子 SPI LCD模块(仅限SPI显示模块),实现SPI LCD模块的显示。通过把LCD模块插入底板上的WIRELESS接口(SPI 接口),按下复位之后,就可以看到SPI LCD模块不停的显示一些信息并不断切换底色。LED闪烁用于提示程序正在运行。

22.2.2 硬件资源

  1. LED
    LED - IO1
  2. 正点原子1.3/2.4寸SPI LCD模块

    22.2.3 原理图

    本章实验使用了正点原子的SPI LCD模块(兼容正点原子1.3/2.4寸的SPI LCD模块),该模块与板载的WIRELESS接口进行连接,该接口与板载MCU的连接原理图,如下图所示:

    图22.2.3.1 SPILCD模块与MCU的连接原理图

    22.3 程序设计

    22.3.1 程序流程图

    程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图:

    图22.3.1.1 SPI_LCD实验程序流程图

    22.3.2 SPI_LCD函数解析

    ESP-IDF提供了一套API来配置SPI。要使用此功能,需要导入必要的头文件:

#include "driver/spi_master.h"

接下来,作者将介绍一些常用的ESP32-S3中的SPI函数,以及IO扩展芯片中用到的函数,这些函数的描述及其作用如下:
1,初始化和配置
该函数用于初始化SPI总线,并配置其GPIO引脚和主模式下的时钟等参数,该函数原型如下所示:

esp_err_t spi_bus_initialize(spi_host_device_thost_id,
                              const spi_bus_config_t *bus_config,
spi_dma_chan_tdma_chan);

该函数的形参描述如下表所示:

表22.3.2.1spi_bus_initialize()函数形参描述
返回值:ESP_OK配置成功。其他配置失败。
该函数使用spi_bus_config_t类型的结构体变量传入,笔者此处列举了我们需要用到的结构体,该结构体的定义如下所示:

typedef struct {
    int miso_io_num;        /* MISO引脚号 */     
    int mosi_io_num;        /* MOSI引脚号 */     
    int sclk_io_num;        /* 时钟引脚号 */   
    int quadwp_io_num;      /* 用于Quad模式的WP引脚号,未使用时设置为-1 */  
    int quadhd_io_num;      /* 用于Quad模式的HD引脚号,未使用时设置为-1 */  
int max_transfer_sz;    /* 最大传输大小 */
…                        /* 其他特定的配置参数 */
}spi_bus_config_t;

完成上述结构体参数配置之后,可以将结构传递给spi_bus_initialize () 函数,用以实例化SPI。
2,设备配置
该函数用于在SPI总线上分配设备,函数原型如下所示:

esp_err_t spi_bus_add_device(spi_host_device_t host_id,
                              const spi_device_interface_config_t *dev_config,                               spi_device_handle_t *handle);

该函数的形参描述,如下表所示:

表22.3.2.2 函数spi_bus_add_device()形参描述
返回值:ESP_OK配置成功。其他配置失败。
该函数使用spi_host_device_t类型以及spi_device_interface_config_t类型的结构体变量传入SPI外围设备的配置参数,该结构体的定义如下所示:

/**
* @brief 带有三个SPI外围设备的枚举,这些外围设备可通过软件访问
*/
typedef enum {
    /* SPI1只能在ESP32上用作GPSPI */
    SPI1_HOST = 0,    /* SPI1 */
    SPI2_HOST = 1,    /* SPI2 */
#if SOC_SPI_PERIPH_NUM > 2
    SPI3_HOST = 2,    /* SPI3 */
#endif
SPI_HOST_MAX,   /* 无效的主机值 */
}spi_host_device_t
typedef struct {
    uint32_t command_bits;      /* 命令阶段的位数 */
    uint32_t address_bits;      /* 地址阶段的位数 */
    uint32_t dummy_bits;        /* 虚拟阶段的位数 */
    int clock_speed_hz;         /* 时钟速率 */
    uint32_t mode;              /* SPI模式(0-3) */
    int spics_io_num;           /* CS引脚号 */
    ...                         /* 其他设备特定的配置参数 */
}spi_device_interface_config_t;

3,数据传输
根据函数功能,以下函数可以归为一类进行讲解,下面将以表格的形式逐个介绍这些函数的作用与参数。

表22.3.2.3 SPI数据传输函数描述

22.3.3 SPI_LCD驱动解析

在IDF版的12_spilcd例程中,作者在12_spilcd\components\BSP路径下新增了一个SPI文件夹和一个LCD文件夹,分别用于存放spi.c、spi.h和lcd.c以及lcd.h这四个文件。其中,spi.h和lcd.h文件负责声明SPI以及LCD相关的函数和变量,而spi.c和lcd.c文件则实现了SPI以及LCD的驱动代码。下面,我们将详细解析这四个文件的实现内容。
1,spi.h文件

/* 引脚定义 */
#define SPI_MOSI_GPIO_PIN   GPIO_NUM_11         /* SPI2_MOSI */
#define SPI_CLK_GPIO_PIN    GPIO_NUM_12         /* SPI2_CLK */
#define SPI_MISO_GPIO_PIN   GPIO_NUM_13         /* SPI2_MISO */

该文件下定义了SPI的时钟引脚与通讯引脚。
2,spi.c文件

/**
*@brief       初始化SPI
*@param       无
*@retval      无
*/
void spi2_init(void)
{
   esp_err_t ret = 0;
   spi_bus_config_t spi_bus_conf = {0};
    /* SPI总线配置 */
   spi_bus_conf.miso_io_num = SPI_MISO_GPIO_PIN;  /* SPI_MISO引脚 */                             
   spi_bus_conf.mosi_io_num = SPI_MOSI_GPIO_PIN;  /* SPI_MOSI引脚 */                             
   spi_bus_conf.sclk_io_num = SPI_CLK_GPIO_PIN;   /* SPI_SCLK引脚 */                             
   spi_bus_conf.quadwp_io_num = -1;   /* SPI写保护信号引脚,该引脚未使能 */                                         
   spi_bus_conf.quadhd_io_num = -1;    /* SPI保持信号引脚,该引脚未使能 */                                          
   spi_bus_conf.max_transfer_sz = 320 * 240 * 2;/* 配置最大传输大小,以字节为单位 */                             
   
    /* 初始化SPI总线 */
    ret=spi_bus_initialize(SPI2_HOST, &spi_bus_conf, SPI_DMA_CH_AUTO);        
   ESP_ERROR_CHECK(ret);               /* 校验参数值 */                                                      
}
/**
*@brief       SPI发送命令
*@param       handle : SPI句柄
*@param       cmd    : 要发送命令
*@retval      无
*/
voidspi2_write_cmd(spi_device_handle_thandle, uint8_t cmd)
{
   esp_err_t ret;
   spi_transaction_t t = {0};
    t.length = 8;                                   /* 要传输的位数 一个字节 8位 */
    t.tx_buffer = &cmd;                             /* 将命令填充进去 */
    ret=spi_device_polling_transmit(handle, &t);  /* 开始传输 */
   ESP_ERROR_CHECK(ret);                          /* 一般不会有问题 */
}
/**
*@brief       SPI发送数据
*@param       handle : SPI句柄
*@param       data   : 要发送的数据
*@param       len    : 要发送的数据长度
*@retval      无
*/
voidspi2_write_data(spi_device_handle_thandle, const uint8_t *data, int len)
{
   esp_err_t ret;
   spi_transaction_t t = {0};
    if (len == 0)
    {
       return;                                     /* 长度为0 没有数据要传输 */
    }
    t.length = len * 8;                             /* 要传输的位数 一个字节 8位 */
    t.tx_buffer = data;                             /* 将命令填充进去 */
    ret=spi_device_polling_transmit(handle, &t);  /* 开始传输 */
   ESP_ERROR_CHECK(ret);                          /* 一般不会有问题 */
}
/**
*@brief       SPI处理数据
*@param       handle       : SPI句柄
*@param       data         : 要发送的数据
*@retval      t.rx_data[0] : 接收到的数据
*/
uint8_t spi2_transfer_byte(spi_device_handle_t handle, uint8_t data)
{
   spi_transaction_t t;
   memset(&t, 0, sizeof(t));
    t.flags =SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA;
    t.length = 8;
    t.tx_data[0] = data;
   spi_device_transmit(handle, &t);
    return t.rx_data[0];
}

在spi2_init()函数中主要工作就是对于SPI参数的配置,如SPI管脚配置和数据传输大小以及SPI总线配置等,通过该函数就可以完成SPI初始化。
SPI驱动中对SPI的各种操作,请读者结合SPI的时序规定查看本实验的配套实验源码。
3,lcd.h文件
lcd.c和lcd.h文件是驱动函数和引脚接口宏定义以及函数声明等。lcdfont.h头文件存放了4种字体大小不一样的ASCII字符集(12 12、16 16、24 24和32 32)。这个跟oledfont.h头文件一样的,只是这里多了32 * 32的ASCII字符集,制作方法请回顾OLED实验。下面我们还是先介绍lcd.h文件,首先是LCD的引脚定义:

/* 引脚定义 */
#define LCD_NUM_WR      GPIO_NUM_40
#define LCD_NUM_CS      GPIO_NUM_21
/* IO操作 */
#define LCD_WR(x)       do{ x ? \
                           (gpio_set_level(LCD_NUM_WR, 1)):   \
                           (gpio_set_level(LCD_NUM_WR, 0));   \
                        }while(0)
#define LCD_CS(x)       do{ x ? \
                           (gpio_set_level(LCD_NUM_CS, 1)):   \
                           (gpio_set_level(LCD_NUM_CS, 0));   \
                        }while(0)
#define LCD_PWR(x)       do{ x ? \
                           (xl9555_pin_write(SLCD_PWR_IO, 1)): \
                           (xl9555_pin_write(SLCD_PWR_IO, 0)); \
                        }while(0)
#define LCD_RST(x)       do{ x ? \
                           (xl9555_pin_write(SLCD_RST_IO, 1)): \
                           (xl9555_pin_write(SLCD_RST_IO, 0)); \
                        }while(0)
/* 常用颜色值 */
#define WHITE           0xFFFF      /* 白色 */
#define BLACK           0x0000      /* 黑色 */
#define RED             0xF800      /* 红色 */
#define GREEN           0x07E0      /* 绿色 */
#define BLUE            0x001F      /* 蓝色 */
#define MAGENTA         0XF81F      /* 品红色/紫红色 = BLUE +RED */
#define YELLOW          0XFFE0      /* 黄色 = GREEN +RED */
#define CYAN            0X07FF      /* 青色 = GREEN +BLUE */  
/* 非常用颜色 */
#define BROWN           0XBC40      /* 棕色 */
#define BRRED           0XFC07      /* 棕红色 */
#define GRAY            0X8430      /* 灰色 */
#define DARKBLUE        0X01CF      /* 深蓝色 */
#define LIGHTBLUE       0X7D7C      /* 浅蓝色 */
#define GRAYBLUE        0X5458      /* 灰蓝色 */
#define LIGHTGREEN      0X841F      /* 浅绿色 */  
#define LGRAY           0XC618      /* 浅灰色(PANNEL),窗体背景色 */
#define LGRAYBLUE       0XA651      /* 浅灰蓝色(中间层颜色) */
#define LBBLUE          0X2B12      /* 浅棕蓝色(选择条目的反色) */
/* 扫描方向定义 */
#define L2R_U2D         0           /* 从左到右,从上到下 */
#define L2R_D2U         1           /* 从左到右,从下到上 */
#define R2L_U2D         2           /* 从右到左,从上到下 */
#define R2L_D2U         3           /* 从右到左,从下到上 */
#define U2D_L2R         4           /* 从上到下,从左到右 */
#define U2D_R2L         5           /* 从上到下,从右到左 */
#define D2U_L2R         6           /* 从下到上,从左到右 */
#define D2U_R2L         7           /* 从下到上,从右到左 */
#define DFT_SCAN_DIR    L2R_U2D    /* 默认的扫描方向 */
/* 屏幕选择 */
#define LCD_320X240     0
#define LCD_240X240     1
/* LCD信息结构体 */
typedef struct _lcd_obj_t
{
    uint16_t        width;         /* 宽度 */
    uint16_t        height;        /* 高度 */
    uint8_t         dir;           /* 横屏还是竖屏控制:0,竖屏;1,横屏。 */
    uint16_t        wramcmd;       /* 开始写gram指令 */
    uint16_t        setxcmd;       /* 设置x坐标指令 */
    uint16_t        setycmd;       /* 设置y坐标指令 */
    uint16_t        wr;            /* 命令/数据IO */
    uint16_t        cs;            /* 片选IO */
} lcd_obj_t;
/* LCD缓存大小设置,修改此值时请注意!!!!修改这两个值时可能会影响以下函数 lcd_clear/lcd_fill/lcd_draw_line */
#define LCD_TOTAL_BUF_SIZE      (320 * 240 * 2)
#define LCD_BUF_SIZE            15360
/* 导出相关变量 */
extern lcd_obj_t lcd_self;
extern uint8_t lcd_buf[LCD_TOTAL_BUF_SIZE];

第一部分的宏定义是对WR/CS引脚的定义,第二部分宏定义是LCD_WR/CS/PWR/RST引脚操作的定义,接下来的部分是对一些常用颜色的RGB数值以及LCD信息结构体的定义。
4,lcd.c文件

/**
* @brief       LCD初始化
* @param       无
* @retval      无
*/
void lcd_init(void)
{
    int cmd = 0;
    esp_err_t ret = 0;
   
    lcd_self.dir = 0;
    lcd_self.wr = LCD_NUM_WR;                                   /* 配置WR引脚 */
    lcd_self.cs = LCD_NUM_CS;                                   /* 配置CS引脚 */
   
    gpio_config_tgpio_init_struct;
    /* SPI驱动接口配置 */
   spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 60 * 1000 * 1000,                 /* SPI时钟 */
        .mode = 0,                                             /* SPI模式0 */
        .spics_io_num = lcd_self.cs,                       /* SPI设备引脚 */
        .queue_size = 7,                                      /* 事务队列尺寸 7个 */
    };
   
    /* 添加SPI总线设备 */
    ret = spi_bus_add_device(SPI2_HOST, &devcfg, &MY_LCD_Handle);
    ESP_ERROR_CHECK(ret);
   gpio_init_struct.intr_type = GPIO_INTR_DISABLE;    /* 失能引脚中断 */
   gpio_init_struct.mode = GPIO_MODE_OUTPUT;              /* 配置输出模式 */
   gpio_init_struct.pin_bit_mask = 1ull << lcd_self.wr;   /* 配置引脚位掩码 */
   gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; /* 失能下拉 */
   gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;      /* 使能上拉 */
    gpio_config(&gpio_init_struct);                     /* 引脚配置 */
    lcd_hard_reset();                                       /* LCD硬件复位 */
    /* 初始化代码,对2.4寸LCD寄存器进行设置 */
#if SPI_LCD_TYPE
    lcd_init_cmd_tili_init_cmds[] =
    {
        (……此处省略初始化代码……)
    };
#else   /* 不为0则视为使用1.3寸SPILCD屏,那么屏幕将不会反显 */
    lcd_init_cmd_tili_init_cmds[] =
    {
       (……此处省略初始化代码……)    };
#endif
    /* 循环发送设置所有寄存器 */
    while (ili_init_cmds[cmd].databytes != 0xff)
    {
       lcd_write_cmd(ili_init_cmds[cmd].cmd);
       lcd_write_data(ili_init_cmds[cmd].data,
                       ili_init_cmds[cmd].databytes & 0x1F);
        
        if (ili_init_cmds[cmd].databytes & 0x80)
        {
           vTaskDelay(120);
        }
        
        cmd++;
    }
    lcd_display_dir(1);                                     /* 设置屏幕方向 */
    LCD_PWR(1);
    lcd_clear(WHITE);                                       /* 清屏 */
}

从上的代码中可以看出,本章实验的SPILCD驱动是兼容了正点原子的1.3寸与2.4寸SPILCD模块的,因此在加载完SPI设备后,会与SPILCD进行通讯,确定SPILCD的型号,然后根据型号针对性地对SPILCD模块进行配置。
SPILCD驱动中与SPILCD模块通讯的函数,如下所示:

/**
* @brief       发送命令到LCD,使用轮询方式阻塞等待传输完成(由于数据传输量很少,因此在轮询方               式处理可提高速度。使用中断方式的开销要超过轮询方式)
* @param       cmd 传输的8位命令数据
* @retval      无
*/
void lcd_write_cmd(const uint8_t cmd)
{
    LCD_WR(0);
    spi2_write_cmd(MY_LCD_Handle, cmd);
}
/**
* @brief       发送数据到LCD,使用轮询方式阻塞等待传输完成(由于数据传输量很少,因此在轮询方               式处理可提高速度。使用中断方式的开销要超过轮询方式)
* @param       data 传输的8位数据
* @retval      无
*/
void lcd_write_data(const uint8_t *data, int len)
{
    LCD_WR(1);
    spi2_write_data(MY_LCD_Handle, data, len);
}
/**
* @brief        发送数据到LCD,使用轮询方式阻塞等待传输完成(由于数据传输量很少,因此在轮询方式处理可提高速度。使用中断方式的开销要超过轮询方式)
* @param       data 传输的16位数据
* @retval      无
*/
void lcd_write_data16(uint16_t data)
{
    uint8_t dataBuf[2] = {0,0};
    dataBuf[0] = data >> 8;
    dataBuf[1] = data & 0xFF;
    LCD_WR(1);
    spi2_write_data(MY_LCD_Handle, dataBuf,2);
}

在上述代码中,lcd_write_cmd()和lcd_write_data()在调用spi的驱动函数前,按照LCD时序图,前者需要先将WR引脚电平信号置0,后者则需要置1。
通过上面介绍的驱动函数就能够与SPILCD模块进行通讯了,而在SPILCD模块的显示屏上显示出特定的图案或字符或设置SPILCD模块的显示方向等等的操作都是能够通过SPILCD模块规定的特定命令来完成的,想深究的读者可以产看正点原子SPILCD模块的用户手册或查看实际使用的SPILCD模块的相关文档。

22.3.4 CMakeLists.txt文件

打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:

set(src_dirs
           IIC
           LCD
           LED
           SPI
           XL9555)
set(include_dirs
           IIC
           LCD
           LED
           SPI
           XL9555)
set(requires
           driver)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

上述的红色LCD与SPI驱动需要由开发者自行添加,以确保SPI_LCD驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了SPI_LCD驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。

22.3.5 实验应用代码

打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。

i2c_obj_t i2c0_master;
/**
* @brief       程序入口
* @param       无
* @retval      无
*/
void app_main(void)
{
    uint8_t x = 0;
    esp_err_t ret;
    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());
        ret = nvs_flash_init();
    }
    led_init();                             /* 初始化LED */
    i2c0_master = iic_init(I2C_NUM_0);      /* 初始化IIC0 */
    spi2_init();                            /* 初始化SPI2 */
    xl9555_init(i2c0_master);            /* IO扩展芯片初始化 */
    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", RED);
       lcd_show_string(10, 80, 240, 24, 24, "SPILCDTEST", RED);
       lcd_show_string(10, 110, 240, 16, 16, "ATOM@ALIENTEK", RED);
        x++;
        if (x == 12)
        {
            x = 0;
        }
        LED_TOGGLE();
        vTaskDelay(500);
    }
}

从上面的代码中可以看出,在初始化完LCD后,便在LCD上显示一些本实验的相关信息,随后便每间隔500毫秒就更换一次LCD屏幕显示的背景色。

22.4 下载验证

在完成编译和烧录操作后,可以看到SPI LCD上不断变换着不同的颜色,LED灯闪烁。

图22.4.1 SPI LCD显示效果图


正点原子
1 声望3 粉丝