IEC 61131-3 與五種程式語言

認識 IEC 61131-3 標準的架構;理解五種語言的特性與適用場合; 能讀寫基本的結構化文字(ST)。

標準概觀

IEC 61131-3 是 PLC 程式語言的國際標準,定義了共通的資料型別程式組織單元(POU)五種語言。學會標準語法後, 跨品牌只需適應開發環境差異,觀念完全可攜。

五種語言

語言 型態 擅長 不擅長
LD 梯形圖 圖形 離散邏輯、互鎖、現場除錯 數學運算、迴圈、字串
FBD 功能塊圖 圖形 訊號流、連續控制、PID 串接 複雜分支邏輯
ST 結構化文字 文字 演算法、狀態機、資料處理 現場人員即時監看較不直觀
SFC 順序功能圖 圖形 明確的順序/批次流程 非順序性的平行邏輯
IL 指令表 文字 (已於標準第 3 版棄用) ——

ST 快速上手

ST 語法近似 Pascal。以下範例涵蓋八成日常語法:

VAR
  temp    : REAL;
  level   : INT;
  pumps   : ARRAY[1..4] OF BOOL;
  i       : INT;
  tDelay  : TON;
END_VAR

(* 條件分支 *)
IF temp > 80.0 THEN
    level := 3;
ELSIF temp > 60.0 THEN
    level := 2;
ELSE
    level := 1;
END_IF;

(* CASE 多路分支:狀態機的核心語法 *)
CASE level OF
  1: pumps[1] := TRUE;
  2: pumps[1] := TRUE;  pumps[2] := TRUE;
  3: FOR i := 1 TO 4 DO
         pumps[i] := TRUE;
     END_FOR;
END_CASE;

(* 功能塊呼叫:計時器 *)
tDelay(IN := pumps[1], PT := T#5s);
IF tDelay.Q THEN
    (* 泵 1 已連續運轉 5 秒 *)
END_IF;

FORWHILE 迴圈在單一掃描週期內跑完全部迭代。 迴圈次數過大或條件永真會拖垮掃描時間、觸發看門狗。 「等待某事發生」永遠不要用 WHILE 空轉,而是讓狀態機跨週期等待。

語言選擇準則

實務上常見的健康組合:主流程與設備邏輯用 ST(好版控、好重構)、 與現場電氣直接對應的互鎖與輸出層用 LD(電機人員可即時監看)、 清楚的批次順序用 SFC。同一專案混用語言是正常且被鼓勵的。

  1. 把第 4 章的自保持電路改寫成 ST(提示:Motor := (Start OR Motor) AND NOT StopPressed;)。

  2. CASE 改寫第 5 章的紅綠燈練習,比較與計時器串接版本的可讀性。

  3. 哪些邏輯你會堅持留在 LD?為什麼?

資料型別、變數與命名

掌握基本與衍生資料型別;理解變數範圍與實體位址映射;建立一致的命名規範。

基本資料型別

型別 大小 範圍/說明 典型用途
BOOL 1 bit TRUE / FALSE 接點、旗標
INT 16 bit \(-32768 \sim 32767\) 一般整數、原始類比值
DINT 32 bit \(\pm 2.1 \times 10^9\) 計數、位置
UINT / UDINT 16/32 bit 無號整數 編碼器原始值
REAL 32 bit IEEE 754 單精度 工程單位、PID
LREAL 64 bit IEEE 754 雙精度 高精度運算
TIME 32 bit T#1h30m15s500ms 計時器設定
STRING 可變 字元串 配方名、條碼
WORD / DWORD 16/32 bit 位元集合 通訊封包、狀態字

三個高頻錯誤:(1) INT 溢位——累計計數請用 DINT; (2) REAL 不可用等號比較IF r = 0.3 幾乎必失敗),改用區間比較; (3) 有號/無號混算——編碼器 UDINT 直接塞進 INT 會變負數。

衍生型別:陣列、結構與列舉

TYPE E_MotorState : (IDLE, STARTING, RUNNING, STOPPING, FAULT); END_TYPE

TYPE ST_Motor : STRUCT
    Cmd_Run    : BOOL;
    Fb_Running : BOOL;      // 接觸器輔助接點回授
    Current    : REAL;      // 電流 (A)
    RunHours   : DINT;      // 累計運轉時數(保持型)
    State      : E_MotorState;
END_STRUCT; END_TYPE

VAR_GLOBAL
    Motors : ARRAY[1..8] OF ST_Motor;   // 八台馬達共用同一套邏輯
END_VAR

把「一台設備的所有資料」收進一個結構(UDT),是模組化(第 8 章)與 HMI 標籤規劃(第 11 章)的基礎。列舉型別取代魔術數字: 寫 STATE := FAULT 而不是 STATE := 5,上一份審查報告的狀態機 若用列舉,可讀性與安全性都會更好。

變數範圍與實體位址

區域變數(POU 內部)優先於全域變數;全域變數只留給真正跨程式共享的資料 (實體 I/O 映射、模式旗標、跨設備互鎖)。實體位址以 AT 綁定:

VAR_GLOBAL
    di_EStopOk    AT %IX0.0 : BOOL;   // 緊急停止迴路正常
    do_ConveyorOn AT %QX0.0 : BOOL;
    ai_TankLevel  AT %IW2   : INT;    // 液位原始值
END_VAR

I/O 映射層:實體位址只在一個「映射程式」中讀寫,其餘程式一律使用具名變數。 好處:感測器換接點位置只改一行;模擬測試時把映射層換成模擬值即可。

命名規範

一套可行的規範(重點是全案一致):

前綴 意義 範例
di_ / do_ 數位輸入/輸出 di_PhotoEyeAdo_GateOpen
ai_ / ao_ 類比輸入/輸出 ai_TankLevel
cmd_ / fb_ 命令/回授 cmd_Startfb_Running
t / c 計時器/計數器 tHoldDwellcPartsOut
E_ / ST_ / FB_ 列舉/結構/功能塊型別 E_MotorState
  1. INT 計數器每秒 +100,多久溢位?換成 DINT 呢?

  2. 為第 5 章停車場範例設計一個 ST_ParkingLot 結構與合理命名。

  3. 寫出判斷 REAL 變數「約等於 25.0(允差 0.01)」的正確條件式。

程式組織與功能塊模組化

區分 Program、Function Block、Function 三種 POU;理解 FB 實例的記憶特性; 能把重複邏輯封裝成可重用的功能塊。

三種 POU

POU 有無內部記憶 用途
Program 頂層流程,由任務(Task)排程執行
Function Block (FB) 有(每個實例獨立) 設備邏輯:馬達、閥、軸、PID——可多實例
Function (FC) 無(純函數) 換算、限幅、單位轉換等無狀態計算

FB 的關鍵在「實例」:宣告 M1, M2 : FB_Motor; 會得到兩份獨立的內部記憶, 同一套邏輯服務多台設備——這是 PLC 世界的物件導向。

範例:馬達控制功能塊

需求:啟動命令後輸出接觸器;2 秒內未收到運轉回授即報故障;故障需復歸才能再啟動。

FUNCTION_BLOCK FB_Motor
VAR_INPUT
    Cmd_Run   : BOOL;   // 運轉命令
    Fb_Aux    : BOOL;   // 接觸器輔助接點回授
    Reset     : BOOL;   // 故障復歸(上升緣)
END_VAR
VAR_OUTPUT
    Out_Contactor : BOOL;
    Fault         : BOOL;
END_VAR
VAR
    tFbCheck  : TON;
    trigReset : R_TRIG;
END_VAR

trigReset(CLK := Reset);
IF trigReset.Q AND NOT Fb_Aux THEN
    Fault := FALSE;                  // 確認接觸器已釋放才允許復歸
END_IF;

Out_Contactor := Cmd_Run AND NOT Fault;

(* 命令已下但回授未到,計時 2 秒判故障 *)
tFbCheck(IN := Out_Contactor AND NOT Fb_Aux, PT := T#2s);
IF tFbCheck.Q THEN
    Fault := TRUE;
END_IF;
END_FUNCTION_BLOCK

呼叫端:

VAR
    fbPump1, fbPump2 : FB_Motor;
END_VAR

fbPump1(Cmd_Run := cmd_Pump1, Fb_Aux := di_Pump1Aux, Reset := cmd_Reset);
do_Pump1 := fbPump1.Out_Contactor;

fbPump2(Cmd_Run := cmd_Pump2, Fb_Aux := di_Pump2Aux, Reset := cmd_Reset);
do_Pump2 := fbPump2.Out_Contactor;

分層架構

成熟專案的常見分層,依賴方向一律由上往下:

判斷模組化品質的試金石:「加一台同型設備要改幾行?」 答案應該是「宣告一個新實例+接上 I/O」而已。若要複製貼上大段邏輯再逐行改名, 表示抽象層次不對。

  1. FB_Motor 擴充:加入運轉時數累計(RunHours)與「維修模式禁止啟動」輸入。

  2. 設計 FB_Valve(開/關命令、雙極限回授、行程超時故障),列出介面即可。

  3. FC 與 FB 各舉三個適用例子,說明為什麼「有無內部記憶」是分界。

狀態機設計

理解為何順序邏輯應以狀態機組織;掌握「單一寫入點、轉移互斥、逾時處理」三大原則; 能以 ST 的 CASE 或 SFC 實作可靠的狀態機。

為什麼需要狀態機

自保持與互鎖能處理「組合邏輯」,但設備的本質是順序: 待機 \(\to\) 啟動 \(\to\) 運轉 \(\to\) 停止,每一步的合法事件都不同。 用一堆旗標與自保持硬拼順序,很快就會出現「旗標爆炸」: 沒人說得清楚 12 個 BOOL 的哪 4 個同時為真代表什麼狀態。 狀態機把系統行為收斂成一個狀態變數 + 一張轉移表,任何時刻只有一個狀態為真。

三大設計原則

  1. 單一寫入點:狀態變數只在一個 CASE 區塊(或一組集中的 rung)被寫入。 分散寫入會讓「誰最後寫誰贏」,正確性綁死在掃描順序上。

  2. 轉移條件互斥:同一狀態的多個出口不可同時成立;若可能同時成立, 必須明訂優先權(例如故障轉移永遠最優先)。

  3. 每個「等待中」的狀態都要有逾時出口:等不到的回授、卡住的工件, 逾時一律轉往 FAULT/ALARM 狀態,絕不默默跳回待機 (上一份審查報告的問題 6 正是反例:60 秒逾時直接回 IDLE,車還在車道裡)。

ST 標準樣板

TYPE E_DoorState : (INIT, CLOSED, OPENING, OPEN, CLOSING, FAULT); END_TYPE

VAR
    State      : E_DoorState;       // 唯一狀態變數
    tMove      : TON;               // 動作逾時
    tHoldOpen  : TON;               // 開門停留
    trigOpenCmd, trigReset : R_TRIG;
END_VAR

trigOpenCmd(CLK := di_RadarDetect OR cmd_OpenButton);
trigReset(CLK := cmd_Reset);

CASE State OF
  INIT:                                  (* 上電初始化:確認位置 *)
    IF di_ClosedLimit THEN      State := CLOSED;
    ELSIF di_OpenLimit THEN     State := OPEN;
    ELSE                        State := CLOSING;   // 位置不明,先關門
    END_IF;

  CLOSED:
    IF trigOpenCmd.Q THEN       State := OPENING;  END_IF;

  OPENING:
    IF di_OpenLimit THEN        State := OPEN;
    ELSIF tMove.Q THEN          State := FAULT;    // 逾時必入故障
    END_IF;

  OPEN:
    IF tHoldOpen.Q AND NOT di_SafetyBeam THEN
                                State := CLOSING;
    END_IF;

  CLOSING:
    IF di_SafetyBeam THEN       State := OPENING;  // 防夾:立即反向
    ELSIF di_ClosedLimit THEN   State := CLOSED;
    ELSIF tMove.Q THEN          State := FAULT;
    END_IF;

  FAULT:
    IF trigReset.Q THEN         State := INIT;     END_IF;
END_CASE;

(* 計時器由狀態驅動 *)
tMove(IN := (State = OPENING) OR (State = CLOSING), PT := T#15s);
tHoldOpen(IN := (State = OPEN), PT := T#5s);

(* 輸出是狀態的純函數 *)
do_MotorOpen  := (State = OPENING);
do_MotorClose := (State = CLOSING);
do_FaultLamp  := (State = FAULT);

注意樣板的固定結構:邊緣偵測 \(\to\) CASE 轉移 \(\to\) 計時器 \(\to\) 輸出。 輸出區沒有任何 IF——輸出永遠可由目前狀態唯一決定,除錯時看一眼狀態值就知道全機該有的行為。

SFC 觀點

SFC 用「步(Step)+轉移條件(Transition)+動作(Action)」畫出同一件事, 天生保證單一活動步。批次流程(清洗 \(\to\) 充填 \(\to\) 封蓋 \(\to\) 貼標)用 SFC 最直觀; 含大量平行分支或非順序性互鎖時,ST 狀態機較靈活。

狀態機四大反模式(皆為實案常見錯誤): (1) 狀態變數多處寫入;(2) 上電狀態(INIT)沒有出口; (3) 逾時直接跳回待機而非故障;(4) 復歸按鈕無條件強制改狀態。 寫完程式後拿這四條逐一檢查,可擋掉大多數現場事故。

  1. 為自動門範例畫出完整狀態轉移圖(含 FAULT 的進出)。

  2. 把第 5 章紅綠燈改為狀態機版本,並加入「行人按鈕:綠燈至少已亮 10 秒即提前轉黃」。

  3. 設計單車道雙向會車控制的狀態機(兩端感測器 A、B),列出狀態、事件、轉移表, 並說明你如何處理「兩端同時來車」與「車輛中途拋錨」。

類比訊號處理與 PID 控制

會做原始值與工程單位的換算與濾波;理解 PID 三項的物理意義; 掌握基本調參步驟與 anti-windup 等實務要點。

工程單位換算(Scaling)

類比模組把 4–20 mA 轉成原始整數(例如 0–27648 或 6400–32000,依平台而異)。 線性換算公式:

\[EU = EU_{\min} + \left( \frac{Raw - Raw_{\min}}{Raw_{\max} - Raw_{\min}} \right) \times (EU_{\max} - EU_{\min})\]

FUNCTION F_Scale : REAL
VAR_INPUT
    Raw : INT;
    RawMin, RawMax : INT;
    EuMin, EuMax   : REAL;
END_VAR
F_Scale := EuMin + (INT_TO_REAL(Raw - RawMin) /
           INT_TO_REAL(RawMax - RawMin)) * (EuMax - EuMin);
END_FUNCTION

搭配斷線偵測:4–20 mA 訊號低於對應 3.6 mA 的原始值即判定斷線, 鎖住最後有效值並發警報,不要讓 PID 拿斷線值去全開閥門。

濾波

一階低通濾波(指數平滑)就能解決大部分雜訊:

\[y_k = y_{k-1} + \alpha \,(x_k - y_{k-1}), \qquad 0 < \alpha \le 1\]

\(\alpha\) 越小越平滑但越遲鈍。注意濾波會增加相位延遲,控制迴路內的濾波要節制。

PID 原理

\[CV(t) = K_p\, e(t) + K_i \int_0^t e(\tau)\, d\tau + K_d \frac{de(t)}{dt}\]

  • P(比例):誤差多大就出多少力。只有 P 會留下穩態誤差。

  • I(積分):把「一直存在的小誤差」累積成力量,消除穩態誤差;太強會震盪。

  • D(微分):對誤差變化率反應,抑制過衝;對雜訊敏感,溫控常用、流量控制常關。

調參

Ziegler–Nichols 閉環法:先只開 P,加大 \(K_p\) 直到系統等幅震盪, 記下臨界增益 \(K_u\) 與震盪週期 \(T_u\)

控制器 \(K_p\) \(T_i\) \(T_d\)
P \(0.5\,K_u\)
PI \(0.45\,K_u\) \(T_u/1.2\)
PID \(0.6\,K_u\) \(T_u/2\) \(T_u/8\)

Z–N 給的是「能動的起點」,通常偏激進;實務上常再把 \(K_p\) 降 30–50%。 溫控類慢程序更常用開環階躍測試(反應曲線法)或廠商自動調諧(autotune)。

實務要點

  • 積分抗飽和(anti-windup):CV 已到上下限時停止積分累積, 否則設定值大幅變動後會嚴重過衝。多數平台內建,須確認有啟用。

  • 無擾切換(bumpless transfer):手動/自動模式切換時,讓 CV 從目前值延續。

  • 取樣週期:PID 應以固定週期任務執行(如 100 ms),不要放在掃描時間會漂移的主程式。

  • 方向(正/反作用):加熱是反作用(PV 高 \(\to\) CV 小)、 冷卻是正作用,接反的迴路會全速衝向極限。

  1. 原始值 0–27648 對應 4–20 mA、量程 0–16 bar:原始值 17280 是幾 bar?

  2. 只有 P 控制為什麼會有穩態誤差?I 項如何消除它?

  3. 一個溫控迴路超調嚴重、收斂很慢,你會先動 \(K_p\)\(T_i\) 還是 \(T_d\)?說明理由。

HMI 介接與警報設計

規劃清晰的 PLC–HMI 標籤介面;設計符合操作習慣的畫面層級; 建立有紀律的警報系統。

PLC 與 HMI 的分工

原則:所有控制邏輯在 PLC,HMI 只做顯示與命令發送。 HMI 腳本裡藏邏輯(例如按鈕按下時由 HMI 計算後直接寫多個標籤)是維護災難: HMI 當機或通訊中斷時邏輯就消失,而且沒人會去 HMI 裡找 bug。

標籤介面設計

為每台設備定義固定的介面結構,HMI 只讀寫這一區:

TYPE ST_HMI_Motor : STRUCT
    (* HMI -> PLC:命令 *)
    Cmd_Start, Cmd_Stop, Cmd_Reset : BOOL;   // PLC 收到後自行清除
    Cmd_SpeedSetpoint : REAL;
    (* PLC -> HMI:狀態 *)
    Sts_Running, Sts_Fault : BOOL;
    Sts_State   : INT;       // 對應列舉,HMI 以文字清單顯示
    Sts_Current : REAL;
END_STRUCT; END_TYPE

按鈕命令用「PLC 收到後清除」的握手方式(momentary 由 PLC 清 flag), 不要依賴 HMI 的「放開時寫 0」——通訊瞬斷時按鈕會「卡住」。 數值設定則要在 PLC 端做上下限檢查,永遠不信任來自 HMI 的原始輸入。

畫面設計原則

  • 層級:總覽 \(\to\) 區域 \(\to\) 設備詳情 \(\to\) 參數/維護,任何畫面三次點擊內可達。

  • 高效能 HMI 理念:灰階為主,顏色只留給異常(紅=故障、黃=警告), 正常運轉的畫面應該是「安靜」的。

  • 狀態顯示文字化(「運轉中/停止/故障」),不要只用顏色區分(色盲友善)。

  • 關鍵操作(清除計數、切換模式)加確認對話與權限分級。

警報設計

參考 ISA-18.2 的核心觀念:每個警報都必須「需要操作員採取行動」, 否則它是事件(event),只該進紀錄不該響鈴。

  • 每個警報定義:條件、延遲(防抖動)、優先級、操作員應採取的行動。

  • 類比警報加死區(deadband)延遲,避免在門檻附近抖動洗版。

  • 區分「未確認/已確認」與「動作中/已復歸」四種組合狀態。

  • 警報洪水(alarm flood)對策:分組抑制——上游斷電時抑制所有下游衍生警報。

  1. 為第 8 章的 FB_Motor 設計對應的 HMI 介面結構。

  2. 「油壓低」警報在門檻附近每秒進出十幾次,給出兩種平息它的方法。

  3. 為什麼「HMI 放開按鈕寫 0」不可靠?畫出通訊中斷時的事件順序。