---
# System prepended metadata

title: 在 STM32 上使用 NuttX 的實驗筆記
tags: [embedded-system]

---

## 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)