简介:面向PLC工程师与自动化学习者,这份培训课件完整讲解了信捷PLC中如何引入C语言编程,重点解决梯形图在复杂数学运算、运算效率及代码保护方面的不足,帮助初学者理解为何在PLC中使用C语言,并掌握从创建、调用到导出导入的完整流程。课件共1个pptx文件,压缩包8.18MB,内容组织清晰,涵盖信捷C语言界面操作、基本语法、变量与数据类型、宏定义、变址,以及C语言与梯形图在语法符号、数据类型、数学运算、类型转换、三角函数和常用逻辑指令等方面的详细对比。已有1173人学习。课件从实际案例出发,先讲解C语言创建与调用、编辑工具使用,再进阶到脉冲当量计算、函数、数组、结构体、结构体数组和指针等关键主题,同时给出typedef数据类型别名、标准库函数表,并对比梯形图与C语言在加法、减法、乘法、除法、赋值和等于指令上的写法差异。无论是想加快扫描周期、简化复杂数学运算,还是希望保护代码知识产权,这套资料都能提供直接可用的学习路径,适合按PPT示例先套用、再理解,逐步掌握C语言在信捷PLC中的高效用法。
1. 信捷PLC C语言:这份培训资料到底在教你什么
做信捷PLC的工程师,手里多半都有几本从某宝打印的培训手册,但真到用类C语言写逻辑时,能一次编译通过的人不多。信捷PLC的C语言编程不是指KEIL里写单片机,而是指XDH、XDC系列在编程软件里用类C文本语言组织程序逻辑,官方培训资料一般会把这种风格叫做“C语言编程”或“ST语言编程”。新手最容易被绕晕的点在于:它长相像C,但不是标准C,没有main函数,没有指针,输入输出靠地址映射。
这份培训资料解决的是三个具体问题:一是把梯形图的触点、线圈、定时器翻译成C语言怎么写;二是信捷类C在数据类型、函数块、中断上跟标准C有什么不同;三是混合编程时C程序块和梯形图程序块怎么划分边界。适合的读者是已经能用梯形图写设备程序、想转文本编程的电气工程师,也适合在学校学过C语言、但没碰过PLC的自动化专业学生。看完能达成的目标是:把一台XDH机型上用梯形图写的启停、定时、计数逻辑,用类C重写并在真机上跑通。
先说一个反直觉的结论:信捷的“C语言”程序块,本质是经过封装的结构化文本,它没有预处理器、没有动态内存、没有递归,你在C语言课上学的很多技巧在这里用不上。越早接受它是“长得像C的PLC语言”,越不容易走弯路。下面每个章节都对应一份实操笔记,能够直接照着写到你的工程里。
2. 信捷类C编程的语法基座:先理解POU、变量区与执行模型
2.1 三个必须接受的设定:POU、扫描周期、地址映射
信捷类C程序的基本单位是POU(程序组织单元),对应编程软件左侧的“程序”节点。每个POU包含变量声明区和程序体两部分,变量声明区声明局部变量,程序体写执行逻辑。程序体没有main函数入口,系统每个扫描周期自动调用一次POU里的代码。
// 伪代码示意:一个POU的结构 VAR_GLOBAL X0 AT %IX0.0 : BOOL; // 输入点,映射到物理端子 Y0 AT %QX0.0 : BOOL; // 输出点 END_VAR VAR start_btn : BOOL; run_state : BOOL; END_VAR start_btn := X0; // 读输入 IF start_btn THEN run_state := TRUE; END_IF; Y0 := run_state; // 写输出说明:X0、Y0这种写法是地址映射,AT关键字把变量绑定到物理I/O。关键点是程序体不带返回值、不带参数,所有数据交换都通过全局变量和地址映射完成。信捷PLC的扫描周期分三个阶段:读输入、执行程序、写输出。程序体里对X0的读取发生在“读输入”阶段之后,对Y0的赋值要等到“写输出”阶段才真正作用到物理端子。这意味着程序体内部用了X0之后想立刻看到Y0变化是不可能的,一个扫描周期内你读到的X0在整个周期内保持不变。
参数上需要注意两点:一是全局变量区可以跨POU共享,局部变量区只在本POU内有效;二是地址映射里%IX0.0和%QX0.0分别是输入和输出的按位寻址格式,初学阶段可以直接用X0、Y0这种别名,系统会自动绑定地址。
2.2 数据类型和标准C的差异:没有指针,没有char[]
信捷类C的数据类型比标准C少很多,常用的是BOOL、INT、DINT、REAL、STRING。没有指针类型,没有结构体,数组虽然支持但有长度限制,字符串操作函数也不全。你在标准C里习惯的char *p = str;这种写法在这里直接不存在,处理字符串要么用STRING类型自带的函数,要么拆成字节数组操作。
// 错误示范:标准C写法在信捷类C里编译不过 // char buf[10]; // buf[0] = 'A'; // 正确做法:用STRING类型 VAR msg : STRING(32); END_VAR msg := 'Hello PLC'; // 之后用系统提供的字符串函数处理msg,比如 LEN(msg)、MID(msg, 2, 3)说明:STRING(32)表示最大长度32字节,赋值用单引号而不是双引号。信捷类C字符串函数命名接近VB风格,这也是很多纯C程序员不习惯的地方。数组方面,信捷类C支持一维和二维数组,下标从0开始,但数组只能做局部变量或全局变量,不能作为函数块的输入输出参数。实际工程里我会把数组用在配方数据暂存上,而不会拿它做复杂数据结构。INT是16位,DINT是32位,REAL是32位浮点,这一点和标准C的int长度不固定不同,信捷的INT在任何机型上都是16位,写跨机型程序时依赖这个特性反而更安全。
2.3 程序体的执行逻辑:默认顺序执行,没有线程、没有任务调度
信捷类C的程序体从上到下按扫描周期顺序执行一遍,同一个POU内代码是单线程的,不存在标准C的多线程问题。但不同POU之间的执行顺序可以配置:编程软件里程序节点可以调整扫描顺序,先执行的POU里赋值的变量,后执行的POU里读到的是新值。
// POU_1(先执行) VAR_GLOBAL g_step : INT; END_VAR g_step := 1; // POU_2(后执行) IF g_step = 1 THEN // 这里能读到POU_1写的新值 END_IF;说明:这里展示的是全局变量在不同POU间的传递。两个POU写在同一个扫描周期内,执行顺序以编程软件里的程序排列顺序为准。如果POU_2排在POU_1前面,那读到的g_step就是上一周期的旧值。这种顺序执行模型下,初学的人容易犯一个错:在同一个POU里前后两段代码都写了同一个全局变量,后面一段会把前面一段的结果覆盖掉。排查这类问题最简单的方法是看编程软件里的程序列表,从上往下看赋值顺序。如果你需要真正并行执行的任务,信捷支持中断程序和定时中断,后面第6章会专门讲。
3. 把梯形图翻译成信捷类C:启停、定时、计数三个标准片段
3.1 起保停电路翻译:从梯形图逻辑到IF语句
起保停是梯形图里最基本的自锁电路,翻译成信捷类C有固定的套路:常开触点直接作为IF条件,常闭触点加上取反,线圈赋值放在IF块内。
// 梯形图等价逻辑:启动按钮X0(常开)、停止按钮X1(常闭串接)、输出Y0 VAR start_pb : BOOL; stop_pb : BOOL; motor : BOOL; END_VAR start_pb := X0; stop_pb := X1; // 起保停核心翻译 IF start_pb THEN motor := TRUE; ELSIF stop_pb THEN motor := FALSE; END_IF; Y0 := motor;说明:这段逻辑和梯形图的行为一致,但有一个隐含差异:梯形图里停止按钮用常闭触点,程序里stop_pb读到的物理点是常开信号。如果外部接的是常闭触点,硬件接线已经取反,程序里就要改成IF stop_pb THEN motor := FALSE;的写法。常见的做法是在程序里统一用常开信号,取反逻辑放在程序里处理,这样排查时不用对着端子猜。参数调整提示:电机启动信号可能需要延时保持,可以把IF start_pb改成IF start_pb OR motor,这样松开按钮后电机继续运行,直到停止条件为真。这个OR self就是自锁的C语言表达方式,和梯形图里输出线圈并联自己的常开触点完全对应。
3.2 定时器翻译:TON定时器的调用姿势
信捷类C里定时器不是直接写delay(100),而是调用TON功能块。TON的输入是IN(启动信号)和PT(预设时间),输出是Q(定时完成标志)和ET(已计时时间)。定时器的执行依赖扫描周期累加,不是实时时钟。
// TON定时器标准用法:X0启动,延时5秒后Y0接通 VAR timer1 : TON; t_start : BOOL; t_done : BOOL; END_VAR timer1(IN := X0, PT := T#5S); t_done := timer1.Q; Y0 := t_done;说明:PT用T#5S这种时间字面量表示5秒,也可以写成T#5000MS。timer1是功能块实例,每个扫描周期调用一次,IN为TRUE时ET从0开始累加,IN变为FALSE时ET清零。Q在ET到达PT时变为TRUE并保持到IN为FALSE。从梯形图转到C的常见问题是定时器复位:梯形图里定时器线圈断开就自动复位,但程序里如果X0一直为TRUE,定时器永远不会复位,Q会一直保持TRUE。要实现“定时完成一次后手动复位”,做法是给定时器IN加一个复位条件,或者用CTU计数器记录Q的上升沿。我一般会用一个辅助变量锁存Q的边沿,在IF块内控制IN。
// 单次定时 + 复位示例 VAR timer1 : TON; trig_ok : BOOL; END_VAR timer1(IN := X0 AND NOT trig_ok, PT := T#3S); IF timer1.Q THEN trig_ok := TRUE; // 锁存完成标志,定时器IN断开实现复位 END_IF; Y0 := timer1.Q;说明:trig_ok是自锁变量,定时器Q为TRUE后立即把IN断开,ET清零,Q回到FALSE。这样做的好处是X0持续为TRUE时只会产生一次定时脉冲,适合做上电延时一次就再也不动作的场景。3.3 计数器翻译:CTU和边沿检测的配合
信捷类C的CTU功能块参数是CU(计数脉冲)、R(复位)、PV(预设值),输出Q在计数值到达PV时为TRUE,CV是当前计数值。计数器的关键是边沿检测:CU每次从FALSE变成TRUE时计数加1,电平持续时间不起作用。
// CTU计数器:X1上升沿计数,到10次后Y1接通,X2复位 VAR cnt1 : CTU; count_up : BOOL; reset_cnt : BOOL; END_VAR cnt1(CU := X1, R := X2, PV := 10); IF cnt1.Q THEN Y1 := TRUE; ELSE Y1 := FALSE; END_IF;说明:计数和定时不同,梯形图里计数器也是边沿触发的,所以这里不需要额外写边沿检测代码。R输入为TRUE时CV清零,Q复位。PV可以写常数也可以写变量,实际用变量时要注意信捷的INT是16位,计数范围超过32767要用DINT类型的计数器或者改用更大类型。如果输入信号抖动严重,计数会不准,需要在CU前面加数字滤波。最简单的做法是用一个定时中断周期采样输入信号,连续若干次为TRUE才认为是一次有效脉冲。这个滤波逻辑我在第6章会给出完整代码。
4. 用XStuio建立信捷C程序工程:离线模拟到真机下载完整流程
4.1 建立工程时的机型选择和POU创建步骤
信捷编程软件新建工程时,先选机型再选编程语言。XDH、XDC系列可以选“C语言”或“梯形图”,但同一个工程里可以混合,这是信捷相比其他品牌的一个灵活点。建议新手先建一个纯类C工程跑通,再考虑混合模式。
操作路径:新建工程 → 选择机型 → 选择程序组织单元 → 添加POU → 选择编程语言(C语言/ST) → 编写变量声明和程序体 → 编译 → 模拟运行说明:信捷把类C风格在界面上标注为C语言或ST,不同软件版本叫法不一,但底层逻辑相同。我习惯在变量声明区把全局变量集中在固定区域,局部变量放在POU开头,程序体里只写逻辑,这样模拟调试时变量监视窗口看得更清爽。编译时常见问题在变量类型不匹配:INT赋值给BOOL、REAL赋值给INT,都会直接报错或警告。信捷的编译报错信息会指向具体行号,新手别急着改代码,先看变量声明区的类型。模拟运行前要确认“允许模拟”选项开启,否则下载时提示无法连接。
4.2 模拟调试三件套:修改变量、强制变量、断点
离线和模拟模式是信捷类C调试的主要战场。在模拟窗口里,你可以修改变量的当前值来模拟输入信号,强制变量把程序里计算值锁定为指定值,断点则让程序在指定行停止执行。
模拟操作步骤: 1. 菜单栏打开模拟调试窗口 2. 在变量监视表添加X0、Y0、timer1.ET 3. 手动把X0改为TRUE → 观察timer1.ET开始累加 4. 把X0改为FALSE → 观察ET清零 5. 断点设置在IF timer1.Q THEN 这一行,触发后单步执行观察变量变化说明:断点调试在类C里比梯形图直观得多,因为程序体是文本。但有一个限制:全局变量在断点触发后可以强制修改,局部变量不行,只能看不能改。如果程序块里用了局部变量且怀疑其计算有问题,可以临时把它改成全局变量再调试,调完再改回去。强制变量和程序赋值有优先级冲突:如果你强制Y0为TRUE,程序体里又写了Y0 := FALSE,那么强制值保持TRUE,程序里的赋值不生效。所有品牌PLC的强制功能都是这个逻辑,排查时不要把强制状态忘了,否则会得出“程序跑飞了”的结论。
4.3 真机下载与在线监视:程序卡死时怎么定位
真机下载前先编译并保存工程,下载时选择COM或以太网口。信捷XDH机型的网口默认IP和电脑IP要设在同一网段,PING通之后才能连接。下载时注意PLC运行状态:运行中下载会停机的,在产线设备上操作前务必确认允许停机。
# 网络连接测试,确认电脑到PLC链路通 ping 192.168.1.33 # 如果是串口,检查串口号和波特率 # 信捷默认波特率常见为9600或115200,查看PLC通信参数面板说明:下载后PLC自动进入运行状态时,监视窗口能看到每个扫描周期的变量变化。如果程序正常运行但某个输出不动作,先看这个输出地址有没有被多个POU赋值,双击程序搜索该地址,十有八九是后执行的POU把先执行的值覆盖了。在线监视时CPU指示灯如果闪红灯,先看错误缓冲区。信捷类C常见的运行时报错是除数为零、数组越界和REAL运算溢出。程序体里没有try-catch机制,这些错误会导致PLC停机。我的习惯是所有除法都先判断除数是否为零,所有数组下标都用限幅函数包一层,从源头避免停机。
5. 信捷C语言避坑:数据类型、时序、FB变量三个重灾区
5.1 坑一:INT和REAL隐式转换丢精度
现象:程序里写a := b / c,b和c都是INT,实际结果一直是0,数据不对。原因:信捷类C的整数除法结果向下取整,两个INT相除得到一个INT,小数部分直接舍去。解决:先把其中一个操作数转成REAL再做除法。
// 错误写法 VAR total : INT; count : INT; avg : REAL; END_VAR avg := total / count; // 结果是0.0 // 正确写法 avg := REAL_TO_REAL(total) / REAL_TO_REAL(count);说明:信捷类C没有标准C的强制类型转换运算符,需要调用转换函数。REAL_TO_REAL看着奇怪,但在信捷软件里是合法的。另一个办法是直接在声明时用REAL类型变量保存其中一个操作数,避免转换。这种坑不会报编译错误,只会给你错误结果,调试时最费时间。5.2 坑二:定时器在扫描周期里的累加行为导致定时不准
现象:TON定时器设定的5秒,实测间隔超过6秒,而且每次偏差不一样。原因:定时器依赖扫描周期累加,每个周期ET增加的时间是这个周期的实际执行时间,不是固定值。扫描周期本身会因为程序复杂度波动,所以定时精度只能达到“大于等于PT”的范围。解决:定时精度要求高时改用定时中断,定时中断里用计数器累加实现精确计时。
// 定时中断里实现精确延时(假设中断周期10ms) VAR int_counter : INT; END_VAR int_counter := int_counter + 1; IF int_counter >= 500 THEN // 10ms x 500 = 5s int_counter := 0; Y0 := TRUE; END_IF;说明:定时中断在梯形图里也能做,但类C程序块里可以直接定义中断程序,写法更直接。注意中断程序和主程序共享变量的冲突:中断程序里修改的变量,主程序同一周期内读到的是修改前还是修改后的值取决于时序,严格做法是在主程序里把需要用的变量先快照到局部变量再使用。5.3 坑三:FB功能块的局部变量不保持导致输出跳动
现象:同一个功能块被多个POU调用,输出结果和预期不一致。原因:功能块实例的局部变量在调用结束时是否保持,取决于功能块的定义方式和调用方式。信捷类C里自定义FB时,局部变量默认不保持,下一次调用时重新初始化。解决:把需要保持的变量改成VAR_GLOBAL或VAR_IN_OUT。
// 自定义功能块:单按钮启停 FUNCTION_BLOCK ToggleFB VAR_INPUT btn : BOOL; END_VAR VAR_OUTPUT state : BOOL; END_VAR VAR last_btn : BOOL; // 默认不保持 END_VAR IF btn AND NOT last_btn THEN state := NOT state; END_IF; last_btn := btn;说明:这个单按钮启停FB在模拟验证时正常,但在多个POU里同时调用时会发现state被反复覆盖。原因就是last_btn不保持,每次调用都认为按钮刚按下。解决方法是把last_btn声明到VAR_GLOBAL区域,或者把整个FB改成用M中间继电器保存状态。自定义FB是信捷类C里面最接近标准C函数的地方,但它的输入输出机制和C函数返回值完全不同。FB没有返回值,所有输出都靠VAR_OUTPUT变量回传。调用方式也有讲究:功能块必须在变量声明区实例化,程序体里用实例名(参数 := 值)的形式调用,不能像C函数那样直接写在表达式里。
5.4 坑四:混合编程时梯形图块和C程序块的交接不畅
现象:梯形图里写好的逻辑,换成类C程序块后设备动作错乱,而且很难查。原因:梯形图里的中间继电器M和C程序块里的BOOL变量,本质是同一块位地址区,但梯形图里对M的赋值是周期执行的,C程序块里对同一个M的赋值也是周期执行的,两者执行顺序没理清就会互相覆盖。解决:划分边界时明确“一个地址只属于一个程序块”,跨程序块的数据传递用专门的交接变量,不要共用同一个M点。
约定: - 梯形图区只操作Y输出和部分M中间继电器 - 类C区只操作自定义全局变量,不直接写Y - 交接区定义专用全局变量,梯形图写、C读或C写、梯形图读说明:这个约定看着简单,实际工程里很多人栽在这上面。特别是老工程师沿用梯形图习惯在C程序块里顺手写Y0、Y1,结果和梯形图里的输出线圈竞争。我的习惯是C程序块里除了调试阶段,一律不写物理输出地址,输出统一放到梯形图最后一段做集中映射。这样梯形图相当于一个“输出映射表”,哪段代码要控制哪个输出一目了然。6. 手写一个输入滤波功能块:中断采样、边沿确认和复位
这一章作为实操收尾,我提供一个我在多个项目里用过的输入滤波方案。PLC输入信号抖动是现场最常见的坑,特别是按钮、行程开关和接近开关,一个抖动可能导致计数多跳几次、工位误动作。梯形图里做滤波要写一大堆定时器,信捷类C里一个功能块加一个定时中断就能搞定。
// 输入滤波功能块 InputFilter // 原理:定时中断每2ms采样一次输入,连续4次为TRUE才输出TRUE,连续4次FALSE才输出FALSE FUNCTION_BLOCK InputFilter VAR_INPUT raw_in : BOOL; // 原始输入信号 END_VAR VAR_OUTPUT filter_out : BOOL; // 滤波后输出 END_VAR VAR samples : ARRAY[0..3] OF BOOL; idx : INT; ones : INT; END_VAR VAR_GLOBAL filter_irq_flag : BOOL; END_VAR // 中断采样函数(每2ms调用一次) // 使用示例:Interrupt 0,周期2ms VAR i : INT; END_VAR samples[idx] := raw_in; idx := idx + 1; IF idx >= 4 THEN idx := 0; END_IF; // 统计连续4次采样中TRUE的个数 ones := 0; FOR i := 0 TO 3 DO IF samples[i] THEN ones := ones + 1; END_IF; END_FOR; IF ones >= 3 THEN filter_out := TRUE; ELSIF ones <= 1 THEN filter_out := FALSE; END_IF; // 当ones=2时保持原状态,这个设计能有效抑制临界抖动说明:这个功能块的关键在比较阈值:连续4次采样,3次为TRUE就输出TRUE,1次以下就输出FALSE,中间状态保持历史值。这样既滤掉窄脉冲,又不会在信号缓慢变化时反复翻转。中断周期2ms,相当于滤波器时间常数约8ms。参数调整建议:现场信号干扰严重时把采样次数从4改成8,阈值改成6,时间常数变成16ms,滤抖动能力翻倍,代价是响应延迟从8ms变成16ms。响应速度要求高的场合(比如编码器脉冲计数),这个逻辑就不适合了,那种场景需要用高速计数通道。
我在一个设备现场遇到过这种情况:接近开关把外部干扰当成脉冲输入,计数器在一天内多了几十个计数。用这个滤波功能块接在计数器CU前面之后,计数器数值再也没跳过。而另一个项目里我犯过反方向的错误:给一个手动急停按钮也加了滤波,结果急停按下去要等几十毫秒才响应,被客户追问了半天。从那之后,急停、安全光幕这类信号一律不滤波,直接进硬接线回路或PLC的立即IO通道。
信捷类C的中断程序在编程软件里单独配置,配置完成后中断函数里可以直接操作全局变量。整个滤波功能块的调用就变成了主循环里一行:
// 主程序调用 Filter1(raw_in := X3, filter_out => X3_filtered); // X3_filtered 是声明的BOOL全局变量,之后所有逻辑都用电信号X3_filtered说明:调用功能块时输入用:=,输出用=>,这是信捷类C功能块的标准调用语法。X3_filtered不需要和物理地址绑定,就当一个中间变量用。想验证滤波效果的话,把采样数组长度临时改到16,阈值改到12,然后对着输入端子快速短接、断开几次,观察filter_out的变化,你会明显看到输出翻转比输入平稳得多。验证完记得把参数改回实际工况。
最后分享一个习惯:不管时间多紧,我都会在工程里保留一份纯类C写的测试POU,里面放着各个功能块的边界测试用例——定时器PT为0、计数器PV为负、数组下标越界这些极端场景全跑一遍再上线。C语言写PLC,越界不报错而是静默出错,等现场发现问题就晚了。这份习惯帮我避免过多次现场返工,希望也能帮到你。
本文还有配套的精品资源,点击获取