From 3306b8083ba168052389e82f8e8d0b968a94f706 Mon Sep 17 00:00:00 2001 From: Iwwww Date: Thu, 25 Dec 2025 21:06:50 +0300 Subject: [PATCH] feat(mic): try1: setup sound with usb --- .gitignore | 3 + client/{ => src}/reveier.py | 0 firmware/App/Inc/audio_adc.h | 29 ++++ firmware/App/Inc/audio_config.h | 35 +++++ firmware/App/Src/audio_adc.c | 238 ++++++++++++++++++++++++++++++++ firmware/App/Src/main.c | 127 ++++++++++++++--- firmware/Makefile | 1 + 7 files changed, 411 insertions(+), 22 deletions(-) rename client/{ => src}/reveier.py (100%) create mode 100644 firmware/App/Inc/audio_adc.h create mode 100644 firmware/App/Inc/audio_config.h create mode 100644 firmware/App/Src/audio_adc.c diff --git a/.gitignore b/.gitignore index b48d248..5f1ef7c 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ Mkfile.old dkms.conf # End of https://www.toptal.com/developers/gitignore/api/c + +Build/ +*.bin diff --git a/client/reveier.py b/client/src/reveier.py similarity index 100% rename from client/reveier.py rename to client/src/reveier.py diff --git a/firmware/App/Inc/audio_adc.h b/firmware/App/Inc/audio_adc.h new file mode 100644 index 0000000..a7c1dc3 --- /dev/null +++ b/firmware/App/Inc/audio_adc.h @@ -0,0 +1,29 @@ +#ifndef AUDIO_ADC_H +#define AUDIO_ADC_H + +#include +#include "audio_config.h" + +/** + * @brief Инициализация ADC, DMA и Timer для audio capture + * @param callback Функция, вызываемая при заполнении буфера + * @return true если успешно, false при ошибке + */ +bool audio_adc_init(audio_buffer_ready_callback_t callback); + +/** + * @brief Запуск непрерывного захвата аудио + */ +void audio_adc_start(void); + +/** + * @brief Остановка захвата аудио + */ +void audio_adc_stop(void); + +/** + * @brief Получить текущее количество обработанных буферов + */ +uint32_t audio_adc_get_buffer_count(void); + +#endif /* AUDIO_ADC_H */ diff --git a/firmware/App/Inc/audio_config.h b/firmware/App/Inc/audio_config.h new file mode 100644 index 0000000..826cf8c --- /dev/null +++ b/firmware/App/Inc/audio_config.h @@ -0,0 +1,35 @@ +#ifndef AUDIO_CONFIG_H +#define AUDIO_CONFIG_H + +#include + +// Audio Configuration +#define AUDIO_SAMPLE_RATE 22050U // Hz (22.05 kHz) +#define AUDIO_BUFFER_SIZE 512U // Samples per buffer (для FFT) +#define AUDIO_NUM_BUFFERS 2U + +// ADC Configuration +#define AUDIO_ADC_CHANNEL 1U // PA1 = ADC1_IN1 +#define AUDIO_ADC_GPIO_PORT GPIOA +#define AUDIO_ADC_GPIO_PIN 1U // PA1 + +// Timer Configuration +// Timer2 будет триггерить ADC на частоте AUDIO_SAMPLE_RATE +// APB1 Timer clock = 72 MHz (на STM32F103 при SYSCLK=72MHz) +// Формула: Timer_Freq = Timer_Clock / ((PSC + 1) * (ARR + 1)) +// Для 22050 Hz: 72000000 / 22050 = 3265.3 +// Используем PSC=0, ARR=3264 → 72MHz / 3265 = 22051 Hz (погрешность 0.004%) +#define AUDIO_TIMER_PRESCALER 0U +#define AUDIO_TIMER_PERIOD 3264U // ARR value + +// DMA Configuration +#define AUDIO_DMA_CHANNEL DMA1_Channel1 // ADC1 использует DMA1_CH1 + +// Data Types +typedef uint16_t audio_sample_t; + +// Callback вызывается когда буфер заполнен +typedef void ( + *audio_buffer_ready_callback_t)(audio_sample_t* buffer, uint32_t size); + +#endif /* AUDIO_CONFIG_H */ diff --git a/firmware/App/Src/audio_adc.c b/firmware/App/Src/audio_adc.c new file mode 100644 index 0000000..a59dcc7 --- /dev/null +++ b/firmware/App/Src/audio_adc.c @@ -0,0 +1,238 @@ +#include "audio_adc.h" +#include +#include "stm32f1xx.h" + +// Двойные буферы для DMA +static audio_sample_t audio_buffer_0[AUDIO_BUFFER_SIZE] = {0}; +static audio_sample_t audio_buffer_1[AUDIO_BUFFER_SIZE] = {0}; + +// Указатель на активный буфер для обработки +static volatile uint8_t active_buffer_index = 0; + +// Callback функция +static audio_buffer_ready_callback_t user_callback = NULL; + +// Статистика (для отладки) +static volatile uint32_t buffer_count = 0; +static volatile uint32_t dma_half_transfer_count = 0; +static volatile uint32_t dma_full_transfer_count = 0; + +// Private Function Prototypes +static void audio_gpio_init(void); +static void audio_timer_init(void); +static void audio_adc_hw_init(void); +static void audio_dma_init(void); + +bool audio_adc_init(audio_buffer_ready_callback_t callback) { + if (callback == NULL) { return false; } + + user_callback = callback; + + // Инициализация компонентов + audio_gpio_init(); + audio_timer_init(); + audio_adc_hw_init(); + audio_dma_init(); + + return true; +} + +void audio_adc_start(void) { + // Запускаем DMA + DMA1_Channel1->CCR |= DMA_CCR_EN; + + // Запускаем ADC + ADC1->CR2 |= ADC_CR2_ADON; + + // Запускаем Timer2, начинаем генерировать TRGO события + TIM2->CR1 |= TIM_CR1_CEN; +} + +void audio_adc_stop(void) { + // Останавливаем Timer + TIM2->CR1 &= ~TIM_CR1_CEN; + + // Останавливаем ADC + ADC1->CR2 &= ~ADC_CR2_ADON; + + // Останавливаем DMA + DMA1_Channel1->CCR &= ~DMA_CCR_EN; +} + +uint32_t audio_adc_get_buffer_count(void) { + return buffer_count; +} + +// Private Functions + +static void audio_gpio_init(void) { + // Включаем тактирование GPIOA + RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; + + // PA1 как Analog Input (CNF=00, MODE=00) + GPIOA->CRL &= ~(GPIO_CRL_CNF1 | GPIO_CRL_MODE1); + // По умолчанию уже 0000, но явно устанавливаем + GPIOA->CRL &= ~(0xF << 4); // Биты [7:4] для Pin 1 +} + +static void audio_timer_init(void) { + // Включаем тактирование Timer2 + RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; + + // Настраиваем Timer2 для генерации TRGO на частоте 22050 Hz + TIM2->PSC = AUDIO_TIMER_PRESCALER; + TIM2->ARR = AUDIO_TIMER_PERIOD; + + // Master Mode Selection: Update event as TRGO + // MMS[2:0] = 010 (Update) + TIM2->CR2 &= ~TIM_CR2_MMS; + TIM2->CR2 |= TIM_CR2_MMS_1; // 010 + + // Auto-reload preload enable + TIM2->CR1 |= TIM_CR1_ARPE; + + // Генерируем Update event для загрузки PSC/ARR + TIM2->EGR |= TIM_EGR_UG; +} + +static void audio_adc_hw_init(void) { + // Включаем тактирование ADC1 + RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; + + // ADC Clock = PCLK2 / 6 = 72MHz / 6 = 12MHz (макс 14MHz для STM32F1) + RCC->CFGR &= ~RCC_CFGR_ADCPRE; + RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; + + // Сброс ADC1 + ADC1->CR2 = 0; + ADC1->CR1 = 0; + + // Конфигурация ADC1 + + // Scan mode disabled (один канал) + ADC1->CR1 &= ~ADC_CR1_SCAN; + + // Continuous mode disabled (будет триггериться Timer2) + ADC1->CR2 &= ~ADC_CR2_CONT; + + // External trigger: Timer2 TRGO + // EXTSEL[2:0] = 011 (Timer2 TRGO для ADC1) + ADC1->CR2 &= ~ADC_CR2_EXTSEL; + ADC1->CR2 |= ADC_CR2_EXTSEL_0 | ADC_CR2_EXTSEL_1; // 011 + + // External trigger enable for regular channels + ADC1->CR2 |= ADC_CR2_EXTTRIG; + + // 5. DMA mode enable + ADC1->CR2 |= ADC_CR2_DMA; + + // Data alignment: right aligned + ADC1->CR2 &= ~ADC_CR2_ALIGN; + + // Настройка канала + + // Regular sequence length = 1 (только один канал) + ADC1->SQR1 &= ~ADC_SQR1_L; + + // First conversion in regular sequence: Channel 1 (PA1) + ADC1->SQR3 &= ~ADC_SQR3_SQ1; + ADC1->SQR3 |= (AUDIO_ADC_CHANNEL << ADC_SQR3_SQ1_Pos); + + // Sample time для Channel 1: 7.5 cycles (минимум для 12MHz ADC) + // SMPR2[5:3] для CH1 = 001 (7.5 cycles) + ADC1->SMPR2 &= ~ADC_SMPR2_SMP1; + ADC1->SMPR2 |= ADC_SMPR2_SMP1_0; // 001 + + // Калибровка ADC + + // Включаем ADC + ADC1->CR2 |= ADC_CR2_ADON; + + // Ждем stabilization time (1 мкс) + for (volatile int i = 0; i < 1000; i++); + + // Запускаем калибровку + ADC1->CR2 |= ADC_CR2_CAL; + + // Ждем завершения калибровки + while (ADC1->CR2 & ADC_CR2_CAL); + + // Выключаем ADC (для запуска использовать audio_adc_start) + ADC1->CR2 &= ~ADC_CR2_ADON; +} + +static void audio_dma_init(void) { + // Включаем тактирование DMA1 + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + + // Отключаем DMA1 Channel1 перед конфигурацией + DMA1_Channel1->CCR &= ~DMA_CCR_EN; + + // Ждем отключения + while (DMA1_Channel1->CCR & DMA_CCR_EN); + + // Конфигурация DMA + + // Peripheral address: ADC1 Data Register + DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR); + + // Memory address: начинаем с buffer_0 + DMA1_Channel1->CMAR = (uint32_t)audio_buffer_0; + + // Number of data to transfer: размер обоих буферов (для circular mode) + DMA1_Channel1->CNDTR = AUDIO_BUFFER_SIZE * 2; + + // Конфигураци + uint32_t ccr = 0; + ccr |= DMA_CCR_MINC; // Memory increment mode + ccr |= DMA_CCR_CIRC; // Circular mode + ccr |= DMA_CCR_HTIE; // Half transfer interrupt enable + ccr |= DMA_CCR_TCIE; // Transfer complete interrupt enable + ccr |= DMA_CCR_PL_1; // Priority level: High (10) + + // Data size: 16-bit (MSIZE и PSIZE = 01) + ccr |= DMA_CCR_MSIZE_0; // Memory size: 16-bit + ccr |= DMA_CCR_PSIZE_0; // Peripheral size: 16-bit + + DMA1_Channel1->CCR = ccr; + + // Включаем прерывание DMA1_Channel1 в NVIC + NVIC_SetPriority(DMA1_Channel1_IRQn, 6); // Приоритет 6 (ниже USB) + NVIC_EnableIRQ(DMA1_Channel1_IRQn); +} + +// DMA Interrupt Handler + +void DMA1_Channel1_IRQHandler(void) { + uint32_t isr = DMA1->ISR; + + // Half Transfer Interrupt (первая половина буфера заполнена) + if (isr & DMA_ISR_HTIF1) { + DMA1->IFCR = DMA_IFCR_CHTIF1; // Сбрасываем флаг + dma_half_transfer_count++; + + // Первая половина = buffer_0 готов к обработке + if (user_callback != NULL) { + user_callback(audio_buffer_0, AUDIO_BUFFER_SIZE); + } + buffer_count++; + } + + // Transfer Complete Interrupt (вторая половина буфера заполнена) + if (isr & DMA_ISR_TCIF1) { + DMA1->IFCR = DMA_IFCR_CTCIF1; // Сбрасываем флаг + dma_full_transfer_count++; + + // Вторая половина = buffer_1 готов к обработке + if (user_callback != NULL) { + user_callback(audio_buffer_1, AUDIO_BUFFER_SIZE); + } + buffer_count++; + } + + // Transfer Error + if (isr & DMA_ISR_TEIF1) { + DMA1->IFCR = DMA_IFCR_CTEIF1; // Сбрасываем флаг + // TODO: обработка ошибки + } +} diff --git a/firmware/App/Src/main.c b/firmware/App/Src/main.c index 1d59769..7059c63 100644 --- a/firmware/App/Src/main.c +++ b/firmware/App/Src/main.c @@ -1,27 +1,30 @@ +#include +#include #include "FreeRTOS.h" +#include "audio_adc.h" #include "stm32f1xx.h" #include "task.h" #include "tusb.h" +// System Clock Configuration + void SystemClock_Config(void) { + // Настройка на 72 MHz через HSE + PLL + // 1. Включаем HSE (внешний кварц 8 МГц) RCC->CR |= RCC_CR_HSEON; while (!(RCC->CR & RCC_CR_HSERDY)); - // 2. Настраиваем Flash: 1 цикл ожидания (достаточно для 48 МГц) - FLASH->ACR = FLASH_ACR_LATENCY_1; + // 2. Настраиваем Flash: 2 цикла ожидания для 72 MHz + FLASH->ACR = FLASH_ACR_LATENCY_2; - // 3. Настраиваем PLL: - // Множитель x6: 8 МГц * 6 = 48 МГц - RCC->CFGR &= ~RCC_CFGR_PLLMULL; // Сброс битов множителя - RCC->CFGR |= RCC_CFGR_PLLMULL6; + // 3. Настраиваем PLL: 8 MHz * 9 = 72 MHz + RCC->CFGR &= ~RCC_CFGR_PLLMULL; + RCC->CFGR |= RCC_CFGR_PLLMULL9; + RCC->CFGR |= RCC_CFGR_PLLSRC; // Источник PLL = HSE - // Источник PLL = HSE - RCC->CFGR |= RCC_CFGR_PLLSRC; - - // 4. Настраиваем USB делитель: - // USBPRE = 1 (делитель /1). Т.е. 48 МГц / 1 = 48 МГц - RCC->CFGR |= RCC_CFGR_USBPRE; + // 4. USB делитель: 72 MHz / 1.5 = 48 MHz + RCC->CFGR &= ~RCC_CFGR_USBPRE; // USBPRE = 0 (делитель 1.5) // 5. Включаем PLL RCC->CR |= RCC_CR_PLLON; @@ -32,21 +35,79 @@ void SystemClock_Config(void) { RCC->CFGR |= RCC_CFGR_SW_PLL; while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); - // Обновляем глобальную переменную частоты - SystemCoreClock = 48000000; + SystemCoreClock = 72000000; } -// Задача USB +// Audio Processing Callback + +// Буфер для отправки статистики через USB +static char usb_tx_buffer[128]; +static volatile uint32_t total_samples = 0; + +void audio_buffer_ready(audio_sample_t *buffer, uint32_t size) { + // Эта функция вызывается из прерывания DMA + // Не делать тяжелых операций здесь + + total_samples += size; + + // мин/макс значения + uint16_t min_val = 4095; + uint16_t max_val = 0; + uint32_t sum = 0; + + for (uint32_t i = 0; i < size; i++) { + uint16_t val = buffer[i]; + if (val < min_val) min_val = val; + if (val > max_val) max_val = val; + sum += val; + } + + uint16_t avg = sum / size; + + // Каждые 2 секунды отправляем статистику: каждый 100-й буфер + static uint32_t report_counter = 0; + report_counter++; + + if (report_counter >= 100) { + report_counter = 0; + + snprintf( + usb_tx_buffer, + sizeof(usb_tx_buffer), + "ADC Stats - Min: %4u, Max: %4u, Avg: %4u, Total samples: %lu\r\n", + min_val, + max_val, + avg, + total_samples); + } +} + +// FreeRTOS Tasks + void usb_device_task(void *param) { (void)param; - while (1) { tud_task(); } + while (1) { + tud_task(); + vTaskDelay(pdMS_TO_TICKS(1)); + } } -// Задача CDC void cdc_task(void *param) { (void)param; + while (1) { if (tud_cdc_connected()) { + // Отправка статистики если есть данные + if (usb_tx_buffer[0] != '\0') { + size_t len = strlen(usb_tx_buffer); + if (tud_cdc_write_available() >= len) { + tud_cdc_write(usb_tx_buffer, len); + tud_cdc_write_flush(); + usb_tx_buffer[0] = '\0'; // Очищаем буфер + } + } + + // Echo для тестирования if (tud_cdc_available()) { uint8_t buf[64]; uint32_t count = tud_cdc_read(buf, sizeof(buf)); @@ -54,21 +115,24 @@ void cdc_task(void *param) { tud_cdc_write_flush(); } } + vTaskDelay(pdMS_TO_TICKS(10)); } } -// Задача LED (отдельно!) void led_task(void *param) { (void)param; + while (1) { GPIOC->BSRR = GPIO_BSRR_BR13; // LED ON - vTaskDelay(pdMS_TO_TICKS(500)); + vTaskDelay(pdMS_TO_TICKS(100)); GPIOC->BSRR = GPIO_BSRR_BS13; // LED OFF - vTaskDelay(pdMS_TO_TICKS(500)); + vTaskDelay(pdMS_TO_TICKS(900)); } } +// USB Reset + void force_usb_reset(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; GPIOA->CRH &= ~GPIO_CRH_CNF12; @@ -79,10 +143,12 @@ void force_usb_reset(void) { GPIOA->CRH |= GPIO_CRH_CNF12_0; } +// Main + int main(void) { SystemClock_Config(); - // Настройка LED + // Настройка LED (PC13) RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; GPIOC->CRH &= ~GPIO_CRH_CNF13; GPIOC->CRH |= GPIO_CRH_MODE13_1; @@ -93,7 +159,6 @@ int main(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; RCC->APB1ENR |= RCC_APB1ENR_USBEN; - // Устанавливаем приоритет USB прерываний больше 5, больше FreeRTOS NVIC_SetPriority(USB_HP_CAN1_TX_IRQn, 6); NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 6); NVIC_SetPriority(USBWakeUp_IRQn, 6); @@ -104,6 +169,21 @@ int main(void) { tusb_init(); + // Инициализация Audio ADC + if (!audio_adc_init(audio_buffer_ready)) { + // Ошибка инициализации - быстро мигаем LED + while (1) { + GPIOC->BSRR = GPIO_BSRR_BR13; + for (volatile int i = 0; i < 100000; i++); + GPIOC->BSRR = GPIO_BSRR_BS13; + for (volatile int i = 0; i < 100000; i++); + } + } + + // Запускаем захват аудио + audio_adc_start(); + + // Создаем задачи FreeRTOS xTaskCreate( usb_device_task, "usbd", @@ -115,9 +195,12 @@ int main(void) { xTaskCreate(led_task, "led", 128, NULL, 1, NULL); vTaskStartScheduler(); + while (1); } +// USB Interrupt Handlers + void USB_HP_CAN1_TX_IRQHandler(void) { tud_int_handler(0); } diff --git a/firmware/Makefile b/firmware/Makefile index eadd545..04431e3 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -5,6 +5,7 @@ BUILD_DIR = Build # 1. Приложение C_SOURCES = \ App/Src/main.c \ +App/Src/audio_adc.c \ App/Src/usb_descriptors.c \ App/Src/system_stm32f1xx.c \