第2章:软件架构总览——分层架构思想、模块划分原则与实时性要求
各位好,我是老张。今天咱们聊聊光伏逆变器软件架构的顶层设计。说实话,我见过太多项目一开始就埋头写代码,结果后期改得痛不欲生。架构这东西,说白了就是给软件搭骨架,骨架歪了,后面怎么填肉都别扭。
我个人习惯,拿到一个逆变器项目,先花一周时间把架构想清楚。这一章,我就把这些年积累的分层思路、模块划分的坑、以及实时性怎么保证,掰开了讲给你听。
2.1 为什么必须分层?
你想想看,一个光伏逆变器里,有MPPT算法、有电网并网逻辑、有孤岛保护、有通讯协议栈、还有底层PWM驱动。如果全揉在一起,改一个地方可能牵动全局。我早期一个项目就吃过这个亏——改了个通讯协议,结果把MPPT的采样时序搞乱了,现场炸了好几台机器。
分层架构的核心思想就一句话:上层只管业务逻辑,下层只管硬件操作,中间层负责协调。这样每一层都能独立演进,互不干扰。
分层的好处:
- 可移植性:换MCU平台,只改驱动层,应用层代码几乎不用动
- 可测试性:每一层都可以单独写单元测试,不用搭整个硬件环境
- 可维护性:新人接手,只看某一层的代码就能快速理解
下面这张图,是我常用的三层架构,画出来给你看:
2.2 模块划分原则——别让代码变成一锅粥
模块怎么切?我总结了三句话:高内聚、低耦合、单一职责。听起来像教科书?嗯,但实际做起来很多人就忘了。
原则一:高内聚
一个模块只做一件事,并且把它做好。比如MPPT模块,就只管最大功率点追踪。别把通讯协议解析也塞进去。我曾经接手过一个项目,MPPT模块里居然还处理了WiFi配网逻辑——你说这找谁说理去?
原则二:低耦合
模块之间通过接口通信,别直接访问对方的全局变量。我习惯的做法是:每个模块暴露一个.h头文件,里面只放函数声明和结构体定义,所有内部实现都藏在.c文件里。这样别人想用你的模块,看头文件就够了。
原则三:单一职责
一个函数只做一件事。比如「读取ADC值并计算功率」这个函数,就应该拆成两个:adc_read() 和 power_calc()。为什么?因为测试的时候,你可以单独测ADC读取准不准,再单独测功率计算对不对。
我的小技巧:
模块划分时,先画一个「模块依赖图」。箭头从依赖方指向被依赖方。如果发现箭头交叉成蜘蛛网,说明耦合太严重,赶紧重构。我一般要求依赖图是「树状结构」或者「有向无环图」。
2.3 实时性要求——逆变器是个急性子
光伏逆变器对实时性的要求,比普通嵌入式系统高得多。为什么?因为电网频率是50Hz,一个周期才20ms。你想想看,如果控制环路延迟超过1ms,波形就可能畸变,严重了直接炸机。
我按实时性要求,把任务分成三个等级:
| 等级 | 响应时间 | 典型任务 | 实现方式 |
|---|---|---|---|
| 硬实时 | < 100μs | PWM占空比更新、过流保护、过压保护 | 硬件中断 + 最高优先级任务 |
| 软实时 | 1ms ~ 10ms | MPPT算法、锁相环(PLL)、电压电流环控制 | 定时器中断 + 中等优先级任务 |
| 非实时 | > 100ms | 通讯处理、日志记录、参数保存 | 后台任务 / 空闲任务 |
硬实时任务,说白了就是「晚一微秒就出事」。比如过流保护,电流一旦超过阈值,必须在几个微秒内关断PWM,否则功率管就烧了。我一般把这些任务放在中断服务函数里,并且中断优先级设到最高。
软实时任务,允许偶尔抖动,但不能持续超时。比如MPPT算法,如果某次计算晚了2ms,可能效率下降一点,但不会炸机。不过如果连续几次都超时,那功率曲线就会乱跳。我习惯用RTOS的定时器任务来跑这些,优先级设中等。
非实时任务,晚一点没关系。比如通过Modbus上报数据,晚个几百毫秒用户根本感觉不到。这些任务我放在最低优先级,有空就跑,没空就等。
避坑指南:
我曾经在一个项目里,把通讯任务和MPPT任务放在同一个优先级。结果通讯量大时,MPPT被频繁抢占,导致MPPT效率下降了5%。后来我把通讯任务降了一级,问题立刻解决。记住:实时性要求高的任务,优先级一定要高,而且不能被低优先级任务阻塞。
2.4 一个实际案例:10kW三相逆变器的任务分配
拿我去年做的一个10kW三相并网逆变器举例,任务分配是这样的:
// 任务优先级定义(数值越小优先级越高)
#define PRIO_ISR_FAULT 0 // 硬件故障保护(硬实时)
#define PRIO_ISR_PWM 1 // PWM更新(硬实时)
#define PRIO_TASK_CTRL 2 // 电压电流环控制(软实时,1ms周期)
#define PRIO_TASK_MPPT 3 // MPPT算法(软实时,10ms周期)
#define PRIO_TASK_PLL 3 // 锁相环(软实时,5ms周期)
#define PRIO_TASK_COMM 4 // 通讯处理(非实时)
#define PRIO_TASK_LOG 5 // 日志记录(非实时)
#define PRIO_TASK_IDLE 6 // 空闲任务
你看,故障保护优先级最高,直接走硬件中断。PWM更新也走中断,但优先级略低。控制环路用RTOS任务,1ms跑一次。MPPT和PLL共享优先级3,但通过时间片轮转保证都不饿死。通讯和日志就放后台慢慢跑。
这样设计的好处是:即使通讯任务卡住了,控制环路和故障保护依然能正常工作。我测试过,哪怕通讯任务死锁,逆变器依然能稳定并网发电——这就是分层和优先级设计带来的鲁棒性。
2.5 总结一下
这一章我们聊了三件事:
- 分层架构:应用层、中间件层、驱动层,各司其职,互不干扰
- 模块划分:高内聚、低耦合、单一职责,别让代码变成一锅粥
- 实时性要求:硬实时走中断,软实时用RTOS任务,非实时放后台
嗯,架构这东西,说起来简单,做起来全是细节。下一章我们深入聊聊应用层的核心——MPPT算法怎么设计。到时候我会拿一个实际项目的代码,一行一行给你拆解。
公众号:蓝海资料掘金营,微信deep3321