みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

ベアメタルのHiFIve1 Rev.BでHello world

はじめに

前回ベアメタルでLチカは動いたので、今度はシリアルを動かす。

ホストPCの環境はUbuntu 18.04

UARTのポート

HiFive1 Rev.BをPCに接続すると/dev/ttyACM0/dev/ttyACM1が見えるようになる。 この内/dev/ttyACM0デバッグコンソールとして使える。

f:id:mickey_happygolucky:20191114074445p:plain

この図のSegger J-Link OBのUART 0FE310のUART0に接続されている。そのためFE310のUART0の内容がttyACM0でも見ることができる。

筆者環境ではSegger J-Link OBを実装しているMK22Fは意外と調子が悪いので、UARTのボーレートなどのパラメータが正しくてもminicomにゴミしか出ないことが多い。

なので、MK22Fに入るまでのUART0が直接見れるJ2コネクタのD0/D1にUSB-シリアル変換ケーブルを接続するほうが確実性は高い。

UART1

意外とわかりづらいのがFE310のUART1。

Segger J-Link OBのUARTには接続されていないのでttyACM1とは無関係。

f:id:mickey_happygolucky:20191114074453p:plain

GPIO_18がUART1_RX、GPIO_23がUART1_TXに割り当てられている。

回路図によるとArduino互換ヘッダのD2(DIG2)がGPIO_18(UART1_RX)、D7(DIG7)がGPIO_23(UART1_TX)に割り当たっている。

f:id:mickey_happygolucky:20191114074502p:plain

だがしかし。筆者の手元のボードではUART1に出力するとD2から波形が出る。

マニュアルなのか配線なのかよくわからないが、とりあえずTXとRXは逆転していそう。 今回はHello worldということでTXしか使用しないので実際のRXがどうなっているかは未確認。

リンカスクリプト

linker.ldは変更なし。

アセンブリ

utils.Sレジスタを読み出すためのget32を追加。

    .global start
start:
    lui sp, 0x80004
    jal main
    j .
    
    .global dummy
dummy:
    ret

    .global put32
put32:
    sw x11,(x10)
    ret

    .global get32
get32:
    lw x10, (x10)
    ret

プログラム

プログラムはすべてmain.cに記述。

定義部

マクロとグローバル変数の定義

void put32(unsigned int, unsigned int);
unsigned int get32(unsigned int);

#define GPIOBASE         0x10012000
#define GPIO_OUTPUT_EN     (GPIOBASE+0x08)
#define GPIO_PORT      (GPIOBASE+0x0c)
#define GPIO_IOF_EN        (GPIOBASE+0x38)
#define GPIO_IOF_SEL   (GPIOBASE+0x3C)

#define IOF_UART0_RX   (1<<16)
#define IOF_UART0_TX   (1<<17)
#define IOF_UART1_RX   (1<<18)
#define IOF_UART1_TX   (1<<23)

#define UART0BASE      0x10013000
#define UART1BASE      0x10023000

#define UART_TXDATA        (0x00)
#define UART_TXCTRL        (0x08)
#define UART_DIV       (0x18)

#define TXCTRL_TXEN        (1)
#define TXCTRL_NSTOP   (1<<1)

#define HFCLK          (16000000) //HF clock is 16MHz
#define LFCLK          (32768)    //LF clock is 32KHz

unsigned int uart = 0;

UART初期化

後からUART0/1両対応にしたので不細工。

int setup_uart(int port, int baudrate)
{
    unsigned int iof;
    switch (port) {
    case 0:
        uart = UART0BASE;
        iof = IOF_UART0_RX|IOF_UART0_TX;
        break;
    case 1:
        uart = UART1BASE;
        iof = IOF_UART1_RX|IOF_UART1_TX;
        break;
    default:
        return -1;
    }

    unsigned int ra;
    ra = get32(GPIO_IOF_SEL);
    ra &= ~iof;
    put32(GPIO_IOF_SEL, ra);

    ra = get32(GPIO_IOF_EN);
    ra |= iof;
    put32(GPIO_IOF_EN, ra);

    unsigned div = HFCLK/baudrate-1;
    put32(uart+UART_DIV, div);

    put32(uart+UART_TXCTRL, TXCTRL_NSTOP|TXCTRL_TXEN);
    return 0;
}

シリアル出力

TX queueがFullかであれば待ってTXDATAへ書き込み。

void putc(char c)
{
    /* wait if queue is full. */
    while (1) {
        if ((get32(uart+UART_TXDATA) & 0x80000000) == 0)
            break;
    }
    put32(uart+UART_TXDATA, c);
}

void puts(const char* s)
{
    do {
        putc(*s++);
    } while (*s != '\0');
}

1文字ずつputcを呼ぶのはだるいので文字列対応。

こんにちわ

int main(int argc, char *argv[])
{
    setup_uart(0, 115200);
    puts("hello world !!\r\n");
    return 0;
}

Makefile

Makefileは少し改良した

PROGRAM=hello
RISCVGNU = riscv32-unknown-elf

AOPS = -march=rv32imac
COPS = -march=rv32imac -Wall -O0 -g  -nostdlib -nostartfiles -ffreestanding 
#COPS = -march=rv32imac -Wall -O2  -nostdlib -nostartfiles -ffreestanding

all : $(PROGRAM).hex

clean :
    rm -f *.o
    rm -f *.elf
    rm -f *.hex
    rm -f *.list
    rm -f *~

utils.o : utils.S
    $(RISCVGNU)-as $(AOPS) utils.S -o utils.o

main.o : main.c
    $(RISCVGNU)-gcc $(COPS) -c main.c -o main.o

$(PROGRAM).hex : linker.ld utils.o main.o 
    $(RISCVGNU)-ld utils.o main.o -T linker.ld -o $(PROGRAM).elf
    $(RISCVGNU)-objdump -D $(PROGRAM).elf > $(PROGRAM).list
    $(RISCVGNU)-objcopy $(PROGRAM).elf -O ihex $(PROGRAM).hex

flash : $(PROGRAM).hex
    JLinkExe -device FE310 -speed 1000 -if JTAG -jtagconf -1,-1 -autoconnect 1 -CommanderScript "upload.jlink"

gdb : $(PROGRAM).hex
    JLinkGDBServer -device FE310 -endian little -speed 1000 &
    riscv32-unknown-elf-gdb $(PROGRAM).elf

プログラム名の変数かとgdbの追加。

これによってmake gdbデバッグを開始できる。

J-Link OBの調子によってgdbの開始に失敗することがある。

実行

make flashで書き込み。

minicomでUART0を開いておくと、次のように表示される。

Bench Clock Reset Complete

ATE0-->ATE0
OK
AT+BLEINIT=0-->OK
AT+CWMODE=0-->OK

hello world !!

hello world !!の前にブートローダによっていくつか表示がある。

main関数で呼び出しているsetup_uartの第1引数を1にするとUART1に変更することができる。

まとめ

UARTを使用してHello worldを作成した。

ttyACM0はUART0の入出力をUSBケーブルで見れるようになるが、若干不安定。

UART1はUSBのttyACM1とは無関係。J2のD2/D7でアクセス可能だが、TX/RXが逆転しているように見える。

HiFive1 Rev.Bはまだハードがこなれていない感じ。

参照

図は下記からの抜粋