Разработка процессора звуковых эффектов на STM32F407VET6
- Войдите на сайт для отправки комментариев
Втр, 25/05/2021 - 18:41
Хотя в данном разделе рекомендуется размещать готовые проекты, но существeет и немало проектов в процессе разработки. Именно к таким и относится текущий проект.
Конечно, хорошо, когда все и сразу. Но нередко проект занимает не один месяц, а то и год. Если его не начать публиковать в процессе, к моменту публикации многие тонкости и особенности будут благополучно забыты. Для простого проекта это, возможно, и не существенно, а для сложного, особенно такого, который из-за сложности вряд ли будет повторен целиком, именно отдельные особенности, которые описывают сложности в отдельных вопросах, и могут оказаться наиболее востребованными при фрагментарном повторении проекта или просто при заимствовании идей или обмене опытом.
Поэтому начинаю публикацию почти сразу после появления первого рабочего прототипа.
// Ввод-вывод звукового стереосигнала через штатные АЦП/ЦАП контроллера stm32f407vet6 // + ФНЧ с регулируемыми частотой и добротностью // //============================================= // PA4-PA5 - Audio OUT 2 (a4,a5) // PA6-PA7 - LED 2 (a6,a7) // PE3,PE4 - Button 2 // PC0,PC4 - Audio In 2 (a10,a14) // PA0-PA3 - Analog In 4 (a0-a3) [+timer2] // PC2,PC3 - Analog In 2 (a12,a13) [+SPI2] // PC1 - LED (Red) 1 //..Analog In 1 (a11) // VR-,VR+ // PB10 - LED (Ylw) 1 //..t2 [+SPI2+I2C] // PE2,PE5,PE6,PC13 - Switch 4 // PB11 - t2cc4 out 1 // [+I2C] 1 // // PB0 - flash cs (a8) // PC5,PB1,PE7-PE15,PB12-PB14 - Display 14 (a15,a9) #include <libmaple\\dmaF4.h> #include "STM32F4xx_RCC.H" #include "STM32F4xx_ADC.H" #include "STM32F4xx_DMA.H" #include "STM32F4xx_TIM.H" #include "stm32f4xx_dac.h" #include "stm32f4xx_gpio.h" #include "prnt407.h" #include "FilterAD_F.h" #define COEFF_TO_24 4096 // коэффициент для приведения звука к 24-разрдному формата (8.16) (при значении 8192 искажений нет или почти нет) #define FULL_BUFFER_SIZE 32 // полная длина буфера в двухбайтовых отсчетах (32 слова = 64 байта) #define BYTE_HALF_BUFFER_SIZE FULL_BUFFER_SIZE // половины буфера в байтах ( = 64 байта) #define HALF_BUFFER_SIZE (FULL_BUFFER_SIZE / 2) // длина половины буфера (16 слов = 32 байта) #define HALF_BUFFER_SAMPLES (HALF_BUFFER_SIZE / 2) // количество стереосэмплов в половине буфера ( 8 стереоотсчетов) int16_t buffer[FULL_BUFFER_SIZE]; // входной буфер (ADC) #define DAC_BUFFER_SIZE FULL_BUFFER_SIZE // #define DAC_HALF_BUFFER_SIZE (DAC_BUFFER_SIZE / 2) int16_t DACbuff[DAC_BUFFER_SIZE]; // выходной буфер (DAC) timer_adv_reg_map* MyTimer((timer_adv_reg_map*)TIMER2_BASE); uint32_t MyTrigger(DAC_Trigger_T2_TRGO); /* Private function prototypes -----------------------------------------------*/ static void DMA_Config(int16_t * buff0); static void GPIO_Config(void); static void ADC_COMMON_Config(void); static void ADC1_Config(uint8_t ch_A); static void ADC2_Config(uint8_t ch_A); static void TIM2_Config(void); static void DAC_Config(void); int counterDMA = 0; tFulter_LF2i LF, RF; // фильтры - левого и правого каналов void handler_DMA(void) { // приаттачен к заполнению очередной паоловины входного буфера digitalWrite(PA7, LOW); // индицируем наружу вход в прерывание int16_t * in_buff; int16_t * out_buff; if((DMA2_BASE->LISR >> 4) & 1) { // Half (1-st half-buffer free) in_buff = buffer; out_buff = DACbuff; } else { // Full (2-nd half-buffer free) in_buff = &buffer[HALF_BUFFER_SIZE]; out_buff = &DACbuff[HALF_BUFFER_SIZE]; } for(int i = 0; i < HALF_BUFFER_SAMPLES; i++) { // переписываем из входного буфера в выходной, пропуская через ФНЧ int sample_L, sample_R; // временные 24-разрядные значения звуковых отсчетов левого и правого каналов sample_L = (in_buff[i*2 + 0] - 2048)*COEFF_TO_24; // ЛЕВЫЙ КАНАЛ: приводим диапазо 0:4095 -> -2048:2047 -> -131072:131071, формат (2.16) sample_L = LF.iir32(sample_L); out_buff[i*2 + 0] = sample_L/COEFF_TO_24 + 2048; sample_R = (in_buff[i*2 + 1] - 2048)*COEFF_TO_24; // ПРАВЫЙ КАНАЛ sample_R = RF.iir32(sample_R); out_buff[i*2 + 1] = sample_R/COEFF_TO_24 + 2048; } counterDMA++; digitalWrite(PA7, HIGH); } void setup(void) { Serial.begin(115200); delay(200); // правильнее здесь будет сделать while (!Serial) {} LF.SetFilterCoeff_32(39, 200); RF.SetFilterCoeff_32(39, 200); delay(200); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); GPIO_Config(); DMA_Config(buffer); // для АЦП ADC_COMMON_Config(); ADC1_Config(ADC_Channel_10); ADC2_Config(ADC_Channel_14); // Enable DMA request after last transfer (Multi-ADC mode) / ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE); // ADC_DMAReq.. + + + ADC_DMAReq - dma_attach_interrupt(DMA2, DMA_STREAM0, handler_DMA); DAC_Config(); ADC_Cmd(ADC1_BASE, ENABLE); ADC_Cmd(ADC2_BASE, ENABLE); // ADC_SoftwareStartConv(ADC1_BASE); // + + + + + + TIM2_Config(); printTimer2(); } extern uint32_t numChanges; void loop() { static unsigned long prevMillisLED = 0; //millis(); const unsigned long interval_LED = 500; // interval at which to blink (milliseconds) unsigned long currentMillis = millis(); if (currentMillis - prevMillisLED >= interval_LED) { prevMillisLED = currentMillis; static int i = 0; digitalWrite(PA6, (i & 1)); i++; } static unsigned long prevMillisFilter = 0; //millis(); const unsigned long intervalFilter = 15; // interval at which to blink (milliseconds) if (currentMillis - prevMillisFilter >= intervalFilter) { prevMillisFilter = currentMillis; int r0 = CheckControl(0); int r1 = CheckControl(1); LF.SetFilterCoeff_32((r1*3)/32, r0 + 64); RF.SetFilterCoeff_32((r1*3)/32, r0 + 64); } } static void ADC_COMMON_Config(void){ ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_CommonStructInit(&ADC_CommonInitStructure); /* ADC Common Init */ // VBAT TRI Dual Interl DMA Camera ADC_CommonInitStructure.ADC_Mode = ADC_DualMode_RegSimult; // Independ Triple + Interl Independ Independ ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2; // + + + + + Div6 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_1; // Disabled 2 + 3 Disabled Disabled ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; // + + + 6Cycl + + ADC_CommonInit(&ADC_CommonInitStructure); Serial.println(" = ADC common OK!"); } static void ADC1_Config(uint8_t channel) { Serial.print(" ADC1_CH10_CH11_Config "); Serial.println((uint32_t)ADC1_BASE, HEX); Serial.print(" ADC1_BASE->CR2 "); Serial.println((uint32_t)&(ADC1_BASE->CR2), HEX); printADC(ADC1_BASE, "begin config"); ADC1_BASE->CR2 = 0x00000001; // ADC_CR2_ADON; // = 0x00000001 - убираем установленные по умолчанию JEXT:0x001E0000 ADC1_BASE->SMPR1 = 0; ADC1_BASE->SMPR2 = 0; printADC(ADC1_BASE, "second string"); ADC_InitTypeDef ADC_InitStructure; ADC_StructInit(&ADC_InitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; // + + + 8b + 8b ADC_InitStructure.ADC_ScanConvMode = DISABLE; //ENABLE; // Disable Disable + Disable Disable Disable ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //ENABLE; // + + + + + + ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising; //None; // + + + + + + ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO; //CC2; // + + + + + - ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // + + + + + + ADC_InitStructure.ADC_NbrOfConversion = 1; // 1 1 + 1 1 1 ADC_Init(ADC1_BASE, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1_BASE, channel, 1, ADC_SampleTime_112Cycles); // VBAT/15cyc + + + + 56cyc Serial.println(" = ADC1 OK!"); } static void ADC2_Config(uint8_t channel) { ADC2_BASE->CR2 = ADC_CR2_ADON; // = 0x00000001 - убираем установленные по умолчанию JEXT:0x001E0000 ADC2_BASE->SMPR1 = 0; ADC2_BASE->SMPR2 = 0; ADC_InitTypeDef ADC_InitStructure; ADC_StructInit(&ADC_InitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode = DISABLE; //ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //ENABLE; ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(ADC2_BASE, &ADC_InitStructure); ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_Rising; // ???? почему триггер инициализируется именно для ADC2 ??? ADC_RegularChannelConfig(ADC2_BASE, channel, 1, ADC_SampleTime_112Cycles); Serial.println(" = ADC2 OK!"); } static void DMA_Config(int16_t * buff0) { DMA_InitTypeDef DMA_InitStructure; DMA_StructInit(&DMA_InitStructure); Serial.print("*C* buff0 "); Serial.println((uint32_t)buff0, HEX); Serial.print("DMA1_BASE "); Serial.println((uint32_t)DMA1_BASE, HEX); Serial.print("DMA1_Stream0 "); Serial.println((uint32_t)DMA1_Stream0, HEX); Serial.print("DMA2_BASE "); Serial.println((uint32_t)DMA2_BASE, HEX); Serial.print("DMA2_Stream0 "); Serial.println((uint32_t)DMA2_Stream0, HEX); Serial.print("DMA2_BASE->STREAM[0] "); Serial.println((uint32_t)&(DMA2_BASE->STREAM[0]), HEX); Serial.print("DMA2_Stream0_BASE "); Serial.println((uint32_t)DMA2_Stream0_BASE, HEX); Serial.print("DMA_GetCmdStatus "); Serial.println(DMA_GetCmdStatus(DMA2_Stream0), HEX); DMA_InitStructure.DMA_Channel = DMA_Channel_0; // + + + + + DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buff0; // &aADCDualConvertedValue; // + + + + + DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC_COMMON->CDR; // // DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1_BASE->DR; // + + + + + DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; // + + + + + DMA_InitStructure.DMA_BufferSize = FULL_BUFFER_SIZE; // 1 3 4 1 1 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // + + + + + DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // Disable + + Disable Disable DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // + Word + + + DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // + Word + + + DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // + + + + + DMA_InitStructure.DMA_Priority = DMA_Priority_High; // + + + + + DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //Enable; // Disable Disable + Disable Disable DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // + + + + + DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // + + + + + DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // + + + + + DMA_Init(DMA2_Stream0, &DMA_InitStructure); Serial.println(" DMA MASTER: ADC (DMA2->Stream[0])"); DMA_ITConfig(DMA2_Stream0, DMA_IT_TC | DMA_IT_HT, ENABLE); // TransfComplete + Half // - - - - - DMA_Cmd(DMA2_Stream0, ENABLE); Serial.println(" = DMA OK!"); } static void DAC_Config(void) { DAC_InitTypeDef DAC_InitStructure; DAC_StructInit(&DAC_InitStructure); DMA_InitTypeDef DMA_InitStructure; DMA_StructInit(&DMA_InitStructure); DAC_InitStructure.DAC_Trigger = MyTrigger; // DAC_Trigger_T6_TRGO; DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; DAC_Init(DAC_Channel_1, &DAC_InitStructure); DAC_Init(DAC_Channel_2, &DAC_InitStructure); /* DMA1_Stream5 channel7 configuration **************************************/ DMA_DeInit(DMA1_Stream5); DMA_InitStructure.DMA_Channel = DMA_Channel_7; DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&DACbuff; DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_ADDRESS; DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; DMA_InitStructure.DMA_BufferSize = DAC_HALF_BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //HalfWord; // _Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //HalfWord; // _Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA1_Stream5, &DMA_InitStructure); DMA_Cmd(DMA1_Stream5, ENABLE); DAC_Cmd(DAC_Channel_1, ENABLE); DAC_Cmd(DAC_Channel_2, ENABLE); DAC_DMACmd(DAC_Channel_1, ENABLE); // Enable DMA for DAC Channel1 Serial.println(" = DAC OK!"); } static void GPIO_Config(void) { // GPIO_InitTypeDef GPIO_InitStructure; // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; // GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; // GPIO_Init(GPIOC, &GPIO_InitStructure); pinMode(PC0, INPUT_ANALOG); // (A10) Left Channel Audio Input pinMode(PC4, INPUT_ANALOG); // (A14) Right Channel Audio Input pinMode(PA4, INPUT_ANALOG); // (A-4) Left Channel Audio Output pinMode(PA5, INPUT_ANALOG); // (A-5) Right Channel Audio Output pinMode(PC2, INPUT_ANALOG); // (A12) Control Input 1 pinMode(PC3, INPUT_ANALOG); // (A13) Control Input 2 pinMode(PA3, INPUT_ANALOG); // (A3) Control Input 3 pinMode(PA2, INPUT_ANALOG); // (A2) Control Input 4 pinMode(PA1, INPUT_ANALOG); // (A1) Control Input 5 pinMode(PA0, INPUT_ANALOG); // (A0) Control Input 6 pinMode(PA6, OUTPUT); // LED 0 pinMode(PA7, OUTPUT); // LED 1 pinMode(PC1, OUTPUT); // LED 2 (A11) pinMode(PB10, OUTPUT); // LED 3 pinMode(PB11, PWM); // таймер GPIO_PinAFConfig(GPIOB_BASE, GPIO_PinSource11, GPIO_AF_TIM2); // pinMode(PE2, INPUT_PULLUP); // Switch 0 // было: для импульса - индикатора пересылки первой половины буфера pinMode(PE5, INPUT_PULLUP); // Switch 1 // было: для импульса - индикатора пересылки второй половины буфера pinMode(PE6, INPUT_PULLUP); // Switch 2 pinMode(PC13, INPUT_PULLUP); // Switch 3 pinMode(PE3, INPUT_PULLUP); // Button 1 pinMode(PE4, INPUT_PULLUP); // Button 0 Serial.println(" = GPIO OK!"); } static void TIM2_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); /* -------------------------------------------------------- TIM2 input clock (TIM2CLK) is set to 2 * APB1 clock (PCLK1), since APB1 prescaler is different from 1. TIM2CLK = 2 * PCLK1 TIM2CLK = HCLK / 2 = SystemCoreClock /2 TIM6 Update event occurs each TIM2CLK/256 ----------------------------------------------------------- */ /* Time base configuration */ TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Period = 874; //0xFF; (Prescaler=1(2), при 2500 - 16.8к, при 438 - 96к) TIM_TimeBaseStructure.TIM_Prescaler = 0; // 16800; //0; (Period=2500, при 16800 - 2 Гц, при 4200 - 8 Гц, при 1050 - 32 Гц, при 1(0) - 16.8 кГц, ) TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(MyTimer, &TIM_TimeBaseStructure); //======================================================================== /* PWM1 Mode configuration: Channel1 */ TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCStructInit(&TIM_OCInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; /* PWM1 Mode configuration: Channel4 */ TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 83; //CCR4_Val; TIM_OC4Init((timer_adv_reg_map*)TIMER2_BASE, &TIM_OCInitStructure); TIM_OC4PreloadConfig((timer_adv_reg_map*)TIMER2_BASE, TIM_OCPreload_Enable); TIM_ARRPreloadConfig((timer_adv_reg_map*)TIMER2_BASE, ENABLE); //======================================================================== /* TIM2 TRGO selection */ TIM_SelectOutputTrigger(MyTimer, TIM_TRGOSource_Update); // TIM_ITConfig(MyTimer, TIM_IT_Trigger, ENABLE); // Timer2.pause(); Timer2.setCompare(TIMER_CH4, TIM_TimeBaseStructure.TIM_Period / 2); // somewhere in the middle Timer2.refresh(); Timer2.resume(); /* TIM2 enable counter */ TIM_Cmd(MyTimer, ENABLE); Serial.println(" = TIMER_2 OK!"); }Таймер 2 задает частоту дискретизации 96кГц, подавая ее на ADC, данные ADC контроллер DMA переносит во входной буфер в ОЗУ. По заполнении первой половины буфера происходит аппаратное прерывание, в котором данные первой половины буфера обрабатываются и переносятся в выходной буфер. Одновременно с записью данных с ADC во входной буфер контроллер DMA считывает данные из выходного буфера и отправляет их в порт DAC. Причем после первого прерывания контроллер DMA уже работает со вторыми половинами входного и выходного буферов. По заполнении второй половины снова генерится прерывание и МК теперь может обработать вторые половины буферов, пока записываются первые.
На данном этапе звук претерпевает простейшую обработку - пропускается через ФНЧ с управляемыми аналоговыми органами управления частотой и добротностью. Для расчета фильтров используются таблицы с заранее насчитанными коэффициентами. Правда, эти таблицы взяты мною из другого проекта, где частота дискретизации составляла 48 кГц. Поэтому при работе таблиц в данном проекте они обеспечивают диапазон резонансной частоты фильтра не от 20 Гц до 16 кГц, а от 40 Гц до 32 кГц.
файл фильтра FilterAD_F.h
#ifndef FILTERAD_H #define FILTERAD_H #include <Arduino.h> extern bool FilterADdebug; // лог настройки фильтра - обязательно выключить перед loop() class tFulter_LF2i { // ФНЧ 2-го порядка : integer(SmallInt) public: int32_t iir32(int32_t NewSample); // БИХ-фильтр, вычисляет один следующий отсчет fixed(8.16) (fixed(1.16)) void SetFilterCoeff(double q, double f); // установка добротности и частоты ФНЧ 2-го порядка для частоты дискретизации 48000 void SetFilterCoeff_32(uint32_t q, uint32_t f); // установка по номеру в таблице (файлы SoftFilQ.c, SoftFilter.c) private: int32_t AC_0, AC_1, BC_1, BC_2; // коэффициенты фильтра A : fixed(8.24), B : fixed(16.16) int32_t x_2, x_1, x_0; // используемые отсчеты фильтра: входные fixed(24.8) (fixed(17.8)) int32_t y_2, y_1, y_0; // используемые отсчеты фильтра: выходные fixed(16.16) (fixed(17.16)) }; void getTimes(int &dt0, int &dt1, int &dt2, int &dt3); // статистика времеени выполнения #endifи файл фильтра FilterAD_F.cpp
#include "FilterAD_F.h" // цифровой фильр (БИХ) целочисленный #include "SoftFilQ.c" #include "SoftFilter.c" #define Pi 3.1415926535897932384626433832795 bool FilterADdebug = false; // лог настройки фильтра - обязательно выключить перед loop() uint32_t t0,t1,t2,t3,t4; // вспомогательные переменные для анализа времени выполнения int32_t tFulter_LF2i::iir32(int32_t NewSample) { // БИХ-фильтр, вычисляет один следующий отсчет, вход fixed(1.16), выход fixed(.) union { int64_t q; // присвоение 0.1us, арифм.операция 0.15us, int32_t d[2]; // присв. 8, операций 10 = 0.8+1.5 = 2.3 us } tmp; x_2 = x_1; // fixed(1.16) сдвигаем предыдущие отсчеты y_2 = y_1; // fixed(8.24) x_1 = x_0; // fixed(1.16) y_1 = y_0; // fixed(8.24) x_0 = NewSample; // fixed(1.16) запоминаем очередное значение // (9.40) = (8.24)*(1.16) (8.24)*(1.16) (16.16)*(8.24) (16.16)*(8.24) (16.16)*(8.24) <<16 tmp.q = (AC_0*int64_t(x_0) + AC_1*int64_t(x_1) - BC_1*int64_t(y_1) + AC_0*int64_t(x_2) - BC_2*int64_t(y_2))*65536; // / 65536; y_0 = tmp.d[1]; // fixed(8:24) берем ситарщие 32 разряда 64-разрядного слова return y_0 >> 8; /// fixed(8.16); } void tFulter_LF2i::SetFilterCoeff(double q, double f) { // установка добротности и частоты ФНЧ 2-го порядка для частоты дискретизации 48000 //begin // A0 = beta^2 // A1 = 2*beta^2 // A2 = beta^2 // B0 = 1 + beta/Q + beta^2 // B1 = 2*beta^2 - 2 // B2 = 1 - beta/Q + beta^2 // beta = alp*tg(Pi/2*f/Fn) t0 = micros(); double beta = tan(Pi*f/48000.0); t1 = micros(); double betaQ = beta/q; double beta2 = beta*beta; double BC = (1.0 + betaQ + beta2); t2 = micros(); //// double b0 = (1.0 + betaQ + beta2)*65536; // 16.16 double b1 = (2.0*beta2 - 2.0)/BC*65536; // 16.16 double b2 = (1.0 - betaQ + beta2)/BC*65536; // 16.16 double a0 = beta2/BC*65536*256; // 8.24 double a1 = 2.0*beta2/BC*65536*256; // 8.24 // double a2 = beta2/BC*65536*256; // 8.24 t3 = micros(); AC_0 = round(a0); AC_1 = round(a1); // AC_2 = round(a2); BC_1 = round(b1); BC_2 = round(b2); t4 = micros(); if(FilterADdebug) { Serial.print("FilterAD q: "); Serial.print(q); Serial.print(", f: "); Serial.print(f); Serial.println(); } } void tFulter_LF2i::SetFilterCoeff_32(uint32_t q, uint32_t f) { // установка добротности и частоты ФНЧ 2-го порядка для частоты дискретизации 48000 // A0 = beta^2 // A1 = 2*beta^2 // A2 = beta^2 // B0 = 1 + beta/Q + beta^2 // B1 = 2*beta^2 - 2 // B2 = 1 - beta/Q + beta^2 // beta = alp*tg(Pi/2*f/Fn) uint32_t beta = FilterCoef[f][1]; //tan(Pi*f/48000.0); (fixed:16.16) uint32_t betaQ = beta*FilterQ[q]; // (fixed:16.16)*(fixed:20.12) = (fixed:4.28) !!! пока для (8.24) uint32_t beta2 = FilterCoef[f][2]; // (fixed:4.28) !!! пока для (8.24) uint32_t BC = (uint64_t)0x0100000000000000/(0x10000000 + betaQ + beta2); // (16.48)/(8.24) = (8.24) +> (8.56)/(4.28) = (4.28) // double b0 = (1.0 + betaQ + beta2)*65536; // 16.16 if(beta2 >= 0x10000000) BC_1 = (uint64_t)(2*beta2 - 0x20000000)*BC/0x10000000000; // (8.24)*(8.24)/2^32 = (16.48)/2^32 = (16.16) +> (4.28)*(4.28)/2^40 = (8.56)/2^40=(16.16) else BC_1 = -(int32_t)((uint64_t)(0x20000000 - 2*beta2)*BC/0x10000000000); // 16.16 BC_2 = (uint64_t)(0x10000000 - betaQ + beta2)*BC/0x10000000000; // 16.16 AC_0 = (uint64_t)beta2*BC/0x0100000000; // 8.24 (8.24)*(8.24)/2^24 = (8,24) +> (4.28)*(4.28)/2^32 = (8.56)/2^32 = (8.24) AC_1 = (uint64_t)(2*beta2)*BC/0x0100000000; // 8.24 // AC_2 = AC_0; // 8.24 if(FilterADdebug) { Serial.print("Filter_32 q: "); Serial.print(q); Serial.print(", f: "); Serial.print(f); Serial.print(", beta: "); Serial.print(beta); Serial.print(", beta2: "); Serial.print(beta2); Serial.print(", betaQ: "); Serial.print(betaQ); Serial.print(", BC: "); Serial.println(BC); Serial.print("FilterAD AC_0: "); Serial.print(AC_0); Serial.print(", AC_1: "); Serial.print(AC_1); Serial.print(", BC_0: "); Serial.print(BC); Serial.print(", BC_1: "); Serial.print(BC_1); Serial.print(", BC_2: "); Serial.print(BC_2); Serial.println(); } } void getTimes(int &dt0, int &dt1, int &dt2, int &dt3) { dt0 = t1-t0; dt1 = t2-t1; dt2 = t3-t2; dt3 = t4-t3; }Рис.4. А теперь отладка со звуком, идущим с MP3-плеера.
Интересный проект. Для прокачки скилов самое то.
А почему не пошли по пути применения для обработки звука спец ДСП ADAU1701 как самый народный или ADAU1452?
А на STM сделать интерфейс пользователя?. Пошли по этому пути и получился очень достойный пгрегат с большими возможностями.
Есть у меня ADAU1401 DSP mini board, но что-то я не нашел внятной документации собственно по командам. Обычно рекомендуют программировать при помощи утилиты SigmaStudio, но как-то это не очень интересно.
Опять же, насколько я понял, там практически линейный код, а максимальная длина этого кода, опять же, не позволяет сделать некоторые интересные штуки вроде ревербератора или линии задержки.
Ну и, кроме того, ARM сам содержит целый набор DSP-шных команд.
В общем, мне показалось, что возможностей для творчества с stm32 больше.
Если будет интересно могу показать какой веб интерфейс управления усилителем с ДСП получился. Пиши в личку.
Пиши в личку.
Может, это и было бы смешно, если бы не исходило от человека, который 5 лет на форуме.
Какова цель данного "Проекта"?
Создать устройство с какой то функциональностью ? Тогда почему не написано в самом начале? Какие звуковые сигналы с какой целью планируется обрабатывать разрабатываемым "процессором звуковых эффектов".
Освоить принципы обработки звука на STM32 ?
Какова цель данного "Проекта"?
Создать устройство с какой то функциональностью ? Тогда почему не написано в самом начале? Какие звуковые сигналы с какой целью планируется обрабатывать разрабатываемым "процессором звуковых эффектов".
Освоить принципы обработки звука на STM32 ?
https://ru.wikipedia.org/wiki/%D0%97%D0%B2%D1%83%D0%BA%D0%BE%D0%B2%D0%BE...
Не благодари!
Какова цель данного "Проекта"?
Чтобы в поисковой выдаче по запросу стало выдавать " процессор звуковых эффектов на ардуино".
А получится или нет - это не важно. Зато всех будет сбивать с толку.
Какова цель данного "Проекта"?
Создать устройство с какой то функциональностью ? Тогда почему не написано в самом начале? Какие звуковые сигналы с какой целью планируется обрабатывать разрабатываемым "процессором звуковых эффектов".
Собственно я специально включил в название слово "разработка" т.к. готового ТЗ еще нет.
Пока что сделан стенд, на котором можно отрабатывать различные эффекты. И цель - именно "пощупать" различные алгоритмы обработки звука, как универсальные (пригодные для любого музыкального инструмента или фонограммы в целом), так и специфические ориентированные на электрогитару.
Освоить принципы обработки звука на STM32 ?
Принципы обработки они как бы от конкретного контроллера (или процессора) на зависят.
Ну и продолжаю публикацию исходников.
Файл stm32f4xx_common.h
#ifndef __STM32F4xx_COMMON_H #define __STM32F4xx_COMMON_H typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus; typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState; #define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE)) typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrorStatus; //#define USE_FULL_ASSERT //#ifdef USE_FULL_ASSERT void assert_failed(char* file, unsigned long line); #define assert_param(expr) ((expr) ? (void)0 : assert_failed((char *)__FILE__, __LINE__)) #define STM32F40_41xxx #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */ #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000) #define SET_BIT(REG, BIT) ((REG) |= (BIT)) #define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT)) #define READ_BIT(REG, BIT) ((REG) & (BIT)) #define CLEAR_REG(REG) ((REG) = (0x0)) #define WRITE_REG(REG, VAL) ((REG) = (VAL)) #define READ_REG(REG) ((REG)) #define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK))) unsigned int CheckControl(unsigned int /*byte*/ index); // проверка вновь полученных от АЦП данных - а не изменились ли они? #endif /*__STM32F4xx_COMMON_H */Файл stm32f4xx_common.cpp
#include "Arduino.h" void assert_failed(char* file, unsigned long line) { Serial.println(" !!! ERROR !!!"); Serial.println(file); Serial.println(line); // while (1) {} } #define OVS_FACTOR 32 #define DIV_FACTOR (8*OVS_FACTOR) #define COMPARE_FACTOR (6*OVS_FACTOR) // (6*OVS_FACTOR) - так, вроде, устойчиво //bool sendADC = false; unsigned int control[6]; // состояние органов управления (редуцированное) unsigned int ADC_data[6]; // ADC data 10 bit: 0-1023 unsigned int oldCtrl_i[6]; // предыдущее запомненное состояние данных АЦП - для устранения шума (порог шума - 6 единиц младшего разряда) uint16 a_adc_read(const adc_dev *dev, uint8 channel); // читает аналоговый вход channel через АЦП adc_dev const uint8 ADC_channels[6] = {12, 13, 3, 2, 1, 0}; uint32_t numChanges = 0; unsigned int CheckControl(unsigned int /*byte*/ index){ // проверка вновь полученных от АЦП данных - а не изменились ли они? ADC_data[index] = a_adc_read(&ADC3, ADC_channels[index]); //analogRead(A[n-32]); for(int i = 1; i < OVS_FACTOR; i++) { ADC_data[index] += a_adc_read(&ADC3, ADC_channels[index]); }; unsigned int newCtrl_i = ADC_data[index]; unsigned int /*byte*/ newCtrl_b = newCtrl_i/DIV_FACTOR; if(newCtrl_b != control[index]) { if(abs((int)newCtrl_i - (int)oldCtrl_i[index]) >= COMPARE_FACTOR) { // не реагируем, пока разница в сырых 10-битных значениях не превзойдет COMPARE_FACTOR control[index] = newCtrl_b; oldCtrl_i[index] = newCtrl_i; numChanges++; } } return newCtrl_b; }Кстати, в процесе работы над проектом обнаружил многочисленные ошибки в ардуиновских файлах поддержки stm32f4xx: отсутствие в структурах существующих регистров, неверные константы (тупо скопированные из stm32f103), отсутствие некоторых аппаратных средств (в частности, таймеров). Пока что пришлось внести правку в файлы: dac.h, timer.h, rccF4.h. Это только то, что оказалось необходимо править в существующих файлах. Кроме них многочисленные заплатки были сделаны в отдельных файлах, добавленных в проект.
Да, и здесь же опубликую схему: