实现目标
1、掌握SPI总线基础知识;
2、会使用软件模拟SPI总线和STM32硬件SPI总线;
3、 学会STM32CubeMX软件关于SPI的配置;
4、掌握OLED显示屏驱动;
5、具体目标:(1)用STM32硬件SPI驱动OLED显示“你好哇,王小波!”;(2)用软件模拟SPI总线驱动OLED显示“你好哇,王小波!”(只提供代码)。
一、SPI概述
1.1 SPI简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串行扩展接口,是一种高速、全双工、同步通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。
SPI有主、从两种模式(一主一从,一主多从),通常由一个主模块和一个或多个从模块组成(SPI不支持多主机),主模块选择一个从模块进行同步通信,从而完成数据的交换。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起,当存在多个从设备时,通过各自的片选信号进行管理。
1.2 SPI通信原理
SPI总线通信原理很简单,需要至少4根线,单向传输时3根线,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)和CS/SS(片选):
MOSI(Master Out Slave In):主设备 --->从设备(主设备发送,从设备接收)
MISO(Master In Slave Out):从设备 --->主设备(主设备接收,从设备发送)
SCLK(Serial Clock):传输时钟信号,用于主从设备的同步
CS (chip select):片选信号,用于选择从设备
SPI主机与从机通讯实际上时两者的移位寄存器交换数据的过程,在时钟信号SCL的推动下,主机发送一位,从机也发送一位,两者互换数据,交换8次之后,便完成了一个字节的传输。
SPI数据通信的流程可以分为以下几步:
1、主设备发起信号,将CS/SS拉低,启动通信。
2、主设备通过发送时钟信号,来告诉从设备进行写数据或者读数据操作(采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低),因为SPI有四种模式,后面会讲到),它将立即读取数据线上的信号,这样就得到了一位数据(1bit)。
3、主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(缓存长度不一定,看单片机配置),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
4、从机(Slave)也将自己的串行移位寄存器(缓存长度不一定,看单片机配置)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。
例如,下图示例中简单模拟SPI通信流程,主机拉低NSS片选信号,启动通信,并且产生时钟信号,上升沿触发边沿信号,主机在MOSI线路一位一位发送数据0X53,在MISO线路一位一位接收数据0X46,如下图所示:
说明:SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。若只进行写操作,主机只需忽略接收到的字节(虚拟数据);反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据
1.3 通信特性
1.3.1、设备选择
SPI是单主设备(Single Master)通信协议,只有一支主设备能发起通信,当SPI主设备想读/写从设备时,它首先拉低从设备对应的SS线(SS是低电平有效)。接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,主设备把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”。如下图所示:
1.3.2、设备时钟
SPI时钟特点主要包括:时钟速率、时钟极性和时钟相位三方面。
(1) 时钟速率
SPI总线上的主设备必须在通信开始时候配置并生成相应的时钟信号。从理论上讲,只要实际可行,时钟速率就可以是你想要的任何速率,当然这个速率受限于每个系统能提供多大的系统时钟频率,以及最大的SPI传输速率。
(2)时钟极性
根据硬件制造商的命名规则不同,时钟极性通常写为CKP或CPOL。时钟极性和相位共同决定读取数据的方式,比如信号上升沿读取数据还是信号下降沿读取数据。
CKP可以配置为1或0。这意味着你可以根据需要将时钟的默认状态(IDLE)设置为高或低。极性反转可以通过简单的逻辑逆变器实现。你必须参考设备的数据手册才能正确设置CKP和CKE。
CKP = 0:时钟空闲IDLE为低电平 0;
CKP = 1:时钟空闲IDLE为高电平1。
(3)时钟相位
根据硬件制造商的不同,时钟相位通常写为CKE或CPHA。顾名思义,时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿;
CKE = 0:在时钟信号SCK的第一个跳变沿采样;
CKE = 1:在时钟信号SCK的第二个跳变沿采样。
1.3.3 SPI的四种模式
根据SPI的时钟极性和时钟相位特性可以设置4种不同的SPI通信操作模式,它们的区别是定义了在时钟脉冲的哪条边沿转换(toggles)输出信号,哪条边沿采样输入信号,还有时钟脉冲的稳定电平值(就是时钟信号无效时是高还是低),详情如下所示:
CPOL=0 | CPOL=1 | |
CPHA=0 | 空闲态时,SCK处于低电平,数据采样是在第1个边沿,也就是SCK由低电平到高电平的跳变,所以数据采样是在上升沿(准备数据),(发送数据)数据发送是在下降沿。 | 当空闲态时,SCK处于低电平,数据发送是在第2个边沿,也就是SCK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。 |
CPHA=1 | 当空闲态时,SCK处于高电平,数据采集是在第1个边沿,也就是SCK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。 | 当空闲态时,SCK处于高电平,数据发送是在第2个边沿,也就是SCK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。 |
(1)相位(CPHA):表示时钟线空闲时的状态;
(2)极性(CPOL):表示设备从数据线采样的时刻;(3)四种模式编号:[00]:Mode0 、[01]:Mode1、[10]:Mode2 、[11]:Mode3
黑线为采样数据的时刻,蓝线为SCK时钟信号
举个例子,下图是SPI Mode0读/写时序,可以看出SCK空闲状态为低电平,主机数据在第一个跳变沿被从机采样,数据输出同理。
下图是SPI Mode3读/写时序,SCK空闲状态为高电平,主机数据在第二个跳变沿被从机采样,数据输出同理。
1.4、多从机模式
多从机模式有两种:多片选和菊花链片选
1.4.1多片选
通常,每个从机都需要一条单独的SS线。如果要和特定的从机进行通讯,可以将相应的NSS
信号线拉低,并保持其他SS信号线的状态为高电平;如果同时将两个SS信号线拉低,则可能会出现乱码,因为从机可能都试图在同一条MISO
线上传输数据,最终导致接收数据乱码。
1.4.2菊花链片选
菊花链的最大缺点是信号串行传输,一旦数据链路中的某设备发生故障的时候,它下面优先级较低的设备就不可能得到服务了。另一方面,距离主机越远的从机,获得服务的优先级越低,所以需要安排好从机的优先级,并且设置总线检测器,如果某个从机超时,则对该从机进行短路,防止单个从机损坏造成整个链路崩溃的情况。
1.5 SPI优缺点
1.5.1 优点
- 高速数据传输速率;
- 没有像I2C这样复杂的从设备寻址系统;
- 分离的MISO和MOSI信号线,全双工通信;
- 极其灵活的数据传输,不限于8位,它可以是任意大小的字;
- 非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)。
1.5.2 缺点
- 使用四根信号线(I2C和UART使用两根信号线);
- 无法确认是否已成功接收数据(I2C拥有此功能);
- 没有任何形式的错误检查,如UART中的奇偶校验位;
- 只允许一个主设备;
- 没有硬件从机应答信号(主机可能在不知情的情况下无处发送);
- 没有定义硬件级别的错误检查协议;
- 与RS-232和CAN总线相比,只能支持非常短的距离;
二、STM32的SPI总线
2.1 STM32 的SPI
1.STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
2.可配置8位/16位数据帧、高位先行/低位先行
3.时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
4.支持多主机模型、主或从操作
5.可精简为半双工/单工通信
6.支持DMA
7.兼容I2S协议
2.2 STM32 SPI框图
2.3 SPI外设对应的引脚
不同型号的芯片基本都有3个SPI外设,其中SPI2,SPI3支持I2S通信因为I2S与SPI协议类似,所以他们共用一套逻辑就是上面的SPI框图。
三、OLED概述
3.1OLED简介
OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
OLED显示技术具有自发光的特性,采用非常薄的有机材料涂层和玻璃基板,当有电流通过时,这些有机材料就会发光,而且OLED显示屏幕可视角度大,并且能够节省电能,从2003年开始这种显示设备在MP3播放器上得到了应用。
LCD都需要背光,而OLED不需要,因为它是自发光的。这样同样的显示,OLED效果要来得好一些。以目前的技术,OLED的尺寸还难以大型化,但是分辨率确可以做到很高。
7针OLED引脚
1.GND -- 电源地,接在板子上任意GND的引脚2.VCC -- 电源正(3.3V~5V)接在板子上任意VCC引脚
3.D0 -- 对应的是时钟线(SCK或者SCLK)
4.D1 -- 对应的是数据线,也就是 MISO (主入从出) 或者 MOSI(主出从入)
5.RES -- 复位线,最开始是低电平,先拉低再拉高,可选择任意空IO口
6.DC -- 数据/命令控制线,高电平为1是数据线,低电平为0是命令线,可选择任意空IO口
7.CS -- 片选信号线,低电平有效,不用可以接地,用来选择从机的
四、原理图设计
五、STM32CubeMX 配置
5.1.SPI 配置(D0、D1)
(1)Mode: 模式
Full-Duplex Master 主机全双工模式
Full-Duplex Slave 从机全双工模式
Half-Duplex Master 主机半双工模式
Half-Duplex Slave 从机半双工模式
Receive Only Master 只接收主机模式
Receive Only Slave 只接收从机模式
Transmit Only Master 只发送从机模式
(2)硬件NSS信号
- 不使能
- NSS输入信号
- NSS输出信号
如果片选引脚没有连接硬件NSS信号,则需要选择软件片选
(3)基本参数
Frame Format帧格式:Motorola摩托罗拉 或 TI
Data size数据大小:8位 或 16位
First Bit:MSB/LSB先行
(4)时钟参数波特率分配因子:
CPOL时钟极性:low or high
CPHA时钟相位:1 edge or 2 edge
(5)高级参数CRC循环校验:使能/失能
NSS信号类型:软件
5.2 其他管脚配置(RES、CS、DC)
PA1、PA4、PA6,配置为普通IO口,gpio_output
六、取模软件的应用(PCtoLCD2002完美版)
1. 打开软件,模式为(字符模式)
2. 点击菜单栏【选项】,打开字模选项
3.勾选【阴码点阵】、逐行式取模、逆向、C51格式。
4.输入“你好哇,王小波!”,进行取模。
软件下载地址:https://wwe.lanzoui.com/iIBEzs77lcf
七、程序设计
7.1修改驱动函数
中景园官方驱动代码(标准库):https://download.csdn.net/download/luojuan198780/89319982
将中景园原版的 OLED_WR_Byte函数内容修改为含SPI发送函数:HAL_SPI_Transmit,两个函数如下:
//中景园原版
void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{ u8 i; if(cmd)OLED_DC_Set();else OLED_DC_Clr(); OLED_CS_Clr();for(i=0;i<8;i++){ OLED_SCL_Clr();if(dat&0x80)OLED_SDA_Set();else OLED_SDA_Clr();OLED_SCL_Set();dat<<=1; } OLED_CS_Set();OLED_DC_Set(); }//修改后带HAL_SPI_Transmit的函数
void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{ uint8_t *data = &dat; if(cmd)OLED_DC_Set();elseOLED_DC_Clr();OLED_CS_Clr();HAL_SPI_Transmit(&hspi1, (uint8_t *)data, 1, 200); OLED_CS_Set();OLED_DC_Set();
}
7.2修改 SPI的其它管脚
打开oled.h文件将CS DC RES 标准库置高置低函数修改为HAL库函数
#define OLED_CS_Clr() HAL_GPIO_WritePin(CS_GPIO_Port,CS_Pin,GPIO_PIN_RESET)//RES
#define OLED_CS_Set() HAL_GPIO_WritePin(CS_GPIO_Port,CS_Pin,GPIO_PIN_SET)#define OLED_DC_Clr() HAL_GPIO_WritePin(DC_GPIO_Port,DC_Pin,GPIO_PIN_RESET)//DC
#define OLED_DC_Set() HAL_GPIO_WritePin(DC_GPIO_Port,DC_Pin,GPIO_PIN_SET)#define OLED_RES_Clr() HAL_GPIO_WritePin(RES_GPIO_Port,RES_Pin,GPIO_PIN_RESET)//RES
#define OLED_RES_Set() HAL_GPIO_WritePin(RES_GPIO_Port,RES_Pin,GPIO_PIN_SET)
7.3 增加 u8/u16/u32 ,并包含各个头文件
标准库是u8/u16/u32,HAL库是uint8_t/uint16_t/uint32_t .可以用下面方法实现转化,也可以直接ctr+h 替换。
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
7.4 添加中文字库
在oledfont.h文件的 Hzk1[][32]={} 数组中添加下列“你好哇,王小波!”字模
{0x00,0x80,0x60,0xF8,0x07,0x40,0x20,0x18,0x0F,0x08,0xC8,0x08,0x08,0x28,0x18,0x00,0x01,0x00,0x00,0xFF,0x00,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00},/*"你",11*/
{0x10,0x10,0xF0,0x1F,0x10,0xF0,0x00,0x80,0x82,0x82,0xE2,0x92,0x8A,0x86,0x80,0x00,0x40,0x22,0x15,0x08,0x16,0x61,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00},/*"好",12*/
{0x00,0xFC,0x04,0x04,0xFC,0x00,0x40,0x44,0x44,0x44,0x7F,0x44,0x44,0x44,0x40,0x00,0x00,0x0F,0x04,0x04,0x0F,0x00,0x40,0x44,0x44,0x44,0x7F,0x44,0x44,0x44,0x40,0x00},/*"哇",13*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*",",14*/
{0x00,0x02,0x82,0x82,0x82,0x82,0x82,0xFE,0x82,0x82,0x82,0x82,0x82,0x02,0x00,0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00},/*"王",15*/
{0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x20,0x40,0x80,0x00,0x00,0x08,0x04,0x03,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,0x01,0x0E,0x00},/*"小",16*/
{0x10,0x60,0x02,0x0C,0xC0,0x00,0xF8,0x88,0x88,0x88,0xFF,0x88,0x88,0xA8,0x18,0x00,0x04,0x04,0x7C,0x03,0x80,0x60,0x1F,0x80,0x43,0x2C,0x10,0x28,0x46,0x81,0x80,0x00},/*"波",17*/
{0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",18*/
7.5 延时函数替换
将驱动函数中的delay_ms延时函数替换为HAL_Delay 函数。删除头文件#include "delay.h"与
#include "sys.h"
HAL_Delay(200);//delay_ms(200);
7.6 OLED初始化,显示设计
/* USER CODE BEGIN 2 */OLED_Init();//oled初始化OLED_ColorTurn(0);//0正常显示,1 反色显示OLED_DisplayTurn(0);//0正常显示 1 屏幕翻转显示/* USER CODE END 2 */while (1){OLED_ShowChinese(0,16,11,16,1);//你OLED_ShowChinese(16,16,12,16,1);//好OLED_ShowChinese(32,16,13,16,1);//哇OLED_ShowChinese(48,16,14,16,1);//,OLED_ShowChinese(64,16,15,16,1);//王OLED_ShowChinese(80,16,16,16,1);//小OLED_ShowChinese(96,16,17,16,1);//波 OLED_ShowChinese(112,16,18,16,1);//! OLED_Refresh();HAL_Delay(500);OLED_Clear();}
八、实验效果
总结
最终版程序(HAL硬件SPI):https://gitee.com/luojaun/qianrushi/tree/master/
最终版程序(HAL软件模拟SPI):https://gitee.com/luojaun/qianrushi