基于HAL库的IIC协议读取温湿度传感器


前言

今天主要讲解IIC协议是什么,以及如何通过IIC对AHT20传感器进行通信和信息获取。


一、IIC是什么

以下内容参考正点原子资料

IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。
CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。IIC 总线时序图如下图所示:

请添加图片描述

1.硬件IIC介绍

STM32的硬件IIC过于复杂,在此我们只做简单了解不做深究。

(1)功能概述

I2C模块接收和发送数据,并将数据从串行转换成并行,或并行转换成串行。可以开启或禁止中断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到I2C总线。允许连接到标准(高达100kHz)或快速(高达400kHz)的I
2C总线。

(2)模式选择

接口可以下述4种模式中的一种运行:
● 从发送器模式
● 从接收器模式
● 主发送器模式
● 主接收器模式
该模块默认地工作于从模式。接口在生成起始条件后自动地从从模式切换到主模式;当仲裁丢
失或产生停止信号时,则从主模式切换到从模式。允许多主机功能.
主模式时,I2C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止条件结束。起始条件和停止条件都是在主模式下由软件控制产生。
从模式时,I2C接口能识别它自己的地址(7位或10位)和广播呼叫地址。软件能够控制开启或禁止广播呼叫地址的识别。
数据和地址按8位/字节进行传输,高位在前。跟在起始条件后的1或2个字节是地址(7位模式为1个字节,10位模式为2个字节)。地址只在主模式发送。

(3)IIC主模式

在主模式时,I2C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止条件结束。当通过START位在总线上产生了起始条件,设备就进入了主模式。
以下是主模式所要求的操作顺序:
● 在I2C_CR2寄存器中设定该模块的输入时钟以产生正确的时序
● 配置时钟控制寄存器
● 配置上升时间寄存器
● 编程I2C_CR1寄存器启动外设
● 置I2C_CR1寄存器中的START位为1,产生起始条件
I2C模块的输入时钟频率必须至少是:
● 标准模式下为:2MHz
● 快速模式下为:4MHz
起始条件
当BUSY=0时,设置START=1,I2C接口将产生一个开始条件并切换至主模式(M/SL位置位)。注: 在主模式下,设置START位将在当前字节传输完后由硬件产生一个重开始条件。
一旦发出开始条件:
● SB位被硬件置位,如果设置了ITEVFEN位,则会产生一个中断。
然后主设备等待读SR1寄存器,紧跟着将从地址写入DR寄存器。
从地址的发送
从地址通过内部移位寄存器被送到SDA线上。
● 在10位地址模式时,发送一个头段序列产生以下事件:
─ ADD10位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。
然后主设备等待读SR1寄存器,再将第二个地址字节写入DR寄存器。
─ ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。
随后主设备等待一次读SR1寄存器,跟着读SR2寄存器。
● 在7位地址模式时,只需送出一个地址字节。
一旦该地址字节被送出,
─ ADDR位被硬件置位,如果设置了ITEVFEN位,则产生一个中断。
随后主设备等待一次读SR1寄存器,跟着读SR2寄存器。
根据送出从地址的最低位,主设备决定进入发送器模式还是进入接收器模式。
● 在7位地址模式时,
─ 要进入发送器模式,主设备发送从地址时置最低位为’0’。
─ 要进入接收器模式,主设备发送从地址时置最低位为’1’。
● 在10位地址模式时
─ 要进入发送器模式,主设备先送头字节(11110xx0),然后送最低位为’0’的从地址。(这里xx代表10位地址中的最高2位。)
─ 要进入接收器模式,主设备先送头字节(11110xx0),然后送最低为’1’的从地址。然后再重新发送一个开始条件,后面跟着头字节(11110xx1)(这里xx代表10位地址中的最高2位。) TRA位指示主设备是在接收器模式还是发送器模式。

(一)主发送器

主发送器 在发送了地址和清除了ADDR位后, 主设备通过内部移位寄存器将字节从DR寄存器发送到SDA线上。 主设备等待,直到TxE被清除。
当收到应答脉冲时: ● TxE位被硬件置位,如果设置了INEVFEN和ITBUFEN位,则产生一个中断。
如果TxE被置位并且在上一次数据发送结束之前没有写新的数据字节到DR寄存器,则BTF被硬件置位,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再写入I2C_DR寄存器将清除BTF位。
关闭通信 在DR寄存器中写入最后一个字节后,通过设置STOP位产生一个停止条件,然后I2C接口将自动回到从模式(M/S位清除)。 注:
当TxE或BTF位置位时,停止条件应安排在出现EV8_2事件时。

如下图为主发送传输序列图:
请添加图片描述

(二)主接受器

主接收器 在发送地址和清除ADDR之后,I2C接口进入主接收器模式。在此模式下,I2C接口从SDA线接收
数据字节,并通过内部移位寄存器送至DR寄存器。在每个字节后,I2 C接口依次执行以下操作:
● 如果ACK位被置位,发出一个应答脉冲。
●硬件设置RxNE=1,如果设置了INEVFEN和ITBUFEN位,则会产生一个中断。
如果RxNE位被置位,并且在接收新数据结束前,DR寄存器中的数据没有被读走,硬件将设置BTF=1,在清除BTF之前I2C接口将保持SCL为低电平;读出I2C_SR1之后再读出I2C_DR寄存器将清除BTF位。
关闭通信
主设备在从从设备接收到最后一个字节后发送一个NACK。接收到NACK后,从设备释放对SCL和SDA线的控制;主设备就可以发送一个停止/重起始条件。
● 为了在收到最后一个字节后产生一个NACK脉冲,在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)必须清除ACK位。
●为了产生一个停止/重起始条件,软件必须在读倒数第二个数据字节之后(在倒数第二个RxNE事件之后)设置STOP/START位。
●只接收一个字节时,刚好在EV6之后(EV6_1时,清除ADDR之后)要关闭应答和停止条件的产生位。在产生了停止条件后,I2C接口自动回到从模式(M/SL位被清除)

如下图为主接收机的传输序列图:
请添加图片描述

2.软件IIC介绍

在这里我们将通过代码的形式介绍软件IIC,下面代码主要的意思是通过初始化引脚PC11和PC12来模拟IIC的两个引脚,在通过模拟IIC的时序来模拟IIC。
该部分为 IIC 驱动代码,实现包括 IIC 的初始化(IO 口)、IIC 开始、IIC 结束、ACK、IIC读写等功能,在其他函数里面,只需要调用相关的 IIC 函数就可以和外部 IIC 器件通信了

#include "myiic.h"
#include "delay.h" 
//IIC 初始化
void IIC_Init(void)
{
 GPIO_InitTypeDef GPIO_Initure;
 __HAL_RCC_GPIOC_CLK_ENABLE(); //使能 GPIOC 时钟
 //PC11,12 初始化设置
 GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12;
 GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
 GPIO_Initure.Pull=GPIO_PULLUP; //上拉
 GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
 HAL_GPIO_Init(GPIOC,&GPIO_Initure);
 
 IIC_SDA=1;
 IIC_SCL=1; 
}
//产生 IIC 起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda 线输出
IIC_SDA=1; 
IIC_SCL=1; delay_us(4);
IIC_SDA=0; delay_us(4);//IIC START: when CLK is high,DATA change form high to low
IIC_SCL=0;//钳住 I2C 总线,准备发送或接收数据
} 
//产生 IIC 停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda 线输出
IIC_SCL=0; IIC_SDA=0;
delay_us(4); 
IIC_SCL=1;//STOP:when CLK is high DATA change form low to high
delay_us(4); 
IIC_SDA=1;//发送 I2C 总线结束信号
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA 设置为输入 
IIC_SDA=1;delay_us(1); 
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250) { IIC_Stop();return 1; }
}
IIC_SCL=0;//时钟输出 0 
return 0; 
} 
//产生 ACK 应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0; delay_us(2);
IIC_SCL=1; delay_us(2);
IIC_SCL=0;
}
//不产生 ACK 应答 
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1; delay_us(2);
IIC_SCL=1; delay_us(2);
IIC_SCL=0;
} 
//IIC 发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答 
void IIC_Send_Byte(u8 txd)
{ 
 u8 t; 
SDA_OUT(); 
 IIC_SCL=0;//拉低时钟开始数据传输
 for(t=0;t<8;t++)
 { 
 IIC_SDA=(txd&0x80)>>7;
 txd<<=1; delay_us(2); 
IIC_SCL=1; delay_us(2); 
IIC_SCL=0; delay_us(2);
 }
} 
//读 1 个字节,ack=1 时,发送 ACK,ack=0,发送 nACK 
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA 设置为输入
 for(i=0;i<8;i++ )
{
 IIC_SCL=0; delay_us(2); 
 IIC_SCL=1;
 receive<<=1;
 if(READ_SDA)receive++; 
delay_us(1); 
 }
 if (!ack) IIC_NAck();//发送 nACK 
 else IIC_Ack(); //发送 ACK 
 return receive;
}

二、AHT20温湿度传感器介绍

1.上电后要等待40ms,读取温湿度值之前, 首先要看状态字的校准使能位Bit[3]是否为 1(通过发送0x71可以获取一个字节的状态字),如果不为1,要发送0xBE命令(初始化),此命令参数有两个字节, 第一个字节为0x08,第二个字节为0x00,然后等待10ms。
2.直接发送 0xAC命令(触发测量),此命令参数有两个字节,第一个字节为 0x33,第二个字节为0x00。
3.等待80ms待测量完成,如果读取状态字Bit[7]为0,表示测量完成,然后可以连续读取六个节;否则继续等待。
4.当接收完六个字节后,紧接着下一个字节是CRC校验数据,用户可以根据需要读出,如果接收端需要CRC校验,则在接收完第六个字节后ACK应答,否则发NACK结束,CRC初始值为0XFF,CRC8校验多项式为:
5.计算温湿度值。
请添加图片描述

三、通过IIC获取温湿度传感器数据

1.IIC配置

IIC初始化代码如下:

#include "i2c.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

I2C_HandleTypeDef hi2c1;

/* I2C1 init function */
void MX_I2C1_Init(void)
{

  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */

  /* USER CODE END I2C1_MspInit 0 */
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* I2C1 clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
  /* USER CODE BEGIN I2C1_MspInit 1 */

  /* USER CODE END I2C1_MspInit 1 */
  }
}

void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle)
{

  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspDeInit 0 */

  /* USER CODE END I2C1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_I2C1_CLK_DISABLE();
  
    /**I2C1 GPIO Configuration    
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA 
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6|GPIO_PIN_7);

  /* USER CODE BEGIN I2C1_MspDeInit 1 */

  /* USER CODE END I2C1_MspDeInit 1 */
  }
} 

2.AHT20初始化

代码如下:

void AHT20_Init(void)
{
	uint8_t receive=0;
	uint8_t trans[3]={0xBE,0x08,0x00};
	uint8_t trans_1[3]={0xAC,0x33,0x00};
	uint32_t out_time=0;
	uint8_t receive_data[6]={0};
	uint8_t tan=0x71;
	HAL_Delay(40);
	HAL_I2C_Master_Transmit(&hi2c1,0x70,NULL,1,0xffff);
	HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
	printf("%x",receive);
	if(!receive&0x08)
	{
		printf("1");
		HAL_I2C_Master_Transmit(&hi2c1,0x70,trans,1,0xffff);
		HAL_Delay(10);
	}
	HAL_I2C_Master_Transmit(&hi2c1,0x70,trans_1,1,0xffff);
	HAL_Delay(80);
	receive=0xff;
	HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
	printf("%x",receive);
	while(receive&0x80&&out_time<0xffff)
	{
		out_time++;
		HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
	}
	if(out_time==0xffff)
	{
		printf("Init error");
		return;
	}
	HAL_I2C_Master_Receive(&hi2c1,0x70,receive_data,6,0xffff);
}

3.AHT20数据获取及解析

代码如下:

void AHT20_GET(void)
{
	uint8_t receive=0xff;
	float RH,T;
	uint32_t ll;
	uint8_t tan[]={0xAC,0x33,0x00,0x71};
	uint32_t out_time=0;
	HAL_I2C_Master_Transmit(&hi2c1,0x70,tan,3,0xffff);
	HAL_Delay(80);
	HAL_I2C_Master_Transmit(&hi2c1,0x70,&tan[3],1,0xffff);
	HAL_I2C_Master_Receive(&hi2c1,0x70,receive_,7,0xffff);
	while(receive_[0]&0x80&&out_time<0xffff)
	{
		out_time++;
		HAL_I2C_Master_Receive(&hi2c1,0x70,&receive,1,0xffff);
	}
	if(out_time==0xffff)
	{
		printf("Get error");
		return;
	}
	RH=(((uint32_t)receive_[3]>>4)+((uint32_t)receive_[2]<<4)+((uint32_t)receive_[1]<<12));
	RH=RH/(1<<20)*100;
	T=(((uint32_t)receive_[3]&0x0f<<16)+((uint32_t)receive_[4]<<8)+((uint32_t)receive_[5]));
	T=T/(1<<20)*200.0f-50;
	printf("水汽度:%f,温度:%f",RH,T);
}

最终结果将通过串口回传。

四、结果演示

请添加图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一份基于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接口。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值