ah20温度采集

实验目的

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

具体任务:

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

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

实验材料

软件

  • KEIL5
  • FlyMcu
  • FireTools串口助手

硬件

  • STM32F103C8T6最小开发板
  • AHT20温湿度传感器
  • CH340模块
  • 面包板一块
  • 杜邦线若干

AHT20

AHT20是奥松电子生产的,一种基于IIC协议的温湿度传感器。单片机通过给AHT20通过IIC协议发送指令,可以从AHT20读取温湿度数据到单片机。

我在本实验中使用的芯片的引脚图如下:

在这里插入图片描述

根据引脚定义进行连接,按规定编写代码给传感器发送指令,读取温湿度数据。

实验原理

什么是I2C

I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

I2C可以分成物理层与协议层。

物理层

在这里插入图片描述

I2C是一个支持设备的总线。可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。对于I2C 总线,只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。

连接到总线上的设备在输出时使用开漏输出的模式。当总线上没有设备发数据时,大家输出高阻态,总线被上拉电阻设置为高电平。当有设备发送数据时,如果发送1,设备输出高阻态,接收设备与上拉电阻相连得到1;如果输出0,由于上拉电阻把总线与电源隔开,整条总线被设置成低电平。

逻辑层

首先主机向总线发送开始信号,发送之后再发送从机地址(7位或10位)+读或写(0或1),对应的从机再给主机发送确认信号。

如果是主机发数据,主机会发送8位数据到从机,从机发送确认信号,主机再接着发下8位数据。发送完毕后,主机发送停止信号,传输结束。

如果是主机读数据,从机会发送8位数据给主机,主机收到后发送确认信号,接着收下8位数据。主机可以发送非确认信号停止接收。发送非停止信号后,主机再发送结束信号。

I2C常使用复合模式,有两次起始信号。主机找到从机后,再发送传感器里面的寄存器地址,再进行读写。

野火的资料里面有图片读写过程的示意图:

在这里插入图片描述

硬件I2C与软件I2C

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

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

本实验中,使用I2C读写寄存器的具体实现已经被官方写成函数给我们使用,我们仅需要根据示例代码,按自己需要进行调用即可。但是想要分析它的源码,就必须先了解I2C协议。

项目制作

制作一个标准库项目

制作标准库项目过程较为复杂,野火的资料库提供了项目模板。我也写好了一个模板在这个链接里面,可以下载好后根据文本文件里面的提示进行构建。

导入AHT20模块与USART配置

构建好标准库项目后,使用空main函数编译无误后,进行这一步。

由于本实验实现的需求是使用AHT20模块采集温湿度数据,并将采集到的数据发送给上位机显示。因此要使用AHT20相关的代码以及配置串口。

对于AHT20的代码,可以下载官方的文档。点击这里下载。

注意,下载到的代码有可能会有bug,下载好代码后,进入.c文件查看Init_I2C_Sensor_Port(void)函数,看SDA对应的GPIO口以及是否为开漏输出。

在这里插入图片描述

检查IIC的GPIO口配置正确后,剪切AHT20-21_DEMO_V1_3.c文件中的示例主函数到main.c中。

接着进行USART配置。具体的配置过程不再赘述,我贴出自己写好的模块化代码,可以分别复制到头文件与源文件中导入项目。

我的代码仅做了对USART1的配置:

//usart.h
#ifndef __PSD_USART_H__
#define __PSD_USART_H__
#include "stm32f10x.h"
void Usart_1_Config();//usart1初始配置
void Usart_SendByte(USART_TypeDef *pUSARTx, uint8_t ch);//发送字符
void Usart_SendString(USART_TypeDef *pUSARTx, char *ch);//发送字符串
void Usart_SendHalfWord(USART_TypeDef *pUSARTx, uint16_t ch);//发送无符号16位数据
void Usart_SendWord(USART_TypeDef *pUSARTx, uint32_t ch);//发送无符号32位数据
void Usart_SendNum_Uint8(USART_TypeDef *pUSARTx, uint8_t num);//发送Uint8类型数字
void Usart_SendNum_Uint16(USART_TypeDef *pUSARTx, uint16_t num);//发送Uint16类型数字
void Usart_SendNum_Uint32(USART_TypeDef *pUSARTx, uint32_t num);//发送Uint32类型数字
void Usart_SendNum_Int8(USART_TypeDef *pUSARTx, int8_t num);//发送int8类型数字
void Usart_SendNum_Int16(USART_TypeDef *pUSARTx, int16_t num);//发送int16类型数字
void Usart_SendNum_Int32(USART_TypeDef *pUSARTx, int32_t num);//发送int32类型数字
void Usart_SendNum_Double(USART_TypeDef *pUSARTx, double num);//发送double类型数字
#endif
//usart.c
#include "psd_usart.h"
#include "math.h"
static void NVIC_Configuration(){
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_Init(&NVIC_InitStruct);
}
void Usart_1_Config(){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//打开串口GPIO时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//打开串口时钟
	//配置RX,TX的GPIO口
	GPIO_InitTypeDef GPIO_InitStruct;
	
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; //将TX配置为浮空输入模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; //将RX配置为复用推挽输出模式
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	//配置USART1
	USART_InitTypeDef USART_InitStruct;
	
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	USART_InitStruct.USART_BaudRate = 115200;
	USART_InitStruct.USART_Parity = USART_Parity_No;
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
	USART_Init(USART1,&USART_InitStruct);
	USART_Cmd(USART1,ENABLE);
	NVIC_Configuration();
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

void Usart_SendByte(USART_TypeDef *pUSARTx, uint8_t ch){
	USART_SendData(pUSARTx,ch);
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
}

void Usart_SendString(USART_TypeDef *pUSARTx, char *ch){
	unsigned int k = 0;
	do{
		Usart_SendByte(pUSARTx,*(ch+k));
		k++;
	}while(*(ch+k)!='\0');
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
}

void Usart_SendHalfWord(USART_TypeDef *pUSARTx, uint16_t ch){
	uint8_t temp_h, temp_l;
	
	temp_h = (ch&0xFF00)>>8;
	temp_l = ch&0xff;
	Usart_SendByte(pUSARTx, temp_h);
	Usart_SendByte(pUSARTx, temp_l);
	
}

void Usart_SendWord(USART_TypeDef *pUSARTx, uint32_t ch){
	uint8_t temp_h, temp_l;
	
	temp_h = (ch&0xFFFF0000)>>16;
	temp_l = ch&0xffff;
	Usart_SendHalfWord(pUSARTx, temp_h);
	Usart_SendHalfWord(pUSARTx, temp_l);
	
}

void Usart_SendNum_Uint8(USART_TypeDef *pUSARTx, uint8_t num){
	if (num == 0){
		 Usart_SendByte(pUSARTx,'0');
		 return;
	}
	char dat[4];
	dat[3] = '\0';
	uint8_t i = 0;
	while(num>0){
		uint8_t n = num%10;
		if(n==0) dat[2-i] = '0';
		else if(n==1) dat[2-i] = '1';
		else if(n==2) dat[2-i] = '2';
		else if(n==3) dat[2-i] = '3';
		else if(n==4) dat[2-i] = '4';
		else if(n==5) dat[2-i] = '5';
		else if(n==6) dat[2-i] = '6';
		else if(n==7) dat[2-i] = '7';
		else if(n==8) dat[2-i] = '8';
		else if(n==9) dat[2-i] = '9';
		i++;
		num/=10;
	}
	uint8_t a = 3-i;
	Usart_SendString(pUSARTx,&dat[a]);
}
void Usart_SendNum_Uint16(USART_TypeDef *pUSARTx, uint16_t num){
	if (num == 0){
		 Usart_SendByte(pUSARTx,'0');
		 return;
	}
	char dat[6];
	dat[5] = '\0';
	uint8_t i = 0;
	while(num>0){
		uint8_t n = num%10;
		if(n==0) dat[4-i] = '0';
		else if(n==1) dat[4-i] = '1';
		else if(n==2) dat[4-i] = '2';
		else if(n==3) dat[4-i] = '3';
		else if(n==4) dat[4-i] = '4';
		else if(n==5) dat[4-i] = '5';
		else if(n==6) dat[4-i] = '6';
		else if(n==7) dat[4-i] = '7';
		else if(n==8) dat[4-i] = '8';
		else if(n==9) dat[4-i] = '9';
		i++;
		num/=10;
	}
	uint8_t a = 5-i;
	Usart_SendString(pUSARTx,&dat[a]);
}
void Usart_SendNum_Uint32(USART_TypeDef *pUSARTx, uint32_t num){
	if (num == 0){
		 Usart_SendByte(pUSARTx,'0');
		 return;
	}
	char dat[11];
	dat[10] = '\0';
	uint8_t i = 0;
	while(num>0){
		uint8_t n = num%10;
		if(n==0) dat[9-i] = '0';
		else if(n==1) dat[9-i] = '1';
		else if(n==2) dat[9-i] = '2';
		else if(n==3) dat[9-i] = '3';
		else if(n==4) dat[9-i] = '4';
		else if(n==5) dat[9-i] = '5';
		else if(n==6) dat[9-i] = '6';
		else if(n==7) dat[9-i] = '7';
		else if(n==8) dat[9-i] = '8';
		else if(n==9) dat[9-i] = '9';
		i++;
		num/=10;
	}
	uint8_t a = 10-i;
	Usart_SendString(pUSARTx,&dat[a]);
}
void Usart_SendNum_Double(USART_TypeDef *pUSARTx, double num){
	int32_t num1 = floor(num);
	int32_t num2 = floor((num-num1)*1000);
	Usart_SendNum_Int32(pUSARTx,num1);
	Usart_SendByte(pUSARTx,'.');
	Usart_SendNum_Int32(pUSARTx,num2);
}
void Usart_SendNum_Int8(USART_TypeDef *pUSARTx, int8_t num){
		if(num>=0) Usart_SendNum_Uint8(pUSARTx,num);
		else{
			if(num == -128){
				Usart_SendString(pUSARTx,"-128");
			}
			else{
				num = 0 - num;
				Usart_SendByte(pUSARTx,'-');
				Usart_SendNum_Uint8(pUSARTx,num);
			}
		}
}
void Usart_SendNum_Int16(USART_TypeDef *pUSARTx, int16_t num){
		if(num>=0) Usart_SendNum_Uint16(pUSARTx,num);
		else{
			if(num == -32768){
				Usart_SendString(pUSARTx,"-32768");
			}
			else{
				num = 0 - num;
				Usart_SendByte(pUSARTx,'-');
				Usart_SendNum_Uint16(pUSARTx,num);
			}
		}
}
void Usart_SendNum_Int32(USART_TypeDef *pUSARTx, int32_t num){
		if(num>=0) Usart_SendNum_Uint32(pUSARTx,num);
		else{
			if(num == -2147483648){
				Usart_SendString(pUSARTx,"-2147483648");
			}
			else{
				num = 0 - num;
				Usart_SendByte(pUSARTx,'-');
				Usart_SendNum_Uint32(pUSARTx,num);
			}
		}
}

修改main函数

官方的实例代码已经读出了放大10倍的温湿度数据,在main.c函数中找到对应的位置,在读到的数据后面使用串口通信代码,将温湿度除以10之后将数据发送给串口助手。

代码如下:

int32_t main(void)
{
	Usart_1_Config();//配置Usart1
    uint32_t CT_data[2];
	volatile int  c1,t1;
	/***********************************************************************************/
	/**///上电初始化SDA,SCL的IO口
	/***********************************************************************************/
	Init_I2C_Sensor_Port();
	/***********************************************************************************/
	/**///①刚上电,产品芯片内部就绪需要时间,延时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_crc(CT_data);
	 	if(CT_data[0]==0xff&&CT_data[1]==0xff){
			Usart_SendString(USART1,"CRC检验未通过!");
	 	}
	 	else{
			c1 = CT_data[0]*100*10/1024/1024;  //计算得到湿度值c1(放大了10倍)
	 		t1 = CT_data[1]*200*10/1024/1024-500;//计算得到温度值t1(放大了10倍)
			//下一步客户处理显示数据,
			Usart_SendString(USART1,"温度:");
			Usart_SendNum_Double(USART1,t1/10.0,1);
			Usart_SendByte(USART1,'\n');
			Usart_SendString(USART1,"湿度:");
			Usart_SendNum_Double(USART1,c1/10.0,1);
			Usart_SendByte(USART1,'\n');
			Usart_SendString(USART1,"数据通过检验!\n");
			Usart_SendByte(USART1,'\n');
	 	}
	 	Delay_1ms(2000);
	}
 }

实验现象

查看SDA,SCL对应的GPIO口,将AHT20芯片的VCC,SDA,GND,SCL四个引脚与芯片对应的引脚在面包板上连接好,连接好硬件后烧录程序到芯片中,打开串口助手,发现接收到温度与湿度数据。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

根据上图的函数查看自己下的代码使用的哪个GPIO口。

总结

本实验实现了使用AHT20传感器收集温湿度数据,并发送到串口助手。涉及到I2C与Usart配置。为了完成这个实验,我将Usart模块化,制作了输出中文,浮点数,数字的函数,巩固了之前所学习的知识。此外还通过AHT20的官方例子学习了I2C的应用。

参考资料

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值