## I. 概要 [NuttX](https://nuttx.apache.org/) 是一個類 Linux 的即時作業系統,並被 [PX4](https://px4.io/) 所採用,以下是一些簡單的學習筆記。 以下做一些目前接觸以來的直觀感受: **優點:** * POSIX 風格: 不同 OS 平台間可保持良好的可移植性 (FreeRTOS 相對較差) * 內建檔案系統、乙太網路等模組,不需要如 FreeRTOS 自行想辦法引入第三方軟體 * Mutex 支援優先權繼承 **缺點:** * 複雜性較高 (FreeRTOS 簡潔有力) FreeRTOS 作為 Software library 提供了最基本的多工、行程間通訊、同步機制等功能,但 Nuttx 相對起來更像是一個如同 Linux 的現代作業系統。 ## II. 安裝開發工具 1. 安裝基本套件: ``` sudo apt install build-essential git zlib1g-dev libsdl1.2-dev automake* autoconf* \ libtool libpixman-1-dev lib32gcc1 lib32ncurses5 libc6:i386 libncurses5:i386 \ libstdc++6:i386 libusb-1.0.0-dev ninja-build ``` 2. 編譯及安裝 OpenOCD: ``` git clone git://git.code.sf.net/p/openocd/code openocd cd openocd ./bootstrap ./configure --prefix=/usr/local --enable-jlink --enable-amtjtagaccel --enable-buspirate \ --enable-stlink --disable-libftdi echo -e "all:\ninstall:" > doc/Makefile make -j4 sudo make install ``` 3. 下載及安裝 ARM GCC toolchain 9: ``` wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2 tar jxf ./gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2 rm gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2 ``` 4. 編譯及安裝 QEMU: ``` git clone git://git.qemu.org/qemu.git cd qemu git submodule init git submodule update --recursive mkdir build cd build ../configure make -j $(nproc) ``` 5. 開啟 `~/.bashrc` 並在最尾端增加以下指令: ``` PATH=$PATH:${YOUR_PATH}/qemu/build PATH=$PATH:${YOUR_PATH}/gcc-arm-none-eabi-9-2019-q4-major/bin ``` 6. 重新啟動終端機即完成所有流程 ## III. 編譯及使用 QEMU 執行 NuttX NuttX 系統結構分為 Kernel 本體以及 App,可以使用以下指令分別取得: ``` mkdir nuttx-git cd nuttx-git git clone https://github.com/apache/nuttx.git nuttx git clone https://gitbox.apache.org/repos/asf/nuttx-apps.git apps ``` 接著使用以下指令編譯 NuttX: ``` cd nuttx tools/configure.sh stm32f4discovery:nsh #nsh console/UART2 make menuconfig make -j $(nproc) ``` 最終可以使用 QEMU 並將 UART2 重導向至 stdio 進行模擬: ``` qemu-system-arm -M netduinoplus2 -kernel nuttx.bin -serial /dev/null -serial stdio ``` 進入 NSH 後可以測試 `ps` 和 `hello` 指令得到: ``` nsh> ps PID GROUP PRI POLICY TYPE NPX STATE EVENT SIGMASK STACK COMMAND 0 0 0 FIFO Kthread N-- Ready 0000000000000000 001016 Idle_Task 2 2 100 RR Task --- Running 0000000000000000 002016 nsh_main nsh> hello Hello, World!! ``` ## IV. 一些常用功能的實驗 這裡針對一些我有興趣的功能進行測試以及紀錄。 NuttX 提供了許多範例程式碼,因此若要了解各個功能的使用,最快的方式就是瀏覽這些範例。這一點個人認為與 NuttX 抽象化程度高有關,因此許多功能並不直接依賴硬體平台。 [NuttX 官方說明文件](https://nuttx.apache.org/docs/latest/) --- ### 4-1: NuttX Driver * [#048: Overview about NuttX Drivers](https://www.youtube.com/watch?v=L-98aIHbTaY&list=PLd73yQk5Fd8JEsVD-lhwYRQKVu6glfDa8&index=40) 術語: * **Lower half driver**: I2C, SPI, CAN 等週邊驅動程式 * **Upper half driver**: 較高階的驅動程式,如 Network, Graphics, CAN, Serial, SPI Sensors, I2C Sensors, SD/MMC 等 一些常見週邊的使用: * [#007 - Using PWM on NuttX](https://www.youtube.com/watch?v=1DWK77Djg6Y&list=PLd73yQk5Fd8JEsVD-lhwYRQKVu6glfDa8&index=8) * [#019: Reading Analog Input on NuttX](https://www.youtube.com/watch?v=oSowWYLDDRI&list=PLd73yQk5Fd8JEsVD-lhwYRQKVu6glfDa8&index=19) * [#037: How to use USB Console on NuttX](https://www.youtube.com/watch?v=5BznGFvodmg&list=PLd73yQk5Fd8JEsVD-lhwYRQKVu6glfDa8&index=36) * [#038: How to use SDCard on NuttX](https://www.youtube.com/watch?v=H28t4RbOXqI&list=PLd73yQk5Fd8JEsVD-lhwYRQKVu6glfDa8&index=36) --- ### 4-2: Timer 定時器 * [設定啟用 Timer](https://nuttx.apache.org/docs/latest/components/drivers/character/timer.html) * [Timer signal handler 範例](https://github.com/apache/nuttx-apps/blob/master/examples/timer/timer_main.c) 先在 menuconfig 中進行以下設定: ``` System Type -> [*] SysTick timer driver -> STM32 Peripheral Support -> [*] TIM2 Device Drivers -> Timer Driver Support -> [*] Timer Support -> [*] Timer Arch Implementation Application Configuration -> Examples -> [*] Timer example ``` 開機載入 NSH 後執行: ``` nsh> timer ``` --- ### 4-3: 修改開機後預設執行程式 預設情況下開機後會載入 NSH, 若要執行使用者定義的應用程式則須透過 NSH 輸入指令後載入。實際上我們可以在 NuttX 的 menuconfig 中修改預設執行的程式。 見 [How to start directly my application instead starting NSH?](https://nuttx.apache.org/docs/latest/faq/index.html#how-to-start-directly-my-application-instead-starting-nsh) ``` RTOS Features -> Tasks and Scheduling -> (hello_main) Application entry point ``` 理論上可以自行設計一初始化程式,除了建立 NSH 的 Task 外,再載入其他應用程式的 Task。(待實驗後補上) --- ### 4-4: 任務控制 / 行程間通訊 / 同步機制 **官方資料:** * [Task Control Interfaces](https://nuttx.apache.org/docs/latest/reference/user/index.html) * [Named Message Queue Interfaces](https://nuttx.apache.org/docs/latest/reference/user/04_message_queue.html) * [Counting Semaphore Interfaces](https://nuttx.apache.org/docs/latest/reference/user/05_counting_semaphore.html) **範例程式:** * [Message queue 範例程式](https://github.com/shengwen-tw/nuttx-stm32/blob/master/message_queue/my_app.c) * [Semaphore 範例程式](https://github.com/shengwen-tw/nuttx-stm32/blob/master/semaphore/my_app.c) `menuconfig`: ``` Application Configuration -> Examples -> [*] NuttX STM32 Message Queue Example [*] NuttX STM32 Semaphore Example ``` ``` nsh > mqueue nsh > semaphore ``` **優先權繼承:** NuttX 支援優先權繼承,考慮以下情境: * 有高-中-低優先權 Task: A, B, 和 C * Task C 取得了 semaphore, 因此可以存取某個受保護的資源 * 接著 Task C 進入休眠狀態,以讓更高優先權的 Task 被執行 * Task A 接著嘗試取得 semaphore, 但因 semaphore 仍被 Task C 持有,因此必須等到 Task C 釋出後 Task A 才能取得。 * 接著 Task C 被允許繼續執行,但卻被中優先權 Task B 搶佔而再次進入休眠狀態。 像是本例中原本優先權最高的 Task A 卻必須反過來等待其他低優先權 Task (即 B 和 C) 的狀況我們稱為 "**優先權反轉 (Priority Inversion)**"。 要解決這樣的問題可以使用所謂"**優先權繼承**"的機制,一般**教科書**上有兩種做法: 1. **Priority Inheritance Protocol (PIP):** 將 Task C 優先權暫時提高到它阻礙到的其他 Task 中的最大優先權 2. **Priority Ceiling Protocol (PCP):** 將 Task C 的優先權暫時提高到所有可能佔有這個鎖的 Task 的最大優先權 根據文件描述,NuttX 應該是實做了 **Priority Inheritance Protocol (PIP)** 的機制,但也允許開發者自行設計所想要的繼承機制。 --- ### 4-5: 高性能、零延遲中斷 (High Performance, Zero Latency Interrupts) * [High Performance, Zero Latency Interrupts](https://nuttx.apache.org/docs/latest/guides/zerolatencyinterrupts.html) * [範例程式](https://github.com/apache/nuttx/blob/master/boards/arm/stm32/viewtool-stm32f107/src/stm32_highpri.c) --- ### 4-6: Work Queues **官方資料:** * [Work Queues](https://nuttx.apache.org/docs/latest/reference/os/wqueue.html) **範例程式:** * [Work queue 範例程式](https://github.com/shengwen-tw/nuttx-stm32/blob/master/work_queue/my_app.c) `menuconfig`: ``` Application Configuration -> Examples -> [*] NuttX Work Queue Example ``` ``` nsh> work_queue ``` 用於將某些任務卸載 (off-loading) 到多個 Threads 上,可分為: * **High Priority Kernel Work queue:** 中斷 (Interrupt) 觸發 work queue 後,經過一些延時卸載任務到單個或多個 Thread 完成。某些 Driver 的設計會需要這樣的機制。同時也負責 Intterupt 的 Garbage collection。 * **Low Priority Kernel Work Queue:** 處理其他一些擴充性功能用,如 File system clean-up, 記憶體垃圾回收 (Memory garbage collection), 以及 異步 I/O 操作 (asynchronous I/O operations) --- ### 4-7: 透過 Ethernet 進行網路連線 參考但步驟有異: * [#042: How to use Ethernet-over-USB (RNDIS) on NuttX](https://www.youtube.com/watch?v=8noH8v7xNgs&t=158s) 使用 [NUCLEO-746ZG](https://www.st.com/en/evaluation-tools/nucleo-f746zg.html) 進行實驗: ![](https://hackmd.io/_uploads/Skietx323.jpg) ``` cd nuttx-stm32/nuttx/ tools/configure.sh nucleo-144:f746-nsh make menuconfig ``` `menuconfig` 須要進行以下設定 (有些選項有相依性,請依照順序設定): ``` RTOS Features -> Work queue support -> [*] High priority (kernel) worker thread Networking Support -> Networking Support -> [*] Networking support -> Link layer support -> [*] Local loopback -> TCP/IP Networking -> [*] Enable TCP/IP write buffering -> UDP Networking -> [*] UDP Networking -> [*] UDP broadcast Rx support -> ICMP Networking Support -> [*] Enable ICMP networking -> ARP Configuration -> [*] ARP send -> [*] Collect network statistics Application Configuration -> Network Utilities -> [*] Telnet daemon -> Network initialization -> [*] Network initialization thread -> [*] Hardware has no MAC address -> NSH Library -> [*] Have architecture-specific initialization Device Drivers -> [*] Network Device/PHY Support ``` STM32 以及 路由器的 IP 位址以及 遮罩可由 `menuconfig` 進行修改: ``` Application Configuration -> Network Utilities -> Network initialization -> IP Address Configuration -> (0x0a000002) Target IPv4 address -> (0x0a000001) Router IPv4 address ``` 其中 `0x0a000002` 將 STM32 的 IP 位址設定為 `10.0.0.2`,而 `0x0a000001` 將路由器的 IP 位址設定為 `10.0.0.1`。 接著進行編譯以及燒錄韌體: ``` make -j16 cd .. ./flash.sh ``` 將 STM32 透過 RJ45 線材接到 Linux 主機 (在此用作路由器) 後進行以下設定: * Static IPv4 (Manual) * IP Address: 10.0.0.1 ![](https://hackmd.io/_uploads/HkfKoen2h.jpg) 在 Linux 主機上輸入: ``` ping 10.0.0.2 ``` 即可得到如下回應: ``` 64 bytes from 10.0.02: icmp_seq=0 ttl=64 time=0.060 ms 64 bytes from 10.0.02: icmp_seq=1 ttl=64 time=0.053 ms 64 bytes from 10.0.02: icmp_seq=2 ttl=64 time=0.072 ms ... ``` --- ## 參考資料 * [Getting Started with NuttX – STM32F4 Discovery (Unix)](https://cwiki.apache.org/confluence/display/NUTTX/STM32F4DISCOVERY+Unix) * [Write an application on NuttX ](https://hmchung.gitbooks.io/stm32-tutorials/content/write-an-application-on-nuttx.html) * [NuttX guides](https://github.com/micro-ROS/NuttX/issues/2)