《ESP32-S3使用指南—IDF版 V1.6》第二十二章 SPI_LCD实验

第二十二章 SPI_LCD实验

本章,我们将学习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显示效果图
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容