这是一个非常重要且常见的需求,因为CODESYS虽然主要使用IEC 61131-3标准语言(如LD, FBD, ST),但也为C/C++提供了强大的支持。

(图片来源网络,侵删)
核心概念:CODESYS C语言 vs 标准 C
你需要明确一个关键点:在CODESYS中运行的C语言,并不是你在PC上用Visual Studio或GCC写的标准C程序。
它是一种嵌入式C语言,运行在CODESYS的Runtime(运行时系统)上,这意味着:
- 没有标准库:你无法使用
printf,scanf,malloc,free等标准C库函数,这些函数依赖于操作系统(如Windows, Linux),而PLC的Runtime是一个精简的、实时的环境。 - 特定的入口点和循环:你的C代码不是从
main()函数开始执行的,它被集成到CODESYS的PLC任务循环中,通过特定的函数(如PROGRAM或FUNCTION_BLOCK)来组织。 - 访问PLC资源:你需要使用CODESYS提供的特定机制来访问I/O变量、全局变量、系统功能等。
- 实时性:代码的执行周期由PLC任务的Cycle Time(循环周期)决定,必须满足实时控制的要求。
如何在CODESYS中编写和运行C代码?
主要有两种方式,适用于不同的场景。
使用CFC(连续功能图) - 最推荐的方式
这是CODESYS官方推荐、最集成、最安全的方式,它允许你将C函数作为“功能块”拖拽到CFC图表中,像使用其他IEC语言块一样连接它们。

(图片来源网络,侵删)
优点:
- 完全集成:C代码与ST, LD等语言无缝协作。
- 可视化:在CFC图表中可以清晰地看到程序的逻辑结构。
- 类型安全:CODESYS会检查C函数的输入/输出参数类型,确保连接正确。
- 易于调试:可以像调试其他IEC程序一样设置断点、监控变量。
步骤:
-
创建C函数:
- 在左侧的POU (Program Organization Unit) 窗口中,右键点击你的应用程序。
- 选择 Add Object -> C Function。
- 给函数命名,
MyCMotorControl。 - 在弹出的编辑器中,你可以用C语言编写函数体。
// MyCMotorControl.c // 函数声明(在CFC中,这个声明会自动生成) // void MyCMotorControl(BOOL bEnable, REAL rSpeed, BOOL *bMotorRunning); // 函数实现 void MyCMotorControl(BOOL bEnable, REAL rSpeed, BOOL *bMotorRunning) { // 这是一个简单的C语言逻辑 static BOOL internalState = FALSE; // static变量可以保持状态 if (bEnable == TRUE) { internalState = TRUE; } else { internalState = FALSE; } // 将结果输出到指针指向的地址 *bMotorRunning = internalState; // 这里可以添加更复杂的算法,例如PID控制(用C实现) // ... } -
在CFC中使用:
(图片来源网络,侵删)- 在你的POU列表中,添加一个 CFC 图表。
- 从左侧的POU目录中,将你刚刚创建的
MyCMotorControl拖拽到CFC图表中。 MyCMotorControl出现在图表中,它有输入(bEnable,rSpeed)和输出(bMotorRunning)接口。- 你可以像连接其他功能块一样,连接PLC的全局变量或其它POU的接口,连接一个
g_bMotorStart按钮到bEnable输入。
-
编译和运行:
像往常一样,将整个应用程序编译并下载到PLC或仿真器中,PLC的每个任务周期都会调用CFC图表中的所有函数。
使用C++ Extensions(C++扩展)
这种方式更接近于传统的嵌入式C/C++开发,提供了更大的灵活性,但风险也更高,因为它绕过了CODESYS的一些安全检查机制。
适用场景:
- 需要访问CODESYS Runtime API(创建任务、读写内存、使用系统定时器等)。
- 需要使用第三方C/C++库。
- 实现非常底层的、高性能的算法。
步骤:
-
创建C++文件:
- 在你的项目中,添加一个 Text File。
- 将其后缀改为
.cpp或.c(MyAdvancedLib.cpp)。 - 在文件中编写你的C/C++代码。
-
编写带有特定入口点的代码:
- 你的代码需要一个明确的入口点,以便CODESYS的Runtime能找到并执行它,通常使用
extern "C"来确保C风格的链接。
// MyAdvancedLib.cpp #include <cmath> // 可以使用一些标准数学库,如果项目配置了 // 定义一个函数,这个函数将被ST代码调用 extern "C" double CalculateDistance(double x1, double y1, double x2, double y2) { double dx = x2 - x1; double dy = y2 - y1; return sqrt(dx * dx + dy * dy); } // 你也可以定义一个可以被PLC主循环调用的函数 // 这个函数需要在ST程序中通过一个特殊的方式(如DUT)来调用 extern "C" void MyBackgroundTask() { // 执行一些后台处理逻辑 // 注意:这个函数不能有阻塞操作,否则会影响PLC实时性 } - 你的代码需要一个明确的入口点,以便CODESYS的Runtime能找到并执行它,通常使用
-
在ST程序中调用:
- 你需要在ST程序中声明这个外部函数,然后才能调用它。
// 在ST程序中 // 声明外部C函数 FUNCTION CalculateDistance : REAL; VAR_INPUT x1, y1, x2, y2 : REAL; END_VAR END_FUNCTION // 调用函数 PROGRAM Main VAR fDistance : REAL; pt1, pt2 : T_Point; // 假设有一个结构体类型 END_VAR pt1.x := 10.0; pt1.y := 20.0; pt2.x := 15.0; pt2.y := 25.0; // 调用C++函数 fDistance := CalculateDistance(pt1.x, pt1.y, pt2.x, pt2.y); // ... 其他逻辑 END_PROGRAM
关键注意事项和最佳实践
-
实时性至上:
- C代码的执行时间必须非常短且可预测,避免使用循环、递归或可能导致长时间阻塞的函数(如文件I/O、网络通信)。
- 如果计算复杂,考虑将其拆分到多个PLC周期中完成。
-
变量访问:
- 全局变量:在C代码中直接使用全局变量名是可行的,但不推荐,因为它破坏了封装性,最好通过函数参数传递。
- I/O变量:通常通过全局变量映射,或者通过CODESYS提供的特定API(在C++ Extensions中)来访问硬件。
-
内存管理:
- 绝对不要使用
malloc和free!PLC的内存管理是静态的,动态分配会导致内存碎片,最终在实时系统中引发灾难性后果。 - 所有需要的变量都应该在栈上(局部变量)或作为全局变量/静态变量声明。
- 绝对不要使用
-
调试:
- CFC方式:调试非常方便,与IEC语言一致。
- C++ Extensions方式:调试比较困难,通常需要使用
printf风格的日志输出(需要CODESYS Runtime支持)或使用硬件调试器,CODESYS的内置调试工具对C++代码的支持有限。
-
何时使用C?
- 现有代码移植:将现有的、成熟的C算法(如滤波器、特定数学库)集成到CODESYS项目中。
- 性能关键部分:当ST语言(尽管已经很快)的性能无法满足某个特定算法的要求时。
- 与硬件交互:在C++ Extensions中,可以通过API更底层地操作硬件。
| 特性 | CFC (C Function) | C++ Extensions |
|---|---|---|
| 集成度 | 极高,完全融入CODESYS生态 | 较低,更像“插件” |
| 安全性 | 高,由CODESYS管理类型和生命周期 | 较低,风险更高,容易出错 |
| 调试 | 非常方便,与标准IDE一致 | 困难,工具支持有限 |
| 灵活性 | 中等,主要用于逻辑实现 | 极高,可访问Runtime API和第三方库 |
| 推荐场景 | 大多数应用,特别是需要与ST/LD混合使用的场景 | 需要极致性能、访问底层硬件或集成现有C/C++库的项目 |
对于大多数CODESYS用户来说,优先选择CFC方式来使用C语言,它提供了最佳的安全性和易用性,只有在CFC无法满足特殊需求时,才应考虑使用C++ Extensions。
