IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 基于I2C/SPI总线的温湿度采集与OLED显示 -> 正文阅读

[嵌入式]基于I2C/SPI总线的温湿度采集与OLED显示


一、实验要求


  1. 学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:
    (1)解释什么是“软件I2C”和“硬件I2C”?(阅读野火配套教材的第23章“I2C–读写EEPROM”原理章节)
    (2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。

  2. 理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:
    (1)显示自己的学号和姓名;
    (2)显示AHT20的温度和湿度;
    (3)上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(最好使用硬件刷屏模式)。


二、实验过程及结果


(一)温湿度显示

1. I2C总线通信协议

I2C 通讯协议是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。对于通讯协议,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。

1)物理层

I2C 通讯设备之间的常用连接方式:

在这里插入图片描述

物理层特点
① 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
② 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
③ 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
④ 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
⑤ 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
⑥ 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
⑦ 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

2)协议层

I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。

① I2C 基本读写过程
I2C 通讯过程:
在这里插入图片描述
在这里插入图片描述
起始信号产生后,所有从机就开始等待主机紧接下来 广播 的从机地址信号(SLAVE_ADDRESS)。在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据 I2C 协议,这个从机地址可以是 7 位或 10 位。在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。

写数据
若配置的方向传输位为“写数据”方向,即第一幅图的情况,广播完地址,接收到应
答信号后,主机开始正式向从机传输数据(DATA),数据包的大小为 8 位,主机每发送完一个字节数据,都要等待从机的应答信号(ACK),重复这个过程,可以向从机传输 N 个数据,这个 N 没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。
读数据
若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址,接收到应
答信号后,从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有大小限制。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。
读和写数据
除了基本的读写,I2C 通讯更常用的是复合格式,即第三幅图的情况,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。

② 通讯的起始和停止信号
当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

在这里插入图片描述
③ 数据有效性
I2C 使用 SDA 信号线来传输数据,使用 SCL信号线进行数据同步。SDA 数据线在 SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候 SDA表示的数据有效,即此时的 SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时,SDA的数据无效,一般在这个时候 SDA进行电平切换,为下一次表示数据做好准备。

在这里插入图片描述
每次数据传输都以字节为单位,每次传输的字节数不受限制。

④ 地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。

在这里插入图片描述
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时,SDA 由主机控制,从机接收信号。

⑤ 响应
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。

在这里插入图片描述
传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。

2. “软件I2C”和“硬件I2C”

硬件 I2C:对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,效率远高于软件模拟的I2C;一般也较为稳定,但是程序较为繁琐。硬件(固件)I2C是直接调用内部寄存器进行配置;而软件I2C是没有寄存器这个概念的。
软件 I2C:一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形,不受管脚限制,接口比较灵活。

主要对比
1)硬件IIC用法比较复杂,模拟IIC的流程更清楚一些。
2)硬件IIC速度比模拟快,并且可以用DMA。
3)模拟IIC可以在任何管脚上,而硬件只能在固定管脚上。

软件i2c是程序员使用程序控制SCL、SDA线输出高低电平,模拟i2c协议的时序。一般较硬件i2c稳定,但是程序较为繁琐,不难。
硬件i2c只要调用i2c的控制函数即可,不用直接的去控制SCL,SDA高低电平的输出。但是有些单片机的硬件i2c不太稳定,调试问题较多。

3. 采集温湿度

1)软件操作

① 下载示例代码
https://github.com/Sunlight-Dazzling/stm32-AHT20/tree/master

在这里插入图片描述
② 分析代码
AHT20芯片的使用过程:

void  read_AHT20_once(void)
{
	delay_ms(10);

	reset_AHT20();       //重置AHT20芯片
	delay_ms(10);

	init_AHT20();        //初始化AHT20芯片
	delay_ms(10);

	startMeasure_AHT20();//开始测试AHT20芯片
	delay_ms(80);

	read_AHT20();       //读取AHT20采集的到的数据
	delay_ms(5);
}

AHT20芯片读取数据:

void read_AHT20(void)
{
	uint8_t   i;

	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}
	I2C_Start();//I2C启动

	I2C_WriteByte(0x71);//I2C写数据
	ack_status = Receive_ACK();//收到的应答信息
	readByte[0]= I2C_ReadByte();//I2C读取数据
	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();//I2C停止函数
	//判断读取到的第一个字节是不是0x08,0x08是该芯片读取流程中规定的,如果读取过程没有问题,就对读到的数据进行相应的处理
	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("读取失败!!!");

	}
	printf("\r\n");
	//根据AHT20芯片中,温度和湿度的计算公式,得到最终的结果,通过串口显示
	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");
}

③ 编译代码

在这里插入图片描述

2)硬件操作

① 硬件连接
USB 转 TTL 模块与STM32F103 核心板的连接:

在这里插入图片描述
AHT20 芯片与STM32F103 核心板的连接:

在这里插入图片描述
② 文件烧录

在这里插入图片描述
③ 串口调试

在这里插入图片描述

3)实际效果

AHT20温湿度采集

(二)OLED屏显

1. SPI协议

SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。

1)物理层

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

在这里插入图片描述

SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线为SS。
作用
① SS(Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、CS,以下用 NSS 表示。当有多个SPI从设备与 SPI主机相连时,设备的其它信号线 SCK、MOSI及 MISO同时并联到相同的SPI总线上,即无论有多少个从设备,都共同只使用这 3条总线;而每个从设备都有独立的这一条 NSS信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 NSS信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以 SPI通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。
② SCK(Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
③ MOSI(Master Output,Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
④ MISO(Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

2)协议层

与 I2C 的类似,SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。

① SPI 基本通讯过程
SPI 通讯的通讯时序:

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

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

③ 数据有效性
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 位为单位,每次传输的单位数不受限制。

④ CPOL/CPHA 及通讯模式
上图中的时序只是 SPI 中的其中一种通讯模式,SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。
时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。
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 的有效信号才发生切换。

当 CPHA=1 时,不受 CPOL 的影响,数据信号在 SCK 的偶数边沿被采样,见下图。

在这里插入图片描述
由 CPOL 及 CPHA 的不同状态,SPI 分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式 0”与“模式 3”。

在这里插入图片描述

2. OLED原理

OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。

OLED点阵显示
点阵屏像素按128列X64行组织,每一行128个像素单元的阴极是连接在一起,作为公共极(COM),每一列64个像素单元的阳极也连接在一起,作为一段(SEG)。行列交叉点上的LED就是一个显示单元,即一个像素。要点亮一个像素,只要在该像素所在列电极上加上正电压、行电极接地。同样,要驱动一整行图像,就需要同时把128列信号加载到列电极上,把该行行电极接地。该行显示时,其他63行均不能显示,其行电极应为高电平或悬空。
可见,整屏的显示,只能分时扫描进行,一行一行的显示,每次显示一行。行驱依次产生低电平扫描各行,列驱动读取显示数据依次加载到列电极上。扫描一行的时间称为行周期,完成一次全屏扫描,就叫做一帧。一般帧频大于60,人眼观察不到逐行显示。每行扫描显示用时叫占空比,占空比小,为达到相同的显示亮度,驱动电流就大。SSD1306段驱动最大电流为100uA,当整行128个像素全部点亮时,行电极就要流过12.8mA的电流。

3. OLED显示

1)软件操作

① 下载示例代码
0.96寸SPI_OLED模块配套资料包

资料包解压缩
打开 1-Demo 文件夹
选择 Demo_STM32 中的 0.96inch_OLED_Demo_STM32F103ZET6_Hardware_4-wire_SPI
点击 PROJECT ,选择工程 OLED

② 字库取模
下载PCtoLCD ,将中文进行取模得到中文的点阵编码并存到oledfont.h中。

在这里插入图片描述
在这里插入图片描述

将文字进行“左旋90度”、“垂直翻转”后,OLED 屏上显示的文字是正向的。

③ 修改示例代码
打开gui.c里的oledfont.h头文件,将cfont16[ ]数组的内容修改为需要的中文文字点阵。

const typFNT_GB16 cfont16[] = 
{
  "姝",0x20,0x20,0x21,0x20,0x21,0x20,0x21,0xFC,0xF9,0x20,0x4A,0x20,0x48,0x20,0x4B,0xFE,
 0x48,0x70,0x90,0xA8,0x50,0xA8,0x21,0x24,0x31,0x24,0x4A,0x22,0x48,0x20,0x80,0x20,/*"姝",0*/
  "华",0x08,0x80,0x08,0x88,0x10,0x90,0x30,0xE0,0x51,0x80,0x96,0x84,0x10,0x84,0x10,0x7C,
 0x11,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,/*"华",1*/

 "重",0x00,0x10,0x00,0xF8,0x3F,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x1F,0xF0,0x11,0x10,
 0x1F,0xF0,0x11,0x10,0x1F,0xF0,0x01,0x00,0x3F,0xF8,0x01,0x00,0xFF,0xFE,0x00,0x00,/*"重",0*/
  "庆",0x01,0x00,0x00,0x80,0x3F,0xFE,0x20,0x00,0x20,0x80,0x20,0x80,0x20,0x80,0x2F,0xFC,
 0x20,0x80,0x21,0x40,0x21,0x40,0x22,0x20,0x42,0x20,0x44,0x10,0x88,0x08,0x10,0x06,/*"庆",1*/
  "交",0x02,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x00,0x00,0x10,0x10,0x10,0x08,0x20,0x24,
 0x48,0x24,0x04,0x40,0x02,0x80,0x01,0x00,0x02,0x80,0x0C,0x40,0x30,0x30,0xC0,0x0E,/*"交",2*/
  "通",0x00,0x00,0x47,0xF8,0x20,0x10,0x21,0xA0,0x00,0x40,0x07,0xFC,0xE4,0x44,0x24,0x44,
 0x27,0xFC,0x24,0x44,0x24,0x44,0x27,0xFC,0x24,0x44,0x24,0x54,0x54,0x08,0x8F,0xFE,/*"通",3*/
  "大",0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,
 0x02,0x80,0x02,0x80,0x04,0x40,0x04,0x40,0x08,0x20,0x10,0x10,0x20,0x08,0xC0,0x06,/*"大",4*/
  "学",0x22,0x08,0x11,0x08,0x11,0x10,0x00,0x20,0x7F,0xFE,0x40,0x02,0x80,0x04,0x1F,0xE0,
 0x00,0x40,0x01,0x80,0xFF,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00,/*"学",5*/
	
	"温",0x00,0x00,0x23,0xF8,0x12,0x08,0x12,0x08,0x83,0xF8,0x42,0x08,0x42,0x08,0x13,0xF8,
 0x10,0x00,0x27,0xFC,0xE4,0xA4,0x24,0xA4,0x24,0xA4,0x24,0xA4,0x2F,0xFE,0x00,0x00,/*"温",0*/
	"度",0x01,0x00,0x00,0x80,0x3F,0xFE,0x22,0x20,0x22,0x20,0x3F,0xFC,0x22,0x20,0x22,0x20,
 0x23,0xE0,0x20,0x00,0x2F,0xF0,0x24,0x10,0x42,0x20,0x41,0xC0,0x86,0x30,0x38,0x0E,/*"度",0*/
	"湿",0x00,0x00,0x27,0xF8,0x14,0x08,0x14,0x08,0x87,0xF8,0x44,0x08,0x44,0x08,0x17,0xF8,
 0x11,0x20,0x21,0x20,0xE9,0x24,0x25,0x28,0x23,0x30,0x21,0x20,0x2F,0xFE,0x00,0x00,/*"湿",0*/
	":",0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
 0x00,0x00,0x30,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x30,0x00,0x00,0x00,0x00,0x00,/*":",0*/
	"℃",0x60,0x00,0x91,0xF4,0x96,0x0C,0x6C,0x04,0x08,0x04,0x18,0x00,0x18,0x00,0x18,0x00,
 0x18,0x00,0x18,0x00,0x18,0x00,0x08,0x00,0x0C,0x04,0x06,0x08,0x01,0xF0,0x00,0x00,/*"℃",0*/
	"%",0x00,0x00,0x18,0x04,0x24,0x08,0x24,0x10,0x24,0x20,0x24,0x40,0x24,0x80,0x19,0x00,
 0x02,0x60,0x04,0x90,0x08,0x90,0x10,0x90,0x20,0x90,0x40,0x90,0x00,0x60,0x00,0x00,/*"%",0*/
};

在这里插入图片描述
编辑main.c文件的代码,注释掉 while 中的函数。

#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
#include "bsp_i2c.h"
int main(void)
{	
	delay_init();	    	       //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	OLED_Init();			         //初始化OLED
	IIC_Init();                //初始化IIC
	OLED_Clear(0);             //清屏(全黑)
	while(1) 
	{
		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(0x01,OLED_CMD);        //终止页 1
		OLED_WR_Byte(0x00,OLED_CMD);        //虚拟字节
		OLED_WR_Byte(0xFF,OLED_CMD);        //虚拟字节
		TEST_ShowMyName();                  //显示文本
		read_AHT20_once();                  //读取温度并显示
		OLED_WR_Byte(0x2F,OLED_CMD);        //开启滚动		
		delay_ms(1500);
		delay_ms(1350);
		/*
		TEST_MainPage();         //主界面显示测试
		OLED_Clear(0); 
		Test_Color();            //刷屏测试
		OLED_Clear(0);
		Test_Rectangular();      //矩形绘制测试
		OLED_Clear(0); 
		Test_Circle();           //圆形绘制测试
		OLED_Clear(0); 
		Test_Triangle();         //三角形绘制测试
		OLED_Clear(0);  
		TEST_English();          //英文显示测试
		OLED_Clear(0); 
		TEST_Number_Character(); //数字和符号显示测试
		OLED_Clear(0); 
		TEST_Chinese();          //中文显示测试
		OLED_Clear(0); 
		TEST_BMP();              //BMP单色图片显示测试
		OLED_Clear(0); 
		TEST_Menu1();            //菜单1显示测试
		OLED_Clear(0); 
		TEST_Menu2();            //菜单2显示测试
		OLED_Clear(0); 
		*/		
	}
}

在这里插入图片描述

设置水平左右移步骤:
OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x26,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); //虚拟字节
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动

设置垂直和水平滚动的步骤:
OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
OLED_WR_Byte(0x29,OLED_CMD); //水平垂直和水平滚动左右 29/2A
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); //终止页 1
OLED_WR_Byte(0x01,OLED_CMD); //垂直滚动偏移量
OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动
设置前需要先发关闭滚动的指令2E,再发滚动指令29(向右)或2A(向左)和5条参数设置指令,用来设置持续水平滚动参数和决定滚动开始页、结束页、滚动速度和垂直滚动偏移,最后发开始滚屏指令2F。
在发送开始滚屏(2F)前要先传输好显示数据,如果在滚屏的时候传输显示数据RAM中的内容可能被损坏,无法正常显示。

test.h头文件中声明函数void TEST_ShowMyName(void)

在这里插入图片描述
test.c文件尾添加void TEST_ShowMyName(void)函数。

void TEST_ShowMyName(void)
{
	GUI_ShowCHinese(0,0,16,"重庆交通大学",1);
	GUI_ShowString(0,16,"631907030710",16,1);
	GUI_ShowCHinese(96,16,16,"姝华",1);
}

在这里插入图片描述

函数说明:
GUI_ShowChinese()
参数一:X 坐标
参数二:Y 坐标
参数三:汉字点阵大小
参数四:要显示的汉字
参数五:显示样式(1:白字黑底;0:黑字白底)
GUI_ShowString()
参数一:X 坐标
参数二:Y 坐标
参数三:字符串(ASCLL码)
参数四:bit (字符显示格式)
参数五:显示样式(1:白字黑底;0:黑字白底)

移植 AHT20 温湿度采集项目中的bsp_i2c.hbsp_i2c.csys.h(移植后改为AHT20_sys.h)、sys.c(移植后改为AHT20_sys.c),并将bsp_i2c.c中的串口发送改为OLED显示void Show_OLED(void)

void read_AHT20(void)
{
	uint8_t i;
	//初始化 readByte 数组
	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}
	I2C_Start();
	//通过发送 0x71 可以获取一个字节的状态字
	I2C_WriteByte(0x71);
	ack_status = Receive_ACK();	
	//接收 6 个 8 bit的数据
	readByte[0]= I2C_ReadByte();
	//发送 ACK 信号
	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();
	//发送 NACK 信号
	SendNot_Ack();
	I2C_Stop();
	//温湿度的二进制数据处理
	//0x68 = 0110 1000
    //0x08 = 0000 1000	
	if( (readByte[0] & 0x68) == 0x08 )
	{
		H1 = readByte[1];
		//H1 左移 8 位并与 readByte[2] 相或 
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		//H1 右移 4 位
		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("完成!\n");
	printf("----温度:%d%d.%d °C\n",T1/100,(T1/10)%10,T1%10);
	printf("----湿度:%d%d.%d %%",H1/100,(H1/10)%10,H1%10);
	printf("\n\n");
	*/
	Show_OLED();
}
//转化字符串输出到 OLED 上
void Show_OLED(void)
{
	t = T1/100;
	switch(t)
	{
		case 0:break;
		case 1:strTemp1 = "1";break;
		case 2:strTemp1 = "2";break;
		case 3:strTemp1 = "3";break;
		case 4:strTemp1 = "4";break;
		case 5:strTemp1 = "5";break;
		case 6:strTemp1 = "6";break;
		case 7:strTemp1 = "7";break;
		case 8:strTemp1 = "8";break;
		case 9:strTemp1 = "9";break;
	}
	t = (T1/10)%10;
	switch(t)
	{
		case 0:strTemp2 = "0";break;
		case 1:strTemp2 = "1";break;
		case 2:strTemp2 = "2";break;
		case 3:strTemp2 = "3";break;
		case 4:strTemp2 = "4";break;
		case 5:strTemp2 = "5";break;
		case 6:strTemp2 = "6";break;
		case 7:strTemp2 = "7";break;
		case 8:strTemp2 = "8";break;
		case 9:strTemp2 = "9";break;
	}
	t = T1%10;
	switch(t)
	{
		case 0:strTemp3 = "0";break;
		case 1:strTemp3 = "1";break;
		case 2:strTemp3 = "2";break;
		case 3:strTemp3 = "3";break;
		case 4:strTemp3 = "4";break;
		case 5:strTemp3 = "5";break;
		case 6:strTemp3 = "6";break;
		case 7:strTemp3 = "7";break;
		case 8:strTemp3 = "8";break;
		case 9:strTemp3 = "9";break;
	}
	t = H1/100;
	switch(t)
	{
		case 0:break;
		case 1:strHumi1 = "1";break;
		case 2:strHumi1 = "2";break;
		case 3:strHumi1 = "3";break;
		case 4:strHumi1 = "4";break;
		case 5:strHumi1 = "5";break;
		case 6:strHumi1 = "6";break;
		case 7:strHumi1 = "7";break;
		case 8:strHumi1 = "8";break;
		case 9:strHumi1 = "9";break;
	}
	t = H1/100;
	switch(t)
	{
		case 0:strHumi2 = "0";break;
		case 1:strHumi2 = "1";break;
		case 2:strHumi2 = "2";break;
		case 3:strHumi2 = "3";break;
		case 4:strHumi2 = "4";break;
		case 5:strHumi2 = "5";break;
		case 6:strHumi2 = "6";break;
		case 7:strHumi2 = "7";break;
		case 8:strHumi2 = "8";break;
		case 9:strHumi2 = "9";break;
	}
	t = H1/100;
	switch(t)
	{
		case 0:strHumi3 = "0";break;
		case 1:strHumi3 = "1";break;
		case 2:strHumi3 = "2";break;
		case 3:strHumi3 = "3";break;
		case 4:strHumi3 = "4";break;
		case 5:strHumi3 = "5";break;
		case 6:strHumi3 = "6";break;
		case 7:strHumi3 = "7";break;
		case 8:strHumi3 = "8";break;
		case 9:strHumi3 = "9";break;
	}
	GUI_ShowString(40,32,"    ",16,1);
	GUI_ShowString(40,48,"    ",16,1);
  GUI_ShowCHinese(0,32,16,"温度:",1);
	GUI_ShowString(40,32,strTemp1,16,1);
	GUI_ShowString(48,32,strTemp2,16,1);
	GUI_ShowString(56,32,".",16,1);
	GUI_ShowString(64,32,strTemp3,16,1);
	GUI_ShowCHinese(72,32,16,"℃",1);
  GUI_ShowCHinese(0,48,16,"湿度:",1);
	GUI_ShowString(40,48,strHumi1,16,1);
	GUI_ShowString(48,48,strHumi2,16,1);
	GUI_ShowString(56,48,".",16,1);
	GUI_ShowString(64,48,strHumi3,16,1);
	GUI_ShowCHinese(72,48,16,"%",1);
}

在这里插入图片描述
编译程序,生成.hex文件。

在这里插入图片描述

2)硬件操作

① 硬件连接

在这里插入图片描述
在这里插入图片描述
② 文件烧录

在这里插入图片描述

3)实际效果

基于SPI的OLED屏显


三、实验总结


在本次实验的过程中,我遇到了一些问题,特别是在采集温湿度时不知道为什么温度和湿度都显示为0,最开始我以为是传感器AHT20有问题,但是我换了4个不同的AHT20后温湿度还是显示为0,询问老师后也没能解决这个问题。最后在其他同学连接AHT20成功采集到温湿度后,我才意识到到可能是杜邦线本身的问题。因此,我更换了之前使用的杜邦线母线,成功采集到了温湿度。通过本次实验,我了解了I2C总线通信协议、OLED屏显和汉字点阵编码原理,知晓了什么是“软件I2C”和“硬件I2C”,学会了使用STM32F103完成基于I2C的AHT20温湿度采集和基于SPI的OLED屏显。总而言之,在此次实验过程中,我受益良多。


四、参考资料


1、硬件IIC和软件IIC区别
2、SSD1306-0.96寸oled屏-滚动指令介绍
3、stm32通过I2C接口实现温湿度(AHT20)的采集
4、基于 SPI 协议在 0.96 寸 OLED上【平滑显示汉字】及【温湿度数据采集显示】
5、零死角玩转STM32—F103指南者.pdf

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-11-22 12:30:12  更:2021-11-22 12:30:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/8 4:51:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码