3. FTU软件总体架构设计
FTU(馈线终端单元)的软件不是“把功能堆进一个main循环”就能稳定运行的。它要同时面对毫秒级的保护逻辑、秒级的遥测刷新、分钟级的故障录波上传,还要在电磁干扰强、供电波动大的柱上环境里长期无人值守。因此,软件总体架构设计的核心,是先把“实时性、可靠性、可维护性”三个约束落到分层与任务划分上,再谈具体代码怎么写。
一、嵌入式实时操作系统选型
FTU的软件通常运行在ARM Cortex-M或Cortex-A系列处理器上,选型时先看三个硬指标:中断响应延迟、任务切换时间、以及是否支持优先级抢占。以典型的馈线保护场景为例,过流保护从采样到出口跳闸要求控制在几毫秒内,如果RTOS的任务切换抖动超过100μs,保护定值再准也会失配。因此,选型的第一条原则是:保护相关逻辑必须能运行在最高优先级任务或中断上下文中,且切换时间可预测。
常见候选包括FreeRTOS、RT-Thread、μC/OS-II/III以及VxWorks。对于中小型FTU,FreeRTOS和RT-Thread更常见:前者内核精简、移植成熟,后者自带设备框架和FinSH控制台,便于调试。若FTU需要同时处理IEC 61850 MMS、GOOSE和大量录波数据,且对内存保护有要求,则可考虑带MMU的Cortex-A平台配合Linux+RT补丁,或直接选用VxWorks。选型时建议按以下维度打分:内核尺寸、抢占延迟、优先级数量、是否支持静态内存分配、是否有成熟BSP、以及授权成本。示例评分表如下(数值为教学假设,非实测):
| 候选RTOS | 典型抢占延迟 | 内核ROM | 优先级数 | 适用场景 |
|---|---|---|---|---|
| FreeRTOS | 约10~30μs | 6~10KB | 32 | 中小型FTU,成本敏感 |
| RT-Thread | 约10~40μs | 10~20KB | 256 | 需设备框架与调试终端 |
| μC/OS-III | 约10~20μs | 10~15KB | 256 | 认证要求较高的场合 |
| VxWorks | 约5~15μs | 较大 | 256 | 高端FTU,IEC 61850全栈 |
选型确定后,必须明确一条纪律:所有任务栈、消息队列、信号量在编译期静态分配,禁止在运行中动态malloc。柱上FTU长期运行,内存碎片是隐性故障源。若必须动态申请,应使用RTOS自带的内存池并限定上限。
二、任务划分与优先级设计
任务划分不是按“功能模块”切,而是按实时性等级和数据依赖关系切。FTU中典型任务可归为四类:
- 采样与保护任务:最高优先级,周期1ms或由ADC中断触发,完成交流采样、有效值计算、过流/零序判断,必要时直接驱动出口继电器。
- 通信与协议任务:中高优先级,处理IEC 60870-5-101/104、Modbus或IEC 61850报文,周期10~100ms,不能阻塞保护任务。
- 人机与维护任务:中低优先级,处理本地按键、液晶刷新、参数读写,允许被抢占。
- 后台与录波任务:最低优先级,负责故障录波存储、历史数据上传、文件系统维护,在空闲时运行。
优先级分配遵循“速率单调”思想:周期越短、截止时间越紧,优先级越高。例如采样任务优先级设为最高(如configMAX_PRIORITIES-1),通信任务次之,录波任务最低。任务间通过消息队列传递采样结果和故障事件,避免共享全局变量。下面给出一个典型任务参数示例(教学假设):
任务名 优先级 周期/触发 栈大小 通信方式
SamplingTask 30 1ms定时器 512字 ADC中断→队列
ProtectionTask 29 由采样队列触发 768字 队列接收
CommTask 20 20ms轮询 1024字 队列+信号量
HmiTask 10 100ms 512字 共享参数区+互斥量
RecordTask 5 事件触发 2048字 文件系统+队列
注意:保护任务与采样任务如果合并为一个任务,可减少一次队列拷贝,但会拉长单次执行时间。工程上常把采样中断只做“取数+打时间戳”,把计算和保护判断放在高优先级任务中,兼顾实时性与可维护性。
三、驱动层/协议层/应用层分层原则
分层的目的不是“好看”,而是让换硬件、换规约、改逻辑时互不牵连。FTU软件通常分为四层:硬件驱动层、操作系统适配层、协议与业务层、应用层。
驱动层直接操作寄存器或调用厂商HAL,向上提供统一接口,例如adc_read_samples()、do_set_output()、uart_send()。这一层不允许出现规约解析或业务判断。驱动层的关键是“可替换”:换ADC芯片时只改驱动,不动上层。
协议层负责报文组帧、解析、超时重传和链路管理。IEC 60870-5-104的APCI/ASDU解析、Modbus功能码处理都放在这里。协议层通过回调或消息队列把“遥测值”“遥控命令”交给应用层,不直接操作继电器。
应用层实现FTU的业务逻辑:遥测越限判断、故障检测、遥控出口、定值管理、录波触发。应用层只调用协议层和驱动层提供的接口,不关心报文具体字节。
层间调用方向应单向向下,数据向上通过队列或回调。禁止应用层直接写寄存器,也禁止驱动层调用应用层函数。这样在调试时,可以用模拟驱动替换真实硬件,在PC上跑应用层逻辑。
四、代码组织与工程结构
代码目录应直接反映分层,而不是按“人”或“日期”分。推荐结构如下:
ftu_project/
├── app/ # 应用层:保护、遥测、遥控、录波
│ ├── protection.c
│ ├── telemetry.c
│ └── record.c
├── protocol/ # 协议层:104、Modbus、61850
│ ├── iec104.c
│ └── modbus_slave.c
├── osal/ # OS适配层:任务、队列、定时器封装
│ ├── osal_task.c
│ └── osal_queue.c
├── drivers/ # 驱动层:ADC、DO、UART、ETH
│ ├── adc_drv.c
│ └── do_drv.c
├── config/ # 定值、点表、硬件配置
│ ├── points_table.h
│ └── protection_cfg.h
└── main.c # 仅负责初始化与启动任务
每个模块提供.h接口和.c实现,接口中不暴露其他层的内部结构体。例如应用层只看到protocol_send_telemetry(),看不到104报文的APCI字段。这样在更换通信规约时,应用层代码几乎不动。
最后,架构设计要留出可观测性:每个任务应有运行计数、栈使用峰值、队列溢出计数,通过调试串口或后台报文可读取。这些数据在柱上FTU出现偶发死机或通信中断时,是定位问题的第一手依据。架构不是一次画完就固定,而是在调试中逐步收敛——先保证保护实时性,再优化通信吞吐,最后完善录波与维护接口。