Professional Documents
Culture Documents
Audio Player Using The PIC32 MCP4822 microSD Card and The MDDFS Library PDF
Audio Player Using The PIC32 MCP4822 microSD Card and The MDDFS Library PDF
Audio Player Using The PIC32 MCP4822 microSD Card and The MDDFS Library PDF
INTRODUCTION
The PIC32 series of microcontrollers come with a powerful processor, lots of memory and a
very rich set of peripherals that make it ideal for a wide range of applications. One such application that
I wanted to have fun with is audio playback. Initially I made a simple audio player where I hard-coded
the audio in a header file and played it back. Obviously this had its drawbacks, specifically with
storage, and so I wanted something more flexible. That led me to exploring the use of a microSD card
in the project. I had an 8GB microSD card lying around that I could use for this project. So, I decided
to build a simple WAV player using that.
I had used the internal PWM module for audio playback but this time I wanted better audio
output and so decided to use the MCP4822 12-bit dual DAC that I had with me. I have previously
talked about it in my articles:
PIC32 SPI: Using the MCP4822 12-bit serial DAC
PIC32 DMA+SPI+DAC: Analog output synthesis with zero CPU overhead
The WAV player I've made supports PCM (pulse code modulation) format WAV files. I've tested
with 16-bit 44,100Hz audio files and the output sounds great. It works with 8-bit audio files as well but
getting that to work has (oddly) been particularly difficult. While I still haven't been able to figure out
what exactly I was doing wrong with the 8-bit part, I managed to find a solution to the problem and that
is what I have for my WAV player.
For now, the user interface is minimalistic and is done through the serial port. I didn't intend this
project to be a full-fledged music player project but just a learning experience and so did not enhance
the UI much further than necessary to select a song and play it back.
SYED TAHMID MAHBUB – PIC32 based audio player with microSD card and 12-bit audio out
PARTS
The circuit itself is very simple and consists of the following sections:
• PIC32MX250F128B – This is the heart of the project and is the central microcontroller
that interfaces with all the different sections. The PIC is operating off of the internal oscillator
(FRCPLL configuration) at 40MHz, with the peripheral bus clock also at 40MHz.
• MicroSD/MDDFS – This is the storage section of the project. The interface is done
using a PIC SPI module – SPI1 specifically. A critical part in the project is Microchip's open-source
file-system library MDDFS. This is part of the Microchip Libraries for Applications (MLA). I have
found it very useful and fairly easy to use – it comes with good documentation and demo code, but just
takes a little bit of effort to get going. I used v2013-06-15 (part of the legacy MLA).
• Audio section – The DAC I used is the MCP4822. It is a dual 12-bit DAC
communicating with the PIC using SPI – SPI2 for the DAC. The SPI clock frequency I used was
20MHz. The two outputs of the DAC have capacitors for AC coupling and have current-limiting
resistors.
• Serial port – UART2 of the PIC is used for the serial port interface. Since my computer
does not have a physical serial port, I used a PL2303-based serial-to-USB converter (Adafruit product
954).
Now on to the actual data. That depends on the number of channels and the bits per sample. The
first key thing to remember is that for 16-bit PCM files, the data is stored as 16-bit SIGNED samples,
whereas for 8-bit PCM files, the data is stored as 8-bit UNSIGNED samples.
The way the audio data is organized is illustrated below for 8-bit and 16-bit mono and stereo
audio files (each block is a byte; the leftmost block is the lowest-numbered byte):
For the audio playback, I managed two cyclic buffers, each 128 points: LSTACK for the left
channel and RSTACK for the right channel. The audio file is read and then the samples are converted to
12-bit values to be stored in the buffers (along with channel information for the DAC). For mono files,
both channels play the same values. For 8-bit samples, the 12-bit data is obtained by bit-shifting left 4
times. For 16-bit samples, the 12-bit data is obtained using the 8 bits of the HIGH byte and 4 bits of the
LOW byte. For 16-bit samples, the SIGNED 12-bit values are then converted to UNSIGNED values
by centering about 2048 (because 2048 = 212/2) instead of 0.
Using the sample rate (obtained from the WAV header), the period register for timer 2 is
configured (PR2). The timer 2 interrupt is enabled and in the ISR, the required values from LSTACK
and RSTACK are output to the DAC. The cyclic buffers are used to ensure smooth audio playback.
SYED TAHMID MAHBUB – PIC32 based audio player with microSD card and 12-bit audio out
TOS is used as the “Top Of Stack” to indicate the position from where to retrieve the audio value to
push to the DAC. BOS is used as the “Bottom Of Stack” in the section where the audio data values are
read; BOS indicates the position onto which a new value is being pushed into the buffer. It was
determined, by testing, that 128-point stacks/buffers are enough to ensure smooth continuous audio
playback.
MDDFS
The Microchip Disk Drive File System library is simple to use and is part of the Microchip
Libraries for Applications (MLA) available for download for free on the Microchip website. Instead of
going into detail about the library, I'll just explain the specific parts I used in my project. Microchip
provides additional documentations if you're interested in learning more.
First, the required files. The only files from MDDFS that are needed for my project (and that I
have uploaded) are under the three folders MDD_includes, MDD_sources, PIC32. The required
SYED TAHMID MAHBUB – PIC32 based audio player with microSD card and 12-bit audio out
function. This function searches for other files with the same filename and attributes as used for
the FindFirst function. The resulting information is, as with FindFirst, stored in the SearchRec
structure rec. The function returns 0 if a file is found; -1 otherwise.
• To open a file for reading, the function FSfopen(filename, “r”) is used. “r” can be replaced
with “w” to open the file for writing. Given a specified filename, the function returns NULL if
opening failed; otherwise it returns a pointer (type FSFILE defined in the library) used later in
my code for the FSfread function mentioned below.
• To actually read data from the opened file, the function used was FSfread(buffer, sz, n,
pointer) is used. The parameter pointer is that returned by FSfopen mentioned above. The
parameter sz tells the function the size of a given item to read; the parameter n tells the function
how many of these items to read. The read values are stored in the provided buffer (the pointer
to which is passed to the function). The function returns the number of read items. I used sz = 1
so that one item was one byte and the number of returned items was equal to the number of
bytes read.
• Once all file operations end, the file is closed with the function FSfclose(pointer), pointer being
the same as that mentioned above for FSfopen (returned by FSfopen).
I made some changes to the default provided files and the files I provide are inclusive of my
changes. Some important points are listed below:
• In HardwareProfile.h, I added a “#define RUN_AT_40MHZ” along with corresponding
GetSystemClock(), GetPeripheralClock() and GetInstructionClock() macros. This is because I
am running my PIC at 40MHz, but there was not definition in this file for 40MHz.
• From line 528 in HardwareProfile.h are the hardware-specific definitions that are of interest to
us, in relation to the use of MDDFS for the specific PIC32. Here I defined the use of SPI1 for
MDDFS and the specific SPI pins required. Note that even though I have pins and directions
defined for CD (Card Detect) and WE (Write Enable) I don't make use of these in my
program. I mentioned above regarding the change in code for CD. For WE, in the function
MDD_SDSPI_WriteProtectState in SD-SPI.c, I just had it return 0 to indicate that my card was
not write protected.
• In the file SD-SPI.c, in the function MDD_SDSPI_InitIO, I defined the pins to be used for
SDO1 and SDI1. Note that SCK1 and SCK2 are not peripheral pin-selectable.
SYED TAHMID MAHBUB – PIC32 based audio player with microSD card and 12-bit audio out
USER FUNCTIONS
Several user functions were utilized in this project.
• The functions initDAC and setupUART are self explanatory: initDAC sets up the SPI2 module
for use with the DAC; setupUART sets up UART2 for use for the serial port interface.
• The function infiniteBlink is used at the end of program execution. All it does is toggle the
LED infinitely (as the name implies) with a small software delay so that the blinking is visible
by the naked eye.
• The function setupAudioPWM essentially clears the timer 2 control register and sets the
corresponding interrupt priority.
• The function setupSystemClock configures Timer 3 so that its period is 1ms. This is a system
clock of sorts used for higher level time-keeping (since its resolution is 1ms). The
corresponding timer interrupt and priority are set.
• The function void getFilename(char * buffer) fills the character array (string) buffer with the
filename. This is called when the user is prompted to type in the filename of the song to play
back. The parameter passed is the “pointer to the string”.
• The function void configureHardware(UINT32 sampleRate) sets up the period register for
timer 2 (PR2) so that the period is equal to the sample rate of the audio file. The sample rate is
read off the WAVE header and passed as the parameter to this function.
• The function inline void writeDac(UINT16 dacVal) writes the required value, dacVal, to the
DAC digital input through the SPI2 interface. It is implemented as an inline function to
eliminate function call overhead since the function itself is fairly short. It lowers the slave select
(SS) line, checks for the transmit buffer to be empty, loads the buffer, waits for the completion
of transfer and then finally raises the slave select (SS) line.
• The function UINT8 getWavHeader(FSFILE * pointer) checks the WAV header to ensure that
the corresponding audio file is supported. The parameter provided to the function [FSFILE *
pointer] is the pointer to the character array that contains the header information. The function
returns a zero if everything is ok; if not, it returns an error code indicative of what type of error
occurred (incorrect header or an unsupported WAV file type).
• The function getParameters checks the read header file (a global character array) and retrieves
the bits per sample, number of channels, data size, sample rate and block align. The
function parameters to be passed are the pointers to the above parameters.
SYED TAHMID MAHBUB – PIC32 based audio player with microSD card and 12-bit audio out
• The function zeroDacs writes to the DAC to set the output to 1.024V (half of the voltage swing
range of the used DAC), effectively the zero point of the output audio. While the absolute value
assigned is not significant due to the output AC coupling, half-range is used for consistency.
• The ISRs (interrupt service routines) are the Timer 2 ISR and Timer 3 ISR. In the Timer 3 ISR,
all that is done is that a millisecond counter is incremented and the corresponding interrupt flag
is cleared. In the Timer 2 ISR, the audio playback happens. This is where the audio information
from the cyclic buffers is output to the DACs.
CONNECTORS/HEADERS
There were 4 headers/connectors used in this project: the microSD card connector/holder, the
3.5mm stereo audio connector, the ICSP (in circuit serial programming) header and the 3.3V serial-to-
USB converter (http://www.adafruit.com/product/954).
Since the current requirement was very small, I used the PICKIT3 to power the microcontroller
with no external power supply. The test circuit was designed on a solderless breadboard/whiteboard and
ran without any issues.
DOWNLOADS
• Entire project folder, including all source files, header files, and schematic:
https://drive.google.com/file/d/0B4SoPFPRNziHRGE0bVNZYV9uVjQ/view?usp=sharing
USEFUL LINKS
• Microchip Libraries for Applications
• AN1045: Implementing File I/O Functions Using Microchip's Memory Disk Drive File System
Library