YS電子工作ラボ

DMA式 WAVプレーヤー

(→プロジェクトファイル(Harmony Ver.2.04版 ) ダウンロード


 ここで紹介するWAVプレーヤは、SDカードの中にあるWAVファイルのサウンドデータをPIC32MZのRAMにSPIで読込み、 このデータを 44.1KHz毎に別のSPIバッファに書き込んで、外付けのDAコンバータ(MCP4922)に転送して演奏しています。 DMA転送は RAMに読み込まれたデータをSPIのバッファーに書き込む部分で行われています。
   

<仕様>  
SDカードにあるWAVファイルを再生するプレーヤーをPIC32MZを用いて製作すること
再生対象のWAVファイルは、ステレオ16ビット、44.1KHzのみとする。
SPIモジュールのSPI4を用いて12ビットDAコンバータMCP4922にLチャンネルのデータのみのデータを転送、再生すること
WAVファイルの読み出し、DAコンバータへの書き出しは以下のような”ダブルバッファ方式”とすること
➀SDカードの読み出しは SPI6とし、サウンドデータ読み出しのバッファは2個(仮称 バッファーA, バッファーB)とし、交互にデータを読みこむこと。
➁バッファA、バッファBから44.1KHz毎にSPI4のバッファに書き出す
③バッファAへの読み込みはバッファAからSPI4への書き込みが完了した後に行う。 同様にバッファBへの読み込みはバッファBからSPI4への書き込みが完了した後に行う。
バッファA、及びバッファーBからSPI4への書き込みはDMA転送を用いること
WAVファイルを再生/停止のボタンスイッチをもうけること 
演奏曲を演奏中でもスキップできるボタンスイッチをもうけること。SDカード内のすべてのWAVファイルを再生したら最初のファイルに戻ること
キャラクタ液晶に演奏中のWAVファイルのファイル名を表示すること
  SDカード脱着、ファイルオープン失敗、ダブルバッファ動作状態に係るLED表示を行うこと
(コンデンサを追加しないとスピーカアンプの入力に直流が印加されてしまいます。必ず挿入してから通電してください)
DAコンバータMCP4922の出力とアンプの間に 100μFのコンデンサを挿入すること
 (参考)以下の写真はRCAコネクタの信号部分に SMCの3216(L3.2 x W1.6 x H1.6mm) 6v 100μFの積層セラミックコンデンサをハンダ付けしたものです。
       
     
     
<回路図> ( → PDF)
    
       
       
<外観>PIC32MZ評価ボード(→購入方法)を使った実験品の外観です。
     汎用モジュール評価ボード(段積みボード)には本テーマと関係ない部品が多々実装されています。
 
     
     
  <動作結果> (→ サウンド付き動画
           
   
    
 
  押しボタンスイッチ  機能動作 備考 
   スタート/ストップ  WAVファイルの演奏を開始する/停止する  
   スキップ  現在演奏中の曲を停止して、次の曲の演奏を開始する。  
   リセット  プログラム書き込み後 or 電源投入後リセットボタンをクリックしてプログラムを動作させる RB1の変化割り込みを追加すると 電源ON後リセットしないとPIC32MZが起動しない現象が発生。 バグ?
   
   演奏中のファイル名の表示結果("エデンの東"を演奏中の場合):
 上段が演奏中のファイル名です
   
   
   
   
<解説>以下に、記載してある内容は要点だけです。 詳細はプロジェクトファイルを精読願います
  (以下は、Harmony v2.04 をもとに作成しています。最新のバージョンとは異なることがあるかもしれませんので注意してください。)


■ MHC設定
  Options 

項目  ①Config設定
Device & Project Configuration
  > PIC32MZ2048 Device Configuration  
➁DMA設定
Harmony Framework Configuration >
 System Services > DMA
③File System設定
Harmony Framework Configuration >
 System Services > File System
MHC
備考 デフォルトからの変更要領:
 FPLLIDIV: DIV3
 FPLLICLK: PLL_POSC
 POSCMOD: EC

システムクロック周波数: 200MHz
外部
 主発振器: 24MHz

#pragma config FNOSC = SPLL
#pragma config POSCMOD = EC
#pragma config FPLLIDIV = DIV_3
#pragma config FPLLICLK = PLL_POSC
#pragma config FPLLMULT = MUL_50
#pragma config FPLLODIV = DIV_2 
デフォルトからの変更要領:
 □Use DMA System Service?チェックをいれる
 Number of DMA channel instances: 3
 DMA CHANNEL0/Interrupt priority
            :INTPRIORITY_LEVEL2
 DMA CHANNEL1/Interrupt priority
            :INTPRIORITY_LEVEL2
 DMA CHANNEL2/Interrupt priority
            :INTPRIORITY_LEVEL2
デフォルトからの変更要領:
 □Use File System Service? チェックを入れる
 Maximum Simultanius Access Files: 2
 Use File System Auto Mount Feature?
                 チェックを入れる
 
 
項目 ④SD Card設定
Harmony Framework Configuration >
 Driver > SDcard
⑤SPI設定1
Harmony Framework Configuration >
 Driver > SPI
⑥SPI設定2
Harmony Framework Configuration >
 Driver > SPI
MHC
備考 デフォルトからの変更要領:
 □Use SD Card driver ? チェックをいれる
 □Enabel Write Protect Check チェックをはずす
 Chip Select Port: PORT_CHANNEL_B
 Chip Select Port Bit: PORT_BIT_POS_13
デフォルトからの変更要領:
 □Use SPI Driver ? チェックを入れる
 □Use 16bit Mode? チェックをいれる
 Number of SPI Driver Instances: 2
 SPI Module ID: SPI_ID6
 Tx Interupt Priority: INT_PRIORITY_LEVEL3
 Rx Interupt Priority: INT_PRIORITY_LEVEL3
 Clock\Bau Rate-Hz: 20000000
 ClockMode: DRV_SPI_
  CLOCK_MODE_IDEL_LOW_EDGE_FALL

デフォルトからの変更要領:
 SPI Module ID: SPI_ID4
 Data Width: □16-bit にチェックをいれる
 Clock\Bau Rate-Hz: 5000000
 ClockMode: DRV_SPI_
  CLOCK_MODE_IDEL_LOW_EDGE_FALL
項目 ⑦Timer設定
Harmony Framework Configuration >
 Driver > Timer 
   
MHC    
備考 デフォルトからの変更要領:
 Number of Timer Driver Instance
 Timer Driver Instance1
 Timer Module ID:TIMER_ID_2
 INTERRUPT PRIORITY:
     INT_PRIORITY_LEVEL4
 Prescale: TIMER_PRESCALE_VALUE_1
 Timer Driver Instance2
 Timer Module ID:TIMER_ID_4
 INTERRUPT PRIORITY:
     INT_PRIORITY_LEVEL4
 Prescale: TIMER_PRESCALE_VALUE_8

   


  Pin Settings

項目 ⑧ポート設定1 ⑨ポート設定2
MHC
備考 デフォルトからの変更要領:
 RG15/Function: GPIO_OUT
 RB1/Function: GPIO_IN
 RB1/Change Notification: チェック追加
 RB1/Pull Up: チェック追加
 RB0/Function: GPIO_IN
 RB0/Change Notification: チェック追加
 RB0/Pull Up: チェック追加
 RF13/Function: SDI6

デフォルトからの変更要領:
 RB13/Function: GPIO_OUT (SD_CS)
 RB15/Function: SDO6
 RD15/Function: SCK6
 RF2/Function: GPIO_OUT (DA_CS)
 RA14/Function: GPIO_OUT
 RA15/Function: GPIO_OUT
 RD10/Function: SCK4
 RD11/Function: SDO4
 RD4/Function: GPIO_OUT



■ キャラクタ液晶表示のライブラリ 1lcd_lib_XC32.h と 1lcd_lib_XC32.cを main.cと同じフォルダにコピーして、プロジェクトに追加します。
     (詳細→ キャラクタ液晶表示方法 参照



■app.h を以下の要領で変更します。

 ①列挙型変数 APP_STATES の定義を以下のように変更します。
  typedef enum
  {
    /* Application's state machine's initial state. */
    APP_STATE_INIT=0,
    APP_STATE_WAIT_FOR_BUS_ENABLE_COMPLETE,
    APP_STATE_WAIT_FOR_DEVICE_ATTACH,
    APP_STATE_SEARCH_FILE,
    APP_STATE_IDLE,
    APP_STATE_START_STOP,
    APP_STATE_SKIP,

  } APP_STATES;




 ②構造体 APP_DATAのメンバーを以下のように変更します。
  typedef struct
  {
    /* The application's current state */
    APP_STATES state;

    /* SYS_FS File handle for 1st file */
    SYS_FS_HANDLE fileHandle;

    // /* Application's current state */
    // APP_STATES state;

    /* Application data buffer */
    uint8_t data[1024];

    /* Number of bytes written */
    uint32_t nBytesWritten;

    /* Number of bytes read */
    uint32_t nBytesRead;

    bool deviceIsConnected;

    /* TODO: Define any additional data used by the application. */

  } APP_DATA;



以下、app.h











■ app.cに、青字部分のように 追加、修正します。 


➀インクルードファイルを追加します。stdio.hがないと、sprintf( )がコンパイラのバージョンによってコンパイルで警告となることがあります。
  #include <stdio.h>
 

➁ 3個のDMAチャンネル(DMA0,DMA1,DMA2)ハンドル、ファイルハンドル、タイマハンドル、ダブルバッファメモリなど
  所要の変数、ハンドルなどの宣言、定義を行います。
  char Buf[32]; //液晶表示バッファー
  int Num; //
  int Numx; //演奏曲No
  int Num_max; //SD収納曲数

  APP_DATA appData;
  SYS_FS_HANDLE dirHandle; //ディレクトリハンドル
  SYS_FS_FSTAT stat;
  char longFileName[300];
  SYS_FS_RESULT Result;
  SYS_FS_HANDLE fileHandle1; //ファイル ハンドル
  char File_Name[30][300]; //ファイル名
  DRV_HANDLE tmrHandle; //タイマ2ハンドル //44.1KHz
  bool PlayFlag; //1:演奏中、 0:非演奏中
  int Num_buf; //読込みバイト数
  bool Flag_BufAB; //SDからの読み込むバッファモード  0:バッファーB 1:バッファーA
  bool SD_Read; //SD読み出しフラグ 1:SDからデータを読み出す 0:SDからデータを読みださない
  unsigned int ptr_A, ptr_B; //バッファーポインタ
  short int Dummy;
  __attribute__((coherent)) unsigned char Buf_A[4096]; // バッファA
  __attribute__((coherent)) unsigned char Buf_B[4096]; // バッファB

  __attribute__((coherent)) unsigned short SpiOut_A; // 16ビットサウンドSpiデータA
  __attribute__((coherent)) unsigned short SpiOut_B; // 16ビットサウンドSpiデータB

  //待ち時間設定
  uint32_t Wait = 63; //周期レジスタ初期値 //最初の割り込みまでの時間をセット
  //5 nsec x2 x63 x 8 = 5040nsec = 5.04μsec

  int delay_Clock = 200000000; //200MHz

  const unsigned char CS0[] = {0x00};
  const unsigned char CS1[] = {0x04};

  SYS_DMA_CHANNEL_HANDLE channelHandle0;
  SYS_DMA_CHANNEL_HANDLE channelHandle1;
  SYS_DMA_CHANNEL_HANDLE channelHandle2;
  DRV_HANDLE Wait_Handle;



③ 関数のプロトタイプ宣言を行います
  void delay_us(volatile unsigned int usec);
  void delay_ms(volatile unsigned int msec);
  void SwCheck(void);
  ……
  ……



④NOPを用いた 1μsec、1msecの遅延関数 delay_usec( )、delay_ms( )をつくります。
  void delay_us(volatile unsigned int usec) //1μsec遅延
  {
    volatile int count;
    count = (int)(delay_Clock/20000000)*usec;

    ……
    ……

⑤関数SwCheck()は、Bポートの変化割り込みハンドラーで呼び出されます。 この関数でどのボタンスイッチが押されたのか判別します。
  void SwCheck(void) //SW クリック検出
  {
    if(appData.deviceIsConnected == true)
    {
      if(PORTBbits.RB0 == 0) //music スタート/ストップ
      {
        delay_ms(50);
        if(PORTBbits.RB0 == 0)appData.state = APP_STATE_START_STOP;
      }
      if(PORTBbits.RB1 == 0) //music スキップ
      {
        delay_ms(50);
        if(PORTBbits.RB1 == 0)
        {
          PlayFlag = 0;
          appData.state = APP_STATE_SKIP;
        }
      }
    }
  }


⑥関数tmrISR2( )はtmrISR( )の中で呼び出される非繰り返しタイマのコールバック関数です。タイマ4がアクティブになってから
変数Waitによって設定されている5.04μsec後に実行されます。 この時間はSPI4のバッファへの書き込み後MPC1922への
転送が完了するまでの時間を確保するものです。 タイマ4が起動するとDMA2チャンネルが起動して RF2(MPC4922のCS)が
0 → 1 となり MP4922の内部でDA変換が開始されます。 
 (注)  "RF: 1 → 0、MP4922へのシリアル転送完了待ち(5.04μsec)、RF: 0  → 1 " のMPC4922への書き込み動作は、
    DMA0、 DMA1、 DMA2を連続的動作により実行しています。
  
  void tmrISR2(uintptr_t context, uint32_t alarmCount ) //タイマ4コールバック関数
  {
    //ここにコードは記載されていませんが、このタイミングでDMA2チャンネルが起動しています。

  } 




⑦関数tmrISR( )は オーディオ再生中、タイマ割り込みにより44.1KHz毎に 呼び出されてます。
 この関数がよびだされるとまずタイマ4がアクティブになります。
 ダブルバッファ方式ですので SDカードから読み込まれたバッファA,バッファBのデータが交互にSPI4BUFレジスタに書き込まれます。
 また、読み出されているバッファが空になると フラグをたててSDカードからの読み込みを催促します。
 バッファA、バッファBにあるデータはRチャンネルのデータも含まれているので読み出し後 捨てています。
 バッファA、バッファBにあるLチャンネルデータをそのままMCP4922に書き込むのではなく MCP4922は12ビットDAコンバータですので
 16で割る演算をしています。 更にMCP4922には2つのDA出力ポートがあります。また出力ゲインを1倍or2倍の選択もあります。これらの
 設定などをb12-b15ビットに演算してSPI4BUFに書き込んでいます。
  void tmrISR(uintptr_t context, uint32_t alarmCount ) //タイマ2コールバック関数 //44.1KHz(22.7μsec)インターバル
  {
    DRV_TMR_AlarmRegister ( Wait_Handle, Wait, false, 0, tmrISR2 ); //非繰り返し呼び出し
    DRV_TMR_Start(Wait_Handle); //タイマ4割り込み許可、タイマ4イネーブル

    if(Flag_BufAB == 0){ // バッファAの場合
      // LATFbits.LATF2 = 0; //MCP4922 CS = 0
      SpiOut_A = ((((Buf_A[ptr_A+1] * 256 + Buf_A[ptr_A])/ 16) + 2048) | 0b0011000000000000); //Lチャンネル Soundデータ送信
      //MP4922 制御コマンド
      //VoutAにゲイン2で出力
      //MCP4922への書き込みコマンド
      //bit15(書き込みレジスタ選択)   1:VoutB   0:VoutA
      //bit14(予備)
      //bit13(出力ゲイン選択)  1:1倍   0:2倍 
      //bit12(出力オフ制御)  1:出力ON  0:出力OFF 
      //bit11 - bit0 データ  MSBから順次送信される
      // SPI4BUF = SpiOut_A;
      //

      // //Vout = 2.048×2×Dn/4095  Vout(max) = 4.096[V] ( > 3.3V(電源電圧))   4.096×3.3/5 = 2.7V (< (3.3V時オペアンプ飽和電圧))
      // //Vout = 2.7×Dn/4095 //32767
      ptr_A += 2; //Sound data 2バイト → ポインタ +2
      // while(SPI4STATbits.SPITBF); //: SPI Transmit Buffer Full Status bit //SPI4SRレジスタへの書込み完了
      // delay_us(5); //MCP4922の処理待ち(必須)
      // LATFbits.LATF2 = 1; //MCP4922 CS = 1;

      Dummy = Buf_A[ptr_A+1] * 256 + Buf_A[ptr_A]; //Rチャンネルデータ出力
      ptr_A += 2; //Sound data 2バイト → ポインタ +2

      if(ptr_A >= Num_buf){ // バッファAデータ読み出し完了の場合
        Flag_BufAB = 1; // 読み出し先をバッファーBに変更
        SD_Read = 1; // SD読み出しフラグセット
        tr_A = 0; // ポインタAリセット
      
    }
    else
    { // バッファBの場合
      // LATFbits.LATF2 = 0; //MCP4922 CS = 0
      SpiOut_A = (((Buf_B[ptr_B+1] * 256 + Buf_B[ptr_B]) /16) + 2048 | 0b0011000000000000);
      // SPI4BUF = (((Buf_B[ptr_B+1] * 2
56 + Buf_B[ptr_B]) /16) + 2048 | 0b0011000000000000);
      ptr_B += 2; // ポインタB更新
      // while(SPI4STATbits.SPITBF); //: SPI Transmit Buffer Full Status bit //SPI4SRレジスタへの書込み完了
      //
      Dummy = Buf_B[ptr_B+1] * 256 + Buf_B[ptr_B]; // 次のRchデータ出力
      ptr_B += 2; // ポインタB更新
      //
      // delay_us(5); //MCP4922の処理待ち
      //
      // LATFbits.LATF2 = 1; //MCP4922 CS = 1;

      if(ptr_B >= Num_buf){ // バッファ終了の場合
        Flag_BufAB = 0; // バッファ切り替え
        SD_Read = 1; // SD読み出しフラグセット
        ptr_B = 0; // ポインタBリセット
      }
    }
  
  }



⑧関数PlayStart( ) は プレースタートの場合に呼び出されます。 WAVファイルは先頭から44バイト目からサウンドデータ
 となります。従ってサウンドデータを呼び出す場合の先頭アドレスは0x2C(= 44)となります。
  void PlayStart(void) //演奏開始
  {
    fileHandle1 = SYS_FS_FileOpen(&File_Name[Numx][300], (SYS_FS_FILE_OPEN_READ));
    if(fileHandle1 == SYS_FS_HANDLE_INVALID)LATDbits.LATD4 = 1; //ファイルオープン失敗の場合 RD4 LED点灯

    Num_buf = SYS_FS_FileRead(fileHandle1, Buf_A, 4096);
    Flag_BufAB = 0; //読込みバッファ:B
    ptr_A = 0x2C; // WAVファイルの先頭指定 //オフセット44(0x2C)
    ptr_B = 0; // バッファポインタリセット
    SD_Read = 1; // SDカード読み込み許可
    DRV_TMR_Start(tmrHandle); //タイマ2割り込み許可、タイマ1イネーブル //44.1KHz 割り込み開始
  }



⑨関数PlayStop( ) は プレーストップの場合に呼び出されます。
  void PlayStop(void) //演奏停止
  {
    int i;
    SYS_FS_FileClose(fileHandle1); // ファイルクローズ
    DRV_TMR_Stop(tmrHandle); //タイマ2割り込み禁止 //44.1KHz 割り込み禁止 // Lch割り込み禁止
    // バッファクリア
    for(i=0; i<4096; i++){
      Buf_A[i] = 0;
      Buf_B[i] = 0;
    }
  }



⑩関数SearchFile( ) はSDカードのルートディレクトリ内にあるWAVファイルを探索する関数です
  void SearchFile(void) //SDカード(ルート)の *.wavファイル探索
  {
    dirHandle = SYS_FS_DirOpen("/mnt/myDrive1");

    stat.lfname = longFileName;
    stat.lfsize = 300;
    Num = 0;
    do
    {
      Result = SYS_FS_DirSearch(dirHandle, "*.wav", SYS_FS_ATTR_ARC, &stat);
      if(Result == SYS_FS_RES_SUCCESS) //SYS_FS_RES_SUCCESS = 0
      {
        Num++;
        sprintf(&File_Name[Num][300],"%s",stat.lfname); //
      }

    }while(Result == SYS_FS_RES_SUCCESS);

    Num_max = Num;

    SYS_FS_DirClose(dirHandle);
  }



⑪DMAチャンネル DMA0 DMA1 DMA2の諸元を設定して有効化している部分です
 DMAが使われている部分は PIC32MZのSPI4からMCP4922への転送動作部分です。この部分は詳しく述べると以下となります。
  1. RF2ポート出力: 1 → 0
  2. SPI4BUFへの書き込み
  3. シリアル転送完了までの時間待機(5μsec)
  4. RF2ポート出力: 0 → 1

  DMA0が 1. を実行しています。
  DMA1が 2. を実行しています。
  タイマ4が 3. を実行しています。
  DMA2が 4. を実行しています。. 
   尚、プレー中、DMA0は44.1KHz毎に タイマ2から起動させらるようにしています。

  関数SYS_DMA_ChannelTransferAdd( )で、どの値を どのアドレスにDMA転送するかと云ったことを設定しています。
  関数SYS_DMA_ChannelSetup( )で、どのようなタイミングでDMAチャンネルを動作させるのか設定しています。

//DMAチャンネル諸元設定(Harmony ライブラリ関数)
//----------------------------------------------------------------------------------------------------
  channelHandle0 = SYS_DMA_ChannelAllocate(DMA_CHANNEL_0); //RFポート用 DMAチャンネルオープン & ハンドル取得
  SYS_DMA_ChannelTransferAdd(channelHandle0, &CS0, sizeof(CS0), (const void *)&LATF , 1, 1 ); //RF2 = 0(CS = 0)
  SYS_DMA_ChannelSetup(
                   channelHandle0,
                   SYS_DMA_CHANNEL_OP_MODE_AUTO, //データ、アドレス自動インクリメント
                   DMA_TRIGGER_TIMER_2 //トリガソース:タイマ2
                  );
  SYS_DMA_ChannelEnable(channelHandle0);

  channelHandle1 = SYS_DMA_ChannelAllocate(DMA_CHANNEL_1); //RGポート用 DMAチャンネルオープン & ハンドル取得
  SYS_DMA_ChannelTransferAdd(channelHandle1, &SpiOut_A, sizeof(SpiOut_A), (const void *)&SPI4BUF, 2, 2 ); //SPI4バッファーへ書き込み
  SYS_DMA_ChannelSetup(
                   channelHandle1,
                   SYS_DMA_CHANNEL_OP_MODE_AUTO, //データ、アドレス自動インクリメント
                   DMA_TRIGGER_TIMER_2 //トリガソース :タイマ2
                  );
  SYS_DMA_ChannelEnable(channelHandle1);

  channelHandle2 = SYS_DMA_ChannelAllocate(DMA_CHANNEL_2); //RFポート用 DMAチャンネルオープン & ハンドル取得
  SYS_DMA_ChannelTransferAdd(channelHandle2, &CS1, sizeof(CS1), (const void *)&LATF , 1, 1) ; //RF2 = 1(CS = 1)
  SYS_DMA_ChannelSetup(
                   channelHandle2,
                   SYS_DMA_CHANNEL_OP_MODE_AUTO, //データ、アドレス自動インクリメント、
                   DMA_TRIGGER_TIMER_4 //トリガソース:タイマ4
                  );
  SYS_DMA_ChannelEnable(channelHandle2);


⑫インターバルタイマ(44.1KHz)のハンドル取得、諸元設定
  tmrHandle = DRV_TMR_Open ( DRV_TMR_INDEX_1, DRV_IO_INTENT_EXCLUSIVE );
  //タイマハンドル取得
  uint32_t divider = 2267; //周期レジスタ初期値 //最初の割り込みまでの時間をセット
                 //5 nsec x2 x 2267 = 22670nsec = 22.67μsec --> 44.111 KHz = 44.1 KHz
                 //5 nsec x2 x 39063 x 256 = 100.00128msec
  DRV_TMR_AlarmRegister ( tmrHandle, divider, true, 0, tmrISR ); //繰り返し呼び出し
                     //コールバック関数繰り返し呼び出しをセットアップ
                     // bool DRV_TMR_AlarmRegister(
                     //                     DRV_HANDLE handle,
                     //                     uint32_t divider, //周期レジスタ初期値
                     //                     bool isPeriodic, //周期性の有無
                     //                     uintptr_t context,
                     //                     DRV_TMR_CALLBACK callBack
                     //                     );

  // タイマハンドル取得
  Wait_Handle = DRV_TMR_Open ( DRV_TMR_INDEX_2, DRV_IO_INTENT_EXCLUSIVE ); //タイマ4ハンドル取得

 

⑬SPI4モジュール有効化
  PLIB_SPI_Enable(SPI_ID_4); //SPIモジュール イネーブル
  // SPI4CONbits.ON = 1; //SPI2 有効



⑭SDカード挿入のチェックをしています。
  void APP_SYSFSEventHandler(SYS_FS_EVENT event, void * eventData, uintptr_t context)
  {
    switch(event)
    {
      case SYS_FS_EVENT_MOUNT: //SDカード挿入済み
          appData.deviceIsConnected = true;
          LATGbits.LATG15 = 0; //RG15:消灯
          break;

      case SYS_FS_EVENT_UNMOUNT: //SDカード未挿入
          appData.deviceIsConnected = false;
          LATGbits.LATG15 = 1; //RG15:点灯

          break;

      default:
          break;
    }
  }



⑮各ステートの処理を記載します。

  void APP_Tasks ( void )
  {
    switch ( appData.state )
    {
      case APP_STATE_INIT:
      {
        bool appInitialized = true;
        if (appInitialized)
        {
          SYS_FS_EventHandlerSet(APP_SYSFSEventHandler, (uintptr_t)NULL);
          appData.state = APP_STATE_WAIT_FOR_DEVICE_ATTACH;
        }
        break;
      }


      case APP_STATE_WAIT_FOR_DEVICE_ATTACH:
        if(appData.deviceIsConnected)

  ……
  ……




以下、app.c




■system_interrupt.c を以下の要領で変更します

➀ポートBの変化割り込みハンドラー 
  void __ISR(_CHANGE_NOTICE_B_VECTOR, ipl1AUTO) _IntHandlerChangeNotification_PortB(void) の中に
  割り込み発生のポートを判別する関数SwCheck();を追加します。
 
  void __ISR(_CHANGE_NOTICE_B_VECTOR, ipl1AUTO) _IntHandlerChangeNotification_PortB(void)
  {
    SwCheck();

    PLIB_INT_SourceFlagClear(INT_ID_0,INT_SOURCE_CHANGE_NOTICE_B);
  }


以下、interrupt.c