STM32 I2C协议读取温湿度传感器

文章目录

  • 一、任务目标
  • 二、材料准备
  • 三、I2C介绍
    • 1.I2C通信
    • 2.硬件电路
    • 3.I2C时序基本单元
    • 4.I2C时序
    • 5.硬件I2C与软件I2C
  • 四、温湿度传感器模块
    • 1.官方教程:
    • 2.建立工程及代码:
    • 3.修改主函数
      • 添加显示数据的代码:
      • CRC校验:
    • 4.电路连接:
    • 5.实验效果:
  • 五、总结
  • 六、参考资料

一、任务目标

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

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

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

二、材料准备

硬件:

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

软件:

  • Keil 5
  • Flymcu
  • 串口助手

三、I2C介绍

1.I2C通信

  • I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
  • 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
  • 同步,半双工
  • 带数据应答
  • 支持总线挂载多设备(一主多从、多主多从)
    STM32 I2C协议读取温湿度传感器_第1张图片

2.硬件电路

  • 所有I2C设备的SCL连在一起,SDA连在一起
  • 设备的SCL和SDA均要配置成开漏输出模式
  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
    STM32 I2C协议读取温湿度传感器_第2张图片
    STM32 I2C协议读取温湿度传感器_第3张图片

3.I2C时序基本单元

起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
STM32 I2C协议读取温湿度传感器_第4张图片
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
STM32 I2C协议读取温湿度传感器_第5张图片
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
STM32 I2C协议读取温湿度传感器_第6张图片
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
STM32 I2C协议读取温湿度传感器_第7张图片

4.I2C时序

指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
STM32 I2C协议读取温湿度传感器_第8张图片
当前地址读
对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
STM32 I2C协议读取温湿度传感器_第9张图片
指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
STM32 I2C协议读取温湿度传感器_第10张图片

5.硬件I2C与软件I2C

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

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

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

四、温湿度传感器模块

1.官方教程:

链接: AHT20 产品手册

2.建立工程及代码:

部分配置代码在这里下载,找到这个东西下载。
在这里插入图片描述

首先我们翻它的教程,找到下面这一页,这个可以便于我们理解它给的代码。
STM32 I2C协议读取温湿度传感器_第11张图片
我们打开AHT20-21_DEMO_V1_3.c这个c程序,翻到最下面可以看到官方给的主函数的代码,对照着上面的图片看,我们就能知道每个函数大概是用来干什么的了。

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 
#include 

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 

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

大概如下图
STM32 I2C协议读取温湿度传感器_第12张图片

然后我们可以在主函数中添加初始化串口和SCL,SDA的函数,如果有warning,就看看是不是缺少头文件没加入,右键该函数,找到定义文件

		Serial_Init();//串口
		Init_I2C_Sensor_Port();//初始化SDA,SCL的IO口的函数

STM32 I2C协议读取温湿度传感器_第13张图片
这个位置原本的程序是有错误的,我们改成下图的程序,
STM32 I2C协议读取温湿度传感器_第14张图片
同时以下代码也需要更改,因为我们这里要用引脚B0和B1,这里我没有更改注释,方便找:
STM32 I2C协议读取温湿度传感器_第15张图片
STM32 I2C协议读取温湿度传感器_第16张图片
到这里引入的函数就完成了。

3.修改主函数

添加显示数据的代码:

(注意要把官方放大10倍的数缩小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");

CRC校验:

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

STM32 I2C协议读取温湿度传感器_第17张图片

			AHT20_Read_CTdata_crc(CT_data);  //crc校验后,读取AHT20的温度和湿度数据 
		
			while(CT_data[0]==0x00&&CT_data[1]==0x00)
			{
				AHT20_Read_CTdata_crc(CT_data);
			}

最终我们的主函数代码:

#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.电路连接:

STM32 I2C协议读取温湿度传感器_第18张图片
我这里使用的是ST-Link,接线稍微麻烦点,B1接管脚2,B0接管脚4,下图可以无视OLED接线
STM32 I2C协议读取温湿度传感器_第19张图片
STM32 I2C协议读取温湿度传感器_第20张图片
图片来源:江科大自化协

5.实验效果:

可能会有显示中文乱码的问题,可以用记事本打开main.c文件,然后另存为编码为ANSI的文件,再编译运行即可。这里我们可以看到,当我把手靠近传感器时(我的手上有点汗),湿度明显变高了,温度也有改变。

五、总结

本次实验我完成了用AHT20传感器收集温度、湿度的数据并发送到电脑的实验。主要了解了一些I2C的知识,结合代码和手册让我对I2C通信的印象更深刻了。中途有的时候一直都读取不出来信息,以为是传感器坏了,不了解协议的话调试都无从下手,后来逐渐理解了一些代码,才读取到了信息,完成了实验。

六、参考资料

《AHT20产品手册》

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

你可能感兴趣的:(stm32)