SIMPLE PLAYERを仕上げる

Spread the love

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は避けろと記載されていた

真っ先に、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の回路は、下図のように変わります。

攻めたLPFを搭載したSIMPLE PLAYERの実態配線図
攻めたLPFを搭載したSIMPLE PLAYERの実態配線図

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

SIMPE PLAYERにLPFを追加
SIMPLE PLAYERにLPFを追加

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

LPF改良後出力波形 1kHz正弦波
LPF改良後出力波形 1kHz正弦波
LPF改良後出力波形 20kHz正弦波
LPF改良後出力波形 20kHz正弦波

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件のピンバック

コメントを残す

This site uses Akismet to reduce spam. Learn how your comment data is processed.