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)で動作させているので仕方ないですね。今や数百円でカードリーダーが買える時代にわざわざこのためだけに電子工作をする必要はありませんね。

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

0 件のコメント:

コメントを投稿