2020年4月26日 星期日

Real-Time Embedded System 心得(3): RTOS 應用基本系統分析

上一篇 ChamberPlus 大老留言提到事前分析的重要,那 RTOS 應用要怎麼分析呢?這個可以講的東西太多了,往底層一路講到 cache 命中率也不奇怪,這邊就講幾個筆者比較熟,偏向軟體也比較基本的一塊。


1. 如何劃分 Priority?

這大概是剛接觸 RTOS 應用的工程師最頭痛的部份,也可能是最重要的部份。

前面提到的 uC/OS-II 其實已經是1x 年前的產品,後面的 uC/OS-III 乃至於當紅的 FreeRTOS 都支援 time-slicing,哪怕你把兩個 task 設成相同 priority,其中一個 task 時間額度用完,OS kernel 就會切到另一個 task,那這樣把 task 全部設成相同 priority 不就免煩惱? 但這樣何必用 RTOS?

所以 uC/OS-II 這個老 RTOS 反而可以迫使我們對重要的問題思考 - 那劃分 priority 的依據是什麼?依筆者所知:
  1. 依執行頻率劃分:執行頻率越高,priority 越高,反之越低
  2. time critical
可以想見 1,2 有相當程度的重疊,1 還有學理上的分析(後面會提到)。這兩項剛好可以用筆者熟習的 HMI 說明:


控制器通訊 task(歐美稱為 protocol driver):
  • 高頻率
  • time critical
  • 以上兩者除了筆者所舉的例子,Linux/Windows 也不約而同把 TCP/IP stack 作為 kernel 的一部分。
UI task:
  • 低頻率: 即便傳統電視視訊格式 NTSC 也只有 30FPS,PAL 只有 25FPS,甚至 15FPS 其實就相當夠用了,見另一篇關於子彈時間的討論
  •  non time critical: 差個幾 ms(甚至更大)並沒有什麼影響,如果你能看出來可以考慮當戰鬥機駕駛員或者當電競選手。

2. 事後諸葛亮分析法

這恐怕才是大部分人碰到的問題,沒辦法科技公司的流動率太高了,老闆不會懂你的難處,他的直覺是我都投資這麼多錢下去了,你來一句話就要砍掉重練,那前面的投資等於丟到水裡了。

而且 debug 或優化時,了解各個 task 的執行頻率也是必要的。

那要如何分析?筆者的作法是統計一秒內,各個 task 佔用的百分比,以 uC/OS-II 來說,他有一個 global variable - OSCtxSwCtr:
//ucos_ii.h OS Version : V2.85
//...
// Counter of number of context switches
OS_EXT INT32U OSCtxSwCtr; 
OS kernel 每次進行 context switch 時,都會遞增 OSCtxSwCtr,另外同時也遞增每個將被執行的 task 的 OSTCBCtxSwCtr:
//ucos_ii.h OS Version : V2.85
//...
typedef struct os_tcb {
//...
// Number of time the task was switched in
INT32U           OSTCBCtxSwCtr;
//...
};
//os_core.c
void OSIntExit (void)
{
    //...
    if(OSIntNesting==0) /* Reschedule only if all ISRs complete ... */
    {                          
        if(OSLockNesting==0)  /* ... and not locked. */
        {                      
            OS_SchedNew();
            if(OSPrioHighRdy!=OSPrioCur)  /* No Ctx Sw if current task is highest rdy */
            {          
                OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];
                #if OS_TASK_PROFILE_EN > 0
                OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task  */
                #endif
                OSCtxSwCtr++;                  /* Keep track of the number of ctx switches */
                OSIntCtxSw();                  /* Perform interrupt level ctx switch       */
            }
        }
    }
    //...
}

void OS_Sched(void)
{
    //...
    if(OSLockNesting==0)   /* ... scheduler is not locked */
    {                     
        OS_SchedNew();
        if(OSPrioHighRdy != OSPrioCur)   /* No Ctx Sw if current task is highest rdy  */
        {         
            OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
            #if OS_TASK_PROFILE_EN > 0
            OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
            #endif
            OSCtxSwCtr++;   /* Increment context switch counter */
            OS_TASK_SW();
        }
    }
    //...
}
清除 OSTCBCtxSwCtr, OSCtxSwCtr 並不會對 OS kernel 造成影響,所以你可以做出一個統計 task,在每秒結束時將每個 task 的 OSTCBCtxSwCtr*100/OSCtxSwCtr,就能得到該 task 佔用的百分比,這個數據可以灌進 Excel 裡畫出折線圖,然後泡杯咖啡好好觀察是哪裡不對勁,比方說有時系統卡死了,那你就會看到某 task 快衝到 100%了。

其實在原書第一章裡,作者就給出了範例,甚至還精細到每個 task 佔用了多少 us,但筆者的作法比較簡單,因為書中的 uC/OS-II 版本太舊,沒有提供 OSTCBCtxSwCtr:


當紅的 FreeRTOS,也提供了類似的資訊

3. CPU 佔用率

如果您留意的話,應該會注意到上圖下方有一個 CPU Usage:


這跟 Windows 上的"CPU 使用率"意義相同:


對於大多數寫純軟體的工程師來說,他們對於這個數字可能沒太大感覺,但對於 Real-Time System 來說,這個弄不好產品是不能賣的,甚至會弄出人命。那多少以下才是合理的?根據 uC/OS-II 作者在書中的說法應該小於 70%,這個當然有學理上的依據,稱為 RMA(Rate Monotonic Analysis),這在上個世紀就由清大前校長劉炯朗博士與 Layland, J 分析完畢,對細節有興趣的朋友除了 uC/OS-II 原書外,也可以參考 Real-Time Concepts for Embedded Systems 一書。

雖然學理上是小於 70%,但其實是越小越好,筆者親眼見證過超過 60% 其實系統就開始遲緩了。越高會造成 priority 越低的 task 越少機會分配到 CPU,甚至可能完全分不到 CPU,那你的產品就會很有問題了。

這個 CPU Usage 是依據一個很簡單的條件計算出來的,你在 Windows 上也見過:


基本上,這個 idle task 被分配的 CPU time 越多,代表 CPU 負載越低,在 RTOS 裡通常就是最低 priority 的 task。

4. CPU 使用率 - 讓老闆看得懂

寫了一大堆,那要怎麼讓老闆看得懂,不然至少也要讓其他同事(包含硬體工程師)一眼就能看得懂?除了用 UART 印一大堆訊息外,這邊再提供幾個爭議少的可視化方法,這些方法取自 The Art of Designing Embedded Systems,這本書非常值得收藏,裡面提供了很多實務技巧與見解。

4.1 toggle GPIO

如果你的 MCU/MPU 上有閒置的 GPIO,這個方法最簡單,把閒置的 GPIO pin 設定為輸出,在 idle task 內不斷對其on/off,如果 CPU usage 小,那你會看到密集的方波,如果 CPU usage 大就會看到方波出現的很少不密集,如果到了 100%,那你就會到電影中常出現的 - 病床上去世的人的心跳圖變成一直線。


4.2 觀察 duty cycle

這個方法可以用示波器,也可以用電錶。跟 4.1 不一樣的地方是在 idle task 對 GPIO on,而切換到 non-idle task 時對 GPIO off,於是如果 CPU 越閒,在示波器上看到的方波 low 的部份就越少,如果你拿電錶監測,如果 GPIO 的位準是 3.3V,趨近 3.3V 代表 CPU 越閒,反之趨近 0,那代表你的系統有問題,這邊要注意的是電錶的反應速度比起示波器要慢多了,所以示波器還是較好的選擇。

4.3 R-2R

這個方法對於寫軟體的人來說太麻煩了,而且要花很多時間解釋,也需要更多 GPIO 與硬體,這邊只大概提一下,有興趣的朋友請自行查書:


簡單來說就是炮製一個簡單的 D/A 轉換器,以上 PIO0-2 都設定為 output,每次在 task switch 時都輸出 outportb(test_port, task_id),這樣就可以得到:
  1. 0v: task 0(task_id = 0)
  2. 1/8v(如果是5V = 5*1/8):  task1
  3. 以此類推


  通常要解釋的越多,聽眾吸收越差,質疑也就越多,通常我只推薦 4.1...

5. 實務探討

筆者在工控打滾的這段期間內發現一件事,就是台廠除了產品比歐美日便宜外,一些細節就比不上進口貨了,當然也有例外,像是那個跟筆者有點淵源的 xx4,他們就會把這些細節測乾淨(所以人家才能一年發到12月年終),但有些公司能聽得懂人話就要偷笑了。

5.1 HMI 寸動(jog)

一台工業設備總免不了需要微調,有可能是馬達轉個幾度,或者某個部份前進幾mm:


筆者之前服務的公司,這個部份就有待加強,進口貨是按一下設備動一下,筆者做的那台是有時按很多下動一下,或者是按很多下沒反應,這很多下累積到 buffer(FIFO) 裡一次送出,這甚至差點把客人的設備搞壞。

所以這也是 ChamberPlus 老大講的,怎麼搞了這麼多年還是弄不出一個精緻的軟(軔)體,既不事前分析也不事後檢討,結果可想而知。

5.2 PLC 通訊模組

筆者之前接觸過歐美日的 PLC 通訊模組,注意到以下幾件事:
  • 可以接受的連線數極少,只有個位數
  • 在通訊協議裡加上容忍多少延遲之類的參數
以前筆者不懂,現在知道這是怎麼一回事了,就是這些通訊模組,怎樣也不能影響主控制器的行為(例如 scan time),而這一切都是經過仔細測試分析過的,如前面提到的執行頻率、CPU 負載 etc...


這也難怪一向把成本看很重的台廠,一邊抱怨貴一邊掏錢買了,也難怪如三菱、西門子能成為百年企業,人家老工程師是家有一老如有一寶,台廠是家有一老如有一草,老闆恨不得快點汰舊換新,還搬出諸如平均年齡太大影響公司活力的奇葩說法,當然部份老工程師們號稱10年經驗1年用10次也不能怪人家,只能說是歷史共業啊..

5.3 FxxA

那個 8 核心 FxxA MCU 某種程度上來說算作對方向,因為他可以調整各個核心的執行頻率,不過以這間公司後來一路作到上興櫃推出超低價 MCU 來說,大概跟多核心沒什麼關係,也許是因為這種產品始終處於一個尷尬的位置,會用 RTOS 的人很少會用 8bit MCU,而且還是只能寫組合語言、記憶體少的要命的 OTP MCU,會用這種 MCU 的也不覺得 RTOS 有什麼必要,所以...

2 則留言: