4、软件架构设计:主控程序框架、任务调度策略、实时性要求与中断处理
做电子齿轮同步控制,说白了就是让两个轴像齿轮一样咬合着转。但软件怎么写,很多人一开始就懵了。我当年第一次做这个项目时,上来就写代码,结果跑起来各种抖、各种丢脉冲。后来才明白——架构没想好,后面全是坑。
这一节,我们就聊聊主控程序该怎么搭架子。嗯,这里要注意,不是让你背框架,而是理解为什么这么搭。
4.1 主控程序框架:别让代码变成一团乱麻
我个人习惯,把主控程序分成三层:
- 底层驱动层:管硬件,比如编码器读取、PWM输出、IO控制。
- 中间算法层:管计算,比如电子齿轮比、位置环、速度环。
- 上层应用层:管逻辑,比如启停、报警、通讯。
为什么要分层?我在项目中遇到过,有人把编码器读取和PID计算写在一个函数里。结果换了个编码器,整个控制逻辑都得重写。你想想看,这多折腾。
核心原则:下层不知道上层存在,上层调用下层接口。这样改硬件不用动算法,改算法不用动逻辑。
下面是我常用的一个框架示意图,用SVG画的,你可以看看整体脉络:
你看,这个结构很清晰。上层只管发命令,比如“主轴转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。这就是实时性没保证。
要保证实时性,你得做到三点:
- 中断响应时间确定:关中断的时间不能太长。我一般控制在10μs以内。
- 任务执行时间确定:每个任务的最大耗时必须算清楚。别用while循环等,用定时器。
- 避免优先级反转:低优先级任务别占用高优先级需要的资源。比如,通讯任务别锁住共享变量不放。
注意:千万别在中断里调用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