【STM32】RTT Studio中HAL库开发进阶教程:IIC通信与AHT20详解
文章目录
一、I2C总线通信协议
使用奥松的AHT20温湿度传感器,对环境温湿度进行采集。AHT20采用的是IIC进行通信,可以使用硬件IIC或者使用模拟IIC进行通信,本例程介绍采用STM32F407芯片自带的硬件IIC进行通讯,具体操作过程如下。
1.I2C介绍
IIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。
在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps 以上。

2.I2C物理层

术语:
主机:启动数据传送并产生时钟信号的设备;
从机:被主机寻址的器件;
多主机:同时有多于一个主机尝试控制总线但不破坏传输;
主模式:用 I2CNDAT 支持自动字节计数的模式; 位 I2CRM,I2CSTT,I2CSTP 控制数据的接收和发送;
从模式:发送和接收操作都是由 I2C 模块自动控制的;
仲裁:是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并 使传输不被破坏的过程;
同步:两个或多个器件同步时钟信号的过程;
发送器:发送数据到总线的器件;
接收器:从总线接收数据的器件
3.I2C协议层
1.起始信号和停止信号
SCL 线为高电平期间,SDA 线由高电平向低电平的变化表示起始信号;SCL 线为高电平期间,SDA 线由低电平向高电平的变化表示终止信号。如下图:
起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用 的状态;在终止信号产生后,总线就处于空闲状态。
2.数据有效性规定
I2C 总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保 持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态 才允许变化。每次数据传输都以字节为单位,每次传输的字节数不受限制。如下图
3.应答和非应答
每当发送器件传输完一个字节的数据后,后面必须紧跟一个校验位,这个校 验位是接收端通过控制 SDA(数据线)来实现的,以提醒发送端数据我这边已经 接收完成,数据传送可以继续进行。这个校验位其实就是数据或地址传输过程中 的响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。
应答信号(ACK)通常是从机向主机发送的信号。
非应答信号(NACK)是由主机发送的。
等待应答信号: 一旦主机发送完地址和读写位,它会释放SDA数据线,即将SDA置为高阻态,并等待从机的应答信号。
4.总线时序图
二、AHT20传感器介绍
英文数据手册:AHT20英文手册
中文数据手册:AHT20中文手册
传感器介绍:
1.发送命令
在启动传输后,随后传输的I2C首字节包括7位的IIC设备地址0x38
和一个SDA方向位x
(读R:‘1’,写W:‘0’)。在第8个SCL时钟下降沿之后,通过拉低 SDA引脚 (ACK位),指示传感器数据接收正常。 在发出初始化命令之后 (‘1011’1110’)代表初始化,‘1010’1100’代表温湿度测量), MCU必须等到测量完成。
地址:
该地址在使用的时候,如果是读设备,地址为0x71
,如果是写设备,则地址为0x70
。所以在设备进行设备通讯的时候设备地址采用0x70
。
状态位说明:
在使用的时候需要查询设备状态,常查询的状态为:校准使能位、忙闲指示位
。
2.读取流程
AHT20传感器的通信过程主要包含以下四个步骤:


在采用HAL库配置的硬件IIC,则直接使用如下两个相关函数即可,无需关注此应答信号。硬件IIC的通信速率比软件IIC更加稳定,速度更快,使用更加方便。
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
3.数据转换
计算相对湿度公式:
计算温度公式:
三、STM32CubeMX配置硬件IIC
1.配置硬件IIC:通过按照下图的配置方式,对IIC进行初始化配置
2.生成代码:按照下图生成IIC的初始化代码。
四、RTT中初始化配置
1.生成初始化代码
通过使用STM32CubeMX生成的初始化代码:
/**
* @brief i2c初始化
*/
static void MX_I2C3_Init(void)
{
hi2c3.Instance = I2C3;
hi2c3.Init.ClockSpeed = 100000;
hi2c3.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c3.Init.OwnAddress1 = 0;
hi2c3.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c3.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c3.Init.OwnAddress2 = 0;
hi2c3.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c3.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c3) != HAL_OK)
{
Error_Handler();
}
}
2.在board.c中添加初始化代码
在board.c中需要添加HAL_开头的函数,便于系统调用该函数,对IIC进行初始化。
/**
* @brief i2c初始化
* @param i2cHandle
*/
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (i2cHandle->Instance == I2C3)
{
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**I2C3 GPIO Configuration
PC9 ------> I2C3_SDA
PA8 ------> I2C3_SCL
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* I2C3 clock enable */
__HAL_RCC_I2C3_CLK_ENABLE();
}
}
3.在board.h中添加宏定义
打开RTT软件中的IIC的宏定义,便于使用IIC的底层驱动。
#define BSP_USING_I2C3
#ifdef BSP_USING_I2C3
#define BSP_I2C3_SCL_PIN GET_PIN(A, 8)
#define BSP_I2C3_SDA_PIN GET_PIN(C, 9)
#endif
五、具体实现代码
1.AHT20.h代码如下:
#ifndef APPLICATIONS_HARDWARE_INC_AHT20_H_
#define APPLICATIONS_HARDWARE_INC_AHT20_H_
#include <rtthread.h>
#include <rtdevice.h>
#include <drv_common.h>
#include "sys_string.h"
/**====================================================###### 宏定义 ######==================================================*/
#define RT_AHT20_THREAD_STACK_SIZE (1024)
#define RT_AHT20_THREAD_PRIORITY (22)
#define RT_AHT20_THREAD_TICK (20)
#define AHT20_SLAVE_ADDRESS 0x70 // I2C从机地址
// 定义 AHT20 命令
#define AHT20_INIT_COMD 0xBE // 初始化寄存器地址
#define AHT20_SOFTRESET 0xBA // 软复位单指令
#define AHT20_TRIGMERSURE_COMD 0xAC // 触发测量 寄存器地址
/**====================================================####### END #######=================================================*/
/**====================================================### 全局变量定义 ####=================================================*/
// aht20温湿度结构体
typedef struct m_AHT20
{
uint8_t alive; // 0-器件不存在; 1-器件存在
uint8_t flag; // 读取/计算错误标志位。0-读取/计算数据正常; 1-读取/计算设备失败
uint32_t HT[2]; // 湿度、温度 原始传感器的值(20Bit).
float RH; // 湿度,转换单位后的实际值,标准单位%
float Temp; // 温度,转换单位后的实际值,标准单位°C
} AHT20_StructureTypedef;
AHT20_StructureTypedef g_aht20;// aht20结构体信息
/**====================================================####### END #######=================================================*/
#endif /* APPLICATIONS_HARDWARE_INC_AHT20_H_ */
2.AHT20.c代码如下:
#include "aht20.h"
#include "i2c.h"
#include "control.h"
#include "power.h"
/*=====================================================### 静态函数调用 ###==================================================*/
#ifdef AHT20_HARDWARE_IIC
/**
* @brief i2c初始化
*/
static void MX_I2C3_Init(void)
{
hi2c3.Instance = I2C3;
hi2c3.Init.ClockSpeed = 100000;
hi2c3.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c3.Init.OwnAddress1 = 0;
hi2c3.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c3.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c3.Init.OwnAddress2 = 0;
hi2c3.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c3.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c3) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief 读AHT20 设备状态字
* @return uint8_t 设备状态字
*/
static uint8_t AHT20_ReadStatusCmd(void)
{
uint8_t tmp = 0;
HAL_I2C_Master_Receive(&hi2c3, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
return tmp;
}
/**
* @brief 读AHT20设备状态字中的Bit3: 校准使能位
* @return 1 - 已校准; 0 - 未校准
*/
static uint8_t AHT20_ReadCalEnableCmd(void)
{
uint8_t tmp = 0;
tmp = AHT20_ReadStatusCmd();
return (tmp >> 3) & 0x01;
}
/**
* @brief AHT20 芯片初始化命令
*/
static void AHT20_IcInitCmd(void)
{
uint8_t tmp = AHT20_INIT_COMD;
HAL_I2C_Master_Transmit(&hi2c3, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
}
/**
* @brief AHT20 软复位命令
*/
static void AHT20_SoftResetCmd(void)
{
uint8_t tmp = AHT20_SOFTRESET;
HAL_I2C_Master_Transmit(&hi2c3, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
}
/**
* @brief AHT20 设备初始化
* @return 0 - 初始化AHT20设备成功; 1 - 初始化AHT20失败,可能设备不存在或器件已损坏
*/
static uint8_t AHT20_Init(void)
{
uint8_t rcnt = 2; // 软复位命令 重试次数,2次
uint8_t icnt = 2; // 初始化命令 重试次数,2次
while (rcnt--)
{
// 上电后要等待40ms
HAL_Delay(40);
// 读取温湿度之前,首先检查[校准使能位]是否为1,2次重试机会
while ((!AHT20_ReadCalEnableCmd()) && (icnt--))
{
HAL_Delay(1);
AHT20_IcInitCmd(); // 如果不为1,要发送初始化命令
HAL_Delay(40); // 按上电时间算40ms
}
// [校准使能位]为1,校准正常
if (icnt)
{
break;
}
else
{
AHT20_SoftResetCmd(); // 软复位AHT20器件,重试
HAL_Delay(20); // 手册标明不超过20ms.
}
}
if (rcnt)
{
return 0; // AHT20设备初始化正常
}
else
{
return 1; // AHT20设备初始化失败
}
}
/**
* @brief AHT20 触发测量命令
*/
static void AHT20_TrigMeasureCmd(void)
{
uint8_t tmp[3] = {AHT20_TRIGMERSURE_COMD, 0x33, 0x00};
HAL_I2C_Master_Transmit(&hi2c3, AHT20_SLAVE_ADDRESS, tmp, 3, 0xFFFF);
}
/**
* @brief 读AHT20 设备状态字 中的Bit7: 忙标志
* @return 忙标志:1 - 设备忙; 0 - 设备空闲
*/
static uint8_t AHT20_ReadBusyCmd(void)
{
uint8_t tmp = 0;
tmp = AHT20_ReadStatusCmd();
return (tmp>>7)&0x01;
}
/**
* @brief AHT20 设备读取 相对湿度和温度(原始数据20Bit)
* @param HT:存储20Bit原始数据的uint32数组
* @return 0-读取数据正常; 1-读取设备失败,设备一直处于忙状态,不能获取数据
*/
uint8_t AHT20_ReadHT(uint32_t *HT)
{
uint8_t cnt = 4; // 忙标志 重试次数,3次
uint8_t tmp[6];
uint32_t RetuData = 0;
// 发送触发测量命令
AHT20_TrigMeasureCmd();
do
{
HAL_Delay(75); // 等待75ms待测量完成,忙标志Bit7为0
}
while (AHT20_ReadBusyCmd() && (--cnt)); // 重试3次
// 设备闲,可以读温湿度数据
if (cnt)
{
HAL_Delay(5);
// 读温湿度数据
HAL_I2C_Master_Receive(&hi2c3, AHT20_SLAVE_ADDRESS, tmp, 6, 0XFFFF);
// 计算相对湿度RH。原始值,未计算为标准单位%。
RetuData = 0;
RetuData = (RetuData | tmp[1]) << 8;
RetuData = (RetuData | tmp[2]) << 8;
RetuData = (RetuData | tmp[3]);
RetuData = RetuData >> 4;
HT[0] = RetuData;
// 计算温度T。原始值,未计算为标准单位°C。
RetuData = 0;
RetuData = (RetuData | tmp[3]) << 8;
RetuData = (RetuData | tmp[4]) << 8;
RetuData = (RetuData | tmp[5]);
RetuData = RetuData & 0xfffff;
HT[1] = RetuData;
return 0;
}
else //设备忙,返回读取失败
{
return 1;
}
}
/**
* @brief AHT20 温湿度信号转换(由20Bit原始数据,转换为标准单位RH=%,T=°C)
* @param aht:存储AHT20传感器信息的结构体
* @return 0-计算数据正常; 1-计算数据失败,计算值超出元件手册规格范围
*/
uint8_t StandardUnitCon(AHT20_StructureTypedef *aht)
{
// 计算温湿度
aht->RH = (double) aht->HT[0] * 100 / (1 << 20);
aht->Temp = (double) aht->HT[1] * 200 / (1 << 20) - 50;
// 限幅,RH=0~100%; Temp=-40~85°C
if ((aht->RH >= 0) && (aht->RH <= 10000) && (aht->Temp >= -4000) && (aht->Temp <= 8500))
{
aht->flag = 0;
return 0; // 测量数据正常
}
else
{
aht->flag = 1;
return 1; // 测量数据超出范围,错误
}
}
/**
* @brief AHT20 温湿度信号转换(由20Bit原始数据,转换为标准单位RH=%,T=°C)
* @param p:存储AHT20传感器信息的结构体
* @return 0-计算数据正常; 1-计算数据失败,计算值超出元件手册规格范围
*/
uint8_t AHT20_Get_Value(AHT20_StructureTypedef *p)
{
uint8_t temp = 0;
temp = AHT20_ReadHT(p->HT);
if (temp == 0)
{
temp = StandardUnitCon(p);
}
return temp;
}
#endif
/*=====================================================####### END #######=================================================*/
3.线程代码如下:
/**
* @brief 温湿度检测入口函数
* @param param
*/
void aht20_temp_humidity_entry(void *param)
{
#ifdef AHT20_HARDWARE_IIC
AHT20_StructureTypedef *pAht20 = (AHT20_StructureTypedef *)param;
POWER_ENABLE_HIGH(); // 温湿度电源使能
MX_I2C3_Init(); // i2c初始化
if(AHT20_Init() != 0) // AHT20设备初始化
{
pAht20->alive = 0;
}
else
{
pAht20->alive = 1;
}
#endif
while (1)
{
#ifdef AHT20_HARDWARE_IIC
AHT20_Get_Value(pAht20); // 温湿度查询
rt_kprintf("T: %d.%d C H: %d.%d %%\n", (int)pAht20->Temp, (int)(pAht20->Temp * 100) % 100,
(int)pAht20->RH, (int)(pAht20->RH * 100) % 100);
#endif
HAL_Delay(300);
}
}
/**
* @brief 温湿度线程初始化
* @return
*/
static int aht20_temp_humidity(void)
{
rt_thread_t ret;
ret = rt_thread_create("aht20", aht20_temp_humidity_entry, (void *)&g_aht20,
RT_AHT20_THREAD_STACK_SIZE,
RT_AHT20_THREAD_PRIORITY,
RT_AHT20_THREAD_TICK);
RT_ASSERT(ret != RT_NULL);
rt_thread_startup(ret);
return 0;
}
INIT_ENV_EXPORT(aht20_temp_humidity);
/**
* @brief AHT20工作状态
* @param argc
* @param argv
*/
void AHT20_Work_State(int argc, char **argv)
{
if (argc != 1)
{
rt_kprintf("[%s:%d] param error!\n", __FUNCTION__, __LINE__);
return;
}
else
{
if (strcmp_nocase(argv[0], "aht20") == 0)
{
// 温湿度
rt_kprintf("aht20_temp: %d.%d C\n", (int)g_aht20.Temp, (int)(g_aht20.Temp * 100) % 100);
rt_kprintf("aht20_humidity: %d.%d %%\n", (int)g_aht20.RH, (int)(g_aht20.RH * 100) % 100);
}
}
}
MSH_CMD_EXPORT_ALIAS(AHT20_Work_State, aht20, AHT20 Work State);
六、实验现象
通过使用aht20
指令进行查询温湿度数据,指令主要是通过控制台进行数据指令的读写,这个是RTT中自带的控制台,很方便进行数据的读取。具体实验数据如下:
作者:0南城逆流0