[西门子] 深度解析西门子PLC的数据类型和变量声明使程序高效运行

[复制链接]
查看72 | 回复0 | 2025-3-17 20:47:26 | 显示全部楼层 |阅读模式

点击蓝字 关注我们


在PLC编程中,掌握数据类型和变量声明的知识就像盖房子先打好地基一样重要。


很多初学者一上来就关注复杂的功能实现,却忽略了这些基础知识,结果程序写得乱七八糟,调试起来头痛不已。


今天我就带大家深入了解西门子PLC的数据类型和变量声明方法,让你的程序从源头上更加规范、高效。


1.

一、基本数据类型解析



西门子PLC提供了多种数据类型,就像我们的工具箱里有不同用途的工具一样。选择合适的数据类型不仅能节省存储空间,还能提高程序的执行效率。


1. 布尔型 (BOOL)

BOOL类型就像家里的电灯开关,只有两种状态:开(TRUE/1)或关(FALSE/0)。它占用1个位(bit)的存储空间。


典型应用:按钮状态、传感器触发信号、电机启停控制等。


注意事项:


虽然BOOL只占一个位,但在S7-300/400中,最小的寻址单位是字节。


即使你只声明一个BOOL变量,系统也会分配一个字节给它,其余7位将被浪费。


在S7-1200/1500中,可以通过AT指令把多个BOOL变量打包到一个字节中,提高存储效率。


2. 整数类型

  • BYTE/WORD/DWORD/LWORD:无符号整数,分别占1/2/4/8个字节
  • SINT/INT/DINT/LINT:有符号整数,分别占1/2/4/8个字节
  • USINT/UINT/UDINT/ULINT:无符号整数,分别占1/2/4/8个字节

举个生活中的例子:


BYTE就像一个8格的鸡蛋盒,每格只能放0或1;WORD就是两个鸡蛋盒;DWORD就是四个鸡蛋盒。无符号类型就像正常计数;有符号类型则像银行账户,可以有正数(存款)也可以有负数(欠款)。


选择技巧:


  • 计数0-255用BYTE或USINT
  • 计数0-65535用WORD或UINT
  • 可能出现负数的场合用INT或DINT
  • 大数值计算用DINT或LINT

3. 实数类型

REAL和LREAL:分别占4字节和8字节,用于处理带小数点的数值。


就像测量温度计,不只是整数的26度,还可能是26.7度这样带小数的值。


注意事项:


实数运算会消耗更多的CPU资源,如果不需要小数精度,尽量使用整数类型。特别是在S7-300等老型号PLC上,实数运算可能会明显拖慢程序执行速度。


4. 时间和日期类型

  • TIME:时间段,以毫秒为单位,格式如T#10S (10秒)
  • TIME_OF_DAY (TOD):一天中的时刻,格式如TOD#10:20:30 (10点20分30秒)
  • DATE:日期,格式如D#2023-05-10
  • DATE_AND_TIME (DT):日期和时间的组合

5. 字符串类型

STRING和WSTRING:用于存储文本信息。


如果你需要在HMI上显示文字信息或记录生产批次信息,就会用到字符串类型。


// STRING变量声明示例
    String_Variable : STRING[20] := 'Hello World'; // 最多存储20个字符
    WString_Variable : WSTRING[10] := “Unicode支持”; // 支持Unicode字符

2.

二、变量声明方法



在西门子PLC中,变量可以分为全局变量和局部变量两大类。这就像在工厂中,有些工具是公用的(全局的),放在公共工具室;有些是专用的(局部的),只在特定工作站使用。


1. 全局变量

全局变量在PLC标签表(Tag Table)中声明,可以被所有程序块访问。


在TIA Portal中,你可以创建多个变量表来组织不同类型的全局变量,比如输入信号表、输出信号表、中间变量表等。



全局变量表示意图


全局变量表示意图


全局变量的命名建议:



  • 输入信号以“I_”开头
  • 输出信号以“Q_”开头
  • 中间变量以“M_”开头
  • 常量以“C_”开头
  • 变量名使用英文单词,见名知意

// 全局变量示例 I_StartButton AT %I0.0 : BOOL; // 启动按钮 I_Sensor1 AT %IW2 : WORD; // 传感器1模拟量 Q_Motor AT %Q0.0 : BOOL; // 电机输出 M_TempValue : REAL; // 温度中间值 C_MaxTemperature : REAL := 85.5; // 最高温度常量


注意事项:


使用符号地址(变量名)而非绝对地址(%I0.0)编程,可以提高程序可读性和可维护性。即使I/O地址发生变化,只需修改变量表中的地址映射,而不必修改程序逻辑。


2. 局部变量

局部变量在功能块(FB)或函数(FC)的变量表中声明,只在该程序块内部可见。


局部变量分为以下几种:


  • Input(输入):从调用者接收数据
  • Output(输出):向调用者返回数据
  • InOut(输入输出):可读可写的引用参数
  • Static(静态):FB独有的,用于保存状态
  • Temp(临时):临时工作变量,程序执行后不保留值
  • Constant(常量):只读的固定值

// FB温度控制器的局部变量示例 VAR_INPUT SetPoint : REAL; // 设定温度 ActualTemp : REAL; // 实际温度 END_VAR


    VAR_OUTPUT
        HeaterOn : BOOL;     // 加热器状态
        AlarmOn : BOOL;      // 报警状态
    END_VAR

    VAR_INOUT
        ManualMode : BOOL;   // 手动模式标志
    END_VAR

    VAR
        LastError : REAL;    // 上次偏差
        IntegralSum : REAL;  // 积分和
    END_VAR

    VAR_TEMP
        TempDiff : REAL;     // 临时计算差值
    END_VAR

    VAR CONSTANT
        MAX_OUTPUT : REAL := 100.0;  // 最大输出
    END_VAR

3.

三、复杂数据类型应用



1. 数组 (Array)

数组就像一排整齐的储物柜,每个柜子都存放相同类型的物品,通过编号快速找到特定位置的物品。


// 数组声明示例
    TempSensors : ARRAY[1..10] OF REAL;   // 10个温度传感器值
    MotorStatus : ARRAY[1..5, 1..4] OF BOOL;   // 5个电机的4种状态

数组访问示例:


#TempSensors[3] := 25.6;  // 设置第3个传感器的温度
    IF #MotorStatus[2, 1] THEN  // 如果第2个电机的第1个状态为TRUE
        // 执行相应操作
    END_IF;

2. 结构体 (Struct)

结构体就像一个多功能工具箱,里面可以放不同类型的工具,但它们作为一个整体一起使用。


// 结构体定义示例 - 在PLC数据类型中创建
    TYPE “Motor_Data”
    VERSION : 0.1
       STRUCT
          Speed : REAL;    // 速度
          Current : REAL;  // 电流
          RunHours : DINT; // 运行小时数
          Status : BOOL;   // 运行状态
          FaultCode : INT; // 故障代码
       END_STRUCT;
    END_TYPE

    // 使用结构体
    Motor1 : “Motor_Data”;
    Motors : ARRAY[1..10] OF “Motor_Data”;  // 结构体数组

结构体访问示例:


#Motor1.Speed := 1500.0;  // 设置速度
    IF #Motors[3].Status THEN  // 如果第3个电机在运行
        // 执行相应操作
    END_IF;

注意事项:


结构体可以大大提高程序的可读性和模块化程度。


比如一个生产线有多台相同的设备,每台设备有多个相同类型的参数,使用结构体数组可以非常优雅地组织这些数据。


但注意,结构体会占用连续的内存空间,所以设计时要考虑内存布局和访问效率。


4.

四、PLC内存区域与变量映射



了解PLC内存区域有助于我们更好地组织变量和理解程序执行。西门子PLC主要有以下几个内存区域:


  • I区(输入):%I - 存储输入信号状态
  • Q区(输出):%Q - 存储输出信号状态
  • M区(标志位):%M - 存储中间计算结果
  • DB区(数据块):%DB - 存储各种数据
  • L区(局部数据):不直接访问 - 存储功能块局部变量

变量映射就是将变量与特定内存地址关联起来,可以通过AT关键字实现。


// 变量映射示例
    StartButton AT %I0.0 : BOOL;  // 映射到输入第0字节第0位
    SpeedValue AT %IW64 : INT;    // 映射到输入第64字(字=2字节)
    MotorSpeed AT %QW20 : INT;    // 映射到输出第20字
    StatusFlag AT %M10.0 : BOOL;  // 映射到M区第10字节第0位
    MyData AT %DB10.DBW0 : WORD;  // 映射到DB10数据块第0字

5.

五、实际应用案例



案例:生产线温控系统

假设我们要设计一个具有10个加热区的生产线温控系统,每个区域需要:


  • 温度传感器输入
  • 加热器输出
  • 设定温度参数
  • 报警限值
  • PID参数

我们设计一个结构体来表示一个加热区:


TYPE “Heating_Zone”
    VERSION : 0.1
       STRUCT
          ActualTemp : REAL;        // 实际温度
          SetPoint : REAL := 25.0;  // 设定温度
          HeaterOutput : REAL;      // 加热输出百分比
          HeaterStatus : BOOL;      // 加热器状态
          HighAlarm : BOOL;         // 高温报警
          LowAlarm : BOOL;          // 低温报警
          AlarmHighLimit : REAL := 40.0;  // 高温报警限值
          AlarmLowLimit : REAL := 10.0;   // 低温报警限值
          // PID参数
          Kp : REAL := 1.0;         // 比例系数
          Ki : REAL := 0.1;         // 积分系数
          Kd : REAL := 0.05;        // 微分系数
          // 内部变量
          Error : REAL;             // 温度偏差
          IntegralSum : REAL;       // 积分和
          LastError : REAL;         // 上次偏差
          ManualMode : BOOL;        // 手动模式
          ManualOutput : REAL;      // 手动输出值
       END_STRUCT;
    END_TYPE

在全局变量表中声明加热区数组和I/O映射:


// 全局变量表
    HeatingZones : ARRAY[1..10] OF “Heating_Zone”;  // 10个加热区

    // I/O映射
    I_TempSensor1 AT %IW64 : INT;    // 第1个温度传感器输入
    I_TempSensor2 AT %IW66 : INT;    // 第2个温度传感器输入
    // ... 其他传感器 ...

    Q_Heater1 AT %Q10.0 : BOOL;      // 第1个加热器输出
    Q_Heater2 AT %Q10.1 : BOOL;      // 第2个加热器输出
    // ... 其他加热器 ...

接下来,我们创建一个温度控制功能块:


FUNCTION_BLOCK “FB_TemperatureControl”
    VAR_INPUT
        ZoneData : “Heating_Zone”;  // 传入区域数据
        SensorValue : INT;          // 传感器原始值
    END_VAR

    VAR_OUTPUT
        HeaterOutput : BOOL;        // 加热器输出
        AlarmOutput : BOOL;         // 报警输出
    END_VAR

    VAR_TEMP
        TempValue : REAL;           // 临时温度值
        OutputPercent : REAL;       // 输出百分比
    END_VAR

    BEGIN
        // 1. 将传感器INT值转换为实际温度
        // 假设是0-10V对应0-200度的转换
        #TempValue := INT_TO_REAL(#SensorValue) * 200.0 / 27648.0;

        // 2. 更新结构体中的实际温度
        #ZoneData.ActualTemp := #TempValue;

        // 3. 计算温度偏差
        #ZoneData.Error := #ZoneData.SetPoint - #ZoneData.ActualTemp;

        // 4. 判断是否报警
        IF #ZoneData.ActualTemp > #ZoneData.AlarmHighLimit THEN
            #ZoneData.HighAlarm := TRUE;
        ELSE
            #ZoneData.HighAlarm := FALSE;
        END_IF;

        IF #ZoneData.ActualTemp < #ZoneData.AlarmLowLimit THEN
            #ZoneData.LowAlarm := TRUE;
        ELSE
            #ZoneData.LowAlarm := FALSE;
        END_IF;

        // 5. 计算PID输出
        IF NOT #ZoneData.ManualMode THEN
            // 简化的PID算法
            #ZoneData.IntegralSum := #ZoneData.IntegralSum + #ZoneData.Error;
            #OutputPercent := (#ZoneData.Kp * #ZoneData.Error) + 
                              (#ZoneData.Ki * #ZoneData.IntegralSum) + 
                              (#ZoneData.Kd * (#ZoneData.Error - #ZoneData.LastError));

            // 限制输出在0-100%范围内
            IF #OutputPercent < 0.0 THEN
                #OutputPercent := 0.0;
            ELSIF #OutputPercent > 100.0 THEN
                #OutputPercent := 100.0;
            END_IF;

            #ZoneData.HeaterOutput := #OutputPercent;
        ELSE
            // 手动模式
            #ZoneData.HeaterOutput := #ZoneData.ManualOutput;
        END_IF;

        // 6. 保存当前偏差用于下次计算
        #ZoneData.LastError := #ZoneData.Error;

        // 7. 设置输出
        // 简化版:输出大于50%时开启加热器
        IF #ZoneData.HeaterOutput > 50.0 THEN
            #ZoneData.HeaterStatus := TRUE;
            #HeaterOutput := TRUE;
        ELSE
            #ZoneData.HeaterStatus := FALSE;
            #HeaterOutput := FALSE;
        END_IF;

        // 8. 设置报警输出
        #AlarmOutput := #ZoneData.HighAlarm OR #ZoneData.LowAlarm;
    END_FUNCTION_BLOCK

在主程序中调用功能块:


// 在OB1主程序中
    // 处理第1个加热区
    “FB_TemperatureControl”(
        ZoneData := “HeatingZones”[1],
        SensorValue := “I_TempSensor1”,
        HeaterOutput => “Q_Heater1”,
        AlarmOutput => “M_Alarm1”
    );

    // 处理第2个加热区
    “FB_TemperatureControl”(
        ZoneData := “HeatingZones”[2],
        SensorValue := “I_TempSensor2”,
        HeaterOutput => “Q_Heater2”,
        AlarmOutput => “M_Alarm2”
    );

    // ... 处理其他加热区 ...

6.

六、常见问题与解决方案



1. 内存对齐问题


问题:在S7-300/400 PLC中,有时会遇到“The DB structure changed. Retranslation is required”(数据块结构变更,需要重新翻译)的错误。


解决方案:这通常是由于内存对齐导致的。可以在PLC硬件配置中的“属性->一般->启动”中设置“启用一致的上载”选项。在S7-1200/1500中,可以使用不可优化的数据块解决这个问题。


2. 初始值问题

问题:有时设置的初始值在PLC运行后被重置。


解决方案:检查数据块是否设置为“仅符号访问”。对于S7-300/400,确保在PLC属性中启用了“启动时启用替换”选项。对于S7-1200/1500,确保数据块的“保持”属性已激活。


3. 访问非法地址

问题:程序在运行时出现“访问非法地址”的错误。


解决方案:这通常是由于访问了数组边界之外的元素或使用了未初始化的指针。


使用循环处理数组时,务必检查索引是否在有效范围内。


例如:


// 安全的数组访问
    IF #Index >= 1 AND #Index <= 10 THEN
        #Value := #MyArray[#Index];
    ELSE
        // 处理错误情况
        #ErrorFlag := TRUE;
    END_IF;

4. 数据类型转换错误

问题:在进行数据类型转换时出现意外结果。


解决方案:使用SCL语言中提供的显式转换函数,而不是隐式转换。


例如:


// 正确的类型转换
    #RealVar := INT_TO_REAL(#IntVar);  // INT转REAL
    #IntVar := REAL_TO_INT(#RealVar);  // REAL转INT(会截断小数部分)
    #ByteVar := INT_TO_BYTE(#IntVar);  // INT转BYTE(可能会丢失高位)

注意事项:


在进行REAL到INT的转换时,小数部分会被截断,而不是四舍五入。如果需要四舍五入,可以使用ROUND函数。实际工作中,我曾经因为不知道这一点,导致一个温度控制程序出现了0.5度的系统偏差。


7.

七、优化建议



  1. 合理选择数据类型

根据实际需要选择最合适的数据类型,不要“杀鸡用牛刀”。如果一个计数器最大只到100,用BYTE就足够了,没必要用DINT。


  1. 使用符号化编程

尽量使用变量名而不是直接的地址,这样程序更易读,也更容易维护。


  1. 适当使用注释

为变量和程序块添加清晰的注释,特别是对于复杂的数据结构和算法。


  1. 模块化设计

将相关功能封装到功能块中,使用数据块存储状态,这样可以创建可复用的代码模块。


  1. 使用UDT(用户定义数据类型)

对于重复使用的数据结构,定义为UDT,这样可以保证一致性,也便于后期修改。


  1. 考虑内存布局

在设计结构体时,考虑内存对齐,将相同类型的变量放在一起,可以提高访问效率。


  1. 变量初始化

为变量设置合理的初始值,避免使用未初始化的变量导致程序错误。


8.

总结



正确选择数据类型和规范声明变量是PLC编程的基础工作,就像盖房子前先打好地基一样重要。掌握了这些知识,你的程序将更加高效、可靠、易于维护。


在实际工作中,我曾经接手过一个老工程师编写的程序,里面的变量命名混乱,数据类型选择不合理,结果每次修改都如同走迷宫。花了一周时间重构后,不仅程序运行效率提高了,维护难度也大大降低。


实操练习建议:


  1. 尝试设计一个简单的温度记录系统,使用数组存储24小时的温度数据。
  2. 设计一个电机控制结构体,包含速度、状态、运行时间等参数,并在功能块中实现启停控制。
  3. 创建一个产品参数数据块,使用UDT定义不同产品型号的参数,实现产品切换功能。


推荐阅读



PLC在流程工业中的应用:过程控制与优化的关键技术



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册哦

x
您需要登录后才可以回帖 登录 | 注册哦

本版积分规则