STM32でZephyrRTOS入門~USART受信割り込みでループバック~
はじめに
これまでWindows上にZephyrRTOSの開発環境を構築し、NucleoF103RBボードを用いて学習を進めてきた。
前回はNucleoF103RBとUSB-Serialボードを接続し、USARTで文字列をループバックする最小限のコードを作成し、USARTの基本的な使い方を学んだ。
今回は、前回のコードをベースにして、USARTの割り込みを利用したループバック処理を実装する。
コード
今回はmain.cの実装に加えて、zephyr/prj.confの編集が必要である。
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;
}
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