SDMMCWavPlayer

Introduction


EasyPIC3 development board used for prototyping

Here is a simple project to create a Wav file player, with files being read from a Secure Digital or Multi Media Card (SD or MMC). The files are read using the SDFileSystem library and the bytes are stored in a circular buffer. A timer interrupt reads bytes from the buffer and sends them to a R-2R digital to analogue converter (DAC) on Port D. A basic filter is used to remove the high frequency noise from the output. In the prototype version of this project, the output is then fed to an external amplifier, although a simple op-amp amplifier could be constructed as part of the project.

The files are read sequentially in the order that they appear on the card. After the last file has been played, the code stops. The header of each file is checked to ensure that it is a Wav file of the correct format - only 8-bit, mono files of 16kHz sampling rate can be played with the attached code, although it would be easy to adapt the code to play files of a lower sampling rate. With the PIC running at 32MHz, it is not possible to go to higher sampling rates using this code.

An alphanumeric LCD is used to display the name of the file being played. It also shows a progress bar indicating the fraction of the file that has been played. There is much scope for adding to the sophistication of the code in this area.

Two LEDS on Port E are also used in the code - that on PORTE.0 indicates that a buffer under-run has occurred whilst reading a file, whilst that on PORTE.1 indicates that the buffer is full. The circular buffer is used to prevent glitches in the output whilst the SD card accesses a new cluster.

During testing, the sound quality was initially quite poor. However, by adjusting the resistors in the R-2R ladder carefully, the sound quality was much improved.

Extensions to the project might include:

  • the ability to select the file to play using buttons
  • the inclusion of play, pause and stop buttons
  • the option to play files in a random order

All of the above would be relatively straightforward. There is ample code space still available, since on a PIC18F452 the code only occupies 6677 bytes of the 32768 available.

I hope that this simple project gives you some ideas.

Steven Wright

Circuit Diagram

Project Code

{
********************************************************************************
*  Name    : WavPlayer.BAS                                                     *
*  Author  : S Wright                                                          *
*  Notice  : Copyright (c) 2006 S Wright                                       *
*          : All Rights Reserved                                               *
*  Date    : 25/10/2006                                                        *
*  Version : 1.0                                                               *
*  Notes   :                                                                   *
*          :                                                                   *
********************************************************************************
}
Device = 18F452
Clock = 32
Config OSC = HSPLL

#option SD_SPI = MSSP
#option SD_SUPPORT_SUB_DIRECTORIES = True
#option LCD_DATA = PORTB.4
#option LCD_RS = PORTB.2
#option LCD_EN = PORTB.3
#option LCD_INIT_DELAY = 300
Include "SDFileSystem.bas"
Include "LCD.bas"

Const BufferSize = 500                  // buffer to avoid glitches
Const TimerReload = 65036               // alter for different sample rates

Dim Timer As TMR1L.AsWord               // alias to Timer1
Dim TimerOn As T1CON.Booleans(0)        // start and stop
Dim TimerEnabled As PIE1.Booleans(0)    // enable interrupts
Dim TimerInterrupt As PIR1.Booleans(0)  // interrupt flag

Dim File As String(13)
Dim WritePointer, ReadPointer As Word   // read/write pointers in
Dim Buffer(BufferSize) As Byte          // circular buffer
Dim DataSize As LongWord                // size of WAV data in file
Dim Progress As LongWord                // increment progress bar when zero
Dim ProgressIncrement As LongWord       // progress bar step

{
********************************************************************************
* Name    : OnTimer (Interupt)                                                 *
* Purpose : Sends bytes to R-2R DAC on PORT D to play file                     *
********************************************************************************
}   
Interrupt OnTimer()                     // ISR
   Timer = TimerReload
   Save(0)                              // context save
   If ReadPointer = WritePointer Then   // buffer empty
      PORTE.0 = 1                       // set buffer empty indicator
   Else
      Inc(ReadPointer)                  // increment read pointer
      If ReadPointer = BufferSize Then  // if end of buffer then...
         ReadPointer = 0                // move back to beginning of buffer 
      EndIf
      PORTD = Buffer(ReadPointer)       // move byte to PORTD (R-2R ladder DAC)
   EndIf
   TimerInterrupt = False               // clear interrupt flag
   Restore
End Interrupt
{
********************************************************************************
* Name    : ReadFmt                                                            *
* Purpose : Reads header of WAV file and checks compatibility                  *
********************************************************************************
}   
Function ReadFmt() As Boolean           // read fmt block of WAV file
Dim Index As Byte
   ReadFmt = True
   Index = 0
   Repeat                               // skip over RIFF header
      SD.ReadLongWord
      Inc(Index)
   Until Index = 3
   If SD.ReadChar <> "f" Then           // check in fmt block
      ReadFmt = False
   EndIf
   If SD.ReadChar <> "m" Then
      ReadFmt = False
   EndIf
   If SD.ReadChar <> "t" Then
      ReadFmt = False
   EndIf
   If SD.ReadChar <> " " Then
      ReadFmt = False
   EndIf
   SD.ReadLongWord                      // skip over fmt size
   If SD.ReadWord <> 1 Then             // check PCM (un-compressed) file
      ReadFmt = False
   EndIf
   If SD.ReadWord <> 1 Then             // check 1 channel (mono) file
      ReadFmt = False
   EndIf
   If SD.ReadLongWord <> 16000 Then     // check sample rate (16kHz)
      ReadFmt = False
   EndIf
   Index = 0
   Repeat                               // skip to data size
      SD.ReadByte
      Inc(Index)
   Until Index = 12
   DataSize = SD.ReadLongWord
   SD.ReadLongWord                      // skip to data section
   ProgressIncrement = DataSize / 16
End Function
{
********************************************************************************
* Name    : ProgressBar                                                        *
* Purpose : Updates progress bar during playback                               *
********************************************************************************
}   
Sub ProgressBar()
   Dec(Progress)
   If Progress = 0 Then
      Progress = ProgressIncrement
      LCD.Write("=")
   EndIf
End Sub
{
********************************************************************************
* Name    : SoftStart                                                          *
* Purpose : Ramps DAC output to new value to avoid clicks                      *
********************************************************************************
}   
Sub SoftStart(pNewValue As Byte)
Dim Value As Byte
   Value = PORTD
   Repeat
      If Value < pNewValue Then
         Inc(Value)
      ElseIf Value > pNewValue Then
         Dec(Value)
      EndIf
      PORTD = Value
      DelayUS(20)
   Until Value = pNewValue
End Sub
{
********************************************************************************
* Name    : PlayFile                                                           *
* Purpose : Plays file                                                         *
********************************************************************************
}   
Sub PlayFile(pFile As String)
Dim TempWritePointer As Word
   LCD.Cls
   LCD.Writeat(1, 1, "{ ", pFile, " }")
   LCD.WriteAt(2, 1, "  OPENING FILE  ")
   SD.OpenFile(pFile)                      
   Timer = TimerReload                  // set timer one
   ReadPointer = 0                      // initialise pointers
   WritePointer = 0                     
   If Not(ReadFmt()) Then               // incompatible file format
      LCD.WriteAt(2, 1, "WRONG FILE TYPE!")
   Else
      LCD.WriteAt(2, 1, "----------------")      
      Progress = ProgressIncrement
      LCD.MoveCursor(2,1)
      Repeat                            // fill buffer initially
         Inc(WritePointer)
         Buffer(WritePointer) = SD.ReadByte
         Dec(DataSize)
         ProgressBar()
      Until (WritePointer = BufferSize - 1) Or SD.EOF   
      SoftStart(Buffer(1))              // ramp up to first byte to play
      TimerOn = True                    // start timer - play starts
      Repeat
         TempWritePointer = WritePointer        // use a temp pointer in order
         Inc(TempWritePointer)                  // to prevent interrupt 
         If TempWritePointer = BufferSize Then  // detecting a buffer underun
            TempWritePointer = 0                // when buffer is full
         EndIf
         While TempWritePointer = ReadPointer
            PORTE.1 = 1
         Wend 
         PORTE.1 = 0
         WritePointer = TempWritePointer
         Buffer(WritePointer) = SD.ReadByte     // write next byte to buffer
         Dec(DataSize)
         ProgressBar()
      Until SD.EOF Or (DataSize = 0)  
   EndIf 
   SD.CloseFile
   Repeat                               // wait until buffer is empty
   Until ReadPointer = WritePointer
   TimerOn = False                      // stop timer
   PORTE = 0
End Sub


// Main program...
TRISD = 0
TRISE = 0
PORTE = 0
SoftStart(127)
TimerOn = False                         // stop timer initially
Enable(OnTimer)                         // assign the interrupt handler
TimerEnabled = True                     // enable timer interrupts

LCD.Cls
LCD.WriteAt(1, 1, "PLEASE INSERT SD")
Repeat
Until SD.Init(spiOscDiv4)

Repeat
   File = SD.Dir(dirNext, sdFile)       // search for next file on SD card
   If File <> Null Then                 // file found
      PlayFile(File)
   EndIf
Until File = Null

LCD.Cls
LCD.WriteAt(1, 5, "FINISHED")