IIC协议读取温湿度数据

一实验目的

1 理解I2C总线通信协议

2 掌握软件I2C和硬件I2C

3 熟悉传感器数据采集与串口通信

二、材料准备
硬件:

STM32F103C8T6最小板
CH340模块
AHT20温湿度传感器
面包板
杜邦线
软件:
Keil 5
Flymcu
串口助手

二实验任务

  1. 学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:

1)解释什么是“软件I2C”和“硬件I2C”? (阅读野火配套教材的第23章“I2C–读写EEPROM”原理章节)

2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。

参考:

  1. https://blog.csdn.net/qq_43279579/article/details/111597278
    stm32通过I2C接口实现温湿度(AHT20)的采集

  2. https://blog.csdn.net/qq_46467126/article/details/121436790

I2C总线通信协议及实操stm32通过I2C实现温湿度(AHT20)采集

  1. https://blog.csdn.net/qq_47281915/article/details/121438731

STM32F103基于I2C协议的AHT20温湿度传感器的数据采集(HAL库,提供源码)

  1. 奥松AHT20传感器厂商资料

AHT20-21 DEMO V1_3(stm32).rar
三实验内容

  1. 学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:

1)解释什么是“软件I2C”和“硬件I2C”? (阅读野火配套教材的第23章“I2C–读写EEPROM”原理章节)

2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。

2C介绍
1.I2C通信
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步,半双工
带数据应答
支持总线挂载多设备(一主多从、多主多从)
在这里插入图片描述

2.硬件电路
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
在这里插入图片描述
在这里插入图片描述

3.I2C时序基本单元
起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
在这里插入图片描述

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
在这里插入图片描述

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
在这里插入图片描述

发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
在这里插入图片描述

4.I2C时序
指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
在这里插入图片描述

当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
在这里插入图片描述

指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
在这里插入图片描述

5.硬件I2C与软件I2C
硬件I2C是直接利用 STM32 芯片中的硬件 I2C 外设,在初始化好 I2C 外设后,只需要把某寄存器位置 1,此时外设就会控制对应的 SCL 及 SDA 线自动产生 I2C 起始信号,不需要内核直接控制引脚的电平。

软件I2C直接使用 CPU 内核按照 I2C 协议的要求控制 GPIO 输出高低电平,从而模拟I2C。

区别:硬件I2C速度快,而软件I2C更灵活。

四、温湿度传感器模块
1.官方教程:
链接: AHT20 产品手册
https://mooc1.chaoxing.com/ueditorupload/read?objectId=21b36eee2c2d5786f11ea582d0b75eb3&fileOriName=AHT20%25E4%25BA%25A7%25E5%2593%2581%25E6%2589%258B%25E5%2586%258Ca2.pdf
2.建立工程及代码:
部分配置代码在这里下载,找到这个东西下载。

首先我们翻它的教程,找到下面这一页,这个可以便于我们理解它给的代码。

int32_t main(void)
{
    uint32_t CT_data[2];
	volatile int  c1,t1;
	/***********************************************************************************/
	/**///①刚上电,产品芯片内部就绪需要时间,延时100~500ms,建议500ms
	/***********************************************************************************/
	Delay_1ms(500);
	/***********************************************************************************/
	/**///②上电第一次发0x71读取状态字,判断状态字是否为0x18,如果不是0x18,进行寄存器初始化
	/***********************************************************************************/
	if((AHT20_Read_Status()&0x18)!=0x18)
	{
	AHT20_Start_Init(); //重新初始化寄存器
	Delay_1ms(10);
	}
	
	/***********************************************************************************/
	/**///③根据客户自己需求发测量命令读取温湿度数据,当前while(1)循环发测量命令读取温湿度数据,仅供参考
	/***********************************************************************************/
	while(1)
	{
	 AHT20_Read_CTdata(CT_data);       //不经过CRC校验,直接读取AHT20的温度和湿度数据    推荐每隔大于1S读一次
    //AHT20_Read_CTdata_crc(CT_data);  //crc校验后,读取AHT20的温度和湿度数据 
	

	 c1 = CT_data[0]*100*10/1024/1024;  //计算得到湿度值c1(放大了10倍)
	 t1 = CT_data[1]*200*10/1024/1024-500;//计算得到温度值t1(放大了10倍)
	下一步客户处理显示数据,
	 }

 }	

但官方给的代码并不能完成我们的任务,我们还要自己添加一些东西,比如把拿到的数据通过串口传输给我们的电脑,这里给出代码:

#include "stm32f10x.h"                  // Serial.c
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint32_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)
	{
		Serial_SendByte(String[i]);
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y --)
	{
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}


uint32_t Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y--)
	{
		Result *= X;
	}
	return Result;
}


void Serial_SendFloat(float Num, uint8_t d_len, uint8_t f_len)
{
	uint8_t Len = d_len+f_len;	
	char arr[Len+2];			
	uint8_t i,j;				
	uint32_t temp;				
	
	i=0;
	if(Num>=0)
	{
		arr[i]=43;
	}else
	{
		arr[i]=45;
		Num=-Num;
	}
	i++;
	
	temp=(uint32_t)(Num*Pow(10,f_len));
	
	j=0;
	while(j<d_len)
	{
		arr[i]=temp/Pow(10,Len-j-1)%10+'0';
		j++;
		i++;
	}
	
	arr[i] = 46;
	i++;
	
	while(j<Len)
	{
		arr[i]=temp/Pow(10,Len-j-1)%10+'0';
		j++;
		i++;
	}
	Serial_SendString(arr);

}


void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}






以及它的头文件Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif





延时函数Delay.c



#include "stm32f10x.h"

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233015
  * @retval 无
  */
void Delay_us(uint32_t xus)
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_ms(uint32_t xms)
{
	while(xms--)
	{
		Delay_us(1000);
	}
}
 
/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_s(uint32_t xs)
{
	while(xs--)
	{
		Delay_ms(1000);
	}
} 

头文件Delay.h

#ifndef __DELAY_H
#define __DELAY_H

void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);

#endif

大概如下图

在这里插入图片描述

然后我们可以在主函数中添加初始化串口和SCL,SDA的函数,如果有warning,就看看是不是缺少头文件没加入,右键该函数,找到定义文件
Serial_Init();//串口
Init_I2C_Sensor_Port();//初始化SDA,SCL的IO口的函数
在这里插入图片描述

这个位置原本的程序是有错误的,我们改成下图的程序,
在这里插入图片描述

修改主函数

char str1[5];
		char str2[5];
		sprintf(str1,"%3.1f",c1/10.0);
		sprintf(str2,"%3.1f",t1/10.0);
		Delay_1ms(2000);
		Serial_SendString("湿度");
		Serial_SendString(str1);
		Serial_SendString("% ");
		Serial_SendString("温度");
		
		Serial_SendString(str2);
		Serial_SendString("℃");
		Serial_SendString("\r\n");

CRC校验:
我们可以看到,官方给的代码里有CRC校验的函数
AHT20_Read_CTdata_crc(CT_data)
我们同样右键该函数,跳转到定义,可以发现如果没有通过校验传回的数据默认是0x00,那我们要进行校验,就是要在它错误的时候,重新执行该函数,再读取一次数据即可。

最终我们的主函数代码:

#include "stm32f10x.h"
#include "AHT20-21_DEMO_V1_3.h"
#include "Serial.h"
#include "stdio.h"
#include "Delay.h"

int main(void)
{
		Serial_Init();//串口
		Init_I2C_Sensor_Port();//初始化SDA,SCL的IO口的函数
    uint32_t CT_data[2];
	  volatile int  c1,t1;
	  /***********************************************************************************/
	  /**///①刚上电,产品芯片内部就绪需要时间,延时100~500ms,建议500ms
	  /***********************************************************************************/
     Delay_1ms(500);
	  /***********************************************************************************/
	  /**///②上电第一次发0x71读取状态字,判断状态字是否为0x18,如果不是0x18,进行寄存器初始化
	  /***********************************************************************************/
	   if((AHT20_Read_Status()&0x18)!=0x18)
	  {
     	AHT20_Start_Init(); //重新初始化寄存器
			Delay_1ms(10);
  	}
	
		/***********************************************************************************/
		/**///③根据客户自己需求发测量命令读取温湿度数据,当前while(1)循环发测量命令读取温湿度数据,仅供参考
		/***********************************************************************************/
		while(1)
		{
			//AHT20_Read_CTdata(CT_data);       //不经过CRC校验,直接读取AHT20的温度和湿度数据    推荐每隔大于1S读一次
			
			
			AHT20_Read_CTdata_crc(CT_data);  //crc校验后,读取AHT20的温度和湿度数据 
		
			while(CT_data[0]==0x00&&CT_data[1]==0x00)
			{
				AHT20_Read_CTdata_crc(CT_data);
			}
			c1 = CT_data[0]*100*10/1024/1024;  //计算得到湿度值c1(放大了10倍)
			t1 = CT_data[1]*200*10/1024/1024-500;//计算得到温度值t1(放大了10倍)
			
			下一步客户处理显示数据,我们这里用两个字符串来表示计算得到的值
			char str1[5];
			char str2[5];
			sprintf(str1,"%3.1f",c1/10.0);
			sprintf(str2,"%3.1f",t1/10.0);
			Delay_1ms(2000);
			Serial_SendString("湿度");
			Serial_SendString(str1);
			Serial_SendString("% ");
			Serial_SendString("温度");
			
			Serial_SendString(str2);
			Serial_SendString("℃");
			Serial_SendString("\r\n");
		}

 }	

4.电路连接
在这里插入图片描述

在这里插入图片描述

实验效果

在这里插入图片描述

四实验总结
通过本次实验,我们学习了使用STM32F103完成基于I2C协议的AHT20温湿度传感器数据采集。加深了对I2C总线通信协议的理解。
此外,我了解“软件I2C”和“硬件I2C”的概念。以及传感器数据采集与串口输出:学生将会通过编程实现每隔2秒钟采集一次AHT20温湿度传感器的数据,并使用串口将温湿度数值发送到上位机(win10)。这将培养他们对传感器数据采集和串口通信的实际应用技能。

在本次实验中,我实现了传感器数据采集与串口通信:通过编程实现每隔2秒钟采集一次AHT20温湿度传感器的数据,并通过串口发送到上位机,我提高了自己的嵌入式系统编程和调试能力。同时,我也加深了对传感器数据处理和串口通信的理解。
我加深了在嵌入式系统中运用I2C协议进行数据采集与通信的实际经验,让我对I2C总线通信协议的原理和应用有了更深入的了解,也提高了我的实际动手能力和解决问题的能力。这些知识和经验将会对我的未来学习和工作产生积极影响。

以下是一份基于STM32的硬件IIC读取温湿度传感器的代码示例: ```c #include "main.h" #include "i2c.h" #define HTU21D_ADDRESS 0x80 // HTU21D传感器地址 // HTU21D传感器命令 #define HTU21D_TRIGGER_TEMP_HOLD 0xE3 #define HTU21D_TRIGGER_HUMI_HOLD 0xE5 #define HTU21D_READ_TEMP_HOLD 0xE0 #define HTU21D_READ_HUMI_HOLD 0xE0 uint8_t i2c_buffer[4]; // I2C接收数据缓存 /** * @brief 读取HTU21D传感器温度数据 * @return 温度值,单位:0.01℃ */ uint16_t HTU21D_Read_Temperature(void) { uint16_t temperature = 0; // 发送温度测量命令 i2c_buffer[0] = HTU21D_TRIGGER_TEMP_HOLD; HAL_I2C_Master_Transmit(&hi2c1, HTU21D_ADDRESS, i2c_buffer, 1, 1000); // 等待传感器测量完成 HAL_Delay(50); // 读取温度数据 HAL_I2C_Master_Receive(&hi2c1, HTU21D_ADDRESS, i2c_buffer, 3, 1000); // 计温度值 temperature = ((uint16_t)i2c_buffer[0] << 8) | (uint16_t)i2c_buffer[1]; temperature &= 0xFFFC; // 温度数据的最后两位为小数位,需要清零 temperature = (uint16_t)((float)temperature * 175.72 / 65536.0 - 46.85) * 100; return temperature; } /** * @brief 读取HTU21D传感器湿度数据 * @return 湿度值,单位:0.01% */ uint16_t HTU21D_Read_Humidity(void) { uint16_t humidity = 0; // 发送湿度测量命令 i2c_buffer[0] = HTU21D_TRIGGER_HUMI_HOLD; HAL_I2C_Master_Transmit(&hi2c1, HTU21D_ADDRESS, i2c_buffer, 1, 1000); // 等待传感器测量完成 HAL_Delay(50); // 读取湿度数据 HAL_I2C_Master_Receive(&hi2c1, HTU21D_ADDRESS, i2c_buffer, 3, 1000); // 计湿度值 humidity = ((uint16_t)i2c_buffer[0] << 8) | (uint16_t)i2c_buffer[1]; humidity &= 0xFFFC; // 湿度数据的最后两位为小数位,需要清零 humidity = (uint16_t)((float)humidity * 125.0 / 65536.0 - 6.0) * 100; return humidity; } ``` 需要注意的是,以上代码中的`hi2c1`变量为`I2C_HandleTypeDef`类型,需要在`main.c`文件中定义并初始化。此外,还需要在`main.c`文件中调用`MX_I2C1_Init()`函数初始化I2C接口。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值