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;
FOR/WHILE 迴圈在單一掃描週期內跑完全部迭代。 迴圈次數過大或條件永真會拖垮掃描時間、觸發看門狗。 「等待某事發生」永遠不要用 WHILE 空轉,而是讓狀態機跨週期等待。
語言選擇準則
實務上常見的健康組合:主流程與設備邏輯用 ST(好版控、好重構)、 與現場電氣直接對應的互鎖與輸出層用 LD(電機人員可即時監看)、 清楚的批次順序用 SFC。同一專案混用語言是正常且被鼓勵的。
把第 4 章的自保持電路改寫成 ST(提示:
Motor := (Start OR Motor) AND NOT StopPressed;)。用
CASE改寫第 5 章的紅綠燈練習,比較與計時器串接版本的可讀性。哪些邏輯你會堅持留在 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_PhotoEyeA、do_GateOpen |
| ai_ / ao_ | 類比輸入/輸出 | ai_TankLevel |
| cmd_ / fb_ | 命令/回授 | cmd_Start、fb_Running |
| t / c | 計時器/計數器 | tHoldDwell、cPartsOut |
| E_ / ST_ / FB_ | 列舉/結構/功能塊型別 | E_MotorState |
INT計數器每秒 +100,多久溢位?換成DINT呢?為第 5 章停車場範例設計一個
ST_ParkingLot結構與合理命名。寫出判斷 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」而已。若要複製貼上大段邏輯再逐行改名, 表示抽象層次不對。
把
FB_Motor擴充:加入運轉時數累計(RunHours)與「維修模式禁止啟動」輸入。設計
FB_Valve(開/關命令、雙極限回授、行程超時故障),列出介面即可。FC 與 FB 各舉三個適用例子,說明為什麼「有無內部記憶」是分界。
狀態機設計
理解為何順序邏輯應以狀態機組織;掌握「單一寫入點、轉移互斥、逾時處理」三大原則; 能以 ST 的 CASE 或 SFC 實作可靠的狀態機。
為什麼需要狀態機
自保持與互鎖能處理「組合邏輯」,但設備的本質是順序: 待機 \(\to\) 啟動 \(\to\) 運轉 \(\to\) 停止,每一步的合法事件都不同。 用一堆旗標與自保持硬拼順序,很快就會出現「旗標爆炸」: 沒人說得清楚 12 個 BOOL 的哪 4 個同時為真代表什麼狀態。 狀態機把系統行為收斂成一個狀態變數 + 一張轉移表,任何時刻只有一個狀態為真。
三大設計原則
單一寫入點:狀態變數只在一個
CASE區塊(或一組集中的 rung)被寫入。 分散寫入會讓「誰最後寫誰贏」,正確性綁死在掃描順序上。轉移條件互斥:同一狀態的多個出口不可同時成立;若可能同時成立, 必須明訂優先權(例如故障轉移永遠最優先)。
每個「等待中」的狀態都要有逾時出口:等不到的回授、卡住的工件, 逾時一律轉往 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) 復歸按鈕無條件強制改狀態。 寫完程式後拿這四條逐一檢查,可擋掉大多數現場事故。
為自動門範例畫出完整狀態轉移圖(含 FAULT 的進出)。
把第 5 章紅綠燈改為狀態機版本,並加入「行人按鈕:綠燈至少已亮 10 秒即提前轉黃」。
設計單車道雙向會車控制的狀態機(兩端感測器 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 小)、 冷卻是正作用,接反的迴路會全速衝向極限。
原始值 0–27648 對應 4–20 mA、量程 0–16 bar:原始值 17280 是幾 bar?
只有 P 控制為什麼會有穩態誤差?I 項如何消除它?
一個溫控迴路超調嚴重、收斂很慢,你會先動 \(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)對策:分組抑制——上游斷電時抑制所有下游衍生警報。
為第 8 章的
FB_Motor設計對應的 HMI 介面結構。「油壓低」警報在門檻附近每秒進出十幾次,給出兩種平息它的方法。
為什麼「HMI 放開按鈕寫 0」不可靠?畫出通訊中斷時的事件順序。