😎

STM32でZephyrRTOS入門~Buttonで学ぶGPIO入力~

に公開

はじめに

前回の記事ではサンプルコードであるBlinkyを題材としてGPIO出力の仕組みを学んだ。今回は同様にサンプルコードであるButtonを通じてGPIO入力の仕組みを掘り下げていく。

Buttonのコード

Zephyr のサンプルに含まれる main.c は以下の通り。

main.cのコード全体
main.c
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <inttypes.h>

#define SLEEP_TIME_MS 1

/*
 * Get button configuration from the devicetree sw0 alias. This is mandatory.
 */
#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS_OKAY(SW0_NODE)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
static struct gpio_callback button_cb_data;

/*
 * The led0 devicetree alias is optional. If present, we'll use it
 * to turn on the LED whenever the button is pressed.
 */
static struct gpio_dt_spec led = GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios, {0});

void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32());
}

int main(void)
{
	int ret;

	if (!gpio_is_ready_dt(&button))
	{
		printk("Error: button device %s is not ready\n",
			   button.port->name);
		return 0;
	}

	ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
	if (ret != 0)
	{
		printk("Error %d: failed to configure %s pin %d\n",
			   ret, button.port->name, button.pin);
		return 0;
	}

	ret = gpio_pin_interrupt_configure_dt(&button,
										  GPIO_INT_EDGE_TO_ACTIVE);
	if (ret != 0)
	{
		printk("Error %d: failed to configure interrupt on %s pin %d\n",
			   ret, button.port->name, button.pin);
		return 0;
	}

	gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
	gpio_add_callback(button.port, &button_cb_data);
	printk("Set up button at %s pin %d\n", button.port->name, button.pin);

	if (led.port && !gpio_is_ready_dt(&led))
	{
		printk("Error %d: LED device %s is not ready; ignoring it\n",
			   ret, led.port->name);
		led.port = NULL;
	}
	if (led.port)
	{
		ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT);
		if (ret != 0)
		{
			printk("Error %d: failed to configure LED device %s pin %d\n",
				   ret, led.port->name, led.pin);
			led.port = NULL;
		}
		else
		{
			printk("Set up LED at %s pin %d\n", led.port->name, led.pin);
		}
	}

	printk("Press the button\n");
	if (led.port)
	{
		while (1)
		{
			/* If we have an LED, match its state to the button's. */
			int val = gpio_pin_get_dt(&button);

			if (val >= 0)
			{
				gpio_pin_set_dt(&led, val);
			}
			k_msleep(SLEEP_TIME_MS);
		}
	}
	return 0;
}


Zephyr特有の箇所について調べたことをまとめる。

GPIO設定の取得

#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS_OKAY(SW0_NODE)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});

デバイスツリーからGPIO設定を取得している。
GPIO_DT_SPEC_GET_ORの第3引数の0は、デバイスツリーにプロパティが存在しない場合にgpio_dt_specの各メンバ変数に無効な値として0を代入することを意味する。

sw0に関するデバイスツリーは下記の通り。

printk

void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32());
}

この関数はボタンが押された際に呼び出されるコールバック関数である。
その中で呼び出されるprintkはZephyrOSのシステムコンソールに文字を出力する関数であり、出力先はデバイスツリーで定義されている。NucleoF103RBの場合、USART2が仮想COMポートに割り当てられているため、printkで出力した文字列はUSB経由でPCのターミナルソフトに表示される。

USART2のボーレートもデバイスツリーで定義されており、この場合115200bpsである。VSCode+PlatformIOでシリアルモニタを使って出力を確認する場合、platfirmio.iniに「monitor_speed = 115200」を追記する必要がある。

GPIO割り込み


	ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
	if (ret != 0)
	{
		printk("Error %d: failed to configure interrupt on %s pin %d\n",
			   ret, button.port->name, button.pin);
		return 0;
	}

	gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
	gpio_add_callback(button.port, &button_cb_data);
  • gpio_pin_interrupt_configure_dt:
    デバイスツリーで定義されているbuttonのGPIOピンを対象に、アクティブ遷移時で割り込みを発生させるように設定
  • gpio_init_callback: gpio_callback構造体であるbutton_cb_dataを初期化する。第2引数で渡すコールバック関数をbutton_cb_dataのコールバック用ハンドラに設定する。第3引数でどのピンのイベントを対象にするか指定している。
  • gpio_add_callback: buttonのGPIOポートにbutton_cb_dataを登録する。

GPIO入力のポーリング


		while (1)
		{
			/* If we have an LED, match its state to the button's. */
			int val = gpio_pin_get_dt(&button);

			if (val >= 0)
			{
				gpio_pin_set_dt(&led, val);
			}
			k_msleep(SLEEP_TIME_MS);
		}


gpio_pin_get_dtでbuttonのGPIOピンの状態を取得し、その値に基づきLEDの点灯状態を制御している。
gpio_pin_get_dtも処理の内部を追っていくとZephyrOSのGPIO APIを呼び出している。
drivers/gpio/gpio_stm32.cに実装されているgpio_stm32_port_get_rawが呼び出され、さらにその関数を追っていくとGPIOx->IDRの値を読み取っていることがわかる。




おわりに

以上、ZephyrOSのサンプルコードButtonを題材にGPIO入力の仕組みを学んだ。
GPIO割り込みの設定方法やGPIO入力のポーリング方法など、ZephyrOS特有のAPIを理解することができた。
引き続き、ZephyrOSのサンプルコードを題材に学習を進めていく予定である。

Discussion