2025年6月4日水曜日

Enhanced Midrange PIC16FシリーズでSDカードの読み書きをする

PIC16F18857など一部のEnhanced midrange PIC16Fシリーズは、FATファイルシステムを持つMMC互換カード(要するにSDカード)の読み書きをできるだけの非常に大きなプログラムメモリとデータメモリを持っています。 

MMCカードは、通常はセクターサイズが512バイトになっているため、512バイトごとの読み書きが求められます。そのため、少なくとも512バイトのバッファーは必要となります。一方で、PIC16F18857は4kB(4096バイト)ものデータメモリを持っているため、充分にその能力があるのです。PIC16Fシリーズもここまで来ました(参考までに、PIC16F84Aは68バイト、PIC16F1827は384バイトです)。

***** 

余談ですが、PIC16Fシリーズは基本的に以下のようなデータメモリ構造になっています。 

このアーキテクチャは昔から変わりませんね。ひとつのバンクに7bit分(128バイト分)のメモリがあって、12バイトのコアレジスタ(どのバンクからでも共通で参照される特別なレジスタ)、SFR(周辺機器を制御するためのレジスタ)、80バイトの汎用レジスタ、16バイトの共通汎用レジスタ(どのバンクからでも共通で参照される汎用レジスタ)となっています。

PIC16F18857では、なんとバンクが全部で64個もあります。 そして、この80バイトの汎用レジスタはバンク0~50で実装されており、80×51=4080バイト、そして共通汎用レジスタの16バイトを足して4096バイトというわけです。

また、PIC16Fシリーズはハーバードアーキテクチャ(プログラムメモリとデータメモリが別のメモリ空間となっているアーキテクチャ)ですが、Enhanced MidrangeではFSR (File Select Register)が16bit長に拡張されており、また、データメモリもプログラムメモリもすべて同じメモリ空間上で間接参照できるようになっています。この汎用データメモリも物理的には80バイト×51バンクとバラバラに配置されていますが、FSR経由でリニアにアクセスできるようにされています。

もはや執念すら感じますね。

***** 

さて、話を戻して、伝統的なアーキテクチャに縛られながらも大容量メモリを実現したこのマイコンを使用して、MMCの読み書きを行ってみたいと思います。

MMCの読み書きをする以上は、FATファイルシステムに対応できなければ意味がありません。今回は(も?)ChaN氏のFatFsを使用してMMCの読み書きをしてみます。

回路

まずは以下のような回路を実装します。

私はユニバーサル基板とDIP化されたMicroSDスロットを使用して実装しました。ブレッドボードでももしかしたらいけるかもしれませんが、SPIが数MHzでの送信となるため、周囲との結合容量が大きいともしかしたら上手く動かないかもしれません。

電源のことは回路図に書きませんでしたが、簡単にUSBから電源をもらって3.3Vの3端子レギュレーターで落とす回路を作って供給しています。

  • RA1:挿入検知
  • RA2:CS
  • RB0:MISO(MMC出力→PIC入力)
  • RC6:MOSI(PIC出力→MMC入力)
  • RC7:SCLK 

もっぱらPIC16F18857にはPPS (Peripheral Pin Select)機能があって、SPI含むペリフェラルを任意のポート(※)に割り当てることができます。そのため、これと全く同じ回路が必須というわけではなく、PPSで割り当て可能な範囲で別のポートを使っても問題ありません。

※:割り当て可能なポートの範囲はペリフェラルごとに決まっています(本当に何でも好きなポートに割り当てられるわけではありません)。データシートをよく読んで回路を設計してください。 

FatFsの適用

さて、FatFsをプロジェクトに追加していきます。結論だけ知りたい人は、こちらのGithubレポジトリにコードを上げておいたので、こちらも参考にしてください。

やることは主に以下の2つです。

  • FatFsのオプション設定
  • FatFsの改造 
  • diskio.cの実装

順に説明していきます。

FatFsのオプションの設定

ffconf.hをいじります。デフォルトで読み書き両方できるオプションになっているので大きくいじる必要は無いかと思います。RTCが搭載されている場合は、FF_FS_NORTCを0にしたうえでget_fattime関数を実装しましょう。今回はそうではないので、RTC代わりに固定の日付を設定しておきます。下の例は日付を2025/6/1としているパターンです。

#define FF_FS_NORTC      1
#define FF_NORTC_MON     6
#define FF_NORTC_MDAY    1
#define FF_NORTC_YEAR    2025
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/  an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/  timestamp feature. Every object modified by FatFs will have a fixed timestamp
/  defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/  To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
/  to the project to read current time form real-time clock. FF_NORTC_MON,
/  FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/  These options have no effect in read-only configuration (FF_FS_READONLY = 1). */

FatFsの改造

さて、PIC16FシリーズでFatFsを使う場合、FatFs本体の改造を一点だけする必要があります。

というのも、XC8では関数の再帰呼び出しができないのですが、FatFsでは再帰呼び出しがされている場所があります。putc_bfd(putbuff* pb, TCHAR c)という内部関数なのですが、LFをCRLFに置き換える場合、\nがやって来た時に再帰呼び出しで\rを追加する処理を行っています。

再帰呼び出しを使わずとも簡単なif文で対処できる程度のものなので、再帰以外の部分をputc_bfd_internal(putbuff* pb, TCHAR c)という別の関数に分離して対処しましょう。

static void putc_bfd_internal(putbuff* pb, TCHAR c)
{
    UINT n;
    int i, nc;
#if FF_USE_LFN && FF_LFN_UNICODE
    WCHAR hs, wc;
#if FF_LFN_UNICODE == 2
    DWORD dc;
    const TCHAR* tp;
#endif
#endif

    i = pb->idx;            /* Write index of pb->buf[] */
    if (i < 0) return;        /* In write error? */
    nc = pb->nchr;            /* Write unit counter */

#if FF_USE_LFN && FF_LFN_UNICODE
#if FF_LFN_UNICODE == 1        /* UTF-16 input */
    if (IsSurrogateH(c)) {    /* Is this a high-surrogate? */
        pb->hs = c; return;    /* Save it for next */
    }
    hs = pb->hs; pb->hs = 0;
    if (hs != 0) {            /* Is there a leading high-surrogate? */
        if (!IsSurrogateL(c)) hs = 0;    /* Discard high-surrogate if a stray high-surrogate */
    } else {
        if (IsSurrogateL(c)) return;    /* Discard stray low-surrogate */
    }
    wc = c;
#elif FF_LFN_UNICODE == 2    /* UTF-8 input */
    for (;;) {
        if (pb->ct == 0) {    /* Not in the multi-byte sequence? */
            pb->bs[pb->wi = 0] = (BYTE)c;    /* Save 1st byte */
            if ((BYTE)c < 0x80) break;                    /* Single byte code? */
            if (((BYTE)c & 0xE0) == 0xC0) pb->ct = 1;    /* 2-byte sequence? */
            if (((BYTE)c & 0xF0) == 0xE0) pb->ct = 2;    /* 3-byte sequence? */
            if (((BYTE)c & 0xF8) == 0xF0) pb->ct = 3;    /* 4-byte sequence? */
            return;                                        /* Invalid leading byte (discard it) */
        } else {                /* In the multi-byte sequence */
            if (((BYTE)c & 0xC0) != 0x80) {    /* Broken sequence? */
                pb->ct = 0; continue;        /* Discard the sequence */
            }
            pb->bs[++pb->wi] = (BYTE)c;    /* Save the trailing byte */
            if (--pb->ct == 0) break;    /* End of the sequence? */
            return;
        }
    }
    tp = (const TCHAR*)pb->bs;
    dc = tchar2uni(&tp);            /* UTF-8 ==> UTF-16 */
    if (dc == 0xFFFFFFFF) return;    /* Wrong code? */
    hs = (WCHAR)(dc >> 16);
    wc = (WCHAR)dc;
#elif FF_LFN_UNICODE == 3    /* UTF-32 input */
    if (IsSurrogate(c) || c >= 0x110000) return;    /* Discard invalid code */
    if (c >= 0x10000) {        /* Out of BMP? */
        hs = (WCHAR)(0xD800 | ((c >> 10) - 0x40));     /* Make high surrogate */
        wc = 0xDC00 | (c & 0x3FF);                    /* Make low surrogate */
    } else {
        hs = 0;
        wc = (WCHAR)c;
    }
#endif
    /* A code point in UTF-16 is available in hs and wc */

#if FF_STRF_ENCODE == 1        /* Write a code point in UTF-16LE */
    if (hs != 0) {    /* Surrogate pair? */
        st_word(&pb->buf[i], hs);
        i += 2;
        nc++;
    }
    st_word(&pb->buf[i], wc);
    i += 2;
#elif FF_STRF_ENCODE == 2    /* Write a code point in UTF-16BE */
    if (hs != 0) {    /* Surrogate pair? */
        pb->buf[i++] = (BYTE)(hs >> 8);
        pb->buf[i++] = (BYTE)hs;
        nc++;
    }
    pb->buf[i++] = (BYTE)(wc >> 8);
    pb->buf[i++] = (BYTE)wc;
#elif FF_STRF_ENCODE == 3    /* Write a code point in UTF-8 */
    if (hs != 0) {    /* 4-byte sequence? */
        nc += 3;
        hs = (hs & 0x3FF) + 0x40;
        pb->buf[i++] = (BYTE)(0xF0 | hs >> 8);
        pb->buf[i++] = (BYTE)(0x80 | (hs >> 2 & 0x3F));
        pb->buf[i++] = (BYTE)(0x80 | (hs & 3) << 4 | (wc >> 6 & 0x0F));
        pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F));
    } else {
        if (wc < 0x80) {    /* Single byte? */
            pb->buf[i++] = (BYTE)wc;
        } else {
            if (wc < 0x800) {    /* 2-byte sequence? */
                nc += 1;
                pb->buf[i++] = (BYTE)(0xC0 | wc >> 6);
            } else {            /* 3-byte sequence */
                nc += 2;
                pb->buf[i++] = (BYTE)(0xE0 | wc >> 12);
                pb->buf[i++] = (BYTE)(0x80 | (wc >> 6 & 0x3F));
            }
            pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F));
        }
    }
#else                        /* Write a code point in ANSI/OEM */
    if (hs != 0) return;
    wc = ff_uni2oem(wc, CODEPAGE);    /* UTF-16 ==> ANSI/OEM */
    if (wc == 0) return;
    if (wc >= 0x100) {
        pb->buf[i++] = (BYTE)(wc >> 8); nc++;
    }
    pb->buf[i++] = (BYTE)wc;
#endif

#else                            /* ANSI/OEM input (without re-encoding) */
    pb->buf[i++] = (BYTE)c;
#endif

    if (i >= (int)(sizeof pb->buf) - 4) {    /* Write buffered characters to the file */
        f_write(pb->fp, pb->buf, (UINT)i, &n);
        i = (n == (UINT)i) ? 0 : -1;
    }
    pb->idx = i;
    pb->nchr = nc + 1;
}

static void putc_bfd (putbuff* pb, TCHAR c)
{
    if(FF_USE_STRFUNC == 2 && c == '\n') {     /* LF -> CRLF conversion */
        putc_bfd_internal(pb, '\r');
    }
    putc_bfd_internal(pb, c);
}

diskio.cの実装

さて、こいつが山場です。MMCへの入出力をすべて実装しなければなりません。

色々悩みましたが、まずはdiskio_hardware.hというファイルを作り、PICの入出力ピンの設定などを盛り込むこととしました。

#ifndef DISKIO_HARDWARE_H
#define    DISKIO_HARDWARE_H

#include <xc.h> // include processor files - each processor file is guarded.  

#define _XTAL_FREQ 32e6

/* Definitions of physical drive number for each drive */
#define DEV_MMC        0    /* Example: Map MMC/SD card to physical drive 1 */

// #PIC   #MMC
//  RA1 -> INS
//  RA2 -> CS
//  RB0 -> DO
//  RC6 -> DI
//  RC7 -> SCLK

#define MMC_TRISA_MASK  0xFB
#define MMC_TRISB_MASK  0xFF
#define MMC_TRISC_MASK  0x3F

#define MMC_PPSOUT_SCK    RC7PPS
#define MMC_PPSOUT_SDO    RC6PPS
#define MMC_PPSIN_SDI    0x08    // RB0
#define MMC_PPSIN_SCK    0x17    // 0x09 for RC7

#define MMC_CS                (LATAbits.LATA2)
//#define MMC_CE                (LATDbits.LATD7)
#define MMC_INS_PORT        (PORTAbits.RA1)
#define MMC_INS_WPU            (WPUAbits.WPUA1)
#define MMC_INS_IOCP        (IOCAPbits.IOCAP1)
#define MMC_INS_IOCN        (IOCANbits.IOCAN1)
#define MMC_INS_IOCF        (IOCAFbits.IOCAF1)
#define MMC_IsInserted()    (!MMC_INS_PORT)

// If Chip enable is implemented, these macro should be implemented
#define MMC_ChipEnable(on)
#define MMC_IsChipEnable()  (true)

void MMC_Init(void);
void MMC_Interrupt(void);

void MMC_Eject(void);
bool MMC_IsEjected(void);

// need to be mplemented in main.c
void MMC_AccessLamp(bool on);

#endif    /* DISKIO_HARDWARE_H */

ここに登録している関数(MMC_Initなど)はFatFsを通り越して呼ぶことを前提とした関数としました。主な役目は以下の通りです。

  • MMC_Init:MMCモジュールの初期化を行う。主にTRISの設定、Interrupt On Changeの設定など。
  • MMC_Interrupt:割り込み処理ルーチン。MMCの抜き差しに反応して発生する割り込みを処理する。
  • MMC_Eject:MMCを安全に取り出す。これを操作した以後はファイルの読み書きはできなくなる。
  • MMC_IsEjected:MMCが安全に取り出されているかどうか。
  • MMC_AccessLamp:MMCのアクセスランプを制御するコールバック関数。main.cなどで実装が必要。

これらの関数と、diskio.hで指定された関数を実装したdiskio.cを作りました。長いので全体はGithubを参照してください。

いくらか抜粋です。まずはdisk_initialize関数です。

DSTATUS disk_initialize (
    BYTE pdrv                /* Physical drive nmuber to identify the drive */
)
{
    BYTE n, cmd, ty, ocr[4];
    UINT tmr;

    if(pdrv != DEV_MMC)
        return STA_NOINIT;    
    if(DiskStat & STA_NODISK)
        return DiskStat;
    
    MMC_ChipEnable(true);
    MMC_SPIInit();
    __delay_ms(5);

    for (n = 10; n; n--)
        MMC_SendSPI(0xFF);  /* 80 dummy clocks */

    ty = 0;
    if(MMC_send_cmd(CMD0, 0) == 1) {            /* Enter Idle state */
        if(MMC_send_cmd(CMD8, 0x1AA) == 1) {    /* SDv2? */
            for(n = 0; n < 4; n++)
                ocr[n] = MMC_SendSPI(0xFF);        /* Get trailing return value of R7 resp */
            if(ocr[2] == 0x01 && ocr[3] == 0xAA) {        /* The card can work at vdd range of 2.7-3.6V */
                for(tmr = 1000; tmr; tmr--) {            /* Wait for leaving idle state (ACMD41 with HCS bit) */
                    if(MMC_send_cmd(ACMD41, 1UL << 30) == 0)
                        break;
                    __delay_ms(1);
                }
                if(tmr && MMC_send_cmd(CMD58, 0) == 0) {        /* Check CCS bit in the OCR */
                    for(n = 0; n < 4; n++)
                        ocr[n] = MMC_SendSPI(0xFF);
                    ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;    /* SDv2 */
                }
            }
        } else {                            /* SDv1 or MMCv3 */
            if(MMC_send_cmd(ACMD41, 0) <= 1)     {
                ty = CT_SD1;
                cmd = ACMD41;    /* SDv1 */
            } else {
                ty = CT_MMC;
                cmd = CMD1;    /* MMCv3 */
            }
            for(tmr = 1000; tmr; tmr--) {            /* Wait for leaving idle state */
                if(MMC_send_cmd(cmd, 0) == 0)
                    break;
                __delay_ms(1);
            }
            if(!tmr || MMC_send_cmd(CMD16, 512) != 0)    /* Set R/W block length to 512 */
                ty = 0;
        }
    }
    CardType = ty;
    MMC_deselect();

    if (ty) {            /* Initialization succeded */
        DiskStat &= ~STA_NOINIT;        /* Clear STA_NOINIT */
    }

    return DiskStat;
}

コマンドをいくつか送ってカードの判別と初期化をしています。

つづいてディスクの読み込み機能です。 

DRESULT disk_read (
    BYTE pdrv,        /* Physical drive nmuber to identify the drive */
    BYTE *buff,        /* Data buffer to store read data */
    LBA_t sector,    /* Start sector in LBA */
    UINT count        /* Number of sectors to read */
)
{
    if((pdrv != DEV_MMC) || (count == 0))
        return RES_PARERR;
    if(DiskStat & STA_NOINIT)
        return RES_NOTRDY;
    
    if(!(CardType & CT_BLOCK))
        sector *= 512;    /* Convert to byte address if needed */

    if(count == 1) {    /* Single block read */
        if((MMC_send_cmd(CMD17, sector) == 0) && MMC_ReceiveDataBlock(buff, 512))
            count = 0;
    } else {                /* Multiple block read */
        if(MMC_send_cmd(CMD18, sector) == 0) {    /* READ_MULTIPLE_BLOCK */
            do {
                if(!MMC_ReceiveDataBlock(buff, 512))
                    break;
                buff += 512;
            } while (--count);
            MMC_send_cmd(CMD12, 0);                /* STOP_TRANSMISSION */
        }
    }
    MMC_deselect();

    return (count > 0) ? RES_ERROR : RES_OK;
}

シングルブロックリードとマルチブロックリードでコマンドを送り分けています。

つづいてディスクの書き込み機能です。

DRESULT disk_write (
    BYTE pdrv,            /* Physical drive nmuber to identify the drive */
    const BYTE *buff,    /* Data to be written */
    LBA_t sector,        /* Start sector in LBA */
    UINT count            /* Number of sectors to write */
)
{
    if((pdrv != DEV_MMC) || (count == 0))
        return RES_PARERR;
    if(DiskStat & STA_NOINIT)
        return RES_NOTRDY;
    if(DiskStat & STA_PROTECT)
        return RES_WRPRT;

    if(!(CardType & CT_BLOCK))
        sector *= 512;    /* Convert to byte address if needed */

    if(count == 1) {    /* Single block write */
        if((MMC_send_cmd(CMD24, sector) == 0) && MMC_SendDataBlock(buff, 0xFE))
            count = 0;
    } else {                /* Multiple block write */
        if(CardType & CT_SDC) MMC_send_cmd(ACMD23, count);
        if(MMC_send_cmd(CMD25, sector) == 0) {    /* WRITE_MULTIPLE_BLOCK */
            do {
                if(!MMC_SendDataBlock(buff, 0xFC)) break;
                buff += 512;
            } while (--count);
            if (!MMC_SendDataBlock(0, 0xFD))    /* STOP_TRAN token */
                count = 1;
        }
    }
    MMC_deselect();

    return count ? RES_ERROR : RES_OK;
}

こちらもシングルブロックライトとマルチブロックライトを別々に実装しています。ここでポイントは、データを送ったからと言って即MMCのフラッシュメモリにデータが書き込まれているわけではないことです。あくまでもデータをMMCに送っただけで、それとは非同期でフラッシュメモリへのデータの書き込みが実行されていきます。

では、書き込みが完了したことを確認するにはどうしたら良いかと言うと、次のdisk_ioctlのコマンドCTRL_SYNCを送れば良いです。

DRESULT disk_ioctl (
    BYTE pdrv,        // Physical drive nmuber (0..)
    BYTE cmd,        // Control code
    void *buff        // Buffer to send/receive control data
)
{
    DRESULT res;
    BYTE n, csd[16];
    DWORD csize;
#if CMD_FATFS_NOT_USED
    BYTE *ptr = buff;
#endif
#if _USE_ERASE
    DWORD *dp, st, ed;
#endif
    
    if(pdrv != DEV_MMC)
        return RES_PARERR;

    res = RES_ERROR;

    if(DiskStat & STA_NOINIT)
        return RES_NOTRDY;

    switch (cmd) {
    case CTRL_SYNC :        // Make sure that no pending write process. Do not remove this or written sector might not left updated. 
        if(MMC_select())
            return RES_OK;
        break;

    case GET_SECTOR_COUNT :    // Get number of sectors on the disk (DWORD) 
        if ((MMC_send_cmd(CMD9, 0) == 0) && MMC_ReceiveDataBlock(csd, 16)) {
            if ((csd[0] >> 6) == 1) {    // SDC ver 2.00 
                csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1;
                *(DWORD*)buff = csize << 10;
            } else {                    // SDC ver 1.XX or MMC
                n = (BYTE)((csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2);
                csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
                *(DWORD*)buff = csize << (n - 9);
            }
            res = RES_OK;
        }
        break;

    case GET_SECTOR_SIZE :    // Get sector size (WORD) 
        *(WORD*)buff = 512;
        res = RES_OK;
        break;

    case GET_BLOCK_SIZE :    // Get erase block size in unit of sector (DWORD) 
        if (CardType & CT_SD2) {    // SDv2? 
            if (MMC_send_cmd(ACMD13, 0) == 0) {    // Read SD status 
                MMC_SendSPI(0xFF);
                if (MMC_ReceiveDataBlock(csd, 16)) {                // Read partial block 
                    for (n = 64 - 16; n; n--) MMC_SendSPI(0xFF);    // Purge trailing data 
                    *(DWORD*)buff = 16UL << (csd[10] >> 4);
                    res = RES_OK;
                }
            }
        } else {                    // SDv1 or MMCv3 
            if ((MMC_send_cmd(CMD9, 0) == 0) && MMC_ReceiveDataBlock(csd, 16)) {    // Read CSD 
                if (CardType & CT_SD1) {    // SDv1 
                    *(DWORD*)buff = (((WORD)(csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
                } else {                    // MMCv3 
                    *(DWORD*)buff = ((DWORD)((csd[10] & 124) >> 2) + 1) * ((BYTE)(((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1));
                }
                res = RES_OK;
            }
        }
        break;
#if _USE_ERASE
    case CTRL_ERASE_SECTOR :    // Erase a block of sectors (used when _USE_ERASE == 1) 
        if (!(CardType & CT_SDC)) break;                // Check if the card is SDC 
        if (disk_ioctl(drv, MMC_GET_CSD, csd)) break;    // Get CSD 
        if (!(csd[0] >> 6) && !(csd[10] & 0x40)) break;    // Check if sector erase can be applied to the card 
        dp = buff; st = dp[0]; ed = dp[1];                // Load sector block 
        if (!(CardType & CT_BLOCK)) {
            st *= 512; ed *= 512;
        }
        if (send_cmd(CMD32, st) == 0 && send_cmd(CMD33, ed) == 0 && send_cmd(CMD38, 0) == 0 && wait_ready(30000))    // Erase sector block 
            res = RES_OK;    // FatFs does not check result of this command 
        break;
#endif
#if CMD_FATFS_NOT_USED
    // Following commands are never used by FatFs module 
    case MMC_GET_TYPE :        // Get card type flags (1 byte)
        *ptr = CardType;
        res = RES_OK;
        break;

    case MMC_GET_CSD :        // Receive CSD as a data block (16 bytes)
        if (MMC_send_cmd(CMD9, 0) == 0        // READ_CSD
            && MMC_ReceiveDataBlock(ptr, 16))
            res = RES_OK;
        break;

    case MMC_GET_CID :        // Receive CID as a data block (16 bytes)
        if (MMC_send_cmd(CMD10, 0) == 0        // READ_CID
            && MMC_ReceiveDataBlock(ptr, 16))
            res = RES_OK;
        break;

    case MMC_GET_OCR :        // Receive OCR as an R3 resp (4 bytes)
        if (MMC_send_cmd(CMD58, 0) == 0) {    // READ_OCR
            for (n = 4; n; n--) *ptr++ = MMC_SendSPI(0xFF);
            res = RES_OK;
        }
        break;

    case MMC_GET_SDSTAT :    // Receive SD statsu as a data block (64 bytes)
        if (MMC_send_cmd(ACMD13, 0) == 0) {    // SD_STATUS
            MMC_SendSPI(0xFF);
            if (MMC_ReceiveDataBlock(ptr, 64))
                res = RES_OK;
        }
        break;
    case CTRL_POWER:
        switch (ptr[0]) {
        case 0:        // Sub control code (POWER_OFF) 
            MMC_ChipEnable(false);
            res = RES_OK;
            break;
        case 1:        // Sub control code (POWER_GET) 
            ptr[1] = MMC_IsChipEnable();
            res = RES_OK;
            break;
        default :
            res = RES_PARERR;
        }
        return res;
#endif
    default:
        res = RES_PARERR;
    }

    MMC_deselect();

    return res;
}

FatFsが使わないメソッドも実装していますが、PICのプログラムメモリを節約できるようにプリプロセッサで除外できるようにしています。

mainルーチンの作成

ここまで作ったらほとんどできたも同然です。mainルーチンは比較的シンプルに実装できます。

#define _XTAL_FREQ 32e6
#include <xc.h>
#include <stdbool.h>
#include <string.h>
#include "FatFs/diskio_hardware.h"
#include "FatFs/ff.h"

#define    LED_TRIS    (TRISBbits.TRISB5)
#define    LED_LAT        (LATBbits.LATB5)
#define    LED(on)        (LED_LAT = (on))

static FATFS fs;
static FIL fp;

void MMC_AccessLamp(bool on)
{
    LED(on);
}

void setup()
{
    ANSELA = 0x00;
    ANSELB = 0x00;
    ANSELC = 0x00;

    TRISA = 0xFF;
    TRISB = 0xFF;
    TRISC = 0xFF;
    
    LED(false);
    LED_TRIS = 0;
    
    MMC_Init();
    
    INTCONbits.PEIE = 1;
    INTCONbits.GIE = 1;
}

void loop()
{
    f_mount(&fs, "0:", 0);
    if(f_open(&fp, "TEST.TXT", FA_OPEN_APPEND | FA_WRITE | FA_READ) == FR_OK) {
        f_puts("Hello, world!!\n", &fp);

        f_close(&fp);
    }

    f_unmount("0:");
    
    __delay_ms(1000);
}

void main(void)
{
    setup();
    while(1)
        loop();
}

void __interrupt() isr(void)
{
    MMC_Interrupt();
}

1秒に1回「Hello, world!!」というテキストをファイルに書き込んでいくだけのプログラムです。

前述した通り、MMC_Interrupt()関数でカードの抜き差しを検知していますので、カードを差していなかったらちゃんとf_open関数がエラーになって書き込みは行われません。また、MMC_AccessLamp関数でアクセス時にLEDを点灯させることもちゃんとできています。

まとめ 

無事、FatFsがPIC16Fシリーズでも動きました。私が初めて触ったマイコンもPIC16Fシリーズでしたが、同じシリーズのマイコンでここまでできるようになるとは、当時の私には想像もできなかったでしょうね。 

さて、ここまで作って使ったメモリはどれくらいでしょう。

データメモリは1,382バイト、プログラムメモリは13,372ワードでした。やはりかなりの量のメモリを使いますが、一方で半分いかないくらいなので、別のプログラムを盛り込んで何か素晴らしいアプリケーションを実装する余裕もあります。ここからまたどんどん夢が広がっていきますね。 

0 件のコメント:

コメントを投稿