基于STM32F103C8T6 HAL库来操作IIC通信的AHT20(轮询,中断,DMA三种模式)
目录
void AHT20_Read(float *Tem,float *Hum)
HAL_I2C_Master_Transmit(&hi2c1,AHT20_ADRESS,send_buffer,3,HAL_MAX_DELAY);
HAL_I2C_Master_Receive(&hi2c1,AHT20_ADRESS,&receive_buffer,1,HAL_MAX_DELAY);
STM32CubeMX配置步骤
系统调试配置
设置debug模式为serial wire ,避免GPIO复用导致下载错误的后果,使用st-link下载
SYS设置:

时钟配置:

USART配置
模式是轮询模式

IIC配置
模式选择标准,下面的配置不用改什么参数

Keil编程
IIC的轮询模式(用AHT20读取温湿度)
-
原理:CPU 循环等待 I2C 传输完成,期间无法执行其他任务。
-
优点:编程简单,适合短数据、低并发场景。
-
缺点:阻塞 CPU,浪费处理能力。
添加新的AHT20的.c和.h文件
注意添加新文件的时候引入正确的路径

写void AHT20_Init(void)初始化函数
#include "aht20.h"
#define AHT20_ADRESS 0X70
void AHT20_Init(void)
{
uint8_t receive_buffer;
HAL_Delay(40);
HAL_I2C_Master_Receive(&hi2c1,AHT20_ADRESS,&receive_buffer,1,HAL_MAX_DELAY);
if((receive_buffer& 0X08)==0)
{
uint8_t send_buffer[]={0XBE,0X08,0X00};
HAL_I2C_Master_Transmit(&hi2c1,AHT20_ADRESS,send_buffer,3,HAL_MAX_DELAY);
}
}

void AHT20_Read(float *Tem,float *Hum)
void AHT20_Read(float *Tem,float *Hum)
{
uint8_t send_buffer[3]={0XAC,0X33,0X00};
HAL_I2C_Master_Transmit(&hi2c1,AHT20_ADRESS,send_buffer,3,HAL_MAX_DELAY);
HAL_Delay(75);
uint8_t read_buffer[6];
HAL_I2C_Master_Receive(&hi2c1,AHT20_ADRESS,read_buffer,6,HAL_MAX_DELAY);
if((read_buffer[0]&0X80)==0X00)
{
uint32_t data = 0;
data = (((uint32_t)read_buffer[1]<<12) + ((uint32_t)read_buffer[2]<<4) + ((uint32_t)read_buffer[3]>>4));
*Hum = (data * 100.0f)/(1<<20);
data = ((((uint32_t)read_buffer[3]&0X0F)<<16) + ((uint32_t)read_buffer[4]<<8) + ((uint32_t)read_buffer[5]));
*Tem = (data * 200.0f)/(1<<20)-50;
}
}

主函数中

总结
HAL_I2C_Master_Transmit(&hi2c1,AHT20_ADRESS,send_buffer,3,HAL_MAX_DELAY);
主机向从机发送
HAL_I2C_Master_Receive(&hi2c1,AHT20_ADRESS,&receive_buffer,1,HAL_MAX_DELAY);
主机等待接受从机的数据
IIC的中断模式(非阻塞)
-
原理:CPU 启动 I2C 传输后立即返回,继续执行其他代码。当 I2C 外设完成一个字节传输或遇到标志事件时,触发中断,在中断服务程序中处理下一个字节或结束传输。
-
HAL 库函数:
-
HAL_I2C_Master_Transmit_IT():启动中断方式发送。 -
HAL_I2C_Master_Receive_IT():启动中断方式接收。 -
需要实现回调函数
HAL_I2C_MasterTxCpltCallback()和HAL_I2C_MasterRxCpltCallback(),在传输完成时进行后续处理。
-
-
工作流程:
-
CPU 调用中断函数,设置好要发送/接收的数据缓冲区。
-
I2C 硬件产生起始信号、发送地址,然后逐字节传输数据,每完成一个字节产生中断。
-
中断服务程序自动处理下一个字节,直到全部完成,最后调用回调函数通知用户。
-
-
优点:提高 CPU 利用率,可在等待 I2C 时执行其他任务。
-
缺点:需处理中断优先级和回调,编程稍复杂。
CubeMX配置

Keil

错误原因
1. 中断模式是非阻塞的
-
HAL_I2C_Master_Transmit_IT()启动发送后立即返回,CPU 不会等待发送完成。此时send_buffer中的数据可能尚未完全移出,甚至还没有开始发送。 -
紧接着调用
HAL_Delay(75)只是延时,并不能保证发送已完成(虽然75ms远大于发送3字节所需时间,但这不是可靠的同步方式)。 -
更严重的是,
HAL_I2C_Master_Receive_IT()在发送尚未完成时就启动了接收,这会导致总线冲突或接收到错误数据。
2. 数据缓冲区生命周期问题
-
send_buffer和read_buffer都是局部变量,函数AHT20_Read()在调用HAL_I2C_Master_Transmit_IT()后很快就执行完毕并返回了,但此时I2C外设可能还在后台发送数据。而发送的数据来自send_buffer这个局部变量(函数已经返回),send_buffer所在的内存可能已经被其他函数或中断破坏了! -
即使延迟足够长,中断模式下的接收也是异步的,
read_buffer在接收完成前就被读取,数据无效。
3. 中断模式需要配合回调或标志使用
-
必须等待发送完成后再启动接收,等待接收完成后再解析数据。这通常通过回调函数或全局标志实现。
所以需要引入状态机
首先拆解函数

#include "aht20.h"
#define AHT20_ADRESS 0X70
uint8_t read_buffer[6];
void AHT20_Init(void)
{
uint8_t receive_buffer;
HAL_Delay(40);
HAL_I2C_Master_Receive(&hi2c1,AHT20_ADRESS,&receive_buffer,1,HAL_MAX_DELAY);
if((receive_buffer& 0X08)==0)
{
uint8_t send_buffer[3]={0XBE,0X08,0X00};
HAL_I2C_Master_Transmit(&hi2c1,AHT20_ADRESS,send_buffer,3,HAL_MAX_DELAY);
}
}
void AHT20_Read(float *Tem,float *Hum)
{
uint8_t send_buffer[3]={0XAC,0X33,0X00};
HAL_I2C_Master_Transmit_IT(&hi2c1,AHT20_ADRESS,send_buffer,3);
HAL_Delay(75);
uint8_t read_buffer[6];
HAL_I2C_Master_Receive_IT(&hi2c1,AHT20_ADRESS,read_buffer,6);
if((read_buffer[0]&0X80)==0X00)
{
uint32_t data = 0;
data = (((uint32_t)read_buffer[1]<<12) + ((uint32_t)read_buffer[2]<<4) + ((uint32_t)read_buffer[3]>>4));
*Hum = (data * 100.0f)/(1<<20);
data = ((((uint32_t)read_buffer[3]&0X0F)<<16) + ((uint32_t)read_buffer[4]<<8) + ((uint32_t)read_buffer[5]));
*Tem = (data * 200.0f)/(1<<20)-50;
}
}
void AHT20_Measure()
{
static uint8_t send_buffer[3]={0XAC,0X33,0X00};
HAL_I2C_Master_Transmit_IT(&hi2c1,AHT20_ADRESS,send_buffer,3);
}
void AHT20_Get()
{
HAL_I2C_Master_Receive_IT(&hi2c1,AHT20_ADRESS,read_buffer,6);
}
void AHT20_Analysis(float *Tem,float *Hum)
{
if((read_buffer[0]&0X80)==0X00)
{
uint32_t data = 0;
data = (((uint32_t)read_buffer[1]<<12) + ((uint32_t)read_buffer[2]<<4) + ((uint32_t)read_buffer[3]>>4));
*Hum = (data * 100.0f)/(1<<20);
data = ((((uint32_t)read_buffer[3]&0X0F)<<16) + ((uint32_t)read_buffer[4]<<8) + ((uint32_t)read_buffer[5]));
*Tem = (data * 200.0f)/(1<<20)-50;
}
}
在aht20.h中声明

修改main.c中的aht20的使用流程



/* USER CODE BEGIN PV */
/*
状态机的状态
0:准备测量
1:正在测量
2:测量完成,并进行等待
3:正在读取
4:读取完成,进行解析
*/
uint8_t aht20_state=0;
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if(hi2c==&hi2c1)
{
aht20_state=2;
}
}
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if(hi2c==&hi2c1)
{
aht20_state=4;
}
}
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
AHT20_Init();
char message[50];
float Tem=0,Hum=0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// AHT20_Read(&Tem,&Hum);
if(aht20_state==0) //0:准备测量
{
AHT20_Measure();
aht20_state=1;
}
// else if(aht20_state==1) 1:正在测量
// {可是不知道啥时候测量完成,就不知道啥时候让aht20_state=2
//这个时候就用到发送中断回调函数
// }
else if(aht20_state==2) // 2:测量完成,并进行等待
{
HAL_Delay(75);
AHT20_Get();
aht20_state=3;
}
// else if(aht20_state==3) 3:正在读取
// {可是不知道啥时候读取完成,就不知道啥时候让aht20_state=4
//这个时候就用到读取中断回调函数
// }
else if(aht20_state==4) //4:读取完成,进行解析
{
AHT20_Analysis(&Tem,&Hum);
sprintf(message,"温度:%.2f°C,湿度:%.2f%%\r\n",Tem,Hum);
HAL_UART_Transmit(&huart1,(uint8_t *)message,strlen(message),HAL_MAX_DELAY);
HAL_Delay(1000);
aht20_state=0;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
IIC的DMA模式
-
原理:在中断模式基础上,将数据从内存到 I2C 外设(或反向)的搬运工作交给 DMA 控制器,CPU 仅在启动传输和传输完成时介入。
-
HAL 库函数:
-
HAL_I2C_Master_Transmit_DMA() -
HAL_I2C_Master_Receive_DMA() -
同样需要实现回调函数,DMA 传输完成后触发中断,再调用回调。
-
-
工作流程:
-
CPU 配置 DMA 通道,启动 DMA 传输。
-
DMA 负责将数据从内存搬运到 I2C 发送寄存器(或从接收寄存器搬到内存),每传输一个字节计数减一,直到计数归零产生 DMA 传输完成中断。
-
DMA 中断服务程序调用 HAL 库回调,最终通知用户。
-
-
优点:比中断模式进一步降低 CPU 负载,尤其适合大数据块传输。
-
缺点:占用 DMA 通道,需合理配置 DMA 与 I2C 的交互,编程复杂度更高。
DMA 模式注意事项
-
起始信号和停止信号仍然由 I2C 硬件自动产生,并非由 CPU 执行。“起始信号和停止信号由 cpu 执行”不准确,CPU 只负责启动传输,信号由硬件产生。
-
使用 DMA 时需确保内存缓冲区在传输过程中不被修改,且地址递增方向正确
CubuMX配置
添加DMA通道


保存并生成代码
Keil
只用将aht20.c的 _IT 改为 _DMA

更多推荐
所有评论(0)