stm32通过I2C实现温湿度(AHT20)采集

一、I2C总线通信协议

1.1 I2C介绍

IIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。

在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps 以上。

注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI
在这里插入图片描述
I²C最重要的功能包括:

只需要两条总线;
没有严格的波特率要求,例如使用RS232,主设备生成总线时钟;
所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址;
I²C是真正的多主设备总线,可提供仲裁和冲突检测;
传输速度;
标准模式:Standard Mode = 100 Kbps
快速模式:Fast Mode = 400 Kbps
高速模式: High speed mode = 3.4 Mbps
超快速模式: Ultra fast mode = 5 Mbps
最大主设备数:无限制;
最大从机数:理论上是127。

1.2 I2C物理层

I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,SCL和SDA被上拉电阻Rp拉高,使SDA和SCL线都保持高电平。

I2C通信方式为半双工,只有一根SDA线,同一时间只可以单向通信,485也为半双工,SPI和uart通
信为全双工。
原理图:
在这里插入图片描述
主机和从机的概念:

主机就是负责整个系统的任务协调与分配,从机一般是通过接收主机的指令从而完成某些特定的任务,主机和从机之间通过总线连接,进行数据通讯。
发布主要命令的称为主机,接受命令的称为从机

1.3、I2C协议层

原理图:
在这里插入图片描述

I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。

开始信号SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。

1.4、软件IIC和硬件IIC

IIC分为软件IIC和硬件IIC

软件IIC 软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。

直接使用 CPU 内核按照 I2C 协议的要求控制 GPIO 输出高低电平,从而模拟I2C。
使用: 需要在控制产生 I2C 的起始信号时,控制作为SCL 线的 GPIO 引脚输出高电平,然后控制作为 SDA 线的 GPIO 引脚在此期间完成由高电平至低电平的切换,最后再控制SCL线切换为低电平,这样就输出了一个标准的 I2C 起始信号。

硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。

直接利用 STM32 芯片中的硬件 I2C 外设。
使用: 只要配置好对应的寄存器,外设就会产生标准串口协议的时序。在初始化好 I2C 外设后,只需要把某寄存器位置 1,此时外设就会控制对应的 SCL 及 SDA 线自动产生 I2C 起始信号,不需要内核直接控制引脚的电平。

硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。

要学习更多关于I2C的介绍,可以参考以下两篇博客

[1] IIC原理超详细讲解—值得一看
[2] I2C协议靠这16张图彻底搞懂(超详细)

1.5 IIC数据传送

数据传送格式
SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位ACK, 此时才认为一个字节真正的被传输完成 ,如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据。
在这里插入图片描述
多数从设备的地址为7位或者10位,一般都用七位。
八位设备地址=7位从机地址+读/写地址,

再给地址添加一个方向位位用来表示接下来数据传输的方向,

0表示主设备向从设备(write)写数据,

1表示主设备向从设备(read)读数据

IIC的每一帧数据由9bit组成,

如果是发送数据,则包含 8bit数据+1bit ACK,

如果是设备地址数据,则8bit包含7bit设备地址 1bit方向
在这里插入图片描述

在起始信号后必须传送一个从机的地址(7位) 1~7位为7位接收器件地址,第8位为读写位,用“0”表示主机发送数据(W),“1”表示主机接收数据 (R), 第9位为ACK应答位,紧接着的为第一个数据字节,然后是一位应答位,后面继续第2个数据字节。

1.6 IIC发送数据

在这里插入图片描述
Start: IIC开始信号,表示开始传输。
DEVICE_ADDRESS:: 从设备地址,就是7位从机地址
R/W: W(write)为写,R(read)为读
ACK: 应答信号
WORD_ADDRESS : 从机中对应的寄存器地址 比方说访问 OLED中的 某个寄存器
DATA: 发送的数据
STOP: 停止信号。结束IIC

主机要向从机写数据时:

主机首先产生START信号
然后紧跟着发送一个从机地址,这个地址共有7位,紧接着的第8位是数据方 向位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)
主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器
这时候主机等待从机的应答信号(A)
当主机收到应答信号时,发送要访问从机的那个地址, 继续等待从机的应答信号
当主机收到应答信号时,发送N个字节的数据,继续等待从机的N次应答信号,
主机产生停止信号,结束传送过程。

1.7 IIC读数据:

在这里插入图片描述
主机要从从机读数据时

主机首先产生START信号
然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令,
这时候主机等待从机的应答信号(ACK)
当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号,
当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据,
这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不在接收数据
主机进而产生停止信号,结束传送过程。

二、STM32基于I2C协议的温湿度传感器的数据采集

2.1 题目要求:

每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机。

2.2 前期准备

温湿度传感器AHT20
串口调试助手

2.3代码撰写

可以根据其他公司提供的示例代码进行修改与代码添加。(如正点原子或者野火)
相关的AHT20资料大家可以去官网进行查看与下载: http://www.aosong.com/class-36.html
AHT20芯片的使用过程read_AHT20_once函数:

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芯片读取数据 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",T1/100,(T1/10)%10,T1%10);
	printf("湿度:%d%d.%d",H1/100,(H1/10)%10,H1%10);
	printf("\r\n");
}

工程一览:
在这里插入图片描述
完整代码链接:https://pan.baidu.com/s/1JBKmXwn6v3D5zzXOPT-1OA
提取码:1902

三、准备工作

1. 温湿度采集器引脚一览表

在这里插入图片描述

2. 硬件线路连接

2.1 usb to ttl ----> STM32F103C8T6核心开发板
3V3 —> 3V3
GND —> GND
RXD —> A9
TXD —> A10
2.2 STM32F103C8T6核心开发板 ----> 温湿度采集器
3V3 —> 引脚1
GND —> 引脚3
PB6 —> SCL
PB7 —> SDA
在这里插入图片描述

四、编译成功烧录后上位机显示

编译:
在这里插入图片描述
烧录:
在这里插入图片描述
上位机展示
在这里插入图片描述

总结

通过这次实验,了解了I2C总线通信协议以及通过I2C协议实现温湿度(AHT20)采集的过程分析,
不断的在失败的过程中寻找答案,还是完成了本次实验,收获很大。
参考
https://blog.csdn.net/as480133937/article/details/105366932
https://blog.csdn.net/qq_53112972/article/details/127589824?spm=1001.2014.3001.5502
https://blog.csdn.net/qq_46467126/article/details/121436790?spm=1001.2014.3001.5502

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
绘制温湿度采集数据曲线同样可以使用Qt自带的绘图类QPainter来实现,具体步骤如下: 1. 创建一个继承自QWidget的自定义控件,用于绘制曲线。 2. 在自定义控件的构造函数中创建一个QTimer,用于定时更新曲线数据。 3. 在自定义控件的paintEvent()函数中,使用QPainter绘制曲线。 4. 在QTimer的timeout()信号中,更新采集数据并调用update()函数刷新绘图。 以下是一个简单的示例代码,仅供参考: ``` #include <QtWidgets> class ChartWidget : public QWidget { public: ChartWidget(QWidget *parent = nullptr) : QWidget(parent) { QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &ChartWidget::updateChart); timer->start(1000); // 1秒钟更新一次 // 初始化采样数据 for (int i = 0; i < 60; i++) { m_temperatureData.append(qrand() % 20 + 10); m_humidityData.append(qrand() % 40 + 40); } } protected: void paintEvent(QPaintEvent *event) override { QPainter painter(this); // 绘制温度曲线 painter.setPen(QPen(Qt::red, 2)); for (int i = 1; i < m_temperatureData.size(); i++) { QPoint p1((i - 1) * 10, height() - m_temperatureData.at(i - 1) * 5); QPoint p2(i * 10, height() - m_temperatureData.at(i) * 5); painter.drawLine(p1, p2); } // 绘制湿度曲线 painter.setPen(QPen(Qt::blue, 2)); for (int i = 1; i < m_humidityData.size(); i++) { QPoint p1((i - 1) * 10, height() - m_humidityData.at(i - 1) * 2); QPoint p2(i * 10, height() - m_humidityData.at(i) * 2); painter.drawLine(p1, p2); } } private: QList<int> m_temperatureData; // 温度采集数据 QList<int> m_humidityData; // 湿度采集数据 void updateChart() { // 更新采集数据 m_temperatureData.removeFirst(); m_temperatureData.append(qrand() % 20 + 10); m_humidityData.removeFirst(); m_humidityData.append(qrand() % 40 + 40); update(); // 刷新绘图 } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); ChartWidget chartWidget; chartWidget.show(); return app.exec(); } ``` 这个示例代码每秒钟更新一次采集数据,采集数据范围是温度10~29度,湿度40~79%,绘制曲线时,温度曲线用红色绘制,湿度曲线用蓝色绘制。你可以根据实际需求修改代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值