DFPlayer (PIC編)

前回ESP32でDFPlayerをコントロールするMP3Playerを製作した。元々車を運転している時のBGMとして作ったのですが使ってみてちょっと不便が有りました。

  • 運転中にフォルダーの選択が出来ない
    • 前回製作したMP3Playerはロータリースイッチでフォルダーを選択していました。
    • 流石に運転中にロータリースイッチの操作が出来ません
  • ロータリースイッチの数字が小さすぎる
    • 元々表示が小さい上に、取り付け角度を横にしたせいでロータリースイッチの表示が見難い
  • USBケーブルの抜き差しが電源のオンオフになる
    • 電源を車のシュガーライターアダプタから取っています。
    • USBケーブルの抜き差しが電源のオンオフなのですが、これが意外とやり難い。
  • DFPlayerにコマンドを送っているのだけなのにESP32を使うのはもったいない
    • 再生はDFPlayer。ESP32をDFPlayerにコマンドを送っているのみ。曲再生中は何もしていない。
    • WiFi機能を使用していない。ESP32自体それなりサイズが大きい。

そこで、

  オンオフスイッチとLCD表示器を付けて、ボタンでフォルダーを選択。CPUはPICとする。

こんなMP3Playerを作る事にしました。

新しいMP3Player

先ずはハードウェアの説明。

使用した主な部品

   部品         説明
 DFPlayer MP3再生用本体。前回購入したもの
 PIC24FJ64GB002 今回のCPU。以前にUSB勉強用買って使用しなかったもの。
 回路的にはPIC24FJ64GA002(USBの無いもの)でも使用可能
 AQM1602XA 状態表示用のLCD
 キャラクターディスプレイだが表示される文字のサイズが大きい
 NJU7223
 0.1uFコンデンサ 2個 
 USBの5V電源から3.3Vを作成する3端子レギュレーター
 プッシュスイッチ 7個 操作用プッシュスイッチ 
 2回路2接点中点有り
 トグルスイッチ 1個
 プログラム開発と実行でPICの端子を切り替えるスイッチ
 他の信号線2本はPICに直接つなぎ、電源とReset端子のみ切り替え 
 このスイッチは最終的に製品の電源スイッチになる
 USBケーブル 今回は壊れたマウスのケーブルを使用
 ケーブルに細工し、電源とスピーカー出力用に使用
 LEDと抵抗1K 各1個 デバッグとエラー表示用LED
 抵抗22K 1個 PIC Reset信号用抵抗

回路図

実際のものは

  1. 完成品。本体をアクリル板で囲っています。
  2. 天井のアクリル板を取った所
  3. LCDを外した所
  4. 電源ケーブルを外した所。これだけの部品しか有りません。

電源ケーブル

本来ならアダプター電源を取るためのUSBケーブルと音出力用のケーブル、2本必要でした。車内でケーブル2本を這わすのは邪魔です。今回は

  • USBは電源のみなので信号線2本は使用していない
  • 車の音声入力、”AUX”がUSBコネクターに近くに有る

なので、USBコネクターの近くで信号線2本をを取り出し、GNDと合わせて音声出力用にしました。これでケーブルはUSBケーブル1本のみになりました。

ソフトウェア

主なプログラムは

  • main.c: メインプログラム
  • AQM1602A.c: LCD関係のプログラム
    • MPLAB® Code Configurator(I2C編)でAQM1602Aの使い方を説明しています。
    • リンク先では変換キットを買わなかったので接続に苦労しました。今回は変換キット込で購入したので接続は簡単でした。
    • ここではAQM1602Aが持っているコマンドの幾つかを関数にしています。
  • DFPlayer.c: DFPlayer関係のプログラム

です。PIC設定用プログラムはMCCを使用して作成しています。

プログラム main.c。

main.c

/**
  Generated main.c file from MPLAB Code Configurator

  @Company
    Microchip Technology Inc.

  @File Name
    main.c

  @Summary
    This is the generated main.c using PIC24 / dsPIC33 / PIC32MM MCUs.

  @Description
    This source file provides main entry point for system initialization and application code development.
    Generation Information :
        Product Revision  :  PIC24 / dsPIC33 / PIC32MM MCUs - 1.170.0
        Device            :  PIC24FJ64GB002
    The generated drivers are tested against the following:
        Compiler          :  XC16 v1.61
        MPLAB 	          :  MPLAB X v5.45
*/

/*
    (c) 2020 Microchip Technology Inc. and its subsidiaries. You may use this
    software and any derivatives exclusively with Microchip products.

    THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
    EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
    WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
    PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION
    WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION.

    IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
    INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
    WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
    BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE
    FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN
    ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
    THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.

    MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE
    TERMS.
*/

/**
  Section: Included Files
*/
#include "mcc_generated_files/system.h"
#include "mcc_generated_files/mcc.h"
#include <xc.h>
#include "AQM1602A.h"
#include "DFPlayer.h"
#include "stdio.h"

#define FCY 4000000UL
#include <libpic30.h>

#define btn_chk     0xec
#define DFP_busy    PORTBbits.RB4
#define vol_data    25

uint8_t flg_run_mode;

void print_num(uint8_t data, uint8_t flg)
{
    char buf[5];
    
    if(flg) sprintf(buf,"%02X",data);
    else sprintf(buf,"%02d",data);
    put_str(0,0,buf);
}

void chk_release()
{
    uint16_t data;
    
    data = btn_chk;
    data <<= 8;
    
    while((PORTB & data) != data) __delay_ms(20);
    __delay_ms(200);
}

void inc_number(uint8_t cursor_pos, uint8_t* value_temp, uint8_t* digit_order, uint8_t p_m)
{
    uint8_t data;
    int a;
    
    cursor(0);
    if(cursor_pos != 2)
    {
        a = value_temp[cursor_pos];
        if(p_m) a --; 
        else a ++;
        if(a > 9) a = 0;
        if(a < 0) a = 9;
        
        value_temp[cursor_pos] = data = a;
    }
    else 
    {
        value_temp[cursor_pos] = !value_temp[cursor_pos];
        data = 'S';
        if(value_temp[cursor_pos]) data = 'R';
        data -= 0x30;
    }    

    put_char(digit_order[cursor_pos], 0, data + 0x30);
    move_cursor(digit_order[cursor_pos], 0);
    cursor(1);
    __delay_ms(50);
}

void print_nemu(uint8_t* data)
{
    cursor(0);
    put_str(0,0,"Folder:   S/R:");
    put_char(8,0, data[0] + 0x30);
    put_char(7,0, data[1] + 0x30);
    if(data[2]) put_char(14,0, 'R');
    else put_char(14,0, 'S');
}

void copy_data(uint8_t* org, uint8_t* data)
{
    int a;

    for(a = 0; a < 3; a ++) data[a] = org[a];
}    

uint16_t get_adc(void)
{
    uint16_t data;
    
    ADC1_Enable();
    ADC1_ChannelSelect(0);
    ADC1_SoftwareTriggerEnable();
    __delay_ms(100);     //Provide Delay
    ADC1_SoftwareTriggerDisable();
//       while(!ADC1_IsConversionComplete(0));
    data = ADC1_ConversionResultGet(0);
    ADC1_Disable();
    return(data);
}    

void randomSeed()
{
    get_adc();
    get_adc();
    srand(get_adc());
}

uint8_t play_rnd_trk(uint8_t trk_no, uint8_t* trk_arry)
{
    int a,randNumber;
    uint8_t now;
    
    randNumber = rand() % trk_no;   
    now = trk_arry[randNumber];

    for(a =  randNumber; a <  (trk_no-1); a ++)
        trk_arry[a] = trk_arry[a + 1];

    return(now);    
}

void put_c(uint8_t x, uint8_t y, uint8_t data)
{
    char buf[20];
    
    sprintf(buf,"%02X",data);
    put_str(x,y,buf);
}

void go_sleep(uint8_t d_all, uint8_t d_now, uint8_t d_play)
{
    char buf[20] = {'A','N','P',':',' '};

    cursor(0);
    set_line(1);
    sprintf(&buf[5],"%03d",d_all);
    sprintf(&buf[9],"%03d",d_now);
    sprintf(&buf[13],"%03d",d_play);
    buf[8] = buf[12] = '/';
    buf[16] = 0;
    put_str(0,1,buf);

    while(DFP_busy) __delay_ms(20);
    __delay_ms(50);

    EX_INT1_InterruptEnable();
    EX_INT2_InterruptEnable();
    __delay_ms(200); 
    __builtin_pwrsav(1);
    asm("nop");
    asm("nop");
    asm("nop");
    __delay_ms(100);
}

void flash_buf(void)
{
    while(!UART1_IsRxReady())  __delay_ms(20); 
    __delay_ms(200);
    while(UART1_IsRxReady()) UART1_Read();
}

#include "mcc_generated_files/memory/flash.h"
// Allocate and reserve a page of flash for this test to use.  The compiler/linker will reserve this for data and not place any code here.
static __prog__ uint8_t flashTestPage[FLASH_ERASE_PAGE_SIZE_IN_PC_UNITS] __attribute__((space(prog),aligned(FLASH_ERASE_PAGE_SIZE_IN_PC_UNITS)));

void FlashError()
{
    LATBbits.LATB5  = 1;
    while(1) __delay_ms(20);
}

uint32_t flash_storage_address;
static void WordWrite(uint8_t fol, uint8_t r_s)
{
    uint32_t write_data[1]= {0};
    bool result;

    // Get flash page aligned address of flash reserved above for this test.
    flash_storage_address = FLASH_GetErasePageAddress((uint32_t)&flashTestPage[0]);

    // Program Valid Key for NVM Commands
    FLASH_Unlock(FLASH_UNLOCK_KEY);

    // Erase the page of flash at this address
    result = FLASH_ErasePage(flash_storage_address);
    if (result == false)
    {
        FlashError();
    }

    // Write 24 bit Data to the first location.
    write_data[0] = fol << 8;
    write_data[0] |= r_s;
    result  = FLASH_WriteWord24(flash_storage_address, write_data[0]);

    if (result == false)
    {
        FlashError();
    }

    // Clear Key for NVM Commands so accidental call to flash routines will not corrupt flash
    FLASH_Lock();
}

/*
                         Main application
 */

int main(void)
{
    uint8_t digit_value[3] = {0,0,0};
    uint8_t value_temp[3] = {0,0,0};
    uint8_t digit_order[3] = {8,7,14};
    uint8_t trk_arry[100];
    uint8_t cursor_pos, data, p_m;
    uint8_t flg_play, flg_cansel;
    uint8_t fol_no, play_now, RndCount, Org_Count;
    uint32_t read_data[1]= {0};
    
// initialize the device
    SYSTEM_Initialize();
    __delay_ms(200);
    init_LCD();
    
    set_line(0);
    put_str(0,0,"   Wait ...");
    
    randomSeed();

    flash_storage_address = FLASH_GetErasePageAddress((uint32_t)&flashTestPage[0]);
    read_data[0] = FLASH_ReadWord24(flash_storage_address);
    digit_value[2] = read_data[0] & 1;
    data = (read_data[0] >> 8) & 0xff;
    digit_value[1] = data / 10;
    digit_value[0] = data % 10;
    copy_data(digit_value, value_temp);

    while(!UART1_IsRxReady())  __delay_ms(20);
    __delay_ms(1000);
    flash_buf();
    
    DFP_set_vol(vol_data);
    print_nemu(digit_value);
    move_cursor(8,0);
    cursor(1);

    flg_cansel = cursor_pos = flg_run_mode = play_now = p_m = 0;

    while(1)
    {
        switch(flg_run_mode)
        {
            case 0: // edit mode
                data = (PORTB >> 8) & btn_chk;
                if(data != btn_chk)
                {
                    chk_release();
                    switch (data)
                    {
                        case 0x6c: // cancel
                            copy_data(digit_value, value_temp);
                            print_nemu(digit_value);
                            if(flg_cansel)
                            {
                                DFP_play();
                                flg_run_mode = 1;
                                go_sleep(Org_Count, Org_Count - RndCount, play_now);
                            }
                            else
                            {
                                move_cursor(8,0);
                                cursor_pos = 0;
                                cursor(1);
                            }
                            break;
                            
                        case 0xcc: //dec number
                            p_m = 1;
                        case 0xe4: //inc number
                            inc_number(cursor_pos, value_temp, digit_order, p_m);
                            p_m = 0;
                            break;
                        
                        case 0xac: //move cursore
                            cursor_pos ++;
                            if(cursor_pos > 2) cursor_pos = 0;
                            move_cursor(digit_order[cursor_pos],0);
                            break;
                    
                        case 0xe8: // OK
                            if(flg_cansel)
                            {
                                data = DFP_get_vol();
                                DFP_set_vol(0);
                                DFP_play();
                                DFP_stop();
                                DFP_set_vol(data);
                            }
                            
                            flg_cansel = 1;
                            copy_data(value_temp, digit_value);
                            fol_no = digit_value[0] + digit_value[1] * 10;
                            Org_Count = RndCount = DFP_get_trkno_fol(fol_no);
                            for(play_now = 0; play_now < Org_Count; play_now ++) 
                                trk_arry[play_now] = play_now;

                            play_now = flg_play = 0;
                            if(digit_value[2])
                            {
                                play_now =  play_rnd_trk(RndCount, trk_arry);  
                                flg_play = 1;
                            }
                            DFP_fol_trk(fol_no, play_now);
                            WordWrite(fol_no, flg_play);
                            RndCount --;
                            go_sleep(Org_Count, Org_Count - RndCount, play_now);
                    }
                }
                break;
                    
            case 1: // enter edit mode
                    EX_INT1_InterruptDisable();
                    EX_INT2_InterruptDisable();
                    DFP_pouse();
                    chk_release();
                    set_line(0);
                    move_cursor(8,0);
                    cursor_pos = 0;
                    flg_run_mode = 0;
                    cursor(1);
                    break;
                    
            case 2: // play next
                    EX_INT1_InterruptDisable();
                    EX_INT2_InterruptDisable();
                    if(flg_play) // Play randam
                    {
                        if(!RndCount) 
                        {
                            for(RndCount = 0; RndCount < Org_Count; RndCount ++) 
                                trk_arry[RndCount] = RndCount;
                            
                            RndCount = Org_Count;
                        }
                        play_now = play_rnd_trk(RndCount, trk_arry);
                    }
                    else    // Play seriral
                    {
                        play_now ++;
                        if(play_now == Org_Count)
                        {
                            play_now = 0;
                            RndCount = Org_Count;
                        }
                    }
                    DFP_fol_trk(fol_no, play_now);
                    RndCount --;
    
                    flash_buf();
    
                    go_sleep(Org_Count, Org_Count - RndCount, play_now);
                    break;
        }
        __delay_ms(20); 
    }

    return 1;
}
/**
 End of File
*/

プログラムの流れ。

MCCでの設定

PeripheralsにADC1 / EXT_INT / FLASH / I2C1 / UART1を追加。

  • ADC1: RA0
    • 乱数を発生用に使用しています。
  • EXT_INT: INT1: RB10 / NT2: RB4
    • INT1: ボタン割り込み
    • INT2: 曲終了割り込み
  • FLASH: 
    • フォルダー 順次/ランダム データの保管
    • 電源オン時に前回の状態を再現する為に使用。
  • I2C1: SCL1: RB8 / SDA1: RB9
    • AQM1602A LCD 制御用
  • UART1: U1RX: RB2 / U1TX: RB3
    • DFPlayerとの通信
    • Baud Rateを9600に設定
    • 割り込み有効
    • DFPlayerとは10バイト単位で通信するので 送信、受信のバッファーサイズを10に設定。
  • Port B 10,11,13,14,15 ー> 入力 PULL UP有り
    • RB10: ”OK” ボタン用
    • RB11: ”+” ボタン用
    • RB13: ”ー” ボタン用
    • RB14: ”移動” ボタン用
    • RB15: ”キャンセル ”ボタン用
  • Port B 5 ー> 出力
    • LED制御用

動作の説明

このMP3Playerは、再生曲を設定する、”編集モード”と、それを実行する、”再生モード”が有ります。

  • 電源をオンにするとこの画面になります。
  • ここでは再生するフォルダー(00から99)と再生モード(S:順次 R:ランダム)を指定します
  • 文字の下にアンダーライン(カーソル)が有る所が編集可能です。
  • ”+” ”ー” キー
    • フォルダー指定の箇所にカーソルが有る場合
      • ”+”キー: 値を1つ増やす。 9の次は0になる。
      • ”ー”キー: 値を1つ減らす。 0の次は9になる。
    • モード指定の箇所にカーソルが有る場合
      • どちらのキーも値を変転させる。
  • ”移動” キー
    • 左回りにカーソルの位置を変更する
    • 移動する箇所は、フォルダーの2桁とモード指定の1桁の3箇所。
  • ”OK” キー
    • 再生開始。
  • ”キャンセル” キー
    • 状態を編集モードに入る前に戻す。
    • 編集モードに入る前の曲が再生される。
  • 再生中は、”キャンセル” ”選択” ”+” ”ー” のキーは使用出来ません。
  • 再生中は ”OK” ボタンのみ有効です。このボタンを押すと編集モードに移行。

SDカードに曲の保存方法

これはESP32の時と同じです。

  • ルートディレクトリに数字2文字(00から99まで)のフォルダーを作成します。
  • ファルダーの中には先頭3文字(000から255まで)が数字になるファイルを保存します。
  • フォルダーと再生ファイルの選択はこの数字を使って行われます。

今回のプロジェクト

今回のプロジェクトをここに保存して置きます。

最後に

前回に比べればかなり改善しました。個人的には上手く出来た方だと思っています。