Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Tutorial: Playing MP3 Files with VS1053B and FRDM Board

DZone's Guide to

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.

· IoT Zone
Free Resource

Download Red Hat’s blueprint for building an open IoT platform—open source from cloud to gateways to devices.

I 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.

MP3 Player with FRDM-KL25Z

MP3 Player with FRDM-KL25Z and Adafruit Music Maker MP3 Shield


Outline

In this project I’m using the following components:

  1. Freescale Freedom Board FRDM-KL25Z (http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=FRDM-KL25Z)
  2. 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.
  3. 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.
  4. Eclipse with Processor Expert. You can use either CodeWarrior for MCU10, Kinetis Design Studio or your own DIY Eclipse Tool Chain.
  5. Extra Processor Expert components from https://sourceforge.net/projects/mcuoneclipse/, see “McuOnEclipse Releases on SourceForge“.
Adafruit Music Maker MP3 Shield with Loud Speakers

Adafruit Music Maker MP3 Shield with Loud Speakers

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:

Processor Expert Components in Eclipse

Processor Expert Components in Eclipse

  1. 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.
  2. 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.
  3. 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.
  4. 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

MusicMakerShield Layout (Source Adafruit)

MusicMakerShield Layout (Source Adafruit)

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

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:

SD Card Detect Wiring

SD Card Detect Wiring

Consequently, the PTD5 pin is used in the SD_Card component:

SD Card Detect Pin in Properties

SD Card Detect Pin in Properties


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.

Kinetis Design Studio Project Creation with Processor Expert

Kinetis Design Studio Project Creation with Processor Expert


:!: 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:

External Clock Configuration

External Clock Configuration

The MCG (Multipurpose Clock Generator) is set to PEE mode to produce a 96 MHz PLL output frequency:

MCG with a PLL of 96 MHz

MCG with a PLL of 96 MHz

Then I can configure the CPU to maximum speed (in my case to 48 MHz core clock and 24 MHz bus clock):

Bus and CPU Clock Settings

Bus and CPU Clock Settings

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:

Diabled NMI pin as used for SD card

Disabled NMI pin as used for SD card

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+):

FreeRTOS Settings

FreeRTOS Settings

Because we are creating two task plus the idle task, make sure there is enough RTOS heap size (8 KByte should be plenty):

FreeRTOS Heap Size

FreeRTOS Heap Size


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.

LED (red) Setting

LED (red) Setting


Shell Component

To communicate with the host machine, I’m have added the Shell component (see “A Shell for the Freedom KL25Z Board“):

Shell Component

Shell Component

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):

SCI Configuration for OpenSDA on FRDM-KL25Z

SCI Configuration for OpenSDA on FRDM-KL25Z

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:

FatFS Component Settings

FatFS Component Settings

As we do not have a real-time clock (RTC) on the board, I’m using the GenericTimeDate component with initial settings:

GenericTimeDate Component Settings

GenericTimeDate Component 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:

SD Card Component Settings

SD Card Component Settings

On the FRDM-KL25Z the SD card slave select pin is on PTA4:

SD Card Slave Selection Pin

SD Card Slave Selection Pin

The SD Card Detection pin is on pin PTD5:

Card Detection Pin

Card Detection Pin

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:

  1. 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.
  2. OnDeactivate(): this hook is called after releasing the SPI bus. Here I can release the semaphore/mutex again.
  3. 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):

Prepared SD_Card for shared SPI Bus operation

Prepared SD_Card for shared SPI Bus operation

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:

Pulling up the PTD5 pin

Pulling up the PTD5 pin

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:

SPI Settings

SPI Settings

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:Two different Clocks for SD Card SPI

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):

Timeout Settings

Timeout Settings


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:

  1. MCS: this is the VS1053B chip select pin, LOW active
  2. DCS: this is the VS1053B data select pin, LOW active
  3. 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):

MCS Pin Configuration

MCS Pin Configuration

Similar the DSC Pin, but to PTC8:

DCS Pin Configuration

DCS Pin Configuration

The DREQ pin I have configured as input pin on PTA12:

DREQ Pin Configuration

DREQ Pin Configuration

This completes the hardware setup. As everything should be configured, I can generate the Processor Expert Code:

Generating Processor Expert Code

Generating Processor Expert Code


Software Modules

Time to write the software. For this we have to add a few files to my project:

Application Source Files

Application Source Files

  • 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:

  1. Line 38: Add an include of “VS1053.h” as interface used in Events.c
  2. 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.
  3. Line 149: Forward SPI block transfer complete.
  4. Line 167: Forward SPI bus activation event.
  5. 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:

Shell Main Help

Shell Main Help

With

FAT1 dir

I can list the content of the SD card:

Directory from SD Card

Directory from 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:

Build an open IoT platform with Red Hat—keep it flexible with open source software.

Topics:
eclipse ,open source ,tips and tricks ,tools & methods ,ides

Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}