问题描述
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实训室!”或者一段歌词或诗词(最好使用硬件刷屏模式)。
3. 用示波器和逻辑分析仪对I2C、SPI、串口的信号进行测量,深入了解这些协议原理,并对照上述作业代码进行分析(时间不够可下次实验完成)。
一、关于I2C协议的简单说明
1、 I2C协议
I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
1.1 I2C物理层
I2C 通讯设备之间的常用连接方式见图 24-1。
它的物理层有如下特点: (1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线 中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。 (2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步。 (3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之 间的访问。 (4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空 闲,都输出高阻态时,由上拉电阻把总线拉成高电平。 (5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用 总线。 (6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式 下可达 3.4Mbit/s,但目前大多 I 2 C 设备尚不支持高速模式。 (7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
1.2协议层
I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。 ①I2C基本读写过程 先看看 I2C 通讯过程的基本结构,它的通讯过程见图 24-2、图 24-3 及图 24-4。 起始信号产生后,所有从机就开始等待主机紧接下来 广播 的从机地址信号(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 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。
② 通讯的起始和停止信号 ③ 数据有效性 I2C使用 SDA信号线来传输数据,使用 SCL信号线进行数据同步。见图 24-6。SDA数据线在 SCL的每个时钟周期传输一位数据。传输时,SCL为高电平的时候 SDA表示的数据有效,即此时的SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL为低电平时,SDA的数据无效,一般在这个时候 SDA进行电平切换,为下一次表示数据做好准备。每次数据传输都以字节为单位,每次传输的字节数不受限制。 ④ 地址及数据方向 I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W——),第 8位或第 11位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。见图 24-7。 读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时,SDA由主机控制,从机接收信号。 ⑤ 响应 I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。 传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)
2、 软硬件I2C
硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的;软件I2C一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形。硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。模拟I2C 是通过GPIO,软件模拟寄存器的工作方式,而硬件(固件)I2C是直接调用内部寄存器进行配置。 ①硬件I2C
直接利用 STM32 芯片中的硬件 I2C 外设。
只要配置好对应的寄存器,外设就会产生标准串口协议的时序。在初始化好 I2C 外设后,只需要把某寄存器位置 1,此时外设就会控制对应的 SCL 及 SDA 线自动产生 I2C 起始信号,不需要内核直接控制引脚的电平。
②软件I2C
直接使用 CPU 内核按照 I2C 协议的要求控制 GPIO 输出高低电平,从而模拟I2C。
需要在控制产生 I2C 的起始信号时,控制作为 SCL 线的 GPIO 引脚输出高电平,然后控制作为 SDA 线的 GPIO 引脚在此期间完成由高电平至低电平的切换,最后再控制SCL 线切换为低电平,这样就输出了一个标准的 I2C 起始信号。
③两者的差别
硬件 I2C 直接使用外设来控制引脚,可以减轻 CPU 的负担。不过使用硬件I2C 时必须使用某些固定的引脚作为 SCL 和 SDA,软件模拟 I2C 则可以使用任意 GPIO 引脚,相对比较灵活。对于硬件I2C用法比较复杂,软件I2C的流程更清楚一些。如果要详细了解I2C的协议,使用软件I2C可能更好的理解这个过程。在使用I2C过程,硬件I2C可能通信更加快,更加稳定。
二、AHT20采集温湿度
1、代码准备工作
1.1下载示例代码
https://github.com/Sunlight-Dazzling/stm32-AHT20/tree/master
1.2 理解代码
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");
}
1.3代码编译
2、硬件连接
2.1连接方式
USB 转 TTL 模块与STM32F103 核心板的连接:
GND----GND
3V3----3.3
RXD----A9
TXD----A10
AHT20 芯片与STM32F103 核心板的连接:
SCL----B6
GND----GND
SDA----B7
VCC----5V
2.2烧录
3、结果显示
由于GIF动态图会比较模糊,这里用连续图片表示,不难看出温湿度是实时变化的。
三、OLED屏显
1、SPI协议简介
1.1 SPI连接方式(物理层)
SS( Slave Select):从设备选择信号线,常称为片选信号线。 SCK (Serial Clock):时钟信号线,用于通讯数据同步。 MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。 MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。
1.2 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 的时序图: 当 CPHA=1 时,不受 CPOL 的影响,数据信号在 SCK 的偶数边沿被采样,见下图。 由 CPOL 及 CPHA 的不同状态,SPI 分成了四种模式,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式 0”与“模式 3”。
2、OLED原理
OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。
3、汉字点阵编码原理与显示
3.1汉字点阵编码
在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点,每个汉字都是由一个矩形的点阵组成,0 代表没有点,1 代表有点,将 0 和 1 分别用不同颜色画出,就形成了一个汉字,常用的点阵矩阵有 1212, 1414, 16*16 三 种字库。
字库根据字节所表示点的不同有分为横向矩阵和纵向矩阵,目前多数的字库都是横向矩阵的存储方式(用得最多的应该是早期 UCDOS 字库),纵向矩阵一 般是因为有某些液晶是采用纵向扫描显示法,为了提高显示速度,于是便把字库 矩阵做成纵向,省得在显示时还要做矩阵转换
3.2 OLED点阵显示
点阵屏像素按128列X64行组织,每一行128个像素单元的阴极是连接在一起,作为公共极(COM),每一列64个像素单元的阳极也连接在一起,作为一段(SEG)。行列交叉点上的LED就是一个显示单元,即一个像素。要点亮一个像素,只要在该像素所在列电极上加上正电压、行电极接地。同样,要驱动一整行图像,就需要同时把128列信号加载到列电极上,把该行行电极接地。该行显示时,其他63行均不能显示,其行电极应为高电平或悬空。
可见,整屏的显示,只能分时扫描进行,一行一行的显示,每次显示一行。行驱依次产生低电平扫描各行,列驱动读取显示数据依次加载到列电极上。扫描一行的时间称为行周期,完成一次全屏扫描,就叫做一帧。一般帧频大于60,人眼观察不到逐行显示。每行扫描显示用时叫占空比,占空比小,为达到相同的显示亮度,驱动电流就大。SSD1306段驱动最大电流为100uA,当整行128个像素全部点亮时,行电极就要流过12.8mA的电流。
4、显示自己的学号和姓名
4.1代码准备工作
下载示例代码 http://www.lcdwiki.com/zh/0.96inch_SPI_OLED_Module
打开 1-Demo 文件夹
选择 Demo_STM32 中的 0.96inch_OLED_Demo_STM32F103ZET6_Hardware_4-wire_SPI
点击 PROJECT ,打开工程 OLED
字库取模 由于OLED显示中使用了中文字符,因此需要将中文进行取模得到中文的点阵编码并存到oledfont.h中,方便程序调用并显示到OLED上去。在此我们应用的是PCtoLCD2002软件来对汉字取模 选项设置: 输入自己名字,点击生成字模: 记事本打开保存的字模,复制到代码里 修改示例代码:
打开gui.c里的oledfont.h头文件,将cfont16[ ]数组的内容修改为需要的中文文字点阵。
const typFNT_GB16 cfont16[] =
{
"魏",0x0C,0x20,0x70,0x40,0x11,0xFC,0xFF,0x24,0x39,0x24,0x55,0xFC,0x93,0x24,0x01,0x24,
0x11,0xFC,0xFC,0x40,0x24,0x68,0x44,0xB2,0x28,0xBE,0x11,0x20,0x29,0x22,0xC6,0x1E,/*"魏",0*/
"帅",0x08,0x20,0x08,0x20,0x48,0x20,0x48,0x20,0x49,0xFC,0x49,0x24,0x49,0x24,0x49,0x24,
0x49,0x24,0x49,0x24,0x49,0x24,0x09,0x34,0x11,0x28,0x10,0x20,0x20,0x20,0x40,0x20 ,/*"帅",1*/
"兵",0x00,0x20,0x00,0xF0,0x1F,0x00,0x10,0x00,0x10,0x00,0x1F,0xF8,0x10,0x80,0x10,0x80,
0x10,0x80,0x10,0x80,0xFF,0xFE,0x00,0x00,0x08,0x40,0x10,0x20,0x20,0x10,0x40,0x08 ,/*"兵",2*/
};
修改test.c里面的学号姓名
4.2硬件连线
4.3结果显示
烧录 结果显示
5、显示AHT20的温度和湿度
main.c
#include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
int main(void)
{
delay_init(); //延时函数初始化
uart_init(115200);
IIC_Init();
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
OLED_Init(); //初始化OLED
OLED_Clear(0);
while(1)
{
//printf("温度湿度显示");
read_AHT20_once();
OLED_Clear(0);
delay_ms(1500);
}
}
主要是将温度采集通过串口发到OLED屏。 编译烧录 运行结果 由于没有温湿度传感器在手,故这里只能显示一下屏幕,后续温湿度传感器连接上去就可以实时显示温湿度了。
6、左右的滑动显示长字符
6.1生成字模
生成字模过程在上面已经描述,这里不再赘述 保存后用记事本打开
6.2修改代码
打开gui.c里的oledfont.h头文件,将cfont16[ ]数组的内容修改为上述生成的中文文字点阵。
"永",0x02,0x00,0x01,0x00,0x00,0x80,0x1F,0x00,0x01,0x04,0x01,0x08,0x7D,0x90,0x05,0xA0,
0x05,0x40,0x09,0x40,0x09,0x20,0x11,0x10,0x21,0x08,0xC1,0x06,0x05,0x00,0x02,0x00,/*"永",0*/
"远",0x00,0x00,0x23,0xF8,0x10,0x00,0x10,0x00,0x00,0x00,0x07,0xFC,0xF1,0x20,0x11,0x20,
0x11,0x20,0x11,0x20,0x11,0x24,0x12,0x24,0x12,0x24,0x14,0x1C,0x28,0x00,0x47,0xFE,/*"远",1*/
"二",0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,/*"二",2*/
"十",0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,
0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,/*"十",3*/
"赶",0x08,0x00,0x08,0xFC,0x08,0x20,0x7E,0x20,0x08,0x20,0x08,0x20,0xFE,0x20,0x09,0xFE,
0x28,0x20,0x28,0x20,0x2E,0x20,0x28,0x20,0x28,0x20,0x58,0x20,0x4F,0xFE,0x80,0x00,/*"赶",4*/
"朝",0x08,0x00,0x08,0x3E,0xFF,0xA2,0x08,0x22,0x7F,0x22,0x41,0x3E,0x7F,0x22,0x41,0x22,
0x7F,0x22,0x49,0x3E,0x08,0x22,0xFF,0xA2,0x08,0x42,0x08,0x42,0x08,0x8A,0x09,0x04,/*"朝",5*/
"暮",0x04,0x40,0x7F,0xFC,0x04,0x40,0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x10,0x10,0x1F,0xF0,
0x04,0x00,0xFF,0xFE,0x10,0x10,0x2F,0xE8,0xC8,0x26,0x0F,0xE0,0x08,0x20,0x0F,0xE0,/*"暮",6*/
修改test.c
6.3编译烧录
6.4滚动结果显示
四、总结
这次实验主要是对于硬件接线的把握以及原理的理解,在实验过程中,也遇到了一些问题,比如代码的修改过程,在字模复制上去后一定要记得,在16进制码前面一定要加上文字,不然会无法显示,总的来说,通过参考大佬博客,还是比较成功的得出了实验结果,另外温湿度采集显示那一块,由于温湿度传感器在实验室,这里忽略了一下,但是连上温湿度传感器一定可以显示出预期效果。
五、参考文献
https://blog.csdn.net/shutupbb/article/details/121517934 https://blog.csdn.net/weixin_45919652/article/details/121409895 https://blog.csdn.net/qq_60678931/article/details/121410035 https://blog.csdn.net/qq_43279579/article/details/111678857 https://blog.csdn.net/qq_43279579/article/details/111597278
|