ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32扫地机器人开发复盘:从硬件选型到IAP升级全记录

STM32扫地机器人开发复盘:从硬件选型到IAP升级全记录 简介本资源是一套基于STM32F103的扫地机器人嵌入式开发完整工程面向嵌入式初学者与智能硬件开发者解决多传感器融合控制、实时任务调度与固件在线升级等典型机器人开发难题。项目采用FreeRTOS实时操作系统集成电机驱动、ADC电池管理含温度/电流双路采样、MPU6050陀螺仪姿态解算、红外掉落检测、超声波避障、碰撞识别、悬空与垃圾盒状态监测等功能模块并配备支持IAP的Bootloader实现安全固件升级。压缩包共343个文件含150个头文件.h与139个源码文件.c涵盖FreeRTOS任务调度、外设驱动、传感器算法及Keil工程配置.uvprojx/.uvoptx辅以README说明、调试配置.dbgconf和批处理脚本.bat总大小4.16MB。已有717人学习下载提供原理图、可直接编译运行的完整代码框架及模块化目录结构便于理解机器人底层控制逻辑与系统级协同设计。 扫地机器人这个项目我前前后后折腾了两个多月从裸机轮询到切换FreeRTOS从不敢做IAP到最后在真机上反复升了二十多次级。回头看这个项目的难度不在某个单一模块而在把所有模块——电机、电池、陀螺仪、超声波、碰撞、悬空检测、IAP——装进一颗STM32里还要让它们互不干扰、协同工作。这篇文章就把我从硬件选型到软件架构从PID参数整定到Bootloader设计完整复盘一遍。适合正在做STM32综合项目、想从“点亮LED”进阶到“跑一个真正产品”的朋友。1. 整机架构先把所有模块的脾气摸清楚1.1 主控选型与资源盘点我做这个项目用的主控是STM32F103RCT6选择它不是因为性能多强而是因为各方生态最成熟、踩坑资料最多。你要真拿到资源清单会发现这芯片其实比较紧张256KB Flash、48KB RAM、51个GPIO但扫地机器人这个场景恰恰是“外设多而杂”不是“计算量大”所以F103这个级别刚好卡在甜点上。先盘一下外设资源占用电机驱动左右两个直流减速电机各需1路PWM控制速度2路GPIO控制方向共2路TMR输出、4个IO口另外两个边刷电机不需要闭环各需1路PWM共2路。PWM全算下来至少4路。编码器左右电机各配一个霍尔编码器需要2路定时器编码器模式或者外部中断计数。电池电压检测1路ADC通过电阻分压采集。电流检测采样电阻运放再进1路ADC。超声波及红外传感器前面和侧面3路超声波底部3路红外掉落检测外加2路物理碰撞检测。MPU6050陀螺仪I2C总线。IAP升级需要UART串口与上位机通信。OLED显示和按键也需要若干GPIO。你把这些算下来单片机的GPIO和定时器几乎用完。我第一版布局时没有做引脚冲突检查结果发现I2C引脚和编码器定时器引脚冲突硬生生改了一版PCB这个教训后面专门讲。1.2 外设布局与引脚冲突排查给新手一个建议动笔写代码之前先画一张完整的引脚分配表。我后来把引脚分配表做成了这样功能模块引脚/外设复用功能备注左电机PWMPA8TIM1_CH120kHz PWM右电机PWMPA9TIM1_CH2左编码器PA6/PA7TIM3_CH1/CH2编码器模式右编码器PB6/PB7TIM4_CH1/CH2编码器模式MPU6050PB8/PB9I2C1注意与编码器冲突电池电压PA0ADC1_CH0分压采样电流检测PA1ADC1_CH1运放输出关键是排查这一步。MPU6050的I2C1默认引脚是PB6/PB7但如果你的右编码器用了TIM4的PB6/PB7就撞车了。我当时解决办法是把MPU6050换到软件模拟I2C随便找两个空闲GPIO就行。说实话对于MPU6050这种低速设备软件模拟I2C完全够用反而比硬件I2C稳。1.3 电源树设计为什么电池管理要单独成块扫地机器人是电池供电设备电源树直接决定系统稳定性。我用的是一块3S锂电池组标称电压11.1V满电12.6V保护板限制放电截止大约9V。电源树设计如下11.1V电池→ 电机驱动芯片DRV8870直接供电电机对电压波动不敏感。11.1V → 降压模块MP1584 → 5V给超声波传感器、OLED、逻辑电路供电。5V → AMS1117-3.3 → 3.3V给STM32、MPU6050供电。电池电压 → 分压电阻100k/10k→ STM32 ADC。这里有个坑降压模块的纹波直接影响ADC采样准确性。如果你用MP1584输出纹波可能有点大我后来在5V输出端加了22uF陶瓷电容100uF电解电容ADC数据还是有波动。最终的做法是把ADC采样做均值滤波并且采样时间点避开PWM开关瞬间。后面电池部分会展开说。2. 轮子上的细节电机控制与编码器闭环2.1 从PWM调速到方向控制电机驱动这块我用的是DRV8870两路H桥驱动芯片单芯片能驱动一个直流电机。控制逻辑很简单IN1/IN2控制方向IN11、IN20正转反过来反转都给0或都停刹车。PWM引脚控制速度。这里先提醒一个新手常踩的坑PWM频率不要随便设。直流减速电机的电感量比较大PWM频率太低会导致电流纹波大电机噪音大而且低速时明显顿挫。我最初用1kHz PWM电机低速时声音刺耳、转速不稳。后来改成20kHz问题立刻消失噪音几乎听不见。扫地机这种对噪音敏感的家电产品PWM频率建议不低于15kHz。代码层面用HAL库配置TIM1输出PWM通道1和通道2分别给左右电机// 初始化TIM1 PWMPSC840, ARR99得到20kHz htim1.Instance TIM1; htim1.Init.Prescaler 84 - 1; htim1.Init.CounterMode TIM_COUNTERMODE_UP; htim1.Init.Period 100 - 1; htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(htim1);注意STM32F103的TIM1是高级定时器需要调用HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1)后还要TIM_CCxChannelCmd使能主输出否则PWM没输出。很多人一开始在这里卡很久其实仔细看HAL库例程就能发现。2.2 编码器闭环扫地机差速转向的基本功扫地机器人要完成直线行走、原地旋转、走弓字形路径全靠左右轮差速。但如果只是开环给左右轮一样的PWM占空比由于电机个体差异、地面摩擦不同、电池电压下降实际转速绝对不一样走不了多远就会跑偏。所以必须在电机尾部加霍尔编码器做闭环。我用的是带AB相霍尔编码器的直流减速电机减速比30:1电机轴每转编码器输出约11个脉冲霍尔式受限于磁极对数经过减速箱后轮子每转大约330个脉冲。不算高但做速度闭环足够。STM32定时器的编码器模式非常方便直接硬件解算方向和计数不需要外部中断去数脉冲省CPU又准确。// 编码器模式初始化以TIM3为例 TIM_Encoder_InitTypeDef sEncoderConfig; sEncoderConfig.EncoderMode TIM_ENCODERMODE_TI12; sEncoderConfig.IC1Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC1Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC1Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC1Filter 0x0F; // 输入滤波防止毛刺误计数 sEncoderConfig.IC2Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC2Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC2Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC2Filter 0x0F; HAL_TIM_Encoder_Init(htim3, sEncoderConfig);读取实时速度的常用方法每10ms读一次编码器计数值差值除以时间就是速度。注意TIM计数器是16位溢出处理要做好否则高速时数据会突变。我的做法是每次读值后立刻把计数器清零用__HAL_TIM_SET_COUNTER(htim3, 0)简单粗暴但有效。2.3 PID整定从“原地画圈”到“直线走3米不偏”速度闭环我用的是经典增量式PID20ms执行一次。公式不复杂增量输出 Kp*(e(k)-e(k-1)) Ki*e(k) Kd*(e(k)-2*e(k-1)e(k-2))我实际调试的参数Kp0.8Ki0.03Kd0.1PWM输出限幅在0-100%积分限幅防止windup。调试过程比写代码痛苦。第一次上电左轮狂转、右轮不转直接原地画圈。后来一步步排查发现是编码器A、B相接反了导致计数方向反了电机正转反馈成反转PID形成一个正反馈越调越猛。调PID的经验先把Ki、Kd设为0只留Kp从小到大加直到轮子能稳定转动但不振荡。加一点点Ki消除稳态误差低速时如果不能维持目标速度就是积分不够。Kd基本用来抑制超调但编码器测速本身有量化噪声Kd太大会引入高频抖动我最终Kd只给了0.1。目标速度变化时用梯形加减速曲线不要阶梯式跳变否则机器人会点头。闭环之后的效果让机器人最左边输入500rpm右边给500rpm直线行进3米横向偏移不会超过5厘米。这个精度对于清扫路径规划完全够用。3. 电池监测与ADC采样别让电压读数骗了你3.1 分压电路与ADC参考电压的选择电池电压监测是扫地机的一项基础功能没电了得自己回充不能干到低压保护突然断电。电路上最简单的方式就是电阻分压电池 ── R1(100k) ── ADC_IN ── R2(10k) ── GNDADC引脚电压 VBAT × R2/(R1R2)也就是电池电压的1/11。12.6V满电时ADC引脚大约1.145V这在F103的3.3V参考电压范围内留了充足余量。但这里有个很多人容易忽略的问题STM32F103的内部ADC参考电压VREF就是VDDA3.3V如果你用的是LDO3.3V本身有一定波动转换出来的电压值就会跟着漂。我用的AMS1117输出精度约±1%算下来电压读数误差可能达到0.1V以上对于电量估算来说有点大。改进方案有两个方案A用外部基准电压芯片比如TL431或者REF3030给ADC提供精确的VREF。但F103的VREF引脚在LQFP64封装上是独立的LQFP48封装则内部连接VDDA改不了。方案B用STM32内部参考电压通道VREFINT做校准。STM32F103在ADC通道17内部连接了一个约1.2V的带隙基准电压这个基准不随供电电压变化。你可以先读出VREFINT的ADC值反推当前真实的VDDA然后再算电池电压。我用的就是这个办法实测能把电压误差控制在±0.05V以内。3.2 多通道ADC采样DMA的配置细节项目里电池电压、电机电流、可能还有温度检测多个ADC通道要采样。我建议用ADC1多通道DMA循环采样而不是每个通道单独调用HAL_ADC_Start_IT中断读值。原因是DMA方式让ADC自己跑不占CPU数据到达后通过标志位一批一批处理。配置要点sConfig.Channel ADC_CHANNEL_0; // 电池电压 sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; // 采样时间要长 sConfig.OffsetNumber ADC_OFFSET_NONE; sConfig.NbrOfConversion 2; // 两个通道采样时间我特意选到239.5周期因为电池电压检测不需要高速采样但需要足够的采样时间让内部采样电容充满否则测出来的电压偏低。这在ADC阻抗比较大的时候尤其明显——你前面的分压电阻有100kADC内部采样电容充电时间就要更长。DMA数据是顺序排列的比如通道0、通道1、通道0、通道1连续刷。我维护一个缓冲区数组取完数据后还要排序判断哪天是哪个通道用HAL_ADC_Start_DMA时配置好DataLength和地址就解决了。3.3 SoC估算百分比不是电压线性映射很多新手会把电池百分比简单映射成电压比如12.6V对应100%、9V对应0%然后线性插值。这在锂电池上误差非常大因为锂电池放电曲线中段特别平缓可能从50%到20%电压只下降了0.3V而到了末端电压又急剧下跌。线性映射会导致前半程电量显示下降很慢后半程直接从40%掉到0%毫无预警。扫地机器人要做低电量回充不能等到没电才反应所以SoC估算一定不能这么简单。我实际用的是电流积分法库仑计法在电池回路上串联一个0.05欧姆的采样电阻通过INA240运放放大后进ADC采样电流再对时间积分得到已消耗电量。每10ms采一次电流累计mAh。但这玩意有个难点采样电阻的压降非常小比如1A电流只有50mV运放精度和ADC分辨率都有限。我的做法是用“电压校准电流积分”的混合方案平时用电流积分但每个100ms用电池电压做一次修正——查充放电曲线表把积分值和电压对应SoC做个加权平均防止积分漂移。最终电量精度能做到±5%左右虽然跟专业BMS比差远了但对于扫地机这种回充容忍度较高的设备够用。4. 传感器全家桶掉落、悬空、碰撞、超声波的组合逻辑4.1 掉落检测和悬空检测的区别与协作我第一次看到“掉落检测”和“悬空检测”这两个词也觉得奇怪这不都是怕机器人掉下台阶吗实际在扫地机产品里它们语义有细微差别掉落检测检测机器人正前方/正下方是否有地面落差防止从台阶边缘掉下去。属于“预防性”检测。悬空检测检测机器人某个轮子或机身是否处于悬空状态。比如人被拎起来的时候、或者跨越门槛时某个轮子短暂悬空系统需要正确识别而不是误判。硬件上我是用3个红外测距传感器装在底盘前方和左右两侧型号是夏普GP2Y0A21YK0F或者更便宜的VL53L0X激光测距。红外模拟输出版本可以直接接ADC激光版本走I2C。我的方案前左、前右用两个GP2Y0A21正前方用VL53L0X这样既能测距离又能判断边缘。关键的逻辑处理不是在传感器本身而是在状态机里typedef enum { ROBOT_STATE_NORMAL, ROBOT_STATE_CLIFF_DETECTED, // 检测到前方悬崖 ROBOT_STATE_WHEEL_HANG, // 轮子悬空 ROBOT_STATE_STUCK // 卡死 } RobotState;当传感器探测到前方距离突然变大超过阈值比如从10cm跳到15cm以上就认为到了边缘立刻停止前进、倒车、转向。这里有个经验不要只看单次测量值要做连续3次确认避免红外传感器在某个角度下偶尔读到异常值。4.2 碰撞检测的机械设计和消抖处理碰撞检测我用的是最朴素的方案微动开关。左右各一个拨片式结构碰撞时触发。但微动开关最烦的是抖动——机器人碰到障碍物的瞬间开关触点会反复通断几十次直接读GPIO会得到一堆乱跳的电平。软件上必须做消抖我采用10ms延时状态确认if (HAL_GPIO_ReadPin(COLLIDE_LEFT_GPIO_PORT, COLLIDE_LEFT_PIN) RESET) { HAL_Delay(10); if (HAL_GPIO_ReadPin(COLLIDE_LEFT_GPIO_PORT, COLLIDE_LEFT_PIN) RESET) { // 确认碰撞 set_robot_state(ROBOT_STATE_COLLIDE_LEFT); } }不过光靠软件消抖还不够机械结构上也可以减少误触发微动开关的安装支架加一点柔性碰撞时有个缓冲行程而不是硬碰硬。这个对硬件设计功底有一定要求但实际效果立竿见影。4.3 超声波避障的盲区与多方向融合超声波测距模块HC-SR04便宜、功耗低但有两个天然的坑盲区HC-SR04的最小测量距离约2cm距离太近读数是乱的。扫地机贴着墙壁走时超声波基本处于盲区。反射角度超声波对斜射的硬表面不太友好如果障碍物是圆柱形或者夹角很大的墙面回波可能反射到别处导致读数偏大。我的方案是超声波只做“中距离避障”比如30cm之外的障碍物提前减速、转向近距离交给红外碰撞开关兜底。三个超声波分别装在前方、左前方、右前方轮询扫描每路测量周期大约50ms。融合策略简单但有效如果前方超声波距离 30cm且 15cm减速到低速同时微调方向避开 如果前方超声波距离 15cm直接原地转向 如果侧方超声波距离 20cm切换到沿墙模式下面陀螺仪部分细说。这里要提醒多路超声波如果同时发送会发生串扰。我做的处理是同一时刻只允许一路超声波触发然后读它的回波下一时刻再触发另一路轮流来。串扰在同时触发时特别明显测出来的距离忽远忽近排查了很久才找到原因。5. 陀螺仪MPU6050不只是看角度5.1 数据读取与姿态融合MPU6050是扫地机上非常关键的一个传感器但很多人第一反应是“用它测倾角”。扫地机在平地上跑倾角变化很小陀螺仪真正的价值在航向角计算和路径纠偏。硬件连接我用的I2C软件模拟SDA/SCL分别接到两个空闲GPIO。初始化时要做几个动作// MPU6050初始化要点 mpu6050_set_sleep(0); // 唤醒 mpu6050_set_clk_source(CLK_SRC_PLL_X); // 时钟源选PLL不要用内部RC mpu6050_set_gyro_range(FSR_500); // 量程±500dps mpu6050_set_accel_range(ACCEL_RANGE_2G); // 加速度计量程±2g mpu6050_set_dlpf(DLPF_20HZ); // 低通滤波器把高频噪声滤掉时钟源选择很重要。MPU6050内部RC振荡器受温度影响很大如果选内部时钟零偏漂移会非常明显。用PLL锁定外部晶振之后零偏稳定性就好了很多。原始数据读出来之后陀螺仪Z轴角速度需要积分得到航向角但直接积分会漂移。我用的是互补滤波加速度计计算出的横滚角和俯仰角作为长期参考陀螺积分作为短期参考融合系数大约0.98的陀螺权重。angle 0.98 * (angle gyro_z * dt) 0.02 * accel_angle;航向角yaw不能用加速度计修正因为水平方向的转动加速度计测不出来只能靠陀螺仪积分。这就需要一个好的零偏校准。5.2 零偏校准与温飘处理MPU6050静止放置时Z轴陀螺仪读数不会正好是0一般有个几十到几百LBSLSB的零偏。如果直接积分哪怕只有1度/秒的零偏60秒后就偏了60度机器人可能朝着错误的方向跑。校准方法很简单上电后保持机器人静止2秒采集200次陀螺Z轴数据取平均值作为零偏然后在每次读取时减掉这个零偏。但零偏不是固定的温度变化会让它漂移。扫地机运行时电机发热、环境温度变化零偏会慢慢变化。一个工程上的做法是每次机器人停止运动且静止超过200ms时自动重新校准零偏。因为静止状态陀螺仪读数就应该是0此时将当前读数作为新的零偏。我用这个策略长时间运行的航向漂移控制在了每分钟±2度以内对于家用扫地机器人完全可接受。5.3 陀螺仪在路径规划里的实际用途扫地机的路径规划分几个档次随机碰撞式、沿墙模式、弓字形覆盖、基于SLAM的全局规划。做SLAM需要激光雷达或者视觉成本高、算法复杂度大这个项目我做的是“弓字形沿墙”的半规划方案。而实现弓字形的核心就是航向角。实现思路机器人先走一段直线利用编码器闭环保证直线编码器已经能保证80%的直行精度每隔一段距离用陀螺仪航向角做修正——如果当前航向偏离了初始方向超过3度就调整左右轮速差把航向掰回来走到边界后原地旋转90度利用陀螺仪判断是否真的转了90度转到之后继续走直线。没有陀螺仪的情况下转90度只能靠“某个速度旋转固定时间”但电池电压变化会导致转速变化转过的角度有时多有时少很难保证直角。有了陀螺仪之后旋转是闭环的——目标角度到了就停稳定、精准。6. FreeRTOS任务划分裸机写不到一半就想换系统6.1 任务列表与优先级设计这个项目一开始我是用裸机main循环写的。功能少还好一旦加入IAP升级、各种传感器、电机控制、蓝牙调试主循环越写越乱时序也开始出问题——超声波测距期间做了一次I2C读取MPU6050导致测距数据超时。后来果断切换到FreeRTOS。任务划分如下任务名优先级周期/触发方式说明motor_ctrl_task高320ms周期读取编码器PID计算输出PWMsensor_task高350ms周期超声波、碰撞、掉落、悬空采集imu_task中210ms周期读取MPU6050姿态和数据融合battery_task中2100ms周期ADC采样、滤波、SoC估算nav_task中2200ms周期状态机、路径规划、避障决策iap_task低1事件触发接收固件数据、写Flashdebug_task低11s周期OLED显示、串口打印优先级之争主要发生在motor_ctrl_task和sensor_task上。电机控制如果被抢占PID周期不固定轮速会抖动传感器任务如果被抢占超声波测距容易超时。我的处理是这两个任务优先级相同但通过不同频段错开且motor_ctrl_task用定时器或高分辨率延时保证20ms周期尽量准。在实践中真正需要严格时序的还是电机控制。6.2 队列、信号量与事件组的选择FreeRTOS各任务之间通信我用了几种方式队列传感器任务采集到的数据放到队列里导航任务从队列里取。比如超声波距离值队列长度设为5如果导航任务忙暂时没取数据也不会丢太多最新的值。事件组用于多条件组合。比如“开始回充”这个动作需要同时满足“电量低于20%”和“当前不在充电座上”这两个条件来自不同任务用事件组可以方便地等待多个bit同时或者任一个置位。二值信号量用在IAP升级中串口接收到一帧完整数据后释放信号量IAP任务取出处理。我自己踩过的一个坑是队列阻塞导致低优先级任务饿死。导航任务优先级低有时会长时间阻塞在队列读取上导致无法执行状态机切换。解决办法是设置合理的超时时间比如xQueueReceive(..., 50)50ms收不到就返回先执行其他逻辑不能死等到数据来。6.3 堆栈溢出检测与内存开销STM32F103只有48KB RAM跑FreeRTOS之后内存非常紧张。每个任务栈是最占资源的我统计过motor_ctrl: 512字节因为调用很多浮点PID计算栈要稍微大点sensor: 512字节imu: 512字节I2C通信有局部变量battery: 256字节nav: 1024字节状态机复杂局部变量多iap: 512字节Flash写操作需要缓冲区debug: 256字节总共栈空间约3.5KB加上内核自身开销、定时器任务栈虽然不多但勉强够用。关键是要开启FreeRTOS的堆栈溢出检测功能我用的是configCHECK_FOR_STACK_OVERFLOW 2这个模式下会在任务切换时检查栈指针是否越界一旦溢出立刻停在vApplicationStackOverflowHook里方便定位。实际调试时nav任务栈一开始给512字节跑几分钟就溢出改到1024字节才稳定。别舍不得内存栈溢出是随机死机最常见的原因。7. IAP升级这台扫地机最怕变砖7.1 Bootloader与App分区设计IAPIn-Application Programming是这个项目里风险最大的部分。没有IAP的时候固件出问题用ST-Link重新下载就行有了IAP升级失败可能直接变砖机器人不动了。我的Flash分区设计如下区域地址大小内容Bootloader0x0800000016KB上电检查、跳转、升级逻辑App区0x08004000220KB主功能代码升级缓存区0x0803800016KB接收固件数据的临时存储参数区0x0803C00016KB校准参数、升级标志、版本号Bootloader占用前16KBApp编译时要改链接脚本把Flash Base改为0x08004000。Keil设置里有个坑如果你用了HAL库默认的SystemInit和中断向量表偏移没有改App跳转会跑飞。必须在App工程启动代码里拉设置SCB-VTOR 0x08004000。// App工程main函数最开头设置中断向量表偏移 SCB-VTOR APP_START_ADDRESS;7.2 升级协议与校验机制升级流程我设计成“上位机 → 先发升级指令 → 上位机分包发送固件 → 机器人逐包写入缓存区 → 全部收完后校验 → 跳转执行”。每个数据包格式帧头 0xAA 0x55 | 包序号(2字节) | 数据长度(1字节) | 数据(n字节) | CRC16(2字节)分包大小我定的是每包512字节数据。为什么是512因为Flash按页擦除STM32F103每个扇区大小是1KB但页擦除单位就是1KB512字节数据两次正好填满一页。实际更合理的做法是分包大小等于Flash页大小直接对齐能省去跨页的逻辑判断。我代码里做了跨页处理因为这个分块小了嵌套扇区临界点处理真烦人。CRC校验一定要做。虽然串口传输偶尔出错概率低但固件数据任何一个bit错了机器人就可能行为异常。我用的是CRC16-Modbus每包数据都校验。7.3 跳转前后的外设复位问题App跳转这里有个非常隐蔽的坑Bootloader里初始化过外设、开过中断、配置过时钟跳转去App时这些状态如果没清干净App就会神秘出错。我一开始遇到的问题Bootloader里用串口接收固件数据跳转到App后App里串口初始化明明是好的但是第一次调用HAL_UART_Transmit直接卡死。后来排查发现是Bootloader里串口中断没有关闭跳转后中断向量表已经偏移到App但中断标志还挂着App中断服务函数没处理导致卡死。解决方案分三部分跳转前关闭所有外设中断把SysTick也停掉调用HAL_DeInit()复位所有外设跳转前把系统时钟切换回默认值HSI 8MHz让App重新初始化时钟树。void jump_to_app(uint32_t app_addr) { // 关闭全局中断 __disable_irq(); // 复位外设 HAL_DeInit(); // 关闭SysTick SysTick-CTRL 0; SysTick-LOAD 0; SysTick-VAL 0; // 跳转 void (*app_reset_handler)(void) (void (*)(void))(*(volatile uint32_t *)(app_addr 4)); __set_MSP(*(volatile uint32_t *)app_addr); // 关键设置VTOR指向App向量表 SCB-VTOR app_addr; app_reset_handler(); }7.4 掉电保护与版本回滚这是IAP升级的安全底线。扫地机电池供电最怕的就是升级过程中电量耗尽。我的方案是“双区备份升级标志”每次固件升级前先把当前固件备份到“备份区”但空间有限实际上我并没有完整备份App而是保留一个最小版本固件在固定区域。在Flash参数区写一个“升级正在进行的标志”内容是一个魔法数。收到完整固件并校验通过后在跳转前把标志从“升级进行中”改为“升级完成”。Bootloader上电时检查这个标志如果标志是“升级进行中”说明上次升级没有正常完成启动回滚机制——把备份区的固件刷回App区或者直接进入升级模式等待重新发送固件。由于Flash空间限制备份整个App实在不行我的回滚方案更加简单粗暴如果“升级进行中”标志存在且App区校验失败就直接进入Bootloader的串口接收模式等待上位机重新发固件。这虽然不是自动回滚到旧版本但至少避免了变砖机器会停在充电座上等待救援。再补充一个细节App区每次写Flash前把整个页读出来保存在RAM擦除后写错数据还能恢复。不过Flash页有1KBRAM比较紧张我实际用的是先把新数据全部收完并存到外部Flash或者一次性缓存然后连续擦写。这个策略取决于内存资源。最后的经验总结这个项目做完之后我最大的感受是STM32扫地机器人本质上不是“单点技术挑战”而是“系统集成能力考验”。每一个模块单独拿出来都不算难——PWM调速、I2C读陀螺仪、UART收数据、ADC采样——但要让它们在RTOS里和谐共处、在电池供电下稳定运行、在升级掉电时不死机这些交叉问题才是真正耗时间的地方。从开发顺序上我非常推荐“由简到繁、逐层叠加”的路径先裸机点灯电机转起来再加编码器闭环然后传感器逐个接入等所有裸机逻辑跑通再迁移到FreeRTOS最后才做IAP。我见过太多人一上来就FreeRTOS全模块结果遇到问题根本分不清是硬件还是软件、是驱动还是调度。如果你准备做类似项目还有一个建议给每个子系统配一个调试开关可以随时打印当前状态。我在最终代码里用了一个DEBUG_LEVEL宏不同级别输出不同模块的日志比如DEBUG_LEVEL1只输出错误DEBUG_LEVEL2输出电机状态DEBUG_LEVEL3输出传感器数据。这个习惯在调试IAP和陀螺仪漂移问题时帮了大忙——没有日志你连问题在哪都无法定位。本文还有配套的精品资源点击获取
返回列表