四格緩衝區先定義三個狀態量
本例由單一任務擁有Buffer與所有索引,讀取安排在寫入完成後。Buffer[0..3]只有四格,NextWrite 指向下一次寫入的實體位置,Count 表示目前有效筆數,OldestIndex 指向最舊資料。空緩衝時 Count=0;未滿時 Count 由 1 增加到 4;滿緩衝時 Count 保持 4,新的資料依政策覆寫或拒收。三個量必須一起設計,不能只靠 NextWrite 猜資料順序。
本文先定義覆寫模式,再另外說明拒收模式。覆寫模式寫入第五筆 50 時,Buffer[0] 被替換,時間順序變成 20、30、40、50,OverwriteCount 增加一。拒收模式在滿緩衝收到 50 時不改四格內容,RejectCount 增加一。兩種政策的畫面提示和驗收結果不能混用。
| 狀態 | NextWrite | Count | OldestIndex |
|---|---|---|---|
| 空 | 0 | 0 | 0 |
| 寫入10後 | 1 | 1 | 0 |
| 寫入40後滿 | 0 | 4 | 0 |
| 覆寫50後 | 1 | 4 | 1 |
| 覆寫60後 | 2 | 4 | 2 |
案例值固定為 10、20、30、40、50、60,故意不用重複數字。這樣不論看實體排列或時間順序,都能追蹤每一次回捲。任何實作都要在測試紀錄中寫明索引從 0 還是 1 開始;本文所有索引均從 0 開始。
逐筆寫入並核對實體與邏輯順序
覆寫模式從空狀態開始。寫入 10 到 Buffer[0],接著 NextWrite=1、Count=1、OldestIndex=0。寫 20 到 Buffer[1],再依序寫 30 和 40。寫 40 完成後四格是 10、20、30、40,NextWrite 回到 0,Count=4,OldestIndex 仍是 0。這裡的回捲只影響下一寫入位置,不代表資料順序從零開始讀。
| 寫入值 | Buffer[0..3] | NextWrite | Count | Oldest | 邏輯順序 |
|---|---|---|---|---|---|
| 10 | 10,空,空,空 | 1 | 1 | 0 | 10 |
| 20 | 10,20,空,空 | 2 | 2 | 0 | 10,20 |
| 30 | 10,20,30,空 | 3 | 3 | 0 | 10,20,30 |
| 40 | 10,20,30,40 | 0 | 4 | 0 | 10,20,30,40 |
| 50 | 50,20,30,40 | 1 | 4 | 1 | 20,30,40,50 |
| 60 | 50,60,30,40 | 2 | 4 | 2 | 30,40,50,60 |
寫入 50 的計算是:物理位置 NextWrite=0,寫入後下一位置為 1;因為原本已滿,OldestIndex=(0+1) mod 4=1;Count 仍為 4;OverwriteCount 從 0 變 1。寫 60 同理使用物理位置 1,OldestIndex 變成 2。這些數字可以逐列重算,不靠圖形猜測。
在提交前讀取 NextWrite、Count 與政策狀態。
若未滿,寫入 NextWrite 後 Count 加一;若剛滿,維持 Count=4。
NextWrite=(NextWrite+1) mod 4,必要時同步更新 OldestIndex。
覆寫時增加 OverwriteCount,拒收時不改 Buffer 與 NextWrite。
把物理陣列和從 OldestIndex 讀出的邏輯序列一起記錄。
覆寫與拒收必須是兩套規格
覆寫模式適合只保留最近四筆的診斷快照,但它會遺失最舊資料,必須讓使用者知道已覆寫幾筆。拒收模式適合每筆都必須被上層確認的佇列;滿載時新資料不能靜默丟掉,應回報 FULL 並讓來源重試或由流程處理。不要把『Buffer 滿了』寫成模糊的成功。
| 政策 | 滿載寫50 | Buffer | 計數 | 呼叫端看到 |
|---|---|---|---|---|
| 覆寫 | 接受 | 50,20,30,40 | OverwriteCount+1 | 成功但最舊遺失 |
| 拒收 | 拒絕 | 10,20,30,40 | RejectCount+1 | FULL |
| 覆寫後讀 | 讀最近四筆 | 邏輯20,30,40,50 | ReadCount+4 | 可回放 |
| 拒收後重試 | 等待空位 | 仍為原四筆 | 每次被拒嘗試各計一次 | 待處理 |
政策不能在同一個執行期間任意切換。若 HMI 允許切換,必須先處理既有資料、記錄版本,再從明確狀態開始;否則同一個 Count=4 可能在一掃描被解讀成覆寫,在下一掃描被解讀成拒收。本文驗收分別建立兩個實例,清楚標示哪一個採哪種政策。
溢位計數也要定義。若OverwriteCount是有限型別,必須選回捲、飽和或報錯之一,本篇採飽和並標示Overflow;不能假設永遠不會滿。測試時可將計數器預設在上限附近,用兩次覆寫確認所選政策,並在報告中說明計數回捲是否會影響日誌解讀。
讀取快照要保持索引與資料一致
讀取邏輯順序不是 Buffer[0] 到 Buffer[3] 的固定順序,而是從 OldestIndex 開始,讀 Count 筆,每次以 4 回捲。寫入 60 後 OldestIndex=2,所以讀取索引為 2、3、0、1,得到 30、40、50、60。讀取結果應附序號或時間戳,否則兩筆數值相同時無法證明先後。
若寫入與讀取在不同任務,讀取可能剛拿到 OldestIndex=1,寫入任務隨即把 Buffer[1] 改成 60,造成索引和資料不屬於同一時刻。可依平台支援採用鎖、暫停提交、雙重版本或複製快照;本文不假定某個廠牌指令。核心驗收是讀者拿到的 Buffer、NextWrite、Count、OldestIndex 必須來自同一個一致版本。
本例單任務先完成資料列,再更新索引與Count,完成後才進入讀取階段。
單任務在沒有其他寫入者的階段複製狀態與資料;若跨任務改用平台已核對的同步快照。
跨任務若採版本法,需具備更新中標記、原子版本讀寫、可見性順序與不重用世代等前提,不能只在寫完才加版本。
從快照的 OldestIndex 走 Count 步,不讀取尚未有效的格子。
測試讀取期間插入 50 或 60,確認結果是舊快照或新快照,不是混合序列。
| 快照版本 | Oldest | Count | 讀取索引 | 結果 |
|---|---|---|---|---|
| A | 0 | 4 | 0,1,2,3 | 10,20,30,40 |
| B | 1 | 4 | 1,2,3,0 | 20,30,40,50 |
| C | 2 | 4 | 2,3,0,1 | 30,40,50,60 |
若環形資料要送往報表或通訊端,應先複製成邏輯順序的連續快照,再交給下游。直接傳送 Buffer[0] 到 Buffer[3] 會讓接收端看見實體排列,卻不知道 OldestIndex。快照欄位至少包含序號、Count、OldestIndex、是否曾覆寫與資料有效性。
對拒收模式做重試測試:Buffer 滿載時送入 50,第一次回覆 FULL;單一管理任務接受取走要求後,先讀OldestIndex那格,再令OldestIndex=(OldestIndex+1) mod 4、Count減一;此時Count=3,重送 50 才成功。若重試沒有唯一序號,可能把同一筆 50 寫入兩次。覆寫模式則要讓呼叫端知道 50 已取代哪一筆,而不是只回傳 TRUE。
空 滿 回捲與計數驗收
驗收不只看最後一列。空緩衝讀取應回報 EMPTY,不能回傳 Buffer 內的舊值;剛寫入第一筆時只能讀一筆;滿緩衝時讀四筆;覆寫第五、六筆後,邏輯順序必須分別是 20、30、40、50 和 30、40、50、60。這些預期值能同時檢查 Count、OldestIndex 和模數回捲。
| 測試 | 預期 Count | 預期讀取 | 計數結果 |
|---|---|---|---|
| 空讀 | 0 | EMPTY | 無增加 |
| 寫10 | 1 | 10 | 無覆寫 |
| 寫40 | 4 | 10,20,30,40 | 無覆寫 |
| 寫50覆寫 | 4 | 20,30,40,50 | Overwrite=1 |
| 寫60覆寫 | 4 | 30,40,50,60 | Overwrite=2 |
| 拒收模式寫50 | 4 | 10,20,30,40 | Reject=1 |
測試溢位計數時另設小型教學計數器,本篇另用八位元教學計數器並選飽和規則:OverwriteCount起始254,兩次覆寫後依序255、255,另設Overflow為真提示已達計數上限。這個小數值只用於驗證型別行為,不代表任何現場設備的計數上限。若計數會回捲,報表必須搭配回捲事件或更寬的累計值。
重啟也是案例的一部分。若緩衝資料未保存,啟動後應明確設為空並清楚標示;若需求要保留,則要驗證 Buffer、Count、NextWrite 和 OldestIndex 是否以同一版本保存。不能只恢復 Count=4 而讓索引回到 0,這會把邏輯順序讀錯。
逐步追蹤時把物理位置和邏輯位置分開寫。例如寫入 50 後,物理 Buffer[0]=50,但它不是邏輯序列的第一筆;邏輯第一筆是 Buffer[1]=20。寫入 60 後,物理位置 1 被更新,邏輯順序則從物理 2 開始。若報表直接把陣列索引當作時間順序,會把 50、60 排到最前而產生錯誤趨勢。
讀取快照驗收可在複製期間安排一次寫入。若版本號前後不同,整批讀取應標成 RETRY,不能回傳三筆舊資料加一筆新資料。若平台只能逐欄複製,仍須先建立有上述保證的同步協定;單有完成旗標不保證資料未在讀取中被覆寫。
FAQ 限制與官方來源
問:滿了之後一定要覆寫嗎?不一定,覆寫與拒收是不同政策,必須依資料遺失容忍度選擇。問:為什麼不能從 Buffer[0] 讀到 Buffer[3]?回捲後最舊資料可能在 1、2 或其他位置,固定順序會把物理位置誤當時間順序。問:不同任務同時讀寫可以直接看四格嗎?不應直接看,先建立一致快照並檢查版本。問:OverwriteCount 可以代替遺失資料嗎?不行,它只能告知發生幾次覆寫,不能還原內容。
適用型號與限制:本文用固定四格與六筆合成數值示範環形緩衝概念,沒有提供可直接編譯的 GX Works 或 Q 系列語法。實際陣列型別、模數運算、任務同步、保持記憶體、計數器回捲與斷電恢復須依目標 CPU、工程軟體版本及官方文件驗證。
參考:CODESYS Array data type and indexing
參考:CODESYS Object: Task(任務與程式呼叫順序)