Zephyrのdriverを作ってみよう②~単純なシリアル送信~
はじめに
Zephyrのdriverを作る方法を学びます。 まずは、qemu_cortex_m3をターゲットとした場合の、UART device driverを写経します。
|> zephyr/drivers/serial/uart_stellaris.c
全体で700行くらいなので、全てを単純に写経する、というよりポイントを押さえて行きたいと思います。
今回は、まず、割込みなしのUARTで、データを送信する部分を写経していきます。
実装するAPI
UART driverで求められるAPIを確認すると、最低限poll_in
とpoll_out
があれば良さそうです。
|> ${ZEPHYR_BASE}/include/uart.h
/** @brief Driver API structure. */ struct uart_driver_api { /** Console I/O function */ int (*poll_in)(struct device *dev, unsigned char *p_char); void (*poll_out)(struct device *dev, unsigned char out_char); ... #ifdef CONFIG_UART_INTERRUPT_DRIVEN ... #endif ... }
前置き
写経する意味がないので、元々のdriverから使いまわすものです。
まず、レジスタマップを表現する構造体です。デバイスのベースアドレスを起点とするレジスタマップを表しています。
/* Stellaris UART module */ struct _uart { u32_t dr; union { u32_t _sr; u32_t _cr; } u1; u8_t _res1[0x010]; u32_t fr; u8_t _res2[0x04]; u32_t ilpr; u32_t ibrd; u32_t fbrd; u32_t lcrh; u32_t ctl; ...
次に、レジスタのビットを定義したマクロです。
/* bits */ #define UARTFR_BUSY 0x00000008 #define UARTFR_RXFE 0x00000010 #define UARTFR_TXFF 0x00000020 ...
最後に、ヘルパーマクロです。
/* convenience defines */ #define DEV_CFG(dev) \ ((const struct uart_device_config * const)(dev)->config->config_info) #define DEV_DATA(dev) \ ((struct uart_stellaris_dev_data_t * const)(dev)->driver_data) #define UART_STRUCT(dev) \ ((volatile struct _uart *)(DEV_CFG(dev))->base)
写経開始
では、uart_stellaris.c
をコピーしたuart_my_uart.c
の内容を全て消して、スクラッチで写経します。
NOTE: マクロ名が
STELLARIS
のままになっている部分がありますが、細かいことは追々考えます。
まずは、driverの登録を行う部分から書いていきます。
driver登録
重要なマクロはDEVICE_AND_API_INITです。
DEVICE_AND_API_INIT
APIは次の通りです。
DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio, api)
デバイス名/ドライバ名、初期化関数、API関数のポインタなどを渡します。
data
はドライバ固有のデータです。現時点では、必要ないため、NULLにします。この後すぐに実装しますが、init_fn
とapi
も一旦NULLにして、コンパイルが通ることを確認します。
cfg_info
に設定データを渡すために、uart.h
に定義されているuart_device_config
のインスタンスを作成します。
#include <soc.h> // DT_UART_MY_UART_CLK_FREQが定義されています #include <uart.h> #ifdef CONFIG_UART_MY_UART_PORT_0 static const struct uart_device_config uart_my_uart_dev_cfg_0 = { .base = (u8_t *)DT_TI_STELLARIS_UART_4000C000_BASE_ADDRESS, .sys_clk_freq = DT_UART_MY_UART_CLK_FREQ, }; #endif
DEVICE_AND_API_INIT
マクロを呼び出します。
DEVICE_AND_API_INIT( uart_stellaris0, DT_TI_STELLARIS_UART_4000C000_LABEL, NULL, NULL, &uart_my_uart_dev_cfg_0, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL );
これで、ビルドが通ります。もちろん、初期化していないので、動作しません。
初期化
初期化関数のひな形作成し、実装していきます。
static int uart_my_uart_init(struct device *dev) { // disable // set baudrate // set line control // enable return 0; } ... DEVICE_AND_API_INIT( uart_stellaris0, DT_TI_STELLARIS_UART_4000C000_LABEL, &uart_my_uart_init, // changed NULL, &uart_my_uart_dev_cfg_0, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL );
まずは、UARTデバイスを無効化します。
static inline void disable(struct device *dev) { volatile struct _uart *uart = UART_STRUCT(dev); uart->ctl &= UARTCTL_UARTEN; while (uart->fr & UARTFR_BUSY) ; uart->lcrh &= ~UARTLCRH_FEN; }
ボーレートを設定します。ここは真面目に見ても仕方ないので、こういうもの、と思いながら写経します。
static void baudrate_set(struct device *dev, u32_t baudrate, u32_t sys_clk_freq_hz) { volatile struct _uart *uart = UART_STRUCT(dev); u32_t brdi, brdf, div, rem; div = (16 * baudrate); rem = sys_clk_freq_hz % div; brdf = ((((rem * 64) << 1) / div) + 1) >> 1; brdi = sys_clk_freq_hz / div; uart->ibrd = (u16_t)(brdi & 0xffff); /* 16 bits */ uart->fbrd = (u8_t)(brdf & 0x3f); /* 6 bits */ } static int uart_my_uart_init(struct device *dev) { ... baudrate_set(dev, DT_TI_STELLARIS_UART_4000C000_CURRENT_SPEED, DT_UART_MY_UART_CLK_FREQ); ... }
ライン制御を8ビットフレームに設定します。
/* 8-bit frame */ #define LINE_CONTROL_DEFAULTS UARTLCRH_WLEN static inline void line_control_defaults_set(struct device *dev) { volatile struct _uart *uart = UART_STRUCT(dev); uart->lcrh = LINE_CONTROL_DEFAULTS; }
UARTデバイスを有効化します。
static inline void enable(struct device *dev) { volatile struct _uart *uart = UART_STRUCT(dev); uart->ctl |= UARTCTL_UARTEN; }
これでデバイスの初期化が完了しました。ビルドも通ります。
シリアル送信
uart_driver_api
構造体のpoll_out
APIを実装します。
static const struct uart_driver_api uart_stellaris_driver_api = { .poll_out = uart_stellaris_poll_out, ... } DEVICE_AND_API_INIT( uart_stellaris0, DT_TI_STELLARIS_UART_4000C000_LABEL, &uart_my_uart_init, NULL, &uart_my_uart_dev_cfg_0, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &uart_my_uart_driver_api );
本当は、TXバッファの状態をチェックする必要がありますが、一旦チェックなしで文字を送信します。
static void uart_my_uart_poll_out(struct device *dev, unsigned char c) { volatile struct _uart *uart = UART_STRUCT(dev); uart->dr = (u32_t)c; }
無事、動きます。
$ ninja run [1/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3 qemu-system-arm: warning: nic stellaris_enet.0 has no peer ***** Booting Zephyr OS 1.13.99 ***** Hello World! qemu_cortex_m3