🦔

STM32でZephyrRTOS入門~USART受信割り込みでループバック~

に公開

はじめに

これまでWindows上にZephyrRTOSの開発環境を構築し、NucleoF103RBボードを用いて学習を進めてきた。
https://zenn.dev/gotoooo/articles/894213f95372bd

前回はNucleoF103RBとUSB-Serialボードを接続し、USARTで文字列をループバックする最小限のコードを作成し、USARTの基本的な使い方を学んだ。
https://zenn.dev/gotoooo/articles/4386771879fce4

今回は、前回のコードをベースにして、USARTの割り込みを利用したループバック処理を実装する。

コード

今回はmain.cの実装に加えて、zephyr/prj.confの編集が必要である。

main.cのコード全体
main.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/ring_buffer.h>

#define BUFFER_SIZE 256
RING_BUF_DECLARE(uart_rb, BUFFER_SIZE);

const struct device *uart1_dev = DEVICE_DT_GET(DT_NODELABEL(usart1));
const struct device *uart2_dev = DEVICE_DT_GET(DT_NODELABEL(usart2));

static void uart_cb(const struct device *dev, void *user_data){
	uint8_t buf[16];
	int rx;

	ARG_UNUSED(user_data);

	while(uart_irq_update(dev) && uart_irq_rx_ready(dev)) {
		rx = uart_fifo_read(dev, buf, sizeof(buf));
		if (rx > 0) {
			ring_buf_put(&uart_rb, buf, rx);
		}
	}
}

int main(void)
{
    if (!device_is_ready(uart1_dev) || !device_is_ready(uart2_dev)) {
        printk("UART device not ready\n");
        return -1;
    }

	uart_irq_callback_user_data_set(uart1_dev, uart_cb, NULL);
	uart_irq_rx_enable(uart1_dev);

	printk("UART IRQ RX demo start\n");

	while (1) {
		uint8_t c;
		if (ring_buf_get(&uart_rb, &c, 1) == 1) {
			uart_poll_out(uart2_dev, c);
		}

		k_msleep(1);
	}

	return 0;
}



zephyr/prj.conf
CONFIG_UART_INTERRUPT_DRIVEN=y

prj.confの設定内容

公式ドキュメントには以下のように説明されている。

Most importantly, the Kconfig options define whether the polling API (default), the interrupt-driven API or the asynchronous API can be used.

つまり、USARTのAPIにはポーリング、割り込み駆動、非同期の3種類があり、CONFIG_UART_INTERRUPT_DRIVENを有効にすることで割り込み駆動APIが使用可能になる。

コードを追っていくと#ifdef文でUART割り込みに関する関数が囲まれており、この設定が有効でない場合、APIに紐づけられる関数ポインタが未指定となることがわかる。

リングバッファ

#include <zephyr/sys/ring_buffer.h>

#define BUFFER_SIZE 256
RING_BUF_DECLARE(uart_rb, BUFFER_SIZE);

Zephyrにはリングバッファを扱うためのAPIが用意されている。
自前で用意しなくてよいのは非常に助かる。

受信割り込み発生時のコールバック関数

static void uart_cb(const struct device *dev, void *user_data){
	uint8_t buf[16];
	int rx;

	ARG_UNUSED(user_data);

	while(uart_irq_update(dev) && uart_irq_rx_ready(dev)) {
		rx = uart_fifo_read(dev, buf, sizeof(buf));
		if (rx > 0) {
			ring_buf_put(&uart_rb, buf, rx);
		}
	}
}

  • ARG_UNUSED(user_data)
    コンパイル時の警告を消すために使用している。
  • uart_irq_update(dev)
    割り込みの状態を更新している。
  • uart_irq_rx_ready(dev)
    受信FIFOにデータがあるかチェックしている。
  • uart_fifo_read(dev, buf, sizeof(buf))
    USARTの受信FIFOから最大16byteを一気に読み取り、bufに格納している。
    戻り値rxは実際に読み込んだbyte数である。
  • ring_buf_put(&uart_rb, buf, rx)
    リングバッファに読み取ったデータを格納している。

なお、ここでは16byteずつまとめて読み取っているが、これは1byteずつ読み取って直接リングバッファに書き込む場合と比べてオーバーヘッドを減らすためである。

受信割り込み設定

	uart_irq_callback_user_data_set(uart1_dev, uart_cb, NULL);
	uart_irq_rx_enable(uart1_dev);

  • uart_irq_callback_user_data_set(uart1_dev, uart_cb, NULL)
    USART1の割り込みが発生した際に呼び出されるコールバック関数を設定している。
    第3引数はコールバック関数に渡されるユーザーデータであるが、今回は使用しないのでNULLを指定している。

  • uart_irq_rx_enable(uart1_dev)
    USART1の受信割り込みを有効にしている。

リングバッファのポーリング

	while (1) {
		uint8_t c;
		if (ring_buf_get(&uart_rb, &c, 1) == 1) {
			uart_poll_out(uart2_dev, c);
		}

		k_msleep(1);
	}

リングバッファから1byteずつデータを取り出し、USART2で送信している。

おわりに

今回はUSARTの割り込みを利用したループバック処理を実装した。
prj.confの設定を変更することで、割り込み駆動APIが使用可能になることがわかった。
他にもAsynchronous APIなるものもあるようなので、今後機会があれば試してみたい。

Discussion