STM32G070 使用 FreeRTOS 和 I2C DMA 驱动 SHT21传感器
·
硬件核心参数
设备地址:7 位 I2C 地址为 1000'000 (0x40) 。在 STM32 HAL 库中需左移一位使用 0x80。
测量命令:使用“非挂起主机(No Hold Master)”模式,命令代码为温度 1111'0011 (0xF3) 和湿度 1111'0101 (0xF5) 。

STM32CubeMX 配置步骤

这一步很关键,如果这里不配置I2C1 event interrupt,HAL_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 */
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)