SIMPLE PLAYERを仕上げる

SIMPLE PLAYERを仕上げます。DFPlayerのクローンである、MP3-TF-16PとArduinoを組み合わせ、MP3プレイヤーを作ってきました。題してSIMPLE PLAYER。使用するモジュール、MP3-TF-16Pの動作速度の遅さや、強烈なノイズに悩ませられてきました。しかし、ハードウエア、ソフトウェア両面で工夫することで、なんとかねじ伏せてました。
今回は、SIMPLE PLAYERの仕上げとして、わずかに残されたノイズを徹底的にやっつけます。そして、スケッチにも少しだけ手を加え、より使いやすくします。そして、当初の予定通りOLEDの取り外しも行います。
SIMPLE PLAYERで使用するMP3-TF-16Pの評判は良くない
ソフトウェア制作時に、DFPlayerのマニュアルに記載ミスが見つかりました。そこで、ミスが修正されたマニュアルを探していたところ、GitHubにこんな記載があるのを見つけました。



真っ先に、MP3-TF-16P V3.0は避けろと書いてありました。私が使用しているのは、まさしくMP3-TF-16PのV3.0です。やはり、使いにくいようです。避ける理由の一つとして、DFPlayerや他のクローンと比較して動作速度が遅いと記載されていました。そして、コマンド間は少なくとも200mSの間隔をあけろと記載されています。
しかし、SIMPLE PLAYERを作るにあたって重ねてきた検証の結果、分かったことがあります。それは、コマンド間の間隔は、200mS均一ではないということです。BUSY信号を使って、疑似的なハンドシェイクをすれば、コマンドごとに適切なタイミングを計れます。一様に200mSの間隔をあけるよりも、確実でスマートだと思います。
SIMPLE PLAYに残されたノイズ問題
前回、SIMPE PLAYERのDAC出力から取り出した信号波形を観察してみました。その結果、P-Pで200mVの強烈なノイズが乗っていることが分かりました。そして対策として、簡易なCR構成のローパスフィルター(以下LPF)をSIMPLE PLAYERに追加しました。しかし、一定の効果はあったものの、完ぺきとは言えませんでした。
そこで、前回の記事の最後に記載した、より攻めた回路定数のLPFを試してみました。改良後のSIMPLE PLAYERの回路は、下図のように変わります。



SIMPLE PLAYERに追加したLPFのカットオフ周波数を、50kHzから20kHzに変更しました。つまり、高域側のカットオフを、可聴域ギリギリの20kHzまで下げたわけです。これによる、SIMPLE PLAYERの音質への影響が懸念されます。確かめるために、ブレッドボードにLPFを追加し、動作させてみました。その結果、音質変化は感じられませんでした。



そして、ノイズ排除の効果を見るため、出力波形を観察しました。






1kHz、20kHz共に、綺麗な正弦波が再現されています。ノイズは完全になくなっています。恐れていた音質への影響もありませんでした。今回採用したLPFは、非常に単純な構造です。そして、信号経路にコンデンサーが直列していません。そのため、位相変化は無視できるはずです。その結果として、SIMPLE PLAYERの音質への影響がなかったのでしょう。
最後の仕上げ
SIMPLE PLAYERの音質は、完ぺきと言えるレベルにまで仕上げることが出来ました。そして残されたのは、ディスプレイの削除です。これは、スケッチの変更で対処します。
今後、デバックが必要となったときのために、キーワード”WITH_OLED”の有無で制御できるようにしました。”WITH_OLED”が定義されている場合にのみ、OLEDのライブラリ読み込みや、状態表示が行われます。デバッグが必要になったら、キーワードをデファインするだけでOLEDが使用できます。
なお、OLED以外に、操作ボタンのチャタリング回避に関する部分を、少しだけ改良しました。
以下に改良後のスケッチを置いておきます。
/* Play audio file with DFPlayer*/
//#define WITH_OLED // OLED display use/unuse switch
#ifdef WITH_OLED
#include <Wire.h> // OLED display
#include <Adafruit_SSD1306.h> // OLED display
#endif
#include <EEPROM.h> // EEPROM storage
#include <avr/sleep.h> // MCU power control
#ifdef WITH_OLED
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#endif
volatile uint8_t PIN_PORT;
volatile uint8_t PIN_NUM;
// DFplayer command structire
#define DFPLAYER_SEND_LENGTH 10
#define SB 0x7E //start byte
#define VER 0xFF //version
#define LEN 0x6 //number of bytes after "LEN" (except for checksum data and EB)
#define FEEDBACK 1 //feedback requested
#define NO_FEEDBACK 0 //no feedback requested
#define EB 0xEF //end byte
// DFplayer command
#define NEXT 0x01
#define PREV 0x02
#define SETTRACK 0x03
#define VOLUP 0x04
#define VOLDN 0x05
#define SETVOL 0x06
#define SETEQ 0x07
#define PBMODE 0x08
#define PBSRC 0x09
#define STANDBY 0x0A
#define NORMAL 0x0B
#define RESET 0x0C
#define PLAY 0x0D
#define PAUSE 0x0E
#define FOLDER 0x0F
#define VOLADJ 0x10
#define REPEAT 0x11
#define RAND 0x18
// DFplayer query commands
#define INIT_SRC 0x3F
#define GET_VOL 0x43
#define GET_EQ 0x44
#define GET_TRACKS 0x48
#define GET_TRACK 0x4B
// EEPROM addresses
#define ADDR_TRACKS 0x180
#define ADDR_TRACK 0x182
#define ADDR_EQ 0x184
#define ADDR_VOL 0x185
// variables for DFplayer
uint8_t sending[DFPLAYER_SEND_LENGTH] = { 0 };
uint8_t response[20];
uint8_t commandValue = 0;
uint8_t feedbackValue = 0;
uint8_t paramMSB = 0;
uint8_t paramLSB = 0;
uint8_t checksumMSB = 0;
uint8_t checksumLSB = 0;
uint16_t Tracks;
uint16_t Track;
uint8_t Folders;
bool Status; // false:PAUSE true:PLAYING
uint8_t Volume;
uint8_t Length;
uint8_t EQ;
// variables for PIN operation
volatile uint8_t P_B;
void setup() {
int i;
uint16_t TEMP_TRACKS;
uint16_t TEMP_TRACK;
#ifdef WITH_OLED
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.clearDisplay();
display.setCursor(22, 14);
display.print("SIMPLE PLAYER");
display.display();
#endif
MCUCR &= ~(0 << PUD); // turn off pull up canceling bit
DDRB &= ~B00011111; // pin 8 to 12 in to read mode
PORTB |= B00001111; // pullup 8,9,10,11 except pin 12. because pin12 is active high
DDRB |= B00100000; // PIN 13 into output mode for debug
PCIFR &= ~B00000001; // clear interrupt flag
/*
PCICR &= ~B00000001; // disabling Pin change interrupt 0
PCMSK0 &= ~B00011111; // disabling PCINT 4::0
*/
PCIFR &= ~B00000001; // clear interrupt flag
PCICR |= B00000001; // enabling Pin change interrupt 0
PCMSK0 |= B00011111; // enabling PCINT 4::0
// start serial port for communicate with DFplayer
Serial.begin(9600);
// reset DFPlayer
commandValue = RESET;
feedbackValue = NO_FEEDBACK;
paramMSB = 0;
paramLSB = 0;
findChecksum();
sendData();
// wait for bootup DFPlayer
busyWait();
// wait for complete scanning TF card
busyWait();
// get tracks
getTracks();
// wait for complete query
busyWait();
// read EQ setting from EEPROM
EQ = EEPROM.read(ADDR_EQ);
// set EQ
setEQ();
busyWait();
// read current tack and total tracks from EEPROM
TEMP_TRACK = (EEPROM.read(ADDR_TRACK) << 8) + EEPROM.read(ADDR_TRACK + 1);
TEMP_TRACKS = (EEPROM.read(ADDR_TRACKS) << 8) + EEPROM.read(ADDR_TRACKS + 1);
if (TEMP_TRACK > TEMP_TRACKS) TEMP_TRACK = 1;
// check SD card has exchanged
if (TEMP_TRACKS == Tracks) {
// set current track and start playing
Track = TEMP_TRACK;
playTrack();
Status = true;
} else {
// reset current track
Status = false;
Track = 1;
}
// get volume setting
getVolume();
#ifdef WITH_OLED
// display current status
updateDisplay();
#endif
}
void loop() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // MCU power down and wait for PIN change interrupt
switch (PIN_NUM) {
case 8: // push PREV button D8
{
// previous command to DFplayer
if (Track > 1) Track--;
//commandValue = SETTRACK;
playTrack();
break;
}
case 9: // push PLAY/STOP button D9
{
if (Status == false) {
Status = true;
// play command to DFplayer
resumePlay();
} else {
Status = false;
// PAUSE command to DFplayer
pause();
}
delay(100);
break;
}
case 10: // push NEXT button D10
{
playNext();
busyWait();
break;
}
case 11: // push EQ button D11
{
// change EQ setting
EQ++;
setEQ();
#ifdef WITH_OLED
updateDisplay();
#endif
break;
}
case 12: // incoming busy signal to D12
{
if (Status == true) {
playNext();
}
break;
}
}
PIN_NUM = 0;
delay(200); // avoid for chattering
}
/*
PIN change interrupt handler
*/
ISR(PCINT0_vect) {
PIN_PORT = ~((PINB & B00001111) | B11110000); // extract D8::D11 pin status
switch (PIN_PORT) {
case B00000001:
PIN_NUM = 8;
break;
case B00000010:
PIN_NUM = 9;
break;
case B00000100:
PIN_NUM = 10;
break;
case B00001000:
PIN_NUM = 11;
break;
default:
if ((PINB & B00010000) == B00010000) PIN_NUM = 12; // check D12 pin
else PIN_NUM = 0;
}
}
/*
wait for done the busy status
*/
void busyWait() {
uint8_t i;
for (i = 0; i < 255; i++) { // detect busy signal rizing
if ((PINB & B00010000) == 0) delay(1);
else break;
}
for (i = 0; i < 255; i++) { // detect busy signal falling
if ((PINB & B00010000) != 0) delay(10);
else break;
}
PCIFR &= ~B00000001; // clear interrupt flag
}
/*
calcurate checksum
*/
void findChecksum() {
uint16_t checksum = (~(VER + LEN + commandValue + feedbackValue + paramMSB + paramLSB)) + 1;
checksumMSB = checksum >> 8;
checksumLSB = checksum & 0xFF;
}
/*
send command to DFPayer
*/
void sendData() {
clearBuffer();
sending[0] = SB;
sending[1] = VER;
sending[2] = LEN;
sending[3] = commandValue;
sending[4] = feedbackValue;
sending[5] = paramMSB;
sending[6] = paramLSB;
sending[7] = checksumMSB;
sending[8] = checksumLSB;
sending[9] = EB;
Serial.write(sending, DFPLAYER_SEND_LENGTH);
}
/*
get response from DFPlayer
*/
uint8_t getResponse() {
uint8_t pointer;
uint8_t i;
// wait for filled buffer
for (i = 0; i < 200; i++) {
if (Serial.available() < 9) delay(10);
else break;
}
// read response code
pointer = 0;
while (Serial.available()) {
response[pointer] = Serial.read();
pointer++;
}
return pointer;
}
/*
Clear serial buffers(RX and TX)
*/
void clearBuffer() {
// clear RX buffer
while (Serial.available()) {
Serial.read();
delay(1);
}
// clear TX buffer
Serial.flush();
}
/*
play next file
*/
void playNext() {
if (Track == Tracks) Track = 1;
else Track++;
playTrack();
}
/*
play specified track
*/
void playTrack() {
// play specified track
commandValue = SETTRACK;
feedbackValue = NO_FEEDBACK;
paramMSB = Track >> 8;
paramLSB = Track & 0xFF;
findChecksum();
sendData();
busyWait();
getTrack();
#ifdef WITH_OLED
updateDisplay();
#endif
}
/*
resume play
*/
void resumePlay() {
Status = true;
// resume plaing
commandValue = PLAY;
feedbackValue = NO_FEEDBACK;
paramMSB = 0;
paramLSB = 0;
findChecksum();
sendData();
// DO NOT WAIT BUSY SIGNAL HERE!!
}
/*
pause
*/
void pause() {
commandValue = PAUSE;
feedbackValue = NO_FEEDBACK;
paramMSB = 0;
paramLSB = 0;
findChecksum();
sendData();
// DO NOT WAIT BUSY SIGNAL HERE!!
}
/*
get VOLUME
*/
void getVolume() {
uint8_t i;
clearBuffer();
// get volume
commandValue = GET_VOL;
feedbackValue = NO_FEEDBACK;
paramMSB = 0;
paramLSB = 0;
findChecksum();
sendData();
if (getResponse() > 9) {
Volume = response[6];
}
}
/*
set EQ
*/
void setEQ() {
uint8_t i;
if (EQ > 5) EQ = 0;
commandValue = SETEQ;
feedbackValue = NO_FEEDBACK;
paramMSB = 0;
paramLSB = EQ;
findChecksum();
sendData();
EEPROM.update(ADDR_EQ, EQ);
delay(4); // wait for done EEPROM written
}
/*
get EQ
*/
void getEQ() {
// get EQ
commandValue = GET_EQ;
feedbackValue = NO_FEEDBACK;
paramMSB = 0;
paramLSB = 0;
findChecksum();
sendData();
if (getResponse() >= 9) {
EQ = response[6];
}
}
/*
get current track
*/
void getTrack() {
EEPROM.update(ADDR_TRACK, (Track >> 8));
delay(4); // wait for done EEPROM written
EEPROM.update(ADDR_TRACK + 1, (Track & 0xFF));
delay(4); // wait for done EEPROM written
}
/*
get tracks
*/
void getTracks() {
// get tracks
commandValue = GET_TRACKS;
feedbackValue = NO_FEEDBACK;
paramMSB = 0;
paramLSB = 0;
findChecksum();
sendData();
if (getResponse() >= 9) {
Tracks = (response[5] << 8) + response[6];
EEPROM.update(ADDR_TRACKS, (Tracks >> 8));
delay(4); // wait for done EEPROM written
EEPROM.update(ADDR_TRACKS + 1, (Tracks & 0xFF));
delay(4); // wait for done EEPROM written
}
}
#ifdef WITH_OLED
/*
get current track,tracks,volume,eq and display
*/
void getStatus() {
// get volume
getVolume();
// get EQ
getEQ();
// get current track
getTrack();
// get tracks
getTracks();
// Display status
updateDisplay();
}
#endif
#ifdef WITH_OLED
/*
update OLED display
*/
void updateDisplay() {
// update display
display.clearDisplay();
display.setCursor(0, 0); // Start at top-left corner
display.print("Track:");
display.print(Track);
display.setCursor(64, 0);
display.print("Tracks:");
display.print(Tracks);
display.setCursor(0, 16);
display.print("EQ:");
switch (EQ) {
case 0:
display.print("NORMAL");
break;
case 1:
display.print("POP");
break;
case 2:
display.print("ROCK");
break;
case 3:
display.print("JAZZ");
break;
case 4:
display.print("CLASSIC");
break;
case 5:
display.print("BASS");
break;
}
display.setCursor(64, 16);
display.print("VOL:");
display.print(Volume);
display.display();
}
#endif
1件のピンバック
失敗したClassAAヘッドホンアンプの改良 - POOQ.BIZ OWNERS BLOG