实验十 LCD显示—SPI

一.实验目标

1.通过编程使用STM32的IO口模拟SPI,控制LCD的显示;

2.通过本实验掌握SPI相关知识和SPI时序;

3.通过本实验掌握STM32的硬件SPI控制外设的基本概念,掌握LCD控制的基本方法。

二.知识储备及设计思路

SPI简介:

SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32F4也有SPI接口。

SPI 通讯设备之间的常用连接方式见图

img

SPI 通讯使用3 条总线及片选线,3 条总线分别为SCK、MOSI、MISO,片选线为SS,它们的作用介绍如下:

(1) SS( Slave Select):从设备选择信号线,常称为片选信号线,也称为NSS、CS,以下

用NSS 表示。当有多个SPI 从设备与SPI 主机相连时,设备的其它信号线SCK、MOSI 及MISO 同时并联到相同的SPI 总线上,即无论有多少个从设备,都共同只使用这3 条总线;而每个从设备都有独立的这一条NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI 协议中没有设备地址,它使用NSS 信号线来寻址,当主机要选择从设备时,把该从设备的NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI 通讯。所以SPI 通讯以NSS 线置低电平为开始信号,以NSS 线被拉高作为结束信号。

(2) SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通

讯的速率,不同的设备支持的最高时钟频率不一样,如STM32 的SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。

(3) MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。

(4) MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

协议层

\1. SPI 基本通讯过程

SPI 通讯的通讯时序:

img

这是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与MISO 的信号只在NSS 为低电平的时候才有效,在SCK 的每个时钟周期MOSI 和MISO 传输一位数据。

\2. 通讯的起始和停止信号

图的标号1处,NSS 信号线由高变低,是SPI 通讯的起始信号。NSS 是每个从机各自独占的信号线,当从机检在自己的NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的标号6处,NSS 信号由低变高,是SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

\3. 数据有效性

SPI 使用MOSI 及MISO 信号线来传输数据,使用SCK 信号线进行数据同步。MOSI及MISO 数据线在SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或LSB 先行并没有作硬性规定,但要保证两个SPI 通讯设备之间使用同样的协定,一般都会采用图中的MSB 先行模式。

观察图中,MOSI 及MISO 的数据在SCK 的上升沿期间变化输出,在SCK 的下降沿时被采样。即在SCK 的下降沿时刻,MOSI 及MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及MISO为下一次表示数据做准备。SPI 每次数据传输可以8 位或16 位为单位,每次传输的单位数不受限制。

\4. CPOL/CPHA 及通讯模式

上面讲述的图中的时序只是SPI 中的其中一种通讯模式,SPI 一共有四种通讯模式,它们的主要区别是总线空闲时SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性CPOL”和“时钟相位CPHA”的概念。

时钟极性CPOL 是指SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即SPI 通讯开始前、 NSS 线为高电平时SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。

时钟相位CPHA 是指数据的采样的时刻,当CPHA=0 时,MOSI 或MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿‖被采样。当CPHA=1 时,数据线在SCK的“偶数边沿“采样。

img

我们来分析这个CPHA=0 的时序图。首先,根据SCK 在空闲状态时的电平,分为两种情况。SCK 信号线在空闲状态为低电平时,CPOL=0;空闲状态为高电平时,CPOL=1。

无论CPOL=0 还是=1,因为我们配置的时钟相位CPHA=0,在图中可以看到,采样时刻都是在SCK 的奇数边沿。注意当CPOL=0 的时候,时钟的奇数边沿是上升沿,而CPOL=1 的时候,时钟的奇数边沿是下降沿。所以SPI 的采样时刻不是由上升/下降沿决定的。MOSI 和MISO 数据线的有效信号在SCK 的奇数边沿保持不变,数据信号将在SCK 奇数边沿时被采样,在非采样时刻,MOSI 和MISO 的有效信号才发生切换。

STM32 的SPI 特性及架构

STM32 的SPI 外设可用作通讯的主机及从机,支持最高的SCK 时钟频率为fpclk/2(STM32F407 型号的芯片默认fpclk1 为84MHz,fpclk2 为42MHz),完全支持SPI 协议的4 种模式,数据帧长度可设置为8 位或16 位,可设置数据MSB 先行或LSB 先行。它还支持双线全双工(前面小节说明的都是这种模式)、双线单向以及单线模式。

SPI框图:

img

STM32 寄存器简介

SPI 控制寄存器 1 (SPI_CR1)描述如下:

img

img

通过SPI_CR1寄存器我们可以设置波特率,时钟极性,时钟相位,SPI使能等。

SPI 控制寄存器 2 (SPI_CR2)描述如下:

img

通过SPI_CR2寄存器我们可以发送/接收中断使能,DMA发送/接收使能等。

SPI 状态寄存器 (SPI_SR)描述如下:

img

通过SPI_SR寄存器我们可以判断SPI的状态和错误信息等。

SPI 数据寄存器 (SPI_DR)描述如下:

img

通过SPI_SR寄存器我们可以判断SPI的状态和错误信息等。

LCD**简介:**

LCD即薄膜晶体管液晶显示器。其英文全称为:Thin Film Transistor-Liquid Crystal Display。

开发板自带的1.3寸屏幕有如下特点:

1,1.3’的屏幕大小。

2,240×240的分辨率。

3,16位真彩显示(RGB565)。

4,采用四线SPI控制显示

​ 图10.1是屏幕数据手册中的管脚描述:

img

图 10-1 LCD屏幕管脚说明(摘自《1.3寸显示屏规格书》)

LCD屏的控制器是ST7789VW,本章,我们将学习如何通过STM32F429来控制该模块显示字符和数字,本章的实例代码支持的是4线SPI方式。

4线SPI模式使用的信号线有如下几条:

CS:LCD片选信号,常接gnd,拉低。

RESET:硬复位LCD。

D/C:命令/数据标志(0,读写命令;1,读写数据)。

SCL:串行SPI时钟线。

SDA:串行SPI数据线。

在4线SPI模式下,每个数据长度均为8位,在SCLK的上升沿,数据从SDA移入到ST7789VW,并且是高位在前的。DC线是用作命令/数据的标志线。在4线SPI模式下,写操作的时序如图10.2所示:

img

图10.2 4线SPI写操作时序图(摘自《ST7789VW芯片手册》)

最后,我们再来介绍一下LCD模块的初始化过程,ST7789VW的典型初始化框图如图10.3所示:

img

图10.3 初始化框图

驱动IC的初始化代码,我们直接使用厂家推荐的设置就可以了,只要对细节部分进行些

修改,使其满足我们自己的要求即可,其他不需要变动。

LCD的介绍就到此为止,我们重点向大家介绍了屏幕的相关知识,接下来我们将使用这个屏幕来显示字符和数字。通过以上介绍,我们可以得出LCD显示需要的相关设置步骤如下:

1)设置STM32F4LCD模块相连接的IO

这一步,先将我们与LCD模块相连的IO口设置为输出,具体使用哪些IO口,这里需要根据连接电路以及LCD模块所设置的通讯模式来确定。这些将在硬件设计部分向大家介绍。

2)初始化LCD模块。

其实这里就是上面的初始化框图的内容,通过对LCD相关寄存器的初始化,来启动LCD的显示。为后续显示字符和数字做准备。

3)通过函数将字符和数字显示到LCD模块上。

这里就是通过我们设计的程序,将要显示的字符送到LCD模块就可以了,这些函数将在软件设计部分向大家介绍。

三.引脚说明与硬件连接

图10.4为分辨率240*240的LCD屏和硬件连接图。 STM32通过SPI接口控制LCD显示,表10.1为其引脚说明。

img

imgimg

10.4 LCD硬件连接图

10.1 LCD引脚说明

设备名 引脚号
SPI5_SCK PH6
SPI5_MOSI PF9
PF10 PF10
SPI5_MISO PH7
SPI5_NSS PH5

img

10.4 LCD硬件图

四.程序设计

本实验,我们新建了lcd.c , lcd.h , lcd_init.c , lcd_init.h , lcdfont.h ,文件。这五个文件用来存放lcd相关的驱动函数及显示代码。

​ lcd_init.c的代码,由于比较长,这里我们就不贴出来了,仅介绍几个比较重要的函数。LCD_Init函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void LCD_Init(void)
{
// LCD_GPIO_Init();//初始化GPIO

LCD_RES_Clr();//复位
delay_ms(400);
LCD_RES_Set();
delay_ms(200);

LCD_BLK_Set();//打开背光
delay_ms(100);

//************* Start Initial Sequence **********//
LCD_WR_REG(0x11); //Sleep out
delay_ms(120); //Delay 120ms
//************* Start Initial Sequence **********//
LCD_WR_REG(0x36);
if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00);
else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0);
else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70);
else LCD_WR_DATA8(0xA0);

LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);

//此处省略部分代码,详情请参考实验工程

LCD_WR_REG(0x29);
}

lcd.h的头文件用于外部调用lcd.c的代码,介绍几个比较重要的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

void LCD_Fill(uint16_t xsta,uint16_t ysta,uint16_t xend,uint16_t yend,uint16_t color);//指定区域填充颜色
void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t color);//在指定位置画一个点
void LCD_DrawLine(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2,uint16_t color);//在指定位置画一条线
void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,uint16_t color);//在指定位置画一个矩形
void Draw_Circle(uint16_t x0,uint16_t y0,uint8_t r,uint16_t color);//在指定位置画一个圆

void LCD_ShowChinese(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示汉字串
void LCD_ShowChinese16x16(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示单个16x16汉字
void LCD_ShowChinese24x24(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示单个24x24汉字
void LCD_ShowChinese32x32(uint16_t x,uint16_t y,uint8_t *s,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示单个32x32汉字

void LCD_ShowChar(uint16_t x,uint16_t y,uint8_t num,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示一个字符
void LCD_ShowString(uint16_t x,uint16_t y,const uint8_t *p,uint16_t fc,uint16_t bc,uint8_t sizey,uint8_t mode);//显示字符串
uint32_t mypow(uint8_t m,uint8_t n);//求幂
void LCD_ShowIntNum(uint16_t x,uint16_t y,uint16_t num,uint8_t len,uint16_t fc,uint16_t bc,uint8_t sizey);//显示整数变量
void LCD_ShowFloatNum1(uint16_t x,uint16_t y,float num,uint8_t len,uint16_t fc,uint16_t bc,uint8_t sizey);//显示两位小数变量

void LCD_ShowPicture(uint16_t x,uint16_t y,uint16_t length,uint16_t width,const uint8_t pic[]);//显示图片

Main.c 函数代码,用于初始话和屏幕显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 MX_GPIO_Init();

/* USER CODE BEGIN 2 */
delay_init(168); //延时初始化
LCD_Init(); //LCD初始化
LCD_Fill(0,0,LCD_W,LCD_H,WHITE); //整个屏幕刷白色
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
LCD_ShowString(0,0," you qing ",RED,WHITE,32,0); //字符串显示函数
LCD_ShowString(30,50,"LCD_W:",RED,WHITE,16,0);
LCD_ShowIntNum(80,50,LCD_W,3,RED,WHITE,16); //整数显示函数
LCD_ShowString(130,50,"LCD_H:",RED,WHITE,16,0);
LCD_ShowIntNum(160,50,LCD_H,3,RED,WHITE,16);
LCD_ShowString(0,80," www.emooc.cc ",RED,WHITE,32,0);
}

对于屏幕如何显示图片和汉字,请自行参照函数来设计。

五.实验结果

将编译好的代码下载到实验板中,按下复位键。

img

六.STM32CubeMX配置硬件SPI

配置IO口时,将SPI模拟的io口配置为高,默认上拉。配置如下:

img