先判斷計算是否依賴前一次資料
同一個換算公式能讓十個感測器共用,但十個按鈕的前一次狀態不能混在一起。本篇用兩個小案例,教你判斷何時選Function、何時需要Function Block,以及建立第二個實例到底隔離了什麼。你將先完成沒有歷史狀態的比例換算,再用兩組不同輸入序列驗證邊緣偵測的狀態獨立性。
本文採CODESYS的Function Block物件說明作為平台參考。功能塊透過實例呼叫,每個實例有自己的輸出與內部狀態,可在兩次呼叫之間保存,因此相同的當前輸入未必產生相同輸出。這裡的保存指正常程式執行期間的狀態延續,不代表斷電後必然保留;保持宣告與重新啟動行為是另一件事。
| 工作內容 | 本篇選擇 | 原因 |
|---|---|---|
| Y = X × Gain + Offset | 無副作用的Function | 結果只依本次完整輸入 |
| 判斷本次是否由0變1 | 各訊號獨立的Function Block實例 | 需要上一次輸入 |
| 累加每站事件數 | 各站獨立狀態容器 | 需要目前累計與重置規則 |
不要只看輸出數量決定種類。實際語言和開發環境可能允許多種參數形式;本篇的設計重點是資料依賴與狀態所有權。即使你把換算寫成FB,也不會自然變得更可靠;若把共享全域資料藏進函式,表面上沒有實例,也不能把它視為純粹可重複的計算。
先做可獨立驗算的換算函式
我們定義三個輸入X、Gain、Offset,一個輸出Y,公式是Y等於X乘Gain再加Offset。這裡只使用小範圍整數案例,避免先引入浮點誤差。正式使用時仍應定義合法範圍、中間值寬度與無效資料的回報,不要因公式簡單就忽略乘法溢位。
| 呼叫順序 | X | Gain | Offset | 預期Y |
|---|---|---|---|---|
| 第一次 | 3 | 2 | 10 | 16 |
| 第二次 | 7 | 2 | 10 | 24 |
| 第三次 | 3 | 2 | 10 | 16 |
| 第四次 | 3 | 4 | −2 | 10 |
第三次重送第一組輸入,應再次得到16。中間曾用X等於7,不應影響結果。把呼叫順序改成第二、第一、第四、第三組,再按各組輸入對照,答案仍應相同。這個測試直接檢查函式是否偷用了上次結果、全域偏移值或某個未傳入的工站設定。
實作介面時把Gain與Offset一起傳入,不要讓A站呼叫前先修改一份全域Gain,再讓B站靠碰巧的執行順序取得自己的倍率。若參數來自可同時更新的結構,呼叫前先取得一致的設定快照;否則函式本身即使沒有狀態,也可能收到一半新、一半舊的輸入。
再建立兩份互不干擾的前值
邊緣案例使用Current、Previous與Pulse。教學邏輯為先計算Pulse等於Current且非Previous,再把Previous更新成Current。順序不能顛倒,否則你先把前值寫成目前值,就永遠看不到差異。本文把A與B的Previous初值都定為0,每個掃描各呼叫一次,輸出Pulse也在每次呼叫重新計算。
| 掃描 | A輸入 | A呼叫前Previous | A Pulse | B輸入 | B Pulse |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 0 | 0 |
| 2 | 1 | 0 | 1 | 0 | 0 |
| 3 | 1 | 1 | 0 | 1 | 1 |
| 4 | 0 | 1 | 0 | 1 | 0 |
請先手算B站的前值:每次呼叫前依序為0、0、0、1。建立EdgeA與EdgeB兩個獨立實例,將A輸入只接到EdgeA,B輸入只接到EdgeB。你要看到A只在第二掃描產生脈衝,B只在第三掃描產生脈衝;A回到0時,不得把B的前值一起清掉。
上述是明確定義的教學演算法,不是宣稱所有廠商現成邊緣指令在開機時都採同一政策。若本篇初始Previous為0,而第一次Current已為1,會產生一次脈衝。若需求是開機不計這次事件,就增加初始化旗標,第一次只建立Previous並輸出0,後續才正常判斷,且另做開機輸入為1的測試。
故意共用實例 看見錯誤怎麼形成
為了理解隔離的必要性,先在紙上分析錯誤接法,不必下載到實體設備。假設同一個Edge實例先處理A再處理B。在第二掃描,A為1、B為0;A呼叫把Previous寫成1,B呼叫隨即把它改回0。到第三掃描A仍為1,卻又看到前值0,因此A被誤判成再次上升。
第三掃描接著處理B為1時,Previous已被A改成1,B真正的上升緣反而不見了。這個例子同時造成A多算一次與B少算一次;如果你只看總事件數,有時兩種錯誤可能抵消。測試因此必須按工站分別記錄脈衝及累計,不能只驗證總數相同。
把呼叫順序改成B先、A後,錯誤位置又可能變化。正確的兩實例設計,在輸入快照相同且沒有其他共享狀態時,交換兩站呼叫順序不應改變各站結果。若仍會改變,查是否共用了內部計時器、全域暫存、輸出變數或診斷結構;建立兩個名稱不同的實例,不等於外部所有資料已經隔離。
也不要只在輸入為1時才呼叫邊緣實例。若輸入為0時跳過呼叫,Previous就沒有機會更新為0,下次變1可能偵測不到。呼叫節奏是模組契約的一部分。需要停用某站時,應明確定義停用期間仍取樣、凍結或重置前值,恢復時再依對應政策驗收,不能讓條件呼叫暗中決定。
操作步驟與驗收結果
在不連接實體輸出的測試程式中,先準備兩份輸入快照、兩個實例及各自結果。每掃描只在固定位置呼叫一次,立即把輸出存到A與B各自的觀察變數。依表格逐掃描送入資料,記下前值與脈衝;再重置兩實例,以交換呼叫順序的方式重跑一次。兩次各站的結果應完全一致。
完成後應看到什麼結果
換算案例每次相同輸入應得到相同Y;邊緣案例A只在第二掃描、B只在第三掃描輸出1。正確重置後重跑應重現同一結果。開機ON、停用再恢復,以及同一實例誤呼叫兩次的案例,應能以已寫明的規格解釋結果。
失敗時先查哪裡
先查呼叫端使用的是FB類型名稱還是正確實例,再查A與B是否引用同一份狀態。脈衝一直為0,查前值更新順序及條件呼叫;脈衝多一次,查初始化政策、同掃描重複呼叫及共享前值。結果只在換呼叫順序時改變,優先查全域變數與外部映射,不要先把問題歸因於PLC速度。
常見問題與適用限制
一、FB一定斷電保持嗎?不一定,須另查保持設定及重啟行為。二、只宣告實例就會執行嗎?不會,還需要符合平台規則的呼叫。三、同一實例能連續呼叫兩次嗎?若平台允許,第二次會接續第一次留下的狀態,不可假定兩次獨立。四、邊緣偵測能補回掃描間漏掉的短脈波嗎?不能,未被取樣的變化需要適當的硬體捕捉方案。
本文以CODESYS物件語意及通用狀態演算法教學,未指定Q系列裝置位址。移植時應確認目標軟體的Function支援方式、FB宣告、輸入輸出傳遞與初始化政策。現成R_TRIG是可查詢的標準庫範例,本篇自訂的開機政策仍應以自己的測試規格為準。