
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏》
❄专栏传送门 :《产品测评专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连
目录
[二、核心设计:两级运行模式(IDLE / RUN)](#二、核心设计:两级运行模式(IDLE / RUN))
[2.1、CTRL_IDLE 模式:低功耗休眠待命](#2.1、CTRL_IDLE 模式:低功耗休眠待命)
[2.2、CTRL_RUN 模式](#2.2、CTRL_RUN 模式)
[三、任务间通信:FreeRTOS 任务通知](#三、任务间通信:FreeRTOS 任务通知)
前言
在上一篇文章中,我们完成了串口通信链路的最后一步 ------ 有效帧解析,成功将上位机下发的字节流,转换成了可以被业务层使用的CmdVel速度指令。
而在整个机器人底盘系统中,通信任务只负责 "接收指令",真正让底盘动起来的,是这篇文章的主角 ------ControlTask控制任务。它是整个系统的 "执行者",负责将抽象的速度指令,转换成电机的实际 PWM 输出,并构建起一套兼顾安全、实时性和效率的闭环控制逻辑。
一、控制任务总体概述
控制任务(ControlTask)是底盘控制的 "执行核心",它的职责非常清晰:
接收指令 :响应CommTask的通知,获取最新的速度指令CmdVel。
运动解算 :根据底盘运动学模型,将线速度 / 角速度转换成左右轮的目标速度。
闭环控制 :通过 PID 算法,计算出合适的 PWM 占空比,驱动电机运转。
安全兜底:当指令超时或通信中断时,自动触发刹车逻辑,保证系统安全。
睡(IDLE):没指令时,车不动,任务睡觉,省电。
醒(通知):上位机发指令,CommTask 唤醒 ControlTask。
算(RUN):
拿指令 -> 算左右轮速度 -> PID 算占空比 -> 输出 PWM。
回到睡:
你停止发指令了(指令超时),或者你发了停止指令。
ControlTask 检测到要停了,就回到步骤①,输出 0 并睡觉。
为了兼顾系统安全性、实时性和资源利用率,我们没有采用固定周期的裸循环设计,而是设计了两级运行模式,实现了从 "休眠待命" 到 "全速运转" 的平滑切换。
二、核心设计:两级运行模式(IDLE / RUN)
两级运行模式设计即该任务在逻辑上化为空闲模式(CTRL_IDLE)和运行模式两种状态。
ControlTask 的核心设计思想是:只有在确实存在运动指令时,才执行闭环控制;在无指令或车辆已停止时,彻底退出控制循环,进入低功耗的阻塞等待状态。
2.1、CTRL_IDLE 模式:低功耗休眠待命
当系统认为当前不存在有效速度指令,或车辆已完成刹停并处于稳定状态时,ControlTask 会进入CTRL_IDLE模式。此时,任务的首要目标不是执行控制算法,而是确保底盘安全、降低系统负载。
PWM清零: 主动将左右轮 PWM 输出清零,确保电机不会被误驱动。
PID 复位 :对 PID 控制器进行复位,清除积分项和历史误差,防止下次进入运行模式时因残留状态导致突变输出。
**阻塞等待:**调用xTaskNotifyWait(portMAX_DELAY),无限期等待来自CommTask的 "新指令" 事件通知。在此模式下,CPU 占用率几乎为零,系统处于 "静默待命" 状态。
这个阻塞等待机制其实可以类比linux驱动开发中的阻塞 I/O模式的机制。
调用方都会在无数据/无事件时进入阻塞,不忙等占用 CPU。
FreeRTOS 里阻塞的是任务;Linux 阻塞 IO 里通常是用户线程/进程。

2.2、CTRL_RUN 模式
当通信任务接收到新指令并唤醒 ControlTask 后,任务会从CTRL_IDLE切换到CTRL_RUN模式,开始承担闭环控制职责。此时,任务以固定的10ms周期(100Hz)运行,每个周期内完成一次完整的控制计算。包括指令接收、目标速度生成、PID运算及PWM输出。

三、任务间通信:FreeRTOS 任务通知
ControlTask 与CommTask之间,没有采用轮询或消息队列的方式交互,而是使用了 FreeRTOS 提供的任务通知(Task Notification)机制。这是一种轻量级、低开销的任务间通信手段,核心思想是:让不需要运行的任务主动进入阻塞状态,只在收到通知时才被唤醒,从而避免无谓的 CPU 占用。
工作原理
获取句柄:ControlTask 启动时,通过xTaskGetCurrentTaskHandle()获取自身句柄,并保存为全局变量,该句柄相当于ControlTask在FreeRTOS里的唯一标识。
其他任务可以通过该句柄精准向ControlTask发送通知,供CommTask使用。
发送通知 :当CommTask解析到新指令时,调用xTaskNotify(g_controlTaskHandle, CTRL_NOTIF_NEW_CMD, eSetBits),向 ControlTask 发送事件通知。
ControlTask 在CTRL_IDLE模式下,通过xTaskNotifyWait(portMAX_DELAY)无限期等待通知;在CTRL_RUN模式下,则使用非阻塞方式快速检查是否有新指令。
任务通知完成一次从 "事件触发" 到 "状态切换" 的完整过程实现了控制任务从休眠到运行的平滑过渡过程如下:
当 ControlTask 在阻塞状态下收到任务通知后,会读取通知值并判断其中是否包含 "新速度指令" 事件标志。如果检测到该事件,控制任务会立即从通信模块中获取最新的速度指令,并切换自身运行模式为 CTRL_RUN,开始进入周期性的闭环控制流程。
cpp
uint32_t notify_val = 0;
// 非阻塞方式检查是否有新通知
if (xTaskNotifyWait(0x00, 0xFFFFFFFF, ¬ify_val, 0) == pdTRUE)
{
if (notify_val & CTRL_NOTIF_NEW_CMD)
{
// 收到新指令,更新目标速度
ref_cmd = Comm_GetLatestCmdVel();
last_cmd_tick = xTaskGetTickCount();
}
}
四、完整工作流程(时序图解析)

本图展示了 控制任务(ControlTask)与通信任务(CommTask)的完整交互时序。
初始状态: ControlTask 处于 CTRL_IDLE 模式,通过 xTaskNotifyWait 无限期阻塞等待,此时电机输出刹车 PWM,系统功耗极低。
事件触发: 当 CommTask 解析到上位机新指令并发送通知后,FreeRTOS 内核唤醒 ControlTask。
模式切换: ControlTask 立即读取最新指令(ref_cmd),切换至 CTRL_RUN 模式,开始以 10ms 周期执行闭环控制。
**实时运行:**在运行周期内,任务非阻塞检查通知,执行指令超时保护、运动学解算、PID 计算及 PWM 输出,实现底盘的平滑驱动。
总结
ControlTask 作为机器人底盘的核心执行单元,通过两级运行模式(IDLE 休眠待命 / RUN 闭环控制)实现了低功耗与高性能的兼顾,依托 FreeRTOS 任务通知完成与通信任务的高效解耦,全程做到无指令不占用 CPU、有指令快速响应、异常时自动刹车。