前言
這一篇原本是 2017-12-21 寫的 (Pixnet 後台文章日期 2017-12-21 18:18:53), 但是因為後面一小段卡了, 一直沒發表. 後來因為工作內容轉為開發前台系統, 由於工作上有許多以前未接觸過的, 實在是空不下來, 也就很少有時間再碰 embedded system. 除非接手的團隊有問題回頭來請教, 非得要試一下才能給答案, 幾乎是沒機會再把它接上電源開起來. 所以它也就在未完成文章中趟了 4 年多, 今天就把後面一小段有關 std io 的 sample code 先拿掉, 作個小結. 發表出來. 至於有關 std io 的部份我想...就以後有緣再寫吧!?
關於 embedded system 的 C/C++
許多剛開始接觸 embedded system 的工程師常常會被 C 語言的底層搞得七葷八素. 最主要的是:
- 弄不清楚到底 embedded system 的 C/C++ 和一般在 Windows PC 用的 C/C++ 到底有什麼差異?
- 以及 (因為專案上的需要) 要如何才能把這些差異補起來?
在開始之前你一定先記住的是記憶體是稀缺資源, 一定不要浪費, 尤其是 local 變數區 (用到的是 STACK).
首先是 C/C++ 的差異. 這裡我們不談 C/C++ Compiler 所遵循的 C 版本標準之間的差異. Embedded system 和 Windows PC 二者所用 C/C++ 最主要的差異在:
- 程式的啟始部分: 一般 Windows PC 上用的程式都是從 main() 開始寫, 程式也是從 main() 開始執行. 但是 embedded system 在 main() 之前還有許多工作要完成:
MCU 的啟始程式 (可能是 Assembly 寫的, 也可能是 C 寫的). 其中包括: - 首先設定 MCU 的 STACK 指標, 以便程式可以呼叫副程式.
程式通常都已經將中斷向量預先掛上好. 使用者沒有使用的會先掛一個空的中斷處理程. 使用者可以稍晚再自行更動, 或者先掛上一個跳板程式. 這時所有的中斷都還是關閉的. - 如果是使用 DRAM 需要先把 DRAM 控制器設定好. (這時才能開始使用副程式)
- 如果系統是要在 RAM 裡執行, 需求把程式由 flash 抄到 RAM, 再切換到 RAM 上去執行.
- 把 CPU 的 clock 調對 (開始高速執行)
- 把系統計時器設好
- 跳到 __main 開始執行.
以上這一小段程式 '晶片供應商' 都會提供, 一般我們只會更動 STACK 及 HEAP 區的大小. 除非你是凡事喜歡自己來的高手, 一般我們不會去更動它的內容.
最後一個項目 "跳到 __main" 一般人都以為 __main 就是我們寫的 main(). 其實不是這樣的, 而是在 __main 和 main() 之間還有二個動作被 '開發工具' (IDE) 藏起來了:
- 接著載入有初始值的整體變數值 (global variable, 從 flash 抄到 SRAM 或 DRAM)
- 再將剩下的的整體變數值清為零.
這二個工作, 看似平常其實也可以作一點文章. 例如: 在打包成 flash 的燒錄檔時, 利用簡單的壓縮程式將 main() 之後的整個程式連同有初始值的整體變數整個壓縮起來. 所以第一小段的程式就會變成將它們解壓縮 (到 RAM 裡). 當然, 前提是你所用的 MCU 支援在 RAM 裡執行(註一). 同時你所實作的系統裡壓縮省下來的空間放得下你用的壓縮程式. (不然, 是弄好玩的?)
我以前也有見過, 有人乾脆專案裡不寫 main(), 而且這二件事也直接自己做.
註一: 許多小型的 MCU 只能在 flash 裡執行 (基於地址區段用途不同的原因). 還有, 一般在 RAM 裡執行速度要快一些. SRAM access time 為 2~3 ns, DRAM access time 約 20~35 ns, flash 則約 100 ~ 200 ns. 所以你所選用的 CPU 執行用的系統 clock 落在 100M Hz 以上 (10ns 以下) 應該就可以考慮是否改成 execute in RAM, 而不是 execute in flash 了.
- 標準的 C/C++ 語言是沒有關於 '中斷處理程式' 的語法. 所以這一部分都是 IDE 廠商自行修改 C/C++ Compiler 擴充出來的. 所以不同的晶片或者是不同的 IDE 語法就會不同.
- 除非是開發 Windows PC 用的外接設備, 或者是主機板的軔體工程師, 不然大部份的使用者是通常是不會碰到中斷處理程式的.
- 大部份初學 embedded system 的工程師都是直接使用晶片供應商提供的 BSP (Board Support Pack) 裡的驅動程式. 但是稍有經驗的 Embedded system 工程師都會遇到 BSP 所提供的驅動程式不符合系統需求, 甚至是有錯 (不可原諒...). 所以做為一個 embedded system 工程師, 一定要有辦法修改中斷處理程式甚至是 '重寫' 中斷處理程式. 不過這一段通常需要研讀大量的手冊及進行實驗, 才能完成, 所以啊, 會寫中斷處理程式才是 embedded system 工程師真正的價值. 尤其是會寫網路驅動程式的工程師: 不論是外掛 Ethernet 晶片 + Switch 晶片, 或者是內建 Ethernet 模組, 甚至是 TCP/IP 加速模組. 這些都需要一些年月的刻苦研讀 TCP/IP 理論, trace 程式, 以及艱苦的把晶片手冊啃懂, 還有無數個深夜獨自一人加班作實驗, 改 driver.
- malloc() 相關的問題: 它和 STACK 及 HEAP 的實作直接相關. 通常比微型再大一點的系統都有可能會遇上, 和系統的複雜度直接相關. 雖然大部份都可以用固定個數的 array, 配合二個環型索引 (start/end) 來暫代, 但是最根本的記憶體配置不足的問題還是沒有解決, 所以總是覺得不妥...
- printf() 相關的問題: 一般 embedded system 工程師如果是遇到很小顆的 MCU, 例如: 8 位元的 8051 晶片 2K/4K Flash, 或是 PIC 晶片, 所要面對的就是: 沒有 printf() 可用, 或者 printf() 太大需要瘦身.
- 試想你開發 8051 或者 PIC 用的程式, 可以使用 C 寫, 不必動用 Assembly 已經是非常快樂的事了, 怎麼還會要求有 printf() 可以用呢?
- 可是你會說: 啊! 我就是有需要啊! 我要從 UART 送一些訊息出來啊... 我還想要送一些除錯用的訊息出來啊... orz
- 還有 stdin, stdout, stderr 這三個標準檔案 handler: 一般比較少動到這一區, 但是還是有可能...
其他的...想到再補充吧!