はじめに
前回ベアメタルでLチカは動いたので、今度はシリアルを動かす。
ホストPCの環境はUbuntu 18.04
UARTのポート
HiFive1 Rev.BをPCに接続すると/dev/ttyACM0
と/dev/ttyACM1
が見えるようになる。
この内/dev/ttyACM0
はデバッグコンソールとして使える。
この図のSegger J-Link OBのUART 0
はFE310の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
とは無関係。
GPIO_18がUART1_RX、GPIO_23がUART1_TXに割り当てられている。
回路図によるとArduino互換ヘッダのD2(DIG2)がGPIO_18(UART1_RX)、D7(DIG7)がGPIO_23(UART1_TX)に割り当たっている。
だがしかし。筆者の手元のボードでは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はまだハードがこなれていない感じ。
参照
図は下記からの抜粋