Implemented USB Audio Class 2.0 measure against buffer drift by very loosely coupling buffer fill level to the feedback value

v1.1
Simon Kueppers 2022-11-13 21:12:33 +01:00
rodzic 133df32a7e
commit cdaf37602e
4 zmienionych plików z 116 dodań i 44 usunięć

Wyświetl plik

@ -41,7 +41,7 @@ uint8_t tu_stm32_edpt_number_cb(uint8_t addr)
void tu_stm32_sof_cb(void) void tu_stm32_sof_cb(void)
{ {
/* Capture timer value */ /* Capture timer value */
TIM2->EGR = TIM_EGR_CC1G; USB_SOF_TIMER->EGR = TIM_EGR_CC1G;
} }
// FIXME: Do all three need to be handled, or just the LP one? // FIXME: Do all three need to be handled, or just the LP one?
@ -98,13 +98,14 @@ void Timer_Init(void)
__HAL_RCC_TIM2_CLK_ENABLE(); __HAL_RCC_TIM2_CLK_ENABLE();
/* TIM2 generates a timebase for USB OUT feedback endpoint */ /* TIM2 generates a timebase for USB OUT feedback endpoint */
TIM2->CR1 = TIM_CLOCKDIVISION_DIV1 | TIM_COUNTERMODE_UP | TIM_AUTORELOAD_PRELOAD_ENABLE; USB_SOF_TIMER->CR1 = TIM_CLOCKDIVISION_DIV1 | TIM_COUNTERMODE_UP | TIM_AUTORELOAD_PRELOAD_ENABLE;
TIM2->PSC = 0; USB_SOF_TIMER->PSC = 0;
TIM2->ARR = 0xFFFFFFFFUL; USB_SOF_TIMER->ARR = 0xFFFFFFFFUL;
TIM2->CCMR1 = (0x1 << TIM_CCMR1_CC1S_Pos); USB_SOF_TIMER->CCMR1 = (0x1 << TIM_CCMR1_CC1S_Pos);
TIM2->EGR = TIM_EGR_UG; USB_SOF_TIMER->EGR = TIM_EGR_UG;
TIM2->CR1 |= TIM_CR1_CEN; USB_SOF_TIMER->CR1 |= TIM_CR1_CEN;
TU_ASSERT((2 * HAL_RCC_GetPCLK1Freq()) == USB_SOF_TIMER_HZ, /**/);
} }
void GPIO_Init(void) void GPIO_Init(void)

Wyświetl plik

@ -1,6 +1,13 @@
#ifndef USB_H_ #ifndef USB_H_
#define USB_H_ #define USB_H_
#include "usb_audio.h"
#include "usb_serial.h"
#define USB_SOF_TIMER TIM2
#define USB_SOF_TIMER_CCR TIM2->CCR1
#define USB_SOF_TIMER_HZ 72000000UL
void USB_Init(void); void USB_Init(void);
void USB_Task(void); void USB_Task(void);

Wyświetl plik

@ -2,21 +2,33 @@
#include "stm32f3xx_hal.h" #include "stm32f3xx_hal.h"
#include "aioc.h" #include "aioc.h"
#include "tusb.h" #include "tusb.h"
#include "usb.h"
#include <math.h> #include <math.h>
#ifndef AUDIO_SAMPLE_RATE /* The one and only supported sample rate */
#define AUDIO_SAMPLE_RATE 48000 #define AUDIO_SAMPLE_RATE 48000
#endif /* 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 */
#define SPEAKER_BUFFERLVL_AVG 64
/* 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 */
#define SPEAKER_FEEDBACK_AVG 8 /* This is feedback average responsivity with a denominator of 65536 */ /* Various state variables. N+1 because 0 is always the master channel */
static bool microphoneMute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1];
static bool microphoneMute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 static bool speakerMute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1];
static bool speakerMute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0 static int16_t microphoneLogVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX] = 0 }; /* in dB */
static int16_t microphoneLogVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX] = 0 }; // +1 for master channel 0 static int16_t speakerLogVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX] = 0 }; /* in dB */
static int16_t speakerLogVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX] = 0 }; // +1 for master channel 0 static uint16_t microphoneLinVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX] = 65535 }; /* 0.16 format */
static uint16_t microphoneLinVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX] = 65535 }; // +1 for master channel 0 static uint16_t speakerLinVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX] = 65535 }; /* 0.16 format */
static uint16_t speakerLinVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1] = { [0 ... CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX] = 65535 }; // +1 for master channel 0 static uint64_t speakerFeedbackAvg; /* 32.32 format */
static uint64_t speakerFeedbackAvg = 0; static uint32_t speakerFeedbackMin;
static uint32_t speakerFeedbackMax;
static uint32_t speakerBufferLvlAvg; /* 16.16 format */
static uint16_t speakerBufferLvlMin;
static uint16_t speakerBufferLvlMax;
#define FLAG_IN_START 0x00000010UL #define FLAG_IN_START 0x00000010UL
#define FLAG_OUT_START 0x00000100UL #define FLAG_OUT_START 0x00000100UL
@ -356,14 +368,25 @@ bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, u
bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting)
{ {
if (flags & FLAG_OUT_START) { /* Get number of total bytes available in FIFO */
uint16_t count = tud_audio_available(); uint16_t count = tud_audio_available();
if (count >= (6 * CFG_TUD_AUDIO_EP_SZ_OUT)) { /* Calculate min/max/average of buffer fill level */
/* Wait until at least n frames are in buffer, then start DAC output */ if ( (count - n_bytes_received) < speakerBufferLvlMin) speakerBufferLvlMin = count - n_bytes_received;
if ( count > speakerBufferLvlMax) speakerBufferLvlMax = count;
speakerBufferLvlAvg = ((uint64_t) speakerBufferLvlAvg * (65536 - SPEAKER_BUFFERLVL_AVG) + ((uint64_t) count << 16) * SPEAKER_BUFFERLVL_AVG) / 65536.0;
if (flags & FLAG_OUT_START) {
if (count >= SPEAKER_BUFFERLVL_TARGET) {
/* Wait until whe are at buffer target fill level, then start DAC output */
flags &= (uint32_t) ~FLAG_OUT_START; flags &= (uint32_t) ~FLAG_OUT_START;
NVIC_EnableIRQ(TIM3_IRQn); NVIC_EnableIRQ(TIM3_IRQn);
} }
/* Initialize/override min/max/avg during startup buffering */
speakerBufferLvlAvg = count;
speakerBufferLvlMin = count;
speakerBufferLvlMax = count;
} }
return true; return true;
@ -437,26 +460,44 @@ void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedba
TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift) TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift)
{ {
static uint32_t prev_cycles = 0; static uint32_t prev_cycles = 0;
uint32_t this_cycles = TIM2->CCR1; /* Load from capture register, which is set in tu_stm32_sof_cb */ uint32_t this_cycles = USB_SOF_TIMER_CCR; /* Load from capture register, which is set in tu_stm32_sof_cb */
uint32_t feedback; uint32_t feedback;
/* Calculate number of master clock cycles between now and last call */ /* Calculate number of master clock cycles between now and last call */
uint32_t cycles = (uint32_t) (((uint64_t) this_cycles - prev_cycles) & 0xFFFFFFFFUL); uint32_t cycles = (uint32_t) (((uint64_t) this_cycles - prev_cycles) & 0xFFFFFFFFUL);
TU_ASSERT(cycles != 0, /**/); TU_ASSERT(cycles != 0, /**/);
/* Notify the USB audio feedback endpoint */
feedback = tud_audio_feedback_update(func_id, cycles);
if (speakerFeedbackAvg == 0) {
/* Init */
speakerFeedbackAvg = (uint64_t) feedback << 16;
}
/* Low pass */
speakerFeedbackAvg = (speakerFeedbackAvg * (65536 - SPEAKER_FEEDBACK_AVG) + ((uint64_t) feedback << 16) * SPEAKER_FEEDBACK_AVG) / 65536.0;
/* Prepare for next time */ /* Prepare for next time */
prev_cycles = this_cycles; prev_cycles = this_cycles;
/* Calculate the feedback value, taken from tinyusb stack */
uint64_t fb64 = (((uint64_t) cycles) * AUDIO_SAMPLE_RATE) << 16;
feedback = (uint32_t) (fb64 / USB_SOF_TIMER_HZ);
uint32_t min_value = (AUDIO_SAMPLE_RATE/1000 - 1) << 16; /* 1000 for full-speed USB */
uint32_t max_value = (AUDIO_SAMPLE_RATE/1000 + 1) << 16;
/* Couple the buffer level bias to the feedback value to avoid buffer drift */
int32_t bias = (int32_t) speakerBufferLvlAvg - ((int32_t) SPEAKER_BUFFERLVL_TARGET << 16); /* 16.16 format same as feedback */
feedback -= ((int64_t) bias * SPEAKER_BUFLVL_FB_COUPLING) / 65536;
/* Limit */
if ( feedback > max_value ) feedback = max_value;
if ( feedback < min_value ) feedback = min_value;
/* Send to host */
tud_audio_n_fb_set(func_id, feedback);
/* Handle min/max/avg */
if (feedback < speakerFeedbackMin) speakerFeedbackMin = feedback;
if (feedback > speakerFeedbackMax) speakerFeedbackMax = feedback;
speakerFeedbackAvg = (speakerFeedbackAvg * (65536 - SPEAKER_FEEDBACK_AVG) + ((uint64_t) feedback << 16) * SPEAKER_FEEDBACK_AVG) / 65536.0;
if (flags & FLAG_OUT_START) {
/* Initialize/overwrite min/max/avg during start */
speakerFeedbackAvg = (uint64_t) feedback << 16;
speakerFeedbackMin = feedback;
speakerFeedbackMax = feedback;
}
} }
void ADC1_2_IRQHandler (void) void ADC1_2_IRQHandler (void)
@ -481,13 +522,10 @@ void TIM3_IRQHandler(void)
{ {
if (TIM3->SR & TIM_SR_UIF) { if (TIM3->SR & TIM_SR_UIF) {
TIM3->SR = (uint32_t) ~TIM_SR_UIF; TIM3->SR = (uint32_t) ~TIM_SR_UIF;
uint16_t items = tud_audio_available();
int16_t sample = 0x0000; int16_t sample = 0x0000;
if (items > 0) { /* Read from FIFO, leave sample at 0 if fifo empty */
/* Read from FIFO */
tud_audio_read(&sample, sizeof(sample)); tud_audio_read(&sample, sizeof(sample));
}
/* Get volume */ /* Get volume */
uint16_t volume = !speakerMute[1] ? speakerLinVolume[1] : 0; uint16_t volume = !speakerMute[1] ? speakerLinVolume[1] : 0;
@ -611,7 +649,21 @@ void USB_AudioInit(void)
DAC_Init(); DAC_Init();
} }
uint32_t USB_AudioGetSpeakerFeedback(void) void USB_AudioGetSpeakerFeedbackStats(usb_audio_fbstats_t * status)
{ {
return (uint32_t) (speakerFeedbackAvg >> 16); *status = (usb_audio_fbstats_t) {
.feedbackMin = speakerFeedbackMin,
.feedbackMax = speakerFeedbackMax,
.feedbackAvg = (uint32_t) (speakerFeedbackAvg >> 16)
};
}
void USB_AudioGetSpeakerBufferStats(usb_audio_bufstats_t * status)
{
*status = (usb_audio_bufstats_t) {
.bufLevelMin = speakerBufferLvlMin,
.bufLevelMax = speakerBufferLvlMax,
.bufLevelAvg = (uint16_t) (speakerBufferLvlAvg >> 16)
};
} }

Wyświetl plik

@ -3,8 +3,20 @@
#include <stdint.h> #include <stdint.h>
void USB_AudioInit(void); typedef struct {
uint16_t bufLevelMin;
uint16_t bufLevelMax;
uint16_t bufLevelAvg;
} usb_audio_bufstats_t;
uint32_t USB_AudioGetSpeakerFeedback(void); typedef struct {
uint32_t feedbackMin;
uint32_t feedbackMax;
uint32_t feedbackAvg;
} usb_audio_fbstats_t;
void USB_AudioInit(void);
void USB_AudioGetSpeakerFeedbackStats(usb_audio_fbstats_t * status);
void USB_AudioGetSpeakerBufferStats(usb_audio_bufstats_t * status);
#endif /* USB_AUDIO_H_ */ #endif /* USB_AUDIO_H_ */