第4章:实时系统概念

各位做运动控制的朋友,咱们今天聊聊实时系统。说实话,我刚入行那会儿,觉得「实时」就是「快」。后来被现实狠狠教育了一回——有一次调试伺服驱动器,位置指令晚到了2毫秒,结果工件直接撞上了限位块。嗯,从那以后我才真正明白:实时不是快,而是「准时」。

4.1 实时系统的定义

实时系统,说白了就是:系统必须在规定的时间内完成规定的任务。这个「规定时间」不是越快越好,而是必须卡死。

我习惯用一个比喻来解释:你去食堂打饭,厨师说「5分钟做好」。如果第6分钟才端出来,你虽然还能吃,但已经耽误了下一节课——这叫软实时。如果这是心脏除颤器,说好5秒放电,第6秒才放,人可能就没了——这叫硬实时。

实时系统的核心三要素:

  • 确定性:任务执行时间可预测,不能忽快忽慢
  • 及时性:必须在截止时间前完成
  • 可靠性:系统不能因为超时而崩溃(至少不能乱来)

在运动控制里,实时性有多重要?你想想看,一个1000线编码器,电机转速3000rpm,每转一圈要处理4000个脉冲。算下来,每两个脉冲之间只有5微秒。如果系统不能在5微秒内完成位置计算和指令输出,电机就会抖动、丢步。

4.2 硬实时与软实时

这两个概念,我当年背得滚瓜烂熟,但真正理解是在一次项目事故之后。

特性 硬实时 软实时
截止时间错过后果 系统崩溃或灾难 性能下降,但可接受
典型应用 飞行控制、心脏起搏器 视频播放、数据采集
运动控制场景 伺服位置环、CNC插补 IO扫描、状态监控
设计原则 最坏情况分析 平均性能优化

举个例子。在伺服驱动器中,电流环是硬实时——通常要求10-50微秒内完成。如果超时,电机电流失控,轻则过流报警,重则烧毁驱动器。位置环可以稍微宽松,100-500微秒,但错过周期会导致轨迹偏差。通讯任务比如EtherCAT从站,1毫秒内完成就算合格,偶尔丢一帧还能重传——这是软实时。

我曾经踩过的坑: 在设计一个多轴同步系统时,我把所有任务都按硬实时处理,结果CPU负载飙到95%,频繁触发看门狗。后来分析发现,状态上报任务完全可以做成软实时,把CPU资源让给位置环。调整后系统稳如老狗。

4.3 RTOS简介

裸机编程做运动控制,就像用手摇计算器做微积分——能算,但累死人。RTOS(实时操作系统)就是来救场的。

我个人常用的两个RTOS:FreeRTOS和RT-Thread。说说我的感受。

4.3.1 FreeRTOS

FreeRTOS是业界标杆,轻量、稳定、文档全。我最早接触它是在一个STM32F4的项目上,跑4轴步进电机控制。

// FreeRTOS 创建实时任务示例
void vMotorControlTask(void *pvParameters)
{
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xFrequency = pdMS_TO_TICKS(1); // 1ms周期
    
    for(;;)
    {
        // 等待到下一个周期(精确时间点)
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
        
        // 执行位置环计算
        vUpdatePositionLoop();
        
        // 输出PWM
        vOutputPWM();
    }
}

注意这里用了vTaskDelayUntil而不是vTaskDelay。为什么?vTaskDelay是相对延时,如果任务执行时间波动,周期会漂移。vTaskDelayUntil是绝对延时,能保证任务在固定的时间点唤醒。做运动控制,必须用绝对延时。

4.3.2 RT-Thread

RT-Thread是国内的开源RTOS,生态更丰富。我最近在用它做EtherCAT主站,感觉比FreeRTOS方便的地方是:

  • 设备驱动框架:SPI、I2C、CAN等外设都有现成驱动,不用自己造轮子
  • FinSH控制台:调试时可以直接敲命令查看任务状态、内存使用
  • 组件丰富:文件系统、网络协议栈、甚至还有POSIX接口
// RT-Thread 创建实时任务
static void motor_control_entry(void *parameter)
{
    rt_uint32_t period = 1000; // 1000us = 1ms
    
    while(1)
    {
        // 获取当前时间戳
        rt_tick_t start = rt_tick_get();
        
        // 执行控制算法
        motor_pid_calculate();
        motor_pwm_output();
        
        // 计算耗时,调整睡眠时间
        rt_tick_t elapsed = rt_tick_get() - start;
        if(elapsed < period)
        {
            rt_thread_mdelay(period - elapsed);
        }
    }
}

我的建议: 如果项目简单、资源紧张,选FreeRTOS。如果项目复杂、需要丰富组件,选RT-Thread。别纠结,两个都学,一通百通。

4.4 任务调度策略

调度策略决定了「哪个任务先跑」。在运动控制中,选错调度策略,后果就是电机抖动、轨迹跑偏。

4.4.1 优先级抢占式调度

这是RTOS最常用的方式。每个任务有个优先级,高优先级任务可以打断低优先级任务。

我习惯这样分配优先级:

  • 最高优先级:电流环任务(10-50us周期)
  • 次高优先级:位置环任务(100-500us周期)
  • 中等优先级:通讯任务(EtherCAT、CANopen)
  • 低优先级:状态监控、日志、人机界面

这里有个坑:优先级反转。低优先级任务拿了锁,高优先级任务等锁,结果中优先级任务抢跑,高优先级反而被阻塞。我曾经在调试时发现位置环偶尔抖动,查了两天才发现是共享数据没加互斥锁,导致优先级反转。

4.4.2 时间片轮转调度

同优先级的任务,轮流分配CPU时间。适合那些没有严格截止时间的任务,比如状态显示、参数配置。

在运动控制中,我很少用纯时间片轮转。因为控制任务必须抢占,不能等别人用完时间片。

4.4.3 合作式调度

任务主动让出CPU。这种调度在运动控制中基本不用——万一哪个任务忘了让出CPU,整个系统就卡死了。

实战经验总结:

  • 运动控制任务必须用优先级抢占式调度
  • 高优先级任务要短小精悍,别在里面做复杂计算
  • 共享数据用互斥锁或关中断保护,但关中断时间不能超过10us
  • 任务执行时间监控,一旦超时立刻报警

4.5 知识体系总览

下面这张图是我自己整理的实时系统知识结构,画了张SVG方便大家理解。

实时系统知识体系 实时系统 定义:在规定时间内完成规定任务 硬实时 vs 软实时 RTOS:FreeRTOS / RT-Thread 任务调度策略 优先级抢占式 时间片轮转 合作式调度 运动控制中推荐:优先级抢占式 + 硬实时任务

这张图把本章的核心内容串起来了。从上到下,先搞清楚实时系统的定义,再区分硬实时和软实时,然后选择合适的RTOS,最后配置正确的调度策略。每一步都踩过坑,才敢说「我懂了」。

好了,实时系统的概念就聊到这儿。记住一句话:实时不是快,是准时。下次调试运动控制代码时,多想想你的任务能不能在截止时间前完成,而不是一味追求CPU跑多快。


公众号:蓝海资料掘金营,微信deep3321