实战:智能传感器节点
综合运用全课程知识,构建完整的 STM32F4 多传感器采集节点:FreeRTOS 多任务、低功耗模式、Bootloader 概念。
综合运用全课程知识,构建完整的 STM32F4 多传感器采集节点:FreeRTOS 多任务、低功耗模式、Bootloader 概念。
本章构建一个完整的室内环境监测节点,综合运用前9章所有知识点:
/** * DHT22 单总线驱动 * 需要精确的微秒延时,使用 DWT 计数器实现 */ #define DHT22_PIN GPIO_PIN_1 #define DHT22_PORT GPIOA /* DWT 微秒延时初始化(Cortex-M4 调试计数器)*/ void DWT_Delay_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } void delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t cycles = us * (SystemCoreClock / 1000000); while ((DWT->CYCCNT - start) < cycles); } static void DHT22_SetOutput(void) { GPIO_InitTypeDef g = {DHT22_PIN, GPIO_MODE_OUTPUT_OD, GPIO_NOPULL, GPIO_SPEED_FREQ_HIGH, 0}; HAL_GPIO_Init(DHT22_PORT, &g); } static void DHT22_SetInput(void) { GPIO_InitTypeDef g = {DHT22_PIN, GPIO_MODE_INPUT, GPIO_PULLUP, GPIO_SPEED_FREQ_HIGH, 0}; HAL_GPIO_Init(DHT22_PORT, &g); } typedef struct { float temp; float humi; uint8_t ok; } DHT22_Data_t; DHT22_Data_t DHT22_Read(void) { DHT22_Data_t result = {0}; uint8_t data[5] = {0}; /* 主机发起:拉低 1.5ms,再释放(拉高)*/ DHT22_SetOutput(); HAL_GPIO_WritePin(DHT22_PORT, DHT22_PIN, GPIO_PIN_RESET); HAL_Delay(2); HAL_GPIO_WritePin(DHT22_PORT, DHT22_PIN, GPIO_PIN_SET); delay_us(30); DHT22_SetInput(); /* 等待 DHT22 响应低电平(约80μs)*/ uint32_t timeout = HAL_GetTick() + 10; while (HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN) && HAL_GetTick() < timeout); while (!HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN) && HAL_GetTick() < timeout); while (HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN) && HAL_GetTick() < timeout); /* 读取 40 bit 数据 */ for (int i = 0; i < 40; i++) { while (!HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN)); /* 等待高电平 */ delay_us(40); /* 40μs 后采样:>28μs = 1,<28μs = 0(因为0只有26-28μs高)*/ if (HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN)) data[i / 8] |= (1 << (7 - i % 8)); while (HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN)); /* 等待低电平 */ } /* 校验(前4字节之和 = 第5字节)*/ if ((data[0]+data[1]+data[2]+data[3]) == data[4]) { result.humi = ((data[0] << 8) | data[1]) / 10.0f; result.temp = (((data[2] & 0x7F) << 8) | data[3]) / 10.0f; if (data[2] & 0x80) result.temp = -result.temp; /* 负温度 */ result.ok = 1; } return result; }
/** * 在 FreeRTOS Idle 任务钩子中进入 STOP 模式 * 配置:FreeRTOSConfig.h 中 configUSE_IDLE_HOOK = 1 */ void vApplicationIdleHook(void) { /* 进入 STOP 模式,降低功耗 */ /* 由 RTC Alarm 或 EXTI 唤醒(SysTick 在 STOP 中停止!)*/ HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); /* 唤醒后:时钟回到 HSI 16MHz,需要重新配置 PLL */ SystemClock_Config(); /* 恢复 168MHz */ } /** * 深度低功耗:STANDBY 模式(仅电池供电设备) * 唤醒后相当于复位,SRAM 数据丢失 * 使用 RTC 每分钟唤醒一次,采集数据后再次进入 STANDBY */ void Enter_Standby_Mode(uint32_t wake_after_sec) { /* 配置 RTC 闹钟 N 秒后唤醒 */ RTC_AlarmTypeDef sAlarm = {0}; /* ... 省略 RTC 配置细节 ... */ /* 清除 WU 标志,使能 WU 引脚 */ __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); /* 进入 STANDBY(永不返回,只有复位/唤醒才能退出)*/ HAL_PWR_EnterSTANDBYMode(); } /* 功耗对比(STM32F407,3.3V 供电):*/ /* 正常运行 168MHz : ~50mA = ~165mW */ /* SLEEP 模式(停CPU): ~25mA = ~82mW */ /* STOP 模式 : ~1.1mA = ~3.6mW */ /* STANDBY 模式 : ~2.8μA = ~9.2μW */
/** * Bootloader 跳转到应用程序 * APP_ADDR = 0x08008000(Application 在 Flash 的起始地址) * 应用程序的链接脚本中 FLASH origin 也要改为 0x08008000 * STM32CubeMX → SCB->VTOR = APP_ADDR(重定向中断向量表) */ #define APP_ADDR 0x08008000U typedef void (*pFunction)(void); void Bootloader_JumpToApp(void) { uint32_t app_sp = *((uint32_t*) APP_ADDR); /* 应用栈顶 */ uint32_t app_reset = *((uint32_t*)(APP_ADDR + 4)); /* 应用复位向量 */ /* 检查栈顶地址是否在 SRAM 范围内 */ if ((app_sp & 0xFF000000) != 0x20000000) { /* 应用程序不存在或损坏,保持在 Bootloader */ return; } /* 关闭所有中断,防止跳转后中断打入 Bootloader 的 ISR */ __disable_irq(); HAL_DeInit(); /* 重定向中断向量表到应用程序地址 */ SCB->VTOR = APP_ADDR; /* 设置主栈指针 */ __set_MSP(app_sp); /* 跳转!*/ pFunction jump = (pFunction)app_reset; jump(); /* 永不返回 */ } /* Bootloader main 逻辑(简化)*/ int main(void) { HAL_Init(); SystemClock_Config(); USART1_Init(); /* 等待 500ms,若收到升级命令进入升级流程 */ if (Bootloader_CheckUpdate()) { Bootloader_ReceiveFirmware(); /* Ymodem/XMODEM 或自定义协议 */ } Bootloader_JumpToApp(); /* 跳转到应用 */ while(1); /* 永不到达 */ }
/* Task_Report:每 5 秒打包一次 JSON 上报 */ void Task_Report(void *pvParams) { SensorData_t data; char json_buf[128]; static uint32_t seq = 0; for (;;) { /* 等待最新数据(超时 6 秒,超时则上报错误)*/ if (xQueuePeek(qSensorData, &data, pdMS_TO_TICKS(6000)) == pdPASS) { snprintf(json_buf, sizeof(json_buf), "{\"seq\":%lu,\"t\":%.1f,\"h\":%.1f,\"p\":%.1f,\"ts\":%lu}\r\n", seq++, data.temperature, data.humidity, data.pressure / 100.0f, /* Pa → hPa */ data.timestamp); } else { snprintf(json_buf, sizeof(json_buf), "{\"seq\":%lu,\"err\":\"sensor_timeout\"}\r\n", seq++); } UART_DMA_Send((uint8_t*)json_buf, strlen(json_buf)); vTaskDelay(pdMS_TO_TICKS(5000)); } }
① STM32CubeMX 中开启所有用到的外设时钟(RCC → AHB/APB Enable)
② DMA Stream/Channel 对照手册确认无误
③ FreeRTOS 优先级:configMAX_PRIORITIES=8;ISR 优先级 ≥ configMAX_SYSCALL_INTERRUPT_PRIORITY
④ 所有共享变量加 volatile,跨任务共享资源用 Mutex 保护
⑤ 每个任务用 uxTaskGetStackHighWaterMark() 验证栈充足(剩余 > 20 字)
⑥ 低功耗模式唤醒后重新调用 SystemClock_Config() 恢复 168MHz
⑦ Bootloader 与 App 的 Flash 起始地址在各自的链接脚本(.ld)和 VTOR 中一致
基础层:架构(Cortex-M4)→ 存储器映射 → 寄存器操作 → GPIO / 时钟
中断层:NVIC → 优先级 → EXTI → ISR 规范 → 临界区
外设层:定时器/PWM → UART → I2C → SPI → ADC/DAC
高效层:DMA(零拷贝)→ 双缓冲 → 环形缓冲区
系统层:FreeRTOS → 任务/队列/信号量 → 低功耗 → Bootloader
嵌入式开发的核心思维:理解硬件,用软件控制硬件,用系统协调软件。