Zephyrのdriverを作ってみよう②~単純なシリアル送信~

はじめに

Zephyrのdriverを作る方法を学びます。 まずは、qemu_cortex_m3をターゲットとした場合の、UART device driverを写経します。

|> zephyr/drivers/serial/uart_stellaris.c

全体で700行くらいなので、全てを単純に写経する、というよりポイントを押さえて行きたいと思います。

今回は、まず、割込みなしのUARTで、データを送信する部分を写経していきます。

実装するAPI

UART driverで求められるAPIを確認すると、最低限poll_inpoll_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_fnapiも一旦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