1. 为什么需要FreeRTOS?

如果你曾经用STM32做过稍微复杂点的项目,肯定遇到过这样的烦恼:既要处理按键扫描,又要采集传感器数据,还得控制电机转动,所有功能都写在main函数的while循环里,代码越写越乱,调试起来简直要命。这时候就该FreeRTOS出场了——它就像个智能管家,帮你把不同任务安排得明明白白。

我在去年做的智能家居项目中深有体会。当时需要同时处理Wi-Fi通信、环境监测和OLED显示,用裸机开发时各种延时函数互相干扰,改用FreeRTOS后每个功能独立成任务,代码量反而减少了30%。STM32CubeMX更是神器,点点鼠标就能生成FreeRTOS配置代码,完全不用操心底层移植。

2. 开发环境搭建

2.1 硬件准备

拿我手边的STM32F407Discovery开发板举例,核心参数如下:

组件 规格
MCU STM32F407VGT6 (Cortex-M4)
时钟频率 168MHz
RAM 192KB
Flash 1MB
外设 USB OTG, 14个定时器, 3个ADC

2.2 软件安装

推荐使用STM32CubeIDE(版本1.11.0以上),它集成了CubeMX和TrueSTUDIO,装一个软件全搞定。安装时注意勾选这两个组件:

  • STM32CubeF4 Firmware Package
  • Middleware中的FreeRTOS组件

第一次打开CubeMX时,在Pinout界面右侧找到Middleware选项卡,像搭积木一样勾选FreeRTOS。关键配置项我一般这样设置:

#define configTOTAL_HEAP_SIZE ((size_t)30*1024)  // 堆空间设为30KB
#define configUSE_PREEMPTION 1  // 启用抢占式调度

3. 创建第一个任务

3.1 图形化配置

在CubeMX的Tasks and Queues标签页,点击Add按钮新建任务。我给LED闪烁任务这样配置:

  • Task Name: LED_Task
  • Priority: osPriorityNormal (注意数值越小优先级越高)
  • Stack Size: 128 words (512字节)
  • Entry Function: LED_Task_Entry

生成代码后,在main.c会自动生成任务框架:

void LED_Task_Entry(void *argument)
{
  for(;;) {
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
    osDelay(500);  // 注意这里用的是RTOS延时而非HAL_Delay
  }
}

3.2 手动创建任务

有时需要动态创建任务,比如根据传感器类型动态创建采集任务。在USER CODE BEGIN RTOS_THREADS区域添加:

osThreadId_t sensorTaskHandle;
const osThreadAttr_t sensor_attributes = {
  .name = "SensorTask",
  .stack_size = 256 * 4,
  .priority = (osPriority_t) osPriorityAboveNormal,
};

void SensorTask(void *argument) {
  while(1) {
    float temp = Read_Temperature();
    osDelay(100);
  }
}

// 在main中创建
sensorTaskHandle = osThreadNew(SensorTask, NULL, &sensor_attributes);

4. 消息队列实战

4.1 配置消息队列

在CubeMX的Queues标签页新建队列:

  • Queue Name: SensorDataQueue
  • Queue Size: 10
  • Item Size: sizeof(float) // 传输温度数据

4.2 生产者-消费者模式

传感器任务作为生产者:

float temp_data = Read_DHT11();
if(osMessageQueuePut(SensorDataQueue, &temp_data, 0, 0) != osOK) {
  printf("队列已满!\n");
}

显示任务作为消费者:

float received_data;
if(osMessageQueueGet(SensorDataQueue, &received_data, NULL, 100) == osOK) {
  OLED_ShowFloat(received_data, 2);
}

4.3 调试技巧

遇到过队列堵塞?在FreeRTOSConfig.h中开启调试宏:

#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTION 1

然后用SystemView工具可视化任务和队列状态,比printf调试高效十倍。

5. 优先级调度陷阱

上周刚踩过一个坑:高优先级任务持续占用CPU导致系统卡死。解决方法有三种:

  1. 合理设置优先级
    我把电机控制任务设为osPriorityHigh,显示任务设为osPriorityLow

  2. 使用时间片轮转
    在CubeMX配置中勾选configUSE_TIME_SLICING

  3. 加入主动延时
    即使在紧急任务中也要加osDelay(1),让出CPU使用权

实测发现,带硬件浮点运算的任务需要更大栈空间,建议至少设置256 words,否则会出现神秘的内存错误。

6. 进阶技巧

6.1 软件定时器

配置一个1秒周期的定时器:

osTimerId_t sysTimerHandle = osTimerNew(SysTimer_Callback, osTimerPeriodic, NULL, NULL);
osTimerStart(sysTimerHandle, 1000);

void SysTimer_Callback(void *argument) {
  static int counter = 0;
  printf("系统运行%d秒\n", ++counter);
}

6.2 内存管理

当动态创建任务频繁时,建议替换默认的heap_4.c内存管理方案。我在项目中使用ThreadX风格的块内存分配器,碎片率降低了60%。

7. 常见问题排查

  1. HardFault_Handler
    80%的情况是栈溢出,在CubeMX里把对应任务的Stack Size调大

  2. osKernelStart卡死
    检查是否在main()中调用了osKernelInitialize()

  3. 消息队列丢失数据
    增大队列长度或提高消费者任务优先级

最近用这套方法给工厂做了套设备监控系统,连续运行30天零崩溃。关键是把看门狗任务设为最高优先级,定期检查其他任务的心跳信号。

Logo

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

更多推荐