1. 项目概述
这个项目是我在开发一款低成本扫地机器人时搭建的嵌入式工程框架。选择STM32F103作为主控芯片,配合FreeRTOS实时操作系统,实现了多任务调度、传感器数据融合和运动控制的核心功能。整套方案在保证性能的前提下,将BOM成本控制在200元以内,特别适合创客和小批量产品开发。
作为一款典型的资源受限型嵌入式系统,这个框架需要解决三个核心矛盾:有限的硬件资源与复杂功能需求之间的平衡、实时性要求与任务调度效率的协调、以及低功耗设计与持续作业需求的兼顾。下面我就从硬件选型开始,逐步拆解这个工程框架的设计思路和实现细节。
2. 硬件架构设计
2.1 主控芯片选型
STM32F103C8T6(俗称"蓝莓派")是这个项目的核心选择,主要基于以下几点考虑:
- 72MHz主频的Cortex-M3内核,性能足够处理传感器融合算法
- 64KB Flash + 20KB RAM的内存配置,刚好满足FreeRTOS和基础功能需求
- 丰富的外设接口:3个USART、2个SPI、2个I2C,方便扩展各类传感器
- 市场价格约12-15元,性价比极高
注意:虽然STM32F103已经属于"老将",但其成熟的生态和丰富的资料库,对于快速原型开发仍然具有不可替代的优势。新手建议选择带USB引脚的版本(如STM32F103CBT6),方便调试。
2.2 关键外设配置
整个硬件架构围绕以下核心模块构建:
| 模块类型 | 具体器件 | 接口方式 | 主要参数 |
|---|---|---|---|
| 运动控制 | TB6612电机驱动 | PWM+GPIO | 双路1.2A输出 |
| 距离检测 | HC-SR04超声波 | GPIO | 2cm-400cm |
| 碰撞检测 | 微动开关 | GPIO | 常开型 |
| 地面检测 | TCRT5000红外 | ADC | 检测高度2-15mm |
| 电源管理 | AMS1117 | - | 5V转3.3V |
特别要说明电机选型:我们采用N20减速电机(6V 200RPM)配合橡胶轮,实测单个电机堵转电流约800mA,TB6612的1.2A输出留有足够余量。电机编码器通过TIM2/TIM3的编码器接口直接读取,不占用CPU资源。
3. FreeRTOS任务设计
3.1 任务划分与优先级设置
整个系统划分为6个主要任务,优先级从高到低排列如下:
- 紧急停止任务(优先级6):监控碰撞传感器,触发后立即停止所有电机
- 运动控制任务(优先级5):处理PID控制和电机PWM输出
- 传感器采集任务(优先级4):轮询各类传感器数据
- 路径规划任务(优先级3):实现简单的弓字形清扫算法
- 状态显示任务(优先级2):驱动OLED显示运行状态
- 电源管理任务(优先级1):监控电池电压,低电量时返回充电座
c复制// 任务创建示例
xTaskCreate(vMotionControlTask, "MotionCtrl", 128, NULL, 5, NULL);
xTaskCreate(vSensorTask, "Sensor", 256, NULL, 4, NULL);
3.2 关键数据通信
任务间通信主要采用三种机制:
- 事件标志组:用于紧急状态通知(如碰撞事件)
- 消息队列:传输传感器数据包(含时间戳)
- 互斥锁:保护电机控制参数等共享资源
c复制// 定义全局通信句柄
EventGroupHandle_t xRobotEvents;
QueueHandle_t xSensorQueue;
SemaphoreHandle_t xMotorMutex;
// 事件标志定义
#define BUMP_EVENT (1 << 0)
#define LOW_BATTERY_EVENT (1 << 1)
4. 运动控制实现
4.1 电机PID控制
采用位置式PID算法控制电机转速,主要参数:
- 采样周期:10ms(与编码器读取同步)
- KP=0.8, KI=0.05, KD=0.12(通过Ziegler-Nichols法整定)
- 输出限幅:±800(对应PWM占空比70%)
c复制typedef struct {
float target_rpm;
float current_rpm;
float error_sum;
float last_error;
} PID_Controller;
void vUpdatePID(PID_Controller *pid) {
float error = pid->target_rpm - pid->current_rpm;
pid->error_sum += error;
float output = KP * error +
KI * pid->error_sum +
KD * (error - pid->last_error);
// 抗积分饱和
if(output > 800) pid->error_sum -= error;
pid->last_error = error;
vSetMotorOutput(output);
}
4.2 运动学模型
采用差分驱动模型,建立轮速与整车运动的关系:
code复制线速度 V = (V_left + V_right)/2
角速度 ω = (V_right - V_left)/L
其中L为轮距(本项目实测13cm)
通过逆运动学计算,可以得到给定线速度和角速度时,左右轮的目标转速:
c复制void vCalculateWheelSpeed(float v, float w, float *left_rpm, float *right_rpm) {
float L = 0.13; // 轮距13cm
*left_rpm = (v - w*L/2) * 60/(2*PI*0.035); // 轮径3.5cm
*right_rpm = (v + w*L/2) * 60/(2*PI*0.035);
}
5. 传感器数据处理
5.1 多传感器数据融合
使用加权平均法处理红外和超声波的地面距离数据:
c复制#define IR_WEIGHT 0.3
#define US_WEIGHT 0.7
float fGetCombinedDistance(void) {
float ir_dist = fGetIRDistance();
float us_dist = fGetUltrasonicDistance();
// 有效性检查
if(us_dist > 400) return ir_dist;
if(ir_dist < 2 || ir_dist > 15) return us_dist;
return ir_dist*IR_WEIGHT + us_dist*US_WEIGHT;
}
5.2 碰撞检测优化
常规的轮询检测方式会存在响应延迟,我们采用外部中断+软件去抖的方案:
c复制// 中断服务程序
void EXTI0_IRQHandler(void) {
if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
static uint32_t last_time = 0;
uint32_t now = xTaskGetTickCount();
// 50ms去抖判断
if(now - last_time > pdMS_TO_TICKS(50)) {
xEventGroupSetBits(xRobotEvents, BUMP_EVENT);
}
last_time = now;
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
6. 低功耗设计技巧
6.1 动态频率调整
根据任务负载动态调整系统时钟:
c复制void vAdjustSystemClock(TaskStatus_t *pxTaskStats) {
uint8_t cpu_load = 100 - pxTaskStats->xIdleTaskCount;
if(cpu_load < 30) {
RCC_PLLCmd(DISABLE);
RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);
SystemCoreClockUpdate();
} else {
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
RCC_PLLCmd(ENABLE);
SystemCoreClockUpdate();
}
}
6.2 任务休眠策略
非实时任务采用事件驱动唤醒:
c复制void vDisplayTask(void *pvParameters) {
while(1) {
// 等待显示更新事件
xEventGroupWaitBits(xDisplayEvents,
UPDATE_DISPLAY_EVENT,
pdTRUE, pdFALSE, portMAX_DELAY);
vUpdateOLED();
}
}
7. 常见问题与解决方案
7.1 FreeRTOS堆栈溢出
现象:系统随机重启,调试显示portCHECK_IF_IN_ISR报错
解决方法:
- 增大FreeRTOS堆大小(修改Heap_Sizein .s文件)
- 使用uxTaskGetStackHighWaterMark()监控任务栈使用
- 优化局部变量使用,特别是避免大数组定义
7.2 电机干扰导致复位
现象:电机启动时MCU意外复位
处理方案:
- 在电机电源端并联1000uF电解电容
- 增加TVS二极管保护电路
- 确保所有数字地单点连接到电源地
7.3 路径规划卡死
现象:机器人卡在角落反复尝试转向
优化策略:
- 增加尝试次数计数器,超过阈值后执行后退动作
- 在算法中加入随机扰动因子
- 记录历史路径,避免重复区域
8. 工程优化建议
经过三个版本迭代,总结出以下优化经验:
- 内存管理:将频繁创建删除的对象改为静态分配,减少堆碎片
- 定时器使用:硬件定时器留给电机控制等硬实时任务
- 调试技巧:利用SEGGER RTT实现无串口日志输出
- 量产考虑:将校准参数存储在Flash最后一页,避免每次烧录
这个框架已经稳定运行超过500小时测试,后续可扩展性主要体现在:
- 通过蓝牙模块接入手机APP控制
- 增加IMU实现更精确的航迹推算
- 引入简单的SLAM算法提升清扫效率
整个项目的源代码和PCB设计文件我已经开源在GitHub,包含详细的注释和搭建教程。对于想要入门嵌入式Linux的开发者,这个项目也是很好的前期铺垫,因为FreeRTOS的任务设计思想与Linux进程/线程管理有很多相通之处。