使用STM32 HAL库函数实现硬件IIC通信
/*出处:【STM32入门教程-2024】第12集 IIC通信与温湿度传感器AHT20(DHT20)_哔哩哔哩_bilibili
*/
AHT20驱动
这篇笔记我主要介绍代码实现,想要了解原理的请自己看视频,我不过多赘述了。
AHT20通信数据帧格式:
①对照手册上的通信流程写初始化函数
关键API介绍:
主机接收函数
HAL_StatusTypeDef HAL_I2C_Master_Receive
(I2C_HandleTypeDef *hi2c, uint16_t DevAddress,
uint8_t *pData, uint16_t Size, uint32_t Timeout)
参数名称 | 介绍 |
I2C_HandleTypeDef *hi2c | 想要操作的I2C函数句柄,eg:&hi2c1 |
uint16_t DevAddress | 设备地址 |
uint8_t *pData | 接收数据的变量的指针 |
uint16_t Size | 读取的目标位数,单位字节 |
uint32_t Timeout | 超时时间 |
主机发送函数
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c,
uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
参数名称 | 介绍 |
I2C_HandleTypeDef *hi2c | 想要操作的I2C函数句柄,eg:&hi2c1 |
uint16_t DevAddress | 设备地址 |
uint8_t *pData | 接收数据的变量的指针 |
uint16_t Size | 读取的目标位数,单位字节 |
uint32_t Timeout | 超时时间 |
初始化函数:
void AHT20_Init(void)
{
uint8_t readBuffer;
HAL_Delay(40);
/*读是写加一,这里地址实际上自动变成了0X71*/
HAL_I2C_Master_Receive(&hi2c1,AHT20_ADDRESS,&readBuffer,1,HAL_MAX_DELAY);
/*检查状态字的校准使能位Bit[3]是否为1*/
if((readBuffer&0x08)==0x00)
{
uint8_t sendBuffer[3]={0xBE,0x80,0x00};
HAL_I2C_Master_Transmit(&hi2c1,AHT20_ADDRESS,sendBuffer,3,HAL_MAX_DELAY);
}
}
②根据手册封装数据读取函数
按照老师讲解的步骤进行数据切割和移位:
void AHT20_Read(float *Temperature,float *Humidity)
{
uint8_t sendBuffer[3]={0xAC,0x33,0x00};
uint8_t readBuffer[6];
HAL_I2C_Master_Transmit(&hi2c1,AHT20_ADDRESS,sendBuffer,3,HAL_MAX_DELAY);
HAL_Delay(75);
HAL_I2C_Master_Receive(&hi2c1,AHT20_ADDRESS,readBuffer,6,HAL_MAX_DELAY);
if((readBuffer[0]&0x80)==0x00)
{
uint32_t data=0;
data=(uint32_t)(readBuffer[3]>>4)+(uint32_t)(readBuffer[2]<<4)+(uint32_t)(readBuffer[1]<<12);
*Humidity=data*100.f/(1<<20);
data=(uint32_t)((readBuffer[3]&0x0F)<<16)+(uint32_t)(readBuffer[4]<<8)+(uint32_t)(readBuffer[5]);
*Temperature=data*200.0f/(1<<20)-50;
}
}
IIC中断、DMA&状态机编程
中断模式:
①在选项卡中开启事件中断向量与错误中断向量
②HAL_I2C_Master_Transmit_IT中断函数无需设置等待时间;但与此同时由于轮询、中断、DMA三者的工作模式的区别,并不能直接在驱动程序中替代函数名。轮询带有强制阻塞机制,程序会等待所有数据发送\接受完成才会接着向下执行,但中断\DMA是非阻塞模式,并不会进行等待。
为了使用I2C的中断机制,我们需要重构AHT20的读取过程,拆解为测量(发送测量指令)、获取(传输读取来的数据)、分析(运算出温湿度值)三大步骤。
void AHT20_measure(void)
{
static uint8_t sendBuffer[3]={0xAC,0x33,0x00};
HAL_I2C_Master_Transmit_IT(&hi2c1,AHT20_ADDRESS,sendBuffer,3);
}
void AHT20_Get(void)
{
HAL_I2C_Master_Receive_IT(&hi2c1,AHT20_ADDRESS,readBuffer,6);
}
void AHT20_Analysis(float *Temperature,float *Humidity)
{
if((readBuffer[0]&0x80)==0x00)
{
uint32_t data=0;
data=((uint32_t)readBuffer[3]>>4)+((uint32_t)readBuffer[2]<<4)+((uint32_t)readBuffer[1]<<12);
*Humidity=data*100.0f/(1<<20);
data=(((uint32_t)readBuffer[3]&0x0F)<<16)+((uint32_t)readBuffer[4]<<8)+((uint32_t)readBuffer[5]);
*Temperature=data*200.0f/(1<<20)-50;
}
}
状态机编程:
在单片机开发领域内中,状态机编程是一种非常常见且强大的设计模式。状态机允许你将系统的行为分解为一系列离散的状态,并根据输入或事件在这些状态之间转移。
以下是使用状态机编程时的一些基本步骤和概念:
-
定义状态:
- 首先,你需要确定你的系统有哪些可能的状态。
- 每个状态都应该有一个唯一的标识符(如枚举值、常量或字符串)。
-
定义状态转移:
- 确定从一个状态转移到另一个状态的条件。
- 这些条件通常基于外部输入、内部事件或定时器。
-
实现状态机:
- 使用循环或中断服务程序来检测状态转移条件。
- 当条件满足时,更新当前状态并执行与该状态关联的任何操作。
-
编写状态处理函数:
- 对于每个状态,编写一个处理函数来执行该状态下的操作。
- 这些函数可能包括读取传感器、控制执行器、更新显示等。
-
管理状态数据:
- 如果状态机需要跟踪数据(如计数器、标志等),请确保在状态转移时正确地保存和恢复这些数据。
-
错误处理:
- 实现错误处理机制以处理无效状态转移或意外事件。
- 这可能包括将系统重置为默认状态、记录错误或触发警报。
-
测试和验证:
- 在开发过程中,通过模拟输入和事件来测试状态机的行为。
- 使用调试工具来监视状态转移和数据流。
-
优化和重构:
- 如果状态机变得复杂或难以维护,请考虑重构它以简化结构或提高性能。
- 使用设计模式和最佳实践来保持代码清晰和可维护。
这次的需求需要五种状态轮换:
0 初始状态 发送测量命令
1 正在发送测量指令
2 测量指令发送完成 等待75ms后开始通信
3 数据读取中
4 读取完成 进行数据解析与输出
因此主函数的逻辑:
/* USER CODE BEGIN WHILE */
while (1)
{
if(aht20State==0){
AHT20_measure();
aht20State=1;
}else if(aht20State==2){
HAL_Delay(75);
AHT20_Get();
aht20State=3;
}else if(aht20State==4){
AHT20_Analysis(&temperature,&humidity);
sprintf(message,"温度:%.1f,湿度:%.1f%%\r\n",temperature,humidity);
HAL_UART_Transmit(&huart2,(uint8_t*)message,strlen(message),HAL_MAX_DELAY);
HAL_Delay(1000);
aht20State=0;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
其中1->2,3->4的逻辑处理交给I2C的中断处理回调函数:
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if(hi2c==&hi2c1)
{
aht20State=2;
}
}
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if(hi2c==&hi2c1)
{
aht20State=4;
}
}
DMA模式:
①在CubeMX选项卡中开启DMA设置。
⑤把传递函数的_IT改成_DMA即可。
作者:番茄灭世神