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]-|

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

為什麼你應該冩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個變數?千萬別這麼幹,去學地址的概念吧