STM32 Воспроизведение звука. Настройка периферии + первый звук

Итак, наша задача  -  воспроизведение WAV-файлов с помощью микроконтроллера STM32. В предыдущих статьях разобрались со структурой  WAV-файлов, с библиотекой FatFs, которая позволяет работать с файловой системой, поигрались с ЦАПом. Приведу ссылки на эти статьи:

  1. Работа с Fat   статья1статья2 ;
  2. Структура WAV-файлов;
  3. Работа с ЦАПом.

Теперь необходимо объединить все это в одно целое, чтобы получить звук на выходе ЦАПа. В начале не будем ставить заоблачные цели, а ограничимся следующими возможностями:

  • фиксированная частота дискретизации – 44,1кГц;
  • монофонический сигнал;
  • разрядность 8 бит

Более того, для начала не будем анализировать контроллером заголовок файла, а начнем считывать данные из файла начиная с того места, где они расположены (в статье “STM32 Воспроизведение звука. Структура WAV-файлов” объясняется как это делать). Добившись успеха в этом, будем постепенно наращивать возможности программы.

Структурная схема воспроизведения звука будет следующая:

image

С помощью библиотеки FatFs загружаем звуковые данные (сэмплы) в буфер ЦАПа, а ДМА по сигналу от таймера транспортирует эти данные в регистр промежуточных данных ЦАПа. Ну а он, в свою очередь, из цифры делает звук. Скорость подачи данных (она же частота дискретизации) задается таймером TIM6. Вся эта система будет работать в автоматическом режиме, без участия процессора. От программы ожидается только в нужное время подавать патроны загружать буфер DAC_Buff по мере его освобождения. Сигналом для загрузки будут служить флаги ДМА. Есть два флага: один устанавливается при освобождении первой половины буфера, второй – при освобождении второй половины.

Алгоритм такой.

  1. Вначале загружаем весь буфер и запускаем ДМА
  2. Ждем освобождения первой половины буфера
  3. Догружаем данные в первую половину буфера
  4. Ждем освобождения второй половины буфера
  5. Догружаем данные во вторую половину буфера
  6. Повторяем пункты с 2 по 5 до тех пор, пока не кончится весь файл

Не знаю как правильно выбрать размер буфера. Если его размер будет слишком маленький, возможна такая ситуация, когда процессор, отвлекшись на другую работу (например, обработку прерывания), не успеет загрузить освободившуюся часть буфера. Но что значит “слишком маленький” ? Для начала задам размер буфера равным 512 байт, а в процессе отладки буду корректировать.

Инициализация ЦАПа, ДМА и таймера будет выглядеть следующим образом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
unsigned char DAC_Buff[512];                   //буфер данных ЦАПа (глобальная переменная)
//Настраиваем первый канал ЦАПа для приема данных от ДМА
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;            //тактирование линий GPIOA
RCC->APB1ENR |= RCC_APB1ENR_DACEN;             //тактирование ЦАПа
DAC->CR      |= DAC_CR_DMAEN1;                 //разрешить прием данных канала №1 от ДМА
DAC->CR      |= DAC_CR_BOFF1;                  //отключить выходной буфер
DAC->CR      |= DAC_CR_EN1;                    //включить канал 1
//Настроить третий канал ДМА для перемещения данных из буфера
//в промежуточный регистр данных первого канала ЦАПа:
RCC->AHBENR         |= RCC_AHBENR_DMA1EN;      //тактирование DMA
DMA1_Channel3->CPAR  = (uint32_t)&DAC->DHR8R1; //указатель на регистр периферии
DMA1_Channel3->CMAR  = (uint32_t)&DAC_Buff[0]; //указатель на начало буфера в памяти
DMA1_Channel3->CCR  |=  DMA_CCR3_DIR;          //направление передачи - из памяти в периферию 
DMA1_Channel3->CNDTR =  512;                   //размер буфера
DMA1_Channel3->CCR  &= ~DMA_CCR3_PINC;         //адрес периферии не инкрементируем
DMA1_Channel3->CCR  |=  DMA_CCR3_MINC;         //адрес памяти инкрементируем
DMA1_Channel3->CCR  &= ~DMA_CCR3_PSIZE;        //размерность данных периферии - 8 бит.
DMA1_Channel3->CCR  &= ~DMA_CCR3_MSIZE;        //размерность данных памяти    - 8 бит
DMA1_Channel3->CCR  |=  DMA_CCR3_CIRC;         //включить циклический режим передачи данных
DMA1_Channel3->CCR  |=  DMA_CCR3_PL;           //приоритет
DMA1_Channel3->CCR  |=  DMA_CCR3_EN;           //разрешаем работу третьего канала DMA
//Запуск передачи очередного значения через ДМА будет происходить от TIM6
RCC->APB1ENR        |=  RCC_APB1ENR_TIM6EN;    //подаем тактирование TIM6
TIM6->PSC            =  0;                     //Задаем частоту дискретизации 44100
TIM6->ARR            =  544;                   //(при тактовой 24000000)
TIM6->DIER          |=  TIM_DIER_UDE;          //разрешить запрос DMA

Понадобится функция, которая откроет файл, и будет подавать из него данные в буфер ЦАПа:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
FATFS fs;           //понадобится для работы с диском (делаем ее глобальной переменной!!!)
//*********************************************************************************************
//function воспроизвести wave-файл                                                           //
//argument имя файла                                                                         //
//result   0 - все нормально, иначе - ошибка                                                 //
//*********************************************************************************************
char wave_playback(const char *FileName)
{
  FRESULT res;                                //для возвращаемого функциями результата
  FIL file;                                   //файловый объект
  UINT cnt;                                   //количество реально прочитанных байт
 
  res = f_open( &file, FileName, FA_READ );   //открыть файл FileName для чтения
  if(res) return 1;
  res = f_lseek(&file,0x2c);                  //переместить указатель на начало полезных данных
  if(res) return 2;
  f_read (&file,&DAC_Buff[0],512,&cnt);       //загрузить буфер ЦАПа данными
  if(res) return 3;
  TIM6->CR1 |= TIM_CR1_CEN;                   //запустить преобразование
 
  /*                        воспроизведение                              */
  while(1)
  {
     while(!(DMA1->ISR & DMA_ISR_HTIF3)) {}   //ждем освобождение первой части буфера
     f_read (&file,&DAC_Buff[0],256,&cnt);    //загрузить ее данными
     DMA1->IFCR |= DMA_ISR_HTIF3;             //сбросить флаг
     if(cnt<256)break;                        //если конец файла
 
     while(!(DMA1->ISR & DMA_ISR_TCIF3)) {}   //ждем освобождение второй части буфера
     f_read (&file,&DAC_Buff[256],256,&cnt);  //загрузить ее данными
     DMA1->IFCR |= DMA_ISR_TCIF3;             //сбросить флаг
     if(cnt<256)break;                        //если конец файла
  }
 
  TIM6->CR1 &= ~TIM_CR1_CEN;                  //остановить преобразование
  f_close(&file);                             //закрыть файл
  return 0;                                   //успешное завершение ф-ии
}

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

1
2
3
//предварительно выполняем инициализацию, указанную в первом листинге                          /
f_mount(0, &fs);           //монтировать диск
wave_playback();           //запустить преобразование

Дальше готовим файл со следующими параметрами: моно (1 канал), разрядность 8 бит, частота дискретизации 44100Гц.

Записываем его на карту памяти с именем “sound”.

Подключаем усилитель к выходу первого канала ЦАПа, загружаем откомпилированную программу в контроллер, и ВУАЛЯ!!! Реально слышен звук в динамике. И очень даже не плохого качества (когда-то я делал что-то подобное на Mega8. Качество было хуже) .

Для конвертирования файлов из mp3 в WAV можно воспользоваться следующей программой:
Cкачать конвертор файлов
(загружено 7 раз)

Подведем небольшой итог: поставленная задача (получение первого звука) выполнена. На следующем этапе расширим возможности программы.

Комментарии (5) на “STM32 Воспроизведение звука. Настройка периферии + первый звук”

  • Max:

    Здравствуйте! У вас отличный и весьма познавательный сайт.
    Не могли бы подсказать, какой усилитель вы используете для воспроизведения звука?

  • Dmytro:

    Для практически полной автоматизации процесса воспроизведения файла можно процедуры заполнения буфера запихнуть в обработчик прерывания от DMA. Он умеет вызывать прерывания по опустошению буфера (половины буфера).

    • kontroller:

      Согласен, надо использовать ДМА.
      Пока руки не доходят — делаю генератор кода

  • Сергей:

    Спасибо за такой познавательный ресурс! Меня интересует можно ли воспроизвести одновременно несколько файлов?

Оставить комментарий

Spam Protection by WP-SpamFree