前回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信号用抵抗 |
回路図
実際のものは
- 完成品。本体をアクリル板で囲っています。
- 天井のアクリル板を取った所
- LCDを外した所
- 電源ケーブルを外した所。これだけの部品しか有りません。
電源ケーブル
本来ならアダプター電源を取るためのUSBケーブルと音出力用のケーブル、2本必要でした。車内でケーブル2本を這わすのは邪魔です。今回は
- USBは電源のみなので信号線2本は使用していない
- 車の音声入力、”AUX”がUSBコネクターに近くに有る
なので、USBコネクターの近くで信号線2本をを取り出し、GNDと合わせて音声出力用にしました。これでケーブルはUSBケーブル1本のみになりました。
ソフトウェア
主なプログラムは
- main.c: メインプログラム
- 基本的には、ESP32でDFPlayer制御する(02)で使用したプログラムをPIC用に変えています。
- 今回もDFPlayerからのメッセージの処理に苦労しています。
- プログラムの流れについては後で説明します。
- AQM1602A.c: LCD関係のプログラム
- MPLAB® Code Configurator(I2C編)でAQM1602Aの使い方を説明しています。
- リンク先では変換キットを買わなかったので接続に苦労しました。今回は変換キット込で購入したので接続は簡単でした。
- ここではAQM1602Aが持っているコマンドの幾つかを関数にしています。
- DFPlayer.c: DFPlayer関係のプログラム
- DFPlayer制御用の関数の集まりです。
- 製品のHP: DFPlayer_Mini_SKU_DFR0299-DFRobot
- Arduinoのサンプルコードと詳しい製品マニュアル: DFRobotDFPlayerMini
です。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まで)が数字になるファイルを保存します。
- フォルダーと再生ファイルの選択はこの数字を使って行われます。
今回のプロジェクト
今回のプロジェクトをここに保存して置きます。
最後に
前回に比べればかなり改善しました。個人的には上手く出来た方だと思っています。