一、实验要求
-
学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务: (1)解释什么是“软件I2C”和“硬件I2C”?(阅读野火配套教材的第23章“I2C–读写EEPROM”原理章节) (2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。 -
理解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();
delay_ms(10);
init_AHT20();
delay_ms(10);
startMeasure_AHT20();
delay_ms(80);
read_AHT20();
delay_ms(5);
}
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();
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("读取失败!!!");
}
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");
}
③ 编译代码
2)硬件操作
① 硬件连接 USB 转 TTL 模块与STM32F103 核心板的连接:
AHT20 芯片与STM32F103 核心板的连接:
② 文件烧录
③ 串口调试
3)实际效果
(二)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,
"华",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,
"重",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,
"庆",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,
"交",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,
"通",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,
"大",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,
"学",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,
"温",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,
"度",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,
"湿",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,
":",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,
"℃",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,
"%",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,
};
编辑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);
OLED_Init();
IIC_Init();
OLED_Clear(0);
while(1)
{
OLED_WR_Byte(0x2E,OLED_CMD);
OLED_WR_Byte(0x27,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0x07,OLED_CMD);
OLED_WR_Byte(0x01,OLED_CMD);
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);
}
}
设置水平左右移步骤: 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.h 、bsp_i2c.c 、sys.h (移植后改为AHT20_sys.h )、sys.c (移植后改为AHT20_sys.c ),并将bsp_i2c.c 中的串口发送改为OLED显示void Show_OLED(void) 。
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();
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;
}
Show_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)实际效果
三、实验总结
在本次实验的过程中,我遇到了一些问题,特别是在采集温湿度时不知道为什么温度和湿度都显示为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
|