Summary
A client needed a replacement for an aimatronics controller that was 20 years old for a display that was being refurbished. The client wanted a Nano or ESP32 implementation reusing the existing control and audio files. The control output was limited to 8 control signals and the client recommended using a self-contained MDFLY AU5016 card for audio playback.
Hardware Configuration

The hardware included an ESP32S NodeMCU module, an Adafruit microSD card breakout board+, a MDFLY AU5013/5016 audio card, and a HiLetgo 8-channel level translator. The AU5013/5016 card is a 5V only device and has a microSD slot for the audio files.
The AU5013/5016 is a fully integrated microSD/SDHC MP3/WAV/APE player module. It is controlled using start/stop protocol over a serial TTL level interface. The AU5016 also has digital signal control for several features. This module contains a high-performance DSP processor core and a high-quality stereo DAC that is capable of driving stereo earphone directly with an amplifier.
Also included in the breadboard test configuration was a MB102 breadboard power supply outputting both 5V and 3.3V, 10K pot for volume control, speaker, and 8 LEDs to represent the control output.
Implementation
The firmware was developed using the Arduino IDE (1.8.13) and use SPI (SD card interface), SD (SD card file system), HardwareSerial (AU5013/5016 com port), and FreeRTOS libraries. The firmware implemented two tasks, one task to control the output/audio and a second task for volume control. Each task was assigned to a core.
Two critical items in the implementation was the control playback at 30Hz and control of the audio card. The 30Hz playback used a hardware timer and interrupt handler for the output timing while the audio card control was implemented as a C++ object.
Timer
A timer object was created to generate an interrupt at the 30Hz rate. The interrupt handler incremented a counter (timeInt) while the main code that waits for the interrupt decrements the same counter. Using this method instead of a boolean allows for missed interrupts to be handled and the control loop to “catch-up” if necessary. The timer setup and interrupt control is greatly simplified for the ESP32 as shown in the code below.
hw_timer_t * timer = NULL; // struct used for timer
// setup 30Hz timer values but don't enable interrupt
timer = timerBegin(1, 320, true); // clock @ 250KHz, timer group 1
timerAttachInterrupt(timer, &onTimer, true); // interrupt handler
timerAlarmWrite(timer, 8333, true); // count and reload flag
timerInt = 0; // non zero used to indicate when an interrupt has occurred
// enable timer interrupt
timerAlarmEnable(timer);
// disable interrupt
timerAlarmDisable(timer);
AU5016 Object
An object was created to interface with the AU5013/5016 audio card. The object implemented most of the audio card commands. The interface used the HardwareSerial library for the serial interface and a FreeRTOS binary semaphore to provided an interface MUTEX due to multiple tasks interfacing to the same card. The object interface is shown in the code segment below.
// AU5016 object class
class AU5016 {
public:
AU5016(int uartNum, int RXPin, int TXPin, int BusyPin); // AU5016 object
int StartTrack(int track); // starts playing track, error return
void Stop(void); // stops tracking playing
int SetVolume(int level); // sets volume level, returns volume level
int VolumeUp(void); // increases volume level + 1, returns volume level
int VolumeDown(void); // decreases volume level -1, returns volume level
void Mute(void); // mutes output
int PlayPause(void); // pasues play track, un-pauses track, error return
int SetEQ(int EQ); // sets EQ, returns error
int RepeatMode(int Mode); // sets repeat mode, return error
int TrackAfterPower(int Mode); // sets power on play track, return error
int SetBusyStatus(int Mode); // sets busy output state, return error
private:
HardwareSerial *_AudioSerial; // UART interface to AU5016
SemaphoreHandle_t _sema_v; // multi task access control
int _Busy; // busy pin
int _audioSendCommand(int cmd); // send a command, return response
int _audioGetResponse(void); // get command response, return response
};
The constructor allows the users to select the UART was well as the pins used for the transmit, receive, and busy signals. Because the audio and control are time synchronized using third party software, when the controller is ready to set the first output word, the audio playback is started using the StartTrack method. What clock slip there is between the ESP32 and AU5013/5016 card is not noticeable during a ~2 minute show.