Implemented OPAMP functionality for v1.2 hardware

master
Simon Kueppers 2025-01-02 11:57:43 +01:00 zatwierdzone przez Simon Küppers
rodzic b61957be94
commit 868ac9323b
3 zmienionych plików z 142 dodań i 13 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -3,6 +3,19 @@
#include <stdint.h>
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;