ST?那是什麼,能吃嗎
什麼是ST?
用純文字來寫PLC的語言
原先是PASCAL語言,所以你可以在markdown中用以下方法來描述ST語言
VAR
xFlag : BOOL;
iData : INT;
diData : DINT;
byData : BYTE;
rData : REAL;
lrData : LREAL;
END_VAR;
xFlag := iData > 0;
iData := 2;
diData := iData * 3 - 2;
rData := TO_REAL(diData) / 5;
以這段代碼來說,首先定義了各種變數,然後寫了一些示範用的程式
簡單,易懂,而且不會像階梯圖那樣,需要一堆專有指令(比如 |-[> D0 0]--------(Y0)-|)這種東西
但如果是圖呢?
|-[> D0 0]--------------------(Y0)-|
|-|M8000|---------------[MOV 2 D0]-|
|-|M8000|-----------[DMUL D0 3 D1]-|
|-|M8000|-----------[DSUB D1 2 D1]-|
|-|M8000|-----------[DSUB D1 2 D1]-|
|-|M8000|--------------[FLT D1 D3]-|
|-|M8000|-----------[EDIV D3 5 D5]-|
…這什麼 這到底是什麼閃現 歐齁齁齁齁

這還是我用純文字冩喔,如果是三菱的階梯圖界面,那麼整個畫面你只能一次看到10行左右
差別一看就懂

為什麼你應該冩ST?
- 純文字
- 自由轉換到別家(除了日系長得有點奇怪以外)
- 效率極高 空間、時間、資源層面上都是
為什麼你應該放棄階梯圖?
- 效率超低,除錯困難
- 綁死廠家
- 階梯圖有ST做不到的事情,比方說可以
不掃描特定行的IF, CASE等語句
ST怎麼寫?
what does the fox say?
這邊不講基本語法,請自行google
講幾個好用的東西
狀態機
也就是流程,流程是個好東西
VAR
iStep : INT;
xFlag1 : BOOL;
xFlag2 : BOOL;
xExecute : BOOL;
xReset : BOOL;
xStop : BOOL;
xPuase : BOOL;
xBusy : BOOL;
xDone : BOOL;
xError : BOOL;
xPaused : BOOL;
xOutput1 : BOOL;
_iLastStep : INT;
fbTON_3S : TON;
END_VAR
//FB盡量定義在CASE外面比較不容易發生"FB不掃描,導致狀態凍結"的問題
//除非你知道你在幹嘛
fbTON_3S(In:=iStep = 2, PT:=T#3S);
//直接要求停止,當然也可以限定條件來停止
IF xStop THEN
iStep := 0;
END_IF;
IF xPause THEN
_iLastStep := iStep;
iStep:=8000;
END_IF;
//主要狀態機
CASE iStep OF
0:
//初始化
xBusy := FALSE;
xDone := FALSE;
xOutput := FALSE;
xError := FALSE;
iErrorID := 0;
//執行
IF xExecute THEN
xBusy := TRUE;
iStep := iStep + 1;
END_IF;
1:
//第一步
IF xFlag1 THEN
iStep := iStep + 1;
END_IF;
2:
//第二步
IF xFlag2 THEN
xOutput := TRUE;
END_IF;
IF NOT xFlag1 THEN//意外狀況要等待Reset
iStep := 9000;
END_IF;
IF fbTON_3S.Q THEN//三秒後下一步
iStep := iStep + 1;
END_IF;
3:
//第二步
IF NOT xFlag2 THEN
xOutput := FALSE;
END_IF;
IF NOT xFlag1 THEN//意外狀況要等待Reset
iStep := 9000;
END_IF;
IF fbTON_3S.Q THEN//三秒後下一步
iStep := iStep + 1;
END_IF;
8000:
//暫停前的步驟,如果需要一定程序停止可以寫在這
Output:=FALSE;
iStep:=iStep+1;
8001:
//暫停中
xPaused := TRUE;
IF NOT xPause THEN
iStep:=iStep +1;
END_IF;
8002:
//恢復步驟
//如果不能直接還原iStep,需要在這邊記錄你要指定的上一步
//這邊假設回上一步一定要回第一步,也可以直接寫成_iLastStep
iStep := 1;
9000..9999:
//各種錯誤,同時iStep所在的號碼就是錯誤類型,可以定義從9000~9999的號碼
xError:=TRUE;
IF xReset THEN
iStep:=FALSE;
END_IF;
ELSE//跑到沒冩(unhandle)的區塊就當做完成
xBusy := FALSE;
xDone := TRUE;
IF NOT xExecute THEN
iStep:=FALSE:
END_IF;
END_CASE;
狀態機可以窮舉各種情況,並且一一對應在每個階段應該做的事情
甚至拋出錯誤,回上一步,強制停止等等功能都沒問題,可謂是居家旅行,殺人滅口,必備良藥。
在階梯圖裡面很難做到的暫停/恢復功能也變得超級簡單
Function Blocks & Functions
- Function Blocks(FB) : 功能塊
- Function : 功能
差別就是是否可以實例化,也就是內部變數是否會在掃描時間內保存
反過來說,就是 跟時間變化有關係的都不能做成一個簡單的函數
從最簡單的R_TRIG,到TON,再到更複雜的MC_MoveAbsolute之類的運動控制命令都是FB,也必須是FB
而完全沒差的,比方說TO_DINT(iData)之類,就是普通的Function
舉例:R_TRIG的實現看起來就像這樣
Q := In AND NOT _xLast; // 這個掃描 1、上個掃描 0 → 抓到上升緣
_xLast := In;
這也就能側面映證三菱的語法裡面包含LDP之類的函數,可以在語法上以Function層級的東西提供FB才能做到的功能,基本上就是黑魔法(指那種用了不正當手段做到的功能)
估計是在編譯器底層註冊了一些固定的Register地址,並且透過行號順序自動分配地址來做到的,我猜系統要是寫得足夠大,LDP遲早會遇到沒作用的問題(當然在那之前就因為步數過大而無法灌進去了,再次說明三菱有多噁心人)
Function也不總是沒用處的,像是有些很常用的功能你就不會希望每用一次就要初始化一個Object
比方說+, -, *, /這類運算子在底層也都是Function的形式,只不過編譯器給你語法糖,讓你可以用iResult=(iData + 1) * 2取代MUL( ADD( iData, 1 ), 2)這種噁心的東西
不過要是您用的是階梯圖,那得耗子尾汁了,或是你可以跟我一樣不講武德,直接用ST進行降維打擊
俗話說得好,打不過就加入。
tips:
請正確的根據功能切割FB,最後再組在一起
比方說一個轉盤上面有N站,每一站都有各自不同的工作
那麼就應該每一站都建立為單獨的FB,各自有單獨的流程
千萬不要硬冩流程,會死人的(這種現場控制,沒冩好可能是物理意義上的會死人)
因為各家建立FB的方式都不一樣,這邊就不示範了
不過上面那個狀態機的抽象方法應該可以給你點啟發,反正是個狀態機流程就應該有iStep, xExecute之類的東西
tricks
一些小花樣,可以方便的做到一些不那麼直觀好懂的事情,就寫在這了
為了方便就不特別冩變數定義了
除非指定數值,否則總是為0
根據程序由左至右,由上到下,依照順序執行的特性,我們就可以通過在不同行寫入不同的數值,並依照執行順序來自然決定Data的數值,不需要任何流程
iData := 0;
IF xFlag1 THEN
iData := 1;
END_IF;
IF xFlag2 THEN
iData := 2;
END_IF;
弄出一個對時間的Pulse
總是會有這種需求: 每500ms,計數+1
所以你可以利用一個簡單的TON做到這件事情
fbTON(In:=NOT fbTON.Q, PT:=T#500ms);
IF fbTON.Q THEN
iData := iData + 1;
END_IF;
位元存取
特別在通訊傳輸情況容易發生
有時候會有把某個16-bit Data拆成16個單獨的訊號
正常情況下需要利用AND操作符對WORD做操作
看起來像這樣xFlag13 := (wData AND 16#2000) <> 0;
但是,在PLC有專屬於WORD, DWORD, QWORD的語法糖,可以這樣寫xFlag13 := wData.13
只不過很可惜的.13不能用變數處理
所以想要一個FOR拆16個變數?千萬別這麼幹,去學地址的概念吧