0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZephyrRTOSAdvent Calendar 2024

Day 10

Zephyr RTOS 〜 GPIO を叩く! 〜

Last updated at Posted at 2024-12-19

1. はじめに

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

前回の記事はこちら

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

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

今回は nucleo_f401re を例に話を進めますが、引き続き bbc_microbit / bbc_microbit_v2 でも確認できます。

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

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

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

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

以下は流し読みでOKです。

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);
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;
	}
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を使って、nucleo_f401re の LED を明滅させてみます。
既に playbook/scripts/west_env.bat がある場合はそのファイルでBOARD_TYPE=nucleo_f401re を指定した上で rebuild & flash を実施。
(west_env.bat がない場合はこちら)

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;
}

bliky.gif

スッキリ…!

ネタばらし

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

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) })
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
さらにネタばらし

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

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;
};

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 での実装例

実際に、main.c からの流れを追っていきます。

playbook/core/main.c

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

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

int main(void)
{
	uni_board_init();

	while (true) {
		k_msleep(1000);
	}

	return 0;
}
                            ↓
playbook/include/boards/unique.h

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

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;
};

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

// ここで board 毎に定義されている CONFIG で board 依存のヘッダーを include
#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.h"
#endif

                            ↓
playbook/include/boards/st/nucleo_f401re.h

ここで、ボード依存の定義を書き連ねる。
参照したデータシート:
um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf 58ページ目

playbook/include/boards/st/nucleo_f401re.h
#pragma once
#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	GREEN_LED					GPIOA_PIN05
#define USER_BUTTON                 GPIOC_PIN13
                            ↓
playbook/boards/st/nucleo_f401re.c

そして、ボード依存の具体的な実装を記述。

playbook/boards/st/nucleo_f401re.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include "boards/unique.h"

// struct gpio_dt_spec と同じデータ構造
struct gpio_spec {
	struct gpio_port_pin gpio;
	uint32_t flags;
};

// gpio_pin_configure を一括で行うための設定リスト
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)
{
	gpio_init_pin();                    // gpio_pin_configure() をまとめて行う
	drv_init_button();                  // ドライバ群の初期化もここでまとめて行う
}

きれいな作りを目指すなら、ドライバの有効/無効等は Kconfig を設けた上で CMakeLists.txt 側での分岐や、#ifdef などで処理の分岐をさせましょう。
あまり分岐が多くないのであれば、各固有の header に記述しても良いかもしれません。

7. 最後に

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

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

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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?