[Update Jan 6th, 2011 / An error in the capacitor unit (pF instead of nF) has been fixed in the schematics]
Long overdue post in my USB WAV player implementation series. Let’s go !
Here are the main software building blocks of the application:
The left side is the USB Mass Storage implementation based on the LUFA library. It is enabled if the user press a predefined button at power-on (see Part 1).
The right side is the WAV playback application and its related blocks.
- The Player block offers a high-level API to control the playback of files,
- The Wav parser contains some simple WAV-header parsing code to determine what are the sampling rate and number of channels of a file; it also helps to locate the audio data start in the file,
- The Dac block implements the digital to analog conversion to output sound to a speaker.
The DAC block converts digital samples to an analog signal using Pulse-Width Modulation (PWM) and a low-pass RC filter.
The PWM is generated from the Timer4 of the ATMEGA32U4 in Fast PWM mode with an 8-bit resolution. Using OCR4A and OC4B we can produce two independent PWM signals.
The PWM clock is set to the highest possible value (PeripheralClock / 256 = 187500 Hz) to avoid a fundamental harmonic close to the audio signal. The RC filter has a cut-off frequency of about 22kHz corresponding to the Nyquist frequency for the 44.1kHz sampling rate.
Two buffers of 8-bit samples (mono or stereo) are used as input. An interrupt triggered by Timer0 at the sampling rate (8kHz, 16kHz, 44.1kHz) read samples for one buffer and update the Timer4 output compare registers. When the end of one buffer is reached, the routine switch to the other one and trigger and End Of Buffer interrupt.
The End Of Buffer interrupt by Timer0 by writing a 0 output compare value. In OCR0B. This immediately raise the TIMER0_COMPB_vect interrupt. As its priority is lower than TIMER0_COMPA_vect, it will not block the sample transfer while being served by the Wav player block (The idea has been borrowed from LadyAda WaveHc library)
The double buffer mechanism allows the Wav player block to refill one buffer while the other one is copied to the PWM generation. It can be viewed as a particular implementation of a circular buffer.
Here is the WAV player API:
When started, the player parse the WAV header from a file then fill its buffers and start the DAC block. Subsequent data copy from the file to buffer are handled in the End Of Buffer interrupt:
When no more data can be read from the file, the End Of File callback is called to notify the client application. This callback is provided by the client when calling the Player_start API (notify_eof).
The routine also performs the sample conversion if the file contains 16-bit samples.
The following sampling rate are currently supported: 8kHz, 16kHz, 22.05kHz and 44.1kHz. Mono and stereo files can be played. As the DAC plays only 8-bit samples, 16-bit per sample files are converted to 8-bit samples when reading from the audio file.
The playback has been successfully tested with files up to 44.1kHz 16-bit mono files with a 16MHz clock. With 44.1kHz 16-bit stereo files, underflows are happening (To be investigated later to see if it can be supported with some optimization…)
The audio outputs are connected to an active amplifier through a stereo jack connector. Capacitors are placed after the RC filters to remove the DC components of the analog signal.
A stereo potentiometer allows to control the level of both audio channels.
Here is the circuit schematic:
I coded two demo applications to illustrate possible use cases:
- Shuffle: the application automatically plays the files in the root directory of the SD card. When the button 0 is pushed, the application randomly plays the next file.
- Trigger: the playback of a predefined file is assigned to each push-button.
As the playback is fully handled with interrupts, the mainloop in those demos is only responsible for the push-button polling and to start/stop the playback depending on the use case.
Testing the demos
Go in the apps/shuffle or apps/trigger subdirectory and type make.
Upload the resulting hex file with the Teensy loader.
Copy some WAV file to an SD card root directory and power-on the hardware.
Here is the result:
Shuffle playback demo
Button-triggered playback demo
The source code and demo binaries can be downloaded from bitbucket.org.
To my few readers
Any comment is welcome: if you learned something, if the code has been useful to a project of yours or if there is an error, leave a message