CODESYS ST语言编程规范 part 2
3. 软件架构与分层设计规范
3.1 平台级架构原则
3.1.1 架构设计目标
软件架构设计应遵循以下目标:
- 可维护性:代码结构清晰,便于理解和修改
- 可扩展性:架构应支持功能的扩展和升级
- 可复用性:模块化设计,提高代码复用率
- 可靠性:架构设计应保证系统稳定运行
- 性能优化:合理设计架构,优化系统性能
- 规范性:所有架构设计必须遵循第2章的命名规范和编程规范
3.1.2 架构设计原则
- 单一职责原则(SRP):每个模块、功能块应有明确的单一职责
- 开闭原则(OCP):对扩展开放,对修改关闭
- 依赖倒置原则(DIP):高层模块不应依赖低层模块,都应依赖抽象
- 接口隔离原则(ISP):使用多个专用接口,而非单一通用接口
- 最小知识原则(LKP):模块间应尽量减少相互了解
3.1.3 架构分层原则
系统架构应遵循分层设计原则:
- 层次清晰:各层职责明确,边界清晰
- 单向依赖:上层可以依赖下层,下层不应依赖上层
- 接口抽象:层间通过接口交互,降低耦合
- 标准化:各层遵循统一的命名规范和编程规范
3.2 分层架构设计
3.2.1 标准分层架构
CODESYS ST项目建议采用以下分层架构:
┌─────────────────────────────────────┐ │ 应用层(Application Layer) │ │ - 业务逻辑实现 │ │ - 程序(PROGRAM) │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 服务层(Service Layer) │ │ - 功能块(FUNCTION_BLOCK) │ │ - 业务服务封装 │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 接口层(Interface Layer) │ │ - 接口定义(INTERFACE) │ │ - 抽象层 │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 数据访问层(Data Access Layer) │ │ - 数据读写功能块 │ │ - 数据管理 │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 设备驱动层(Device Driver Layer) │ │ - I/O访问 │ │ - 硬件抽象 │ └─────────────────────────────────────┘3.2.2 应用层设计规范
应用层负责业务逻辑的实现,主要包含程序(PROGRAM)单元。
设计原则:
- 程序职责:每个程序应负责一个明确的业务功能模块
- 程序命名:使用
PRG_前缀,遵循第2.1.3节命名规范 - 程序结构:程序应调用服务层功能块,不应直接访问设备驱动层
- 状态管理:程序负责系统级状态管理
应用层示例:
PROGRAM PRG_MainControl VAR // 服务层功能块实例(遵循第2章命名规范,使用v_fb前缀) v_fbMotorController : FB_MotorController; v_fbSafetyMonitor : SAFE_SafetyMonitor; v_fbDataLogger : FB_DataLogger; // 应用层状态变量(遵循第2章命名规范) v_eSystemState : E_SystemState; v_bSystemEnable : BOOL; END_VAR // 应用层逻辑:协调各服务模块 IF v_bSystemEnable THEN v_fbMotorController( i_bEnable := TRUE, i_rTargetSpeed := 1000.0, o_bStatus => v_bMotorStatus, o_rActualSpeed => v_rMotorSpeed ); END_IF END_PROGRAM3.2.3 服务层设计规范
服务层负责封装业务逻辑,主要包含功能块(FUNCTION_BLOCK)。
设计原则:
- 功能封装:每个功能块封装一个完整的业务功能
- 功能块命名:使用
FB_前缀,遵循第2.1.2节命名规范 - 接口设计:功能块接口应清晰,参数合理
- 状态保持:功能块可保持状态,适合控制逻辑
服务层示例:
FUNCTION_BLOCK FB_MotorController VAR_INPUT i_bEnable : BOOL; // 使能 i_rTargetSpeed : REAL; // 目标速度 END_VAR VAR_OUTPUT o_bStatus : BOOL; // 状态 o_rActualSpeed : REAL; // 实际速度 END_VAR VAR v_rCurrentSpeed : REAL; // 当前速度 v_eState : E_MotorState; // 状态机 END_VAR // 服务层逻辑:实现电机控制业务逻辑 // ... END_FUNCTION_BLOCK3.2.4 接口层设计规范
接口层定义抽象接口,降低模块间耦合。
设计原则:
- 接口定义:使用INTERFACE定义接口
- 接口命名:使用
I_前缀,遵循第2.3.5节规范 - 接口抽象:接口应抽象通用操作,不包含具体实现
- 接口实现:功能块或类通过IMPLEMENTS实现接口
接口层示例:
INTERFACE I_IControllable METHOD Start : BOOL METHOD Stop : BOOL METHOD GetStatus : ST_DeviceStatus END_INTERFACE FUNCTION_BLOCK FB_MotorController IMPLEMENTS I_IControllable // 实现接口方法 METHOD Start : BOOL // 启动逻辑 Start := TRUE; END_METHOD END_FUNCTION_BLOCK3.2.5 数据访问层设计规范
数据访问层负责数据读写和管理。
设计原则:
- 数据封装:数据访问功能块封装数据操作
- 数据抽象:提供统一的数据访问接口
- 数据安全:确保数据访问的安全性和一致性
3.2.6 设备驱动层设计规范
设备驱动层负责硬件I/O访问和硬件抽象。
设计原则:
- 硬件抽象:封装硬件细节,提供统一接口
- 驱动命名:使用
DRV_前缀标识驱动功能块 - 错误处理:完善的错误检测和处理机制
- 资源管理:合理管理硬件资源
3.3 模块化与组件化设计
3.3.1 模块化设计原则
模块化设计应遵循以下原则:
- 高内聚:模块内部元素紧密相关,功能集中
- 低耦合:模块间依赖关系简单,接口清晰
- 接口稳定:模块接口应保持稳定,便于复用
- 独立测试:模块应可独立测试
3.3.2 模块划分规范
模块划分应遵循以下规范:
- 功能模块:按业务功能划分模块
- 模块命名:模块使用命名空间组织,命名清晰
- 模块接口:模块通过接口暴露功能,隐藏实现
- 模块文档:每个模块应提供完整文档
3.3.3 组件化设计规范
组件是比模块更细粒度的复用单元。
组件设计原则:
- 组件独立性:组件应相对独立,可单独使用
- 组件接口:组件通过标准接口交互
- 组件复用:组件设计应考虑复用场景
- 组件测试:组件应可独立测试和验证
3.4 代码复用与模块化规范
3.4.1 模块复用原则
模块复用应遵循以下原则:
- 通用性设计:模块设计应考虑通用场景,而非特定项目
- 参数化设计:通过参数配置适应不同应用场景
- 接口标准化:模块接口遵循统一标准
- 文档完整性:模块应提供完整的使用文档
3.4.2 库开发与组织规范
库组织结构:
LibraryName/ ├── Libraries/ // 库文件 │ ├── Functions/ // 函数库 │ ├── FunctionBlocks/ // 功能块库 │ ├── Types/ // 类型定义库 │ └── Programs/ // 程序库 ├── Documentation/ // 文档 └── Examples/ // 示例代码库命名规范:
- 库命名:使用描述性名称,清晰表达库的功能
- 库版本:使用语义化版本控制(Semantic Versioning)
- 库文档:提供完整的API文档和使用示例
库开发示例:
LIBRARY MotionControl FUNCTION_BLOCK FB_PID VAR_INPUT i_rSetpoint : REAL; i_rActualValue : REAL; END_VAR VAR_OUTPUT o_rOutput : REAL; END_VAR // PID控制算法实现 END_FUNCTION_BLOCK END_LIBRARY3.4.3 模块接口设计规范
模块接口设计应遵循以下规范:
- 接口清晰:接口定义清晰,参数明确
- 接口稳定:接口应保持稳定,避免频繁变更
- 接口文档:接口应提供完整文档
- 接口版本:接口变更应进行版本管理
接口设计示例:
FUNCTION_BLOCK FB_DataProcessor VAR_INPUT // 输入参数(遵循第2章命名规范) i_stInputData : ST_InputData; i_bEnable : BOOL; END_VAR VAR_OUTPUT // 输出参数(遵循第2章命名规范) o_stOutputData : ST_OutputData; o_bSuccess : BOOL; o_iErrorCode : INT; END_VAR // 处理逻辑 // ... END_FUNCTION_BLOCK3.4.4 模块版本兼容性管理
模块版本管理应遵循以下规范:
- 版本号规则:使用语义化版本控制(主版本号.次版本号.修订号)
- 向后兼容:次版本号和修订号变更应保持向后兼容
- 版本文档:记录版本变更内容
- 迁移指南:主版本号变更时提供迁移指南
3.4.5 跨项目代码复用策略
跨项目代码复用应遵循以下策略:
- 通用库开发:将通用功能提取为独立库
- 库管理:使用统一的库管理系统
- 库测试:库应经过充分测试
- 库文档:提供完整的库文档和使用指南
3.5 接口设计规范
3.5.1 接口设计原则
接口设计应遵循以下原则:
- 最小接口原则:接口应包含最少的必要方法
- 接口隔离:使用多个专用接口,而非单一通用接口
- 接口稳定:接口定义应保持稳定
- 接口文档:接口应提供完整文档
3.5.2 功能块接口设计
功能块接口设计规范:
- 输入参数:使用
i_前缀,遵循第2.1.1节规范 - 输出参数:使用
o_前缀,遵循第2.1.1节规范 - 参数顺序:输入参数在前,输出参数在后
- 参数命名:参数命名应清晰表达含义
3.5.3 接口版本管理
接口版本管理规范:
- 版本标识:接口应标识版本号
- 兼容性:新版本应尽可能保持向后兼容
- 废弃策略:废弃接口应提供迁移路径
- 文档更新:接口变更应及时更新文档
3.6 依赖关系管理
3.6.1 依赖关系原则
依赖关系管理应遵循以下原则:
- 单向依赖:依赖关系应为单向,避免循环依赖
- 依赖最小化:尽量减少模块间的依赖关系
- 依赖明确:依赖关系应明确声明
- 依赖稳定:依赖的模块应保持稳定
3.6.2 依赖关系层次
依赖关系应遵循清晰的层次结构:
- 应用层→服务层→接口层→数据访问层→设备驱动层
- 同层依赖:同层模块间可相互依赖,但应谨慎设计
- 跨层依赖:禁止跨层直接依赖
3.6.3 循环依赖处理
应避免循环依赖,如存在循环依赖,应通过以下方式解决:
- 接口抽象:通过接口抽象打破循环依赖
- 依赖倒置:使用依赖倒置原则
- 模块重构:重新设计模块结构
3.7 架构长期维护与移植策略
3.7.1 架构演进原则
架构演进应遵循以下原则:
- 渐进式演进:架构变更应渐进式进行
- 向后兼容:尽可能保持向后兼容
- 文档同步:架构变更应及时更新文档
- 团队沟通:架构变更应充分沟通
3.7.2 架构移植策略
架构移植应遵循以下策略:
- 抽象层设计:通过抽象层屏蔽平台差异
- 接口标准化:使用标准化接口
- 配置驱动:通过配置适应不同平台
- 测试验证:移植后进行充分测试
3.7.3 架构文档维护
架构文档应持续维护:
- 文档更新:架构变更时及时更新文档
- 文档完整性:确保文档完整准确
- 文档可读性:文档应清晰易懂
- 文档版本管理:文档应与代码版本同步
4. 注释、文档与可读性规范
4.1 代码注释规范
4.1.1 文件头注释
所有POU文件应在文件开头添加文件头注释。
文件头注释格式:
(** * 文件名称:FB_MotorController.st * 功能描述:电机控制器功能块,实现电机的启动、停止、速度控制等功能 * 作者:[作者姓名] * 创建日期:2025-12 * 修改日期:2025-12 * 版本:v1.0 * 依赖模块:FB_PID, ST_MotorStatus * 注意事项:使用时需确保输入参数在有效范围内 *)文件头注释要求:
- 必填项:文件名称、功能描述、作者、创建日期、版本
- 可选项:修改日期、依赖模块、注意事项
- 格式统一:使用统一的注释格式
- 内容准确:注释内容应与代码实际功能一致
4.1.2 模块/功能块注释(需遵循第2章命名规范)
功能块应提供完整的注释说明。
功能块注释格式:
(** * 功能块:FB_MotorController * 功能描述:实现电机的基本控制功能,包括启动、停止、速度控制 * * 输入参数: * i_bEnable : BOOL - 使能信号,TRUE时允许电机运行 * i_rTargetSpeed : REAL - 目标速度,单位rpm,范围0-3000 * * 输出参数: * o_bStatus : BOOL - 运行状态,TRUE表示电机正在运行 * o_rActualSpeed : REAL - 实际速度,单位rpm * * 使用示例: * v_fbMotorController( * i_bEnable := TRUE, * i_rTargetSpeed := 1000.0, * o_bStatus => v_bStatus, * o_rActualSpeed => v_rSpeed * ); *) FUNCTION_BLOCK FB_MotorController VAR_INPUT i_bEnable : BOOL; // 使能信号 i_rTargetSpeed : REAL; // 目标速度 (rpm) END_VAR VAR_OUTPUT o_bStatus : BOOL; // 运行状态 o_rActualSpeed : REAL; // 实际速度 (rpm) END_VAR END_FUNCTION_BLOCK功能块注释要求:
- 功能描述:清晰描述功能块的作用
- 参数说明:详细说明输入输出参数的含义、单位和范围
- 使用示例:提供典型使用示例
- 注意事项:说明使用时的注意事项
4.1.3 函数/方法注释(需遵循第2章命名规范)
函数和方法应提供完整的注释说明。
函数注释格式:
(** * 函数:CalculateAverage * 功能描述:计算数组中元素的平均值 * * 参数: * i_arValues : ARRAY[0..9] OF REAL - 输入数组,包含待计算的值 * i_iCount : INT - 数组元素个数,范围1-10 * * 返回值: * REAL - 平均值,如果输入无效返回0.0 * * 异常情况: * - i_iCount <= 0 或 i_iCount > 10:返回0.0 *) FUNCTION CalculateAverage : REAL VAR_INPUT i_arValues : ARRAY[0..9] OF REAL; i_iCount : INT; END_VAR // 函数实现 END_FUNCTION方法注释格式:
(** * 方法:Start * 功能描述:启动电机,设置目标速度并开始运行 * * 参数: * i_rTargetSpeed : REAL - 目标速度,单位rpm * * 返回值: * BOOL - 启动是否成功,TRUE表示成功,FALSE表示失败 *) METHOD PUBLIC Start : BOOL VAR_INPUT i_rTargetSpeed : REAL; END_VAR // 方法实现 END_METHOD4.1.4 行内注释规范
行内注释用于解释代码的意图和逻辑。
行内注释规则:
- 注释位置:注释放在代码行的右侧,与代码之间至少保留2个空格
- 注释内容:注释应简洁明了,解释"为什么"而非"是什么"
- 注释语言:使用中文注释,保持注释与代码逻辑一致
- 避免冗余:代码本身已清晰的,不需要注释
行内注释示例:
VAR v_rTemperature : REAL; // 当前温度值 (°C) v_bAlarm : BOOL; // 报警标志 v_iCounter : INT := 0; // 计数器,初始化为0 END_VAR // 温度超限检查 IF v_rTemperature > C_MAX_TEMPERATURE THEN v_bAlarm := TRUE; // 设置报警标志 v_iCounter := v_iCounter + 1; // 计数递增 END_IF注释分组:
相关变量可以使用分组注释:
VAR // ===== 温度相关变量 ===== v_rTemperature : REAL; v_rTargetTemp : REAL; // ===== 状态变量 ===== v_bIsRunning : BOOL; v_bHasError : BOOL; // ===== 错误处理 ===== v_iErrorCode : INT; v_sErrorMessage : STRING(80); END_VAR4.1.5 复杂逻辑注释要求
复杂逻辑必须添加详细注释说明。
复杂逻辑注释要求:
- 算法说明:复杂算法应说明算法原理
- 状态机说明:状态机应说明状态转换逻辑
- 条件判断说明:复杂条件判断应说明判断逻辑
- 特殊处理说明:特殊处理应说明原因
复杂逻辑注释示例:
// ===== PID控制算法 ===== // 算法说明: // 1. 计算误差:误差 = 设定值 - 实际值 // 2. 比例项:P = Kp * 误差 // 3. 积分项:I = Ki * 积分累积值 // 4. 微分项:D = Kd * 误差变化率 // 5. 输出 = P + I + D // 计算误差 v_rError := i_rSetpoint - i_rActualValue; // 比例项计算 v_rP := i_rKp * v_rError; // 积分项计算(使用积分累积值避免积分饱和) v_rIntegral := v_rIntegral + v_rError; IF v_rIntegral > C_MAX_INTEGRAL THEN v_rIntegral := C_MAX_INTEGRAL; // 积分限幅 ELSIF v_rIntegral < C_MIN_INTEGRAL THEN v_rIntegral := C_MIN_INTEGRAL; END_IF v_rI := i_rKi * v_rIntegral; // 微分项计算(使用误差变化率) v_rDerivative := v_rError - v_rErrorLast; v_rD := i_rKd * v_rDerivative; v_rErrorLast := v_rError; // 输出计算 o_rOutput := v_rP + v_rI + v_rD;4.1.6 AI生成代码注释要求与检查
AI生成的代码必须包含完整的注释。
AI生成代码注释要求:
- 文件头注释:必须包含文件头注释
- 功能描述注释:所有POU必须包含功能描述注释
- 参数注释:所有参数必须有行内注释说明
- 逻辑注释:复杂逻辑必须有注释说明
AI生成代码注释检查清单:
- 文件头注释是否完整(文件名、功能描述、作者、日期、版本)
- 功能块/函数是否有功能描述注释
- 输入输出参数是否有注释说明
- 复杂逻辑是否有注释说明
- 注释内容是否准确,与代码一致
- 注释格式是否符合规范
- 注释语言是否使用中文
4.2 文档生成规范
4.2.1 文档格式要求
技术文档应遵循统一的格式要求。
文档格式要求:
- 文档结构:使用标准的文档结构(标题、章节、段落)
- 文档格式:使用Markdown或Word格式
- 图表规范:图表清晰,标注完整
- 代码示例:代码示例应完整可运行
4.2.2 技术文档模板
技术文档应使用统一的模板。
功能块技术文档模板:
# FB_MotorController 功能块文档 ## 1. 概述 ### 1.1 功能描述 [功能块的详细功能描述] ### 1.2 版本信息 - 版本:v1.0 - 创建日期:2025-12 - 最后修改:2025-12 - 作者:[作者姓名] ## 2. 接口说明 ### 2.1 输入参数 | 参数名 | 类型 | 说明 | 单位 | 范围 | |--------|------|------|------|------| | i_bEnable | BOOL | 使能信号 | - | TRUE/FALSE | | i_rTargetSpeed | REAL | 目标速度 | rpm | 0-3000 | ### 2.2 输出参数 | 参数名 | 类型 | 说明 | 单位 | 范围 | |--------|------|------|------|------| | o_bStatus | BOOL | 运行状态 | - | TRUE/FALSE | | o_rActualSpeed | REAL | 实际速度 | rpm | 0-3000 | ## 3. 使用示例 [使用示例代码] ## 4. 注意事项 [使用注意事项]4.2.3 API文档生成
API文档应自动生成或手动维护。
API文档要求:
- 完整性:包含所有公共接口的文档
- 准确性:文档内容应与代码一致
- 示例代码:提供完整的使用示例
- 更新及时:代码变更时及时更新文档
4.3 代码可读性规范
4.3.1 代码格式与缩进
代码格式应统一,提高可读性。
代码格式要求:
- 缩进:使用4个空格进行缩进,不使用Tab
- 换行:每行代码长度不超过120个字符
- 空行:使用空行分隔逻辑块
- 对齐:相关代码应对齐
代码格式示例:
FUNCTION_BLOCK FB_MotorController VAR_INPUT i_bEnable : BOOL; // 使能 i_rTargetSpeed : REAL; // 目标速度 END_VAR VAR_OUTPUT o_bStatus : BOOL; // 状态 o_rActualSpeed : REAL; // 实际速度 END_VAR VAR v_rCurrentSpeed : REAL; // 当前速度 END_VAR // 使能处理 IF i_bEnable THEN v_rCurrentSpeed := i_rTargetSpeed; o_bStatus := TRUE; ELSE v_rCurrentSpeed := 0.0; o_bStatus := FALSE; END_IF o_rActualSpeed := v_rCurrentSpeed; END_FUNCTION_BLOCK4.3.2 代码长度与复杂度控制
代码长度和复杂度应控制在合理范围内。
代码长度要求:
- 单个POU长度:不超过500行
- 函数长度:不超过100行
- 方法长度:不超过50行
- 代码块长度:单个代码块不超过50行
复杂度控制:
- 嵌套深度:嵌套深度不超过4层
- 圈复杂度:单个函数的圈复杂度不超过10
- 参数个数:函数参数个数不超过10个
- 变量个数:单个作用域内变量个数不超过20个
4.3.3 变量与函数命名可读性
变量和函数命名应清晰表达意图。
命名可读性要求:
- 描述性命名:使用有意义的名称,避免缩写
- 命名长度:命名长度适中,通常3-30个字符
- 命名一致性:相同概念使用相同的命名
- 命名规范性:遵循第2章命名规范
命名可读性示例:
// ✅ 良好的命名:清晰表达意图 VAR v_rMotorTargetSpeed : REAL; // 电机目标速度 v_rMotorActualSpeed : REAL; // 电机实际速度 v_bMotorRunningStatus : BOOL; // 电机运行状态 END_VAR // ❌ 不好的命名:含义不明确 VAR v_rMTS : REAL; // 含义不明确 v_rMAS : REAL; // 含义不明确 v_bMRS : BOOL; // 含义不明确 END_VAR