2017年7月8日 星期六

高精度 Sleep() 在工業通訊的應用

以 Modbus RTU 通訊時,兩個 Frame 必須至少間隔 3.5 char 的時間:

以常用的 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 時客戶的痛點在哪,可惜啊~

6 則留言:

  1. 還好,我已經脫離了工控的領域…

    回覆刪除
  2. 每個搞MCU 的老闆都說:唉~消費性電子價錢太殺了。不好做! 我們應該要跨行工控。

    結果在這裡卻是不同的想法。只能說:做一行怨一行啊。

    回覆刪除
    回覆
    1. 工控一個客戶一年一千台就算多了,一年出只1K MCU...恐怕MCU老闆會覺得這個量@#$^&...真的要讓MCU老闆滿意,大概只有研華、台達這種,但台灣有幾家研華台達?

      另一種工控產品會有量,就是做sensor,但問題又來了,sensor規格太多,很容易就搞出一堆庫存,所以台灣也沒幾家敢玩(可能要去對岸或歐美玩?)

      而且工控動不動要求你5-10年不能斷貨,如果公司太小,這搞工控的也會怕怕的

      講到最後...都不好做啊,哈哈哈

      刪除
  3. hi, 好奇問問..
    因為我平常在開發的是embedded Linux
    看到你說需要3.65秒的精度,第一個想到的是usleep()
    考量到你說在windows上開發
    找了一下windows上是否也有usleep()可以用
    ref:
    http://hant.ask.helplib.com/c++/post_1308504

    看起來應該可以?

    回覆刪除
    回覆
    1. 感謝回應,你的連結中有提到

      1. QueryPerformanceCounter
      這個我用過,不過你用了會發現CPU loading飆升,這個比較適合計算時間間隔而非拿來替代 Sleep()

      2.另外SetWaitableTimer這個在我文章中的連結也有提到,我實驗過後發現效果也沒有比Chrome用的方法好

      其實3.65ms並非真的要追求這麼準的精度,一般來說比這個略大就可以了,事實上,很多 modbus device 本身也沒有那麼精準,市面上大多數 modbus device 也沒有 modbus 組織官方認證。

      刪除
  4. 這都是workaround,也難以保證,會飄主要是Win OS排程的方式,SetWaitableTimer因為是APC應該在大多數應用就很足夠了,要更精準是driver (IRQL),但你還是難保證別人是不是搶過去用如網路卡,若要足夠,也是用獨立子系統來處理,如SoC/MCU

    回覆刪除