STM32F407 Урок 05. Работаем с модулем VS1053.
Добрый вечер, уважаемые читатели ! Сегодня я хочу рассказать вам о "замечательном" во всех отношениях, модуле на базе VS1053b. Прошу прощения перед теми, кто ожидал про работу с SPI LCD, ее мы отложим на следующий раз. А сейчас, пока свежи воспоминания и хитрости при работе с этой микросхемой, прошу располагаться поудобнее.
Все началось с моего желания сделать жене приятное (кхм, порутчик ...) подарить ей вместо неплохого FM приемника GoldStar, но шипящего и требующего зарядки по 2 раза в день, новоиспеченное чудо "WiFi радио". С опухшей головой после вечера поисков на Али и подофигевший от цен - около 100 убитых енотов за готовый экземпляр я решил сделать свой (ну я же все-таки инжинер-програмер). Про радио на ESP8266 я слышал вскользь, причем не очень хорошее. Про свое творение я как-нибудь расскажу попозже, но столкнувшись с рядом проблем (задание адреса конкретным IP и заикания), почитал повнимательнее про Ka-Radio на ESP32 и понял, это то что мне нужно, к ней был заказан модуль на VS1053 для поддержки не только MP3, но и AAC потоков. Опять столкнулся с проблемами (кто бы мог подумать) как с самим модулем, так и с размерами (все это сложно было упихать в синезубую колонку размерами 15x8x7см). После штудирования форумов, решил заказать Wrover с I2S DAC PCM5102, а VS1053 оставить на опыты. Про Ka-Radio тоже планирую рассказать как запихну все в корпус.
И так, начнем. Кратко из даташита что умеет эта замечательная микросхема :
- проигрывать MP3 различные форматы
- проигрывать AAC
- проигрывать Ogg Vorbis
- проигрывать WAV (PCM и ADPCM)
- проигрывать FLAC
- проигрывать MIDI
- кодировать в IMA ADPCM / PCM
- кодировать в Ogg Vorbis
Ну как вам список ? У меня уже чешутся руки. Чтоб не стать жертвой жестокого облома, проверьте, соеденены ли 33 и 34 pin микросхемы, иначе рискуете наслаждаться тишиной (ненадолго может проскакивать звук) и припаяйте лучше танталовые конденсаторы на выход стабилизаторов 1,8V и 3,3V, иначе теплое аналоговое звучание будет обильно сдобрено цифровым хрустом. Мне попалась зеленая плата, у нее 9, 10, 11 и 12 pin висели в воздухе, хотя по даташиту должно быть через 100К на землю. Возможно последнее лишнее, но лучше перестраховаться. Фото, того что получилось у меня :
Запускаем наш, любимый Куб, выбираем наш MCU STM32F407VE, подключим осциляторы и не забудем включить отладку :
Подключим к I2C1 наш дисплей, скорость на Fast Mode, не забываем про DMA в TX режиме и NVIC. Кто забыл - ссылка на урок , коротко :
Обмен между STM32F407 и VS1053 будет происходить по SPI2, так как наши братья китайцы на SPI1 повесили светодиоды, режим FullDuplex Master, Prescaler в 256, Polarity в High, Phase в 2Edge, DMA_TX режим Normal Byte, прерывания включены глобальные и на передачу :
Настройки SD карты такие же, как и в прошлом уроке , кратко обозначу основное, режим SD 4 bits Wide bus, делитель на 3, DMA для SDIO_RX, прерывания глобальные и для DMA включены :
Еще, желательно, подключить, хотя бы, один светодиод, кнопки, если желаете интерактивного взаимодействия, обязательно обозначьте вход PA0, подтянутый к земле, для SD карты, также, настроим служебные пины VS1053, на выход XCS, XDCS, XRST и на вход DREQ :
Включите поддержку в FATFS SD card и определите в меню Platform Settings контакт PA0, по которому FatFS будет определять что карта всегда вставлена :
Выбираем в FreeRTOS CMSIS_V1. Меняем значение стэка у defaultTask на 1024, создаем новую задачу с таким-же стэком и OsPriorityNormal, чтобы при генерации пректа Куб не ругался на таймер - выделим самый слабый TIM14 в Timebase Source :
В Clock Configuration меняем кварц на 8MHz и максимальную частоту 168MHz.
В Project Manager увеличиваем размер кучи до 0x400 и стек 0x800, в ToolChain выбираем среду TrueStudio, папку куда генерировать, обзываем проект и жмакаем на генератор кода. Перед тем как открывать проект, скопируйте файлы обновленной библиотеки IC1306 (теперь не виснет при проблемах с дисплеем) в соответствующие папки Src и Inc своего проекта, иначе придется подключать их компиляцию в настройках проекта.
Теперь про подключение, кому очевидно, пролистайте дальше, экранчик I2C SSD1306 и модуль VS1053b подключаем так :
SSD1306 --- STM32F407
- GND ----- GND
- VDD ----- 3V3
- SCK ----- PB6
- SDA ----- PB7
VS1053 --- STM32F407
- XDCS --- PE2
- XCS ---- PE1
- XRST --- PE0
- DREQ --- PA5
- SCK ---- PB10
- MOSI --- PC3
- MISO --- PC2
- DGND --- GND
- 5V ----- 5V
Подключите библиотеку IC1306 для работы с экраном :
/* USER CODE BEGIN Includes */
#include <ic1306.h>
/* USER CODE END Includes */
В теле первой (главной) задачи, до бесконечного цикла, произведем инициализацию, очистку и вывод сообщения на дисплей :
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
/* init code for FATFS */
MX_FATFS_Init();
/* USER CODE BEGIN 5 */
display_init (50,0x22,0);
display_clear(0,7);
display_SmallPrint(0,0,"Hello");
/* Infinite loop */
Поехали дальше, объявим переменные для работы с SD картой :
/* USER CODE BEGIN 5 */FATFS SDFatFs;
char USER_Path[4];
DWORD fre_clust;
FATFS* fs;
char str[32] = {0,};
FRESULT res;
FIL MyFile;
uint32_t bytesread,lenfile;
uint8_t nbuf;
display_init (50,0x22,0);
Обьявим глобальный буфер и переменную, которую будем использовать в обеих задачах чтения с карты и вывода в vs1053:
/* USER CODE BEGIN PV */uint16_t buflen;
uint8_t bufmp3[65536];
/* USER CODE END PV */
Подождем немного пока пройдет внутренняя инициализация карты, смонтируем том, откроем файл, записанный в корень карты с названием 6.mp3, считаем из него полный буфер и выведем результат на дисплей, инициализация переменных нужна для последующего правильного чтения буфера в цикле :
display_SmallPrint(0,0,"Hello");
osDelay(500);
if(f_mount(&SDFatFs,(TCHAR const*)USER_Path,0)!=FR_OK)
{display_SmallPrint(0,7,"Error mount"); }
else
{display_SmallPrint(0,7,"OK mount");
if(f_open(&MyFile, "0:/6.mp3", FA_READ) != FR_OK)
{sprintf(str,"Err %d file open",res);
display_SmallPrint(0,7,str);}
else {display_SmallPrint(0,6,"OK file open");
}
res = f_read(&MyFile, bufmp3, 65536, (void *)&bytesread);
if((bytesread == 0) || (res != FR_OK))
{display_SmallPrint(0,7,"Error read");}
else
{
sprintf(str, "%d byte read", bytesread);
display_SmallPrint(0,7,str);
}
nbuf=0;
lenfile=bytesread;
}
/* Infinite loop */
Если все нормально - любуемся на 65536 считанных в буфер байт, теперь немного о VS1053. Обмен с ней идет по шине SPI, прижав, перед этим, к земле XCS для команд или XDCS для данных. Определим макросы для удобства :
/* USER CODE BEGIN PD */
#define VS1053_RESET_RES() HAL_GPIO_WritePin(GPIOE,XRST_Pin, GPIO_PIN_RESET);
#define VS1053_SET_RES() HAL_GPIO_WritePin(GPIOE,XRST_Pin, GPIO_PIN_SET);
#define VS1053_SET_XCS() HAL_GPIO_WritePin(GPIOE, XCS_Pin, GPIO_PIN_SET);
#define VS1053_RESET_XCS() HAL_GPIO_WritePin(GPIOE, XCS_Pin, GPIO_PIN_RESET);
#define VS1053_SET_XDCS() HAL_GPIO_WritePin(GPIOE, XDCS_Pin, GPIO_PIN_SET);
#define VS1053_RESET_XDCS() HAL_GPIO_WritePin(GPIOE, XDCS_Pin, GPIO_PIN_RESET);
#define VS1053_DREQ HAL_GPIO_ReadPin(DREQ_GPIO_Port, DREQ_Pin)
#define VS1053_CMD_READ 0x03 // Read Opcode
#define VS1053_CMD_WRITE 0x02 // Write Opcode
#define vs1053_MODE 0x00 // Mode control
#define vs1053_STATUS 0x01 // Status of vs1053b
#define vs1053_CLOCKF 0x03 // Clock freq + doubler
#define vs1053_AUDATA 0x05 // Misc. audio data
#define vs1053_VOL 0x0B // Volume control
#define SM_SDINEW 0x0800 // VS1002 native SPI modes 11 bit
#define SM_RESET 0x0004 // Soft Reset
#define SM_TESTS 0x0020 // Allow SDI tests
/* USER CODE END PD */
Напишем функции для записи данных и записи команд в VS1053 :
/* USER CODE BEGIN 4 */
void vs1053_writeData ( uint8_t data )
{
VS1053_SET_XCS();
VS1053_RESET_XDCS();
HAL_SPI_Transmit(&hspi2, &data, 1, 1);
while(!VS1053_DREQ){};
VS1053_SET_XDCS();
}
void vs1053_writeCommand(uint8_t addr,uint16_t data)
{ uint8_t temp[4];
VS1053_RESET_XCS();
VS1053_SET_XDCS();
temp[0]=VS1053_CMD_WRITE;
temp[1]=addr;// send address
temp[2]=data>>8;
temp[3]=data&0xff;
HAL_SPI_Transmit(&hspi2, &temp, 4, 2);
while(!VS1053_DREQ){__NOP();};
VS1053_SET_XCS();
}
/* USER CODE END 4 */
Функция чтения сначала посылает номер регистра, который мы хотим считать, потом читает два байта, склеивая их в 16-битное слово :
uint16_t vs1053_readData(uint8_t addr)
{
uint16_t tempdata = 0;
uint8_t temp[2];
VS1053_SET_XDCS();
VS1053_RESET_XCS();
temp[0]=VS1053_CMD_READ; // send read opcode 0x03
temp[1]=addr;
HAL_SPI_Transmit(&hspi2, &temp, 2, 1);
HAL_SPI_Receive(&hspi2,&temp,2,1);
tempdata = temp[0];
tempdata <<= 8;
tempdata |= temp[1];
while(!VS1053_DREQ){};
VS1053_SET_XCS();
return tempdata; // return data word
}
При пересылке пакетов данных по 32 байта надо следить за состоянием DREQ из основной программы :
void vs1053_send32(uint8_t *pData)
{
VS1053_RESET_XDCS();
HAL_SPI_Transmit(&hspi2,pData,32,1);
while (HAL_SPI_GetState(&hspi2)==HAL_SPI_STATE_BUSY_TX) {__NOP();}
VS1053_SET_XDCS();
}
Определим еще глобальные переменные, значение которых нужны сразу после сброса :
uint8_t bufmp3[65536];uint16_t status, mode;
/* USER CODE END PV */
Еще одна подпрограмма, которая выполняет сброс и начальную инициализацию VS1053 :
void vs1053_reset(void)
{
VS1053_SET_XDCS(); // XDCS = 1
VS1053_SET_XCS(); // nCS = 1
osDelay(2);
VS1053_RESET_RES(); // XRESET = 0
osDelay(2);
VS1053_SET_RES();
osDelay(5);
while(!VS1053_DREQ){__NOP();}
status=vs1053_readData(vs1053_STATUS);
mode=vs1053_readData(vs1053_MODE);
}
/* USER CODE END 4 */
Во второй задаче определим переменные и сразу таблицу команд для генерации и отключения синусоидального сигнала :
/* USER CODE BEGIN StartTask02 */
uint8_t sine_on[8]={ 0x53, 0xEF, 0x6E, 0x44, 0x00, 0x00, 0x00, 0x00 };
uint8_t sine_off[8]={0x45, 0x78, 0x69, 0x74, 0x00, 0x00, 0x00, 0x00 };
uint8_t chip_id;
uint16_t temp;
char str[32] = {0,};
Выполним сброс, выведем значение системных регистров VS1053 на экран, выполним программный сброс и выведем ID микросхемы :
char str[32] = {0,};
osDelay(500);
vs1053_reset();
sprintf(str,"Status VS %x",status);
display_SmallPrint(0,5,str);
sprintf(str,"Mode VS %x",mode);
display_SmallPrint(0,4,str);
vs1053_writeCommand(vs1053_MODE,SM_RESET|SM_SDINEW);
osDelay(500);
do
{
temp=vs1053_readData(vs1053_MODE);
} while((temp&SM_RESET)!=0);
vs1053_writeCommand(vs1053_MODE, SM_SDINEW);
temp=vs1053_readData(vs1053_STATUS);
chip_id=(temp >> 4)&0x0F;// 0 - VS1001, 1 - VS1011, 2 - VS1002, 3 - VS1003, 4 - VS1053
sprintf(str,"Chip_id %x",chip_id);
display_SmallPrint(0,3,str);
/* Infinite loop */
Полюбуемся на результат : Mode=4800 Status=48, по идее у нас он должно быть сразу после сброса 0x4000 и 0x000C. Скорее всего это связано с тормознутостью HAL и микросхема уже успевает изменить свое состояние. ID для VS1053 равен 4, тут все совпадает.
Теперь попробуем сгенерировать синус :
display_SmallPrint(0,3,str);
vs1053_writeCommand (vs1053_CLOCKF, 0x8000 | 0x0800);
vs1053_writeCommand(vs1053_VOL, 0x3030);
vs1053_writeCommand(vs1053_MODE,0x0806);
vs1053_writeCommand(vs1053_MODE,0x0820);
while(!VS1053_DREQ){__NOP();}
for(uint8_t i=0;i<8;i++)
vs1053_writeData(sine_on[i]);
osDelay(2000);
for(uint8_t i=0;i<8;i++)
vs1053_writeData(sine_off[i]);
/* Infinite loop */
Ну как, получили эстетическое наслаждение для ушей ? Тогда двигаем дальше, до бесконечного цикла мы должны переключить VS1053 в нормальный режим, увеличить скорость SPI для пересылки данных (если необходимо послать еще команды - надо будет уменьшать скорость), заполнить внутренний буфер микросхемы размером 2Kb. Из кода, думаю, все понятно:
vs1053_writeData(sine_off[i]);
vs1053_writeCommand(vs1053_MODE, SM_SDINEW);
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
if (HAL_SPI_Init(&hspi2) != HAL_OK)
{
display_SmallPrint(0,2,"SPI Speed Error");
}
buflen=0;
while(VS1053_DREQ)
{
vs1053_send32(&(bufmp3[buflen]));
buflen+=32;
}
/* Infinite loop */
В цикле второй задачи вставим такой код :
for(;;)
{
if(VS1053_DREQ)
{
vs1053_send32(&(bufmp3[buflen]));
buflen+=32;
buflen&=65535;
}
if(VS1053_DREQ)
{
vs1053_send32(&(bufmp3[buflen]));
buflen+=32;
buflen&=65535;
}
osDelay(1);
Зачем два раза одно и тоже? Я никогда не повторяюсь, не повторяюсь. ;-) Если битрейт вашего mp3 выше 256Kbs вы просто не успеете передать нужное колличество байт за 1мс - тик, пока выполняется задача. Слушать начало мелодии в цикле - занятие на любителя, поэтому возвращаемя к первой задаче и добавляем в цикл последовательное чтение буфера, то в первую, то во вторую половину, в зависимости от того, из какой части буфера вторая задача перекидывает данные в VS1053 :
for(;;)
{
if((nbuf==0)&&(buflen>32768))
{
res = f_read(&MyFile, bufmp3, 32768, (void *)&bytesread);
nbuf=1;
lenfile+=bytesread;
}
if((nbuf==1)&&(buflen<32768))
{
res = f_read(&MyFile, &(bufmp3[32768]), 32768, (void *)&bytesread);
nbuf=0;
lenfile+=bytesread;
}
if((bytesread == 0) || (res != FR_OK))
{display_SmallPrint(0,2,"End of file");}
else
{sprintf(str, "%d total read", lenfile);
display_SmallPrint(0,1,str);
}
osDelay(100);
}
/* USER CODE END 5 */
Теперь можно и дослушать до конца. Просто проиграть первый попавшийся aac и ogg (найти музыку в найтивном формате ogg та еще проблема) у меня сразу не получилось. На этом пока все с VS1053, если будут пожелания, попробуем проиграть MIDI. Следующий урок планирую подключению SPI дисплея ST7735 и превращению вашего МК в осцилографический пробник.
Адрес для контактов : imax9@narod.ru
Если вам понравились мои работы и вы желаете поддержать сайт - сделайте дотацию.
При копировании статьи – обязательна ссылка на авторство и источник. Без разрешения автора копирование запрещено.
© Максим Ильин 2020г.