3. 任务划分与优先级设计
任务划分和优先级设计,说白了就是决定「谁先干、谁后干、谁可以打断谁」。我见过太多机器人项目,代码写得挺漂亮,一跑起来就卡顿、抖震,最后查下来,十有八九是任务划分不合理、优先级乱套了。
这一章,咱们就聊聊怎么把任务拆得干净利落,怎么分配优先级才不出乱子,以及怎么避开那些坑——优先级反转和死锁。
3.1 任务分解原则
任务分解不是拍脑袋。我个人的习惯是,先画一张功能框图,把机器人要干的事列出来:传感器采集、数据处理、控制计算、电机输出、通信、日志……然后按下面几个原则去切。
| 原则 | 说明 | 我踩过的坑 |
|---|---|---|
| 单一职责 | 一个任务只做一件事。比如「读取IMU数据」和「融合姿态」必须分开。 | 我曾经把IMU读取和姿态解算放一起,结果IMU驱动一卡,整个姿态都挂了。 |
| 时间解耦 | 不同频率的任务拆开。控制环路1kHz,日志100ms,混在一起谁也跑不好。 | —— |
| 资源隔离 | 共享同一硬件资源(如I2C、SPI)的操作尽量放一个任务里,避免争抢。 | 两个任务同时读写同一个I2C传感器,数据全乱套了。 |
| 可测试性 | 每个任务都能独立跑、独立测。别搞成「不启动整个系统就没法测」。 | —— |
3.2 优先级分配策略
优先级分配,核心就一句话:紧急且重要的给高优先级,不紧急不重要的给低优先级。但实际做起来,没那么简单。
我一般把任务分成三类:
- 硬实时任务:比如电机电流环、急停检测。错过截止时间=系统崩溃。给最高优先级。
- 软实时任务:比如里程计计算、路径规划。偶尔延迟一次还能忍。给中等优先级。
- 非实时任务:比如日志记录、远程调试。晚几秒无所谓。给最低优先级。
你想想看,如果给日志任务一个高优先级,电机控制反而被抢了CPU,那机器人跑起来肯定一抖一抖的。嗯,这就是典型的「优先级错配」。
一个实用的分配流程:
- 列出所有任务,标注周期和截止时间。
- 按截止时间从短到长排序——截止时间越短,优先级越高。
- 检查是否有共享资源(比如同一个传感器),有的话适当调整,避免低优先级任务长时间霸占资源。
- 用速率单调调度(RMS)或最早截止时间优先(EDF)做理论验证。
3.3 避免优先级反转与死锁
这两个问题,是实时系统里的「隐形杀手」。我先说优先级反转。
3.3.1 优先级反转
什么叫优先级反转?简单说:高优先级任务在等低优先级任务释放资源,而低优先级任务又被中等优先级任务抢占了CPU。结果高优先级任务反而被「饿死」了。
我曾经在一个四轮机器人项目里遇到过:电机控制任务(高优先级)要读取IMU数据,但IMU驱动任务(低优先级)正在写数据,被一个日志任务(中等优先级)抢占了CPU。电机控制等了整整200ms——机器人直接撞墙了。
解决办法有三种:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 优先级继承 | 低优先级任务在持有锁时,临时提升到等待该锁的最高优先级任务的优先级。 | RTOS原生支持(如FreeRTOS、RT-Thread) |
| 优先级天花板 | 给每个资源设定一个「天花板优先级」,任何任务访问该资源时,优先级自动提升到天花板。 | 资源少、访问频繁的场景 |
| 关中断/关调度 | 访问共享资源时禁止任务切换。 | 临界区极短(几微秒)的场景 |
3.3.2 死锁
死锁就是两个任务互相等对方释放资源,谁也动不了。典型的场景:任务A持有锁1,等锁2;任务B持有锁2,等锁1。死锁了。
避免死锁,我总结了四条铁律:
- 按固定顺序加锁:所有任务加锁的顺序必须一致。比如先锁A再锁B,不能有的先B后A。
- 使用超时机制:加锁时设置超时时间,超时了就放弃并释放已有锁。
- 尽量少用嵌套锁:能用一个锁解决的问题,别用两个。
- 用无锁数据结构:比如环形缓冲区、原子操作,从根上避免死锁。
3.4 知识体系图
下面这张图,把任务划分和优先级设计的核心逻辑串起来了。你可以把它贴在工位旁边,做设计时对照着看。
3.5 小结
任务划分和优先级设计,是机器人系统开发的「地基」。地基没打好,后面优化再多也白搭。我个人建议,每次新项目启动时,花半天时间把任务分解和优先级分配画清楚,比后期花几天去调bug划算得多。
嗯,这一章就到这里。记住:单一职责、时间解耦、固定顺序加锁、避免嵌套——这四句话记住了,能省你很多调试时间。