Программный декодер MP3 на STM32F10x. Демопроект

В целях тренировки при освоении нового для меня микроконтроллера решил реализовать аудиоплейер. Поставленную задачу выполнил успешно, велосипед был изобретён и даже смог поехать. Результаты работы хочу представить вашему вниманию.

Воспроизведением MP3 нас сегодня не удивишь. Практически любой гаджет, имеющий на борту продвинутый микроконтроллер под операционной системой, может работать как MP3-проигрыватель. В помощь микроконтроллеру разработаны десятки чипов, декодирующих MP3 (и прочие популярные форматы) на аппаратном уровне. Насколько сложно декодировать MP3 программно? И каких усилий это будет стоить? Давайте разберёмся.

Как обычно, проект должен начинаться с обзора литературы (интернета)

1. Десятки тысяч карманных, переносных, стационарных плейеров, телефонов, планшетов, телевизоров и т.д. Данную категорию не рассматриваем, ничего полезного для нашего проекта там нет…

2. Конструкторы «собери сам» типа мастеркитовского и китайских поделок. Ничего против не имею, но это не для нас. Уже всё реализовано, нередко с использованием чипа VLSI…

3. Довольно много ссылок на самопальные проекты собрано здесь. Схемы преимущественно используют аппаратный декодер от VLSI. Вот ещё нашёл пару аппаратных декодеров: тут и тут.

4. ARM MP3/AAC Player. Неплохой вариант проигрывателя, выполненный на основе контроллера AT91SAM7S256. Внешний ЦАП. Умеет программно декодировать потоки MP3 (декодер Helix) и AAC. Исходники открыты. Уже ближе к теме.

5. Проект от AVRman. Описание проекта не нашёл, вероятно это демо-пример для очередной отладочной платы. Проект достаточно серьёзный, ориентирован на использование STM32, uC/OS-II, GUI и TouchPanel. Программное декодирование MP3 при помощи библиотеки Helix. Рекомендуется для ознакомления. Собственно, данный пример может закрыть все основные вопросы по воспроизведению MP3.

6. Демопроект от RTOS Кокоса. Ещё один неплохой пример для подражания. Основан на программном декодере libmad.

7. Lossless Digital Audio Player TRAXMOD. Качественно проработанный и законченный самопальный MP3-проигрыватель. Также может воспроизводить flac и mod форматы. Программное декодирование выполняется контроллером PIC32.

… и т.д.

Итак, самодельные устройства можно разделить на 2 категории: с аппаратным и программным декодером. Первые обеспечивают хорошее качество звука, поддерживают несколько форматов (зависит от типа чипа), имеют относительно простую реализацию, не требовательны к производительности контроллера. Вторая группа требует серьёзных вычислительных затрат (и соответственно мощного контроллера), но в то же время предоставляет больший простор творчеству.

В дальнейшем мы воспользуемся указанными наработками. Наш «велосипед» будем не изобретать, а собирать из представленных в свободном доступе «запчастей», что существенно упростит нашу задачу.

Постановка задачи
  • минимальное количество корпусов микросхем; в идеале — один — микроконтроллер должен декодировать MP3-поток без помощи внешнего аппаратного декодера;
  • будем использовать встроенный в микроконтроллер ЦАП (проект обучающий, да простят нам 12-битный звук любители HI-FI);
  • файлы будем хранить на карте памяти SD/SDHC;
  • программа должна быть максимально простой, читабельной, с возможностью расширения её функциональности; из этого следует следующий пункт:
  • в качестве основы программы должна использоваться операционная система реального времени (RTOS);
  • проект должен иметь минимальный бюджет; будем пользоваться только свободным ПО и подручными макетными платами;
  • так как проект обучающий, то остановимся на этапе «играющей и поющей» макетной платы и не будем требовать от него получение законченного изделия в виде коробочки с кнопочками, лампочками, экранчиком и батарейкой.

Цель нашей работы — «пощупать» RTOS, научиться читать файлы с карты SD, и сделать наработки кода с возможностью его применения в других проектах.

Используемый инструментарий
1. IDE Eclipse Juno;
собственно, при использовании компилятора ARM GCC версия IDE значения не имеет; можно также использовать свой любимый Кокос , Code::Blocks, emIDE и т.д.

2. toolchain yagarto версии 4.7.2;
можно использовать любую свежую версию компилятора ARM GCC.

3. сервер отладки OpenOCD версии 0.6.0 + интерфейс FTDI-JTAG;
Бонусом идёт дополнительный COM-порт. Данная связка работает медленнее, чем фирменные JTAG-отладчики, но мне для работы обычно хватает.

4. макетная плата Olimex STM32-P103;
ничем особо не примечательна, имеет один пользовательский светодиод, одну пользовательскую кнопку, кнопку сброса, один разъём RS-232 и т.д. Всё-в-одном-по-одному. Но главное, есть разъём для карты памяти SD. К сожалению, предлагаемый по умолчанию контроллер нам не подходит — слишком мало памяти RAM (забегая вперёд, скажу, что используемому декодеру Helix необходимо минимум 24 кБ памяти). Поэтому плату пришлось немного доработать — заменить контроллер на STM32F105RC. Впрочем, вместо него пойдёт любой подходящий по корпусу контроллер с RAM 64 кБ и тактовой частотой 72 МГц.


Приступим…
Начнём с выбора и запуска операционной системы. Разных RTOS для микроконтроллеров придумано уже настолько много, что может возникнуть проблема выбора.
Недолго мучаясь, для изучения выбираем малораспространённую ось CoOS от CooCOX. Операционка бесплатна, снабжена описанием и примерами, и, главное, заточена под контроллер ARM Cortex-M. Неплохой перевод фирменного описания CoOS присутствует здесь, за что отдельное большое спасибо его автору. Рекомендую к ознакомлению.
Если совсем не понятно, о чём там идёт речь, можно почитать серию статей про FreeRTOS в Сообществе EasyElectronics.ru. Если же слово «мьютекс» не вводит в ступор, продолжаем…

В качестве примера к CoOS прилагается и MP3-проигрыватель. Очередной велосипед :-). Скачиваем, смотрим, пугаемся количеству файлов и содержимому main.c, откладываем в сторону, и начинаем сами не спеша разбираться со своим кодом.

CoOS заточена под работу с контроллером ARM с ядром Cortex-M, коим и является STM32F105, поэтому запуск оси не вызывает особых сложностей.

  1. Создаём проект, делаем основные настройки.
  2. Не забываем скопировать CMSIS и STM32F10x_StdPeriph_Driver. «Высший пилотаж» по самостоятельной инициализации контроллера нас сейчас не интересует, поэтому будем пользоваться предлагаемыми производителем библиотеками.
  3. В отдельную папку (например, одноимённую CoOS) помещаем папку kernel с файлами ядра ОС и файлы arch.c, OsArch.h, OsConfig.h и port.c из папки /portable/GCC.
  4. Выполняем предварительную настройку параметров ОС в файле OsConfig.h. Об этом чуть подробнее далее.

Указываем тип используемого контроллера:
// Defines chip type,cortex-m3(1),cortex-m0(2)      
#define CFG_CHIP_TYPE           (1) 

Определяем максимальное количество одновременно запущенных задач. Под каждую задачу ядро ОС для собственных нужд выделяет память. У нас будет 5 задач: ЦАП, опрос клавиатуры, задача управления проигрывателем, декодер и задача чтения файлов:
// Max number of tasks that can be running.		     
#define CFG_MAX_USER_TASKS      (5) 

Указываем тактовую частоту ядра микроконтроллера. Значение используется только для расчёта частоты работы системного таймера SysTimer. Контроллер будем запускать на максимально поддерживаемой частоте 72 МГц:
// System frequency (Hz)
#define CFG_CPU_FREQ            (72000000)

Частота системного тика. Обычно выбирается из диапазона 100-1000 Гц как компромисс между скоростью реакции программы и накладными расходами на сохранение контекста при переключении задач. Зададим системный тик длительностью 1 мс:
// systick frequency (Hz)	                         
#define CFG_SYSTICK_FREQ        (1000)

Обязательно включаем проверку допустимости параметров и контроль переполнения стека задач:
// Enable(1) or disable(0) parameter checkout
#define CFG_PAR_CHECKOUT_EN     (1)
// Enable(1) or disable(0) stack overflow checkout
#define CFG_STK_CHECKOUT_EN     (1)

Увеличиваем максимальное поддерживаемое количество системных событий ОС:
// Max number of event.(must be less than 255) 	      
// Event = semaphore + mailbox + queue;			      
#define CFG_MAX_EVENT           (32)

Разрешаем использование mailbox и флагов:
// Enable(1) or disable(0) mailbox management.
#define CFG_MAILBOX_EN          (1)
// Enable(1) or disable(0) flag management. Max number of flag is 32.
#define  CFG_FLAG_EN            (1)

Мьютексы тоже по умолчанию запрещены. Разрешаем, возможно пригодятся:
#define  CFG_MUTEX_EN           (1)

Остальные параметры оставляем пока без изменений.

Hello, World
Наша первая программа:
// светодиод
#define LED_PIN		GPIO_Pin_12
#define LED_PORT	GPIOC

// описание тестовой задачи
#define TEST_TASK_STK_SIZE 64              // размер стека задачи
OS_STK testTaskStk[TEST_TASK_STK_SIZE];    // сам стек задачи

// тело задачи
void TestTask(void* pdata)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = LED_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(LED_PORT, &GPIO_InitStructure);
    GPIO_SetBits(LED_PORT, LED_PIN);	// LED_OFF;

    for(;;)
    {
        GPIO_ResetBits(LED_PORT, LED_PIN);	// LED_ON;
        CoTickDelay(500);
        GPIO_SetBits(LED_PORT, LED_PIN);	// LED_OFF;
        CoTickDelay(500);
    }
}

// главная функция
int main(void)
{
    // инициализация ОС
    CoInitOS();

    // инициализация задачи
    CoCreateTask(TestTask,   // указатель на задачу
                 (void *)0,  // указатель на передаваемые в задачу данные, не используем
                 0,          // приоритет задачи, максимум
                 &testTaskStk[TEST_TASK_STK_SIZE-1],    // указатель на конец стека задачи
                 TEST_TASK_STK_SIZE);                   // размер стека, слов (4-байтных)

    // запуск ОС
    CoStartOS();
}
Дополнительные комментарии здесь излишни. Запускаем программу, светодиод мигает как и положено с периодом 1 сек.

Пара слов про отладку
При возникновении ошибок работы ядра ARM вызывается исключение HardFault. Контекст, характеризующий параметры ошибки, сохраняется в стеке и в регистрах ядра Cortex. При отладке программы мы будем нередко попадать в HardFault, поэтому, чтобы упростить восприятие отладочной информации, обязательно нужно преобразовать эту самую информацию в удобный вид. Для этого переопределяем обработчик прерывания HardFault_Handler
__attribute__((naked)) void HardFault_Handler(void)
{
    __asm volatile
    (
        "TST LR, #4		\n"
	"ITE EQ			\n"
	"MRSEQ R0, MSP		\n"
	"MRSNE R0, PSP		\n"
	"B hard_fault_handler_c	\n"
    );
}

и пишем функцию hard_fault_handler_c (см. модуль exceptions.c):
void hard_fault_handler_c (uint32_t * hardfault_args)
{
    volatile uint32_t stacked_r0 __attribute__((unused));
    volatile uint32_t stacked_r1 __attribute__((unused));
    volatile uint32_t stacked_r2 __attribute__((unused));
    volatile uint32_t stacked_r3 __attribute__((unused));
    volatile uint32_t stacked_r12 __attribute__((unused));
    volatile uint32_t stacked_lr __attribute__((unused));
    volatile uint32_t stacked_pc __attribute__((unused));
    volatile uint32_t stacked_psr __attribute__((unused));

    // Bus Fault Address Register
    volatile uint32_t BFAR __attribute__((unused));
    // Configurable Fault Status Register Consists of MMSR, BFSR and UFSR
    volatile uint32_t CFSR __attribute__((unused));  
    // Hard Fault Status Register
    volatile uint32_t HFSR __attribute__((unused));	
    // Debug Fault Status Register
    volatile uint32_t DFSR __attribute__((unused));	
    // Auxiliary Fault Status Register
    volatile uint32_t AFSR __attribute__((unused));	
    // MemManage Fault Address Register
    volatile uint32_t MMAR __attribute__((unused));   

    volatile uint32_t SCB_SHCSR __attribute__((unused));

    stacked_r0 = hardfault_args[0];
    stacked_r1 = hardfault_args[1];
    stacked_r2 = hardfault_args[2];
    stacked_r3 = hardfault_args[3];
    stacked_r12 = hardfault_args[4];
    stacked_lr = hardfault_args[5];
    stacked_pc = hardfault_args[6];
    stacked_psr = hardfault_args[7];

    BFAR = (*((volatile unsigned long *)(0xE000ED38)));
    CFSR = (*((volatile unsigned long *)(0xE000ED28)));
    HFSR = (*((volatile unsigned long *)(0xE000ED2C)));
    DFSR = (*((volatile unsigned long *)(0xE000ED30)));
    AFSR = (*((volatile unsigned long *)(0xE000ED3C)));
    MMAR = (*((volatile unsigned long *)(0xE000ED34)));
    SCB_SHCSR = SCB->SHCSR;

    DBG_HALT(0);
    while (1);
}

Теперь при возникновении исключения все отладочные параметры будут помещены в отладочные переменные, которые вполне комфортно можно просмотреть в отладчике IDE. При наличии интерфейса dbgu можно вывести соответствующее сообщение в терминальное окно.

Более подробно про анализ исключений следует почитать этот аппноут и документацию на официальном сайте ARM.

Также рекомендую к применению макрос
#define DBG_HALT(numb) \
    do { \
	if (CoreDebug->DHCSR & 1) \
	{ \
	    __asm( "BKPT %0\n"::"M"(numb) ); \
	} \
    } while(0)

Он позволяет остановить выполнение программы и передать управление отладчику, если микроконтроллер запущен из-под отладчика. Использование данного макроса в обработчиках ошибок (например, в функции assert_failed) позволяет максимально быстро реагировать на возникающие неполадки программы. И, что очень важно, не тратятся дефицитные аппаратные брейкпоинты Кортекса. Значение numb ни к чему не обязывает, это просто маркер — число, которое может быть считано отладчиком для дальнейшего сопоставления брейкпоинта с исходниками.

Далее...
В следующей статье будем настраивать ЦАП.


Оглавление
Программный декодер MP3 на STM32F10x. Демопроект (Введение)
Программный декодер MP3 на STM32F10x. Часть 2. Запуск ЦАП
Программный декодер MP3 на STM32F10x. Часть 3. Извлекаем звуки
Программный декодер MP3(+MOD) на STM32F10x. Часть 4. Трекерная музыка
Программный декодер MP3(+). Переход на платформу STM32F407

  • +17
  • 09 сентября 2013, 16:50
  • MikeSmith
  • 1
Файлы в топике: mp3_player_step1.zip

Комментарии (14)

RSS свернуть / развернуть
Очень интересная тема для статьи, забегая вперед есть небольшая просьба, если есть какие то мысли/наработки/идеи или какая то другая информация (структурированная и осмысленная) относительно цифровой обработки звука «налету» или каких-либо эффектов, накладываемых на звук, то включите, пожалуйста, эту информацию в следующие статьи =)
0
Тема ЦОС конечно интересна. Но используемый контроллер не настолько шустр, чтобы возложить на него дополнительные функции по обработке звука. Пару эффектов можно сделать, но возможно придётся использовать ассемблер и тщательно оптимизировать алгоритмы. А это слишком трудоёмко для обучающей задачи. Однако если переложить проект на STM32F4x, появятся дополнительные ресурсы в виде FPU/DSP/RAM и кучи дополнительных мипсов, вот тогда и будем думать в сторону новых идей и наработок.
0
забегая вперёд, скажу, что используемому декодеру Helix необходимо минимум 24 кБ памяти
А какая ему нужна тактовая частота?
0
  • avatar
  • Vga
  • 09 сентября 2013, 19:38
См. третью часть статьи. 36 МГц впритык, но уже вполне достаточно.
0
Интересно, спасибо.
нередко с использованием чипа VLSI…
Чипы от финов (VLSI Solutions) — шикарная штука по многим параметрам особенно новые (VS1063), по структуре напоминают AVR с гипертрофированной периферией. Работал с ними, понравилось. Пара штук ещё лежит, где-то.
0
почему при запуске TestTask не выполняется снова код инициализации, а работает только цикл с миганием диода?
мог бы ты еще написать про ртос от кокоса?

еще я хотел бы почитать о способах генерации звука и модуляции
например взять ф4 и на выходе генерить 3.5мгц, которая модулируется по амплитуде и все это можно сразу подавать на усилитель

3й вопрос
будет ли этот декодер мр3 работать на пинборд2 с 103с6 вроде?
0
почему при запуске TestTask не выполняется снова код инициализации, а работает только цикл с миганием диода?
Что значит «снова»? Эта функция вызывается только один раз и работает все время работы программы.
например взять ф4 и на выходе генерить 3.5мгц, которая модулируется по амплитуде и все это можно сразу подавать на усилитель
А в чем проблема? Задача весьма простая. Ну, не считая того, что нужен ЦАП на 7МС/с минимум, да и производительности МК может не хватить на расчет данных для него.
будет ли этот декодер мр3 работать на пинборд2 с 103с6 вроде?
Нет. У него слишком мало ОЗУ. И там 103С8.
0
  • avatar
  • Vga
  • 10 сентября 2013, 23:54
Эта функция вызывается только один раз и работает все время работы программы.
я может туплю, но ведь там и другие задачи вызываются и менеджер задач должен переключать их и как я это вижу — каждый раз должна вызываться функция-задача и код должен обрабатываться полностью
в данном случае инициализация портов

Ну, не считая того, что нужен ЦАП на 7МС/с минимум,
как раз не нужен, в этом и весь цимес
причем генерация телеграфа на 3мгц сделана даже на меге8, но на асм
там идет прямоугольник и потом при помощи электронной магии он на выходе с антенны боле менее приличной формы уже
это класс д для увч типа
а с применением ф4 и его блока фпу можно поднимать частоты до 7 и 14мгц и наверное до 27
0
Функция-задача запускается один раз и выполняется линейно. Планировщик может прерывать выполнение функции и возобновлять работу с места прерывания. Поэтому мы и будем крутиться внутри цикла for.
Какой вид модуляции интересует? CW, понятно, без проблем. АМ тоже можно освоить. А вот с SSB или цифровыми видами модуляции придётся попотеть.
0
мог бы ты еще написать про ртос от кокоса?
В основе работы разных RTOS лежат единые принципы. Много материала, в том числе на русском, есть в Сети по FreeRTOS. Операционка от Кокоса имеет другой синтаксис и урезанный функционал, но алгоритм применения тот же.
0
Много материала, в том числе на русском
я в курсе, только мной усваивается 5% из того кала, что есть
нужно иметь особый дар преподнесения сложных вещей простым языком
это я тебе говорю как обладатель такой штуки (со слов других людей)
еще ди хальт так может и может быть еще пару человек припомню

ну и у тебя получились 3 статьи, которые легко читаются и понимаются мной
0
У меня такая плата, по описанию ее STM32 чипа, у него НЕТ ЦАП!
0
У меня такая плата, по описанию ее STM32 чипа, у него НЕТ ЦАП!

Что-то мешает ВНИМАТЕЛЬНО читать пост?
плату пришлось немного доработать — заменить контроллер на STM32F105RC.
0
Не увидел…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.