硬件核心参数

设备地址:7 位 I2C 地址为 1000'000 (0x40) 。在 STM32 HAL 库中需左移一位使用 0x80。

测量命令:使用“非挂起主机(No Hold Master)”模式,命令代码为温度 1111'0011 (0xF3) 和湿度 1111'0101 (0xF5) 。

STM32CubeMX 配置步骤

这一步很关键,如果这里不配置I2C1 event interruptHAL_BUSY 错误将无法消除

FreeRTOS通过移植或者直接使用STM32CubeMX生成,之前的博客有写这些,这里不过多赘述。

通过 FreeRTOS 信号量 + DMA,将原本耗时的温湿度采集转变为异步任务。CPU 仅需几微秒来启动传输,其余时间可以释放给其他实时任务。

踩坑记录与解决办法

问题一:持续报错 HAL_BUSY (0x02)

原因:没有勾选 I2C1 event interrupt

解决办法:勾选 I2C1 event interrupt

SHT21.c和SHT21.h代码

SHT21.c

#include "SHT30.h"

/* 变量定义 */
TaskHandle_t SHT21Task_Handler = NULL;
TaskHandle_t LedTask_Handler = NULL;
SemaphoreHandle_t i2c_sem = NULL;

float Temperature = 0.0f;
float Humidity = 0.0f;

// 接收缓冲区:2字节数据 + 1字节校验位
uint8_t i2c_rx_buf[3];


/* --- 任务 A:温湿度采集任务 --- */
void vSHT21Task(void *pvParameters)
{
// 1. 创建二值信号量
    i2c_sem = xSemaphoreCreateBinary();
    
    uint8_t cmd_t = 0xF3;  // 触发温度测量 (No Hold Master)
    uint8_t cmd_rh = 0xF5; // 触发湿度测量 (No Hold Master)
	
    while(1) {
				/* --- 读取温度 --- */
        // 发送测量命令,地址 0x80 
        HAL_I2C_Master_Transmit(&hi2c1, SHT21_ADDR, &cmd_t, 1, 100);          
				// 关键:手册规定 14位温度转换最长 85ms 
				vTaskDelay(pdMS_TO_TICKS(100)); 

				// 启动 DMA 接收 3 字节 (数据+校验)
				if (HAL_I2C_Master_Receive_DMA(&hi2c1, SHT21_ADDR, i2c_rx_buf, 3) == HAL_OK) {
						// 等待中断回调释放信号量
						if (xSemaphoreTake(i2c_sem, pdMS_TO_TICKS(50)) == pdPASS) {
								// 数据转换:屏蔽最后2位状态位 
								uint16_t st = (i2c_rx_buf[0] << 8) | (i2c_rx_buf[1] & 0xFC);
								// 换算公式 
								Temperature = -46.85f + 175.72f * (float)st / 65536.0f;
						}
				}
						
        /* --- 读取湿度 --- */
        HAL_I2C_Master_Transmit(&hi2c1, 0x80, &cmd_rh, 1, 100);
        // 关键:根据手册,12bit湿度转换最长需要29ms
        vTaskDelay(pdMS_TO_TICKS(40)); 

        if(HAL_I2C_Master_Receive_DMA(&hi2c1, 0x80, i2c_rx_buf, 3) == HAL_OK) {
            if(xSemaphoreTake(i2c_sem, pdMS_TO_TICKS(50)) == pdPASS) {
                // 计算湿度:清除状态位并代入公式
                uint16_t raw_rh = (i2c_rx_buf[0] << 8) | (i2c_rx_buf[1] & 0xFC);
                Humidity = -6.0f + 125.0f * (float)raw_rh / 65536.0f; 
            }
        }

        vTaskDelay(pdMS_TO_TICKS(2000)); // 每2秒采集一次
    }
}

/* --- 任务 B:LED 闪烁任务 (心跳灯) --- */
void vLedTask(void *pvParameters)  
{
    while(1)
    {
        HAL_GPIO_TogglePin(GPIOA, DBG_Pin);
        
        // 这里的延时决定闪烁频率
        // 比如 500ms 翻转一次,就是 1Hz 的频率
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}


void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
    if (hi2c->Instance == I2C1) {
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        
        // 释放信号量,叫醒正在等待数据的任务
        xSemaphoreGiveFromISR(i2c_sem, &xHigherPriorityTaskWoken);
        
        // 如果被唤醒的任务优先级更高,立即切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c->Instance == I2C1)
    {
        // 如果进到这里,说明发生了错误(如应答错误 AF)
        // 记得也给个信号量,让任务醒过来处理错误,否则会死等
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        xSemaphoreGiveFromISR(i2c_sem, &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

SHT21.h

#ifndef __SHT21_H__
#define __SHT21_H__

#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "i2c.h"
#include "semphr.h"

// SHT21 核心参数 
#define SHT21_ADDR         (0x40 << 1) // 7位地址 0x40 转为 HAL 8位地址 0x80
#define CMD_MEASURE_T_NH   0xF3        // 触发温度测量 (不挂起主机)
#define CMD_MEASURE_RH_NH  0xF5        // 触发湿度测量 (不挂起主机)


/* 全局变量声明 (使用 extern 防止重复定义) */
extern TaskHandle_t SHT21Task_Handler;
extern TaskHandle_t LedTask_Handler;
extern SemaphoreHandle_t i2c_sem; 

extern float Temperature;
extern float Humidity;

/* 任务函数声明 */
void vSHT21Task(void *pvParameters);
void vLedTask(void *pvParameters);

#endif 

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "i2c.h"
#include "spi.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "SHT21.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* 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_DMA_Init();
  MX_ADC1_Init();
  MX_I2C1_Init();
  MX_I2C2_Init();
  MX_SPI1_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
	

/* 创建信号量 */
	i2c_sem = xSemaphoreCreateBinary();

	/* 创建任务 A:温湿度采集 (优先级稍高) */
	xTaskCreate(vSHT21Task, "SHT21", 256, NULL, 2, &SHT21Task_Handler);

	/* 创建任务 B:LED 闪烁 (优先级稍低) */
	xTaskCreate(vLedTask, "LED", 128, NULL, 1, &LedTask_Handler);	


	vTaskStartScheduler();
							
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
//		HAL_GPIO_TogglePin(GPIOA, DBG_Pin);
//		HAL_Delay(100);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
  RCC_OscInitStruct.PLL.PLLN = 16;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
/* 1. 堆栈溢出钩子函数 */
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    /* 当某个任务的堆栈超过预设大小时,会进入这里 */
    /* 调试时可以查看 pcTaskName 确认是哪个任务溢出了 */
    __disable_irq();
    while(1);
}

/* 2. 滴答定时器钩子函数 */
void vApplicationTickHook(void)
{
    /* 每个 Tick 中断都会调用一次,如果不需要功能,可以留空 */
}

/* 3. 内存申请失败钩子函数 */
void vApplicationMallocFailedHook(void)
{
    /* 当 pvPortMalloc 申请不到内存(堆内存不足)时,会进入这里 */
    __disable_irq();
    while(1);
}
/* USER CODE END 4 */

/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM1 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1)
  {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐