Вт окт 02, 2018 00:44:18
Вт окт 02, 2018 06:58:03
Вт окт 02, 2018 14:40:24
Вт окт 02, 2018 17:31:47
Ср окт 03, 2018 15:27:35
Реквесты канала DMA по TIMx_UP
Ср окт 03, 2018 18:40:41
Ср окт 03, 2018 22:45:11
Чт окт 04, 2018 06:49:30
Чт окт 04, 2018 09:47:33
Сб окт 06, 2018 03:32:57
Подозреваю, что "помехи" проистекают от задержки на чтение очередной порции данных с SD-шки. В таких случаях помогает двойная буферизация.marengo писал(а):При любой частоте дискретизации проскакивают сильные помехи, которые возникают при чтении с SD-карты!
Сб окт 06, 2018 23:22:24
TIM1->PSC = 0;
TIM1->ARR = 0xFF;//сброс таймера при достижении данного значения (глубина звука)
TIM1->RCR = (F_IN_TIM1 / (TIM1->PSC+1)) / (TIM1->ARR+1) / sampleRate;// - 1;
вообще ничего не понял.В таких случаях помогает двойная буферизация
Вс окт 07, 2018 03:06:26
Я имел в виду вот это:marengo писал(а):вообще ничего не понял.
Две половины одного буфера, или два отдельных буфера - не суть важно, по-любому, это двойная буферизация.dosikus писал(а):В любом случае отслеживаем половину буфера и завершение транзакции ,или поллингом флагов DMA либо в прерывании.
Так называемый пинг-понг - пока воспроизводится одна половина буфера - заполняем другую...
Вс окт 07, 2018 07:33:15
/* Попытка прочитать и воспроизвести аудио-файл.
* 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 прерывания
}
/* Попытка прочитать с 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 при возникновении события
}
Вс окт 07, 2018 07:53:21
DMA1->IFCR |= DMA_ISR_TCIF5; //очистить флаг окончания обмена
DMA1->IFCR |= DMA_ISR_HTIF5; //очистить флаг передачи половины буфера
Вс окт 07, 2018 09:37:25
DMA1->IFCR = DMA_IFCR_TCIF5 | DMA_IFCR_HTIF5;
Вс окт 07, 2018 09:50:06
Вс окт 07, 2018 15:38:46
Вс окт 07, 2018 15:58:39
Вс окт 07, 2018 16:01:07
Вс окт 07, 2018 16:03:52