ラベル USB の投稿を表示しています。 すべての投稿を表示
ラベル USB の投稿を表示しています。 すべての投稿を表示

2025年6月22日日曜日

PIC32MXでUSB MicroSDカードリーダーを作る (USB Device MSD class)

今回はPIC32MXでUSB接続のMicroSDカードリーダーを作ります。

もっぱら、PIC32MXではUSB Hostも対応しておりますので、USBメモリーの読み書きなどにも使えますが、今回はDevice側となります。 

環境

  • PIC32MX250F128B
  • MPLAB X IDE v6.20
  • XC32 v4.60
  • MPLAB Code Configurator v5.5.1

MPLAB Code Configurator (MCC)はMPLAB Harmonyを包含したコード生成ツールで、最近はHarmonyはMCC経由で入れることになるようです。

回路

まずは回路図です。 

シンプルにUSBとMicroSDカードのソケットを接続した回路になります。インジケーターとしてD1のLEDも用意しました。

PIC32MXシリーズでは内蔵オシレーターを持っていますが、USBを使うためには外付け8MHzのクロックが必要となります。

ファームウェア

続いて、MPLAB Xにてファームウェアを作成していきます。

空のプロジェクトの作成 

プロジェクトを作った後にMCCを使ってコード生成を行うので、空のプロジェクト(Application Project)を作成すればOKです。

ちなみにMCCはプロジェクトフォルダと同じ階層にsrcフォルダを作る仕様になっていて、これは設定等で変更できないようです。ですので、自分でプロジェクト用のフォルダを作って、その中にプロジェクトを作ると良いでしょう(あまり良い感じの解決法ではないですが)。

MPLAB Code Configurator (MCC)の設定

プロジェクトを作成すると自動的にMCCが起動します。起動しない場合はMPLABの上のほうの「MCC」アイコンをクリックします。アイコンが無い場合はプラグインが入っていない可能性があるのでググりましょう。

クロック設定

まずはクロック設定をします。Project GraphタブのPluginsからClock Configurationを選択します。

そうすると、Clock Diagramが出てくるので設定を行います。

まずはPrimary OscillatorをXTに設定し、周波数を8,000,000Hz (=8MHz)に設定します。ただしこのままでは右上のUSB Clockが8MHzのままなので、UPLLEN (USB PLL Enable)をONして48MHzにしてあげましょう。

これでクロックの設定は完了です。コンフィグレーションビットなどを直接触ることなく設定できるので超簡単ですね。

ピン設定

続いてピン設定を行います。PIC32MXシリーズもPPS (Peripheral Pin Select)という周辺回路を好きなピン(実はそんなに自由度は高くないですが)に割り当てる機能がありますが、MCCではその設定を行うこともできます。

クロック設定と同様にProject Graphタブから「Pin Configuration」を開きます。3つタブが出てきますが、Pin Tableで設定をしていきましょう。 

横にポート番号が並んでいて、縦に周辺回路の機能が並んでいます。青色が割り当て可能、緑が割り当て済み、灰色が割り当て不可です。

こんな感じに設定してみました。

USBはUSBID及びVBUSONは使用しないので未選択です。SPIはSPI2を使用することとします。SDカードのCSとLEDはGPIOになるのでその設定をします。クロックも忘れずにポートを設定する必要があります。

続いてPin Settingsタブを開いてGPIOの設定をします。

SDカードのCSピン(RB3)は出力ピンで、非選択のときはHighなのでそのように設定します。LEDのRB4も出力で、不点灯のときはLowなのでそのように設定します。 

Harmonyの設定

続いて使用するHarmony(ライブラリ)の設定をします。左側のDevice Resourcesから、以下のライブラリを取り込み(『+』ボタンを押し)ます (もしも出てこない場合は、ライブラリがインストールされていないので、MCC Content Managerから対応するライブラリを事前にインストールしておいてください)。

  • MSD Function Driver (Libraries - Harmony - USB - Device Stack)
  • SPI (Libraries - Harmony - Drivers - SDCARD)
  • SD Card (Libraries - Harmony - Drivers - SDCARD)
  • SPI2 (Libraries - Harmony - Peripherals - SPI)
  • CORE TIMER (Libraries - Harmony - Peripherals - CORE TIMER)

途中で依存関係にあたるライブラリを一緒に有効化するかという問い合わせが出てきます。基本OKで良いですが、FreeRTOSだけはNoにしておきます(容量がでかすぎて入らなくなるので)。

一通り入れ終わると、Project Graphタブにいろいろなモジュールが並びます。

赤色の部分は何かしら接続しないといけませんので接続していきます。黄色は必須じゃないらしいのでそのままで。

ライブラリの設定

ライブラリにも一部設定が必要なものがあります。

SD Card (SPI)_Instance 0を選択し、Chip Select PinをRB3に設定してやりましょう。  

USB Device Layerを選択するとUSBの設定が出てきます。デフォルトのままでも問題ないですが、何かしら変更したければここに入力すれば良いでしょう。 

コード生成 

最後に上の「Generate」を押すとコードが自動生成されます。

無事コードが生成されたら、最後にビルドしてビルドが通ることを確認しておきましょう。

アプリケーションコードの作成

さて、ビルドが通ったからと喜んで書き込んでみると、うんともすんとも言わないプログラムが出来上がります。デバイスマネージャーからもUSBが認識されない状態です。なぜかと言うと、アプリケーションコードが一切無いからです。USBデバイスを開いて~みたいな作業を全くしていないため何も起こらないわけです。 

なお、このセクションはこのGithubリポジトリのコードを参考にしています。

app.h

まずはapp.hを開いて以下の3つのファイルをincludeに追加します。

#include "definitions.h"
#include "usb/usb_chapter_9.h"
#include "usb/usb_device.h"

つづいて、APP_STATESとAPP_DATAを以下のように修正します。

typedef enum
{
    /* Application's state machine's initial state. */
    APP_STATE_INIT=0,

    /* Application's state machine running state */
    APP_STATE_RUNNING,
} APP_STATES;


typedef struct
{
    /* The application's current state */
    APP_STATES state;

    /* USB Device Handle */
    USB_DEVICE_HANDLE usbDeviceHandle;

    /* Device configured state */
    bool isConfigured;
    
} APP_DATA;

これでapp.hは完了です。

app.c

続いてapp.cの修正をしていきます。まずは下準備としてLEDの制御関数を作っておきます。

void LED_Off(void)
{
    GPIO_RB4_Clear();
}

void LED_On(void)
{
    GPIO_RB4_Set();
}

次にUSBのイベントが発生したときのイベントハンドラーを作っておきます。

void APP_USBDeviceEventHandler( USB_DEVICE_EVENT event, void * pEventData, uintptr_t context )
{
    /* This is an example of how the context parameter
       in the event handler can be used.*/

    APP_DATA * appDataObject = (APP_DATA*)context;

    switch( event )
    {
        case USB_DEVICE_EVENT_RESET:
        case USB_DEVICE_EVENT_DECONFIGURED:
            appData.isConfigured = false;
            /* Device was reset or de-configured. Update LED status */
            LED_Off();
            break;

        case USB_DEVICE_EVENT_CONFIGURED:
            appData.isConfigured = true;
            /* Device is configured. Update LED status */
            LED_On();
            break;

        case USB_DEVICE_EVENT_SUSPENDED:
            
            LED_Off();
            break;

        case USB_DEVICE_EVENT_POWER_DETECTED:

            /* VBUS is detected. Attach the device. */
            USB_DEVICE_Attach(appDataObject->usbDeviceHandle);
            break;

        case USB_DEVICE_EVENT_POWER_REMOVED:
            appData.isConfigured = false;
            /* VBUS is not detected. Detach the device */
            USB_DEVICE_Detach(appDataObject->usbDeviceHandle);
            LED_Off();
            break;

        /* These events are not used in this demo */
        case USB_DEVICE_EVENT_RESUMED:
            if(appData.isConfigured == true)
            {
                LED_On();
            }
            break;
        case USB_DEVICE_EVENT_ERROR:
        case USB_DEVICE_EVENT_SOF:
        default:
            break;
    }
}

続いて、APP_Initialize関数で追加したAPP_DATA構造体のメンバーの初期化を追加しておきましょう。

void APP_Initialize ( void )
{
    /* Place the App state machine in its initial state. */
    appData.state = APP_STATE_INIT;
    
    /* Set device layer handle as invalid */
    appData.usbDeviceHandle = USB_DEVICE_HANDLE_INVALID;
    
    appData.isConfigured = false;
}

最後に、APP_Tasks関数にてUSBデバイスの初期化を行っておきましょう。

void APP_Tasks ( void )
{
    /* Check the application's current state. */
    switch ( appData.state )
    {
        /* Application's initial state. */
        case APP_STATE_INIT:
        {
             appData.usbDeviceHandle = USB_DEVICE_Open(USB_DEVICE_INDEX_0, DRV_IO_INTENT_READWRITE);

            if(appData.usbDeviceHandle != USB_DEVICE_HANDLE_INVALID)
            {
                /* Set the Event Handler. We will start receiving events after
                 * the handler is set */
                USB_DEVICE_EventHandlerSet(appData.usbDeviceHandle, APP_USBDeviceEventHandler, (uintptr_t)&appData);

                /* Move the application to the next state */
                appData.state = APP_STATE_RUNNING;
            }
             
            break;
        }

        case APP_STATE_RUNNING:
            /* The MSD Device is maintained completely by the MSD function
             * driver and does not require application intervention. So there
             * is nothing related to MSD Device to do here. */
            break;

        /* The default state should never be executed. */
        default:
            break;
        
    }
}

USB_DEVICE_Open関数でデバイスを開いたうえで、USB_DEVICE_EventHandlerSet関数で先ほど実装したAPP_USBDeviceEventHandler関数を登録しているだけです。ソースコードに記載されている通り、初期化さえしてしまえばAPP_STATE_RUNNINGで特段ルーチン処理をする必要はありません。 

---

これにてひとまずパソコンに差すとSDカードのフォルダーが表示されるような状態になったはずです。目標であるMicroSDカードリーダーの完成です。

ちなみに、ファイルの読み書きをしてみるとわかりますがめちゃめちゃ読み書きが遅いです。まあ、まあ、USB Full Speed (12MHz) + SPIアクセス(5MHz)で動作させているので仕方ないですね。今や数百円でカードリーダーが買える時代にわざわざこのためだけに電子工作をする必要はありませんね。

では、このデバイスをベースに市販のカードリーダーには無いものを作るにはどうするか。ここがスタート地点です。 

2014年7月15日火曜日

PIC24FJ64GB002でUSBメモリーにアクセスする

最近、秋月電子でPIC24FJ64GB002の取り扱いが始まりました。
USB-OTGに対応したPICとしてはかなり有名どころで、多くの書籍やウェブページでも取り上げられており、資料は豊富にあります。にもかかわらず入手性は最悪で、国内の通販では共立電子くらいでしか取り扱っていなくて、さもなくばDigiKeyとかで海外から取り寄せるくらいしか方法はありませんでした。
しかし、ついに秋月電子で取り扱いが始まりました。秋葉原に行くだけで買える!しかも、共立電子の半額以下という破格の値段です。

というわけで、早速買ってきました。

 
手前がPIC24FJ64GB002で、奥がPIC32MX250F128Bです。
この2つ、前者は16bitマイコン、 後者は32bitマイコンでアーキテクチャも何もかも全く違うのかなと思ったら、なんとまあピンコンパチ(多分)でした。少なくとも電源やUSB周りは同じで、PIC32MXのほうをいじってみたときに使った回路をそのまま転用することができました。
というわけで、そのPIC32MXをいじったときに最終的にいきついたFatFsを動かすお話に用意したプログラムをPIC24FJ64GB002に移植してみました。


と言ってもほとんど苦労はしません。

そもそもこのデモプログラムはPIC24FJ64GB004でも動くように作られてて、多分これと002の違いはピン数とかパッケージとかその程度です。また、FatFsは完全にデバイス非依存で、型の大きさとかに気をつけておく程度でちゃんと動作はしてくれるはずです。というわけで、プログラムの修正はほとんど必要ありません。


まずは前回のプロジェクトのプロパティからDeviceをPIC24FJ64GB002に変更します。
…と言いたいところですが、PIC32MXのほうの設定を消すのはもったいないです。かと言ってバックアップとってゴニョゴニョやるのもめんどくさい。というわけで、プロジェクトのコンフィグレーションを追加してしまいましょう。MPLAB Xのプロジェクトはコンフィグレーションと呼ばれるものを複数作っておくことで、同じ構成のプログラムを別のデバイスに簡単に移植できるようになっているようです。もちろん、コードがそれに対応していたらの話ですが。

プロパティのカテゴリーの欄の下にある「Manage Configurations...」というボタンを押すとこんな画面が出てきます。


Duplicateで既存のコンフィグレーションをコピーできますが、まあコピーしてもコンパイラが違うとコンパイラの設定が全部吹っ飛んじゃうんであまり意味無いでしょう。ということで、Newを押して、適当な名前のコンフィグレーションを作ります。そして、Set Activeボタンを押せば、そのコンフィグレーションがアクティブになります。


そして、作ったPIC24FJ64GB002のほうのコンフィグレーションに対して、設定をしていきます。デバイスが未設定なので、右上のデバイスの欄からPIC24FJ64GB002を選択します。そして、コンパイラをXC16に設定します。そして前回同様、インクルードファイルのパスとヒープ領域の容量を設定してあげます。


次に、ソースコードの修正を若干します。修正箇所は、コンフィグレーションビットの設定とマイコンの初期化のプログラムのみです。

とは言っても、デモプログラムのほうにPIC24FJ64GB004のコードが入っているので、プリプロセッサの__PIC24FJ64GB004__を__PIC24FJ64GB002__に変更してやるだけです。というより、#if definedで追加してやる形にしました(具体的なコードはMicrochipの著作物なので掲載は遠慮しておきます。各自MLAのサンプルコードからコピーしてください)。

#if defined(__PIC24FJ64GB002__) || defined(__PIC24FJ64GB004__)
    //中略
#elif defined( __PIC32MX__ )
    //中略
#else
    #error Unsupposed Processor
#endif

コンパイルは、クリーンビルドをしてください。PIC32MXのほうの何かが残っているとリンクで謎のエラーが起きて焦ります。

そして、書き込みが終わればUSBメモリーを挿して、めでたくサンプルファイルの書き込みがされて完成です。

めでたしめでたし。



ここまで書いてなんですが、値段で見ると、PIC24FJ64GB002が340円、 PIC32MX250F128Bが360円です。20円差でより高性能なPICが買えるなら、別にPIC32MXのほうでいいんじゃないかなーって気はします。
まあでも最初に言った通り、PIC24のほうは資料の豊富さは格別です。気が向いたらBluetooth Stackとかに手を出してみようかなー。

2014年6月18日水曜日

USBメモリーをFatFsで操作する

さて、前回の時点でUSBメモリーをPIC32MX250F128Bからアクセスすることに成功しました。
FATファイルシステムもMicrochipのライブラリで制御しておりますが、やはりFATファイルシステムのライブラリと言ったらFatFsですね。(少なくとも私の中では)

FatFs 汎用FATファイルシステム・モジュール

もうね、このライブラリには何も不満は抱けないです。それくらい完成度の高いライブラリです。
ディスクの初期化や指定セクタの読み書きなどのほんのいくつかのデバイスに依存した関数をユーザーが用意するだけで、FAT16/32のアクセスができるようになります。
そのほか、非常に細かくオプションを指定できたりし(メモリが足りればLFNやUnicodeファイル名も使える)、その上、パフォーマンスもかなり良いです。言うこと無しです。

それでは導入していきましょう。

まずは上記のサイトよりFatFsのソースコードを入手し、ヘッダファイル、Cファイルをともにプロジェクトに登録します。ついでに、MicrochipのライブラリのほうのファイルFSIO.cを無効化しておきましょう。


次に、整数型の定義をします。integer.h内で、各ビット数に対応した型を定義してやるのですが、すでにPICのプロジェクトはGenericTypeDefs.hで同名の型が定義されており、二重定義エラーが起きてしまいます。しかし、BYTEだのWORDだの、この辺の定義はもはやビット数はだいたい決まっていて、FatFs内の定義とGenericTypeDefs.h内の定義が同じなので、integer.hでGenericTypeDefs.hをIncludeしてあげるだけにしました。WCHARの定義を残して、それ以外はコメントアウトします。

そしたら、一番重要なdiskio.cの定義をしてあげます。こんなかんじになります。

#include "diskio.h"
#include "ffconf.h"        /* FatFs lower layer API */

/* Definitions of physical drive number for each media */
#define USB        0


typedef struct
{
    BYTE    errorCode;
    union
    {
        BYTE    value;
        struct
        {
            BYTE    sectorSize  : 1;
            BYTE    maxLUN      : 1;
        }   bits;
    } validityFlags;

    WORD    sectorSize;
    BYTE    maxLUN;
} MEDIA_INFORMATION;

typedef enum
{
    MEDIA_NO_ERROR,                     // No errors
    MEDIA_DEVICE_NOT_PRESENT,           // The requested device is not present
    MEDIA_CANNOT_INITIALIZE             // Cannot initialize media
} MEDIA_ERRORS;

MEDIA_INFORMATION * USBHostMSDSCSIMediaInitialize( void );
BYTE USBHostMSDSCSIMediaDetect( void );
BYTE USBHostMSDSCSIWriteProtectState( void );
BYTE USBHostMSDSCSISectorRead( DWORD sectorAddress, BYTE *dataBuffer );
BYTE USBHostMSDSCSISectorWrite( DWORD sectorAddress, BYTE *dataBuffer, BYTE allowWriteToZero );


static BOOL isInitialized = FALSE;

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive
*/
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
    BYTE pdrv                /* Physical drive nmuber (0..) */
)
{
    DSTATUS stat;

    switch (pdrv) {
    case USB:
        if(USBHostMSDSCSIMediaDetect()) {    //メディアがつながっているか?
            if(!isInitialized) {
                MEDIA_INFORMATION *mediaInformation;
                
                mediaInformation = USBHostMSDSCSIMediaInitialize();

                if(mediaInformation->errorCode != MEDIA_NO_ERROR)
                    stat = STA_NOINIT;
                else {
                    stat = 0;
                    isInitialized = TRUE;
                }
            } else
                stat = 0;

            if(USBHostMSDSCSIWriteProtectState())
                stat |= STA_PROTECT;
        } else
            stat = STA_NODISK | STA_NOINIT;

        return stat;
    }
    return STA_NOINIT;
}


/*-----------------------------------------------------------------------*/
/* Get Disk Status
*/
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
    BYTE pdrv        /* Physical drive nmuber (0..) */
)
{
    DSTATUS stat;

    switch (pdrv) {
    case USB :
        if(USBHostMSDSCSIMediaDetect()) {
            if(isInitialized)
                return 0;
            else
                return STA_NOINIT;
        } else
            return STA_NODISK | STA_NOINIT;

        return stat;
    }
    return STA_NOINIT;
}


/*-----------------------------------------------------------------------*/
/* Read Sector(s)
*/
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
    BYTE pdrv,        /* Physical drive nmuber (0..) */
    BYTE *buff,        /* Data buffer to store read data */
    DWORD sector,    /* Sector address (LBA) */
    UINT count        /* Number of sectors to read (1..128) */
)
{
    UINT i;
    WORD SectorSize;

    if(disk_ioctl(pdrv, GET_SECTOR_SIZE, SectorSize) != RES_OK)
        return RES_ERROR;

    switch (pdrv) {
    case USB:
        for(i = 0; i < count; i++) {
            if(USBHostMSDSCSISectorRead(sector + i, buff + i * SectorSize) == FALSE)
                return RES_ERROR;
        }

        return RES_OK;
    }
    return RES_PARERR;
}



/*-----------------------------------------------------------------------*/
/* Write Sector(s)
*/
/*-----------------------------------------------------------------------*/

#if _USE_WRITE
DRESULT disk_write (
    BYTE pdrv,            /* Physical drive nmuber (0..) */
    const BYTE *buff,    /* Data to be written */
    DWORD sector,        /* Sector address (LBA) */
    UINT count            /* Number of sectors to write (1..128) */
)
{
    UINT i;
    WORD SectorSize;

    if(disk_ioctl(pdrv, GET_SECTOR_SIZE, SectorSize) != RES_OK)
        return RES_ERROR;

    switch (pdrv) {
    case USB :
        for(i = 0; i < count; i++) {
            if(USBHostMSDSCSISectorWrite(sector, (BYTE *)buff + i * SectorSize, TRUE) == FALSE)
                return RES_ERROR;
        }

        return RES_OK;
    }
    return RES_PARERR;
}
#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions
*/
/*-----------------------------------------------------------------------*/

#if _USE_IOCTL
DRESULT disk_ioctl (
    BYTE pdrv,        /* Physical drive nmuber (0..) */
    BYTE cmd,        /* Control code */
    void *buff        /* Buffer to send/receive control data */
)
{
    DRESULT res;
    int result;

    switch (pdrv) {
    case USB :
        switch(cmd) {
            case CTRL_SYNC:
                res = RES_OK;
                break;
            case GET_SECTOR_COUNT:    //フォーマットするときにしか使われない
                res = RES_ERROR;
                break;
            case GET_SECTOR_SIZE:
#if(_MAX_SS == _MIN_SS)
                *((WORD *)buff) = _MAX_SS;
                res = RES_OK;
#else
                res = RES_ERROR;
#endif
                break;
            case GET_BLOCK_SIZE:
                *((DWORD *)buff) = 1;
                res = RES_OK;
                break;
            case CTRL_ERASE_SECTOR:
                res = RES_OK;
                break;
            default:
                res = RES_PARERR;
                break;
        }
        return res;
    }
    return RES_PARERR;
}
#endif

typedef union _tagFATTIME {
    DWORD value;
    struct {
        unsigned SecDiv2 : 5;
        unsigned Min : 6;
        unsigned Hour : 5;
        unsigned Date : 5;
        unsigned Month : 4;
        unsigned YearFrom1980 : 7;
    };
} FATTIME;

DWORD get_fattime (void)
{
    FATTIME time;

    time.YearFrom1980 = 34;
    time.Month = 6;
    time.Date = 17;
    time.Hour = 23;
    time.Min = 16;
    time.SecDiv2 = 0;

    return time.value;
}

void disk_detatched(void)
{
    isInitialized = FALSE;
}

ディスクが初期化されたかどうかは結構厳密に聞いてくるようなので、isInitializedという変数を用意し初期化されたかを記憶しておきます。そして、disk_detached関数を作り、main.c側でUSBメモリーが取り外されたことがわかったらこの関数を呼び出し初期化されていないことを伝えます。

USBメモリーのアクセス関係の関数はusb_host_msd_scsi.cに入っているようです。
disk_initialize関数とdisk_status関数はUSBHostMSDSCSIMediaInitialize関数とUSBHostMSDSCSIMediaDetect関数をうまく使って処理しています。また、disk_writeとdisk_readはマルチセクタリード/ライトにも対応できるようなインターフェースをしていますが、USBHostMSDSCSISectorWrite関数とUSBHostMSDSCSISectorRead関数はマルチセクタリードに対応していないようなので、とりあえずfor文でそれをエミュレートしてあげています。デバイスドライバレベルでマルチセクタアクセスできたら相当速くなるんでしょうがね…。(少なくとも昔、SDカードのアクセスをやった時はそうでした。)

disk_ioctrl関数は一番めんどくさかったですね。
CTRL_SYNCはデバイスドライバレベルでキャッシュしてる場合にflushするための関数なようで、おそらくそういうことはしていないっぽいのでとりあえず何も処理をせずに成功を返すようにしました。CTRL_ERASE_SECTORは実装しなくても大丈夫なようなのでこちらも同様にそうしました。
GET_SECTOR_COUNTはセクタの個数だそうです。えっ、それってFATファイルシステム内読めばわかるでしょ?って思ったらどうもこれ、フォーマットするときに使うようですね。そりゃそうだ、フォーマットされていないディスクだったらディスクの容量はファイルシステムからじゃ読み出せませんからね。
…どうしろって言うんだ…。 usb_host_msd_scsi.cにはそれっぽいAPI無いですしね…。というわけで、フォーマットしなけりゃ問題ないっしょということで実装するのをやめました。はい。
GET_SECTOR_SIZEですが、普通は512バイトみたいですね。 最大セクタサイズと最小セクタサイズが同じだったらそれでいいっしょということでそれを返すようにしました。違ったらサポートしないってことで。結構適当な実装です。はい。ごめんなさい。

あとはget_fattime関数を定義してやる必要があります。これはファイルのタイムスタンプを書き込むための関数で、要するにRTCなどから読みだした現在時刻をここで返すようにしてやればいいわけです。
が、NTPだのGPSだの何だので時計合わせするようなハードはあいにく持ち合わせていないので、適当にコーディングしていた時の時刻を返すようにしました。FATファイルシステムはタイムスタンプを32bitに収めるために、 秒なんか2秒毎にしか記録できないんですね。シフト演算子を組合せて時刻を定義しても良いでしょうが、ここはFATTIMEという共用体を作ってあげることにしました。こうやって各時刻要素の構造体とDWORDを共用体にすることで、構造体に入れた値をそのままDWORDとして読みだすことができます。すごく素朴でコストの小さい書き方でとても良いと思います。

これで最低限は動きそうな感じになったと思うんで、最後にmain関数側からこれを呼び出します。

    while(1)
    {
        LATAbits.LATA0 = 0;
        //USB stack process function
        USBTasks();

        //if thumbdrive is plugged in
        if(USBHostMSDSCSIMediaDetect())
        {
            FRESULT result;
            FATFS fatfs;
            FIL file;

            deviceAttached = TRUE;
            LATAbits.LATA0 = 1;

            f_mount(&fatfs, "0:", 0);
            result = f_mkdir("0:testdir");
            if((result == FR_OK) || (result == FR_EXIST)) {
                result = f_open(&file, "0:testdir/file.txt", FA_CREATE_ALWAYS | FA_WRITE);
                if(result == FR_OK) {
                    char szText[32] = "Hello, FatFs!";
                    UINT bw;

                    f_write(&file, szText, strlen(szText) * sizeof(char), &bw);

                    f_close(&file);
                    f_mount(NULL, "0:", 0);

                    while(deviceAttached == TRUE)
                        USBTasks();
                }
            }
        }
    }

ディレクトリの生成の試験も兼ねて、ドライブの中にtestdirというディレクトリを生成し、その中にファイルを生成しています。


そして無事、ファイルを書き込むことができました。
Microchipのライブラリと違い、タイムスタンプもちゃんと入っています。

さらに、USBドライバとFatFsを入れた時点でのプログラムメモリの使用率はなんと54%なんですね。
プログラムメモリを78%も占有することになったMicrochip製のライブラリよりも相当コンパクトだと言えるでしょう。さっすがFatFsです。

2014年6月17日火曜日

PIC32MX250F128BでUSBメモリーに書き込む

前回につづいてPIC32MX250のお話です。

前回はプログラムのビルドが終わりました。今度は実際に回路を作ってみましょう。
必要最低限の回路です。こんな感じです。



セラロックは8MHzを付けています。PIC32MXのオシレーターのブロック図は結構複雑になっていて、USBは8MHzを1/2プリスケーラを通して4MHzにした後、24倍PLLを通して96MHzにし、さらに1/2ポストスケーラを通して48MHzにしています。CPUのメインクロックは1/2プリスケーラで4MHzにした後PLLで15倍の60MHzにして供給しています。結構高速で動いてくれるんですね。
PICは3.3Vで動きますが、USBは5Vを供給してやる必要があります。そのため、5VのACアダプタを使って、USBには5V、PICにはレギュレーターを介して3.3Vを供給してやっています。Vbusは5Vトレラントですが(そうじゃなかったら困る)、PIC32MXのピンは5Vトレラントのピンがだいぶ限られているので、もっと実用的なアプリケーションを開発するときには注意する必要がありそうですね。

LEDは必要ないって言えば必要ないですが、LEDデバッグをするために用意しています。


実物はこんなかんじになっています。Cサイズのユニバーサル基板に適当に組み上げました。

プログラムでは、USBメモリーを挿すとLEDが点灯し、抜くと消灯するようにしてみました。USBメモリーを挿してLEDが点灯してから引っこ抜き、パソコンで中身を確認します。


見事にファイルが書き込まれていました。めでたしめでたし。

一番苦労したのはPICの配線ですかね(汗
USB周りのピン(Vbus、Vusb3v3など)をどうつなぐのかを回路図とかでパッと説明してるサイトがパッと出てこずに少し苦労しました。でも、もう回路図は上に載せておいたのですんなりと動かせるかと思います。

このデモプログラムにはタイムスタンプを書き込む機能が付いていないようで、上のように更新日時が空になっています。メモ帳では読めるみたいですが、TeraPadは受け付けてくれませんでしたね。この辺、改良の余地はありそうです。

USBメモリーがマイコンで使えるとなると、ロガーなどの用途には一気に使いやすくなりますよね。
その他、ENC28J60と組合せてHTTPサーバーとか作れるかも。
夢は広がりんぐです。

2014年6月16日月曜日

PIC32MX250F128BでUSBホストをやる

さて、NTP時計の次の連載になる気がしているPIC32MX250の記事をちょっと書こうと思います。



最近、秋葉原の某電子部品店で取り扱いが始まったPIC32MX250ですが、USB-OTGが付いております。そもそもUSBとはパソコン(ホスト)とデバイスが通信するためのものですが、デバイス同士の通信ニーズも出てきておりますので(スマホとUSBメモリー、カメラとプリンターの接続などなど)USB-OTG(On The Go)っていうのが定義されたらしいです。PICはパソコンじゃないのでそもそもはデバイス側になるのですが、OTGでそれがホストとして動作させられるってことですね。(細かい話はわかってないので違ってたらごめんなさい)

PICでUSBホストをやるとなると、やはりメジャーなのはPIC24FJ64GB002/004とかなのでしょうが、とにかくこれの入手性が悪いんですよね。秋葉原を探しまわっても見つけたことがありません。多分、DigiKeyとかじゃないと手に入らないんでしょうね…。


前置きはこれくらいにしておいて、早速プログラムのビルドに入ってみましょう。
ビルドできる見込みが立ったらハードを作るという流れで。

例によってMicrochip Libraries for Applicationsを使います。

Microchip Libraries for Applications

めんどくさいので、ENC28J60を動かすときに使った2013/06/15のバージョンでビルドをしていきます。使うのはこのデモです。

microchip_solutions_v2013-06-15\USB\Host - Mass Storage - Simple Demo

しかしこのデモ、どうもデフォルトのターゲットはPIC24FJ256GB106のようですね。しかし、PIC32MXシリーズで動かすことも想定された設計がされており、おそらくPIC32MXでもある程度容易にビルドできるだろうということがわかります。



このように左側のCategoriesペインに多くのビルドプロファイルがありますね。PIC32MX250こそ無いものの、PIC32MXシリーズがあるのでうまくいく可能性は結構あります。

プロジェクトに以下のようなファイルを登録しました。


ファイルがどこにあるかは、もとのデモプロジェクトを見ればわかります。
HardwareProfile.hとかその辺はいじらなければいけないオーラがものすごく出ていますが、まあとりあえずビルドを通すことを目標にして今回はパスしておきます。

例によってIncludeディレクトリのパスを通しておく必要があります。microchip_solutions_v2013-06-15\Microchip\Includeと、プロジェクトのディレクトリを通しておけば大丈夫です。


この他に重要なのは、ヒープ領域の容量を設定する必要があることです。
ヒープ領域とは、いわゆる関数の呼び出しやブロックの変数確保で使われるスタック領域とは違い、malloc等でメモリを動的に確保するときに使われるメモリ領域です。デモプログラムのどこかでmallocされてるってことなんでしょうね。(ファイルシステムのところの気がしています)

ヒープ領域はリンク時に用意されるものなので、xc32-ldの設定になります。デモプログラムでは2000bytes用意されていたので、とりあえず2000bytesにしておきましょう。


これでビルドが通りました。めでたしめでたし。
でもこれだけでもうプログラムメモリの78%を使ってしまうんですね…。