嵌入式系统开发环境下,SPI通信协议原理理解及STM32+OLED屏显应用初步

理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI协议并结合OLED屏显,实现学号姓名、温湿度、长字符的显示功能



一、SPI通信协议简介

1. 发展背景

  • 在之前的串口学习中,我们经常提到一个东西叫做:“UART”,即通用串行异步通讯协议
  • 因为UART没有时钟信号,无法控制何时发送数据,也无法保证双发按照完全相同的速度接收数据;因此,双方以不同的速度进行数据接收和发送,就会出现问题
  • 如果要解决这个问题,UART为每个字节添加额外的起始位和停止位,以帮助接收器在数据到达时进行同步;双方还必须事先就传输速度达成共识(设置相同的波特率,例如每秒9600位)
  • 异步串行工作得很好,但是在每个字节发送的时候都需要额外的起始位和停止位以及在发送和接收数据所需的复杂硬件方面都有很多开销,不难发现,如果接收端和发送端设置的速度都不一致,那么接收到的数据将是乱码
  • 因此,便出现了今天要介绍的协议——SPI通信协议

2. 协议概述

  • 相比较于UART,SPI的工作方式略有不同
  • SPI是一个同步的数据总线,也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的完美同步;时钟是一个振荡信号,它告诉接收端在确切的时机对数据线上的信号进行采样
  • 产生时钟的一侧称为主机,另一侧称为从机。总是只有一个主机(一般来说可以是微控制器/MCU),但是可以有多个从机(后面详细介绍)
  • 数据的采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低)

3. SPI通信协议的配置

  • 整体的传输大概可以分为以下几个过程:
    1)、主机先将NSS信号拉低,这样保证开始接收数据;
    2)、当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就得到了一位数据(1bit);
    3)、由于时钟是随数据一起发送的,因此指定数据的传输速度并不重要,尽管设备将具有可以运行的最高速度(稍后我们将讨论选择合适的时钟边沿和速度)
    4)、主机发送到从机时:主机产生相应的时钟信号,然后数据一位一位地将从MOSI信号线上进行发送到从机;
    5)、主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续生成预定数量的时钟信号,并且从机会将数据通过MISO信号线发送

  • 具体如下图所示:
    在这里插入图片描述

  • 注意,SPI是“全双工”(具有单独的发送和接收线路),因此可以在同一时间发送和接收数据;另外SPI的接收硬件可以是一个简单的移位寄存器,这比异步串行通信所需的完整UART要简单得多,并且更加便宜

4. SPI通信协议的特性

  • SPI总线包括4条逻辑线,定义如下:
    MISO:Master input slave output 主机输入,从机输出(数据来自从机);
    MOSI:Master output slave input 主机输出,从机输入(数据来自主机);
    SCLK :Serial Clock 串行时钟信号,由主机产生发送给从机;
    NSS:Slave Select 片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号

  • 其他制造商可能会遵循其他命名规则,但是最终他们指的相同的含义;以下是一些常用术语:
    MISO也可以是SIMO,DOUT,DO,SDO或SO(在主机端);
    MOSI也可以是SOMI,DIN,DI,SDI或SI(在主机端);
    NSS也可以是CE,CS或SSEL;
    SCLK也可以是SCK;

  • 下图显示了单个主机和单个从机之间的典型SPI连接:
    在这里插入图片描述

5. 时钟配置

1、时钟频率

  • SPI总线上的主机必须在通信开始时候配置并生成相应的时钟信号,在每个SPI时钟周期内,都会发生全双工数据传输;
  • 主机在MOSI线上发送一位数据,从机读取它,而从机在MISO线上发送一位数据,主机读取它;
  • 就算只进行单向的数据传输,也要保持这样的顺序,这就意味着无论接收任何数据,必须实际发送一些东西!在这种情况下,我们称其为虚拟数据;
  • 从理论上讲,只要实际可行,时钟速率就可以是您想要的任何速率,当然这个速率受限于每个系统能提供多大的系统时钟频率,以及最大的SPI传输速率

2、时钟极性 CKP/Clock Polarity

  • 除了配置串行时钟速率(频率)外,SPI主设备还需要配置时钟极性;
  • 根据硬件制造商的命名规则不同,时钟极性通常写为CKP或CPOL。时钟极性和相位共同决定读取数据的方式,比如信号上升沿读取数据还是信号下降沿读取数据;
  • CKP可以配置为1或0。这意味着您可以根据需要将时钟的默认状态(IDLE)设置为高或低。极性反转可以通过简单的逻辑逆变器实现。您必须参考设备的数据手册才能正确设置CKP和CKE:
    CKP = 0:时钟空闲IDLE为低电平 0
    CKP = 1:时钟空闲IDLE为高电平1

3、时钟相位 CKE /Clock Phase (Edge)

  • 除配置串行时钟速率和极性外,SPI主设备还应配置时钟相位(或边沿)。根据硬件制造商的不同,时钟相位通常写为CKE或CPHA;
  • 顾名思义,时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿:
    CKE = 0:在时钟信号SCK的第一个跳变沿采样
    CKE = 1:在时钟信号SCK的第二个跳变沿采样

4、时钟配置总结

  • 综上几种情况,下图总结了所有时钟配置组合,并突出显示了实际采样数据的时刻;
    其中黑色线为采样数据的时刻;
    蓝色线为SCK时钟信号;
    在这里插入图片描述

6. 模式编号

  • SPI的时钟极性和相位的配置通常称为 SPI模式,所有可能的模式都遵循以下约定,具体如下表所示:
    在这里插入图片描述
  • 除此之外,还应该仔细检查微控制器数据手册中包含的模式表,以确保一切正常

7. 多从机模式

  • 前面说到SPI总线必须有一个主机,可以有多个从机,那么具体连接到SPI总线的方法有以下两种

7.1 第一种方法:多NSS

  • 通常,每个从机都需要一条单独的SS线
  • 如果要和特定的从机进行通讯,可以将相应的NSS信号线拉低,并保持其他NSS信号线的状态为高电平;如果同时将两个NSS信号线拉低,则可能会出现乱码,因为从机可能都试图在同一条MISO线上传输数据,最终导致接收数据乱码
  • 具体连接方式如下图:
    在这里插入图片描述

7.2 第二种方法:菊花链

  • 在数字通信世界中,在设备信号(总线信号或中断信号)以串行的方式从一 个设备依次传到下一个设备,不断循环直到数据到达目标设备的方式被称为菊花链;
  • 菊花链的最大缺点是因为是信号串行传输,所以一旦数据链路中的某设备发生故障的时候,它下面优先级较低的设备就不可能得到服务了;
  • 另一方面,距离主机越远的从机,获得服务的优先级越低,所以需要安排好从机的优先级,并且设置总线检测器,如果某个从机超时,则对该从机进行短路,防止单个从机损坏造成整个链路崩溃的情况
  • 具体的连接如下图:
    在这里插入图片描述
  • 其中红线加粗为数据的流向
  • 不难发现,菊花链模式充分使用了SPI其移位寄存器的功能,整个链充当通信移位寄存器,每个从机在下一个时钟周期将输入数据复制到输出

8. SPI通信协议的优缺点

1、优点:

  • 全双工串行通信;
  • 高速数据传输速率。
  • 简单的软件配置;
  • 极其灵活的数据传输,不限于8位,它可以是任意大小的字;
  • 非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同),不需要收发器(与CAN不同)

2、缺点/不足:

  • 没有硬件从机应答信号(主机可能在不知情的情况下无处发送);
  • 通常仅支持一个主设备;
  • 需要更多的引脚(与I2C不同);
  • 没有定义硬件级别的错误检查协议;
  • 与RS-232和CAN总线相比,只能支持非常短的距离

二、OLED屏显简介

1. 屏显概述

  • 有机发光二极管(OrganicLight-Emitting Diode,OLED),又称为有机电激光显示、有机发光半导体(OrganicElectroluminescence Display,OLED),是指有机半导体材料和发光材料在电场驱动下,通过载流子注入和复合导致发光的现象
  • 一般而言,OLED可按发光材料分为两种:小分子OLED和高分子OLED(也可称为PLED)
  • OLED是一种利用多层有机薄膜结构产生电致发光的器件,它很容易制作,而且只需要低的驱动电压,这些主要的特征使得OLED在满足平面显示器的应用上显得非常突出
    在这里插入图片描述

2. 屏显分类

  • 从器件结构上进行分类:
    1、 单层器件
    单层器件也就是在器件的正、负极之间接入一层可以发光的有机层,其结构为衬底/ITO/发光层/阴极。在这种结构中由于电子、空穴注入、传输不平衡,导致器件效率、亮度都较低,器件稳定性差
    2、双层器件
    双层器件是在单层器件的基础上,在发光层两侧加入空穴传输层(HTL)或电子传输层(ETL),克服了单层器件载流子注入不平衡的问题,改善了器件的电压-电流特性,提高了器件的发光效率
    4、三层器件
    三层器件结构是应用最广泛的一种结构,其结构为衬底/ITO/HTL/发光层/ETL/阴极。这种结构的优点是使激子被局限在发光层中,进而提高器件的效率
    4、多层结构
    多层结构的性能是比较好的一种结构,其能够很好的发挥各个层面的作用。发光层也可以由多层结构组成,由于各发机层之间相互独立,可以分别优化。因此,这种结构能充分发挥各有机层的作用,极大地提高了器件设计的灵活性

  • 从驱动方式上进行分类:
    1、一种是主动式,一种是被动式
    2、主动式的一般为有源驱动,被动式的为无源驱动。在实际的应用过程中,有源驱动主要是用于高分辨率的产品,而无源驱动主要应用在显示器尺寸比较小的显示器中

  • 从材料上进行分类:
    1、可根据有机物的种类划分,一种为小分子,另一种是高分子
    2、这两种器件的主要差别在制作工艺上,小分子器件主要采用的是真空热蒸发工艺,高分子器件采用的是旋转涂覆或者是喷涂印刷工艺

3. 屏显结构

  • OLED器件由基板、阴极、阳极、空穴注入层(HIL)、电子注入层(EIL)、空穴传输层(HTL)、电子传输层(ETL)、电子阻挡层(EBL)、空穴阻挡层(HBL)、发光层(EML)等部分构成:
    在这里插入图片描述

4. OLED屏显的优势

  • 1、相较于LED或LCD的晶体层,OLED的有机塑料层更薄、更轻而且更富于柔韧性
  • 2、OLED的发光层比较轻:因此它的基层可使用富于柔韧性的材料,而不会使用刚性材料。OLED基层为塑料材质,而LED和LCD则使用玻璃基层
  • 3、OLED比LED更亮:OLED有机层要比LED中与之对应的无机晶体层薄很多,因而OLED的导电层和发射层可以采用多层结构。此外,LED和LCD需要用玻璃作为支撑物,而玻璃会吸收一部分光线。OLED则无需使用玻璃
  • 4、OLED并不需要采用LCD中的逆光系统:LCD工作时会选择性地阻挡某些逆光区域,从而让图像显现出来,而OLED则是靠自身发光。因为OLED不需逆光系统,所以它们的耗电量小于LCD(LCD所耗电量中的大部分用于逆光系统)。这一点对于靠电池供电的设备(例如移动电话)来说,尤其重要
  • 5、OLED制造起来更加容易,还可制成较大的尺寸:OLED为塑胶材质,因此可以将其制作成大面积薄片状,而想要使用如此之多的晶体并把它们铺平,则要困难得多
  • 6、OLED的视野范围很广,可达170度左右:而LCD工作时要阻挡光线,因而在某些角度上存在天然的观测障碍。OLED自身能够发光,所以视域范围也要宽很多

5. OLED屏显的特性

  • OLED技术之所以能够获得广泛的应用,在于其与其它技术相比,具有以下优点:
    (1)功耗低
    (2)响应速度快
    (3)较宽的视角
    (4)能实现高分辨率显示
    (5)宽温度特性
    (6)OLED能够实现软屏
    (7)OLED成品的质量比较轻
    在这里插入图片描述

6. 电路图以及接法

  • 电路图:
    在这里插入图片描述
  • 引脚介绍:
    在这里插入图片描述
  • 开源代码分享链接:
    链接:https://pan.baidu.com/s/1gD4f5UOWS4tslH8N5MTBsw
    提取码:v5ti

三、实验一:STM32+OLED显示姓名学号

1. 字模提取器下载

  • 网站上搜索字模提取器 V2.2即可下载文件包:
    在这里插入图片描述
  • 打开应用程序效果:
    在这里插入图片描述

2. 字模器初始设置及点阵生成

  • 点击参数设置,点击其它选项,选择横向取模
    在这里插入图片描述
  • 文字输入区输入姓名,并ctrl+enter,得到显示图:
    在这里插入图片描述
  • 选择取模方式,点击C51格式,即可生成点阵:
    在这里插入图片描述

3. 工程代码修改与编写

  • 项目文件夹:
    在这里插入图片描述

  • 找到工程项目中oledfont.h文件下的cfont16数组:
    在这里插入图片描述

  • 在该数组中添加姓名以及字模提取器生成的点阵码
    在这里插入图片描述

  • test.c文件中对函数Test_MainPage进行修改:
    在这里插入图片描述

  • 主函数模块:
    在这里插入图片描述

  • 工程编译生成hex文件:
    在这里插入图片描述

4. 线路连接及代码烧录

  • 线路连接规则
    usb to ttl —> stm32f103c8t6:
    3V3 —> 3V3
    GND —> GND
    RXD —> A9
    TXD —> A10

    stm32f103c8t6 —> OLED屏显:
    在这里插入图片描述

  • 实物连接效果展示:
    在这里插入图片描述

  • 代码烧录:
    (注意此时核心板的跳帽应该为:BOOT0置1,BOOT1置0
    在这里插入图片描述

5. 屏显效果展示

  • 注意:此时的跳帽BOOT0和BOOT1都置0
    在这里插入图片描述

四、实验二:STM32+OLED显示温湿度

1. 字模提取器生成点阵

  • 字模提取器的初始化以及点阵生成方法在实验一已经讲解过,这里不做赘述
  • 在文字输入区输入“”温湿度显示,并ctrl+enter,得到显示图:
    在这里插入图片描述

2. 进入工程文件,修改代码

  • 项目文件夹:
    在这里插入图片描述
  • 找到项目中oledfont.h,增添所需文字点阵:
    在这里插入图片描述
  • bsp_i2c.c中重新写入函数read_AHT20
void read_AHT20(void)
{
	uint8_t   i;
	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}

	//-------------
	I2C_Start();

	I2C_WriteByte(0x71);
	ack_status = Receive_ACK();
	readByte[0]= I2C_ReadByte();
	Send_ACK();

	readByte[1]= I2C_ReadByte();
	Send_ACK();

	readByte[2]= I2C_ReadByte();
	Send_ACK();

	readByte[3]= I2C_ReadByte();
	Send_ACK();

	readByte[4]= I2C_ReadByte();
	Send_ACK();

	readByte[5]= I2C_ReadByte();
	SendNot_Ack();
	//Send_ACK();

	I2C_Stop();

	//--------------
	if( (readByte[0] & 0x68) == 0x08 )
	{
		H1 = readByte[1];
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		H1 = H1>>4;

		H1 = (H1*1000)/1024/1024;

		T1 = readByte[3];
		T1 = T1 & 0x0000000F;
		T1 = (T1<<8) | readByte[4];
		T1 = (T1<<8) | readByte[5];

		T1 = (T1*2000)/1024/1024 - 500;

		AHT20_OutData[0] = (H1>>8) & 0x000000FF;
		AHT20_OutData[1] = H1 & 0x000000FF;

		AHT20_OutData[2] = (T1>>8) & 0x000000FF;
		AHT20_OutData[3] = T1 & 0x000000FF;
	}
	else
	{
		AHT20_OutData[0] = 0xFF;
		AHT20_OutData[1] = 0xFF;

		AHT20_OutData[2] = 0xFF;
		AHT20_OutData[3] = 0xFF;
		printf("lyy");

	}
	/*通过串口显示采集得到的温湿度
	printf("\r\n");
	printf("温度:%d%d.%d",T1/100,(T1/10)%10,T1%10);
	printf("湿度:%d%d.%d",H1/100,(H1/10)%10,H1%10);
	printf("\r\n");*/
	t=T1/10;
	t1=T1%10;
	a=(float)(t+t1*0.1);
	h=H1/10;
	h1=H1%10;
	b=(float)(h+h1*0.1);
	sprintf(strTemp,"%.1f",a);   //调用Sprintf函数把DHT11的温度数据格式化到字符串数组变量strTemp中  
    sprintf(strHumi,"%.1f",b);    //调用Sprintf函数把DHT11的湿度数据格式化到字符串数组变量strHumi中  
	GUI_ShowCHinese(16,00,16,"温湿度显示",1);
	GUI_ShowCHinese(16,20,16,"温度",1);
	GUI_ShowString(53,20,strTemp,16,1);
	GUI_ShowCHinese(16,38,16,"湿度",1);
	GUI_ShowString(53,38,strHumi,16,1);
	delay_ms(1500);		
	delay_ms(1500);
}

在这里插入图片描述

  • 主函数main.c文件:
    在这里插入图片描述

3. 线路连接规则,烧录代码

  • usb to ttl —> stm32f103c8t6以及stm32f103c8t6 —> OLED屏显的连线在实验一已经介绍过了,这里不再赘述
  • STM32F103C8T6核心开发板 ----> 温湿度采集器的连接:
    3V3 —> 引脚1
    GND —> 引脚3
    PB6 —> SCL
    PB7 —> SDA
    在这里插入图片描述
    在这里插入图片描述

4. 效果演示

  • 烧录时:BOOT0置1,BOOT1置0;运行时:BOOT0和BOOT1都置0

    OLED显示温湿度采集信息

五、实验二:STM32+OLED滑动显示长字符

1. 字模提取器生成长字符点阵

  • 在文字输入区输入“”温湿度显示,并ctrl+enter,得到显示图:
    在这里插入图片描述

2. 修改对应工程项目代码

  • 项目文件夹:
    在这里插入图片描述
  • 添加文字字模到oledfont.h文件中:
    在这里插入图片描述
  • test.c中对函数Test_MainPage进行修改:
void TEST_MainPage(void)
{	

	GUI_ShowCHinese(28,10,16,"要什么避风港钞票才是梦想",1);
	delay_ms(1500);		
}

在这里插入图片描述

  • 修改主函数,添加相应的OLED滚动代码:
  • 删除while内的函数Test_MainPage:
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
int main(void)
{	
	delay_init();	    	       //延时函数初始化	  
	NVIC_Configuration(); 	   //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 	
	OLED_Init();			         //初始化OLED  
	OLED_Clear(0);             //清屏(全黑)
	OLED_WR_Byte(0x2E,OLED_CMD);        //关闭滚动
    OLED_WR_Byte(0x27,OLED_CMD);        //水平向左或者右滚动 26/27
    OLED_WR_Byte(0x00,OLED_CMD);        //虚拟字节
	OLED_WR_Byte(0x00,OLED_CMD);        //起始页 0
	OLED_WR_Byte(0x07,OLED_CMD);        //滚动时间间隔
	OLED_WR_Byte(0x07,OLED_CMD);        //终止页 7
	OLED_WR_Byte(0x00,OLED_CMD);        //虚拟字节
	OLED_WR_Byte(0xFF,OLED_CMD);        //虚拟字节
	TEST_MainPage();
	OLED_WR_Byte(0x2F,OLED_CMD);        //开启滚动
}

在这里插入图片描述

3. 线路连接

  • 烧录时:BOOT0置1,BOOT1置0
  • 运行时:BOOT0和BOOT1都置0
  • 该实验的连线与实验一相同,这里也不再赘述,自行翻阅查看!

4. 硬件实现视频展示

  • 视频展示:

    OLED显示长字符


总结

通过本次实验,掌握了对于SPI通信协议原理的理解,以及STM32f103c8t6与OLED屏显的联动应用演训;
在实验过程中,实验一与实验三都非常的顺利,只有实验二出现了无法采集温湿度的情况,这跟笔者上一篇博客分享的实验中出现的问题雷同,最开始以为是代码的问题,就用自己的板子烧录其他同学的代码,发现依旧解决不了;后来觉得是板子接触不良的问题,就用同学的板子烧录了自己的代码,发现问题得以解决!过程很费时间,但OLED屏显示的时候,还是觉得成就感满满呢!
同时也期待大家能够积极留言,指出我存在的问题,谢谢!

参考博客:
https://blog.csdn.net/qq_52362275/article/details/127573490
https://blog.csdn.net/qq_46467126/article/details/121439142?spm=1001.2014.3001.5502

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32控制OLED屏幕显示时,可以使用以下函数来实现: 1. `OLED_Init()`函数用于初始化OLED屏幕。 2. `OLED_Clear()`函数用于清除屏幕上的内容。 3. `OLED_ShowChar()`函数用于在指定位置显示一个字符。 4. `OLED_ShowNum()`函数用于在指定位置显示一个数字。 5. `OLED_ShowString()`函数用于在指定位置显示一个字符串。 此外,还可以使用`OLED_DrawPoint()`函数来在指定位置绘制一个点,使用`OLED_Fill()`函数来填充一个矩形区域,使用`OLED_Refresh_Gram()`函数来刷新屏幕显示。 在控制OLED屏幕显示时,需要使用SPI接口来与屏幕进行通信。可以使用`OLED_WR_Byte()`函数来向SSD1306写入一个字节的数据或命令。其中,`dat`参数表示要写入的数据,`cmd`参数表示数据或命令的标志,0表示命令,1表示数据。 综上所述,你可以使用以上提到的函数来控制STM32控制OLED屏幕的显示。 #### 引用[.reference_title] - *1* [使用STM32实现OLED屏显](https://blog.csdn.net/qq_52362275/article/details/127573490)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [基于stm32oled显示](https://blog.csdn.net/ababababa23/article/details/126438483)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值