みつきんのメモ

組み込みエンジニアです。Interface誌で「My オリジナルLinuxの作り方」連載中

RISC-Vのベアメタル入門(自分用) UART編

はじめに

前回動かした環境でUARTを動かしてみる。

具体的には、またdwelch67/sifive_samplesを写経する。

UARTのレジスタ構成

SiFive FE310-G000 Manual v2p3から抜粋する。

f:id:mickey_happygolucky:20190304134208p:plain
UARTレジスタ

必要なレジスタは7つ。

注意する点としては、txdataのところにこんな一文がある。

Reading from txdata returns the current value of the full flag and zero in the data field. 
The full flag indicates whether the transmit FIFO is able to accept new entries; when set, writes to data are ignored.
A RISC-V amoswap instruction can be used to both read the full status and attempt to enqueue data,
with a non-zero return value indicating the character was not accepted.

どうやらtxdataの操作にはAtomic命令のamoswapを使用する必要があるらしい。

ピンファンクションの設定

UARTを有効化するには、ピンファンクションを設定する必要がある。

SiFive FE310-G000 Preliminary Datasheet v1p5のGPIOピンアサインを抜粋。

f:id:mickey_happygolucky:20190304134120p:plain
GPIOピンアサイ

GPIO16にUART0_RXが,GPIO17にUART0_TXが割り当たっているらしい。

ここでは、次のようにしている。

    ra=GET32(GPIO_IOF_SEL);
    ra&=~(1<<16); //UART0_RX
    ra&=~(1<<17); //UART0_TX
    PUT32(GPIO_IOF_SEL,ra);
    ra=GET32(GPIO_IOF_EN);
    ra|=1<<16; //UART0_RX
    ra|=1<<17; //UART0_TX
    PUT32(GPIO_IOF_EN,ra);

iof_selの16と17を落としてiof_enの16と17を立てているのは分かるんだが、このレジスタの仕様が全然見つからない。。。

レジスタのビットと機能の対応はどこで分かるんだろう。

サンプルプログラム

dwelch67/sifive_samplesのUART0を元にhello worldを作る。

Makefileとmemmapは前回と同じ。

novectors.s

    .globl _start
_start:
    lui x2, 0x80004 /* set sp to 0x80004 */
    jal notmain /* call notmain */
    j .

    .globl dummy
dummy:
    ret

    .globl PUT32
PUT32:
    sw x11, (x10)
    ret

    .globl GET32
GET32:
    lw x10, (x10)
    ret

    .globl AMOSWAP
AMOSWAP:
    amoswap.w x10, x11, (x10) /* ret = *a0; *a0 = a1 */
    ret

基本は前回と同じだが、txdata操作用にAMOSWAPの実装を追加。

notmain.c

uart0_divには、以前QEMUでHiFive1のプログラムを動かした時に、core freq at 11644342 Hzと表示されたので、ざっくりと11644342/115200=101として101を設定している。

void PUT32(unsigned int, unsigned int);
unsigned int GET32(unsigned int);
void dummy(unsigned int);
unsigned int AMOSWAP(unsigned int, unsigned int);

#define GPIOBASE 0x10012000
#define GPIO_VALUE      (GPIOBASE+0x00)
#define GPIO_INPUT_EN   (GPIOBASE+0x04)
#define GPIO_OUTPUT_EN  (GPIOBASE+0x08)
#define GPIO_PORT       (GPIOBASE+0x0C)
#define GPIO_PUE        (GPIOBASE+0x10)
#define GPIO_OUT_XOR    (GPIOBASE+0x40)
#define GPIO_IOF_EN     (GPIOBASE+0x38)
#define GPIO_IOF_SEL    (GPIOBASE+0x3C)

#define UART0BASE 0x10013000
#define UART0_TXDATA    (UART0BASE+0x00)
#define UART0_RXDATA    (UART0BASE+0x04)
#define UART0_TXCTRL    (UART0BASE+0x08)
#define UART0_RXCTRL    (UART0BASE+0x0C)
#define UART0_IE        (UART0BASE+0x10)
#define UART0_IP        (UART0BASE+0x14)
#define UART0_DIV       (UART0BASE+0x18)

#define MTIME 0x0200BFF8

void putc(char c)
{
    /* wait if queue is full. */
    while (1) {
        if ((GET32(UART0_TXDATA)&0x80000000) == 0)
            break;
    }
    AMOSWAP(UART0_TXDATA, c);
}

int notmain(void)
{
    unsigned int ra;

    //11644342/115200 = 101
    ra = GET32(GPIO_IOF_SEL);
    ra &= ~(1<<16); //UART0_RX
    ra &= ~(1<<17); //UART0_TX
    PUT32(GPIO_IOF_SEL, ra);

    ra = GET32(GPIO_IOF_EN);
    ra |= (1<<16); //UART0_RX
    ra |= (1<<17); //UART0_TX

    PUT32(UART0_DIV, 101-1);
    PUT32(UART0_TXCTRL, 0x00000003); //txen=1, nstop=1

    putc('h');
    putc('e');
    putc('l');
    putc('l');
    putc('o');
    putc(' ');
    putc('w');
    putc('o');
    putc('r');
    putc('l');
    putc('d');
    putc('\n');
    return 0;
}

華麗なるベタ書き。putc()をひたすらに並べる。

QEMUで実行

$ qemu-system-riscv32 -nographic -machine sifive_e -kernel ./notmain.elf
hello world

無事に表示された。

まとめ

UART送信は、GPIOのピンファンクションの設定を除けば、txctrlとdivを設定するだけで使えるので非常に簡単。

txdataの操作にamoswapを使用するのも注意点か。 でも、uart02uart03の実装を見るに、amoswapは必須ではないのか?

参考資料

SiFive FE310-G000 Preliminary Datasheet v1p5

SiFive E300 Platform Reference Manual