Tutorial: Playing MP3 Files with VS1053B and FRDM Board
This tutorial is about adding music and sound capabilities to the Freescale Freedom board, and to have a lot of fun with it.
Join the DZone community and get the full member experience.
Join For FreeI want to make some noise with this post!!! This tutorial is about adding music and sound capabilities to the Freescale Freedom board, and to have a lot of fun with it :-). I need this ability for a larger project working on for a while. But I thought I share that sub-part how to play sound files. So with this tutorial I can turn my Freescale Freedom board into a music or sound player :-). And adding sounds is a cool way for any project, and as the music is stored on an SD card it fits easily hours of music or sounds.
Outline
In this project I’m using the following components:
- Freescale Freedom Board FRDM-KL25Z (http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=FRDM-KL25Z)
- Adafruit Music Maker Shield (Part Number #1788) has the VLSI VS1053B encoder on it, and comes with a stereo amplifier (https://www.adafruit.com/products/1788). Adafruit has a version of that shield without the amplifier too (https://www.adafruit.com/products/1790). Or you can use any VS1053 board plus SD card adapter for this project.
- Adafruit 3W 4 Ohm Speaker Set (Part Number #1669) (https://www.adafruit.com/product/1669). This is an optional part, you can use the line out/headphone connector too.
- Eclipse with Processor Expert. You can use either CodeWarrior for MCU10, Kinetis Design Studio or your own DIY Eclipse Tool Chain.
- Extra Processor Expert components from https://sourceforge.net/projects/mcuoneclipse/, see “McuOnEclipse Releases on SourceForge“.
In this tutorial I show how to play MP3 (or any other supported) sound files with the shield, using the FRDM-KL25Z board with Eclipse, GNU gcc and Processor Expert. You should be able to apply the instructions to any other board or IDE too.
In the software project I use these major components:
- FreeRTOSrealtime operating system. This is an optional part, but makes everything easier, e.g. playing files and still doing other work or serving a command line interface.
- FatFS(FAT File System) to read from the SD card. This is used to read the sound files from the micro SD card on the board.
- Shellcomponent to implement a command line interface. With this I have a serial connection to the board (SCI over USB, using the OpenSDA debug chip), can change the volume, play files, etc.
- SPI (SPIMaster_LDD) is used to communicate both with the SD card and the VS1053B, sharing the same SPI bus.
At the end of this tutorial there is a link to the complete project on GitHub.
Hardware
The Adafruit shield comes with integrated 3W amplifiers and has a head phone/line out. 3W/4 Ohm speakers can be directly attached to the shield. Otherwise the line out can be used to connect to an amplifier.
The schematics and layout of the shield can be found here: https://learn.adafruit.com/adafruit-music-maker-shield-vs1053-mp3-wav-wave-ogg-vorbis-player/downloads
The shield has the VLSI VS1053B codec chip on it, able to play Ogg Vorbis, MP3/MP2/MP1, MP4, AAC, WMA, FLAC, WAV/PCM, MIDI. I’m using it with MP3 files which are read from the micro-SD card socket on the board. VS1053B has a serial and a SPI interface which does all the encoding and heavy work.
The SD card has all the needed level shifter, so it would be possible to use that shield with 5V logic too.
The picture below shows my board with headers mounted. If you plan to add more boards to your project, I recommend to use stackable headers instead (https://www.adafruit.com/products/85).
Adafruit Music Maker MP3 Shield with headers added and SD Card inserted
I would like the software to be aware if an SD card is inserted or removed. Therefore I need to check the ‘SD card detect pin’. Because the SD Card Detection pin is not connected to the Arduino header, I wired it to PTD5 on the FRDM-KL25Z:
Consequently, the PTD5 pin is used in the SD_Card component:
Project Creation
This step depends on the IDE you are using. For Kinetis Design Studio use the menu File > New Kinetis Design Studio Project, then select the microcontroller of your board. Make sure that you select Processor Expert as option for the project.
:!: You cannot use the Kinetis SDK, as it breaks compatibility with all existing Processor Expert components :-(. Technically it would be possible to use the SDK, but it would be much more complicated to build such a project, as you cannot use all the McuOnEclipse components. I have made most of my components compatible with the Kinetis SDK, but all the built-in Processor Expert components (BitIO, SPI, SCI, etc) are *not* compatible with the SDK.
Components
In this section we add and configure the Processor Expert components used for this project. Some components will add more components (WAIT, UTILITY, …) which I do not need to discuss here, as they can use the default settings.
CPU Component
I configure the CPU to use the maximum clock frequency. For the FRDM-KL25Z I set it up to 48 MHz. For this I enable the external crystal with 8 MHz:
The MCG (Multipurpose Clock Generator) is set to PEE mode to produce a 96 MHz PLL output frequency:
Then I can configure the CPU to maximum speed (in my case to 48 MHz core clock and 24 MHz bus clock):
As we are going to use the NMI (PTA4) pin later for the SD card as chip/slave select, I disable this pin in the CPU properties:
This completes the CPU component setup.
FreeRTOS Component
Add the FreeRTOS component to the project. Verify the settings for your processor (below shown for the FRDM-KL25Z which is an ARM Cortex-M0+):
Because we are creating two task plus the idle task, make sure there is enough RTOS heap size (8 KByte should be plenty):
LED Components
To show status information, add LED components to the project. For the FRDM-KL25Z I add the Red (PTB18) and Green LED (PTB19). I cannot use the Blue LED (PTD1) as it would conflict with the SPI clock pin.
Shell Component
To communicate with the host machine, I’m have added the Shell component (see “A Shell for the Freedom KL25Z Board“):
Adding the Shell component will ask me to add add a connection method, for which I use the Serial/Asynchroserial component. This component deals with the SCI (Serial Communication Interface), and I need to configure it so it talks with the OpenSDA chip on the FRDM board. I configure it to use interrupts with reasonable buffers (I use 64 bytes for each). For the FRDM-KL25Z the Rx pin is on PTA1 and the Tx pin is on PTA2. And I configure it to use 38400 baud (that baud needs to match with the terminal connection settings):
Having the shell component in the system allows me to add command line functionality to most of my components, including the FreeRTOS or the file system (FatFS).
File System Components
To access the files on the micro-SD card, I’m using FatFS (see “FatFs with Kinetis” for a detailed tutorial on FatFS). Adding the FAT_FileSystem will ask for at least two needed subcomponents: the drive (SD1, SD_Card component) and if I want to write to the file system a component which gives me date/time:
As we do not have a real-time clock (RTC) on the board, I’m using the GenericTimeDate component with initial settings:
For the SD Card device, I configure it to use two different speed modes (Slow Baud Rate Mode 0 and 1), That it has a Slave Select pin plus a Card detection pin which is Low Active:
On the FRDM-KL25Z the SD card slave select pin is on PTA4:
The SD Card Detection pin is on pin PTD5:
As we are going to use the SPI bus shared between the SD card and the VS1053B, I need to enable three hooks in the SD_Card component:
- OnActivate(): this hook is called before accessing the SPI bus. A perfect place to use a semaphore/mutex to make sure mutual exclusive access to the bus.
- OnDeactivate(): this hook is called after releasing the SPI bus. Here I can release the semaphore/mutex again.
- OnBlockReceived() is called after a successful SPI block transfer, when I have received the data. I need to check a flag this way to wait until the transaction is over.
Make sure that these three components are enabled in the SD_Card component (use the context menu to enable them, so they do not have the ‘x’ icon decorator on it):
The SD card socket will pull this pin to LOW, but is floating otherwise. So I need to pull up this pin either with a resistor to Vcc on the board, or I enable the microcontroller internal pull-up resistors. For this I add the Init_GPIO component and configure it to enable the pull-up for PTD5:
Next I need to configure the SPI driver. According to the schematics the shield is using SPI0, with MISO on PTD3, MOSI on PTD2 and the SPI clock on PTD1. SD card SPI communication is special and needs two different speed modes: 375 kHz slow and a faster (usually 12 MHz) clock. So I have two attribute sets, with two clock rates:
I can configure the two clocks using the ‘…‘ control (klick into the clock rate value) and configure it to use a list of values. The question is: which values?
The SD card needs to run initially at 375 kHz, and typically it can go up to 12 MHz. To keep things simple, I don’t want to switch the SPI clock speed between the SD card and the VS1053B. So what is the max SPI speed of the VS1053B after reset? The data sheet on page 22 is not very clear on this, but http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=11&t=47 has the answer: it is CLKI/4. According to the Adafruit schematics, the VS1053B uses a 12.288 MHz clock. To be safe, I use a maximum clock of 3 MHz both for the SD card and the VS1053B:
The SD_Card component calls as well for a ‘Timeout’ component, which I configure to use the RTOS and a maximum of two counters (which will be enough):
VS1053B Pins
The VS1053B uses an SPI interface (MISO, MOSI, CLK) which is shared with the SD card (more about this later). The VS1053B uses three more pins for which use BitIO components:
- MCS: this is the VS1053B chip select pin, LOW active
- DCS: this is the VS1053B data select pin, LOW active
- DREQ: this is the VS1053B data request interrupt pin. I’m not using that pin so far, but I connect it to an input pin for now, and could use it later with an interrupt pin.
The MCS is configured as output pin on PTC9, with initial value HIGH (1):
Similar the DSC Pin, but to PTC8:
The DREQ pin I have configured as input pin on PTA12:
This completes the hardware setup. As everything should be configured, I can generate the Processor Expert Code:
Software Modules
Time to write the software. For this we have to add a few files to my project:
- Application.c and Application.h: entry point of my application, initialize the modules and starts the RTOS scheduler.
- Shell.c and Shell.h: this implements my command line interface. The command line parser is implemented as RTOS task.
- VS1053.c and VS1035.h is the driver to the VS1053B device with a command line interface to play files or to check the status of the device.
Application Module
The application interface is very simple:
/*
* Application.h
*
* Author: Erich Styger
*/
#ifndef APPLICATION_H_
#define APPLICATION_H_
/*!
* \brief Run the application
*/
void APP_Run(void);
#endif /* APPLICATION_H_ */
In APP_Run() the shell and the VS1053 driver get initialized, and then the RTOS started:
/*
* Application.c
* Author: Erich Styger
*/
#include "Application.h"
#include "FRTOS1.h"
#include "Shell.h"
#include "VS1053.h"
void APP_Run(void) {
SHELL_Init(); /* initialize shell */
VS_Init(); /* initialize VS1053B module */
FRTOS1_vTaskStartScheduler();
}
Shell Module
The shell interface only needs a function to initialize it:
/*
* Shell.h
* Author: Erich Styger
*/
#ifndef SHELL_H_
#define SHELL_H_
/*! \brief Serial driver initialization */
void SHELL_Init(void);
#endif /* SHELL_H_ */
The Shell uses a table of command line handlers in CmdParserTable[]. This table is processed in the task created by the SHELL_Init() function. The task checks if the SD card gets inserted or removed and shows this with the green and red LED:
/*
* Shell.c
* Author: Erich Styger
*/
#include "Shell.h"
#include "Application.h"
#include "FRTOS1.h"
#include "CLS1.h"
#include "LEDR.h"
#include "LEDG.h"
#include "FAT1.h"
#include "VS1053.h"
static const CLS1_ParseCommandCallback CmdParserTable[] =
{
CLS1_ParseCommand,
#if FRTOS1_PARSE_COMMAND_ENABLED
FRTOS1_ParseCommand,
#endif
#if FAT1_PARSE_COMMAND_ENABLED
FAT1_ParseCommand,
#endif
VS_ParseCommand,
NULL /* sentinel */
};
static portTASK_FUNCTION(ShellTask, pvParameters) {
bool cardMounted = FALSE;
static FAT1_FATFS fileSystemObject;
unsigned char buf[48];
(void)pvParameters; /* not used */
buf[0] = '\0';
(void)CLS1_ParseWithCommandTable((unsigned char*)CLS1_CMD_HELP, CLS1_GetStdio(), CmdParserTable);
FAT1_Init();
for(;;) {
(void)FAT1_CheckCardPresence(&cardMounted,
"0" /* drive */, &fileSystemObject, CLS1_GetStdio());
if (cardMounted) {
LEDG_On();
LEDR_Off();
} else {
LEDG_Off();
LEDR_On();
}
(void)CLS1_ReadAndParseWithCommandTable(buf, sizeof(buf), CLS1_GetStdio(), CmdParserTable);
FRTOS1_vTaskDelay(50/portTICK_RATE_MS);
}
}
void SHELL_Init(void) {
if (FRTOS1_xTaskCreate(ShellTask, "Shell", configMINIMAL_STACK_SIZE+200, NULL, tskIDLE_PRIORITY+1, NULL) != pdPASS) {
for(;;){} /* error */
}
}
VS1053 Driver Module
The VS1053 driver offers methods to read/write device registers and a command line parser. Additionally it offers entry points for the three SD_Card hooks we have created earlier:
/*
* VS1053.h
*
* Author: Erich Styger
*/
#ifndef VS1053_H_
#define VS1053_H_
/* VS1053 Registers */
#define VS_MODE 0x00
#define VS_STATUS 0x01
#define VS_BASS 0x02
#define VS_CLOCKF 0x03
#define VS_DECODE_TIME 0x04
#define VS_AUDATA 0x05
#define VS_WRAM 0x06
#define VS_WRAMADDR 0x07
#define VS_HDAT0 0x08
#define VS_HDAT1 0x09
#define VS_AIADDR 0x0A
#define VS_VOL 0x0B
#define VS_AICTRL0 0x0C
#define VS_AICTRL1 0x0D
#define VS_AICTRL2 0x0E
#define VS_AICTRL3 0x0F
#define VS_IO_DDR 0xC017
#define VS_IO_IDATA 0xC018
#define VS_IO_ODATA 0xC019
#include "CLS1.h" /* shell interface */
/*!
* \brief Module command line parser
* \param cmd Pointer to the command
* \param handled Return value if the command has been handled by parser
* \param io Shell standard I/O handle
* \return Error code, ERR_OK if everything is OK
*/
uint8_t VS_ParseCommand(const unsigned char *cmd, bool *handled, const CLS1_StdIOType *io);
/*!
* \brief Event hook called before activating/accessing the SPI bus
*/
void VS_OnSPIActivate(void);
/*!
* \brief Event hook called after activating/accessing the SPI bus
*/
void VS_OnSPIDeactivate(void);
/*!
* \brief Event hook handler, called from an interrupt when we have received the MISO SPI data from the device.
*/
void VS_OnSPIBlockReceived(void);
/*!
* \brief Plays a song file
* \param fileName file name of the song
* \param io Shell standard I/O for messages, NULL for no message printing
* \return Error code, ERR_OK if everything is OK
*/
uint8_t VS_PlaySong(const uint8_t *fileName, const CLS1_StdIOType *io);
/*!
* \brief Read a device register
* \param reg Register address to read
* \param value Pointer where to store the register value
* \return Error code, ERR_OK if everything is OK
*/
uint8_t VS_ReadRegister(uint8_t reg, uint16_t *value);
/*!
* \brief Write a device register
* \param reg Register address to write
* \param value VAlue to write to the register
* \return Error code, ERR_OK if everything is OK
*/
uint8_t VS_WriteRegister(uint8_t reg, uint16_t value);
/*!
* \brief Driver initialization.
*/
void VS_Init(void);
/*!
* \brief Driver deinitalization
*/
void VS_Deinit(void);
#endif /* VS1053_H_ */
In the VS1053 driver the hooks are used to get and release a semaphore for mutual access of the SPI bus. The low level SPI
/*
* VS1053.c
* Author: Erich Styger
*/
#include "VS1053.h"
#include "MCS.h" /* low active chip select */
#include "DCS.h" /* data control select */
#include "DREQ.h" /* data request, HIGH means I can send data */
#include "SM1.h"
#include "UTIL1.h"
#include "CLS1.h"
#include "FAT1.h"
#include "FRTOS1.h"
/* macros to select device and to switch between data and control mode */
#define VS_CONTROL_MODE_ON() DCS_SetVal(); MCS_ClrVal()
#define VS_CONTROL_MODE_OFF() MCS_SetVal()
#define VS_DATA_MODE_ON() MCS_SetVal(); DCS_ClrVal()
#define VS_DATA_MODE_OFF() DCS_SetVal()
#define VS_DATA_SIZE_BYTES 32 /* always 32 bytes of data */
static volatile bool VS_SPIDataReceivedFlag = FALSE;
static SemaphoreHandle_t spiSem;
void VS_OnSPIBlockReceived(void) {
VS_SPIDataReceivedFlag = TRUE;
}
void VS_OnSPIActivate(void) {
FRTOS1_xSemaphoreTakeRecursive(spiSem, portMAX_DELAY);
}
void VS_OnSPIDeactivate(void) {
FRTOS1_xSemaphoreGiveRecursive(spiSem);
}
static void VS_SPI_WRITE(unsigned char write) {
unsigned char dummy;
VS_SPIDataReceivedFlag = FALSE;
(void)SM1_ReceiveBlock(SM1_DeviceData, &dummy, sizeof(dummy));
(void)SM1_SendBlock(SM1_DeviceData, &write, sizeof(write));
while(!VS_SPIDataReceivedFlag){}
}
static void VS_SPI_WRITE_READ(unsigned char write, unsigned char *readP) {
VS_SPIDataReceivedFlag = FALSE;
(void)SM1_ReceiveBlock(SM1_DeviceData, readP, 1);
(void)SM1_SendBlock(SM1_DeviceData, &write, 1);
while(!VS_SPIDataReceivedFlag){}
}
static bool VS_Ready(void) {
return DREQ_GetVal()!=0; /* HIGH: ready to receive data */
}
uint8_t VS_WriteRegister(uint8_t reg, uint16_t value) {
VS_OnSPIActivate();
VS_CONTROL_MODE_ON();
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
/* send instruction byte, address byte and 16bit data word */
VS_SPI_WRITE(0x02); /* write instruction */
VS_SPI_WRITE(reg);
VS_SPI_WRITE(value>>8); /* high byte first */
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
VS_SPI_WRITE(value&0xff); /* low byte */
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
VS_CONTROL_MODE_OFF();
VS_OnSPIDeactivate();
return ERR_OK;
}
uint8_t VS_ReadRegister(uint8_t reg, uint16_t *value) {
uint8_t val0, val1;
VS_OnSPIActivate();
VS_CONTROL_MODE_ON();
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
/* send instruction byte, address byte and 16bit data word */
VS_SPI_WRITE(0x03); /* read instruction */
VS_SPI_WRITE(reg);
VS_SPI_WRITE_READ(0xff, &val0); /* read first byte */
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
VS_SPI_WRITE_READ(0xff, &val1); /* read second byte */
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
VS_CONTROL_MODE_OFF();
*value = (val0<<8)|val1;
VS_OnSPIDeactivate();
return ERR_OK;
}
static uint8_t VS_SendZeroes(size_t nof) {
size_t chunk;
VS_OnSPIActivate();
VS_DATA_MODE_ON();
while(nof!=0) {
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
if (nof>VS_DATA_SIZE_BYTES) { /* max 32 bytes */
chunk = VS_DATA_SIZE_BYTES;
} else {
chunk = nof;
}
nof -= chunk;
while(chunk>0) {
VS_SPI_WRITE(0);
chunk--;
}
}
VS_DATA_MODE_OFF();
VS_OnSPIDeactivate();
return ERR_OK;
}
uint8_t VS_SetVolume(uint16_t leftright) {
/* max volume: 0x0000, total silence: 0xFEFE, 0xFFFF analog power down */
return VS_WriteRegister(VS_VOL, leftright);
}
uint8_t VS_SendData(uint8_t *data, size_t dataSize) {
if (dataSize!=VS_DATA_SIZE_BYTES) {
return ERR_FAULT; /* need 32 bytes! */
}
VS_OnSPIActivate();
VS_DATA_MODE_ON();
while(dataSize>0) {
while(!VS_Ready()) {
/* wait until pin goes high so we know it is ready */
}
VS_SPI_WRITE(*data++);
dataSize--;
}
VS_DATA_MODE_OFF();
VS_OnSPIDeactivate();
return ERR_OK;
}
uint8_t VS_StartSong(void) {
return VS_SendZeroes(10);
}
uint8_t VS_StopSong(void) {
return VS_SendZeroes(2048);
}
uint8_t VS_PlaySong(const uint8_t *fileName, const CLS1_StdIOType *io) {
UINT bytesRead;
uint8_t readBuf[32];
uint8_t res = ERR_OK;
static FIL fp;
if (io!=NULL) {
CLS1_SendStr("Playing file '", io->stdOut);
CLS1_SendStr(fileName, io->stdOut);
CLS1_SendStr("'\r\n", io->stdOut);
}
if (FAT1_open(&fp, fileName, FA_READ)!=FR_OK) {
if (io!=NULL) {
CLS1_SendStr("ERR: Failed to open song file\r\n", io->stdErr);
}
return ERR_FAILED;
}
for(;;) { /* breaks */
bytesRead = 0;
if (FAT1_read(&fp, readBuf, sizeof(readBuf), &bytesRead)!=FR_OK) {
if (io!=NULL) {
CLS1_SendStr("ERR: Failed to read file\r\n", io->stdErr);
}
res = ERR_FAILED;
break;
}
if (bytesRead==0) { /* end of file? */
break;
}
while(!VS_Ready()) {
FRTOS1_vTaskDelay(10/portTICK_RATE_MS);
}
VS_SendData(readBuf, sizeof(readBuf));
}
/* closing file */
(void)FAT1_close(&fp);
VS_StartSong();
return res;
}
static uint8_t PrintStatus(const CLS1_StdIOType *io) {
uint8_t buf[24];
uint16_t val;
CLS1_SendStatusStr((unsigned char*)"VS1053", (unsigned char*)"\r\n", io->stdOut);
if (VS_ReadRegister(VS_MODE, &val)==ERR_OK) {
UTIL1_strcpy(buf, sizeof(buf), "0x");
UTIL1_strcatNum16Hex(buf, sizeof(buf), val);
UTIL1_strcat(buf, sizeof(buf), "\r\n");
} else {
UTIL1_strcpy(buf, sizeof(buf), "ERROR\r\n");
}
CLS1_SendStatusStr((unsigned char*)" MODE", buf, io->stdOut);
if (VS_ReadRegister(VS_STATUS, &val)==ERR_OK) {
UTIL1_strcpy(buf, sizeof(buf), "0x");
UTIL1_strcatNum16Hex(buf, sizeof(buf), val);
UTIL1_strcat(buf, sizeof(buf), "\r\n");
} else {
UTIL1_strcpy(buf, sizeof(buf), "ERROR\r\n");
}
CLS1_SendStatusStr((unsigned char*)" STATUS", buf, io->stdOut);
if (VS_ReadRegister(VS_CLOCKF, &val)==ERR_OK) {
UTIL1_strcpy(buf, sizeof(buf), "0x");
UTIL1_strcatNum16Hex(buf, sizeof(buf), val);
UTIL1_strcat(buf, sizeof(buf), "\r\n");
} else {
UTIL1_strcpy(buf, sizeof(buf), "ERROR\r\n");
}
CLS1_SendStatusStr((unsigned char*)" CLOCKF", buf, io->stdOut);
if (VS_ReadRegister(VS_VOL, &val)==ERR_OK) {
UTIL1_strcpy(buf, sizeof(buf), "0x");
UTIL1_strcatNum16Hex(buf, sizeof(buf), val);
UTIL1_strcat(buf, sizeof(buf), "\r\n");
} else {
UTIL1_strcpy(buf, sizeof(buf), "ERROR\r\n");
}
CLS1_SendStatusStr((unsigned char*)" VOLUME", buf, io->stdOut);
return ERR_OK;
}
static uint8_t PrintHelp(const CLS1_StdIOType *io) {
CLS1_SendHelpStr((unsigned char*)"VS1053", (unsigned char*)"Group of VSL1053 commands\r\n", io->stdOut);
CLS1_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Print help or status information\r\n", io->stdOut);
CLS1_SendHelpStr((unsigned char*)" volume ", (unsigned char*)"Set volume, full: 0x0000, 0xFEFE silence\r\n", io->stdOut);
CLS1_SendHelpStr((unsigned char*)" play ", (unsigned char*)"Play song file\r\n", io->stdOut);
return ERR_OK;
}
uint8_t VS_ParseCommand(const unsigned char *cmd, bool *handled, const CLS1_StdIOType *io)
{
const uint8_t *p;
uint32_t val32u;
if (UTIL1_strcmp((char*)cmd, CLS1_CMD_HELP)==0 || UTIL1_strcmp((char*)cmd, "VS1053 help")==0) {
*handled = TRUE;
return PrintHelp(io);
} else if ((UTIL1_strcmp((char*)cmd, CLS1_CMD_STATUS)==0) || (UTIL1_strcmp((char*)cmd, "VS1053 status")==0)) {
*handled = TRUE;
return PrintStatus(io);
} else if (UTIL1_strncmp((char*)cmd, "VS1053 volume ", sizeof("VS1053 volume ")-1)==0) {
*handled = TRUE;
p = cmd+sizeof("VS1053 volume ")-1;
if (UTIL1_xatoi(&p, &val32u)==ERR_OK) {
return VS_SetVolume((uint16_t)val32u);
} else {
CLS1_SendStr("Failed reading volume", io->stdErr);
return ERR_FAILED;
}
} else if (UTIL1_strncmp((char*)cmd, "VS1053 play ", sizeof("VS1053 play ")-1)==0) {
*handled = TRUE;
p = cmd+sizeof("VS1053 play ")-1;
return VS_PlaySong(p, io);
}
return ERR_OK;
}
void VS_Deinit(void) {
/* nothing needed */
}
void VS_Init(void) {
MCS_SetVal(); /* chip select is low active, deselect it */
DCS_SetVal(); /* data mode is low active, deselect data mode */
VS_SPIDataReceivedFlag = FALSE; /* Initialization */
spiSem = FRTOS1_xSemaphoreCreateRecursiveMutex();
if (spiSem==NULL) { /* creation failed? */
for(;;);
}
FRTOS1_vQueueAddToRegistry(spiSem, "SpiSem");
}
Glue Code
Now I need to add some glue code to bring the driver and the Processor Expert generated code together. The first place is to forward the event hooks in Events.c:
- Line 38: Add an include of “VS1053.h” as interface used in Events.c
- Line 102/103: From the RTOS tick interrupt hook, add ticks to the timeout module (for the timeout handling) and ticks to the TimeDate driver.
- Line 149: Forward SPI block transfer complete.
- Line 167: Forward SPI bus activation event.
- Line 185: Forward SPI bus deactivation event.
/* ###################################################################
** Filename : Events.c
** Project : FRDM-KL25Z_MusicMaker
** Processor : MKL25Z128VLK4
** Component : Events
** Version : Driver 01.00
** Compiler : GNU C Compiler
** Date/Time : 2014-11-18, 13:26, # CodeGen: 0
** Abstract :
** This is user's event module.
** Put your event handler code here.
** Settings :
** Contents :
** Cpu_OnNMIINT - void Cpu_OnNMIINT(void);
**
** ###################################################################*/
/*!
** @file Events.c
** @version 01.00
** @brief
** This is user's event module.
** Put your event handler code here.
*/
/*!
** @addtogroup Events_module Events module documentation
** @{
*/
/* MODULE Events */
#include "Cpu.h"
#include "Events.h"
#ifdef __cplusplus
extern "C" {
#endif
/* User includes (#include below this line is not maintained by Processor Expert) */
#include "VS1053.h"
/*
** ===================================================================
** Event : Cpu_OnNMIINT (module Events)
**
** Component : Cpu [MKL25Z128LK4]
*/
/*!
** @brief
** This event is called when the Non maskable interrupt had
** occurred. This event is automatically enabled when the [NMI
** interrupt] property is set to 'Enabled'.
*/
/* ===================================================================*/
void Cpu_OnNMIINT(void)
{
/* Write your code here ... */
}
/*
** ===================================================================
** Event : FRTOS1_vApplicationStackOverflowHook (module Events)
**
** Component : FRTOS1 [FreeRTOS]
** Description :
** if enabled, this hook will be called in case of a stack
** overflow.
** Parameters :
** NAME - DESCRIPTION
** pxTask - Task handle
** * pcTaskName - Pointer to task name
** Returns : Nothing
** ===================================================================
*/
void FRTOS1_vApplicationStackOverflowHook(xTaskHandle pxTask, char *pcTaskName)
{
/* This will get called if a stack overflow is detected during the context
switch. Set configCHECK_FOR_STACK_OVERFLOWS to 2 to also check for stack
problems within nested interrupts, but only do this for debug purposes as
it will increase the context switch time. */
(void)pxTask;
(void)pcTaskName;
taskDISABLE_INTERRUPTS();
/* Write your code here ... */
for(;;) {}
}
/*
** ===================================================================
** Event : FRTOS1_vApplicationTickHook (module Events)
**
** Component : FRTOS1 [FreeRTOS]
** Description :
** If enabled, this hook will be called by the RTOS for every
** tick increment.
** Parameters : None
** Returns : Nothing
** ===================================================================
*/
void FRTOS1_vApplicationTickHook(void)
{
/* Called for every RTOS tick. */
/* Write your code here ... */
TMOUT1_AddTick();
TmDt1_AddTick();
}
/*
** ===================================================================
** Event : FRTOS1_vApplicationMallocFailedHook (module Events)
**
** Component : FRTOS1 [FreeRTOS]
** Description :
** If enabled, the RTOS will call this hook in case memory
** allocation failed.
** Parameters : None
** Returns : Nothing
** ===================================================================
*/
void FRTOS1_vApplicationMallocFailedHook(void)
{
/* Called if a call to pvPortMalloc() fails because there is insufficient
free memory available in the FreeRTOS heap. pvPortMalloc() is called
internally by FreeRTOS API functions that create tasks, queues, software
timers, and semaphores. The size of the FreeRTOS heap is set by the
configTOTAL_HEAP_SIZE configuration constant in FreeRTOSConfig.h. */
taskDISABLE_INTERRUPTS();
/* Write your code here ... */
for(;;) {}
}
/*
** ===================================================================
** Event : SD1_OnBlockReceived (module SD1)
**
** Component : SM1 [SPIMaster_LDD]
*/
/*!
** @brief
** This event is called when the requested number of data is
** moved to the input buffer. This method is available only if
** the ReceiveBlock method is enabled.
** @param
** UserDataPtr - Pointer to the user or
** RTOS specific data. The pointer is passed
** as the parameter of Init method.
*/
/* ===================================================================*/
void SD1_OnBlockReceived(LDD_TUserData *UserDataPtr)
{
VS_OnSPIBlockReceived();
}
/*
** ===================================================================
** Event : SD1_OnActivate (module Events)
**
** Component : SD1 [SD_Card]
** Description :
** Event called when Activate() method is called. This gives an
** opportunity to the application to synchronize access to a
** shared bus.
** Parameters : None
** Returns : Nothing
** ===================================================================
*/
void SD1_OnActivate(void)
{
VS_OnSPIActivate();
}
/*
** ===================================================================
** Event : SD1_OnDeactivate (module Events)
**
** Component : SD1 [SD_Card]
** Description :
** Event called when Deactivate() method is called. This gives
** an opportunity to the application to synchronize access to a
** shared bus.
** Parameters : None
** Returns : Nothing
** ===================================================================
*/
void SD1_OnDeactivate(void)
{
VS_OnSPIDeactivate();
}
/* END Events */
#ifdef __cplusplus
} /* extern "C" */
#endif
/*!
** @}
*/
/*
** ###################################################################
**
** This file was created by Processor Expert 10.4 [05.11]
** for the Freescale Kinetis series of microcontrollers.
**
** ###################################################################
*/
And in main.c I add the include the application interface (line 65) and call the APP_Run() function (line 78):
/* ###################################################################
** Filename : main.c
** Project : FRDM-KL25Z_MusicMaker
** Processor : MKL25Z128VLK4
** Version : Driver 01.01
** Compiler : GNU C Compiler
** Date/Time : 2014-11-18, 13:26, # CodeGen: 0
** Abstract :
** Main module.
** This module contains user's application code.
** Settings :
** Contents :
** No public methods
**
** ###################################################################*/
/*!
** @file main.c
** @version 01.01
** @brief
** Main module.
** This module contains user's application code.
*/
/*!
** @addtogroup main_module main module documentation
** @{
*/
/* MODULE main */
/* Including needed modules to compile this module/procedure */
#include "Cpu.h"
#include "Events.h"
#include "FRTOS1.h"
#include "LEDR.h"
#include "LEDpin1.h"
#include "BitIoLdd1.h"
#include "LEDG.h"
#include "LEDpin2.h"
#include "BitIoLdd2.h"
#include "TmDt1.h"
#include "WAIT1.h"
#include "TMOUT1.h"
#include "SM1.h"
#include "FAT1.h"
#include "SD1.h"
#include "SS2.h"
#include "CD2.h"
#include "UTIL1.h"
#include "AS1.h"
#include "ASerialLdd1.h"
#include "CLS1.h"
#include "CS1.h"
#include "PTD.h"
#include "MCS.h"
#include "BitIoLdd4.h"
#include "DCS.h"
#include "BitIoLdd6.h"
#include "DREQ.h"
#include "BitIoLdd5.h"
/* Including shared modules, which are used for whole project */
#include "PE_Types.h"
#include "PE_Error.h"
#include "PE_Const.h"
#include "IO_Map.h"
/* User includes (#include below this line is not maintained by Processor Expert) */
#include "Application.h"
/*lint -save -e970 Disable MISRA rule (6.3) checking. */
int main(void)
/*lint -restore Enable MISRA rule (6.3) checking. */
{
/* Write your local variable definition here */
/*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/
PE_low_level_init();
/*** End of Processor Expert internal initialization. ***/
/* Write your code here */
APP_Run();
/*** Don't write any code pass this line, or it will be deleted during code generation. ***/
/*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS component. DON'T MODIFY THIS CODE!!! ***/
#ifdef PEX_RTOS_START
PEX_RTOS_START(); /* Startup of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of RTOS startup code. ***/
/*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/
for(;;){}
/*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/
} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/
/* END main */
/*!
** @}
*/
/*
** ###################################################################
**
** This file was created by Processor Expert 10.4 [05.11]
** for the Freescale Kinetis series of microcontrollers.
**
** ###################################################################
*/
That’s it :-) Time to compile and try it out!
Usage
At startup the shell shows the help menu (or with the ‘help’) command:
With
FAT1 dir
I can list the content of the SD card:
With
VS1053 status
I can list the status and registers of the device.
VS1053 volume 0x2020
sets the volume level for left and right to 0x20. 0x00 is maximum volume level.
And with
VS1053 play dave.mp3
a mp3 file gets played :-)
Summary
With this project, I can make some noise and impress my family with HAL9000 and other geeky sounds :-). The shield has even more capabilities: it would be possible to record sound and store it on the SD card. Or transform it into a MIDI drum machine.
The potential is really huge, now it is all about using the Wave Shield for the next project: A Halloween scream box? (sorry, too late! But what about a Christmas scream box?) A talking clock? Or an arcade machine? I have many more ideas, will see ;-).
The sources and project is available on GitHub: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-KL25Z/FRDM-KL25Z_MusicMaker. This is a Kinetis Design Studio project, but can be easily ported to other tool chains (see Export and Import Processor Expert Component Settings).
Happy Blasting :-)
Links:
- Adafruit Music Maker Shield: https://www.adafruit.com/products/1788
- VS1053B data sheet: https://www.adafruit.com/datasheets/vs1053.pdf
- https://learn.sparkfun.com/tutorials/mp3-player-shield-hookup
- https://github.com/maniacbug/VS1053
- http://garagelab.com/profiles/blogs/tutorial-mp3-player-shield-with-arduino
Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments