Zephyr RTOS 〜 GPIO を叩く！ 〜

# 1. はじめに

前回紹介したリポジトリを使って、GPIO へのアクセスや移植性を維持した運用例について例示していきたいと思います。

[前回の記事はこちら](https://qiita.com/Corgeek/items/ca4c515ccf556551562f) [^1]

公式のLチカを見たとき、「コレジャナイ」感、ありませんでした？
そして、DeviceTree とマクロの仕組みを頑張って読み解き、理解し始めたとき、
改めて「やりたいことは理解できるけど、やっぱりコレジャナイ」感…ワカリマス。

なので、おそらく多くの方が求めていたであろう、直感的でわかりやすい GPIO の叩き方の紹介です。

今回は [nucleo_f401re](https://docs.zephyrproject.org/4.1.0/boards/st/nucleo_f401re/doc/index.html) [^2] を例に話を進めますが、引き続き [bbc_microbit](https://docs.zephyrproject.org/4.1.0/boards/bbc/microbit/doc/index.html) [^3] / [bbc_microbit_v2](https://docs.zephyrproject.org/4.1.0/boards/bbc/microbit_v2/doc/index.html) [^4] でも確認できます。

# 2. まずは公式のLチカを改めて眺める

初めてだと見慣れない多重マクロに面を食らうと思いますが、落ち着いて読むと Arudino とやってることはほとんど同じです。

* 関数の外で定義・宣言 == Arudino の各ヘッダー内部の実装
* ループの外で初期化 == Arduino の setup() 相当
* 以降のループや割込みイベント等で随時 GPIO を叩く == Arduino の loop() 相当


違いといえば、PA_0 とか、Serial だとか、__使うつもりがなくても__ あらかじめ宣言されている所を、明示的に宣言して確保する手続きを踏んでいるぐらいです(そこが重いんですが)。

以下は、サンプルを機能毎に3分割しただけなので流し読みでOKです。

```c:zephyr/samples/basic/blinky/src/main.c 定義・宣言部分
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS   1000

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)

/*
 * A build error on this line means your board is unsupported.
 * See the sample documentation for information on how to fix this.
 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
```

```c:zephyr/samples/basic/blinky/src/main.c 初期化部分
int main(void)
{
	int ret;
	bool led_state = true;

	if (!gpio_is_ready_dt(&led)) {
		return 0;
	}

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return 0;
	}
```

```c:zephyr/samples/basic/blinky/src/main.c 実際に叩く部分
	while (1) {
		ret = gpio_pin_toggle_dt(&led);
		if (ret < 0) {
			return 0;
		}

		led_state = !led_state;
		printf("LED state: %s\n", led_state ? "ON" : "OFF");
		k_msleep(SLEEP_TIME_MS);
	}
	return 0;
}
```

# 3. 実際の運用を想定した作り(仮)

では、[ZephyrOpsPlaybook](https://github.com/Corgeek/ZephyrOpsPlaybook) [^5] を使って、[nucleo_f401re](https://docs.zephyrproject.org/4.1.0/boards/st/nucleo_f401re/doc/index.html) の LED を明滅させてみます。
playbook/scripts/setup.bat (Windows) or playbook/scripts/setup.sh (Ubuntu) ファイル内で `BOARD_TYPE=nucleo_f401re` を指定した上で setup を実行後、rebuild & flash

:::note info

※ west_env.bat がない場合はこちら

Windows 向け
https://github.com/Corgeek/ZephyrOpsPlaybook/blob/master/docs/README_lnx.md#27-west-コマンドの簡略用スクリプトを準備

Ubuntu 向け
https://github.com/Corgeek/ZephyrOpsPlaybook/blob/master/docs/README_lnx.md#27-west-コマンドの簡略用スクリプトを準備

:::

```c:playbook/core/main.c
#include <zephyr/kernel.h>
#include "boards/unique.h"

int main(void)
{
	gpio_pin_configure(GREEN_LED.port, GREEN_LED.pin, GPIO_OUTPUT_ACTIVE);

	while (true) {
		gpio_pin_toggle(GREEN_LED.port, GREEN_LED.pin);
		k_msleep(1000);
	}

	return 0;
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38092/ea7c72d1-f6d7-920b-440a-b7f25a4120ee.gif" width=95%>

スッキリ…！

<details><summary>ネタばらし</summary>

上記ができるように、ヘッダー側で下記を定義をしています。
また、事前に device_is_ready() でポートの状態を確認した上で gpio_pin_configure() を呼ぶべきでしょう。

```c:playbook/include/boards/unique.h
struct gpio_port_pin {
    const struct device *const port;
    const gpio_pin_t pin;
};

#define GPIO_PORT_PIN(_port, _pin)          ((struct gpio_port_pin){ .port = DEVICE_DT_GET(DT_NODELABEL(_port)), .pin = (_pin) })
```

```c:playbook/include/boards/st/nucleo_f401re.h
#define GPIOA_PIN05                 GPIO_PORT_PIN(gpioa,  5)        // PA_5, D13, SPI1_SCLK, LED1, PWM2/1, ADC1/5
#define	GREEN_LED					GPIOA_PIN05
```

</details>

<details><summary>さらにネタばらし</summary>

`struct gpio_port_pin` は blinky でも使用している `struct gpio_dt_spec` から `gpio_dt_flags_t dt_flags;` を削除した下位互換です。
flag を使って pin の設定を動的に変える処理が多い設計であれば `struct gpio_dt_spec` をそのまま使う方が良いと思います。

```c:zephyr/include/zephyr/drivers/gpio.h 実際の定義
struct gpio_dt_spec {
	/** GPIO device controlling the pin */
	const struct device *port;
	/** The pin's number on the device */
	gpio_pin_t pin;
	/** The pin's configuration flags as specified in devicetree */
	gpio_dt_flags_t dt_flags;
};
```

</details>

# 4. 課題

先述の実装はシンプルではありますが、例えば、2023年度モデル用の基板と2024年度モデルの基板はほとんど共通で、pin-assign の一部が異なる、といったケースが非常によくあるかと思います。
そういったケースに備え、基板やモデル依存の処理は分けて実装する必要が出てきます。
# 5. 公式 Zephyr の管理方法
ここで、公式 Zephyr の管理方法を見てみます。
公式では各 SoC やモデルを boards という単位で依存部分を分けて管理しています。
(他にもシリーズモノは zephyr/dts や zephyr/soc にも派生して管理しています)

```
.
├── playbook/                                 # ZephyrOpsPlaybook リポジトリ
└── zephyr/                                   # 公式の Zephyr リポジトリ
    └── boards/                               # boards 依存の処理
        ├── bbc/                              # ベンダー名: BBC
        │   ├── microbit/                     # ボード名: microbit
        │   │   ├── bbc_microbit.dts          # microbit の DeviceTree 本体
        │   │   ├── bbc_microbit_defconfig    # microbit の デフォルトの prj.conf
        │   │   │           :
        │   │   └── board.h                   # microbit 固有の定義
        │   └── microbit_v2/                  # ボード名: microbit_v2
        │       └── 以下同様
        ├── st/                               # ベンダー名: STM
        │   └── nucleo_f401re/                # ボード名: nucleo_f401re
        │       └── 以下同様
```

そして、ZephyrOpsPlaybook も同様に boards 以下に分けて管理を行っています。

# 6. ZephyrOpsPlaybook での実装例

大まかな流れは以下の通り

playbook/core/main.c
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;↓
playbook/include/boards/unique.h
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;↓
playbook/include/boards/st/nucleo_f401re/nucleo_f401re.h
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;↓
playbook/boards/st/nucleo_f401re/nucleo_f401re.c

## 6.1. playbook/core/main.c

まずは playbook/include/boards/unique.h を include しており、
その後 main 関数の冒頭で、ボード依存の初期化処理 uni_board_init() を呼んでいます。

```c:playbook/core/main.c
#include <zephyr/kernel.h>
#include "boards/unique.h"
int main(void)
{
	uni_board_init();
	while (true) {
        gpio_pin_toggle(GREEN_LED.port, GREEN_LED.pin);    // 追記
		k_msleep(1000);
	}
	return 0;
}
```

## 6.2. playbook/include/boards/unique.h

ここからはボード依存を含むことがわかるように、boards 以下のディレクトリに配置しています。
このヘッダーは、共通処理側からアクセスできるように unique.h というボカシた名称にしています。

```c:playbook/include/boards/unique.h
#pragma once

#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>

struct gpio_port_pin {
    const struct device *const port;
    const gpio_pin_t pin;
};

void uni_board_init(void);

#define GPIO_PORT_PIN(_port, _pin)          ((struct gpio_port_pin){ .port = DEVICE_DT_GET(DT_NODELABEL(_port)), .pin = (_pin) })
#define GPIO_DUMMY                          ((struct gpio_port_pin){ .port = NULL, .pin = 0 })

#if   defined(CONFIG_BOARD_BBC_MICROBIT)
#include "boards/bbc/microbit/bbc_microbit.h"
#elif defined(CONFIG_BOARD_BBC_MICROBIT_V2)
#include "boards/bbc/microbit_v2/bbc_microbit_v2.h"
#elif defined(CONFIG_BOARD_NUCLEO_F401RE)
#include "boards/st/nucleo_f401re/nucleo_f401re.h"
#elif defined(CONFIG_BOARD_NUCLEO_F030R8)
#include "boards/st/nucleo_f030r8/nucleo_f030r8.h"
#elif defined(CONFIG_BOARD_RPI_PICO)
#include "boards/raspberrypi/rpi_pico/rpi_pico.h"
#else
#include "boards/generic/generic_board.h"
#endif
```

## 6.3. playbook/include/boards/st/nucleo_f401re/nucleo_f401re.h

ここで、ボード依存の定義を書き連ねる。
参照したデータシート：
[um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf 58ページ目](https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf)

<details><summary>playbook/include/boards/st/nucleo_f401re/nucleo_f401re.h</summary>

```c:playbook/include/boards/st/nucleo_f401re/nucleo_f401re.h
#pragma once
#include "drivers/i2c/drv_i2c_common.h"
#include "drivers/sensor/measure/drv_tof_vl53l4cd.h"
#include "drivers/button/drv_button_nucleo.h"

// CN7 odd
#define GPIOC_PIN10                 GPIO_PORT_PIN(gpioc, 10)        // PC_10, SPI3 SCLK
#define GPIOC_PIN12                 GPIO_PORT_PIN(gpioc, 12)        // PC_12, SPI3_MOSI
                                                                    // VDD
                                                                    // BOOT0
                                                                    // -
                                                                    // -
#define GPIOA_PIN13                 GPIO_PORT_PIN(gpioa, 13)        // PA_13
#define GPIOA_PIN14                 GPIO_PORT_PIN(gpioa, 14)        // PA_14
#define GPIOA_PIN15                 GPIO_PORT_PIN(gpioa, 15)        // PA_15, PWM2/1, SPI1_SSEL
                                                                    // GND
#define GPIOB_PIN07                 GPIO_PORT_PIN(gpiob,  7)        // PB_7, UART1_RX, PWM4/2, I2C1_SDA
#define GPIOC_PIN13                 GPIO_PORT_PIN(gpioc, 13)        // PC_13, BUTTON1
#define GPIOC_PIN14                 GPIO_PORT_PIN(gpioc, 14)        // PC_14
#define GPIOC_PIN15                 GPIO_PORT_PIN(gpioc, 15)        // PC_15
#define GPIOH_PIN00                 GPIO_PORT_PIN(gpioh,  0)        // PH_0
#define GPIOH_PIN01                 GPIO_PORT_PIN(gpioh,  1)        // PH_1
                                                                    // VBAT
#define GPIOC_PIN02                 GPIO_PORT_PIN(gpioc,  2)        // PC_2, SPI2_MISO, ADC1/12
#define GPIOC_PIN03                 GPIO_PORT_PIN(gpioc,  3)        // PC_3, SPI2_MOSI, ADC1/13

// CN7 even
#define GPIOC_PIN11                 GPIO_PORT_PIN(gpioc, 11)        // PC_11
#define GPIOD_PIN02                 GPIO_PORT_PIN(gpiod,  2)        // PD_2
                                                                    // E5V
                                                                    // GND
                                                                    // -
                                                                    // IOREF
                                                                    // RESET
                                                                    // +3.3V
                                                                    // +5V
                                                                    // GND
                                                                    // GND
                                                                    // VIN
                                                                    // -
#define GPIOA_PIN00                 GPIO_PORT_PIN(gpioa,  0)        // PA_0, A0, ADC1/0, PWM2/1, UART2_CTS
#define GPIOA_PIN01                 GPIO_PORT_PIN(gpioa,  1)        // PA_1, A1, ADC1/1, PWM2/2, UART2_RTS
#define GPIOA_PIN04                 GPIO_PORT_PIN(gpioa,  4)        // PA_4, A2, ADC1/4, SPI1_SSEL
#define GPIOB_PIN00                 GPIO_PORT_PIN(gpiob,  0)        // PB_0, A3, ADC1/8, PWM1/2N
#define GPIOC_PIN01                 GPIO_PORT_PIN(gpioc,  1)        // PC_1, A4, ADC1/11
#define GPIOC_PIN00                 GPIO_PORT_PIN(gpioc,  0)        // PC_0, A6, ADC1/10

// CN10 odd
#define GPIOC_PIN09                 GPIO_PORT_PIN(gpioc,  9)        // PC_9, PWM3/4, I2C3_SDA
#define GPIOB_PIN08                 GPIO_PORT_PIN(gpiob,  8)        // PB_8, D15, PWM4/3, I2C1_SCL
#define GPIOB_PIN09                 GPIO_PORT_PIN(gpiob,  9)        // PB_9, D14, SPI2_SSEL, PWM4/4, I2C1_SDA
                                                                    // AVDD
                                                                    // GND
#define GPIOA_PIN05                 GPIO_PORT_PIN(gpioa,  5)        // PA_5, D13, SPI1_SCLK, LED1, PWM2/1, ADC1/5
#define GPIOA_PIN06                 GPIO_PORT_PIN(gpioa,  6)        // PA_6, D12, SPI1_MISO, PWM3/1, ADC1/6
#define GPIOA_PIN07                 GPIO_PORT_PIN(gpioa,  7)        // PA_7, D11, SPI1_MOSI, PWM1/1N, ADC1/7
#define GPIOB_PIN06                 GPIO_PORT_PIN(gpiob,  6)        // PB_6, D10, I2C1_SCL, PWM4/1, UART1_TX
#define GPIOC_PIN07                 GPIO_PORT_PIN(gpioc,  7)        // PC_7, D9, PWM3/2, UART6_RX
#define GPIOA_PIN09                 GPIO_PORT_PIN(gpioa,  9)        // PA_9, D8, USB_VBUS, PWM1/2, UART1_TX
#define GPIOA_PIN08                 GPIO_PORT_PIN(gpioa,  8)        // PA_8, D7, USB_SOF, PWM1/1, I2C3_SCL
#define GPIOB_PIN10                 GPIO_PORT_PIN(gpiob, 10)        // PB_10, D6, SPI2_SCLK, PWM2/3, I2C2_SCL
#define GPIOB_PIN04                 GPIO_PORT_PIN(gpiob,  4)        // PB_4, D5, SPI1_MISO, PWM3/1, I2C3_SDA
#define GPIOB_PIN05                 GPIO_PORT_PIN(gpiob,  5)        // PB_5, D4, SPI1_MOSI, PWM3/2,
#define GPIOB_PIN03                 GPIO_PORT_PIN(gpiob,  3)        // PB_3, D3, SPI1_SCLK, PWM2/2, I2C2_SDA
#define GPIOA_PIN10                 GPIO_PORT_PIN(gpioa, 10)        // PA_10, D2, USB_ID, PWM1/3, UART1_RX
#define GPIOA_PIN02                 GPIO_PORT_PIN(gpioa,  2)        // PA_2, D1, UART2_TX
#define GPIOA_PIN03                 GPIO_PORT_PIN(gpioa,  3)        // PA_3, D0, UART2_RX

// CN10 even
#define GPIOC_PIN08                 GPIO_PORT_PIN(gpioc,  8)        // PC_8, PWM3/3
#define GPIOC_PIN06                 GPIO_PORT_PIN(gpioc,  6)        // PC_6, UART6_TX, PWM3/1
#define GPIOC_PIN05                 GPIO_PORT_PIN(gpioc,  5)        // PC_5, ADC1/15
                                                                    // U5V
                                                                    // -
#define GPIOA_PIN12                 GPIO_PORT_PIN(gpioa, 12)        // PA_12, UART6_RX, UART1_RTS, USB_DP
#define GPIOA_PIN11                 GPIO_PORT_PIN(gpioa, 11)        // PA_11, UART6_TX, PWM1/4, USB_DM, UART1_CTS
#define GPIOB_PIN12                 GPIO_PORT_PIN(gpiob, 12)        // PB_12, SPI2_SSEL
                                                                    // -
                                                                    // GND
#define GPIOB_PIN02                 GPIO_PORT_PIN(gpiob,  2)        // PB_2
#define GPIOB_PIN01                 GPIO_PORT_PIN(gpiob,  1)        // PB_1, ADC1/9, PWM1/3N
#define GPIOB_PIN15                 GPIO_PORT_PIN(gpiob, 15)        // PB_15, PWM1/3N, SPI2_MOSI
#define GPIOB_PIN14                 GPIO_PORT_PIN(gpiob, 14)        // PB_14, PWM1/2N, SPI2_MISO
#define GPIOB_PIN13                 GPIO_PORT_PIN(gpiob, 13)        // PB_13, PWM1/1N, SPI2_SCLK
                                                                    // AGND
#define GPIOC_PIN04                 GPIO_PORT_PIN(gpioc,  4)        // PC_4, ADC1/14
                                                                    // -
                                                                    // -

#define I2C_100KHZ_BUS              DEVICE_DT_GET(DT_NODELABEL(i2c1))
#define	GREEN_LED					GPIOA_PIN05
#define USER_BUTTON                 GPIOC_PIN13
#define TOF_XSHUT                   GPIOA_PIN00
```

</details>

## 6.4. playbook/boards/st/nucleo_f401re/nucleo_f401re.c
そして、ボード依存の具体的な実装を記述。

```c:playbook/boards/st/nucleo_f401re/nucleo_f401re.c
#include <zephyr/kernel.h>
#include "boards/unique.h"

struct gpio_spec {
	struct gpio_port_pin gpio;
	uint32_t flags;
};

static const struct gpio_spec s_gpio_list[] = {
    { GREEN_LED, GPIO_OUTPUT_ACTIVE }
};

void gpio_init_pin(void)
{
    for (size_t i = 0; i < ARRAY_SIZE(s_gpio_list); ++i) {
        if (device_is_ready(s_gpio_list[i].gpio.port) == false)
			continue;

        gpio_pin_configure(s_gpio_list[i].gpio.port, s_gpio_list[i].gpio.pin, s_gpio_list[i].flags);
    }
}

void uni_board_init(void)
{
#if defined(CONFIG_GPIO)
	gpio_init_pin();
#endif // CONFIG_GPIO
#if defined(CONFIG_I2C)
	drv_init_i2c();
#endif // CONFIG_I2C

#if defined(CONFIG_BUTTON)
	drv_init_button();
#endif // CONFIG_BUTTON
}
```

きれいな作りを目指すなら、ドライバの有効／無効等は Kconfig を設けた上で CMakeLists.txt 側での分岐や、#ifdef などで処理の分岐をさせましょう。

# 7. 最後に

今回は `GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios)` のように dts に定義されたものを使うのではなく、別途マクロを定義して利用する方法を例示してみました。

「マクロが増えて余計大変なのでは？」と思うかもしれませんが、blinky のように使う場合、 dts に書き起こした上で `DT_ALIAS(led0)` のようにアクセスする必要が出てきます。

しかし、基板とソースコードを公開するような企業や個人でなければ、そこまで流儀に従う必要もなく、直接マクロとして定義してしまう方が、ソースコード内から定義元にジャンプして確認できるなど、長期的に見るとマクロのほうが効率良いと感じています。

また、「回路屋さんが作成したエクセルベースの pin-assign 表をマクロに一括変換」みたいな仕組みを持っている会社もあるでしょうし、その辺の流用性も鑑みて検討してみてはいかがでしょうか。
(もちろん、エクセルからdtsに変換という道もあります😇)

[^1]: Zephyr RTOS 〜 Lチカのその先へ 〜
https://qiita.com/Corgeek/items/ca4c515ccf556551562f
[^2]: Nucleo F401RE 個別情報
https://docs.zephyrproject.org/4.1.0/boards/st/nucleo_f401re/doc/index.html
[^3]: BBC Micro:bit 個別情報
https://docs.zephyrproject.org/4.1.0/boards/bbc/microbit/doc/index.html
[^4]: BBC Micro:bit v2 個別情報
https://docs.zephyrproject.org/4.1.0/boards/bbc/microbit_v2/doc/index.html

[^5]: ZephyrOpsPlaybook
https://github.com/Corgeek/ZephyrOpsPlaybook