Friday, May 5, 2017

Programming the MeinEnigma - Playing Sounds



In this next instalment we'll look at how to play sounds on the MeinEnigma hardware.

Sound support is provided using a DFPlayer Mini sound module which is controlled by a serial interface to the Arduino. It contains a microSD card with MP3 files on it. Serial commands from the Arduino instruct it to play sounds on the SD card. The unit directly drives a small speaker which is mounted on the PCB.

The module is sold by various vendors. The datasheet I found was from Flyron Technology Co., Ltd. for their FN-M16P Embedded MP3 Audio Module.

The interface between the Arduino and sound module is a TTL-level serial interface on digital i/o pins 8 and 9. Since the Arduino only has one hardware serial port which is used for programming through USB, it is not used for this purpose. The MeinEnigma software, and my example program, use the AltSoftSerial.h library to support serial communications on pins 8 and 9. The protocol is mostly done in software.

There is also a hardware BUSY line connected to Arduino pin 12 which is used to determine if the module is currently playing a sound file. It is at a low level when the module is playing and a high level when idle.

Serial communication is at 9600 bps and the module accepts commands in a format documented in the data sheet. A command contains various fields containing parameters. A typical command is to play a specific file on the microSD card or to change the sound volume.

The sample program, listed below, starts with a couple of functions which were lifted from the MeinEnigma code. Function sendCommand() sends an arbitrary command to the sound module.

The function playSound() sends the command to play a sound file, specifying a number. The number is used to determine the filename on the microSD card to play.

The setup() method initializes the serial port and then sends commands to reset the sound module and set the volume.

The main loop() routine plays 26 sound files corresponding to the name of each letter of the alphabet being spoken. The numbers passed to playSound() are 1501 through 1526 and the corresponding sound files on the microSD card are called 1501-a.mp3 through 1526-z.mp3.

It plays each file in turn. At the end it pauses for 10 seconds, and then starts again.

/*
  MeinEnigma Example

  Demonstrates playing sounds with the sound module. The module must
  be present and have MP3 files on the SD card.

  Uses code from the MeinEnigma software.

  Jeff Tranter

*/

#include

AltSoftSerial altSerial;

// For sound module FN_M16P aka DFplayer
#define BUSY 12

#define dfcmd_PLAYNO 0x03       // Play the nth song
#define dfcmd_PLAYNAME 0x12     // Play song in /mp3 named nnnn*mp3
#define dfcmd_VOLUME 0x06       // Set volume
#define dfcmd_RESET  0x0c       // Reset unit
#define dfcmd_GETCNT 0x48       // Get SD card file count
#define dfcmd_GETSTATE 0x42     // Get current status
#define dfcmd_GETFEEDBACK 0x41  // Get feedback from module

// Buffer for sound commands.
uint8_t msgBuf[10]= { 0X7e, 0xff, 0, 0, 0, 0, 0, 0, 0, 0xef};

// Write data to sound board. Send "cmd" with the option "opt".
void sendCommand(uint8_t cmd, uint16_t opt=0) {
  uint8_t i;
  uint16_t csum;

  msgBuf[0]= 0x7e;        // Start
  msgBuf[1]= 0xff;        // Version
  msgBuf[2]= 6;           // Length = always 6
  msgBuf[3]= cmd;         // Command
  msgBuf[4]= 0;           // Feedback with 0x41, 0 if no and 1 if yes
  msgBuf[5]= opt >> 8;    // Optional value high byte
  msgBuf[6]= opt & 0xff;  // Optional value low byte
  csum = 0 - (msgBuf[1] + msgBuf[2] + msgBuf[3] + msgBuf[4] + msgBuf[5] + msgBuf[6]);
  msgBuf[7]= csum >> 8;
  msgBuf[8]= csum & 0xff;
  msgBuf[9]= 0xef;        // End

  for (i = 0; i < 10; i++) {
    altSerial.write(msgBuf[i]);
  }
}

// Write data to soundboard. To abort any currently playing sound, set
// wait=false.
void playSound(uint16_t fileno, boolean wait=true) {
  int16_t cnt;
  uint8_t retry;

  if (wait) {  // Should we make sure it's done playing.
              // It's just small snippets so it shouldn't take too long.
    cnt = 0;
    while (digitalRead(BUSY) == LOW && cnt < 300) {
      cnt++;
      delay(10);
    }
  }

  retry = 3;
  do {
    // Send play command.
    sendCommand(dfcmd_PLAYNAME, fileno);
    // Wait for it to start playing.
    cnt = 0;
    while (digitalRead(BUSY) == HIGH && cnt < 200) {
      cnt++;
      delay(1);
    }
    retry--;
  } while (digitalRead(BUSY) == HIGH && retry > 0); // If not started, send again.
}

void setup() {
    altSerial.begin(9600);
    sendCommand(dfcmd_RESET, 0);   // Reset unit.
    delay(500);    
    sendCommand(dfcmd_VOLUME, 30); // Set volume.
}

void loop() {
  for (int i = 1; i <= 26; i++) {
    playSound(1500 + i);
  }
  delay(10000);
}

References


  1. http://www.flyrontech.com/eproducts/84.html
  2. https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
  3. https://github.com/jefftranter/meinEnigma/tree/master/Examples/SoundFiles