Кто любит RISC в жизни, заходим, не стесняемся.
Ответить

Аудио-плеер. PWM-DAC + DMA

Вт окт 02, 2018 00:44:18

Здравствуйте, уважаемые радиокоты.
Осваиваю тут потихоньку МК stm32. Вот сейчас делаю wav-плеер (в целях изучения МК от ST) на stm32f103c8.
Плеер работает! Читает в буфер с SD-карты через SPI, используя либу FatFs. Таймер4 работает в качестве ЦАПа. Прерывания 3-го таймера срабатывают с частотой дискретизации wav-файла. В обработчике запихиваю байт в регистр сравнения таймера4.
DMA не используется.
В каких-то проектах этого было бы достаточно, но ведь было бы красивее сделать чтоб DMA запихивал порцию байт аудио-файла из буфера в регистр сравнения. Тем самым ещё больше уменьшить нагрузку на ЦПУ!
Ну вот и вопрос: как же сделать так, чтоб буфер с аудио-данными, длиною, например 512 байт, был передан таймеру4 с заданной частотой (частотой дискретизации аудио) и на протяжении всей передачи этого буфера 512 байт, на это не отвлекалось ЦПУ?

Надеюсь вопрос и задумка ясна.

UPD:
Всё-же дополнительно объясню, чего хочу добиться.
В текущей реализации проекта, каждый байт аудио-файла пихаю в "ЦАП" (точнее регистр сравнения таймера4) силами ЦПУ, в прерывании.
Идея, дать команду DMA переслать 512 байт (или больше) в "ЦАП" и занять ЦПУ чем-то другим, выглядит заманчиво!
Но вот тут-то и появляется проблема: как заставить DMA слать байты по заданной частоте?

В случае связки DMA с каким-нибудь периферийным модулем, например USART, всё понятно: по окончанию передачи очередного байта DMA получает об этом уведомление и если ещё есть данные для передачи "кидает" в USART следующий байт.
А как быть с регистром сравнения какого-нибудь таймера или с аппаратным ЦАП-ом какого-нибудь другого МК данной конторы.

Re: Аудио-плеер. PWM-DAC + DMA

Вт окт 02, 2018 06:58:03

Сперва для 8бит моно.Таймер конфигурируем как PWM.
Частота PWM -частота дискретизации звука, лежит в заголовке wav.
Реквесты канала DMA по TIMx_UP. Источник буфер звука, приемник CCP таймера, счетчик - размер буфера. Режим не циклический.
CCP тамера буферизированный

8бит стерео, здесь придется задействовать DMA burst.

В любом случае отслеживаем половину буфера и завершение транзакции ,или поллингом флагов DMA либо в прерывании.
Так называемый пинг-понг - пока воспроизводится одна половина буфера - заполняем другую...

Re: Аудио-плеер. PWM-DAC + DMA

Вт окт 02, 2018 14:40:24

То-есть используется только один таймер: и для вывода во внешний мир ШИМ-сигнала, и для "пинания" DMA?
Тогда получается частота несущей == частоте модуляции (дискретизации звука). Так?
Вроде как, чем больше частота ШИМ-сигнала тем проще его преобразовать (сгладить) в аналоговый.
Поэтому я и хотел сделать частоту несущей выше частоты дискретизации звука.

По Вашему способу решения вопроса:
Предполагаю что, аудио-файлы с частотой дискретизации звука более 20kHz будут воспроизводиться нормально, возможно даже без фильтров!
А как быть с частотами дискретизации в диапазоне 20 - 20 000 Hz? Например, частота дискретизации 8kHz считается достаточной для передачи голоса.
Не будут ли слышны изъяны в таком случае, когда частота несущей == частоте модуляции?

Re: Аудио-плеер. PWM-DAC + DMA

Вт окт 02, 2018 17:31:47

Делал плеер на 22КГц все звучало достойно.
Завтра посмотрю, вроде и до 44 поднимал...

Re: Аудио-плеер. PWM-DAC + DMA

Ср окт 03, 2018 15:27:35

В общем всё получилось! Воспроизведение звука сделал с использованием одного таймера, который работает в режиме ШИМ и "пинает" DMA на частоте дискретизации звука.
Аудио-файлы с частотой дискретизации 22050Hz и 44100Hz воспроизводятся нормально — звучание достойное! Пробовал файлы 8000Hz, слышно несущую 8 килоГерц (свист). Надо ставить фильтр — это минус такой реализации, так сказать дань за использование только одного таймера.
При любой частоте дискретизации проскакивают сильные помехи, которые возникают при чтении с SD-карты! Но это к программированию уже не относится! Как именно это решить я пока не знаю.
Позже смогу выложить исходники, тем более если это будет интересно читателям.
Реквесты канала DMA по TIMx_UP

dosikus, спасибо за эту фразу и за идею использовать только один таймер! Пытался тебя плюсануть, ничего не получается, flash зависает, может с другого браузера получится.

Re: Аудио-плеер. PWM-DAC + DMA

Ср окт 03, 2018 18:40:41

На счет помех -у меня их нет. Карточка подключена через отдельный стаб , подтяжки везде кроме CLK.
Время выкрою попробую на 8КГц, но помнится свиста не было.
Да у меня F0,a так же делал вариант на F1.
Завтра не забуду скину.
И о дма_бурст http://mcu.goodboard.ru/viewtopic.php?id=31

Re: Аудио-плеер. PWM-DAC + DMA

Ср окт 03, 2018 22:45:11

Аудио-файлы с частотой дискретизации 22050Hz и 44100Hz воспроизводятся нормально — звучание достойное! Пробовал файлы 8000Hz, слышно несущую 8 килоГерц (свист).

Какой смысл привязываться к частоте дискретизации источника? И ещё и перестраивать частоту под каждый источник.
Делайте вывод на любой частоте какая нравится. А входной сигнал нетрудно передискретизировать на нужную частоту.
Мой проект (интернет-радио) воспроизводит с любых источников (MP3- или AAC-) независимо от их частоты. Частота ЦАП стоит 98кГц - никаких свистов не слышно. Использую встроенный ЦАП, а не ШИМ. Фильтров никаких нет (правда они есть в самих декодерах).

Re: Аудио-плеер. PWM-DAC + DMA

Чт окт 04, 2018 06:49:30

имхо, DMA надо пинать с частотой дискретизации файла/потока, а таймер ШИМом должен молотить на фиксированной частоте, как можно более высокой. и свиста не будет, и качество будет предельно достижимое технически

Re: Аудио-плеер. PWM-DAC + DMA

Чт окт 04, 2018 09:47:33

имхо, DMA надо пинать с частотой дискретизации файла/потока, а таймер ШИМом должен молотить на фиксированной частоте, как можно более высокой. и свиста не будет, и качество будет предельно достижимое технически

Бред. Частота изменения выходного сигнала останется прежней. Как было 8 кГц, так и останется. Куда тогда денется свист?
DMA должна "пинать" периферия, которую он обслуживает. В данном случае - таймер. С частотой работы этой самой периферии. И соответственно сэмплы в буфере отправляемом DMA, должны идти с той же частотой. Ресэмплинг этого буфера на данную частоту делается CPU (предварительно). Можно простой кусочно-линейной интерполяцией (как у меня), можно другим методом (более качественно). Таймер должен работать в shadow-режиме.
Вот тогда всё будет ок.

Re: Аудио-плеер. PWM-DAC + DMA

Сб окт 06, 2018 03:32:57

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

Re: Аудио-плеер. PWM-DAC + DMA

Сб окт 06, 2018 23:22:24

DMA надо пинать с частотой дискретизации файла/потока, а таймер ШИМом должен молотить на фиксированной частоте, как можно более высокой

Вот именно так я и хотел сделать, используя general purpose timer! Но покопавшись в reference manual, посмотрев регистры этих таймеров, я как-то не нашел ничего, с помощью чего можно было бы выполнить условие "Частота несущей выше частоты модуляции звуком этой несущей". Вот и решил спросить у знающих. И как оказалось не зря! dosikus указал мне на регистр "RCR" — Reload Counter Register, за что ему отдельное спасибо! Данный регистр позволяет "пинать" DMA (или выдавать запрос на прерывание) не при каждом переполнении счётного регистра таймера, а при каждом N переполнений! Таким образом частоту ШИМ можно сделать выше частоты дискретизации аудио.
Правда есть и один минус: регистра RCR нет в general purpose timers, он есть только в advanced таймерах (TIM1, TIM8), коих в МК значительно меньше чем general purpose таймеров (1-2 шт.). Прошу учесть что я читал reference manual и прочие материалы только те, которые относятся к stm32f103c8.

В результате вот что пишу в регистры предделителя (PSC), автоматической перезагрузки (ARR) и счётчика повторений (RCR):
Спойлер
Код:
   TIM1->PSC = 0;
   TIM1->ARR = 0xFF;//сброс таймера при достижении данного значения (глубина звука)
   TIM1->RCR = (F_IN_TIM1 / (TIM1->PSC+1)) / (TIM1->ARR+1) / sampleRate;// - 1;
    sampleRate — частота дискретизации
    F_IN_TIM1 — входная частота таймера1 (до предделителя)

Есть, правда, и один минус (как же без него). Частота воспроизведения (чит. запросы DMA) могут не соответствовать частоте дискретизации источника. Следствие: замедленное или ускоренное воспроизведение аудио-потока! Как с этим бороться, кроме как применять предварительный ресемплинг буфера (как писал jcxz), я не знаю! Нет, конечно можно завести рядом с МК какой-нибудь внешний генератор с требуемой частотой, но опять же минус — ещё один компонент на плате.
Но меня такой результат пока что устраивает!

Обращение к jcxz:
Спойлер
Какой смысл привязываться к частоте дискретизации источника?

Смысл в минимизации нагрузки на ЦПУ!
входной сигнал нетрудно передискретизировать на нужную частоту

"Передискретизировать на нужную частоту" — означает выполнять дополнительную работу, то-есть использовать ресурсы ЦПУ!
Понимаете, те проекты по воспроизведению звука что я делаю сейчас — это тест-проекты, проекты-шпаргалки на будущие, называйте это как хотите.
В каждом таком проекте свой способ выполнения такой абстрактной задачи как "воспроизведение звука" из какого-нибудь источника. Пока что замарочился только с SD-картами.
Как известно, в каждом способе реализации могут быть свои как плюсы, так и минусы! А для каждой конкретной задачи какой-то способ воспроизведения звука будет подходить больше, а какой-то меньше!
В Вашем случае
Мой проект (интернет-радио) воспроизводит с любых источников (MP3- или AAC-) независимо от их частоты.

ресурсы ЦПУ задействовать всё равно приходится (на декодирование), так чего уж тут экономить ресурсы ЦПУ!? (вопрос риторический!)

имхо, DMA надо пинать с частотой дискретизации файла/потока, а таймер ШИМом должен молотить на фиксированной частоте, как можно более высокой

"более высокой" ARV видимо имел ввиду частоты выше, хотя бы, 20-ти килоГерц.
А так да, чем больше тем лучше! Пожалуй каждый кто сталкивался с ШИМ, об этом знает.


Обращение к afz:
Спойлер
Подозреваю, что "помехи" проистекают от задержки на чтение очередной порции данных с SD-шки. В таких случаях помогает двойная буферизация.

Помехи возникают именно в моменты чтения SD-карты! Причём если увеличить буфер чтения, то частота возникновения этих помех уменьшается на столько, на сколько увеличен буфер! Частота самой помехи остаётся прежней!
Ну да ладно, доберусь до задачи "качества звучания", вопрос "помехи" при чтении с SD-карты будет решён, надеюсь, простыми мероприятиями:
    - отдельное питание для SD-карты. А она, кажись, может потреблять до 100mA, а у меня на макетке стоит AMS1117... ыыыыыы
    - подтяжки по линиям данных SD-карты

А вот про
В таких случаях помогает двойная буферизация
вообще ничего не понял.


Фух

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 03:06:26

marengo писал(а):вообще ничего не понял.
Я имел в виду вот это:
dosikus писал(а):В любом случае отслеживаем половину буфера и завершение транзакции ,или поллингом флагов DMA либо в прерывании.
Так называемый пинг-понг - пока воспроизводится одна половина буфера - заполняем другую...
Две половины одного буфера, или два отдельных буфера - не суть важно, по-любому, это двойная буферизация.

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 07:33:15

afz
Проблем с логикой работы программы у меня не было! Были вопросы по настройке работы периферии (и не надо тыкать меня носом в "CubeMX").
Но всё равно спасибо!

Короче, выкладываю исходники.
Сначала тот вариант, который был до того как я создал эту тему. Шпора "PWM_wav0.3"
Спойлер
Код:
/* Попытка прочитать и воспроизвести аудио-файл.
 * WAV 22.05kHz, 8bit, mono PCM, без сжатия.
 * Используются два буфера поперемено: один для чтения, один для воспроизведения.
 * Только прерывания, без DMA.
 * Таймер4 в качестве ЦАПа!
 * Таймер3 настраивается в соответствии с частотой дискретизации
 * воспроизводимых файлов!
 * Чтение данных с SD-карты без DMA и по SPI!
 * Заголовки wav-файла не читаем, пропускаем.
 */

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"

#include "./FatFs/integer.h"
#include "./FatFs/ff.h"
#include "./FatFs/diskio.h"

#include "virtualTimers.h" //это свой "велосипед"-реализация программных таймеров обратного отсчёта. Необходима для либы FatFs!

#define F_IN_TIM1   72000000

void SetSysClockTo72(void);
void wavPlayer_init(uint16_t sampleRate);

#define wavPlayer_pause()      TIM3->CR1 &= ~TIM_CR1_CEN

//TCHAR* file2play = "sd1:/TEST_DAC/44K1_16M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/44K1_8M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/22K05_16.WAV";
TCHAR* file2play = "sd1:/TEST_DAC/22K05_8M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/8K_8M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/SILENT22.WAV";//тишина 22k05, 8bit, mono

#define buffToPlay_SIZE   512
static BYTE buffToPlay1[buffToPlay_SIZE];
static BYTE buffToPlay2[buffToPlay_SIZE];
static BYTE *buffToPlay = 0;
static BYTE *buffToReadStorage = buffToPlay1;
static volatile UINT countOfBuffToPlay =      0;//счётчик воспроизведённых байт
static UINT countOfBuffToReadStorage =   0;//счётчик прочитаных байт
static UINT previousValueCountOfBuffToReadStorage =   0;//значение счётчика прочитаных байт, которые воспроизводятся в run time

//переменные для доступа к SD
FATFS fs[1];
FIL fil_obj; //структура файла, с которым работаем

int main(void){
   SetSysClockTo72();

   FRESULT res;

   BYTE *tmp_ptr;

   VirtualTimers_init();//это свой "велосипед"-реализация программных таймеров обратного отсчёта. Необходима для либы FatFs!
   if(f_mount(&fs[0], "sd1:", 1) != FR_OK){return 0;}
   if(f_open(&fil_obj, file2play, FA_READ) != FR_OK){return 0;}
   f_lseek(&fil_obj, 44);
   //wavPlayer_init(8000);
   //wavPlayer_init(11025);
   wavPlayer_init(22050);
   //wavPlayer_init(33075);
   //wavPlayer_init(44100);
   while(1){
      res = f_read(&fil_obj, buffToReadStorage, buffToPlay_SIZE, &countOfBuffToReadStorage);
      if(!countOfBuffToReadStorage || (res != FR_OK)){break;}
      while(countOfBuffToPlay < previousValueCountOfBuffToReadStorage);//ждём окончания воспроизведения предыдущего буфера
      previousValueCountOfBuffToReadStorage = countOfBuffToReadStorage;
      countOfBuffToPlay = 0;//reset counter play
      //exchange of buffers
      tmp_ptr = buffToPlay;
      if(!buffToPlay){//первая итерация
         buffToPlay = buffToPlay1;
         buffToReadStorage = buffToPlay2;
      }else{
         buffToPlay = buffToReadStorage;
         buffToReadStorage = tmp_ptr;
      }
   }
   f_close(&fil_obj); //закрываем файл
   while(countOfBuffToPlay < previousValueCountOfBuffToReadStorage);//ждём окончания воспроизведения
   wavPlayer_pause();
}

void TIM3_IRQHandler(void){
   TIM3->SR &= ~TIM_SR_UIF; //Сбрасываем флаг UIF
   if(countOfBuffToPlay >= buffToPlay_SIZE){return;}//если вылезли за пределы буфера
   if(!buffToPlay){
      countOfBuffToPlay = 0;
      return;
   }
   TIM4->CCR1 = buffToPlay[countOfBuffToPlay++];
}

void wavPlayer_init(uint16_t sampleRate) {
   //Включем порт B
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
   //Включаем Таймер 4
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

   //настраиваем ШИМ-канал вместо DAC
   GPIO_InitTypeDef PORT;
   // Настроим ногу ШИМ-канала на выход
   PORT.GPIO_Pin = (GPIO_Pin_6);//канал №1, таймер №4
   //Будем использовать альтернативный режим а не обычный GPIO
   PORT.GPIO_Mode = GPIO_Mode_AF_PP;
   PORT.GPIO_Speed = GPIO_Speed_2MHz;
   //PORT.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_Init(GPIOB, &PORT);

   //Разрешаем таймеру использовать ногу PB6 для ШИМа
   TIM4->CCER |= (TIM_CCER_CC1E);
   // Для канала задаем инверсный ШИМ.
   //TIM4->CCMR1|=(TIM_CCMR1_OC1M_0| TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2);
   //или прямой
   TIM4->CCMR1|=(TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2); TIM4->CCMR1 &= ~(TIM_CCMR1_OC1M_0);
   TIM4->PSC = 1; // Настраиваем делитель таймера
   TIM4->ARR = 0xFF;//сброс таймера при достижении данного значения (max 65535 for 16bit). 0xFF для 8bit audio, 0xFFFF for 16bit!
   
   TIM4->CR1 |= (
      TIM_CR1_CEN //Запускаем таймер!
      //| ARPE //предварительная загрузка регистра ARR. 1 — on, 0 — off
   );
   //После этого пишем данные в TIM4->CCRx - и скважность меняется

   //настраеваем ещё один таймер на частоту соответствующую частоте дискретизации аудио.
   //Включаем Таймер 3
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
   //TIM3->CCMR1|=(TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2); TIM3->CCMR1 &= ~(TIM_CCMR1_OC1M_0);
   TIM3->PSC = (F_IN_TIM1 / 600000); // Настраиваем делитель таймера
   TIM3->ARR = (sampleRate>0)?(600000/sampleRate) : 0;//сброс таймера при достижении данного значения (max 65535 for 16bit)
   TIM3->DIER |= TIM_DIER_UIE; //разрешаем прерывание от таймера
   //Запускаем таймер!
   TIM3->CR1 |= TIM_CR1_CEN;

   NVIC_EnableIRQ(TIM3_IRQn); //Разрешение TIM3_IRQn прерывания
}


И вариант на текущий момент (PWM_wav0.5)
Спойлер
Код:
/* Попытка прочитать с SD-карты и воспроизвести аудио-файл.
 * WAV 22.05kHz, 8bit, mono PCM, без сжатия.
 * Используются один буфер, для чтения и воспроизведения.
 * Прерывание от DMA срабатывает каждый раз, когда воспроизведено половина буфера и когда полностью.
 * По этим прерываниям, переписываем воспроизведённую половину буфера новыми данными.
 * DMA работает в циклическом режиме. Нет необходимости в постоянном перезапуске DMA и в отслеживании окончания именно второй половины буфера!
 * Канал1 Таймер1 — в качестве ЦАПа!
 * Также Таймер1 "пинает" DMA, а DMA, в свою очередь, пихает байты в регистр сравнения этого таймера.
 * Частота ШИМ больше частоты дискретизации аудио благодаря применению регистра "RCR"!
 * Чтение данных с SD-карты по прежнему без DMA и по SPI! Ежели сюда DMA прикрутить ещё и для чтения с SD-карты, то это ещё больше разгрузит ЦПУ!
 * Заголовки wav-файла не читаем, пропускаем.
 */
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"

#include "./FatFs/integer.h"
#include "./FatFs/ff.h"
#include "./FatFs/diskio.h"

#include "virtualTimers.h" //это свой "велосипед"-реализация программных таймеров обратного отсчёта. Необходима для либы FatFs!

#define F_IN_TIM1   72000000

void SetSysClockTo72(void);
void wavPlayer_init(uint16_t sampleRate);

//TCHAR* file2play = "sd1:/TEST_DAC/44K1_16M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/44K1_8M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/22K05_16.WAV";
TCHAR* file2play = "sd1:/TEST_DAC/22K05_8M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/8K_8M.WAV";
//TCHAR* file2play = "sd1:/TEST_DAC/SILENT22.WAV";//тишина 22k05, 8bit, mono, 30sec
#define buffToPlay_SIZE   1024
BYTE buffToPlay[buffToPlay_SIZE];

BYTE* buffToPlay_selector_ptrs[2] = {&buffToPlay[0], &buffToPlay[buffToPlay_SIZE / 2]};//в коде спрятана за макросом

uint8_t buffToPlay_selected = 0;//выбранная половинка для заполнения новыми данными //циклический инкремент: 0x00 ... 0xFF, 0x00... & 0b00000001 всегда = 0 или 1.
#define buffToPlay_changeHalf()      {buffToPlay_selected = ++buffToPlay_selected & 0x01;}   //передвинуть указатель на другую половину буфера
#define buffToPlay_currentHalf      buffToPlay_selector_ptrs[buffToPlay_selected]

static UINT countOfBuffToReadStorage =   0;//счётчик прочитаных байт

//переменные для доступа к SD
FATFS fs[1];
FIL fil_obj; //структура файла, с которым работаем

int main(void){
   SetSysClockTo72();

   VirtualTimers_init();//это свой "велосипед"-реализация программных таймеров обратного отсчёта. Необходима для либы FatFs!
   if(f_mount(&fs[0], "sd1:", 1) != FR_OK){return 0;}
   if(f_open(&fil_obj, file2play, FA_READ) != FR_OK){return 0;}
   f_lseek(&fil_obj, 44);//пропускаем заголовки, переходим сразу к полезным данным
   f_read(&fil_obj, buffToPlay, buffToPlay_SIZE, &countOfBuffToReadStorage);//без проверки...

   //wavPlayer_init(8000);
   //wavPlayer_init(11025);
   wavPlayer_init(22050);
   //wavPlayer_init(33075);
   //wavPlayer_init(44100);

   while(1){}
}

void DMA1_Channel5_IRQHandler(void){
   static FRESULT res;
   DMA1->IFCR |= DMA_ISR_TCIF5;        //очистить флаг окончания обмена
   DMA1->IFCR |= DMA_ISR_HTIF5;        //очистить флаг передачи половины буфера

   res = f_read(&fil_obj, buffToPlay_currentHalf, buffToPlay_SIZE / 2, &countOfBuffToReadStorage);
   if((res != FR_OK) || (!countOfBuffToReadStorage)){
      //остановить воспроизведение, грубо!
      DMA1_Channel5->CCR &= ~DMA_CCR5_EN;
      TIM1->CCER &= ~TIM_CCER_CC1E;
      return;
   }
   buffToPlay_changeHalf();//в следующей итерации будем читать в другую половину буфера!
}

void wavPlayer_init(uint16_t sampleRate) {
   //DMA...
   RCC->AHBENR |= RCC_AHBENR_DMA1EN;// разрешаем тактирование DMA1

   DMA1_Channel5->CPAR = (uint32_t) &TIM1->CCR1;// задаем адрес приемника данных
   DMA1_Channel5->CMAR = (uint32_t) &buffToPlay[0];// задаем адрес источника данных
   DMA1_Channel5->CNDTR = buffToPlay_SIZE;// указываем число пересылаемых данных
   // разрешаем работу + режим
   DMA1_Channel5->CCR = 0;
   DMA1_Channel5->CCR = DMA_CCR5_MINC //инкрементировать адрес источника
         | DMA_CCR5_CIRC //использовать цикличный режим, т.е. передавать данные по кругу
         | DMA_CCR5_DIR //направление данных “чтение из памяти”
         | DMA_CCR5_EN //ВКЛ.
         //& ~DMA_CCR5_MSIZE //
         //& ~DMA_CCR5_PSIZE //размер приемника данных 8 бит
         | DMA_CCR5_PSIZE //размер приемника данных 16 бит
         //| DMA_CCR5_TEIE //Разрешение прерывания при возникновении ошибки при обмене
         | DMA_CCR5_HTIE //Разрешение прерывания по  завершении половины обмена
         | DMA_CCR5_TCIE //Разрешение прерывания по  завершении обмена
         ;
   NVIC_EnableIRQ(DMA1_Channel5_IRQn);            //Разрешение прерываний канала


   //Включем порт A + Таймер 1
   RCC->APB2ENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1;

   //настраиваем ШИМ-канал вместо ЦАПа
   GPIO_InitTypeDef PORT;
   // Настроим ногу ШИМ-канала на выход
   PORT.GPIO_Pin = GPIO_Pin_8;//канал №1, таймер №1
   //Будем использовать альтернативный режим а не обычный GPIO
   PORT.GPIO_Mode = GPIO_Mode_AF_PP;
   PORT.GPIO_Speed = GPIO_Speed_2MHz;
   //PORT.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_Init(GPIOA, &PORT);

   TIM1->BDTR |= TIM_BDTR_MOE; //Разрешаем вывод сигнала на выводы (only for Advanced Timers — TIM1 & TIM8)
   TIM1->CCER |= (TIM_CCER_CC1E);//Разрешаем таймеру использовать ногу PA8 для ШИМа
   // Для канала задаем инверсный ШИМ.
   //TIM1->CCMR1|=(TIM_CCMR1_OC1M_0| TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2);
   //или прямой
   TIM1->CCMR1|=(TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2); TIM1->CCMR1 &= ~(TIM_CCMR1_OC1M_0);

   //TIM1->CCMR1 |= TIM_CCMR1_OC1PE;

   TIM1->PSC = 0;
   TIM1->ARR = 0xFF;//сброс таймера при достижении данного значения (глубина звука)
   TIM1->RCR = (F_IN_TIM1/ (TIM1->PSC+1)) / (TIM1->ARR+1) / sampleRate;// - 1;

   //TIM1->CR1 = 0; //оптимизатор может выкинуть эту операцию
   TIM1->CR1 |= (
      TIM_CR1_CEN //Запускаем таймер!
      //| TIM_CR1_ARPE //предварительная загрузка регистра ARR. 1 — on, 0 — off
      //| TIM_CR1_CMS_0
      //| TIM_CR1_CMS_1
      //CMS0, CMS1 — включает режим “выравнивания по центру”; если CMS !=00, то режим счета следующий –
         //от нуля до значения регистра ARR и обратно до нуля
      //| DIR   //если CMS =00, определяет направление счета: 0 – считает вверх, 1 – вниз
   );
   TIM1->DIER |= TIM_DIER_UDE; //разрешаем выдавать запрос DMA при возникновении события
}

Код написан в CoIDE. И не забываем что это только "шпоры"! Но исходники рабочие, поставленную задачу выполняют!

P.S. Так, если кто копирует исходники, те ставят мне "плюс"! 8) dosikus-а тоже можно плюсовать, без него я ещё долго не мог бы прикрутить DMA к своему проекту!

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 07:53:21

Две половины одного буфера, или два отдельных буфера - не суть важно, по-любому, это двойная буферизация.


Кому-то может и не важно, но во втором случае придется останавливать канал дма, плюс лишние телодвижения в прерывании не есть гуд.

Добавлено after 4 minutes 45 seconds:
marengo,
Код:
DMA1->IFCR |= DMA_ISR_TCIF5;        //очистить флаг окончания обмена
   DMA1->IFCR |= DMA_ISR_HTIF5;        //очистить флаг передачи половины буфера


DMA1->IFCR = DMA_ISR_TCIF5;
DMA1->IFCR = DMA_ISR_HTIF5;

IFCR write only

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 09:37:25

IFCR write only
Можно даже не побояться и
Код:
DMA1->IFCR = DMA_IFCR_TCIF5 | DMA_IFCR_HTIF5;

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 09:50:06

Ну это и ежу понятно, но вот на смарте на этом движке форума редактировать -каторга. :)))

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 15:38:46

Таким образом частоту ШИМ можно сделать выше частоты дискретизации аудио.

А смысл? Зачем?? :dont_know:

Добавлено after 3 minutes 20 seconds:
"Передискретизировать на нужную частоту" — означает выполнять дополнительную работу, то-есть использовать ресурсы ЦПУ!

А без этого не избавитесь от свиста на 8 кГц на который сами жалуетесь.
Ресурсы они на то и есть чтобы их использовать. А зачем они ещё? солить впрок? :)))

Добавлено after 12 minutes 44 seconds:
Две половины одного буфера, или два отдельных буфера - не суть важно, по-любому, это двойная буферизация.

Совершенно неверно!
"Два буфера" - это фактически даже хуже чем вообще без DMA, по прерываниям. Так как требует времени реакции от ISR не превышающего 1/Ts (где Ts - период сэмплирования). Такое же требование и для работы без DMA, по прерываниям. Но в случае с "двумя буферами" всё гораздо хуже, так как операций внутри ISR придётся сделать гораздо больше (перепрограммирование DMA). И в этом случае теряются почти все плюсы DMA.
А вот "две половинки одного буфера" - совсем другое дело, так как в этом случае от ISR требуется время реакции <= размер_половины_буфера/Ts.

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 15:58:39

А смысл? Зачем?? :dont_know:


Что, действительно не въезжаешь? :)))

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 16:01:07

Что, действительно не въезжаешь? :)))

Не въезжаю.
Видимо - чтобы посильнее загрузить шины МК? :?

Re: Аудио-плеер. PWM-DAC + DMA

Вс окт 07, 2018 16:03:52

"Нагрузка на шины" точно такая же что и в варианте с двумя таймерами. Здесь же один.
Причем можно еще прикрутить DMA burst -получится стерео и дополнить каждый канал инверсным -повысим громкость .
Ты не смотри со своей колокольни, здесь же простой плеер вавок.
Последний раз редактировалось dosikus Вс окт 07, 2018 16:29:15, всего редактировалось 1 раз.
Ответить