diff --git a/stm32/aioc-fw/Inc/stm32f3xx_hal_conf.h b/stm32/aioc-fw/Inc/stm32f3xx_hal_conf.h index 642b4ab..21e56bf 100644 --- a/stm32/aioc-fw/Inc/stm32f3xx_hal_conf.h +++ b/stm32/aioc-fw/Inc/stm32f3xx_hal_conf.h @@ -54,7 +54,7 @@ /* #define HAL_I2S_MODULE_ENABLED */ /* #define HAL_IRDA_MODULE_ENABLED */ #define HAL_IWDG_MODULE_ENABLED -/* #define HAL_OPAMP_MODULE_ENABLED */ +#define HAL_OPAMP_MODULE_ENABLED /* #define HAL_PCD_MODULE_ENABLED */ #define HAL_PWR_MODULE_ENABLED #define HAL_RCC_MODULE_ENABLED diff --git a/stm32/aioc-fw/Src/usb_audio.c b/stm32/aioc-fw/Src/usb_audio.c index 8095bfc..8c1ac6e 100644 --- a/stm32/aioc-fw/Src/usb_audio.c +++ b/stm32/aioc-fw/Src/usb_audio.c @@ -76,8 +76,11 @@ static void Timer_ADC_Init(void); static void Timer_DAC_Init(void); static void ADC_Init(void); static void DAC_Init(void); +static void RX_Config(usb_audio_rxgain_t rxGain); +static void TX_Config(usb_audio_txboost_t txBoost); static void Timeout_Timers_Init(void); + //--------------------------------------------------------------------+ // Application Callback API Implementations //--------------------------------------------------------------------+ @@ -516,6 +519,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) */ + RX_Config(USB_AUDIO_RXGAIN_1X); NVIC_EnableIRQ(ADC1_2_IRQn); microphoneState = STATE_RUN; @@ -539,8 +543,9 @@ bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, u if (speakerState == STATE_START) { if (count >= SPEAKER_BUFFERLVL_TARGET) { - /* Wait until whe are at buffer target fill level, then start DAC output */ + /* Wait until we are at buffer target fill level, then start DAC output */ speakerState = STATE_RUN; + TX_Config(USB_AUDIO_TXBOOST_OFF); NVIC_EnableIRQ(TIM6_DAC1_IRQn); /* Update debug register */ @@ -718,10 +723,19 @@ TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t void ADC1_2_IRQHandler (void) { - if (ADC2->ISR & ADC_ISR_EOS) { - ADC2->ISR = ADC_ISR_EOS; + if ( (ADC1->ISR & ADC_ISR_EOS) || (ADC2->ISR & ADC_ISR_EOS) ) { + int16_t sample = 0; + /* Get ADC sample */ - int16_t sample = ((int32_t) ADC2->DR - 32768) & 0xFFFFU; + if (ADC1->ISR & ADC_ISR_EOS) { + ADC1->ISR = ADC_ISR_EOS; + sample = ((int32_t) ADC1->DR - 32768) & 0xFFFFU; + } + + if (ADC2->ISR & ADC_ISR_EOS) { + ADC2->ISR = ADC_ISR_EOS; + 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; @@ -844,6 +858,7 @@ void TIM17_IRQHandler(void) static void GPIO_Init(void) { + __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef ADCInGpio; @@ -854,6 +869,22 @@ static void GPIO_Init(void) ADCInGpio.Alternate = 0; HAL_GPIO_Init(GPIOB, &ADCInGpio); + GPIO_InitTypeDef OPAMP1InGpio; + OPAMP1InGpio.Pin = GPIO_PIN_5; + OPAMP1InGpio.Mode = GPIO_MODE_ANALOG; + OPAMP1InGpio.Pull = GPIO_NOPULL; + OPAMP1InGpio.Speed = GPIO_SPEED_FREQ_LOW; + OPAMP1InGpio.Alternate = 0; + HAL_GPIO_Init(GPIOA, &OPAMP1InGpio); + + GPIO_InitTypeDef OPAMP2OutGpio; + OPAMP2OutGpio.Pin = GPIO_PIN_6; + OPAMP2OutGpio.Mode = GPIO_MODE_ANALOG; + OPAMP2OutGpio.Pull = GPIO_NOPULL; + OPAMP2OutGpio.Speed = GPIO_SPEED_FREQ_LOW; + OPAMP2OutGpio.Alternate = 0; + HAL_GPIO_Init(GPIOA, &OPAMP2OutGpio); + GPIO_InitTypeDef SamplerateGpio; SamplerateGpio.Pin = GPIO_PIN_0; SamplerateGpio.Mode = GPIO_MODE_AF_PP; @@ -869,6 +900,14 @@ static void GPIO_Init(void) DACOutGpio.Speed = GPIO_SPEED_FREQ_LOW; DACOutGpio.Alternate = 0; HAL_GPIO_Init(GPIOA, &DACOutGpio); + + GPIO_InitTypeDef DACAttenGpio; + DACAttenGpio.Pin = GPIO_PIN_3; + DACAttenGpio.Mode = GPIO_MODE_OUTPUT_OD; + DACAttenGpio.Pull = GPIO_NOPULL; + DACAttenGpio.Speed = GPIO_SPEED_FREQ_LOW; + DACAttenGpio.Alternate = 0; + HAL_GPIO_Init(GPIOA, &DACAttenGpio); } static void Timer_ADC_Init(void) @@ -926,9 +965,13 @@ static void Timer_DAC_Init(void) static void ADC_Init(void) { + /* We use two ADCs. ADC1 is used, when OPAMP(PGA) is used. Otherwise ADC2 with direct hardware connection is used */ + __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_ADC2_CLK_ENABLE(); + ADC1->CR = 0x00 << ADC_CR_ADVREGEN_Pos; ADC2->CR = 0x00 << ADC_CR_ADVREGEN_Pos; + ADC1->CR = 0x01 << ADC_CR_ADVREGEN_Pos; ADC2->CR = 0x01 << ADC_CR_ADVREGEN_Pos; for (uint32_t i=0; i<200; i++) { @@ -938,32 +981,35 @@ static void ADC_Init(void) /* Select AHB clock */ ADC12_COMMON->CCR = (0x1 << ADC12_CCR_CKMODE_Pos) | (0x00 << ADC12_CCR_MULTI_Pos); + ADC1->CR |= ADC_CR_ADCAL; ADC2->CR |= ADC_CR_ADCAL; - while (ADC2->CR & ADC_CR_ADCAL) + while ( (ADC1->CR & ADC_CR_ADCAL) || (ADC2->CR & ADC_CR_ADCAL) ) ; + ADC1->CR |= ADC_CR_ADEN; ADC2->CR |= ADC_CR_ADEN; /* Wait for ADC to be ready */ - while (!(ADC2->ISR & ADC_ISR_ADRDY)) + while (!(ADC1->ISR & ADC_ISR_ADRDY) || !(ADC2->ISR & ADC_ISR_ADRDY) ) ; /* External Trigger on TIM3_TRGO, left aligned data with 12 bit resolution */ + ADC1->CFGR = (0x01 << ADC_CFGR_EXTEN_Pos) | (0x04 << ADC_CFGR_EXTSEL_Pos) | (ADC_CFGR_ALIGN) | (0x00 << ADC_CFGR_RES_Pos); ADC2->CFGR = (0x01 << ADC_CFGR_EXTEN_Pos) | (0x04 << ADC_CFGR_EXTSEL_Pos) | (ADC_CFGR_ALIGN) | (0x00 << ADC_CFGR_RES_Pos); - /* Maximum sample time of 601.5 cycles for channel 12. */ + /* Maximum sample time of 601.5 cycles for channel 3/channel 12. */ + ADC1->SMPR1 = 0x7 << ADC_SMPR1_SMP3_Pos; ADC2->SMPR2 = 0x7 << ADC_SMPR2_SMP12_Pos; - /* Sample only channel 12 in a regular sequence */ + /* Sample only channel 3/channel 12 in a regular sequence */ + ADC1->SQR1 = ( 3 << ADC_SQR1_SQ1_Pos) | (0 << ADC_SQR1_L_Pos); ADC2->SQR1 = (12 << ADC_SQR1_SQ1_Pos) | (0 << ADC_SQR1_L_Pos); /* Enable Interrupt Request */ + ADC1->IER = ADC_IER_EOSIE; ADC2->IER = ADC_IER_EOSIE; - /* Start ADC */ - ADC2->CR |= ADC_CR_ADSTART; - NVIC_SetPriority(ADC1_2_IRQn, AIOC_IRQ_PRIO_AUDIO); } @@ -973,9 +1019,79 @@ static void DAC_Init(void) /* Select TIM6 TRGO as trigger and enable DAC */ DAC->CR = (0x0 << DAC_CR_TSEL1_Pos) | DAC_CR_TEN1 | DAC_CR_EN1; + + /* Output VDD/2 */ + DAC1->DHR12L1 = 32768; } -static void Timeout_Timers_Init() +static void RX_Config(usb_audio_rxgain_t rxGain) +{ + /* Disable OPAMPs */ + OPAMP1->CSR = 0x00; + OPAMP2->CSR = 0x00; + + if (rxGain == USB_AUDIO_RXGAIN_1X) { + /* Legacy mode that is compatible with pre-v1.2 hardware */ + OPAMP2->CSR = OPAMP_FOLLOWER_MODE | OPAMP_VREF_50VDDA | OPAMP_CSR_FORCEVP | OPAMP2_CSR_OPAMP2EN; /* 50% VDD for bias */ + + /* Start ADC2 with direct hardware ADC input (no PGA in between) */ + if (ADC1->CR & ADC_CR_ADSTART) ADC1->CR |= ADC_CR_ADSTP; + if (!(ADC2->CR & ADC_CR_ADSTART)) ADC2->CR |= ADC_CR_ADSTART; + } else { + /* Initialize OPAMPs so that OPAMP1 is a PGA (non inverting) and OPAMP2 produces the correct DC-bias voltage according to OPAMP1 gain. */ + static const uint32_t pgaConfig[] = { + [USB_AUDIO_RXGAIN_2X] = OPAMP_PGA_MODE | OPAMP_PGA_GAIN_2, /* ADCin: 0.825V +/- 0.825V */ + [USB_AUDIO_RXGAIN_4X] = OPAMP_PGA_MODE | OPAMP_PGA_GAIN_4, /* ADCin: 0.4125V +/- 0.4125V */ + [USB_AUDIO_RXGAIN_8X] = OPAMP_PGA_MODE | OPAMP_PGA_GAIN_8, /* ADCin: 0.20625V +/- 0.20625V */ + [USB_AUDIO_RXGAIN_16X] = OPAMP_PGA_MODE | OPAMP_PGA_GAIN_16 /* ADCin: 0.103125V +/- 0.103125V */ + }; + + static const uint32_t biasConfig[] = { + [USB_AUDIO_RXGAIN_2X] = OPAMP_PGA_MODE | OPAMP_PGA_GAIN_8, /* 3.3V * 3.3% * 8 = 0.8712V */ + [USB_AUDIO_RXGAIN_4X] = OPAMP_PGA_MODE | OPAMP_PGA_GAIN_4, /* 3.3V * 3.3% * 4 = 0.4356V */ + [USB_AUDIO_RXGAIN_8X] = OPAMP_PGA_MODE | OPAMP_PGA_GAIN_2, /* 3.3V * 3.3% * 2 = 0.2178V */ + [USB_AUDIO_RXGAIN_16X] = OPAMP_FOLLOWER_MODE /* 3.3V * 3.3% * 1 = 0.1089V */ + }; + + /* Set up OPAMP1 PGA */ + OPAMP1->CSR = pgaConfig[rxGain] | OPAMP_NONINVERTINGINPUT_IO3 | OPAMP1_CSR_OPAMP1EN; + + /* Add a trimming offset adjustment to get closer to the required bias voltage reference of 1.65V/16 = 103mV. + * Default setting is 3.3V * 3.3% = 109mV. + * These return the factory trim values, because the CSR register (and thus the USERTRIM bit) is 0 */ + uint32_t trimmingOffsetP2 = (OPAMP2->CSR & OPAMP2_CSR_TRIMOFFSETP_Msk) >> OPAMP2_CSR_TRIMOFFSETP_Pos; + uint32_t trimmingOffsetN2 = (OPAMP2->CSR & OPAMP2_CSR_TRIMOFFSETN_Msk) >> OPAMP2_CSR_TRIMOFFSETN_Pos; + int8_t trimAdjust = 7; /* Found empirically. It looks like 1 step amounts to roughly 1mV input offset */ + + /* Observe the register limits, do saturated arithmetic */ + trimmingOffsetP2 = (((int32_t) trimmingOffsetP2 + trimAdjust) <= 0x1F) ? trimmingOffsetP2 + trimAdjust : 0x1F; + trimmingOffsetN2 = (((int32_t) trimmingOffsetN2 - trimAdjust) >= 0x00) ? trimmingOffsetN2 - trimAdjust : 0x00; + + /* Set up OPAMP2 bias voltage generation, disable the factory trimming */ + OPAMP2->CSR = biasConfig[rxGain] | OPAMP_VREF_3VDDA | OPAMP_CSR_FORCEVP | OPAMP2_CSR_OPAMP2EN | OPAMP_TRIMMING_USER; + + /* Load trimming values */ + OPAMP2->CSR |= (OPAMP2->CSR & ~(OPAMP2_CSR_TRIMOFFSETP_Msk | OPAMP2_CSR_TRIMOFFSETN_Msk)) | + ((trimmingOffsetP2 << OPAMP2_CSR_TRIMOFFSETP_Pos) & OPAMP2_CSR_TRIMOFFSETP_Msk) | + ((trimmingOffsetN2 << OPAMP2_CSR_TRIMOFFSETN_Pos) & OPAMP2_CSR_TRIMOFFSETN_Msk); + + /* Start ADC1 using PGA output as ADC input */ + if (ADC2->CR & ADC_CR_ADSTART) ADC2->CR |= ADC_CR_ADSTP; + if (!(ADC1->CR & ADC_CR_ADSTART)) ADC1->CR |= ADC_CR_ADSTART; + } +} + +static void TX_Config(usb_audio_txboost_t txBoost) +{ + /* Set PINA3 (ATTEN Hi-Z) in TX Boost mode, reset PINA3 (pull ATTEN low) in normal mode */ + if (txBoost == USB_AUDIO_TXBOOST_ON) { + GPIOA->BSRR = GPIO_BSRR_BS_3; + } else { + GPIOA->BRR = GPIO_BRR_BR_3; + } +} + +static void Timeout_Timers_Init(void) { uint32_t timerFreq = (HAL_RCC_GetHCLKFreq() == HAL_RCC_GetPCLK2Freq()) ? HAL_RCC_GetPCLK2Freq() : 2 * HAL_RCC_GetPCLK2Freq(); uint32_t pttTimeout = (settingsRegMap[SETTINGS_REG_VPTT_TIMCTRL] & SETTINGS_REG_VPTT_TIMCTRL_TIMEOUT_MASK) >> SETTINGS_REG_VPTT_TIMCTRL_TIMEOUT_OFFS; diff --git a/stm32/aioc-fw/Src/usb_audio.h b/stm32/aioc-fw/Src/usb_audio.h index 9a63ab9..cf43924 100644 --- a/stm32/aioc-fw/Src/usb_audio.h +++ b/stm32/aioc-fw/Src/usb_audio.h @@ -3,6 +3,19 @@ #include +typedef enum { + USB_AUDIO_RXGAIN_1X, + USB_AUDIO_RXGAIN_2X, + USB_AUDIO_RXGAIN_4X, + USB_AUDIO_RXGAIN_8X, + USB_AUDIO_RXGAIN_16X, +} usb_audio_rxgain_t; + +typedef enum { + USB_AUDIO_TXBOOST_OFF, + USB_AUDIO_TXBOOST_ON +} usb_audio_txboost_t; + typedef struct { uint16_t bufLevelMin; uint16_t bufLevelMax;