4
Help us understand the problem. What are the problem?

posted at

ラズパイPicoでFatFsを使う

はじめに

電子工作界隈で注目されているRaspberry Pi Pico(以後、ラズパイPico)でSDカード上のテストファイルやバイナリファイルへアクセスできるようオープンソースのFatFsを用いて実装することにしました。

以前に「Cypress FM4でFatfsを使う」で FatFsとCypress製マイコンのFM4へ SPI経由での接続、実装は経験済みで、基本的にSPIへアクセスする以下の6つの関数やマクロをラズパイPico用に改造すればよいことが既に分かっています。

・SPI初期化:init_spi()
・SPI1バイト送受信:xchg_spi()
・SPI複数バイト受信:rcvr_spi_multi()
・SPI複数バイト送信:xmit_spi_multi()
・チップセレクト・High:CS_HIGH()
・チップセレクト・Low:CS_LOW()

※Cypress FM4でFatfsを使う
 https://qiita.com/Yukiya_Ishioka/items/77cd75ca03fb5c7c5c0b

ラズパイPicoでは開発用にPico-SDKという各種デバイスへのアクセス用サブルーチン群が提供されていて、この中にSPI用のアクセスルーチンがあることから、Pico-SDKのルーチンを使って実装しました。

ラズパイPicoのビルド環境

私はインターフェース誌 2021年8月号でFreeRTOSを紹介した記事内に記してある Ubuntu上に必要なパッケージをインストールした環境を使っていますが、Pico-SDKを使ってビルドできる環境であれば問題ないと思います。

※インターフェース誌 2021年8月号
 https://interface.cqpub.co.jp/magazine/202108/

ファイルの取得

ビルド環境の構築ができたら、今回使用するソース類をネットワークから取得します。

FatFs

FatFsは以下のページからソースをダウンロードできます。
使用するファイルやダウンロード場所の詳細は、FatFsは先に紹介したQiitaの記事「Cypress FM4でFatfsを使う」を参照してください。
ここでも stm32f用のコードをラズパイPico用に改造して使用します。

※FatFsのページ
 http://elm-chan.org/fsw/ff/00index_e.html

FatFs本体と併せ、以下の2つのファイルをダウンロードしてください。
ff14b.zip
ffsample.zip

Pico-SDK

Pico-SDKは以下のページからソースをダウンロードできます。
ダウロードや展開方法は、やはりインターフェース誌 2021年8月号のFreeRTOSの記事内に記してあるので参考にしてください。

※Raspberry Pi Pico SDKのページ
 https://github.com/raspberrypi/pico-sdk

FatFsの展開

FatFs本体を展開します。
ff14b.zipファイル内から sourceディレクトリを取り出し、PC内の任意のディレクトリへコピーします。
今後の操作を考えると、以下のようにPico-SDKを展開した同じディレクトリにsrcディレクトリを作成し、その中に展開するとよいと思います。

|--pico-sdk
|
|--src

次に、ffsample.zipファイル内から\stm32\mmc_stm32f1_spi.cファイルを取り出し、先のFatFs本体のファイルと同じディレクトリへ置きます。
その後、mmc_pico_spi.cというファイル名に置き替えます。
以下のファイルが置かれていると思います。

00history.txt
00readme.txt
diskio.c
diskio.h
ff.c
ff.h
ffconf.h
ffsystem.c
ffunicode.c
mmc_pico_spi.c

展開後のディレクトリ構成は以下のようになっていると思います。

|--pico-sdk
|
|--src
|  |--00history.txt
|  |--00readme.txt
|  |--diskio.c
|  |--diskio.h
|  |--ff.c
|  |--ff.h
|  |--ffconf.h
|  |--ffsystem.c
|  |--ffunicode.c
|  |--mmc_pico_spi.c

FatFs試行用のハードウェア

・SDカードモジュールとの結線

PicoのPin GPIO No. SD Card-Module
21 GPIO 16 MISO (SDO)
25 GPIO 19 MOSI (SDI)
24 GPIO 18 SCK
22 GPIO 17 /CS

・USB-シリアル モジュールとの結線

PicoのPin GPIO No. USB-シリアル
25 GPIO 20 TxD
26 GPIO 21 RxD

Pico_FatFs_Pic1.jpg

Pico用コードの作成

コピーした状態では mmc_pico_spi.c はSTM32用の制御コードとなっているため、14行目から208行目までを以下のコードへ置き替えます。

mmc_pico_spi.c
#include "pico/stdlib.h"
#include "hardware/spi.h"

/*
   GPIO 16 (pin 21) MISO/spi0_rx-> SDO
   GPIO 17 (pin 22) Chip select -> !CS
   GPIO 18 (pin 24) SCK/spi0_sclk -> SCK
   GPIO 19 (pin 25) MOSI/spi0_tx -> SDI
*/
#define DEF_SPI_TX_PIN  19
#define DEF_SPI_RX_PIN  16
#define DEF_SPI_SCK_PIN 18
#define DEF_SPI_CSN_PIN 17

#define SPIDEV      spi0

#define FCLK_FAST() { }
#define FCLK_SLOW() { }

#define CS_HIGH()   { gpio_put( DEF_SPI_CSN_PIN, 1 ); /* HIGH */ }
#define CS_LOW()    { gpio_put(DEF_SPI_CSN_PIN , 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 )
{
  for( conter1=0; conter1<125*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_init( SPIDEV, 5 * 1000 * 1000 ); /* 5Mbps */
    gpio_set_function( DEF_SPI_TX_PIN, GPIO_FUNC_SPI );
    gpio_set_function( DEF_SPI_RX_PIN, GPIO_FUNC_SPI );
    gpio_set_function( DEF_SPI_SCK_PIN, GPIO_FUNC_SPI );

    /* CS# */
    gpio_init( DEF_SPI_CSN_PIN );
    gpio_set_dir( DEF_SPI_CSN_PIN, GPIO_OUT);

    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 */
)
{
    uint8_t  src, dst;

    src = dat;
    spi_write_read_blocking( SPIDEV, &src, &dst, 1 );
    return  (BYTE)dst;
}


/* 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 );
}


/* Send multiple byte */
static void xmit_spi_multi (
    const BYTE *buff,   /* Pointer to the data */
    UINT btx            /* Number of bytes to send (even number) */
)
{
    spi_write_blocking( SPIDEV, buff, btx );
}

1)定義

修正プログラムの先頭部分は ラズパイPico用のヘッダファイルの読み込みやデバイスアクセスに必要な定数やマクロを定義しています。

2)init_spi()

今回、SDカードのアクセスに SPIを用いるので、SPIデバイスの初期化と使用するピンの設定を行っています。
SPIの信号は以下のピンを用いています。

   GPIO 16 (pin 21) MISO/spi0_rx-> SDO
   GPIO 17 (pin 22) Chip select -> !CS
   GPIO 18 (pin 24) SCK/spi0_sclk -> SCK
   GPIO 19 (pin 25) MOSI/spi0_tx -> SDI

3)xchg_spi()

SPIへのデータの送信と受信を行う関数です。
SDK内の spi_write_read_blocking()を使って書き込み、読み出しを行っています。

4)rcvr_spi_multi()

複数データの受信を行う関数です。
SDK内の spi_read_blocking()を使って読み出しを行っています。

5)xmit_spi_multi()

複数データの送信を行う関数です。
SDK内の spi_write_blocking()を使って書き込みを行っています。

Fatfsのコンフィグレーション

Fatfsをどのように使うかのコンフィグレーションをFatfs本体内のファイル ffconf.hで設定することができます。
今回はファイルのタイムスタンプに必要な時間を取得する機能がなたいめ、ファイル更新日時をマクロ固定で取得することにしました。
コンフィグレーションを以下のように変更します。

ffconf.h
#define FF_FS_NORTC             0

 ↓

ffconf.h
#define FF_FS_NORTC             1

cmake用の定義ファイル

cmakeでビルドするための定義ファイルをsrcディレクトリ内に追加します。
add_executable()へビルド対象のCソースファイルを列記します。
diskio.c は今回使用しないため、記述に含みません。
main()関数を含むサンプルプログラムを記述するためのsd-test.cというファイル名を追記します。
ビルド後、 sd-testという名称のファイル群が出力されます。

CMakeLists.txt
add_executable(sd-test
    sd-test.c
    ff.c
    ffsystem.c
    ffunicode.c
    mmc_pico_spi.c
        )

# Pull in our pico_stdlib which pulls in commonly used features
target_link_libraries(sd-test pico_stdlib  hardware_spi)

# create map/bin/hex file etc.
pico_add_extra_outputs(sd-test)

# add url via pico_set_program_url
example_auto_set_url(sd-test)

サンプルプログラム

以下のサンプルプログラムを示します。
SDカード上の /TEST.TXT ファイルの内容を読み出し、/W_TEST1.TXT と W_TEST2.TXT を生成して読み出した内容を書き込みます。
プログラムの動作状況は UARTへ出力します。
最後に TEST.TXT の先頭10バイトを UARTへ出力します。

sd-test.c
#include <stdio.h>
#include <string.h>
#include "ff.h"
#include "diskio.h"

#include "hardware/gpio.h"
#include "hardware/uart.h"

#define UART_ID uart1
#define BAUD_RATE 115200

#define UART_TX_PIN 20
#define UART_RX_PIN 21


char  sbuff[64];
#define DEF_FATBUFF  1024
char  buff_fattest[ DEF_FATBUFF ];


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

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

    return  result;
}


int  fat_test_read( 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, "test.txt", FA_READ );
    if( ret != FR_OK ) {
        return  -2;
    }

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

    f_close( &fil );

    return  (int)rdsz;
}


int  fat_test_write( char *filename, char *buff, int size )
{
    FRESULT  ret;
    FATFS  fs;
    FIL  fil;
    UINT  wsize ;

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

    ret = f_write( &fil, buff, (UINT)size, &wsize );
    if( ret != FR_OK ) {
        return  -3;
    }

    f_close( &fil );

    return  (int)wsize;
}


int  main( void )
{
    int  ret;
    int  wsize;

    uart_init( UART_ID, BAUD_RATE );
    gpio_set_function( UART_TX_PIN, GPIO_FUNC_UART );
    gpio_set_function( UART_RX_PIN, GPIO_FUNC_UART );

    uart_puts( UART_ID, "\n----- START fatfs on pico -----\n" );

    ret = fat_test_init();
    if( ret != 0 ) {
        uart_puts( UART_ID, "fat_test_init()  ERROR!\n" );
    }
    ret = fat_test_read( buff_fattest, DEF_FATBUFF );
    sprintf( sbuff, "fat_test_read()  ret = %d\n", ret );
    uart_puts( UART_ID, sbuff );

    if( ret > 0 ) {
        wsize = ret;
        ret = fat_test_write( "w_test1.txt", buff_fattest, wsize );
        sprintf(sbuff, "fat_test_write()  ret = %d\n", ret );
        uart_puts( UART_ID, sbuff );

        ret = fat_test_write( "w_test2.txt", buff_fattest, wsize );
        sprintf(sbuff, "fat_test_write()  ret = %d\n", ret );
        uart_puts( UART_ID, sbuff );
    }
    buff_fattest[ 10 ] = 0xd;
    buff_fattest[ 11 ] = 0xa;
    buff_fattest[ 12 ] = 0x0;
    uart_puts( UART_ID, buff_fattest );

    return  0;
}

以下の内容の test.txt を使った場合の出力結果です。

 test.txt
ABCDEFGHTJKLMNOPQRSTUVWXYZ
abcdefghtjklmnopqrstuvwxyz
0123456789

Pico_FatFs_Pic2.jpg

さいごに

ラズパイPicoを使った工作はいろいろネット上に公開され、SDカードを使った例もあるようです。
ただ、複数の機能を組み合わせた工作品として組み込まれているため、SDカードのみを使う場合には他の機能を削除するなどの作業が必要そうだったので、FatFs単一でSDカードを使うプログラムを公開してみました。

配線もSDカードモジュールだけなら6本、UARTを含めても9本を接続することで使えるので、一度、試してみてはいかがでしょうか。

- 以上 -

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
4
Help us understand the problem. What are the problem?