Try   HackMD

I. 概要

NuttX 是一個類 Linux 的即時作業系統,並被 PX4 所採用,以下是一些簡單的學習筆記。

以下做一些目前接觸以來的直觀感受:

優點:

  • 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
  1. 編譯及安裝 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
  1. 下載及安裝 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
  1. 編譯及安裝 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)
  1. 開啟 ~/.bashrc 並在最尾端增加以下指令:
PATH=$PATH:${YOUR_PATH}/qemu/build
PATH=$PATH:${YOUR_PATH}/gcc-arm-none-eabi-9-2019-q4-major/bin
  1. 重新啟動終端機即完成所有流程

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 後可以測試 pshello 指令得到:

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 官方說明文件


4-1: NuttX Driver

術語:

  • Lower half driver: I2C, SPI, CAN 等週邊驅動程式
  • Upper half driver: 較高階的驅動程式,如 Network, Graphics, CAN, Serial, SPI Sensors, I2C Sensors, SD/MMC 等

一些常見週邊的使用:


4-2: Timer 定時器

先在 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?

RTOS Features -> Tasks and Scheduling  -> (hello_main) Application entry point

理論上可以自行設計一初始化程式,除了建立 NSH 的 Task 外,再載入其他應用程式的 Task。(待實驗後補上)


4-4: 任務控制 / 行程間通訊 / 同步機制

官方資料:

範例程式:

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)


4-6: Work Queues

官方資料:

範例程式:

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 進行網路連線

參考但步驟有異:

使用 NUCLEO-746ZG 進行實驗:

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

在 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
...

參考資料