かずの不定期便ブログ

備忘録代わりに書きます

FPGA Sipeed Tang Nano 9Kを使ってSDカードアクセス(その2。ソフト設計編)

リーズナブルFPGAシリーズのTang Nano 9Kを使ってTFカードスロットにアクセスしました(その2。ソフト設計編)

今回はソフト設計編になります。

SDカードへのアクセスメニューを追加したpicotiny

exampleのソフトについて

まずはexampleの構造から説明します。 exampleをダウンロードすると、以下の様な構造になっています。(ソフト開発に必要なディレクトリのみを挙げています)

root
  +- fw
     +- fw-brom    ←ココはブートROMコード。今回は触らず。(修正したらRTL埋め込みを変更する)
     +- fw-flash ← ボード内蔵のフラッシュへ書き込むプログラムが格納
  +- sw
     +- pico-programmer.py ← ボード内蔵のフラッシュメモリを書き換えるpythonプログラム

前回、記載しましたようにboot programはRTLに埋め込まれています。このプログラムのソースコードはfw-brom内に格納されています。今回このプログラムは触りません。 またboot programにはUARTから受信したデータをフラッシュへ書き込むプログラムが仕込まれています。(ISPプログラムとwikiでは呼んでいます) このプログラムを呼び出し、UARTを介して通信するのがpythonプログラム "pico-programmer.py"になります。

フラッシュメモリファームウェアの書き込み方法

書き込みプログラムはpythonプログラムなので、pythonをインストールする必要があります。 TangNano9KとPCはUSBを介したCOMポートで繋がります。wsl2からだとCOMポートを繋げるのに苦労しそうだったので、書き込みはwindowsから行う事にします。
windows用のpythonをインストールします。
適当にググればpythonのインストール方法は出てきますが、wikiのREADMEに記載があるようにpythonのバージョンは3.6以上でないといけません。
私はインストール時点の最新版3.11.1をインストールしました。
以下からダウンロードしました。
Welcome to Python.org
本書き込みプログラムは、初期状態でインストールされないモジュールpyserialが必要です。 以下の様なコマンドを打ってインストールします。
python -m pip install pyserial
他に必要なモジュールがあるかもしれません(忘れてしまいました)。うまく動かなかったら随時インストールしましょう。足りないモジュールはpythonから、このモジュールがありませんと言われるので、雰囲気でモジュールは追加できます。

TANG Nano 9KボードをUSB経由でPCと接続し、GOWIN FPGA Designerからbit streamを書き込むと、本記事の冒頭の様なメニューがHDMI表示されます。
この状態で、ファームウェア書き換えプログラム を動作させます
DOS窓またはpower shellを立ち上げて sw ディレクトリが見えているフォルダへ移動します。

python sw/pico-programmer.py example-fw-flash.v COM14
COMの番号は環境によって異なりますので、デバイスマネージャでCOMの何番で繋がっているか確認してください。
本コマンドでsw/example-fw-flash.vをフラッシュメモリへ書き込めます。

Read program with xxx bytes
  - Waiting for reset -
    ...
と出たら,S1ボタンを1sec以下で押して離します。

Total sectors 3
Total pages 46
Flashing 1 / 3
Flashing 2 / 3
Flashing 3 / 3
と出て書き込みが完了します

FatFsドライバ

FatFsの展開

ラズパイPicoでFatFsを使う - Qiita
本記事を参考に作成します。
※FatFsのページ
http://elm-chan.org/fsw/ff/00index_e.html
ここからFatfs本体のff15.zip及びサンプルの"ffsample.zip" をダウンロードし展開します。
fw-flashの下にfatfsというディレクトリを作成して

fw --- fw-flash -- fatfs

ff15/source ディレクトリ内のファイルをfatfs内へ入れます。
次に、ffsample.zipファイル内から\stm32\mmc_stm32f1_spi.cファイルを取り出し、先のfatfs内へmmc_picorv32_spi.cというファイル名でコピーします。 以下のファイルが置かれていると思います。

fw --- fw-flash --- fatfs
                       |-- 00history.txt
                       |-- 00readme.txt
                       |-- diskio.c
                       |-- diskio.h
                       |-- ff.c
                       |-- ff.h
                       |-- ffconf.h
                       |-- ffsystem.c
                       |-- ffunicode.c
                       |-- mmc_picorv32_spi.c

diskio.cは不要なので削除してください。

picorv32用のコードへ修正

fatfsを動作させるのに必要な関数は上記の参考ページに記載されていますが、その修正すべき該当関数を修正します。
mmc_picorv32_spi.c の14行目から208行目までを以下のコードへ置き替えます。

#include "spi.h"
#define FCLK_FAST() { }
#define FCLK_SLOW() { }

#define CS_HIGH()   { SPI_SD->CSout = 1; /* HIGH */ }
#define CS_LOW()    { SPI_SD->CSout = 0; /* LOW */ }

#define MMC_CD      1 /* Card detect (yes:true, no:false, default:true) */
#define MMC_WP      0 /* Write protected (yes:true, no:false, default:false) */


volatile  int  conter1;

void  Delay( int ms )
{
    // 25.2MHz
  //for( conter1=0; conter1<25200*ms ; conter1++ ) ;
  for( conter1=0; conter1<25.2*ms ; conter1++ ) ;
}


/*--------------------------------------------------------------------------

   Module Private Functions

---------------------------------------------------------------------------*/
#include "ff.h"         /* Obtains integer types */
#include "diskio.h"     /* Declarations of disk functions */


/* MMC card type flags (MMC_GET_TYPE) */
#define CT_MMC3     0x01        /* MMC ver 3 */
#define CT_MMC4     0x02        /* MMC ver 4+ */
#define CT_MMC      0x03        /* MMC */
#define CT_SDC1     0x02        /* SDC ver 1 */
#define CT_SDC2     0x04        /* SDC ver 2+ */
#define CT_SDC      0x0C        /* SDC */
#define CT_BLOCK    0x10        /* Block addressing */


/* MMC/SD command */
#define CMD0    (0)         /* GO_IDLE_STATE */
#define CMD1    (1)         /* SEND_OP_COND (MMC) */
#define ACMD41  (0x80+41)   /* SEND_OP_COND (SDC) */
#define CMD8    (8)         /* SEND_IF_COND */
#define CMD9    (9)         /* SEND_CSD */
#define CMD10   (10)        /* SEND_CID */
#define CMD12   (12)        /* STOP_TRANSMISSION */
#define ACMD13  (0x80+13)   /* SD_STATUS (SDC) */
#define CMD16   (16)        /* SET_BLOCKLEN */
#define CMD17   (17)        /* READ_SINGLE_BLOCK */
#define CMD18   (18)        /* READ_MULTIPLE_BLOCK */
#define CMD23   (23)        /* SET_BLOCK_COUNT (MMC) */
#define ACMD23  (0x80+23)   /* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24   (24)        /* WRITE_BLOCK */
#define CMD25   (25)        /* WRITE_MULTIPLE_BLOCK */
#define CMD32   (32)        /* ERASE_ER_BLK_START */
#define CMD33   (33)        /* ERASE_ER_BLK_END */
#define CMD38   (38)        /* ERASE */
#define CMD55   (55)        /* APP_CMD */
#define CMD58   (58)        /* READ_OCR */


static volatile DSTATUS Stat = STA_NOINIT;  /* Physical drive status */
static volatile UINT Timer1, Timer2;        /* 1kHz decrement timer stopped at zero (disk_timerproc()) */

static BYTE CardType;   /* Card type flags */



/*-----------------------------------------------------------------------*/
/* SPI controls (Platform dependent)                                     */
/*-----------------------------------------------------------------------*/

/* Initialize MMC interface */
static void init_spi (void)
{
    // SPI clock 25.2/4=6.3Mbps

    /* CS# */
    CS_HIGH();            /* Set CS# high */

#if 0
    for (Timer1 = 10; Timer1; ) ;   /* 10ms */
#else
    Delay( 10 );
#endif
}


/* Exchange a byte */
static BYTE xchg_spi (
    BYTE dat    /* Data to send */
)
{

    SPI_SD->txdata = dat;
    //while( !(SPI_SD->status&0x1) ) ; // SPI_BSY=1になるのを待つ
    while( !(SPI_SD->status&0x8) ) ; // SPI_RNE=1(rxfifo not empty)になるまで待つ
    return  (BYTE)SPI_SD->rxdata;
}



/* Receive multiple byte */
static void rcvr_spi_multi (
    BYTE *buff,     /* Pointer to data buffer */
    UINT btr        /* Number of bytes to receive (even number) */
)
{
    //spi_read_blocking( SPIDEV, 0xff, buff, btr );
    UINT  rx_remaining,  tx_remaining;
    UINT i;
    rx_remaining = btr;
    tx_remaining = btr;
    while(rx_remaining || tx_remaining) {
        if(tx_remaining && (!(SPI_SD->status & 0x4)) ) {  // txfifoがフルにならない間
            SPI_SD->txdata = 0xff;  // dummy tx
            --tx_remaining;
        }
        if(rx_remaining && (SPI_SD->status & 0x8)) {    // rxfifoにデータ有
            *buff++ = (uint8_t) SPI_SD->rxdata;
            --rx_remaining;
        }
    }
}


/* Send multiple byte */
static void xmit_spi_multi (
    const BYTE *buff,   /* Pointer to the data */
    UINT btx            /* Number of bytes to send (even number) */
)
{
    volatile uint8_t dummy_receive;
    //spi_write_blocking( SPIDEV, buff, btx );
    // btn分 送信する(rxdataは無視する)
    UINT i;
    for(i=0; i<btx; i++) {
        while(SPI_SD->status & 0x4) ;   // txfifoがフルの時,wait
        SPI_SD->txdata = *buff++;
    }
    // 送信がはけるまで待つ(=BSY=0)
    while(SPI_SD->status & 0x1) ;
    // rxfifo がemptyになるようにdummy read
    while(SPI_SD->status & 0x8) // rxfifo not empty
        dummy_receive = (uint8_t) SPI_SD->rxdata;
}

spi.hをincludeしていますが、以下の内容でspi.hという名前でfatfs配下に新規作成します。これは作成したSPIモジュールのレジスタ定義とアドレスになります。

#include <stdint.h>
typedef struct {
    volatile uint32_t control0;
    volatile uint32_t control1;
    volatile uint32_t txdata;
    volatile uint32_t rxdata;
    volatile uint32_t status;
    volatile uint32_t CSout;
} PICOSPI;

#define SPI_SD  ((PICOSPI*)0x84000000)

fw-flash/fatfs/Makefileの追加

fatfsのバイナリ作成用にMakefileを作成します。
オリジナルのfw-flash/Makefileをベースに修正します。

  • RISCV_PATHの変更
  • LDFLAGSから-nostdlibの削除
  • linkerスクリプトの呼び出しの削除
  • *.oの作成のみ

Makefileの作成方法がイマイチ分からなかった為、合ってるかどうかは分かりませんが、以下の様に作成しました。

PROJ_NAME=fw-flash
DEBUG=no
BENCH=no
MULDIV=no
COMPRESSED=no

SRCS =  $(wildcard *.c)             \
        $(wildcard *.S)

LDSCRIPT = ./linker_flash.ld

RISCV_NAME ?= riscv-none-embed
RISCV_PATH ?= ~/riscv/riscv-none-embed-gcc/8.2.0-3.1

MABI=ilp32
MARCH := rv32i
ifeq ($(MULDIV),yes)
    MARCH := $(MARCH)m
endif
ifeq ($(COMPRESSED),yes)
    MARCH := $(MARCH)ac
endif

CFLAGS += -march=$(MARCH)  -mabi=$(MABI)  -ffunction-sections -fdata-sections
LDFLAGS += -march=$(MARCH)  -mabi=$(MABI)  -Wl,--gc-sections

ifeq ($(DEBUG),yes)
    CFLAGS += -g3 -O0 
endif

ifeq ($(DEBUG),no)
    CFLAGS += -g -O3 
endif

ifeq ($(BENCH),yes)
    CFLAGS += -fno-inline  
endif

RISCV_CLIB=$(RISCV_PATH)/$(RISCV_NAME)/lib/$(MARCH)/$(MABI)/

RISCV_OBJCOPY = $(RISCV_PATH)/bin/$(RISCV_NAME)-objcopy
RISCV_OBJDUMP = $(RISCV_PATH)/bin/$(RISCV_NAME)-objdump
RISCV_CC = $(RISCV_PATH)/bin/$(RISCV_NAME)-gcc

CFLAGS +=  -MD -fstrict-volatile-bitfields 
#LDFLAGS +=  -nostdlib -lgcc -mcmodel=medany -nostartfiles -ffreestanding -Wl,-Bstatic,-T,$(LDSCRIPT),-Map,$(OBJDIR)/$(PROJ_NAME).map,--print-memory-usage
LDFLAGS +=  -lgcc -mcmodel=medany -nostartfiles -ffreestanding -Wl,-Bstatic

OBJDIR = build
OBJS := $(SRCS)
OBJS := $(OBJS:.c=.o)
OBJS := $(OBJS:.cpp=.o)
OBJS := $(OBJS:.S=.o)
OBJS := $(addprefix $(OBJDIR)/,$(OBJS))

SUBOBJ := $(addprefix $(OBJDIR)/,$(SUBDIRS))
SUBOBJ := $(addsuffix /*.o,$(SUBOBJ))

export RISCV_CC CFLAGS LDFLAGS OBJDIR

#all: $(SUBDIRS) $(OBJDIR)/$(PROJ_NAME).elf $(OBJDIR)/$(PROJ_NAME).hex $(OBJDIR)/$(PROJ_NAME).asm $(OBJDIR)/$(PROJ_NAME).v
all: $(OBJS)

$(SUBDIRS): ECHO
    make -C $@

ECHO:
    @echo $(SUBDIRS)


$(OBJDIR)/%.o: %.c
    mkdir -p $(dir $@)
    $(RISCV_CC) -c $(CFLAGS)  $(INC) -o $@ $^
    
$(OBJDIR)/%.o: %.cpp
    mkdir -p $(dir $@)
    $(RISCV_CC) -c $(CFLAGS)  $(INC) -o $@ $^   

$(OBJDIR)/%.o: %.S
    mkdir -p $(dir $@)
    $(RISCV_CC) -c $(CFLAGS) -o $@ $^ -D__ASSEMBLY__=1

$(OBJDIR):
    mkdir -p $@

clean:
    rm -f $(OBJDIR)/$(PROJ_NAME).elf
    rm -f $(OBJDIR)/$(PROJ_NAME).hex
    rm -f $(OBJDIR)/$(PROJ_NAME).map
    rm -f $(OBJDIR)/$(PROJ_NAME).v
    rm -f $(OBJDIR)/$(PROJ_NAME).asm
    find $(OBJDIR) -type f -name '*.d' -print0 | xargs -0 -r rm
    find $(OBJDIR) -type f -name '*.o' -print0 | xargs -0 -r rm

.SECONDARY: $(OBJS)

ffconf.hの修正

LFNを扱うためと、timestamp機能が無いので、以下のdefineへ修正します。

#define FF_USE_LFN              3
#define FF_FS_NORTC             1 ←固定日付
#define FF_NORTC_MON    2 ←固定日付の月指定
#define FF_NORTC_MDAY   12←固定日付の日指定
#define FF_NORTC_YEAR   2023←固定日付の年指定

fatfs配下でmakeコマンドをたたけば、build配下に以下のファイルが生成されてるはずです。

ff.d  ff.o  ffsystem.d  ffsystem.o  ffunicode.d  ffunicode.o  mmc_picorv32_spi.d  mmc_picorv32_spi.o

firmware.cの修正

fw-flash/linker_flash.ldの修正

exampleではstackサイズが1KBとなっていますが、このサイズだとfatfsの関数呼び出し時にハングアップする事があったので、倍の2KBへ変更します。
linker_flash.ld内の_stack_size定義を以下の様に修正しました。
cpp _stack_size = DEFINED(_stack_size) ? _stack_size : 2k;

fw-flash/Makefileの修正

こちらのMakefileも修正しています。
LDFLAGSから-nostdlibの削除、sprintfを使っているので--specs=nano.specs --specs=nosys.specsが必要でした。
fw-flash/ 配下のMakefile

PROJ_NAME=fw-flash
DEBUG=no
BENCH=no
MULDIV=no
COMPRESSED=no

SRCS =  $(wildcard *.c)             \
        $(wildcard *.S)

LDSCRIPT = ./linker_flash.ld

SUBDIRS = fatfs

RISCV_NAME ?= riscv-none-embed
RISCV_PATH ?=  ~/riscv/riscv-none-embed-gcc/8.2.0-3.1

MABI=ilp32
MARCH := rv32i
ifeq ($(MULDIV),yes)
    MARCH := $(MARCH)m
endif
ifeq ($(COMPRESSED),yes)
    MARCH := $(MARCH)ac
endif

CFLAGS += -march=$(MARCH)  -mabi=$(MABI)  -ffunction-sections -fdata-sections
LDFLAGS += -march=$(MARCH)  -mabi=$(MABI)  -Wl,--gc-sections

ifeq ($(DEBUG),yes)
    CFLAGS += -g3 -O0 
endif

ifeq ($(DEBUG),no)
    CFLAGS += -g -O3 
endif

ifeq ($(BENCH),yes)
    CFLAGS += -fno-inline  
endif

RISCV_CLIB=$(RISCV_PATH)/$(RISCV_NAME)/lib/$(MARCH)/$(MABI)/

RISCV_OBJCOPY = $(RISCV_PATH)/bin/$(RISCV_NAME)-objcopy
RISCV_OBJDUMP = $(RISCV_PATH)/bin/$(RISCV_NAME)-objdump
RISCV_CC = $(RISCV_PATH)/bin/$(RISCV_NAME)-gcc

CFLAGS +=  -MD -fstrict-volatile-bitfields 
#LDFLAGS +=  -nostdlib -lgcc -mcmodel=medany -nostartfiles -ffreestanding -Wl,-Bstatic,-T,$(LDSCRIPT),-Map,$(OBJDIR)/$(PROJ_NAME).map,--print-memory-usage
LDFLAGS +=  -lgcc -mcmodel=medany -nostartfiles -ffreestanding --specs=nano.specs --specs=nosys.specs -Wl,-Bstatic,-T,$(LDSCRIPT),-Map,$(OBJDIR)/$(PROJ_NAME).map,--print-memory-usage

OBJDIR = build
OBJS := $(SRCS)
OBJS := $(OBJS:.c=.o)
OBJS := $(OBJS:.cpp=.o)
OBJS := $(OBJS:.S=.o)
OBJS := $(addprefix $(OBJDIR)/,$(OBJS))

#SUBOBJ := $(addprefix $(OBJDIR)/,$(SUBDIRS))
SUBOBJ := $(addsuffix /$(OBJDIR),$(SUBDIRS))
SUBOBJ := $(addsuffix /*.o,$(SUBOBJ))

export RISCV_CC CFLAGS LDFLAGS OBJDIR

all: $(SUBDIRS) $(OBJDIR)/$(PROJ_NAME).elf $(OBJDIR)/$(PROJ_NAME).hex $(OBJDIR)/$(PROJ_NAME).asm $(OBJDIR)/$(PROJ_NAME).v

$(SUBDIRS): ECHO
    make -C $@

ECHO:
    @echo $(SUBDIRS)

#$(OBJDIR)/%.elf: $(OBJS) | $(OBJDIR)
#   $(RISCV_CC) $(CFLAGS) -o $@ $^ $(SUBOBJ) $(LDFLAGS) $(LIBS)
$(OBJDIR)/%.elf: $(OBJS) | $(OBJDIR) 
    $(RISCV_CC) $(CFLAGS) -o $@ $^ $(SUBOBJ) $(LDFLAGS) $(LIBS)

#$(OBJDIR)/%.elf: $(SUBDIRS)/$(OBJDIR)
$(OBJDIR)/%.elf: $(SUBOBJ)

%.hex: %.elf
    $(RISCV_OBJCOPY) -O ihex $^ $@

%.bin: %.elf
    $(RISCV_OBJCOPY) -O binary $^ $@
    
%.v: %.elf
    $(RISCV_OBJCOPY) -O verilog $^ $@

%.asm: %.elf
    $(RISCV_OBJDUMP) -S -d $^ > $@

$(OBJDIR)/%.o: %.c
    mkdir -p $(dir $@)
    $(RISCV_CC) -c $(CFLAGS)  $(INC) -o $@ $^
    
$(OBJDIR)/%.o: %.cpp
    mkdir -p $(dir $@)
    $(RISCV_CC) -c $(CFLAGS)  $(INC) -o $@ $^   

$(OBJDIR)/%.o: %.S
    mkdir -p $(dir $@)
    $(RISCV_CC) -c $(CFLAGS) -o $@ $^ -D__ASSEMBLY__=1

$(OBJDIR):
    mkdir -p $@

clean:
    rm -f $(OBJDIR)/$(PROJ_NAME).elf
    rm -f $(OBJDIR)/$(PROJ_NAME).hex
    rm -f $(OBJDIR)/$(PROJ_NAME).map
    rm -f $(OBJDIR)/$(PROJ_NAME).v
    rm -f $(OBJDIR)/$(PROJ_NAME).asm
    find $(OBJDIR) -type f -name '*.d' -print0 | xargs -0 -r rm
    find $(OBJDIR) -type f -name '*.o' -print0 | xargs -0 -r rm

.SECONDARY: $(OBJS)
        

firmware.cのコード修正

これでfatfsの関数は使えるようになったので、 firmware.cのmain関数から呼び出すだけです。 メニュー"z"からSDカードアクセスが可能なようにしました。
root dirからファイル一覧を取得し、指定されたファイルを読みだして、UART(HDMI画面)へ出力するものです。
以下の3ファイルをSDカードのroot dirへ置きました。

file1.txt
this is file1

file2.txt
this is file2

test.txt
ABCDEFGHTJKLMNOPQRSTUVWXYZ
abcdefghtjklmnopqrstuvwxyz
0123456789

以下の様なコードへfirmware.cを修正しました。

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "fatfs/ff.h"
#include "fatfs/diskio.h"
#include "fatfs/spi.h"
//#include <string.h>

// a pointer to this is a null pointer, but the compiler does not
// know that because "sram" is a linker symbol from sections.lds.
extern uint32_t sram;

typedef struct {
    volatile uint32_t DATA;
    volatile uint32_t CLKDIV;
} PICOUART;

typedef struct {
    volatile uint32_t OUT;
    volatile uint32_t IN;
    volatile uint32_t OE;
} PICOGPIO;

typedef struct {
    union {
        volatile uint32_t REG;
        volatile uint16_t IOW;
        struct {
            volatile uint8_t IO;
            volatile uint8_t OE;
            volatile uint8_t CFG;
            volatile uint8_t EN; 
        };
    };
} PICOQSPI;


#define QSPI0 ((PICOQSPI*)0x81000000)
#define GPIO0 ((PICOGPIO*)0x82000000)
#define UART0 ((PICOUART*)0x83000000)


#define FLASHIO_ENTRY_ADDR ((void *)0x80000054)

void (*spi_flashio)(uint8_t *pdata, int length, int wren) = FLASHIO_ENTRY_ADDR;

// for fat test
char  sbuff[64];
#define DEF_FATBUFF  1024
char  buff_fattest[ DEF_FATBUFF ];
char file_name[10][64];     // 最大file数=10
// end

int putchar(int c)
{   
    if (c == '\n')
        UART0->DATA = '\r';
    UART0->DATA = c;
    
    return c;
}

void print(const char *p)
{
    while (*p)
        putchar(*(p++));
}

void print_hex(uint32_t v, int digits)
{
    for (int i = 7; i >= 0; i--) {
        char c = "0123456789abcdef"[(v >> (4*i)) & 15];
        if (c == '0' && i >= digits) continue;
        putchar(c);
        digits = i;
    }
}

void print_dec(uint32_t v)
{
    if (v >= 100) {
        print(">=100");
        return;
    }

    if      (v >= 90) { putchar('9'); v -= 90; }
    else if (v >= 80) { putchar('8'); v -= 80; }
    else if (v >= 70) { putchar('7'); v -= 70; }
    else if (v >= 60) { putchar('6'); v -= 60; }
    else if (v >= 50) { putchar('5'); v -= 50; }
    else if (v >= 40) { putchar('4'); v -= 40; }
    else if (v >= 30) { putchar('3'); v -= 30; }
    else if (v >= 20) { putchar('2'); v -= 20; }
    else if (v >= 10) { putchar('1'); v -= 10; }

    if      (v >= 9) { putchar('9'); v -= 9; }
    else if (v >= 8) { putchar('8'); v -= 8; }
    else if (v >= 7) { putchar('7'); v -= 7; }
    else if (v >= 6) { putchar('6'); v -= 6; }
    else if (v >= 5) { putchar('5'); v -= 5; }
    else if (v >= 4) { putchar('4'); v -= 4; }
    else if (v >= 3) { putchar('3'); v -= 3; }
    else if (v >= 2) { putchar('2'); v -= 2; }
    else if (v >= 1) { putchar('1'); v -= 1; }
    else putchar('0');
}

char getchar_prompt(char *prompt)
{
    int32_t c = -1;

    uint32_t cycles_begin, cycles_now, cycles;
    __asm__ volatile ("rdcycle %0" : "=r"(cycles_begin));

    if (prompt)
        print(prompt);

    // if (prompt)
        // GPIO0->OUT = ~0;
        // reg_leds = ~0;

    while (c == -1) {
        __asm__ volatile ("rdcycle %0" : "=r"(cycles_now));
        cycles = cycles_now - cycles_begin;
        if (cycles > 12000000) {
            if (prompt)
                print(prompt);
            cycles_begin = cycles_now;
            // if (prompt)
                // GPIO0->OUT = ~GPIO0->OUT;
                // reg_leds = ~reg_leds;
        }
        c = UART0->DATA;
    }
    // if (prompt)
        // GPIO0->OUT = 0;
        // reg_leds = 0;
    return c;
}

//char getchar()
char getchar_wrap()
{
    return getchar_prompt(0);
}

#define QSPI_REG_CRM  0x00100000
#define QSPI_REG_DSPI 0x00400000

void cmd_set_crm(int on)
{
    if (on) {
        QSPI0->REG |= QSPI_REG_CRM;
    } else {
        QSPI0->REG &= ~QSPI_REG_CRM;
    }
}

int cmd_get_crm() {
    return QSPI0->REG & QSPI_REG_CRM;
}

void cmd_set_dspi(int on)
{
    if (on) {
        QSPI0->REG |= QSPI_REG_DSPI;
    } else {
        QSPI0->REG &= ~QSPI_REG_DSPI;
    }
}

int cmd_get_dspi() {
    return QSPI0->REG & QSPI_REG_DSPI;
}

void cmd_read_flash_id()
{
    int pre_dspi = cmd_get_dspi();

    cmd_set_dspi(0);
    
    uint8_t buffer[4] = { 0x9F, /* zeros */ };
    spi_flashio(buffer, 4, 0);

    for (int i = 1; i <= 3; i++) {
        putchar(' ');
        print_hex(buffer[i], 2);
    }
    putchar('\n');

    cmd_set_dspi(pre_dspi);
}

// --------------------------------------------------------

uint32_t cmd_benchmark(bool verbose, uint32_t *instns_p)
{
    uint8_t data[256];
    uint32_t *words = (void*)data;

    uint32_t x32 = 314159265;

    uint32_t cycles_begin, cycles_end;
    uint32_t instns_begin, instns_end;
    __asm__ volatile ("rdcycle %0" : "=r"(cycles_begin));
    __asm__ volatile ("rdinstret %0" : "=r"(instns_begin));

    for (int i = 0; i < 20; i++)
    {
        for (int k = 0; k < 256; k++)
        {
            x32 ^= x32 << 13;
            x32 ^= x32 >> 17;
            x32 ^= x32 << 5;
            data[k] = x32;
        }

        for (int k = 0, p = 0; k < 256; k++)
        {
            if (data[k])
                data[p++] = k;
        }

        for (int k = 0, p = 0; k < 64; k++)
        {
            x32 = x32 ^ words[k];
        }
    }

    __asm__ volatile ("rdcycle %0" : "=r"(cycles_end));
    __asm__ volatile ("rdinstret %0" : "=r"(instns_end));

    if (verbose)
    {
        print("Cycles: 0x");
        print_hex(cycles_end - cycles_begin, 8);
        putchar('\n');

        print("Instns: 0x");
        print_hex(instns_end - instns_begin, 8);
        putchar('\n');

        print("Chksum: 0x");
        print_hex(x32, 8);
        putchar('\n');
    }

    if (instns_p)
        *instns_p = instns_end - instns_begin;

    return cycles_end - cycles_begin;
}

void cmd_benchmark_all()
{
    uint32_t instns = 0;

    print("default        ");

    cmd_set_dspi(0);
    cmd_set_crm(0);

    print(": ");
    print_hex(cmd_benchmark(false, &instns), 8);
    putchar('\n');

    print("dspi-");
    print_dec(0);
    print("         ");

    cmd_set_dspi(1);

    print(": ");
    print_hex(cmd_benchmark(false, &instns), 8);
    putchar('\n');

    print("dspi-crm-");
    print_dec(0);
    print("     ");

    cmd_set_crm(1);

    print(": ");
    print_hex(cmd_benchmark(false, &instns), 8);
    putchar('\n');

    print("instns         : ");
    print_hex(instns, 8);
    putchar('\n');
}

volatile int i;
// --------------------------------------------------------

#define CLK_FREQ        25175000
#define UART_BAUD       115200

int  fat_test_init( void )
{
    DSTATUS  ret;
    int  result = 0;

    ret = disk_initialize( 0 );
    if( ret & STA_NOINIT ) {
        result = -1;
    }

    return  result;
}


int  cat_file( char *filename, char *buff, int bsize )
{
    FRESULT  ret;
    FATFS  fs;
    FIL  fil;
    UINT  rdsz ;

    ret = f_mount( &fs, "", 0 );
    if( ret != FR_OK ) {
        return  -1;
    }
    ret = f_open( &fil, filename, FA_READ );
    if( ret != FR_OK ) {
        return  -2;
    }

    ret = f_read( &fil, buff, (UINT)bsize, &rdsz );
    if( ret != FR_OK ) {
        return  -3;
    }
    buff[rdsz]=0;
    print(buff);
    
    f_close( &fil );

    return  (int)rdsz;
}
//FRESULT scan_files (
int scan_files (
    //char* path        /* Start node to be scanned (***also used as work area***) */
    void        /* Start node to be scanned (***also used as work area***) */
)
{
    FATFS fs;
    FRESULT res;
    DIR dir;
    UINT i;
    static FILINFO fno;

    int file_num=0;
    int file_name_pos;
    
    res = f_mount( &fs, "", 1 );

    if(res == FR_OK) {
    
        res = f_opendir(&dir, "/");             /* Open the directory */
        //return 0;
        if (res == FR_OK) {
            for (;;) {
                res = f_readdir(&dir, &fno);                   /* Read a directory item */
                if (res != FR_OK || fno.fname[0] == 0) break;  /* Break on error or end of dir */
                if (fno.fattrib & AM_DIR) {                    /* It is a directory */
                } else {                                       /* It is a file. */
                    //sprintf(sbuff, "%s/%s\n", "/", fno.fname);
                    sprintf(sbuff, "%d) %s\n", file_num,fno.fname);
                    print(sbuff);
                    file_name_pos=0;
                    while(fno.fname[file_name_pos]!=0) {
                        file_name[file_num][file_name_pos]=fno.fname[file_name_pos];
                        file_name_pos++;
                    }
                    file_name[file_num][file_name_pos]=fno.fname[file_name_pos];
                    file_num++;
                }
            
            }
            f_closedir(&dir);
        }
    }

    return file_num;
}

int move_to_sd_menu()
{
    int file_num=0;
    print("\n");
    print("TF menu> Select an action:\n");
    print("\n");
    print("TF menu>    [1] print files in root dir\n");
    print("TF menu>    [2] cat file\n");
    print("TF menu>    [q] Exit TF card menu\n");

    for (int rep = 10; rep > 0; rep--) {
        print("\n");

        print("TF menu> Command> ");
        char cmd = getchar_wrap();
        if (cmd > 32 && cmd < 127)
            putchar(cmd);
        print("\n");

        switch (cmd) {
        case '1':
            file_num = scan_files();
            break;
        case '2':
            if(file_num != 0) {
                print("input file no> ");
                while(1) {
                    cmd = getchar_wrap();
                    if (cmd > 32 && cmd < 127)
                        putchar(cmd);
                    print("\n");
                    if((cmd>=48)&&(cmd<=57)) {  // 0-9
                        sprintf(sbuff, "disp : %s\n\n", file_name[cmd-48]);
                        print(sbuff);
                        cat_file(file_name[cmd-48],buff_fattest, DEF_FATBUFF );
                        break;
                    }
                    else {
                        sprintf(sbuff,"illegal no. range: %d - %d\n",0,file_num-1);
                        print(sbuff);
                    }
                 }
            }
            else {
                print("first select no.1 or nothing files\n");
            }
            break;
        case 'q':
        case 'Q':
            return 0;
            break;
        default:
            continue;
        }
    }
}

void main()
{
    int fatfs_ret;
    int  wsize;

    UART0->CLKDIV = CLK_FREQ / UART_BAUD - 2;

    GPIO0->OE = 0x3F;
    GPIO0->OUT = 0x3F;

    cmd_set_crm(1);
    cmd_set_dspi(1);

    print("\n");
    print("  ____  _          ____         ____\n");
    print(" |  _ \\(_) ___ ___/ ___|  ___  / ___|\n");
    print(" | |_) | |/ __/ _ \\___ \\ / _ \\| |\n");
    print(" |  __/| | (_| (_) |__) | (_) | |___\n");
    print(" |_|   |_|\\___\\___/____/ \\___/ \\____|\n");
    print("\n");
    print("        On Lichee Tang Nano-9K\n");
    print("This is modified firmware. add SD access.\n");
    print("\n");

    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x01;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x02;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x04;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x08;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x10;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F ^ 0x20;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x00;
    for ( i = 0 ; i < 10000; i++);
    GPIO0->OUT = 0x3F;
    for ( i = 0 ; i < 10000; i++);

// SD card

    fatfs_ret = fat_test_init();
    if( fatfs_ret != 0 ) {
        print("fat_test_init()  ERROR!\n" );
    }
    while (1)
    {
        print("\n");
        print("Select an action:\n");
        print("\n");
        print("   [1] Toggle led 1\n");
        print("   [2] Toggle led 2\n");
        print("   [3] Toggle led 3\n");
        print("   [4] Toggle led 4\n");
        print("   [5] Toggle led 5\n");
        print("   [6] Toggle led 6\n");
        print("   [F] Get flash mode\n");
        print("   [I] Read SPI flash ID\n");
        print("   [S] Set Single SPI mode\n");
        print("   [D] Set DSPI mode\n");
        print("   [C] Set DSPI+CRM mode\n");
        print("   [B] Run simplistic benchmark\n");
        print("   [A] Benchmark all configs\n");
        print("<Additional Menu>\n");
        print("   [Z] Check files in TF card\n");
        
        for (int rep = 10; rep > 0; rep--)
        {
            print("\n");
            print("IO State: ");
            print_hex(GPIO0->IN, 8);
            print("\n");

            print("\n");

            print("Command> ");
            char cmd = getchar_wrap();
            if (cmd > 32 && cmd < 127)
                putchar(cmd);
            print("\n");

            switch (cmd)
            {
            case 'F':
            case 'f':
                print("\n");
                print("SPI State:\n");
                print("  DSPI ");
                if ( cmd_get_dspi() )
                    print("ON\n");
                else
                    print("OFF\n");
                print("  CRM  ");
                if ( cmd_get_crm() )
                    print("ON\n");
                else
                    print("OFF\n");

                break;

            case 'I':
            case 'i':
                cmd_read_flash_id();
                break;

            case 'S':
            case 's':
                cmd_set_dspi(0);
                cmd_set_crm(0);
                break;

            case 'D':
            case 'd':
                cmd_set_crm(0);
                cmd_set_dspi(1);
                break;

            case 'C':
            case 'c':
                cmd_set_crm(1);
                cmd_set_dspi(1);
                break;

            case 'B':
            case 'b':
                cmd_benchmark(1, 0);
                break;

            case 'A':
            case 'a':
                cmd_benchmark_all();
                break;

            case '1':
                GPIO0->OUT ^= 0x00000001;
                break;

            case '2':
                GPIO0->OUT ^= 0x00000002;
                break;

            case '3':
                GPIO0->OUT ^= 0x00000004;
                break;

            case '4':
                GPIO0->OUT ^= 0x00000008;
                break;

            case '5':
                GPIO0->OUT ^= 0x00000010;
                break;

            case '6':
                GPIO0->OUT ^= 0x00000020;
                break;
            case 'z':
            case 'Z':
                move_to_sd_menu();
                break;

            default:
                continue;
            }
        }
    }
}

void irqCallback() {

}

makeコマンドをたたくと、build/fw-flash.v が生成されます。
下記コマンドをDOS窓またはpower shellで実行しS1ボタンを押すとフラッシュへ書き込まれます。
python .\sw\pico-programmer.py .\fw\fw-flash\build\fw-flash.v COM14

メニューがHDMIに現れたら、teratermなどのシリアル端末で繋ぎます。Enterキーを押すとHDMIに表示されたメニューと同様なメニューがシリアル端末に表示されます
z→1 と押すとSDカードのrootに置いたファイル名が以下の様に表示されます。

0) test.txt
1) file1.txt
2) file2.txt

2を押すとinput file no> と聞かれます。
0-2のどれかを押すと、そのファイルの中身が表示されます。2を押すと

input file no> 2
disp : file2.txt

this is file2

となり無事に中身が表示されます。

シリアルコンソールの表示の様子↓

_|  ___  / ___|
 | |_) | |/ __/ _ \___ \ / _ \| |
 |  __/| | (_| (_) |__) | (_) | |___
 |_|   |_|\___\___/____/ \___/ \____|

        On Lichee Tang Nano-9K
This is modified firmware. add SD access.


Select an action:

   [1] Toggle led 1
   [2] Toggle led 2
   [3] Toggle led 3
   [4] Toggle led 4
   [5] Toggle led 5
   [6] Toggle led 6
   [F] Get flash mode
   [I] Read SPI flash ID
   [S] Set Single SPI mode
   [D] Set DSPI mode
   [C] Set DSPI+CRM mode
   [B] Run simplistic benchmark
   [A] Benchmark all configs

   [Z] Check files in TF card

IO State: 0000007f

Command>

IO State: 0000007f

Command> z

TF menu> Select an action:

TF menu>    [1] print files in root dir
TF menu>    [2] cat file
TF menu>    [q] Exit TF card menu

TF menu> Command> 1
0) test.txt
1) file1.txt
2) file2.txt

TF menu> Command> 2
input file no> 2
disp : file2.txt

this is file2

TF menu> Command>

今回は以上となります。

SDカードへアクセスするために、ハードよりもソフト面が苦労しました。コンパイルオプションを変更しないとコンパイルが通らなかったりとか、stackサイズが足りずにハングアップとか。

次はボードに搭載されているSDRAMを動かしたいと思います。いつになることやら。。。
SDカードへのアクセスが遅いので、改善もさせたいです。

参考にさせていただいたサイト

PicoRV on nano 9K - Sipeed Wiki
TangNano-9K-example/picotiny at main · sipeed/TangNano-9K-example · GitHub
ラズパイPicoでFatFsを使う - Qiita
GitHub - nandland/spi-master: SPI Master for FPGA - VHDL and Verilog
Copyright (c) 2019 russell-merrick
MMC/SDCの使いかた
Release xPack GNU RISC-V Embedded GCC v8.2.0-3.1 · xpack-dev-tools/riscv-none-embed-gcc-xpack · GitHub
UG286-1.9.5J_Gowin Clockユーザーガイド.pdf
Tang_Nano_9K_3672_schematic.pdf
DS117-2.9.3E_GW1NR series of FPGA Products DataSheet.pdf

前回の記事はこちら
spend-carefree.hatenablog.com