以常用的 9600bps, n81 來說 (none parity, 8 data bits, 1 stop bit),換算的結果是:
不過在 Windows 下,以筆者平常使用的 Windows 7 家用版為例,Sleep() 的精度並不理想 。
以前面的 3.65ms 為例,因為 Sleep() 只接受整數、單位為 ms, 3.65ms 四捨五入就是 Sleep(4)。測試程式如下:
#include <Windows.h> #include <mmsystem.h> #include <stdio.h> int main() { LARGE_INTEGER frequency; // ticks per second LARGE_INTEGER t1, t2; // ticks // get ticks per second double elapsedTime; QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&t1); Sleep(4); QueryPerformanceCounter(&t2); elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart; printf("%f ms\n", elapsedTime); return 0; }輸出結果:
由上圖可知擺蕩的很厲害。這對想要盡量拉高串口通訊速度、或是對 Modbus RTU device 做精密測試的人來說根本不適用。
某天同事 Willy 先生發現 Google Chrome 打開時 Sleep() 突然會變得很精準(最近我發現 VMWare 打開也會),當下我們就把 Chrome source code 下載回來,原來他是做了這些手腳:
#include <Windows.h> #include <mmsystem.h> #include <stdio.h> int main() { LARGE_INTEGER frequency; // ticks per second LARGE_INTEGER t1, t2; // ticks // get ticks per second double elapsedTime; timeBeginPeriod(1); QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&t1); Sleep(4); QueryPerformanceCounter(&t2); elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart; printf("%f ms\n", elapsedTime); timeEndPeriod(1); return 0; }但測試下去發現 Sleep() 還是一樣會飄:
先延遲 100ms 就沒有這個問題了(似乎不會馬上致能):
#include <Windows.h> #include <mmsystem.h> #include <stdio.h> int main() { LARGE_INTEGER frequency; // ticks per second LARGE_INTEGER t1, t2; // ticks // get ticks per second double elapsedTime; timeBeginPeriod(1); Sleep(100); QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&t1); Sleep(4); QueryPerformanceCounter(&t2); elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart; printf("%f ms\n", elapsedTime); timeEndPeriod(1); return 0; }輸出結果:
至於 Linux,可以參考這篇。
除此之外還有哪些應用?像是建築自動化使用的 BACnet MS/TP protocol 就要求至少要有 5ms 的精度,西門子 S7-200 PLC 如果沒有在對的時間送出封包,則根本不會理你。
有心的讀者應該會發現這篇沒提到 Overlapped IO,有興趣的朋友可以參考 libevent,libevent 使用 heap 管理 timer/timeout,multiplexing IO 處理這一塊並非易事。
另外筆者在網路上發現有人寫了一個 nanosleep for Windows,不過測試起來的效果比本篇的方法還差。
也難怪 Linux 在嵌入式、IoT 把 Windows 壓著打,連這些基本需求都沒滿足...
Deterministic
為什麼要這麼龜毛,差一點不行嗎?通常這種對 timing 有嚴格要求的 RS232/422/485 protocol 稱為 deterministic protocol。
deterministic 中文翻譯為「決定性」、「確定性」。可以想成緊密嵌合的齒輪,當齒輪連動時,每個齒輪轉多少度、幾圈、位置都是可預測的。
回到通訊這邊來,可類比的東西就是 clock,但 RS232/422/485 屬於非同步傳輸,要模擬出這種效果,就只有靠精準的 delay、timeout。
可能還是會有人覺得...frame 送得快送得慢也沒差,反正對方收得到就好!?
Modbus 最早是 Modicon 發展出來,Modicon 的本業是 PLC,PLC 通常用來控制機械設備。設想如果 PLC (Modbus slave)收到命令做一個動作,而 Modbus Master 不是用固定、穩定的間隔傳送命令,不用說大家也猜得到,因為誤差傳撥,這個機械設備動起來一定會怪怪的。
講個實際案例,很多機械設備上都有「寸動(jog)」這種功能,簡單來說就是按個按鈕,馬達可能要轉一度,前進一小格...因為每台機械設備生產出來都會有些微誤差,或是使用久了要人工調整。
如果今天您沒有把「deterministic」放在心裡,那客戶在使用寸動調整時就會抓狂,覺得怎麼這麼難調整,忽快忽慢的,what the fuck!?
林伸茂老師很久前講過一句名言:
「學軟體的人沒有時序的觀念,學電子的人沒有重量的觀念」
時至今日,小弟才想通,過去幾年做 HMI/SCADA 時客戶的痛點在哪,可惜啊~
還好,我已經脫離了工控的領域…
回覆刪除每個搞MCU 的老闆都說:唉~消費性電子價錢太殺了。不好做! 我們應該要跨行工控。
回覆刪除結果在這裡卻是不同的想法。只能說:做一行怨一行啊。
工控一個客戶一年一千台就算多了,一年出只1K MCU...恐怕MCU老闆會覺得這個量@#$^&...真的要讓MCU老闆滿意,大概只有研華、台達這種,但台灣有幾家研華台達?
刪除另一種工控產品會有量,就是做sensor,但問題又來了,sensor規格太多,很容易就搞出一堆庫存,所以台灣也沒幾家敢玩(可能要去對岸或歐美玩?)
而且工控動不動要求你5-10年不能斷貨,如果公司太小,這搞工控的也會怕怕的
講到最後...都不好做啊,哈哈哈
hi, 好奇問問..
回覆刪除因為我平常在開發的是embedded Linux
看到你說需要3.65秒的精度,第一個想到的是usleep()
考量到你說在windows上開發
找了一下windows上是否也有usleep()可以用
ref:
http://hant.ask.helplib.com/c++/post_1308504
看起來應該可以?
感謝回應,你的連結中有提到
刪除1. QueryPerformanceCounter
這個我用過,不過你用了會發現CPU loading飆升,這個比較適合計算時間間隔而非拿來替代 Sleep()
2.另外SetWaitableTimer這個在我文章中的連結也有提到,我實驗過後發現效果也沒有比Chrome用的方法好
其實3.65ms並非真的要追求這麼準的精度,一般來說比這個略大就可以了,事實上,很多 modbus device 本身也沒有那麼精準,市面上大多數 modbus device 也沒有 modbus 組織官方認證。
這都是workaround,也難以保證,會飄主要是Win OS排程的方式,SetWaitableTimer因為是APC應該在大多數應用就很足夠了,要更精準是driver (IRQL),但你還是難保證別人是不是搶過去用如網路卡,若要足夠,也是用獨立子系統來處理,如SoC/MCU
回覆刪除