Статьи

Учебник. Воспроизведение файлов MP3 с платой VS1053B и FRDM

Я хочу пошуметь с этим постом !!! Этот урок посвящен добавлению музыкальных и звуковых возможностей на доску Freescale Freedom и получению от этого удовольствия :-). Мне нужна эта способность для большого проекта, над которым мы работаем некоторое время. Но я думал, что разделяю эту часть, как играть звуковые файлы. Итак, с помощью этого урока я могу превратить свою доску Freescale Freedom в музыкальный или звуковой плеер :-). А добавление звуков — это отличный способ для любого проекта, и, поскольку музыка хранится на SD-карте, она легко умещается в часах музыки или звуков.

MP3-плеер с FRDM-KL25Z

MP3-плеер с FRDM-KL25Z и Adafruit Music Maker MP3 Shield

Контур

В этом проекте я использую следующие компоненты:

  1. Freescale Freedom Board FRDM-KL25Z ( http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=FRDM-KL25Z )
  2. Щит Adafruit Music Maker Shield (номер детали # 1788) имеет кодировщик VLSI VS1053B и поставляется со стереоусилителем ( https://www.adafruit.com/products/1788 ). У Adafruit есть версия этого щита без усилителя ( https://www.adafruit.com/products/1790 ). Или вы можете использовать любую плату VS1053 плюс адаптер SD-карты для этого проекта.
  3. Adafruit 3W 4 Ом Комплект АС (номер # 1669) ( https://www.adafruit.com/product/1669 ). Это дополнительная часть, вы также можете использовать разъем линейного выхода / наушников.
  4. Затмение с экспертом по процессору . Вы можете использовать CodeWarrior для MCU10 , Kinetis Design Studio или свою собственную цепочку инструментов Eclipse .
  5. Дополнительные компоненты Expert Processor Expert из https://sourceforge.net/projects/mcuoneclipse/ , см. « Выпуски McuOnEclipse на SourceForge ».
Adafruit Music Maker MP3 Shield с громкоговорителями

Adafruit Music Maker MP3 Shield с громкоговорителями

В этом уроке я покажу, как воспроизводить MP3 (или любые другие поддерживаемые) звуковые файлы со щитом, используя плату FRDM-KL25Z с Eclipse, GNU gcc и Processor Expert. Вы должны быть в состоянии применить инструкции к любой другой доске или IDE тоже.

В программном проекте я использую следующие основные компоненты:

Компоненты Processor Expert в Eclipse

Компоненты Processor Expert в Eclipse

  1. Операционная система реального времени FreeRTOS . Это необязательная часть, но упрощает все, например, воспроизведение файлов и выполнение другой работы или обслуживание интерфейса командной строки.
  2. FatFS (файловая система FAT) для чтения с SD-карты. Это используется для чтения звуковых файлов с карты micro SD на плате.
  3. Компонент оболочки для реализации интерфейса командной строки. Благодаря этому у меня есть последовательное соединение с платой (SCI через USB, с использованиемотладочного чипа OpenSDA ), я могу изменять громкость, воспроизводить файлы и т. Д.
  4. SPI (SPIMaster_LDD) используется для связи как с SD-картой, так и с VS1053B, совместно использующих одну и ту же шину SPI.

В конце этого урока есть ссылка на полный проект на GitHub.

аппаратные средства

Щит Adafruit поставляется со встроенными усилителями мощностью 3 Вт и имеет головной телефон / линейный выход. Динамики 3 Вт / 4 Ом могут быть непосредственно прикреплены к экрану. В противном случае линейный выход можно использовать для подключения к усилителю.

В схемные и расположение экрана можно найти здесь: https://learn.adafruit.com/adafruit-music-maker-shield-vs1053-mp3-wav-wave-ogg-vorbis-player/downloads

MusicMakerShield Layout (Источник Adafruit)

MusicMakerShield Layout (Источник Adafruit)

Экран имеет кодек VLSI VS1053B , способный воспроизводить Ogg Vorbis, MP3 / MP2 / MP1, MP4, AAC, WMA, FLAC, WAV / PCM, MIDI. Я использую его с файлами MP3, которые считываются из гнезда для карты micro-SD на плате. VS1053B имеет последовательный интерфейс и интерфейс SPI, который выполняет всю кодировку и тяжелую работу.

На SD-карте есть все необходимые регуляторы уровня, так что можно было бы использовать этот экран с 5-вольтовой логикой.

На рисунке ниже показана моя доска с установленными заголовками. Если вы планируете добавить больше плат в свой проект, я рекомендую вместо этого использовать стекируемые заголовки ( https://www.adafruit.com/products/85 ).

Adafruit Music Maker MP3 Shield

Adafruit Music Maker MP3 Shield с добавленными заголовками и вставленной SD-картой

Я хотел бы, чтобы программное обеспечение знало, вставлена ​​или извлечена SD-карта. Поэтому мне нужно проверить «PIN-код обнаружения SD-карты». Поскольку вывод определения SD-карты не подключен к разъему Arduino, я подключил его к PTD5 на FRDM-KL25Z:

SD-карта обнаруживает проводку

SD-карта обнаруживает проводку

Следовательно, вывод PTD5 используется в компоненте SD_Card:

SD-карта определить Pin в свойствах

SD-карта определить Pin в свойствах

Создание проекта

Этот шаг зависит от используемой вами среды IDE. Для Kinetis Design Studio используйте меню Файл> Новый проект Kinetis Design Studio , затем выберите микроконтроллер вашей платы. Убедитесь, что вы выбрали Processor Expert в качестве опции для проекта.

Создание проекта Kinetis Design Studio с помощью Processor Expert

Создание проекта Kinetis Design Studio с помощью Processor Expert

❗ Вы не можете использовать Kinetis SDK, так как он нарушает совместимость со всеми существующими компонентами Processor Expert :-(. Технически было бы возможно использовать SDK, но было бы намного сложнее создать такой проект, так как вы не можете используйте все компоненты McuOnEclipse. Я сделал большинство своих компонентов совместимыми с Kinetis SDK, но все встроенные компоненты Processor Expert (BitIO, SPI, SCI и т. д.) * не * совместимы с SDK.

Составные части

В этом разделе мы добавляем и настраиваем компоненты Processor Expert, используемые для этого проекта. Некоторые компоненты добавят больше компонентов (WAIT, UTILITY,…), которые мне не нужно обсуждать здесь, так как они могут использовать настройки по умолчанию.

Компонент CPU

Я настраиваю процессор на использование максимальной тактовой частоты. Для FRDM-KL25Z я установил его на 48 МГц. Для этого я включаю внешний кристалл с 8 МГц:

Конфигурация внешних часов

Конфигурация внешних часов

MCG (многоцелевой генератор тактовой частоты) установлен в режим PEE для получения выходной частоты ФАПЧ 96 МГц:

MCG с ФАПЧ 96 МГц

MCG с ФАПЧ 96 МГц

Затем я могу настроить процессор на максимальную скорость (в моем случае тактовая частота ядра 48 МГц и тактовая частота шины 24 МГц):

Настройки шины и процессора

Настройки шины и процессора

Поскольку мы будем использовать контакт NMI (PTA4) позже для карты SD в качестве выбора чипа / слэйва, я отключу этот вывод в свойствах процессора:

Надежный контакт NMI, используемый для SD-карты

Отключенный вывод NMI, используемый для SD-карты

На этом настройка компонента ЦП завершена.

Компонент FreeRTOS

Добавьте компонент FreeRTOS в проект. Проверьте настройки вашего процессора (ниже показано для FRDM-KL25Z, который является ARM Cortex-M0 +):

Настройки FreeRTOS

Настройки FreeRTOS

Поскольку мы создаем две задачи и задачу простоя, убедитесь, что имеется достаточно кучи RTOS (8 КБ должно быть достаточно):

Размер кучи FreeRTOS

Размер кучи FreeRTOS

Светодиодные компоненты

Чтобы показать информацию о состоянии, добавьте светодиодные компоненты в проект. Для FRDM-KL25Z я добавляю красный (PTB18) и зеленый светодиод (PTB19). Я не могу использовать синий светодиод (PTD1), поскольку он конфликтует с тактовым выводом SPI.

Настройка светодиода (красный)

Настройка светодиода (красный)

Компонент оболочки

Для связи с хост-машиной я добавил компонент Shell (см. « Оболочка для платы Freedom KL25Z »):

Компонент оболочки

Компонент оболочки

При добавлении компонента Shell мне будет предложено добавить метод подключения, для которого я использую компонент Serial / Asynchroserial. Этот компонент имеет дело с SCI (интерфейс последовательной связи), и мне нужно настроить его так, чтобы он взаимодействовал с чипом OpenSDA на плате FRDM. Я настраиваю его на использование прерываний с разумными буферами (я использую 64 байта для каждого). Для FRDM-KL25Z вывод Rx находится на PTA1, а вывод Tx на PTA2. И я настраиваю его на использование 38400 бод (эта скорость должна соответствовать настройкам подключения терминала):

Конфигурация SCI для OpenSDA на FRDM-KL25Z

Конфигурация SCI для OpenSDA на FRDM-KL25Z

Наличие компонента оболочки в системе позволяет мне добавлять функции командной строки к большинству моих компонентов, включая FreeRTOS или файловую систему (FatFS).

Компоненты файловой системы

Для доступа к файлам на карте micro-SD я использую FatFS ( подробное руководство по FatFS см. В « FatFs with Kinetis »). При добавлении FAT_FileSystem потребуются как минимум два необходимых подкомпонента: диск (SD1, компонент SD_Card) и, если я хочу записать в файловую систему компонент, который дает мне дату / время:

Настройки компонента FatFS

Настройки компонента FatFS

Поскольку на плате нет часов реального времени (RTC), я использую компонент GenericTimeDate с начальными настройками:

Настройки компонента GenericTimeDate

Настройки компонента GenericTimeDate

Для устройства с картой SD я настраиваю его на использование двух разных режимов скорости ( режим медленной скорости передачи 0 и 1 ), что на нем есть вывод выбора ведомого плюс вывод обнаружения карты, который имеет низкий уровень активности :

Настройки компонента SD-карты

Настройки компонента SD-карты

На FRDM-KL25Z вывод выбора ведомого SD-карты находится на PTA4:

SD Card Slave Выбор PIN-кода

SD Card Slave Выбор PIN-кода

Контакт SD Card Detection находится на контакте PTD5:

PIN-код обнаружения карты

PIN-код обнаружения карты

Поскольку мы собираемся использовать шину SPI, совместно используемую между SD-картой и VS1053B, мне нужно включить три компонента в компоненте SD_Card:

  1. OnActivate () : эта ловушка вызывается перед доступом к шине SPI. Идеальное место для использования семафора / мьютекса, чтобы обеспечить взаимоисключающий доступ к шине.
  2. OnDeactivate () : эта ловушка вызывается после освобождения шины SPI. Здесь я могу снова выпустить семафор / мьютекс.
  3. OnBlockReceived () вызывается после успешной передачи блока SPI, когда я получил данные. Мне нужно проверить флажок таким образом, чтобы дождаться завершения транзакции.

Убедитесь, что эти три компонента включены в компоненте SD_Card (используйте контекстное меню, чтобы включить их, чтобы на них не было декоратора значков ‘x’):

Подготовленная SD_Card для работы с общей шиной SPI

Подготовленная SD_Card для работы с общей шиной SPI

Гнездо для SD-карты потянет этот штырь в НИЗКОЕ, но в противном случае оно будет плавать. Поэтому мне нужно подтянуть этот вывод либо резистором к Vcc на плате, либо я включаю внутренние подтягивающие резисторы микроконтроллера. Для этого я добавляю компонент Init_GPIO и настраиваю его для включения подтягивания для PTD5:

Вытягивание штифта PTD5

Вытягивание штифта PTD5

Далее мне нужно настроить драйвер SPI. Согласно схемам экран использует SPI0, с MISO на PTD3, MOSI на PTD2 и тактовым сигналом SPI на PTD1. Связь SDI с SD-картой является особенной и требует двух различных режимов скорости: медленная 375 кГц и более быстрая (обычно 12 МГц) тактовая частота. Итак, у меня есть два набора атрибутов с двумя тактовыми частотами:

Настройки SPI

Настройки SPI

Я могу настроить два такта с помощью элемента управления « » (кликните на значении тактовой частоты) и настроить его на использование списка значений . Вопрос в том, какие ценности?

Первоначально SD-карта должна работать на частоте 375 кГц, и обычно она может достигать 12 МГц. Для простоты я не хочу переключать тактовую частоту SPI между SD-картой и VS1053B. Так какова максимальная скорость SPI VS1053B после сброса? Лист данных на странице 22 не очень ясен по этому вопросу, но http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=11&t=47 имеет ответ: это CLKI / 4. Согласно схеме Adafruit, VS1053B использует тактовую частоту 12,288 МГц. Для безопасности я использую максимальную частоту 3 МГц как для SD-карты, так и для VS1053B:Два разных часов для SD Card SPI

Компонент SD_Card также вызывает компонент Timeout, который я настраиваю для использования ОСРВ и максимум двух счетчиков (этого будет достаточно):

Настройки тайм-аута

Настройки тайм-аута

Булавки VS1053B

VS1053B использует интерфейс SPI (MISO, MOSI, CLK), который используется совместно с SD-картой (подробнее об этом позже). VS1053B использует еще три вывода, для которых используются компоненты BitIO :

  1. MCS : это вывод выбора микросхемы VS1053B, НИЗКИЙ активный
  2. DCS : это вывод выбора данных VS1053B, НИЗКИЙ активный
  3. DREQ : это вывод прерывания запроса данных VS1053B. Пока я не использую этот вывод, но сейчас я подключаю его к входному выводу и могу использовать позже с выводом прерывания.

MCS сконфигурирован как выходной контакт на PTC9 с начальным значением HIGH (1):

Конфигурация контактов MCS

Конфигурация контактов MCS

Аналог DSC Pin, но для PTC8:

Конфигурация контактов DCS

DCS Pin Configuration

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

Конфигурация DREQ Pin

DREQ Pin Configuration

This completes the hardware setup. As everything should be configured, I can generate the 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.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 Главная Помощь

Shell Main Help

With

FAT1 dir

I can list the content of the SD card:

Справочник с SD-карты

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: