Зарегистрирован: Пт май 20, 2016 02:44:46 Сообщений: 12
Рейтинг сообщения:0
Здравствуйте, уважаемые радиокоты. Осваиваю тут потихоньку МК 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 следующий байт. А как быть с регистром сравнения какого-нибудь таймера или с аппаратным ЦАП-ом какого-нибудь другого МК данной конторы.
Сперва для 8бит моно.Таймер конфигурируем как PWM. Частота PWM -частота дискретизации звука, лежит в заголовке wav. Реквесты канала DMA по TIMx_UP. Источник буфер звука, приемник CCP таймера, счетчик - размер буфера. Режим не циклический. CCP тамера буферизированный
8бит стерео, здесь придется задействовать DMA burst.
В любом случае отслеживаем половину буфера и завершение транзакции ,или поллингом флагов DMA либо в прерывании. Так называемый пинг-понг - пока воспроизводится одна половина буфера - заполняем другую...
Зарегистрирован: Пт май 20, 2016 02:44:46 Сообщений: 12
Рейтинг сообщения:0
То-есть используется только один таймер: и для вывода во внешний мир ШИМ-сигнала, и для "пинания" DMA? Тогда получается частота несущей == частоте модуляции (дискретизации звука). Так? Вроде как, чем больше частота ШИМ-сигнала тем проще его преобразовать (сгладить) в аналоговый. Поэтому я и хотел сделать частоту несущей выше частоты дискретизации звука.
По Вашему способу решения вопроса: Предполагаю что, аудио-файлы с частотой дискретизации звука более 20kHz будут воспроизводиться нормально, возможно даже без фильтров! А как быть с частотами дискретизации в диапазоне 20 - 20 000 Hz? Например, частота дискретизации 8kHz считается достаточной для передачи голоса. Не будут ли слышны изъяны в таком случае, когда частота несущей == частоте модуляции?
Зарегистрирован: Пт май 20, 2016 02:44:46 Сообщений: 12
Рейтинг сообщения:0
В общем всё получилось! Воспроизведение звука сделал с использованием одного таймера, который работает в режиме ШИМ и "пинает" DMA на частоте дискретизации звука. Аудио-файлы с частотой дискретизации 22050Hz и 44100Hz воспроизводятся нормально — звучание достойное! Пробовал файлы 8000Hz, слышно несущую 8 килоГерц (свист). Надо ставить фильтр — это минус такой реализации, так сказать дань за использование только одного таймера. При любой частоте дискретизации проскакивают сильные помехи, которые возникают при чтении с SD-карты! Но это к программированию уже не относится! Как именно это решить я пока не знаю. Позже смогу выложить исходники, тем более если это будет интересно читателям.
Цитата:
Реквесты канала DMA по TIMx_UP
dosikus, спасибо за эту фразу и за идею использовать только один таймер! Пытался тебя плюсануть, ничего не получается, flash зависает, может с другого браузера получится.
На счет помех -у меня их нет. Карточка подключена через отдельный стаб , подтяжки везде кроме CLK. Время выкрою попробую на 8КГц, но помнится свиста не было. Да у меня F0,a так же делал вариант на F1. Завтра не забуду скину. И о дма_бурст http://mcu.goodboard.ru/viewtopic.php?id=31
Аудио-файлы с частотой дискретизации 22050Hz и 44100Hz воспроизводятся нормально — звучание достойное! Пробовал файлы 8000Hz, слышно несущую 8 килоГерц (свист).
Какой смысл привязываться к частоте дискретизации источника? И ещё и перестраивать частоту под каждый источник. Делайте вывод на любой частоте какая нравится. А входной сигнал нетрудно передискретизировать на нужную частоту. Мой проект (интернет-радио) воспроизводит с любых источников (MP3- или AAC-) независимо от их частоты. Частота ЦАП стоит 98кГц - никаких свистов не слышно. Использую встроенный ЦАП, а не ШИМ. Фильтров никаких нет (правда они есть в самих декодерах).
имхо, DMA надо пинать с частотой дискретизации файла/потока, а таймер ШИМом должен молотить на фиксированной частоте, как можно более высокой. и свиста не будет, и качество будет предельно достижимое технически
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
имхо, DMA надо пинать с частотой дискретизации файла/потока, а таймер ШИМом должен молотить на фиксированной частоте, как можно более высокой. и свиста не будет, и качество будет предельно достижимое технически
Бред. Частота изменения выходного сигнала останется прежней. Как было 8 кГц, так и останется. Куда тогда денется свист? DMA должна "пинать" периферия, которую он обслуживает. В данном случае - таймер. С частотой работы этой самой периферии. И соответственно сэмплы в буфере отправляемом DMA, должны идти с той же частотой. Ресэмплинг этого буфера на данную частоту делается CPU (предварительно). Можно простой кусочно-линейной интерполяцией (как у меня), можно другим методом (более качественно). Таймер должен работать в shadow-режиме. Вот тогда всё будет ок.
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), я не знаю! Нет, конечно можно завести рядом с МК какой-нибудь внешний генератор с требуемой частотой, но опять же минус — ещё один компонент на плате. Но меня такой результат пока что устраивает!
входной сигнал нетрудно передискретизировать на нужную частоту
"Передискретизировать на нужную частоту" — означает выполнять дополнительную работу, то-есть использовать ресурсы ЦПУ! Понимаете, те проекты по воспроизведению звука что я делаю сейчас — это тест-проекты, проекты-шпаргалки на будущие, называйте это как хотите. В каждом таком проекте свой способ выполнения такой абстрактной задачи как "воспроизведение звука" из какого-нибудь источника. Пока что замарочился только с SD-картами. Как известно, в каждом способе реализации могут быть свои как плюсы, так и минусы! А для каждой конкретной задачи какой-то способ воспроизведения звука будет подходить больше, а какой-то меньше! В Вашем случае
имхо, DMA надо пинать с частотой дискретизации файла/потока, а таймер ШИМом должен молотить на фиксированной частоте, как можно более высокой
"более высокой" ARV видимо имел ввиду частоты выше, хотя бы, 20-ти килоГерц. А так да, чем больше тем лучше! Пожалуй каждый кто сталкивался с ШИМ, об этом знает.
Подозреваю, что "помехи" проистекают от задержки на чтение очередной порции данных с SD-шки. В таких случаях помогает двойная буферизация.
Помехи возникают именно в моменты чтения SD-карты! Причём если увеличить буфер чтения, то частота возникновения этих помех уменьшается на столько, на сколько увеличен буфер! Частота самой помехи остаётся прежней! Ну да ладно, доберусь до задачи "качества звучания", вопрос "помехи" при чтении с SD-карты будет решён, надеюсь, простыми мероприятиями:
- отдельное питание для SD-карты. А она, кажись, может потреблять до 100mA, а у меня на макетке стоит AMS1117... ыыыыыы
Карма: 13
Рейтинг сообщений: 163
Зарегистрирован: Сб дек 22, 2012 08:17:42 Сообщений: 744 Откуда: Караганда, Казахстан
Рейтинг сообщения:0
marengo писал(а):
вообще ничего не понял.
Я имел в виду вот это:
dosikus писал(а):
В любом случае отслеживаем половину буфера и завершение транзакции ,или поллингом флагов DMA либо в прерывании. Так называемый пинг-понг - пока воспроизводится одна половина буфера - заполняем другую...
Две половины одного буфера, или два отдельных буфера - не суть важно, по-любому, это двойная буферизация.
_________________ Кто мешает тебе выдумать порох непромокаемый? (К. Прутков, мысль № 133)
Зарегистрирован: Пт май 20, 2016 02:44:46 Сообщений: 12
Рейтинг сообщения:0
afz Проблем с логикой работы программы у меня не было! Были вопросы по настройке работы периферии (и не надо тыкать меня носом в "CubeMX"). Но всё равно спасибо!
Короче, выкладываю исходники. Сначала тот вариант, который был до того как я создал эту тему. Шпора "PWM_wav0.3" Спойлер
Код:
/* Попытка прочитать и воспроизвести аудио-файл. * WAV 22.05kHz, 8bit, mono PCM, без сжатия. * Используются два буфера поперемено: один для чтения, один для воспроизведения. * Только прерывания, без DMA. * Таймер4 в качестве ЦАПа! * Таймер3 настраивается в соответствии с частотой дискретизации * воспроизводимых файлов! * Чтение данных с SD-карты без DMA и по SPI! * Заголовки wav-файла не читаем, пропускаем. */
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;
/* Попытка прочитать с 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"
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();//в следующей итерации будем читать в другую половину буфера! }
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. Так, если кто копирует исходники, те ставят мне "плюс"! dosikus-а тоже можно плюсовать, без него я ещё долго не мог бы прикрутить DMA к своему проекту!
Две половины одного буфера, или два отдельных буфера - не суть важно, по-любому, это двойная буферизация.
Совершенно неверно! "Два буфера" - это фактически даже хуже чем вообще без DMA, по прерываниям. Так как требует времени реакции от ISR не превышающего 1/Ts (где Ts - период сэмплирования). Такое же требование и для работы без DMA, по прерываниям. Но в случае с "двумя буферами" всё гораздо хуже, так как операций внутри ISR придётся сделать гораздо больше (перепрограммирование DMA). И в этом случае теряются почти все плюсы DMA. А вот "две половинки одного буфера" - совсем другое дело, так как в этом случае от ISR требуется время реакции <= размер_половины_буфера/Ts.
"Нагрузка на шины" точно такая же что и в варианте с двумя таймерами. Здесь же один. Причем можно еще прикрутить DMA burst -получится стерео и дополнить каждый канал инверсным -повысим громкость . Ты не смотри со своей колокольни, здесь же простой плеер вавок.
Последний раз редактировалось dosikus Вс окт 07, 2018 16:29:15, всего редактировалось 1 раз.
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 15
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения