From efe5e3812d14a436764ba351ba330b567d043c97 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Fri, 9 Aug 2024 15:22:21 +0800 Subject: [PATCH 1/9] Use DMA for ADC --- stm32/aioc-fw/Src/audio_dsp.c | 57 ++++++++++++ stm32/aioc-fw/Src/audio_dsp.h | 22 +++++ stm32/aioc-fw/Src/usb_audio.c | 162 ++++++++++++++++++++-------------- 3 files changed, 174 insertions(+), 67 deletions(-) create mode 100644 stm32/aioc-fw/Src/audio_dsp.c create mode 100644 stm32/aioc-fw/Src/audio_dsp.h diff --git a/stm32/aioc-fw/Src/audio_dsp.c b/stm32/aioc-fw/Src/audio_dsp.c new file mode 100644 index 0000000..42679a9 --- /dev/null +++ b/stm32/aioc-fw/Src/audio_dsp.c @@ -0,0 +1,57 @@ +#include "audio_dsp.h" + +#include + +void rational_decimator_init(rational_decimator_t* rd) { + memset(rd, 0, sizeof(*rd)); + rd->integer_rate = 1; +} + +void rational_decimator_reset(rational_decimator_t* rd) { + rd->sum = 0; + rd->current_sample = 0; + rd->output_samples = 0; +} + +static uint32_t gcd(uint32_t a, uint32_t b) { + while (b != 0) { + uint32_t temp = b; + b = a % b; + a = temp; + } + return a; +} + +void rational_decimator_set_rate(rational_decimator_t* rd, uint32_t input_rate, uint32_t output_rate) { + uint32_t n = input_rate % output_rate; + uint32_t d = output_rate; + rd->integer_rate = input_rate / output_rate; + rd->frac_rate_n = n / gcd(n,d); + rd->frac_rate_d = d / gcd(n,d); +} +uint32_t rational_decimator_get_integer_rate(rational_decimator_t* rd) { + return rd->integer_rate; +} + +uint32_t rational_decimator_process_block_u16(rational_decimator_t* rd, uint16_t* buf, uint32_t len) { + uint32_t cur_sum = rd->sum; + uint32_t cur_sample = rd->current_sample; + uint32_t rate = rd->integer_rate; + for(int i = 0;i < len;i++) { + cur_sum += buf[i]; + cur_sample++; + if (cur_sample >= rate) { + rd->output_buffer[rd->output_samples++] = cur_sum; + cur_sample = 0; + cur_sum = 0; + } + } + rd->sum = cur_sum; + rd->current_sample = cur_sample; + return rd->output_samples; +} + +uint32_t* rational_decimator_get_outputs(rational_decimator_t* rd) { + rd->output_samples = 0; + return rd->output_buffer; +} diff --git a/stm32/aioc-fw/Src/audio_dsp.h b/stm32/aioc-fw/Src/audio_dsp.h new file mode 100644 index 0000000..753c470 --- /dev/null +++ b/stm32/aioc-fw/Src/audio_dsp.h @@ -0,0 +1,22 @@ +#ifndef AUDIO_DSP_H_ +#define AUDIO_DSP_H_ + +#include + +typedef struct { + uint32_t sum; + uint32_t integer_rate; + uint32_t frac_rate_n, frac_rate_d; + uint32_t current_sample; + uint32_t output_samples; + uint32_t output_buffer[128]; +} rational_decimator_t; + +void rational_decimator_init(rational_decimator_t* rd); +void rational_decimator_reset(rational_decimator_t* rd); +void rational_decimator_set_rate(rational_decimator_t* rd, uint32_t input_rate, uint32_t output_rate); +uint32_t rational_decimator_get_integer_rate(rational_decimator_t* rd); +uint32_t rational_decimator_process_block_u16(rational_decimator_t* rd, uint16_t* buf, uint32_t len); +uint32_t* rational_decimator_get_outputs(rational_decimator_t* rd); + +#endif diff --git a/stm32/aioc-fw/Src/usb_audio.c b/stm32/aioc-fw/Src/usb_audio.c index 8095bfc..65ba81b 100644 --- a/stm32/aioc-fw/Src/usb_audio.c +++ b/stm32/aioc-fw/Src/usb_audio.c @@ -5,6 +5,7 @@ #include "tusb.h" #include "usb.h" #include "cos.h" +#include "audio_dsp.h" #include /* The one and only supported sample rate */ @@ -17,9 +18,15 @@ #define SPEAKER_BUFLVL_FB_COUPLING 1 /* We try to stay on this target with the buffer level */ #define SPEAKER_BUFFERLVL_TARGET (5 * CFG_TUD_AUDIO_EP_SZ_OUT) /* Keep our buffer at 5 frames, i.e. 5ms at full-speed USB and maximum sample rate */ +/* DMA buffer length for ADC */ +#define ADC_BUFFER_LEN 4096 +/* ADC DSP variables */ +static uint16_t ADC_samples[ADC_BUFFER_LEN]; +static rational_decimator_t adc_resampler; typedef enum { + SAMPLERATE_96000, SAMPLERATE_48000, /* The high-quality default */ SAMPLERATE_32000, /* For completeness sake, support 32 kHz as well */ SAMPLERATE_24000, /* Just half of 48 kHz */ @@ -60,6 +67,7 @@ static volatile state_t speakerState = STATE_OFF; static audio_control_range_4_n_t(SAMPLERATE_COUNT) sampleFreqRng = { .wNumSubRanges = SAMPLERATE_COUNT, .subrange = { + [SAMPLERATE_96000] = {.bMin = 96000, .bMax = 96000, .bRes = 0}, [SAMPLERATE_48000] = {.bMin = 48000, .bMax = 48000, .bRes = 0}, [SAMPLERATE_32000] = {.bMin = 32000, .bMax = 32000, .bRes = 0}, [SAMPLERATE_24000] = {.bMin = 24000, .bMax = 24000, .bRes = 0}, @@ -72,12 +80,16 @@ static audio_control_range_4_n_t(SAMPLERATE_COUNT) sampleFreqRng = { }; /* Prototypes of static functions */ -static void Timer_ADC_Init(void); static void Timer_DAC_Init(void); static void ADC_Init(void); static void DAC_Init(void); static void Timeout_Timers_Init(void); +/* ADC clock functions */ +static uint32_t ADC_get_sample_rate() { + return HAL_RCC_GetSysClockFreq() / 2 / 15; +} + //--------------------------------------------------------------------+ // Application Callback API Implementations //--------------------------------------------------------------------+ @@ -194,7 +206,10 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * microphoneSampleFreq = ((audio_control_cur_4_t*) pBuff)->bCur; TU_LOG2(" Set Mic. Sample Freq: %lu\r\n", microphoneSampleFreq); - Timer_ADC_Init(); + + rational_decimator_reset(&adc_resampler); + rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), microphoneSampleFreq); + microphoneSampleFreqCfg = ADC_get_sample_rate() / rational_decimator_get_integer_rate(&adc_resampler); /* Update debug register */ settingsRegMap[SETTINGS_REG_INFO_AUDIO2] = (((uint32_t) microphoneSampleFreqCfg) << SETTINGS_REG_INFO_AUDIO2_RECRATE_OFFS) & SETTINGS_REG_INFO_AUDIO2_RECRATE_MASK; @@ -516,7 +531,8 @@ bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, u if (microphoneState == STATE_START) { /* Start ADC sampling as soon as device stacks starts loading data (will be a ZLP for first frame) */ - NVIC_EnableIRQ(ADC1_2_IRQn); + rational_decimator_reset(&adc_resampler); + NVIC_EnableIRQ(DMA2_Channel1_IRQn); microphoneState = STATE_RUN; /* Update debug register */ @@ -617,7 +633,7 @@ bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const switch (itf) { case ITF_NUM_AUDIO_STREAMING_IN: /* Microphone channel has been stopped */ - NVIC_DisableIRQ(ADC1_2_IRQn); + NVIC_DisableIRQ(DMA2_Channel1_IRQn); microphoneState = STATE_OFF; /* Update debug register */ @@ -716,32 +732,6 @@ TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t settingsRegMap[SETTINGS_REG_INFO_AUDIO15] = ((uint32_t) speakerFeedbackMax << SETTINGS_REG_INFO_AUDIO15_PLAYFBMAX_OFFS) & SETTINGS_REG_INFO_AUDIO15_PLAYFBMAX_MASK; } -void ADC1_2_IRQHandler (void) -{ - if (ADC2->ISR & ADC_ISR_EOS) { - ADC2->ISR = ADC_ISR_EOS; - /* Get ADC sample */ - int16_t sample = ((int32_t) ADC2->DR - 32768) & 0xFFFFU; - - /* Automatic COS */ - uint16_t cosThreshold = (settingsRegMap[SETTINGS_REG_VCOS_LVLCTRL] & SETTINGS_REG_VCOS_LVLCTRL_THRSHLD_MASK) >> SETTINGS_REG_VCOS_LVLCTRL_THRSHLD_OFFS; - - if (!microphoneMute[1] && ( (sample > cosThreshold) || (sample < -cosThreshold) )) { - /* Reset timeout and make sure timer is enabled */ - TIM17->EGR = TIM_EGR_UG; /* Generate an update event in the timer */ - } - - /* Get volume */ - uint16_t volume = !microphoneMute[1] ? microphoneLinVolume[1] : 0; - - /* Scale with 16-bit unsigned volume and round */ - sample = (int16_t) (((int32_t) sample * volume + (sample > 0 ? 32768 : -32768)) / 65536); - - /* Store in FIFO */ - tud_audio_write (&sample, sizeof(sample)); - } -} - void TIM6_DAC_IRQHandler(void) { if (TIM6->SR & TIM_SR_UIF) { @@ -871,33 +861,6 @@ static void GPIO_Init(void) HAL_GPIO_Init(GPIOA, &DACOutGpio); } -static void Timer_ADC_Init(void) -{ - /* Calculate clock rate divider for requested sample rate with rounding */ - uint32_t timerFreq = (HAL_RCC_GetHCLKFreq() == HAL_RCC_GetPCLK1Freq()) ? HAL_RCC_GetPCLK1Freq() : 2 * HAL_RCC_GetPCLK1Freq(); - uint32_t rateDivider = (timerFreq + microphoneSampleFreq / 2) / microphoneSampleFreq; - - /* Store actually realized samplerate */ - microphoneSampleFreqCfg = timerFreq / rateDivider; - - /* Enable clock and (re-) initialize timer */ - __HAL_RCC_TIM3_CLK_ENABLE(); - - /* TIM3_TRGO triggers ADC2 */ - TIM3->CR1 &= ~TIM_CR1_CEN; - TIM3->CR1 = TIM_CLOCKDIVISION_DIV1 | TIM_COUNTERMODE_UP | TIM_AUTORELOAD_PRELOAD_ENABLE; - TIM3->CR2 = TIM_TRGO_UPDATE; - TIM3->PSC = 0; - TIM3->ARR = rateDivider - 1; - TIM3->EGR = TIM_EGR_UG; -#if 1 /* Output sample rate on compare channel 3 */ - TIM3->CCMR2 = TIM_OCMODE_PWM1 | TIM_CCMR2_OC3PE; - TIM3->CCER = (0 << TIM_CCER_CC3P_Pos) | TIM_CCER_CC3E; - TIM3->CCR3 = rateDivider/2 - 1; -#endif - TIM3->CR1 |= TIM_CR1_CEN; -} - static void Timer_DAC_Init(void) { /* Calculate clock rate divider for requested sample rate with rounding */ @@ -924,9 +887,52 @@ static void Timer_DAC_Init(void) NVIC_SetPriority(TIM6_DAC1_IRQn, AIOC_IRQ_PRIO_AUDIO); } +static void ADC_process_samples(uint16_t* buf) { + uint32_t samples = rational_decimator_process_block_u16(&adc_resampler, buf, ADC_BUFFER_LEN/2); + uint32_t* output_buffer = rational_decimator_get_outputs(&adc_resampler); + uint32_t output_scale = rational_decimator_get_integer_rate(&adc_resampler); + + for(int i = 0;i < samples;i++) { + /* Get ADC sample */ + int16_t sample = ((int32_t) (output_buffer[i] * 16 / output_scale) - 32768) & 0xFFFFU; + + /* Automatic COS */ + uint16_t cosThreshold = (settingsRegMap[SETTINGS_REG_VCOS_LVLCTRL] & SETTINGS_REG_VCOS_LVLCTRL_THRSHLD_MASK) >> SETTINGS_REG_VCOS_LVLCTRL_THRSHLD_OFFS; + + if (!microphoneMute[1] && ( (sample > cosThreshold) || (sample < -cosThreshold) )) { + /* Reset timeout and make sure timer is enabled */ + TIM17->EGR = TIM_EGR_UG; /* Generate an update event in the timer */ + } + + /* Get volume */ + uint16_t volume = !microphoneMute[1] ? microphoneLinVolume[1] : 0; + + /* Scale with 16-bit unsigned volume and round */ + sample = (int16_t) (((int32_t) sample * volume + (sample > 0 ? 32768 : -32768)) / 65536); + + /* Store in FIFO */ + tud_audio_write (&sample, sizeof(sample)); + } +} + +void DMA2_Channel1_IRQHandler(void) { + if(DMA2->ISR & DMA_ISR_HTIF1) { + DMA2->IFCR = DMA_IFCR_CHTIF1; + ADC_process_samples(ADC_samples); + } + if(DMA2->ISR & DMA_ISR_TCIF1) { + DMA2->IFCR = DMA_IFCR_CTCIF1; + ADC_process_samples(&ADC_samples[ADC_BUFFER_LEN/2]); + } +} + static void ADC_Init(void) { + /* Set ADC clock divisor */ + RCC->CFGR2 |= RCC_CFGR2_ADCPRE12_DIV2; + __HAL_RCC_ADC2_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); ADC2->CR = 0x00 << ADC_CR_ADVREGEN_Pos; ADC2->CR = 0x01 << ADC_CR_ADVREGEN_Pos; @@ -936,7 +942,7 @@ static void ADC_Init(void) } /* Select AHB clock */ - ADC12_COMMON->CCR = (0x1 << ADC12_CCR_CKMODE_Pos) | (0x00 << ADC12_CCR_MULTI_Pos); + ADC12_COMMON->CCR = (0 << ADC12_CCR_CKMODE_Pos) | (0x00 << ADC12_CCR_MULTI_Pos); ADC2->CR |= ADC_CR_ADCAL; @@ -949,22 +955,43 @@ static void ADC_Init(void) while (!(ADC2->ISR & ADC_ISR_ADRDY)) ; - /* External Trigger on TIM3_TRGO, left aligned data with 12 bit resolution */ - ADC2->CFGR = (0x01 << ADC_CFGR_EXTEN_Pos) | (0x04 << ADC_CFGR_EXTSEL_Pos) | (ADC_CFGR_ALIGN) | (0x00 << ADC_CFGR_RES_Pos); + /* Disable DMA */ + DMA2_Channel1->CCR &= ~DMA_CCR_EN; - /* Maximum sample time of 601.5 cycles for channel 12. */ - ADC2->SMPR2 = 0x7 << ADC_SMPR2_SMP12_Pos; + /* Enable peripheral to memory from ADC2 */ + DMA2_Channel1->CCR = (0x3 << DMA_CCR_PL_Pos)/* High priority */ + | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ + | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ + | (DMA_CCR_MINC) /* Memory increment mode */ + | (DMA_CCR_CIRC) /* Circular mode */ + | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ + | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ + + /* Buffer length */ + DMA2_Channel1->CNDTR = ADC_BUFFER_LEN; + + /* ADC conversion result source */ + DMA2_Channel1->CPAR = (uint32_t)&ADC2->DR; + + /* Buffer as destination */ + DMA2_Channel1->CMAR = (uint32_t)ADC_samples; + + /* Enable DMA */ + DMA2_Channel1->CCR |= DMA_CCR_EN; + + /* Left align, Continuous mode, DMA Circular Mode, DMA Enabled */ + ADC2->CFGR = ADC_CFGR_CONT | ADC_CFGR_DMACFG | ADC_CFGR_DMAEN; + + /* Maximum sample time of 2.5 cycles for channel 12 */ + ADC2->SMPR2 = 0x1 << ADC_SMPR2_SMP12_Pos; /* Sample only channel 12 in a regular sequence */ ADC2->SQR1 = (12 << ADC_SQR1_SQ1_Pos) | (0 << ADC_SQR1_L_Pos); - /* Enable Interrupt Request */ - ADC2->IER = ADC_IER_EOSIE; - /* Start ADC */ ADC2->CR |= ADC_CR_ADSTART; - NVIC_SetPriority(ADC1_2_IRQn, AIOC_IRQ_PRIO_AUDIO); + NVIC_SetPriority(DMA2_Channel1_IRQn, AIOC_IRQ_PRIO_AUDIO); } static void DAC_Init(void) @@ -1005,8 +1032,9 @@ static void Timeout_Timers_Init() void USB_AudioInit(void) { GPIO_Init(); - Timer_ADC_Init(); Timer_DAC_Init(); + rational_decimator_init(&adc_resampler); + rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), DEFAULT_SAMPLE_RATE); ADC_Init(); DAC_Init(); From 0670758f102562bc97149be885bd9f2ce4078d5e Mon Sep 17 00:00:00 2001 From: rhgndf Date: Fri, 9 Aug 2024 16:48:44 +0800 Subject: [PATCH 2/9] Add fractional resampler --- README.md | 4 +- stm32/aioc-fw/Src/audio_dsp.c | 102 ++++++++++++++++++++++++---------- stm32/aioc-fw/Src/audio_dsp.h | 14 ++--- stm32/aioc-fw/Src/usb_audio.c | 66 +++++++++++----------- 4 files changed, 115 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 96b8ed0..a662774 100644 --- a/README.md +++ b/README.md @@ -135,10 +135,10 @@ The soundcard interface of the AIOC gives access to the audio data channels. It - 48000 Hz (preferred) - 32000 Hz - 24000 Hz - - 22050 Hz (specifically for APRSdroid, has approx. 90 ppm of frequency error) + - 22050 Hz (specifically for APRSdroid) - 16000 Hz - 12000 Hz - - 11025 Hz (has approx. 90 ppm of frequency error) + - 11025 Hz - 8000 Hz Since firmware version 1.2.0, a CM108 style PTT interface is available for public testing. This interface works in parallel to the COM-port PTT. diff --git a/stm32/aioc-fw/Src/audio_dsp.c b/stm32/aioc-fw/Src/audio_dsp.c index 42679a9..93f0b79 100644 --- a/stm32/aioc-fw/Src/audio_dsp.c +++ b/stm32/aioc-fw/Src/audio_dsp.c @@ -3,55 +3,97 @@ #include void rational_decimator_init(rational_decimator_t* rd) { - memset(rd, 0, sizeof(*rd)); - rd->integer_rate = 1; + memset(rd, 0, sizeof(*rd)); + rd->integer_rate = 1; } void rational_decimator_reset(rational_decimator_t* rd) { - rd->sum = 0; - rd->current_sample = 0; - rd->output_samples = 0; + rd->sum = 0; + rd->current_sample = 0; + rd->frac_samples_left = rd->frac_rate_n; + rd->output_samples = 0; } static uint32_t gcd(uint32_t a, uint32_t b) { while (b != 0) { - uint32_t temp = b; + uint32_t temp = b; b = a % b; a = temp; } return a; } +static uint32_t min(uint32_t a, uint32_t b) { + return (a>b) ? b : a; +} + void rational_decimator_set_rate(rational_decimator_t* rd, uint32_t input_rate, uint32_t output_rate) { - uint32_t n = input_rate % output_rate; - uint32_t d = output_rate; - rd->integer_rate = input_rate / output_rate; - rd->frac_rate_n = n / gcd(n,d); - rd->frac_rate_d = d / gcd(n,d); + uint32_t n = input_rate % output_rate; + uint32_t d = output_rate; + rd->integer_rate = input_rate / output_rate; + rd->frac_rate_n = n / gcd(n,d); + rd->frac_rate_d = d / gcd(n,d); } -uint32_t rational_decimator_get_integer_rate(rational_decimator_t* rd) { - return rd->integer_rate; +uint32_t rational_decimator_scale(rational_decimator_t* rd, uint32_t sample) { + uint64_t scaled_sample = sample; + scaled_sample *= rd->frac_rate_d; + scaled_sample /= rd->integer_rate * rd->frac_rate_d + rd->frac_rate_n; + return scaled_sample; } uint32_t rational_decimator_process_block_u16(rational_decimator_t* rd, uint16_t* buf, uint32_t len) { - uint32_t cur_sum = rd->sum; - uint32_t cur_sample = rd->current_sample; - uint32_t rate = rd->integer_rate; - for(int i = 0;i < len;i++) { - cur_sum += buf[i]; - cur_sample++; - if (cur_sample >= rate) { - rd->output_buffer[rd->output_samples++] = cur_sum; - cur_sample = 0; - cur_sum = 0; - } - } - rd->sum = cur_sum; - rd->current_sample = cur_sample; - return rd->output_samples; + uint32_t cur_sum = rd->sum; + uint32_t cur_sample = rd->current_sample; + uint32_t rate = rd->integer_rate; + while(len > 0) { + uint32_t next_block = min(min(rate - cur_sample, len), rate); +#pragma GCC unroll 4 + for(int i = 0;i < next_block;i++) { + cur_sum += *buf++; + } + cur_sample += next_block; + len -= next_block; + + if (cur_sample >= rate) { + uint32_t frac_samples_left = rd->frac_samples_left; + if (frac_samples_left == 0) { + rd->output_buffer[rd->output_samples++] = cur_sum; + cur_sample = 0; + cur_sum = 0; + } else if (len > 0) { + // Handle the fractional sample if there is a sample left + uint32_t cur_frac_sample = *buf++; + len--; + + // Add the fractional sample + uint32_t n = rd->frac_rate_n; + uint32_t d = rd->frac_rate_d; + cur_sum += cur_frac_sample * frac_samples_left / d; + + // Write the completed sample to output buffer + rd->output_buffer[rd->output_samples++] = cur_sum; + + // Initialize the next block with the leftover fractional sample; + uint32_t next_frac_left = (d - frac_samples_left); + cur_sum = cur_frac_sample * next_frac_left / d; + + // Calculate the next fractional sample amount + if (next_frac_left > n) { + cur_sample = 1; + rd->frac_samples_left = n + d - next_frac_left; + } else { + cur_sample = 0; + rd->frac_samples_left = n - next_frac_left; + } + } + } + } + rd->sum = cur_sum; + rd->current_sample = cur_sample; + return rd->output_samples; } uint32_t* rational_decimator_get_outputs(rational_decimator_t* rd) { - rd->output_samples = 0; - return rd->output_buffer; + rd->output_samples = 0; + return rd->output_buffer; } diff --git a/stm32/aioc-fw/Src/audio_dsp.h b/stm32/aioc-fw/Src/audio_dsp.h index 753c470..93e98a7 100644 --- a/stm32/aioc-fw/Src/audio_dsp.h +++ b/stm32/aioc-fw/Src/audio_dsp.h @@ -4,18 +4,18 @@ #include typedef struct { - uint32_t sum; - uint32_t integer_rate; - uint32_t frac_rate_n, frac_rate_d; - uint32_t current_sample; - uint32_t output_samples; - uint32_t output_buffer[128]; + uint32_t sum; + uint32_t integer_rate; + uint32_t frac_rate_n, frac_rate_d; + uint32_t current_sample, frac_samples_left; + uint32_t output_samples; + uint32_t output_buffer[128]; } rational_decimator_t; void rational_decimator_init(rational_decimator_t* rd); void rational_decimator_reset(rational_decimator_t* rd); void rational_decimator_set_rate(rational_decimator_t* rd, uint32_t input_rate, uint32_t output_rate); -uint32_t rational_decimator_get_integer_rate(rational_decimator_t* rd); +uint32_t rational_decimator_scale(rational_decimator_t* rd, uint32_t sample); uint32_t rational_decimator_process_block_u16(rational_decimator_t* rd, uint16_t* buf, uint32_t len); uint32_t* rational_decimator_get_outputs(rational_decimator_t* rd); diff --git a/stm32/aioc-fw/Src/usb_audio.c b/stm32/aioc-fw/Src/usb_audio.c index 65ba81b..446b851 100644 --- a/stm32/aioc-fw/Src/usb_audio.c +++ b/stm32/aioc-fw/Src/usb_audio.c @@ -9,7 +9,7 @@ #include /* The one and only supported sample rate */ -#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_SAMPLE_RATE 48000 /* This is feedback average responsivity with a denominator of 65536 */ #define SPEAKER_FEEDBACK_AVG 32 /* This is buffer level average responsivity with a denominator of 65536 */ @@ -28,12 +28,13 @@ static rational_decimator_t adc_resampler; typedef enum { SAMPLERATE_96000, SAMPLERATE_48000, /* The high-quality default */ + SAMPLERATE_44010, SAMPLERATE_32000, /* For completeness sake, support 32 kHz as well */ SAMPLERATE_24000, /* Just half of 48 kHz */ - SAMPLERATE_22050, /* For APRSdroid support. NOTE: Has approx. 90 ppm of clock frequency error (ca. 22052 Hz) */ + SAMPLERATE_22050, /* For APRSdroid support */ SAMPLERATE_16000, /* On ARM platforms, direwolf will by default, divide configured sample rate by 3, thus support 16 kHz */ SAMPLERATE_12000, /* Just a quarter of 48 kHz */ - SAMPLERATE_11025, /* NOTE: Has approx. 90 ppm of clock frequency error (ca. 11026 Hz) */ + SAMPLERATE_11025, SAMPLERATE_8000, SAMPLERATE_COUNT /* Has to be last element */ } samplerate_t; @@ -67,8 +68,9 @@ static volatile state_t speakerState = STATE_OFF; static audio_control_range_4_n_t(SAMPLERATE_COUNT) sampleFreqRng = { .wNumSubRanges = SAMPLERATE_COUNT, .subrange = { - [SAMPLERATE_96000] = {.bMin = 96000, .bMax = 96000, .bRes = 0}, + [SAMPLERATE_96000] = {.bMin = 96000, .bMax = 96000, .bRes = 0}, [SAMPLERATE_48000] = {.bMin = 48000, .bMax = 48000, .bRes = 0}, + [SAMPLERATE_44010] = {.bMin = 44010, .bMax = 44010, .bRes = 0}, [SAMPLERATE_32000] = {.bMin = 32000, .bMax = 32000, .bRes = 0}, [SAMPLERATE_24000] = {.bMin = 24000, .bMax = 24000, .bRes = 0}, [SAMPLERATE_22050] = {.bMin = 22050, .bMax = 22050, .bRes = 0}, @@ -87,7 +89,7 @@ static void Timeout_Timers_Init(void); /* ADC clock functions */ static uint32_t ADC_get_sample_rate() { - return HAL_RCC_GetSysClockFreq() / 2 / 15; + return HAL_RCC_GetSysClockFreq() / 2 / 15; } //--------------------------------------------------------------------+ @@ -208,8 +210,8 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * rational_decimator_reset(&adc_resampler); - rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), microphoneSampleFreq); - microphoneSampleFreqCfg = ADC_get_sample_rate() / rational_decimator_get_integer_rate(&adc_resampler); + rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), microphoneSampleFreq); + microphoneSampleFreqCfg = microphoneSampleFreq; /* Update debug register */ settingsRegMap[SETTINGS_REG_INFO_AUDIO2] = (((uint32_t) microphoneSampleFreqCfg) << SETTINGS_REG_INFO_AUDIO2_RECRATE_OFFS) & SETTINGS_REG_INFO_AUDIO2_RECRATE_MASK; @@ -531,7 +533,7 @@ bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, u if (microphoneState == STATE_START) { /* Start ADC sampling as soon as device stacks starts loading data (will be a ZLP for first frame) */ - rational_decimator_reset(&adc_resampler); + rational_decimator_reset(&adc_resampler); NVIC_EnableIRQ(DMA2_Channel1_IRQn); microphoneState = STATE_RUN; @@ -888,13 +890,13 @@ static void Timer_DAC_Init(void) } static void ADC_process_samples(uint16_t* buf) { - uint32_t samples = rational_decimator_process_block_u16(&adc_resampler, buf, ADC_BUFFER_LEN/2); - uint32_t* output_buffer = rational_decimator_get_outputs(&adc_resampler); - uint32_t output_scale = rational_decimator_get_integer_rate(&adc_resampler); + uint32_t samples = rational_decimator_process_block_u16(&adc_resampler, buf, ADC_BUFFER_LEN/2); + uint32_t* output_buffer = rational_decimator_get_outputs(&adc_resampler); - for(int i = 0;i < samples;i++) { + for(int i = 0;i < samples;i++) { /* Get ADC sample */ - int16_t sample = ((int32_t) (output_buffer[i] * 16 / output_scale) - 32768) & 0xFFFFU; + int32_t scaled_sample = rational_decimator_scale(&adc_resampler, output_buffer[i] * 16); + int16_t sample = (scaled_sample - 32768) & 0xFFFFU; /* Automatic COS */ uint16_t cosThreshold = (settingsRegMap[SETTINGS_REG_VCOS_LVLCTRL] & SETTINGS_REG_VCOS_LVLCTRL_THRSHLD_MASK) >> SETTINGS_REG_VCOS_LVLCTRL_THRSHLD_OFFS; @@ -911,19 +913,19 @@ static void ADC_process_samples(uint16_t* buf) { sample = (int16_t) (((int32_t) sample * volume + (sample > 0 ? 32768 : -32768)) / 65536); /* Store in FIFO */ - tud_audio_write (&sample, sizeof(sample)); - } + tud_audio_write (&sample, sizeof(sample)); + } } void DMA2_Channel1_IRQHandler(void) { - if(DMA2->ISR & DMA_ISR_HTIF1) { - DMA2->IFCR = DMA_IFCR_CHTIF1; - ADC_process_samples(ADC_samples); - } - if(DMA2->ISR & DMA_ISR_TCIF1) { - DMA2->IFCR = DMA_IFCR_CTCIF1; - ADC_process_samples(&ADC_samples[ADC_BUFFER_LEN/2]); - } + if(DMA2->ISR & DMA_ISR_HTIF1) { + DMA2->IFCR = DMA_IFCR_CHTIF1; + ADC_process_samples(ADC_samples); + } + if(DMA2->ISR & DMA_ISR_TCIF1) { + DMA2->IFCR = DMA_IFCR_CTCIF1; + ADC_process_samples(&ADC_samples[ADC_BUFFER_LEN/2]); + } } static void ADC_Init(void) @@ -960,15 +962,15 @@ static void ADC_Init(void) /* Enable peripheral to memory from ADC2 */ DMA2_Channel1->CCR = (0x3 << DMA_CCR_PL_Pos)/* High priority */ - | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ - | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ - | (DMA_CCR_MINC) /* Memory increment mode */ - | (DMA_CCR_CIRC) /* Circular mode */ - | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ - | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ - - /* Buffer length */ - DMA2_Channel1->CNDTR = ADC_BUFFER_LEN; + | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ + | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ + | (DMA_CCR_MINC) /* Memory increment mode */ + | (DMA_CCR_CIRC) /* Circular mode */ + | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ + | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ + + /* Buffer length */ + DMA2_Channel1->CNDTR = ADC_BUFFER_LEN; /* ADC conversion result source */ DMA2_Channel1->CPAR = (uint32_t)&ADC2->DR; From dab349a65695335b56f548010bd048aa76d08d07 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Fri, 9 Aug 2024 16:58:54 +0800 Subject: [PATCH 3/9] Minor fix --- stm32/aioc-fw/Src/usb_audio.c | 56 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/stm32/aioc-fw/Src/usb_audio.c b/stm32/aioc-fw/Src/usb_audio.c index 446b851..1390dcf 100644 --- a/stm32/aioc-fw/Src/usb_audio.c +++ b/stm32/aioc-fw/Src/usb_audio.c @@ -9,7 +9,7 @@ #include /* The one and only supported sample rate */ -#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_SAMPLE_RATE 48000 /* This is feedback average responsivity with a denominator of 65536 */ #define SPEAKER_FEEDBACK_AVG 32 /* This is buffer level average responsivity with a denominator of 65536 */ @@ -89,7 +89,7 @@ static void Timeout_Timers_Init(void); /* ADC clock functions */ static uint32_t ADC_get_sample_rate() { - return HAL_RCC_GetSysClockFreq() / 2 / 15; + return HAL_RCC_GetSysClockFreq() / 2 / 15; } //--------------------------------------------------------------------+ @@ -210,8 +210,8 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * rational_decimator_reset(&adc_resampler); - rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), microphoneSampleFreq); - microphoneSampleFreqCfg = microphoneSampleFreq; + rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), microphoneSampleFreq); + microphoneSampleFreqCfg = microphoneSampleFreq; /* Update debug register */ settingsRegMap[SETTINGS_REG_INFO_AUDIO2] = (((uint32_t) microphoneSampleFreqCfg) << SETTINGS_REG_INFO_AUDIO2_RECRATE_OFFS) & SETTINGS_REG_INFO_AUDIO2_RECRATE_MASK; @@ -533,7 +533,7 @@ bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, u if (microphoneState == STATE_START) { /* Start ADC sampling as soon as device stacks starts loading data (will be a ZLP for first frame) */ - rational_decimator_reset(&adc_resampler); + rational_decimator_reset(&adc_resampler); NVIC_EnableIRQ(DMA2_Channel1_IRQn); microphoneState = STATE_RUN; @@ -890,12 +890,12 @@ static void Timer_DAC_Init(void) } static void ADC_process_samples(uint16_t* buf) { - uint32_t samples = rational_decimator_process_block_u16(&adc_resampler, buf, ADC_BUFFER_LEN/2); - uint32_t* output_buffer = rational_decimator_get_outputs(&adc_resampler); + uint32_t samples = rational_decimator_process_block_u16(&adc_resampler, buf, ADC_BUFFER_LEN/2); + uint32_t* output_buffer = rational_decimator_get_outputs(&adc_resampler); - for(int i = 0;i < samples;i++) { + for(int i = 0;i < samples;i++) { /* Get ADC sample */ - int32_t scaled_sample = rational_decimator_scale(&adc_resampler, output_buffer[i] * 16); + int32_t scaled_sample = rational_decimator_scale(&adc_resampler, output_buffer[i] * 16); int16_t sample = (scaled_sample - 32768) & 0xFFFFU; /* Automatic COS */ @@ -913,19 +913,19 @@ static void ADC_process_samples(uint16_t* buf) { sample = (int16_t) (((int32_t) sample * volume + (sample > 0 ? 32768 : -32768)) / 65536); /* Store in FIFO */ - tud_audio_write (&sample, sizeof(sample)); - } + tud_audio_write (&sample, sizeof(sample)); + } } void DMA2_Channel1_IRQHandler(void) { - if(DMA2->ISR & DMA_ISR_HTIF1) { - DMA2->IFCR = DMA_IFCR_CHTIF1; - ADC_process_samples(ADC_samples); - } - if(DMA2->ISR & DMA_ISR_TCIF1) { - DMA2->IFCR = DMA_IFCR_CTCIF1; - ADC_process_samples(&ADC_samples[ADC_BUFFER_LEN/2]); - } + if(DMA2->ISR & DMA_ISR_HTIF1) { + DMA2->IFCR = DMA_IFCR_CHTIF1; + ADC_process_samples(ADC_samples); + } + if(DMA2->ISR & DMA_ISR_TCIF1) { + DMA2->IFCR = DMA_IFCR_CTCIF1; + ADC_process_samples(&ADC_samples[ADC_BUFFER_LEN/2]); + } } static void ADC_Init(void) @@ -962,15 +962,15 @@ static void ADC_Init(void) /* Enable peripheral to memory from ADC2 */ DMA2_Channel1->CCR = (0x3 << DMA_CCR_PL_Pos)/* High priority */ - | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ - | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ - | (DMA_CCR_MINC) /* Memory increment mode */ - | (DMA_CCR_CIRC) /* Circular mode */ - | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ - | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ - - /* Buffer length */ - DMA2_Channel1->CNDTR = ADC_BUFFER_LEN; + | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ + | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ + | (DMA_CCR_MINC) /* Memory increment mode */ + | (DMA_CCR_CIRC) /* Circular mode */ + | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ + | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ + + /* Buffer length */ + DMA2_Channel1->CNDTR = ADC_BUFFER_LEN; /* ADC conversion result source */ DMA2_Channel1->CPAR = (uint32_t)&ADC2->DR; From eee31f63edb20e4eb7c49439bbdc7baace49cd98 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Fri, 9 Aug 2024 17:08:05 +0800 Subject: [PATCH 4/9] Update README with new sample rates --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a662774..ce37cf9 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,9 @@ The serial interface of the AIOC enumerates as a regular COM (Windows) or ttyACM __Note__ before firmware version 1.2.0, PTT was asserted by ``DTR=1`` (ignoring RTS) which caused problems with certain radios when using the serial port for programming the radio e.g. using CHIRP. The soundcard interface of the AIOC gives access to the audio data channels. It has one mono microphone channel and one mono speaker channel and currently supports the following baudrates: + - 96000 Hz - 48000 Hz (preferred) + - 44100 Hz - 32000 Hz - 24000 Hz - 22050 Hz (specifically for APRSdroid) From 8c9187dac1fbca062d707d949558a49efda10dd9 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Fri, 9 Aug 2024 18:07:19 +0800 Subject: [PATCH 5/9] Update README for DAC --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ce37cf9..c522ba2 100644 --- a/README.md +++ b/README.md @@ -134,13 +134,13 @@ __Note__ before firmware version 1.2.0, PTT was asserted by ``DTR=1`` (ignoring The soundcard interface of the AIOC gives access to the audio data channels. It has one mono microphone channel and one mono speaker channel and currently supports the following baudrates: - 96000 Hz - 48000 Hz (preferred) - - 44100 Hz + - 44100 Hz (has approx. 227 ppm of frequency error for DAC) - 32000 Hz - 24000 Hz - - 22050 Hz (specifically for APRSdroid) + - 22050 Hz (specifically for APRSdroid, has approx. 90 ppm of frequency error for DAC) - 16000 Hz - 12000 Hz - - 11025 Hz + - 11025 Hz (has approx. 90 ppm of frequency error for DAC) - 8000 Hz Since firmware version 1.2.0, a CM108 style PTT interface is available for public testing. This interface works in parallel to the COM-port PTT. From 6fe894fcffb4d60169631a4f3426cb6c9f3834c9 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Sun, 11 Aug 2024 20:56:19 +0800 Subject: [PATCH 6/9] Reduce processing time to allow TIM6 interrupt to process --- stm32/aioc-fw/Src/usb_audio.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/stm32/aioc-fw/Src/usb_audio.c b/stm32/aioc-fw/Src/usb_audio.c index 1390dcf..e201772 100644 --- a/stm32/aioc-fw/Src/usb_audio.c +++ b/stm32/aioc-fw/Src/usb_audio.c @@ -19,7 +19,7 @@ /* We try to stay on this target with the buffer level */ #define SPEAKER_BUFFERLVL_TARGET (5 * CFG_TUD_AUDIO_EP_SZ_OUT) /* Keep our buffer at 5 frames, i.e. 5ms at full-speed USB and maximum sample rate */ /* DMA buffer length for ADC */ -#define ADC_BUFFER_LEN 4096 +#define ADC_BUFFER_LEN 128 /* ADC DSP variables */ static uint16_t ADC_samples[ADC_BUFFER_LEN]; @@ -930,16 +930,21 @@ void DMA2_Channel1_IRQHandler(void) { static void ADC_Init(void) { + /* Set ADC clock divisor */ RCC->CFGR2 |= RCC_CFGR2_ADCPRE12_DIV2; + for (uint32_t i=0; i<100; i++) { + asm volatile ("nop"); + } + __HAL_RCC_ADC2_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); ADC2->CR = 0x00 << ADC_CR_ADVREGEN_Pos; ADC2->CR = 0x01 << ADC_CR_ADVREGEN_Pos; - for (uint32_t i=0; i<200; i++) { + for (uint32_t i=0; i<720; i++) { asm volatile ("nop"); } From 5366358e461a486a1c726c04e244ce900a974f39 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Wed, 14 Aug 2024 00:07:12 +0800 Subject: [PATCH 7/9] Implement oversampling for DAC --- stm32/aioc-fw/Src/audio_dsp.c | 90 ++++++++++++++- stm32/aioc-fw/Src/audio_dsp.h | 12 ++ stm32/aioc-fw/Src/usb_audio.c | 206 +++++++++++++++++++++++----------- 3 files changed, 241 insertions(+), 67 deletions(-) diff --git a/stm32/aioc-fw/Src/audio_dsp.c b/stm32/aioc-fw/Src/audio_dsp.c index 93f0b79..ae03e6f 100644 --- a/stm32/aioc-fw/Src/audio_dsp.c +++ b/stm32/aioc-fw/Src/audio_dsp.c @@ -24,7 +24,7 @@ static uint32_t gcd(uint32_t a, uint32_t b) { } static uint32_t min(uint32_t a, uint32_t b) { - return (a>b) ? b : a; + return (a > b) ? b : a; } void rational_decimator_set_rate(rational_decimator_t* rd, uint32_t input_rate, uint32_t output_rate) { @@ -46,11 +46,15 @@ uint32_t rational_decimator_process_block_u16(rational_decimator_t* rd, uint16_t uint32_t cur_sample = rd->current_sample; uint32_t rate = rd->integer_rate; while(len > 0) { - uint32_t next_block = min(min(rate - cur_sample, len), rate); + uint32_t next_block = min(rate - cur_sample, len); + + /* Simple moving average filter */ #pragma GCC unroll 4 for(int i = 0;i < next_block;i++) { - cur_sum += *buf++; + cur_sum += *buf; + buf++; } + cur_sample += next_block; len -= next_block; @@ -60,9 +64,11 @@ uint32_t rational_decimator_process_block_u16(rational_decimator_t* rd, uint16_t rd->output_buffer[rd->output_samples++] = cur_sum; cur_sample = 0; cur_sum = 0; + rd->frac_samples_left = rd->frac_rate_n; } else if (len > 0) { // Handle the fractional sample if there is a sample left - uint32_t cur_frac_sample = *buf++; + uint32_t cur_frac_sample = *buf; + buf++; len--; // Add the fractional sample @@ -97,3 +103,79 @@ uint32_t* rational_decimator_get_outputs(rational_decimator_t* rd) { rd->output_samples = 0; return rd->output_buffer; } + +void rational_interpolator_init(rational_interpolator_t* ri) { + memset(ri, 0, sizeof(*ri)); + ri->integer_rate = 1; +} + +void rational_interpolator_reset(rational_interpolator_t* ri) { + ri->current_val = 0; + ri->current_sample = 0; + ri->frac_samples_left = ri->frac_rate_n; +} + +void rational_interpolator_set_rate(rational_interpolator_t* ri, uint32_t input_rate, uint32_t output_rate) { + uint32_t n = output_rate % input_rate; + uint32_t d = input_rate; + ri->integer_rate = output_rate / input_rate; + ri->frac_rate_n = n / gcd(n,d); + ri->frac_rate_d = d / gcd(n,d); +} + +void rational_interpolator_fill_buffer(rational_interpolator_t* ri, uint16_t* buf, uint32_t len) { + uint32_t cur_val = ri->current_val; + uint32_t cur_sample = ri->current_sample; + uint32_t rate = ri->integer_rate; + while(len > 0) { + uint32_t next_block = min(rate - cur_sample, len); + + /* Simple replication of samples */ +#pragma GCC unroll 4 + for(int i = 0;i < next_block;i++) { + *buf = cur_val; + buf++; + } + + cur_sample += next_block; + len -= next_block; + + if (cur_sample >= rate) { + uint32_t frac_samples_left = ri->frac_samples_left; + if (frac_samples_left == 0) { + cur_sample = 0; + cur_val = DAC_get_next_sample(); + ri->frac_samples_left = ri->frac_rate_n; + } else if (len > 0) { + // Handle the fractional sample if there is a sample left + uint32_t next_sample = DAC_get_next_sample(); + + // Average the fractional samples + uint32_t n = ri->frac_rate_n; + uint32_t d = ri->frac_rate_d; + uint32_t next_frac_left = (d - frac_samples_left); + uint32_t averaged_sample = (cur_val * frac_samples_left + next_sample * next_frac_left) / d; + + // Write the sample to output buffer + *buf = averaged_sample; + buf++; + len--; + + // Initialize the next block with the leftover fractional sample; + cur_val = next_sample; + + // Calculate the next fractional sample amount + if (next_frac_left > n) { + cur_sample = 1; + ri->frac_samples_left = n + d - next_frac_left; + } else { + cur_sample = 0; + ri->frac_samples_left = n - next_frac_left; + } + } + } + } + ri->current_val = cur_val; + ri->current_sample = cur_sample; +} + diff --git a/stm32/aioc-fw/Src/audio_dsp.h b/stm32/aioc-fw/Src/audio_dsp.h index 93e98a7..a01936c 100644 --- a/stm32/aioc-fw/Src/audio_dsp.h +++ b/stm32/aioc-fw/Src/audio_dsp.h @@ -19,4 +19,16 @@ uint32_t rational_decimator_scale(rational_decimator_t* rd, uint32_t sample); uint32_t rational_decimator_process_block_u16(rational_decimator_t* rd, uint16_t* buf, uint32_t len); uint32_t* rational_decimator_get_outputs(rational_decimator_t* rd); +typedef struct { + uint32_t current_val; + uint32_t integer_rate; + uint32_t frac_rate_n, frac_rate_d; + uint32_t current_sample, frac_samples_left; +} rational_interpolator_t; + +void rational_interpolator_init(rational_interpolator_t* ri); +void rational_interpolator_reset(rational_interpolator_t* ri); +void rational_interpolator_set_rate(rational_interpolator_t* ri, uint32_t input_rate, uint32_t output_rate); +void rational_interpolator_fill_buffer(rational_interpolator_t* ri, uint16_t* buf, uint32_t len); + #endif diff --git a/stm32/aioc-fw/Src/usb_audio.c b/stm32/aioc-fw/Src/usb_audio.c index e201772..2a1617a 100644 --- a/stm32/aioc-fw/Src/usb_audio.c +++ b/stm32/aioc-fw/Src/usb_audio.c @@ -17,13 +17,20 @@ /* This is the amount of buffer level to feedback coupling with a denominator of 65536 to prevent buffer drift */ #define SPEAKER_BUFLVL_FB_COUPLING 1 /* We try to stay on this target with the buffer level */ -#define SPEAKER_BUFFERLVL_TARGET (5 * CFG_TUD_AUDIO_EP_SZ_OUT) /* Keep our buffer at 5 frames, i.e. 5ms at full-speed USB and maximum sample rate */ +#define SPEAKER_BUFFERLVL_TARGET (10 * CFG_TUD_AUDIO_EP_SZ_OUT) /* Keep our buffer at 10 frames, i.e. 10ms at full-speed USB and maximum sample rate */ /* DMA buffer length for ADC */ -#define ADC_BUFFER_LEN 128 +#define ADC_BUFFER_LEN 256 +/* DMA buffer length for DAC */ +#define DAC_BUFFER_LEN 256 +/* DAC clock divider 72MHz/150 = 480Ksps */ +#define DAC_CLOCK_DIVIDER 150 /* ADC DSP variables */ static uint16_t ADC_samples[ADC_BUFFER_LEN]; static rational_decimator_t adc_resampler; +/* ADC DAC variables */ +static uint16_t DAC_samples[DAC_BUFFER_LEN]; +static rational_interpolator_t dac_resampler; typedef enum { SAMPLERATE_96000, @@ -89,7 +96,13 @@ static void Timeout_Timers_Init(void); /* ADC clock functions */ static uint32_t ADC_get_sample_rate() { - return HAL_RCC_GetSysClockFreq() / 2 / 15; + return HAL_RCC_GetSysClockFreq() / 2 / 15; +} +/* DAC clock functions */ +static uint32_t DAC_get_sample_rate() { + /* Calculate clock rate divider for requested sample rate with rounding */ + uint32_t timerFreq = (HAL_RCC_GetHCLKFreq() == HAL_RCC_GetPCLK1Freq()) ? HAL_RCC_GetPCLK1Freq() : 2 * HAL_RCC_GetPCLK1Freq(); + return timerFreq / DAC_CLOCK_DIVIDER; } //--------------------------------------------------------------------+ @@ -210,8 +223,8 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * rational_decimator_reset(&adc_resampler); - rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), microphoneSampleFreq); - microphoneSampleFreqCfg = microphoneSampleFreq; + rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), microphoneSampleFreq); + microphoneSampleFreqCfg = microphoneSampleFreq; /* Update debug register */ settingsRegMap[SETTINGS_REG_INFO_AUDIO2] = (((uint32_t) microphoneSampleFreqCfg) << SETTINGS_REG_INFO_AUDIO2_RECRATE_OFFS) & SETTINGS_REG_INFO_AUDIO2_RECRATE_MASK; @@ -245,7 +258,10 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * speakerSampleFreq = ((audio_control_cur_4_t*) pBuff)->bCur; TU_LOG2(" Set Spk. Sample Freq: %lu\r\n", speakerSampleFreq); - Timer_DAC_Init(); + rational_interpolator_reset(&dac_resampler); + rational_interpolator_set_rate(&dac_resampler, speakerSampleFreq, DAC_get_sample_rate()); + /* With rational resampler it is exact*/ + speakerSampleFreqCfg = speakerSampleFreq; /* Update debug register */ settingsRegMap[SETTINGS_REG_INFO_AUDIO8] = (((uint32_t) speakerSampleFreqCfg) << SETTINGS_REG_INFO_AUDIO8_PLAYRATE_OFFS) & SETTINGS_REG_INFO_AUDIO8_PLAYRATE_MASK; @@ -533,7 +549,7 @@ bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, u if (microphoneState == STATE_START) { /* Start ADC sampling as soon as device stacks starts loading data (will be a ZLP for first frame) */ - rational_decimator_reset(&adc_resampler); + rational_decimator_reset(&adc_resampler); NVIC_EnableIRQ(DMA2_Channel1_IRQn); microphoneState = STATE_RUN; @@ -559,7 +575,8 @@ bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, u if (count >= SPEAKER_BUFFERLVL_TARGET) { /* Wait until whe are at buffer target fill level, then start DAC output */ speakerState = STATE_RUN; - NVIC_EnableIRQ(TIM6_DAC1_IRQn); + rational_interpolator_reset(&dac_resampler); + NVIC_EnableIRQ(DMA1_Channel3_IRQn); /* Update debug register */ settingsRegMap[SETTINGS_REG_INFO_AUDIO0] = (settingsRegMap[SETTINGS_REG_INFO_AUDIO0] & ~SETTINGS_REG_INFO_AUDIO0_PLAYSTATE_MASK) @@ -645,7 +662,7 @@ bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const case ITF_NUM_AUDIO_STREAMING_OUT: /* Speaker channel has been stopped */ - NVIC_DisableIRQ(TIM6_DAC1_IRQn); + NVIC_DisableIRQ(DMA1_Channel3_IRQn); speakerState = STATE_OFF; /* Update debug register */ @@ -734,32 +751,29 @@ TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t settingsRegMap[SETTINGS_REG_INFO_AUDIO15] = ((uint32_t) speakerFeedbackMax << SETTINGS_REG_INFO_AUDIO15_PLAYFBMAX_OFFS) & SETTINGS_REG_INFO_AUDIO15_PLAYFBMAX_MASK; } -void TIM6_DAC_IRQHandler(void) +uint32_t DAC_get_next_sample(void) { - if (TIM6->SR & TIM_SR_UIF) { - TIM6->SR = (uint32_t) ~TIM_SR_UIF; - int16_t sample = 0x0000; + int16_t sample = 0x0000; - /* Read from FIFO, leave sample at 0 if fifo empty */ - tud_audio_read(&sample, sizeof(sample)); + /* Read from FIFO, leave sample at 0 if fifo empty */ + tud_audio_read(&sample, sizeof(sample)); - /* Automatic PTT */ - uint16_t pttThreshold = (settingsRegMap[SETTINGS_REG_VPTT_LVLCTRL] & SETTINGS_REG_VPTT_LVLCTRL_THRSHLD_MASK) >> SETTINGS_REG_VPTT_LVLCTRL_THRSHLD_OFFS; + /* Automatic PTT */ + uint16_t pttThreshold = (settingsRegMap[SETTINGS_REG_VPTT_LVLCTRL] & SETTINGS_REG_VPTT_LVLCTRL_THRSHLD_MASK) >> SETTINGS_REG_VPTT_LVLCTRL_THRSHLD_OFFS; - if (!speakerMute[1] && ( (sample > pttThreshold) || (sample < -pttThreshold) )) { - /* Reset timeout and make sure timer is enabled */ - TIM16->EGR = TIM_EGR_UG; /* Generate an update event in the timer */ - } + if (!speakerMute[1] && ( (sample > pttThreshold) || (sample < -pttThreshold) )) { + /* Reset timeout and make sure timer is enabled */ + TIM16->EGR = TIM_EGR_UG; /* Generate an update event in the timer */ + } - /* Get volume */ - uint16_t volume = !speakerMute[1] ? speakerLinVolume[1] : 0; + /* Get volume */ + uint16_t volume = !speakerMute[1] ? speakerLinVolume[1] : 0; - /* Scale with 16-bit unsigned volume and round */ - sample = (int16_t) (((int32_t) sample * volume + (sample > 0 ? 32768 : -32768)) / 65536); + /* Scale with 16-bit unsigned volume and round */ + sample = (int16_t) (((int32_t) sample * volume + (sample > 0 ? 32768 : -32768)) / 65536); - /* Load DAC holding register with sample */ - DAC1->DHR12L1 = ((int32_t) sample + 32768) & 0xFFFFU; - } + /* Load DAC holding register with sample */ + return ((int32_t) sample + 32768) & 0xFFFFU; } void TIM16_IRQHandler(void) @@ -865,13 +879,6 @@ static void GPIO_Init(void) static void Timer_DAC_Init(void) { - /* Calculate clock rate divider for requested sample rate with rounding */ - uint32_t timerFreq = (HAL_RCC_GetHCLKFreq() == HAL_RCC_GetPCLK1Freq()) ? HAL_RCC_GetPCLK1Freq() : 2 * HAL_RCC_GetPCLK1Freq(); - uint32_t rateDivider = (timerFreq + speakerSampleFreq / 2) / speakerSampleFreq; - - /* Store actually realized samplerate for feedback algorithm to use */ - speakerSampleFreqCfg = timerFreq / rateDivider; - /* Enable clock and (re-) initialize timer */ __HAL_RCC_TIM6_CLK_ENABLE(); @@ -880,22 +887,19 @@ static void Timer_DAC_Init(void) TIM6->CR1 = TIM_CLOCKDIVISION_DIV1 | TIM_COUNTERMODE_UP | TIM_AUTORELOAD_PRELOAD_ENABLE; TIM6->CR2 = TIM_TRGO_UPDATE; TIM6->PSC = 0; - TIM6->ARR = rateDivider - 1; + TIM6->ARR = DAC_CLOCK_DIVIDER - 1; TIM6->EGR = TIM_EGR_UG; - TIM6->DIER = TIM_DIER_UIE; TIM6->CR1 |= TIM_CR1_CEN; - - NVIC_SetPriority(TIM6_DAC1_IRQn, AIOC_IRQ_PRIO_AUDIO); } static void ADC_process_samples(uint16_t* buf) { - uint32_t samples = rational_decimator_process_block_u16(&adc_resampler, buf, ADC_BUFFER_LEN/2); - uint32_t* output_buffer = rational_decimator_get_outputs(&adc_resampler); + uint32_t samples = rational_decimator_process_block_u16(&adc_resampler, buf, ADC_BUFFER_LEN/2); + uint32_t* output_buffer = rational_decimator_get_outputs(&adc_resampler); - for(int i = 0;i < samples;i++) { + for(int i = 0;i < samples;i++) { /* Get ADC sample */ - int32_t scaled_sample = rational_decimator_scale(&adc_resampler, output_buffer[i] * 16); + int32_t scaled_sample = rational_decimator_scale(&adc_resampler, output_buffer[i] * 16); int16_t sample = (scaled_sample - 32768) & 0xFFFFU; /* Automatic COS */ @@ -913,19 +917,26 @@ static void ADC_process_samples(uint16_t* buf) { sample = (int16_t) (((int32_t) sample * volume + (sample > 0 ? 32768 : -32768)) / 65536); /* Store in FIFO */ - tud_audio_write (&sample, sizeof(sample)); - } + tud_audio_write (&sample, sizeof(sample)); + } } void DMA2_Channel1_IRQHandler(void) { - if(DMA2->ISR & DMA_ISR_HTIF1) { - DMA2->IFCR = DMA_IFCR_CHTIF1; - ADC_process_samples(ADC_samples); - } - if(DMA2->ISR & DMA_ISR_TCIF1) { - DMA2->IFCR = DMA_IFCR_CTCIF1; - ADC_process_samples(&ADC_samples[ADC_BUFFER_LEN/2]); - } + /* Half buffer interrupt, process first half */ + if(DMA2->ISR & DMA_ISR_HTIF1) { + DMA2->IFCR = DMA_IFCR_CHTIF1; + ADC_process_samples(ADC_samples); + } + /* Full buffer interrupt, process second half */ + if(DMA2->ISR & DMA_ISR_TCIF1) { + DMA2->IFCR = DMA_IFCR_CTCIF1; + ADC_process_samples(&ADC_samples[ADC_BUFFER_LEN/2]); + } + /* DMA Error, reinit ADC */ + if(DMA2->ISR & DMA_ISR_TEIF1) { + DMA2->IFCR = DMA_IFCR_CTEIF1; + DAC_Init(); + } } static void ADC_Init(void) @@ -967,15 +978,16 @@ static void ADC_Init(void) /* Enable peripheral to memory from ADC2 */ DMA2_Channel1->CCR = (0x3 << DMA_CCR_PL_Pos)/* High priority */ - | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ - | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ - | (DMA_CCR_MINC) /* Memory increment mode */ - | (DMA_CCR_CIRC) /* Circular mode */ - | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ - | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ + | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ + | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ + | (DMA_CCR_MINC) /* Memory increment mode */ + | (DMA_CCR_CIRC) /* Circular mode */ + | (DMA_CCR_TEIE) /* Enable error interrupt */ + | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ + | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ - /* Buffer length */ - DMA2_Channel1->CNDTR = ADC_BUFFER_LEN; + /* Buffer length */ + DMA2_Channel1->CNDTR = ADC_BUFFER_LEN; /* ADC conversion result source */ DMA2_Channel1->CPAR = (uint32_t)&ADC2->DR; @@ -1001,12 +1013,78 @@ static void ADC_Init(void) NVIC_SetPriority(DMA2_Channel1_IRQn, AIOC_IRQ_PRIO_AUDIO); } +void DAC_process_samples(uint16_t* samples) { + rational_interpolator_fill_buffer(&dac_resampler, samples, DAC_BUFFER_LEN/2); +} + +void DMA1_Channel3_IRQHandler(void) { + /* Half buffer interrupt, fill first half */ + if(DMA1->ISR & DMA_ISR_HTIF3) { + DMA1->IFCR = DMA_IFCR_CHTIF3; + DAC_process_samples(DAC_samples); + } + /* Full buffer interrupt, fill second half */ + if(DMA1->ISR & DMA_ISR_TCIF3) { + DMA1->IFCR = DMA_IFCR_CTCIF3; + DAC_process_samples(&DAC_samples[DAC_BUFFER_LEN/2]); + } + /* DMA Error, reinit ADC */ + if(DMA1->ISR & DMA_ISR_TEIF3) { + DMA1->IFCR = DMA_IFCR_CTEIF3; + DAC_Init(); + } +} + +void TIM6_DAC_IRQHandler(void) { + /* DMA Error, reinit ADC */ + if (DAC->SR & DAC_SR_DMAUDR1) { + DAC->SR |= DAC_SR_DMAUDR1; + DAC_Init(); + } +} + static void DAC_Init(void) { __HAL_RCC_DAC1_CLK_ENABLE(); + __HAL_RCC_DMA1_CLK_ENABLE(); + + /* Remap DAC DMA to channel 1 to not conflict with ADC */ + SYSCFG->CFGR1 |= SYSCFG_CFGR1_TIM6DAC1Ch1_DMA_RMP; + + /* Disable DMA */ + DMA1_Channel3->CCR &= ~DMA_CCR_EN; + + /* Enable peripheral to memory from ADC2 */ + DMA1_Channel3->CCR = (0x3 << DMA_CCR_PL_Pos)/* High priority */ + | (0x1 << DMA_CCR_MSIZE_Pos) /* 16 bits */ + | (0x1 << DMA_CCR_PSIZE_Pos) /* 16 bits */ + | (DMA_CCR_DIR) /* Memory to Peripheral */ + | (DMA_CCR_MINC) /* Memory increment mode */ + | (DMA_CCR_CIRC) /* Circular mode */ + | (DMA_CCR_TEIE) /* Enable error interrupt */ + | (DMA_CCR_HTIE) /* Enable half buffer interrupt */ + | (DMA_CCR_TCIE); /* Enable full buffer interrupt */ + + /* Buffer length */ + DMA1_Channel3->CNDTR = DAC_BUFFER_LEN; + + /* DAC left aligned as destination */ + DMA1_Channel3->CPAR = (uint32_t)&DAC->DHR12L1; + + /* Buffer as source */ + DMA1_Channel3->CMAR = (uint32_t)DAC_samples; + + /* Enable DMA */ + DMA1_Channel3->CCR |= DMA_CCR_EN; /* Select TIM6 TRGO as trigger and enable DAC */ - DAC->CR = (0x0 << DAC_CR_TSEL1_Pos) | DAC_CR_TEN1 | DAC_CR_EN1; + DAC->CR = DAC_CR_DMAUDRIE1 /* Underflow enable */ + | DAC_CR_DMAEN1 /* DMA enable */ + | (0x0 << DAC_CR_TSEL1_Pos) | DAC_CR_TEN1 | DAC_CR_EN1; + + NVIC_SetPriority(DMA1_Channel3_IRQn, AIOC_IRQ_PRIO_AUDIO); + NVIC_EnableIRQ(TIM6_DAC_IRQn); + NVIC_SetPriority(TIM6_DAC_IRQn, AIOC_IRQ_PRIO_SYSTICK); } static void Timeout_Timers_Init() @@ -1039,9 +1117,11 @@ static void Timeout_Timers_Init() void USB_AudioInit(void) { GPIO_Init(); - Timer_DAC_Init(); rational_decimator_init(&adc_resampler); rational_decimator_set_rate(&adc_resampler, ADC_get_sample_rate(), DEFAULT_SAMPLE_RATE); + rational_interpolator_init(&dac_resampler); + rational_interpolator_set_rate(&dac_resampler, DEFAULT_SAMPLE_RATE, DAC_get_sample_rate()); + Timer_DAC_Init(); ADC_Init(); DAC_Init(); From 6d3e9ebb57d3d4edab08022a4bbffa30a14b2638 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Wed, 14 Aug 2024 01:14:23 +0800 Subject: [PATCH 8/9] Add sigma-delta modulator to DAC --- stm32/aioc-fw/Src/audio_dsp.c | 27 +++++++++++++++++++++++++-- stm32/aioc-fw/Src/audio_dsp.h | 2 ++ stm32/aioc-fw/Src/usb_audio.h | 2 ++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/stm32/aioc-fw/Src/audio_dsp.c b/stm32/aioc-fw/Src/audio_dsp.c index ae03e6f..e07ccb5 100644 --- a/stm32/aioc-fw/Src/audio_dsp.c +++ b/stm32/aioc-fw/Src/audio_dsp.c @@ -1,4 +1,5 @@ #include "audio_dsp.h" +#include "usb_audio.h" #include @@ -123,17 +124,37 @@ void rational_interpolator_set_rate(rational_interpolator_t* ri, uint32_t input_ ri->frac_rate_d = d / gcd(n,d); } +static inline uint16_t saturating_sub(uint16_t a, uint16_t b) { + //return a - b; + return (b > a) ? 0 : a - b; +} + +static inline uint16_t saturating_add(uint16_t a, uint16_t b) { + //return a + b; + return (b > (0xFFFF ^ a)) ? 0 : a + b; +} + +#define sigma_delta_modulator(sample, output) { \ + /* sd_sum += sample - sd_error */ \ + /* We know that sd_sum >= sd_error, so no saturating subtract needed */ \ + sd_sum = saturating_add(sample, sd_sum - sd_error); \ + /* 4 bits that can't be converted by the DAC */ \ + sd_error = sd_sum & 0xFFF0; \ + output = sd_error; } + void rational_interpolator_fill_buffer(rational_interpolator_t* ri, uint16_t* buf, uint32_t len) { uint32_t cur_val = ri->current_val; uint32_t cur_sample = ri->current_sample; uint32_t rate = ri->integer_rate; + uint16_t sd_error = ri->sd_error; + uint16_t sd_sum = ri->sd_sum; while(len > 0) { uint32_t next_block = min(rate - cur_sample, len); /* Simple replication of samples */ #pragma GCC unroll 4 for(int i = 0;i < next_block;i++) { - *buf = cur_val; + sigma_delta_modulator(cur_val, *buf); buf++; } @@ -157,7 +178,7 @@ void rational_interpolator_fill_buffer(rational_interpolator_t* ri, uint16_t* bu uint32_t averaged_sample = (cur_val * frac_samples_left + next_sample * next_frac_left) / d; // Write the sample to output buffer - *buf = averaged_sample; + sigma_delta_modulator(averaged_sample, *buf); buf++; len--; @@ -177,5 +198,7 @@ void rational_interpolator_fill_buffer(rational_interpolator_t* ri, uint16_t* bu } ri->current_val = cur_val; ri->current_sample = cur_sample; + ri->sd_error = sd_error; + ri->sd_sum = sd_sum; } diff --git a/stm32/aioc-fw/Src/audio_dsp.h b/stm32/aioc-fw/Src/audio_dsp.h index a01936c..79503d8 100644 --- a/stm32/aioc-fw/Src/audio_dsp.h +++ b/stm32/aioc-fw/Src/audio_dsp.h @@ -24,6 +24,8 @@ typedef struct { uint32_t integer_rate; uint32_t frac_rate_n, frac_rate_d; uint32_t current_sample, frac_samples_left; + uint16_t sd_error; + uint16_t sd_sum; } rational_interpolator_t; void rational_interpolator_init(rational_interpolator_t* ri); diff --git a/stm32/aioc-fw/Src/usb_audio.h b/stm32/aioc-fw/Src/usb_audio.h index 9a63ab9..f330e8d 100644 --- a/stm32/aioc-fw/Src/usb_audio.h +++ b/stm32/aioc-fw/Src/usb_audio.h @@ -19,4 +19,6 @@ void USB_AudioInit(void); void USB_AudioGetSpeakerFeedbackStats(usb_audio_fbstats_t * status); void USB_AudioGetSpeakerBufferStats(usb_audio_bufstats_t * status); +uint32_t DAC_get_next_sample(void); + #endif /* USB_AUDIO_H_ */ From 661f3a0e6d346bc33a466677df2011a8253a55d8 Mon Sep 17 00:00:00 2001 From: rhgndf Date: Wed, 14 Aug 2024 01:16:57 +0800 Subject: [PATCH 9/9] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c522ba2..ce37cf9 100644 --- a/README.md +++ b/README.md @@ -134,13 +134,13 @@ __Note__ before firmware version 1.2.0, PTT was asserted by ``DTR=1`` (ignoring The soundcard interface of the AIOC gives access to the audio data channels. It has one mono microphone channel and one mono speaker channel and currently supports the following baudrates: - 96000 Hz - 48000 Hz (preferred) - - 44100 Hz (has approx. 227 ppm of frequency error for DAC) + - 44100 Hz - 32000 Hz - 24000 Hz - - 22050 Hz (specifically for APRSdroid, has approx. 90 ppm of frequency error for DAC) + - 22050 Hz (specifically for APRSdroid) - 16000 Hz - 12000 Hz - - 11025 Hz (has approx. 90 ppm of frequency error for DAC) + - 11025 Hz - 8000 Hz Since firmware version 1.2.0, a CM108 style PTT interface is available for public testing. This interface works in parallel to the COM-port PTT.