1. 项目概述:当STM32F103遇上FreeRTOS
去年帮朋友改造一台老式扫地机器人时,我发现商业方案虽然性能强大,但代码封闭得像黑盒子。于是决定用STM32F103C8T6+FreeRTOS搭建一个教学级框架,这个蓝色小板子72MHz主频配上64KB Flash,刚好能承载实时系统的核心功能。不同于玩具级的轮询架构,这里每个功能模块都是独立任务——电机控制、传感器采集、路径规划就像工厂里各司其职的工人,通过FreeRTOS的队列和信号量高效协作。
这个框架最实用的价值在于:你可以清晰看到PWM波形如何驱动L298N电机模块,红外避障信号怎样触发任务切换,以及陀螺仪数据如何通过队列传递给路径规划算法。所有硬件层操作都基于HAL库封装,上层逻辑完全遵循RTOS设计范式,特别适合想从裸机开发进阶到实时系统的工程师。我曾用这套框架给公司新人培训,三个月内就有两个实习生独立做出了可用的原型机。
2. 硬件架构设计要点
2.1 核心器件选型逻辑
主控选择STM32F103C8T6绝非偶然:它的Cortex-M3内核在72MHz下能流畅运行FreeRTOS,TIM3/TIM4硬件PWM生成器可直接驱动电机,内置的I2C和SPI接口完美适配各类传感器。相比更高级的F4系列,F103在性价比和生态成熟度上仍是学习首选。实际采购时要注意区分正版(丝印清晰有STlogo)和山寨版(价格低于15元慎买)。
电机驱动模块用L298N是个经典选择,虽然效率不如DRV8833这类现代芯片,但其双H桥设计能同时控制两个直流电机(左右轮)和两个单相电机(边刷/滚刷)。我在PCB布局时特意将PWM信号线(PA6/PA7)远离模拟传感器线路,避免高频干扰导致ADC采样异常。有个坑要注意:L298N的使能端必须接PWM才能调速,直接给高电平会导致电机全速运行。
2.2 传感器组配置技巧
三路红外避障模块(左/右/前)的安装角度决定了避障效果。经过实测,建议将左右传感器呈30°斜向前方安装,这样在碰到墙角时能提前检测到侧向障碍物。碰撞开关最好用弹簧片式而非微动开关,前者寿命更长且能缓冲机械冲击。MPU6050陀螺仪的安装位置要尽量靠近机器人重心,并用软质海绵减震,否则电机振动会导致姿态数据异常。
电源部分我推荐两节18650锂电池(7.4V)配合LM2596降压模块,关键是要在3.3V和5V输出端都加装1000μF的电解电容。曾有个学员没加电容,电机启动时的电压跌落导致STM32反复复位。OLED显示屏选用0.96寸SSD1306足够显示状态信息,其I2C接口要加上拉电阻(4.7KΩ),否则长导线传输时会丢帧。
3. FreeRTOS任务规划详解
3.1 任务优先级设计原则
这个框架包含6个核心任务,优先级从1(最低)到4(最高):
- Task_Main(优先级4):作为系统指挥官,它通过状态机管理全局模式(自动/定点/待机)。高优先级确保模式切换能立即响应
- Task_Motor(优先级3):直接控制PWM输出的关键任务,必须优先保证电机指令执行
- Task_PathPlan(优先级3):与电机任务同级,避免路径计算延迟导致控制滞后
- Task_Sensor(优先级2):传感器数据采集允许适度延迟,但需快于UI刷新
- Task_Comm(优先级2):串口通信任务处理非实时指令
- Task_UI(优先级1):OLED刷新和按键扫描对实时性要求最低
重要经验:电机控制任务的堆栈要预留余量(至少128字),我曾遇到因堆栈溢出导致PWM输出异常的bug,后来通过uxTaskGetStackHighWaterMark()函数监控才发现问题。
3.2 任务间通信实战
传感器数据通过队列传递是最经典的RTOS模式。这里创建了一个能存储10组数据的队列:
c复制QueueHandle_t sensor_queue = xQueueCreate(10, sizeof(SensorData_t));
当红外检测到障碍物时,Task_Sensor将数据打包发送:
c复制SensorData_t data;
data.ir_left = HAL_GPIO_ReadPin(IR_LEFT_GPIO_Port, IR_LEFT_Pin);
xQueueSend(sensor_queue, &data, portMAX_DELAY);
而Task_PathPlan在另一端接收:
c复制xQueueReceive(sensor_queue, &rx_data, pdMS_TO_TICKS(100));
这个100ms的超时设置很关键——既避免任务永久阻塞,又给紧急任务留出处理时间。实际调试中发现,若队列深度不足会导致新数据覆盖未处理的旧数据,后来通过uxQueueMessagesWaiting()监控将队列深度调整到15才稳定。
4. 关键代码实现解析
4.1 电机控制精要
L298N的驱动逻辑需要同时控制PWM和方向引脚。以左轮为例:
c复制// 设置PWM占空比(0-100对应0-100%)
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, abs(speed));
// 设置方向(IN1/IN2控制正反转)
HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, (speed>0)?GPIO_PIN_SET:GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, (speed<0)?GPIO_PIN_SET:GPIO_PIN_RESET);
这里有个优化点:通过__HAL_TIM_SET_COMPARE直接操作寄存器,比HAL库的HAL_TIM_PWM_Start更高效。电机死区处理也很重要,我在代码中加入速度渐变逻辑:
c复制// 速度渐变函数(避免急启停)
void RampSpeed(int16_t *current, int16_t target, uint8_t step) {
if(*current < target) *current += step;
else if(*current > target) *current -= step;
}
实测发现step值设为5时,电机既能快速响应又不会因突变电流触发保护。
4.2 传感器融合算法
简单的避障逻辑可以通过状态机实现:
c复制typedef enum {
OBSTACLE_NONE,
OBSTACLE_FRONT,
OBSTACLE_LEFT,
OBSTACLE_RIGHT
} ObstacleState_t;
ObstacleState_t CheckObstacle(SensorData_t data) {
if(!data.collision) return OBSTACLE_FRONT; // 碰撞开关优先级最高
if(!data.ir_front) return OBSTACLE_FRONT;
if(!data.ir_left) return OBSTACLE_LEFT;
if(!data.ir_right) return OBSTACLE_RIGHT;
return OBSTACLE_NONE;
}
结合陀螺仪数据还能实现更智能的转向控制。例如检测到左侧障碍物时,可以结合Z轴角速度让机器人平滑右转:
c复制if(state == OBSTACLE_LEFT) {
cmd.right_speed = -20 * (1 + gyro_z/100.0); // 角速度补偿
cmd.left_speed = 20;
}
这个公式中的100.0是经验值,需要根据实际陀螺仪量程调整。
5. 系统调试与优化
5.1 FreeRTOS性能监控
在FreeRTOSConfig.h中开启相关宏定义:
c复制#define configUSE_TRACE_FACILITY 1
#define configGENERATE_RUN_TIME_STATS 1
然后通过串口打印任务状态:
c复制char taskList[512];
vTaskList(taskList); // 获取任务状态表
HAL_UART_Transmit(&huart1, (uint8_t*)taskList, strlen(taskList), HAL_MAX_DELAY);
典型输出如下:
code复制TaskName State Priority Stack Num
Task_Main R 4 88 1
Task_Motor B 3 104 2
Task_Sensor R 2 72 3
其中Stack列显示的剩余栈空间(单位字)要特别关注,小于20就需要调整堆栈大小。
5.2 电源管理技巧
在空闲任务中添加WFI指令可降低功耗:
c复制void vApplicationIdleHook(void) {
__WFI(); // 进入等待中断模式
}
同时要配置PWM频率在10kHz以上(人耳听不见的超声范围),否则电机线圈会发出刺耳的啸叫声。电池电压检测建议用ADC的DMA模式采样,避免任务阻塞:
c复制HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 3); // 连续采样3次
6. 项目进阶方向
6.1 硬件升级路径
如果想提升扫地能力,可以考虑:
- 换装带编码器的直流电机(如JGA25-370),通过脉冲计数实现里程计
- 增加TOF激光测距传感器(VL53L0X)替代部分红外模块
- 使用集成电机驱动芯片(如TB6612FNG)替代L298N,效率提升30%
6.2 软件算法扩展
在现有框架上可以轻松集成:
- 基于A*算法的全局路径规划
- 使用IMU数据实现航迹推算(Dead Reckoning)
- 通过ESP8266接入MQTT实现手机控制
- 添加FatFS文件系统记录清扫日志
有个学员在毕业设计中给这个框架增加了语音识别模块,通过离线语音芯片实现声控启停,这展示了系统的良好扩展性。