目录

STM32CubeMX配置步骤

系统调试配置

SYS设置:

时钟配置:

 USART配置

模式是轮询模式

 IIC配置

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

Keil编程

IIC的轮询模式(用AHT20读取温湿度)

添加新的AHT20的.c和.h文件

写void AHT20_Init(void)初始化函数

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);

IIC的中断模式(非阻塞)

CubeMX配置

Keil

错误原因

1. 中断模式是非阻塞的

2. 数据缓冲区生命周期问题

3. 中断模式需要配合回调或标志使用

所以需要引入状态机

首先拆解函数

在aht20.h中声明

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

IIC的DMA模式

DMA 模式注意事项

CubuMX配置

Keil


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(),在传输完成时进行后续处理。

  • 工作流程

    1. CPU 调用中断函数,设置好要发送/接收的数据缓冲区。

    2. I2C 硬件产生起始信号、发送地址,然后逐字节传输数据,每完成一个字节产生中断。

    3. 中断服务程序自动处理下一个字节,直到全部完成,最后调用回调函数通知用户。

  • 优点:提高 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 传输完成后触发中断,再调用回调。

  • 工作流程

    1. CPU 配置 DMA 通道,启动 DMA 传输。

    2. DMA 负责将数据从内存搬运到 I2C 发送寄存器(或从接收寄存器搬到内存),每传输一个字节计数减一,直到计数归零产生 DMA 传输完成中断。

    3. DMA 中断服务程序调用 HAL 库回调,最终通知用户。

  • 优点:比中断模式进一步降低 CPU 负载,尤其适合大数据块传输。

  • 缺点:占用 DMA 通道,需合理配置 DMA 与 I2C 的交互,编程复杂度更高。

DMA 模式注意事项
  • 起始信号和停止信号仍然由 I2C 硬件自动产生,并非由 CPU 执行。“起始信号和停止信号由 cpu 执行”不准确,CPU 只负责启动传输,信号由硬件产生。

  • 使用 DMA 时需确保内存缓冲区在传输过程中不被修改,且地址递增方向正确

CubuMX配置

添加DMA通道

保存并生成代码

Keil

只用将aht20.c的 _IT 改为 _DMA

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐