4、软件架构设计:主控程序框架、任务调度策略、实时性要求与中断处理

做电子齿轮同步控制,说白了就是让两个轴像齿轮一样咬合着转。但软件怎么写,很多人一开始就懵了。我当年第一次做这个项目时,上来就写代码,结果跑起来各种抖、各种丢脉冲。后来才明白——架构没想好,后面全是坑

这一节,我们就聊聊主控程序该怎么搭架子。嗯,这里要注意,不是让你背框架,而是理解为什么这么搭。

4.1 主控程序框架:别让代码变成一团乱麻

我个人习惯,把主控程序分成三层:

  • 底层驱动层:管硬件,比如编码器读取、PWM输出、IO控制。
  • 中间算法层:管计算,比如电子齿轮比、位置环、速度环。
  • 上层应用层:管逻辑,比如启停、报警、通讯。

为什么要分层?我在项目中遇到过,有人把编码器读取和PID计算写在一个函数里。结果换了个编码器,整个控制逻辑都得重写。你想想看,这多折腾。

核心原则:下层不知道上层存在,上层调用下层接口。这样改硬件不用动算法,改算法不用动逻辑。

下面是我常用的一个框架示意图,用SVG画的,你可以看看整体脉络:

电子齿轮同步控制 — 主控程序框架 应用层 状态机管理 | 通讯协议解析 | 报警处理 | 人机交互 算法层 电子齿轮比计算 | 位置/速度环PID | 前馈补偿 | 插补 驱动层 编码器接口 | PWM生成 | IO控制 | 定时器中断 调用接口 调用接口

你看,这个结构很清晰。上层只管发命令,比如“主轴转100圈”,中间层算好比例,底层去执行。我曾经见过一个项目,因为没分层,调试时改了一个定时器参数,结果通讯也崩了。嗯,那场面,真是欲哭无泪。

4.2 任务调度策略:别让CPU累死

电子齿轮同步,对实时性要求很高。但CPU就一个,任务却一堆。怎么调度?

我建议用时间片轮转 + 优先级抢占的混合策略。说白了,就是:

  • 高实时任务(比如编码器中断、PWM更新)—— 用中断,立即执行。
  • 中等实时任务(比如PID计算、位置同步)—— 放在固定时间片,比如1ms执行一次。
  • 低实时任务(比如通讯、日志、显示)—— 空闲时再跑,别抢时间。

我的经验:把PID计算和编码器读取放在同一个中断里,能减少延迟抖动。但注意,中断里别做浮点运算,否则会拖死系统。我曾经吃过这个亏,后来改成查表法,速度直接翻倍。

下面是一个典型的任务调度表,你可以参考:

任务名称 优先级 执行周期 最大耗时 说明
编码器读取 最高 中断触发 5μs 硬件中断,立即响应
PWM更新 最高 中断触发 3μs 与编码器同步
位置环PID 1ms 50μs 固定时间片
速度环PID 0.5ms 30μs 比位置环快一倍
电子齿轮计算 1ms 20μs 跟随主轴位置
通讯处理 10ms 200μs 空闲时执行
状态显示 100ms 100μs 不要求实时

为什么会这样安排?因为电子齿轮同步的核心是“跟随”。主轴每转一个角度,从轴必须马上跟上。如果延迟超过一个控制周期,就会丢步。所以,编码器读取和PWM更新必须用中断,不能等。

4.3 实时性要求:别让“差不多”毁了精度

做同步控制,最怕的就是“抖动”。我刚开始做时,觉得1ms执行一次就够了。结果一测,主轴匀速转,从轴却在抖。后来用示波器一看,中断响应时间不稳定,有时候0.8ms,有时候1.5ms。这就是实时性没保证。

要保证实时性,你得做到三点:

  1. 中断响应时间确定:关中断的时间不能太长。我一般控制在10μs以内。
  2. 任务执行时间确定:每个任务的最大耗时必须算清楚。别用while循环等,用定时器。
  3. 避免优先级反转:低优先级任务别占用高优先级需要的资源。比如,通讯任务别锁住共享变量不放。

注意:千万别在中断里调用printf或串口打印。我曾经有个同事,为了调试在中断里加了个打印,结果系统直接卡死。中断里只做最必要的事,其他都扔到任务队列里。

另外,我建议用看门狗定时器来监控主循环。如果某个任务超时,看门狗会复位系统。这样至少能保证系统不会死机。嗯,这个习惯我一直保留着。

4.4 中断处理:别让中断变成灾难

中断是实时性的灵魂,但用不好就是灾难。我总结了几条铁律:

  • 中断服务程序要短:只做最核心的事,比如读寄存器、置标志位。计算放外面。
  • 中断优先级要合理:编码器中断优先级最高,定时器中断次之,外部IO中断最低。
  • 中断嵌套要小心:高优先级中断可以打断低优先级,但别让低优先级打断高优先级。
  • 共享数据要保护:中断和主循环共享的变量,用volatile修饰,必要时关中断访问。

下面是一个典型的中断处理代码片段,你可以看看:

// 编码器中断服务程序
void Encoder_IRQHandler(void)
{
    // 1. 读取编码器计数值(硬件寄存器)
    uint32_t cnt = ENC->CNT;
    
    // 2. 计算位置增量
    int32_t delta = cnt - last_cnt;
    last_cnt = cnt;
    
    // 3. 更新位置变量(volatile修饰)
    g_encoder_pos += delta;
    
    // 4. 置位标志位,通知主循环处理
    g_encoder_flag = 1;
    
    // 5. 清除中断标志
    ENC->SR = 0;
}

你看,这个中断里只做了5件事,每件事都是必须的。没有计算PID,没有打印,没有延时。这样就能保证中断响应时间在5μs以内。

避坑指南:我曾经在中断里用了除法运算,结果发现每次中断耗时从5μs变成了20μs。后来改成移位和查表,才恢复正常。记住,中断里尽量用加减法和位运算,别用乘除和浮点。

最后,再说一个容易被忽略的点:中断优先级分组。不同的MCU,优先级分组方式不一样。比如STM32有抢占优先级和子优先级。我建议把所有实时中断设为相同的抢占优先级,然后用子优先级区分。这样能避免复杂的嵌套问题。

好了,这一节的内容就这些。软件架构设计,说白了就是“分层、调度、实时、中断”这八个字。你把这四个点想清楚了,代码怎么写都不会乱。下一节我们聊聊具体的电子齿轮比算法实现,到时候见。


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