当前位置: 首页 > news >正文

针对前面2篇文章的一个细节的修订(UAC ADC/DAC录音播放,以及UAC ADC/PWM录音播放)

1. 前言

  • 本文主要是为了修正前面2篇文章的一个bug, 就是从ADC采集并填充数据的时候,左/右声道没有填充相同的数据。从UAC接收数据并发送给DAC/PWM的时候,把左右声道的数据全部扔给了一个DAC/PWM来播放, 没有从中只抽出一个声道的数据来播放。从原理上说,这样是不好的。

  • 32kHz采样后分配给两个声道,每个声道实际上是以16kHz速率播放,这意味着每个声道的Nyquist频率应为8kHz,如果没有适当的低通滤波处理(滤除高于8kHz的频率),在将32kHz数据转换为16kHz声道时会产生混叠(aliasing)失真

  • 前面存在问题的这2篇文章名字如下:
    《Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放》
    《Cherryusb UAC例程对接STM32内置ADC和PWM播放音乐和录音(下)=>UAC+STM32 ADC+PWM实现录音和播放》

2. 针对《Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放》的修正

  • 首先这个temp_buffer的size修改为DAC_DMA_BUFFER_SIZE*2
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t temp_buffer[DAC_DMA_BUFFER_SIZE*2];
  • 然后修改HAL_ADC_ConvHalfCpltCallback和HAL_ADC_ConvCpltCallback函数,对于每个sample,我们重复填入ringbuffer,让左右声道一样
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{if (hadc->Instance == ADC1){// 处理前半部分ADC数据for (int i = 0; i < ADC_DMA_BUFFER_SIZE; i++){// 将12位ADC数据转换为16位音频数据uint16_t adc_value = adc_dma_buffer[i];int16_t audio_sample = adc_value - 32768;// 写入ringbufferrt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2);rt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2); /*左右声道填一样的值, 重复填一遍*/}rt_sem_release(adc_data_ready_sem);}
}void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{if (hadc->Instance == ADC1){// 处理后半部分ADC数据for (int i = ADC_DMA_BUFFER_SIZE; i < ADC_DMA_BUFFER_SIZE * 2; i++){uint16_t adc_value = adc_dma_buffer[i];int16_t audio_sample = adc_value - 32768;rt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2);rt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2); /*左右声道填一样的值, 重复填一遍*/}rt_sem_release(adc_data_ready_sem);}
}
  • 对于dac线程, 需要修改的包括:一次要从ringbuffer读取DAC_DMA_BUFFER_SIZE * 4字节数据,然后从temp_buffer中抽取偶数序列的数据填充dma_buffer。相当于DAC只播放一个声道的数据。
static void usb_to_dac_thread_entry(void *parameter)
{while (1){// 等待DAC缓冲区需要填充if (rt_sem_take(dac_data_req_sem, RT_WAITING_FOREVER) == RT_EOK){uint16_t *target_buffer;// 根据标志确定填充哪个缓冲区if (buffer_ready_flag == 1)target_buffer = &dac_dma_buffer[0];  // 前半缓冲区elsetarget_buffer = &dac_dma_buffer[DAC_DMA_BUFFER_SIZE];  // 后半缓冲区// 从USB ringbuffer读取数据if (rt_ringbuffer_data_len(&usb_to_dac_ring) >= DAC_DMA_BUFFER_SIZE * 4){size_t read_len = rt_ringbuffer_get(&usb_to_dac_ring, (uint8_t *)temp_buffer, DAC_DMA_BUFFER_SIZE * 4);// 数据格式转换并填充目标缓冲区for (int i = 0; i < read_len/4; i++){int16_t audio_sample = ((int16_t *)temp_buffer)[2*i];  /*只播放偶数声道数据*/target_buffer[i] = (audio_sample + 32768) >> 4;}} else{// 数据不够时填充静音memset(target_buffer, 0x80, DAC_DMA_BUFFER_SIZE * 2);}}}
}
  • 定时器MX_TIM2_Init函数的修改,频率修改为16kHz(之前为32kHz),也就是说ADC和DAC都只需要以16kHz进行采集和播放
   /* USER CODE END TIM2_Init 1 */htim2.Instance = TIM2;htim2.Init.Prescaler = 0;htim2.Init.CounterMode = TIM_COUNTERMODE_UP;htim2.Init.Period = 14999; /*adc_dac_sample 240e6/15000=16kHz*/htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim2) != HAL_OK)

3. 针对《Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放》的修正

  • 首先这个temp_buffer的size修改为DAC_DMA_BUFFER_SIZE*2
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t temp_buffer[DAC_DMA_BUFFER_SIZE*2];
  • 然后修改HAL_ADC_ConvHalfCpltCallback和HAL_ADC_ConvCpltCallback函数,对于每个sample,我们重复填入ringbuffer,让左右声道一样
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{if (hadc->Instance == ADC1){// 处理前半部分ADC数据for (int i = 0; i < ADC_DMA_BUFFER_SIZE; i++){// 将12位ADC数据转换为16位音频数据uint16_t adc_value = adc_dma_buffer[i];int16_t audio_sample = adc_value - 32768;// 写入ringbufferrt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2);rt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2); /*左右声道填一样的值, 重复填一遍*/}rt_sem_release(adc_data_ready_sem);}
}void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{if (hadc->Instance == ADC1){// 处理后半部分ADC数据for (int i = ADC_DMA_BUFFER_SIZE; i < ADC_DMA_BUFFER_SIZE * 2; i++){uint16_t adc_value = adc_dma_buffer[i];int16_t audio_sample = adc_value - 32768;rt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2);rt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&audio_sample, 2); /*左右声道填一样的值, 重复填一遍*/}rt_sem_release(adc_data_ready_sem);}
}
  • 对于dac线程, 需要修改的包括:一次要从ringbuffer读取DAC_DMA_BUFFER_SIZE * 4字节数据,然后从temp_buffer中抽取偶数序列的数据填充dma_buffer。相当于PWM只播放一个声道的数据。
static void usb_to_dac_thread_entry(void *parameter)
{while (1){// 等待DAC缓冲区需要填充if (rt_sem_take(dac_data_req_sem, RT_WAITING_FOREVER) == RT_EOK){uint32_t *target_buffer;// 根据标志确定填充哪个缓冲区if (buffer_ready_flag == 1)target_buffer = &dac_dma_buffer[0];  // 前半缓冲区elsetarget_buffer = &dac_dma_buffer[DAC_DMA_BUFFER_SIZE];  // 后半缓冲区// 从USB ringbuffer读取数据if (rt_ringbuffer_data_len(&usb_to_dac_ring) >= DAC_DMA_BUFFER_SIZE * 4){size_t read_len = rt_ringbuffer_get(&usb_to_dac_ring, (uint8_t *)temp_buffer, DAC_DMA_BUFFER_SIZE * 4);// 数据格式转换并填充目标缓冲区for (int i = 0; i < read_len/4; i++){int16_t audio_sample = ((int16_t *)temp_buffer)[2*i]; /*只播放偶数声道数据*/target_buffer[i] = ((uint16_t)((int16_t)audio_sample + 32768) * 1499)/65535;}} else{// 数据不够时填充静音for(int i = 0; i < DAC_DMA_BUFFER_SIZE; i++){target_buffer[i] = 1499/2;}// memset(target_buffer, 0x00, DAC_DMA_BUFFER_SIZE * 4);}}}
}
  • 定时器MX_TIM2_Init函数的修改,频率修改为16kHz(之前为32kHz),也就是说ADC都只需要以16kHz进行采集
   /* USER CODE END TIM2_Init 1 */htim2.Instance = TIM2;htim2.Init.Prescaler = 0;htim2.Init.CounterMode = TIM_COUNTERMODE_UP;htim2.Init.Period = 14999; /*adc_dac_sample 240e6/15000=16kHz*/htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  • 定时器MX_TIM12_Init函数的修改,频率修改为16kHz(之前为32kHz),也就是说DAC都只需要以16kHz进行播放
   /* USER CODE END TIM12_Init 1 */htim12.Instance = TIM12;htim12.Init.Prescaler = 0;htim12.Init.CounterMode = TIM_COUNTERMODE_UP;htim12.Init.Period = 14999; /*pwm_dac 240e6/15000=16kHz*/htim12.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim12.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim12) != HAL_OK)

3. 修改后的效果测试

经过修改后,整体播放效果ok,但跟修改前的效果对比,听不出明显差别。

http://www.dtcms.com/a/333682.html

相关文章:

  • const修饰指针用法详解
  • libdrm 和 libgbm
  • 零基础从头教学Linux(Day 13)
  • 13_集合框架
  • ScanNet项目介绍
  • Linux网络配置:聚合链路与网桥实战
  • 开疆智能ModbusTCP转Ethernet网关连接FBOX串口服务器配置案例
  • MySQL多表查询案例
  • 360 集团20周年会:战略升级ALL IN Agent,抢占智能体时代先机
  • OSCP - Proving Grounds - CVE-2024-25180
  • 基于WSL搭建Ubuntu 22.04.x LTS开发环境
  • LLM - 使用 SGLang 部署 Qwen3-32B 与 Qwen2.5-VL-32B-Instruct
  • 《GUI-Actor: Coordinate-Free Visual Grounding for GUI Agents》论文精读笔记
  • 今日分享:C++模板(全详解)
  • 大数据系列之:设置CMS垃圾收集器
  • std::set_symmetric_difference
  • 第七十三章:AI的“黑箱”迷局:推理链路中的断点与Tensor调试——让模型“交代一切”!
  • CCS双轴相位偏移光源 让浅凹痕无处遁形
  • 【Redis】超详细基础入门学习
  • 硬件开发_基于STM32单片机的热水壶系统
  • GitHub的使用教程
  • Upload 上传 vue2前端 + 后端
  • 【DDIA】第二部分:分布式数据
  • 【大模型微调系列-02】 深度学习与大模型初识
  • Java Lambda表达式是什么,怎么用
  • C语言笔记6:C高级 part1
  • Go从入门到精通系列学习路线规划
  • 区块链技术原理(13)-以太坊燃料费Gas
  • ITM(仪器跟踪宏单元)是什么?
  • Elasticsearch赋能规章制度智能检索:从海量文档到秒级响应