本文主要介绍I2C总线通信协议,并在此基础上,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。
目录
一、任务要求
学习 I2C 总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:
1)解释什么是“软件I2C”和“硬件I2C”? (阅读野火配套教材的第23章“I2C–读写EEPROM”原理章节)
2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win11)。
二、I2C总线通信协议
1、I2C简介
IIC(Inter-Integrated Circuit) 总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在 小数据量场合 使用, 传输距离短 ,任意时刻只能有一个主机等特性。
I2C 是很常见的一种总线协议, I2C 是 NXP 公司设计的, I2C 使 用两条线在主控制器和从机之间进行数据通信 。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),由于不接上拉电阻,这两个引脚为悬空状态,由于 悬空状态引脚的电平是无法确定 的,所以这 两条数据线需要接上拉电阻 ,一般是4.7K,总线 空闲 的时候 SCL 和 SDA 都处于高电平。
I2C总线 在 CPU 与被控器件之间、被控器件与被控器件之间进行 双向传送 , I2C 总线 标准模式 下速度可以达到 100Kb/S,快速模式下可以达到 400Kb/S。 I2C 总线工作是按照一定的协议来运行的,I2C 是 支持多从机 的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C 从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备了,一个 I2C 总线可以连接多个 I2C 设备。
注意 I2C 是为了与 低速设备通信 而发明的,所以 I2C 的传输速率比不上 SPI。
2、I2C总线的特点
I²C特性:
-
只需要一根 数据线SDA 和一根 时钟线SCL,**SDA(串行数据线)**和 SCL(串行时钟线) 都是 双向 I/O线
SCL(Serial Clock): 串行 时钟线,传输CLK信号,一般是主设备向从设备提供
SDA(Serial Data) : 串行 数据线, 传输通信数据 -
多主机总线,任何器件既可以作为主机又可以作为从机,但是 同一时刻只能有一个主机,连接到总线的每个设备均可通过 唯一地址 进行软件寻址;可以通过 外部连线检测 ,便于系统故障诊断和调试
-
连接到相同总线上的IC数量只 受总线最大电容的限制,串行的8位双向数据传输位速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s
-
在总线上 消耗的电流很小,因此,总线上扩展的器件数量主要由电容负载来决定,抗高噪声干扰,增加总线驱动器可以使总线电容扩大10倍,传输距离达到15m;兼容不同电压等级的器件, 工作温度范围宽
-
接口电路为 开漏输出,需 通过上拉电阻接电源VCC ,当总线 空闲 时,两根线都是 高电平,连接总线的外同器件都是CMOS器件输出级也是开漏电路。为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是 漏极开路(OD)输出 或 集电极开路(OC)输出
-
没有严格的波特率要求 ,例如使用RS232,主设备生成总线时钟,产生起始信号和停止信号
-
IIC是 半双工,而不是全双工 ,同一时间 只可以 单向通信
传输速度:
- 标准模式: Standard Mode = 100 Kbps
- 快速模式: Fast Mode = 400 Kbps
- 高速模式: High speed mode = 3.4 Mbps
- 超快速模式: Ultra fast mode = 5 Mbps
最大主设备数: 无限制
最大从机数: 理论上是127
3、I2C物理层
I2C 总线 在物理连接上非常简单,分别由 SDA(串行数据线) 和 SCL(串行时钟线) 及 上拉电阻 组成。通信原理是通过对 SCL和SDA线高低电平时序的控制 ,来产生I2C总线协议所需要的信号进行数据的传递。在总线 空闲 状态时,SCL和SDA 被上拉电阻Rp拉高 ,使SDA和SCL线都 保持高电平。
I2C通信方式为半双工 ,只有一根 SDA 线,同一时间只可以单向通信 ,485也为半双工,SPI 和 uart 通信为 全双工。
主机和从机的概念:
主机就是负责整个系统的任务协调与分配,从机一般是通过接收主机的指令从而完成某些特定的任务,主机和从机之间通过总线连接,进行数据通讯。
发布主要命令的称为主机,接受命令的称为从机
4、I2C协议层
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是: 开始信号、结束信号 和 应答信号。
开始信号 SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号 SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号 接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的 ,结束信号和应答信号,都可以不要。
5、I2C总线时序图
起始位:
I2C开始通信的标志,主机通过这个标志告诉从机要开始进行通信了,当 SCL为高电平,SDA出现下将沿时表示起始位
停止位:
I2C通信结束的标志,当 SCL为高电平,SDA出现上升沿时表示停止位
数据传输:
I2C数据总线传输要保证在 SCL为高电平时,SDA数据稳定 ,所以SDA上数据变化只能在SCL为低电平时
地址帧/读写位/ACK位:
地址帧: 这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为1 的话表示这是一个读操作,为 0 的话表示这是一个写操作
读/写位: 0:低电平,主设备向从设备写数据,1:高电平从机读数据
ACK: 消息的每一个帧都有一个确认位,如果设备接收到了地址帧或者数据帧,都会给发送设备返回一个ACK
空闲状态:
当IIC总线的数据线SDA和时钟线SCL 两条信号线同时处于高电平 时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在 截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高
应答信号:
发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
应答信号为 低电平 时,规定为 有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;应答信号为 高电平 时,规定为 非应答位(NACK),一般表示接收器接收该字节没有成功
接收器在第9个时钟脉冲之前的低电平期间将数据线SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平,发送一个ACK信号, 如果接收器是主机端,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放数据线SDA,以便主机端发送一个停止信号P
补充:
非应答信号可能是主机发出的也可能是从机产生的
有几种可能:
1、I2C总线上没有主机所指定地址的从机设备
2、从机正在执行一些操作,处于忙状态,还没有准备好与主机通讯
3、主机发送的一些控制命令,从机不支持
4、主机接收从机数据时,主机产生非应答信号,通知从机数据传输结束,不要再发数据了
6、数据传输
数据发送:
主机向从机写时序,红色部分表示主机发送,蓝色部分表示从机发送。A表示应答,NA表示非应答(高电平)。S表示起始信号,P表示终止信号
- 1.开始信号:在SCL从高电平切换为低电平之前,主设备通过将SDA从高电平切换到低电平,启动信号与停止信号始终是由主设备控制产生的
- 2.主设备向每个从设备发送要与之通信的从设备的7位或10位地址,通过发送具体的设备地址来决定要访问哪个I2C设备,这是一个8位的数据,其中高8位是设备地址,最后1位是读写位,1表示读操作,0表示写操作
- 3.每一个从设备会根据主设备发过来的地址与自身的地址做比较,如果地址匹配,那么从设备会将SDA拉低来返回ACK应答信号,地址不匹配时,从设备会将SDA拉高,在从设备没有将SDA拉低会将Master视为超时,将放弃数据传送,发送“Stop”
- 4.重新发送开始信号
- 5.主机发送要写入的丛机的寄存器地址
- 6.从机发送ACK信号
- 7.发送要写入寄存器的数据帧,传输完每个数据帧后,即8位数据,主机将SDA拉高,等待从机将SDA拉低,即返回ACK,以确认已成功接收到该帧
- 8.结束信号:主设备将SCL切换为高电平,再将SDA切换为高电平,从而向从设备发送停止信号
数据接收:
接收数据就是读时序的过程,读时序主要分为四个大步骤:1.发送设备地址;2.发送要读取的寄存器值;3.重新发送设备地址;4.I2C 从设备输出要读取的寄存器值
- 1.主机发送起始信号
- 2.主机发送要读写的从设备地址
- 3.读写控制位,因为是向 I2C 从设备发送数据,因此是写信号
- 4.从机发送 ACK 应答信号
- 5.重新发送start信号
- 6.主机发送要读取的寄存器地址
- 7.从机发送的 ACK 应答信号
- 8.重新发送 START 信号
- 9.重新发送要读取的 I2C 从设备地址
- 10.读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据
- 11.从机发送的 ACK 应答信号
- 12.从 I2C 器件里面读取数据
- 13.在读到最后8位数据时,主机要将SDA置1,主机发出 NO ACK 信号,表示读取完成,不需要从机再发送 ACK 信号了
- 14.主机发出 STOP 信号,停止 I2C 通信
注意:
SCL一直由主机端控制 ,SDA依照数据传送的方向,读数据时由从机控制SDA,写数据时由主机控制SDA。当8位数据传送完毕之后,应答位或者否应答位的SDA控制权与数据位传送时相反
7、软件I2C和硬件I2C
I2C分为 软件I2C 和 硬件I2C
软件IIC 软件I2C通信指的是用单片机的两个I/O端口 模拟 出来的I2C,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。
直接使用 CPU 内核按照 I2C 协议的要求控制 GPIO 输出高低电平,从而模拟I2C。
使用: 需要在控制产生 I2C 的起始信号时,控制作为SCL 线的 GPIO 引脚输出高电平,然后控制作为 SDA 线的 GPIO 引脚在此期间完成由高电平至低电平的切换,最后再控制SCL线切换为低电平,这样就输出了一个标准的 I2C 起始信号。
硬件I2C 一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。
直接利用 STM32 芯片中的硬件 I2C 外设。
使用: 只要配置好对应的寄存器,外设就会产生标准串口协议的时序。在初始化好 I2C 外设后,只需要把某寄存器位置 1,此时外设就会控制对应的 SCL 及 SDA 线自动产生 I2C 起始信号,不需要内核直接控制引脚的电平。
硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
三、实现过程
1、任务要求
阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win11)。
2、准备工作
建议根据正点原子或者野火提供的示例代码进行修改与代码添加。
相关的AHT20资料大家可以去官网进行查看与下载: http://www.aosong.com/class-36.html
3、核心代码
AHT20芯片的使用过程 read_AHT20_once
函数:
void read_AHT20_once(void)
{
delay_ms(1);
reset_AHT20();//重置AHT20芯片
delay_ms(1);
init_AHT20();//初始化AHT20芯片
delay_ms(1);
startMeasure_AHT20();//开始测试AHT20芯片
delay_ms(1);
read_AHT20();//读取AHT20采集的到的数据
delay_ms(1);
}
AHT20芯片读取数据 read_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\n",T1/100,(T1/10)%10,T1%10);
printf("当前湿度为:%d%d.%d",H1/100,(H1/10)%10,H1%10);
printf("\r\n");
}
4、项目总览
5、项目地址
我已经将项目打包上传到gitee仓库里面了,地址如下:
四、编译烧录
1、硬件连接
通信模块和供电模块连接方法:
USB转TTL | STM32F103C8T6 |
---|---|
GND | G |
3V3 | 3V3 |
RXD | PA9 |
TXD | PA10 |
注意将核心板上的BOOT0设置为1,BOOT1设置为0
AHT20传感器模块连接方法:
STM32F103C8T6 | AHT20传感器 |
---|---|
3.3V | 1号脚(VDD) |
PB7 | 2号脚(SDA) |
GND | 3号脚(GND) |
PB6 | 4号脚(SCL) |
2、编译
(1)点击 Options for Target…,在 Output 下勾选 Create HEX File
(2)在 Debug 下勾选 Use Simulator,将 Dialog DLL下的输入框改为 DARMSTM.DLL,Parameter 输入框改为 -pSTM32F103C8
(3)在 Target 选择使用 V5编译器
(4)点击 Rebuild进行编译
3、烧录
打开 FlyMcu 烧录助手,选择刚刚生成的 HEX文件,点击 开始编程 进行烧录
4、运行效果
使用SSCOM串口调试助手观察串口输出,发现可以正常显示采集到的温度。我把AHT20传感器放在电脑散热口,发现测量到的温度和湿度都发生了改变。
基于I2C协议使用AH20温湿度传感器采集温湿度
五、总结
通过本次实验,我基本了解了I2C总线通信协议。并在此基础上,我实现了使用 STM32F103C8T6 基于 I2C 协议的 AHT20 温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。
本次实验不仅加深了我对I2C总线协议的理解,还加深了我对串口通信相关知识的理解,还学习到了AHT20温湿度传感器的使用。
本次实验完成过程中也遇到了不少问题。刚开始的时候,我本来打算用自己最开始创建的模板来实现,但是进行到一半的时候,发现缺少一些必须的文件,比如 bsp_i2c.c 文件;另外就是当代码开始运行时,串口读到的温度和湿度一直都是 0.0 。最后发现是因为:由于传感器引脚很细,不能很好地固定住,于是为了让温度传感器更加牢固地接到杜邦线上,给传感器的四个引脚点了一些锡,这样的确把温度传感器固定住了,但是却导致了接触不良,后面换了一个传感器就成功解决了这个问题。
最后就是,只有在动手实践的过程中才能发现自己掌握到的知识的局限性,才能发现实现过程中容易遇到的问题以及解决办法,从而提升自己解决问题的能力。
参考列表:
1.I2C总线通信协议及实操stm32通过I2C实现温湿度(AHT20)采集
2.I2C总线协议详解(特点、通信过程、典型I2C时序)
3.i2c总线协议简介
4.一文看懂 I2C 通信协议