> 技术文档 > Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放_基于CherryUSB的UAC音频传输与STM32 ADC DAC应用

Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放_基于CherryUSB的UAC音频传输与STM32 ADC DAC应用


1. 程序基本框架

Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放_基于CherryUSB的UAC音频传输与STM32 ADC DAC应用

  • audio_v1_mic_speaker_multichan_template.c文件中,主要usbd_audio_out_callback和usbd_audio_in_callback函数。在usbd_audio_out_callback中添加rt_ringbuffer_put操作,在usbd_audio_in_callback中目前基本不做修改。
  • 在tim_adc_dac_dma_usb.c文件中, 主要实现了2个线程, 一个是adc线程, 一个是dac线程。主要实现adc和dac的dma传输,以及usb的读取和写入。
  • adc线程主要实现了adc的dma buffer读取(通过ringbuffer), 以及usb的发送功能
  • dac线程主要实现了usb的接收数据的读取功能(通过ringbuffer), 以及dac的dma buffer的填充
  • 在整个程序结构中, ringbuffer和信号量起到了非常关键的调节作用。
  • 信号量主要用于控制线程的运行, 比如当ringbuffer中的数据不足时, 就会阻塞线程, 等待数据的到来。
  • ringbuffer主要用于存储数据, 比如usb接收的数据, 或者是dac需要播放的数据。
  • 当usb接收数据时, 会将数据存储到ringbuffer中, dac线程从ringbuffer中读取数据进行播放。那么dac什么时候从ringbuffer中读取数据呢?这取决于dac的传输半完成中断和传输完成中断. 当中断发生的时候,发出信号量通知dac线程填充buffer数据, 那么dac线程被唤醒之后, 根据标志从ringbuffer获取数据,然后填充dma buffer。如果ringbuffer数量不足, 就填充静音数据, 这是dac线程的特别之处.
  • 对于adc线程要简单一些, 因为对于usb来说, 我们的发送方, 我们主动性强很多.

2. audio_v1_mic_speaker_multichan_template.c的修改说明

  • 主要修改了usbd_audio_out_callback函数, 主要添加了rt_ringbuffer_put操作, 用于将usb接收的数据存储到ringbuffer中。
void usbd_audio_out_callback(uint8_t busid, uint8_t ep, uint32_t nbytes){ // USB_LOG_RAW(\"actual out len:%d\\r\\n\", nbytes); /* 写入 ring-buffer */ extern struct rt_ringbuffer usb_to_dac_ring; rt_ringbuffer_put(&usb_to_dac_ring, read_buffer, nbytes); /* 继续启动下一次 USB 读取 */ usbd_ep_start_read(busid, AUDIO_OUT_EP, read_buffer, AUDIO_OUT_PACKET);}void usbd_audio_in_callback(uint8_t busid, uint8_t ep, uint32_t nbytes){ // USB_LOG_RAW(\"actual in len:%d\\r\\n\", nbytes); ep_tx_busy_flag = false;}

3. tim_adc_dac_dma_usb.c主程序的实现说明

  • 首先定义了2个ringbuffer, 一个是usb_to_dac_ring, 一个是adc_to_usb_ring. 分别用于存储usb接收的数据, 以及adc采集的数据.
  • 然后定义了2个信号量, 一个是adc_data_ready_sem, 一个是dac_data_req_sem.
  • 其中adc_data_ready_sem用于控制adc线程的运行, 当ringbuffer中的数据不足时, 就会阻塞线程, 等待数据的到来.
  • 而dac_data_req_sem用于控制dac线程的运行, 当dac发生传输完成或者传输半完成中断时, 就会发出信号量通知dac线程填充buffer数据.
  • 定义了2个线程, 一个是usb_to_dac_thread, 一个是adc_to_usb_thread. 分别用于实现dac需要播放的数据的填充, 以及usb发送需要的数据的读取.
  • 定义了一些dma使用到的buffer, 注意这些buffer放到了USB_NOCACHE_RAM_SECTION区域,并且要对齐.
  • 为了将adc和dac的中断触发频率降低一些, 我们增大了dma buffer的大小, 有320x2个16位样本. 也就是DMA每发送或接收320个数据后, 触发一次中断(传输完成或半传输完成). 由于本例程音频采样频率是16kHz, 这意味着每隔20ms触发一次中断, 这个足够慢了(实际可能不需要这么大buffer).
// 定义USB音频参数#define USB_AUDIO_SAMPLE_RATE 16000#define USB_AUDIO_CHANNELS 2#define USB_AUDIO_BYTES_PER_SAMPLE 2 // 16bit#define USB_AUDIO_PACKET_SIZE 64 // 与USB定义匹配// 定义ringbuffer大小#define USB_TO_DAC_BUFFER_SIZE 4096 // USB→DAC缓冲#define ADC_TO_USB_BUFFER_SIZE 4096 // ADC→USB缓冲// 创建ringbufferstruct rt_ringbuffer usb_to_dac_ring;static struct rt_ringbuffer adc_to_usb_ring;static uint8_t usb_to_dac_buf[USB_TO_DAC_BUFFER_SIZE];static uint8_t adc_to_usb_buf[ADC_TO_USB_BUFFER_SIZE];// 信号量和线程static rt_sem_t adc_data_ready_sem = RT_NULL;static rt_sem_t dac_data_req_sem = RT_NULL;static volatile uint8_t buffer_ready_flag = 0;static rt_thread_t usb_to_dac_thread = RT_NULL;static rt_thread_t adc_to_usb_thread = RT_NULL;// 修改缓冲区定义#define DAC_DMA_BUFFER_SIZE (USB_AUDIO_PACKET_SIZE*10/2) // 320个16位样本#define ADC_DMA_BUFFER_SIZE (USB_AUDIO_PACKET_SIZE*10/2) // 320个16位样本USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t dac_dma_buffer[DAC_DMA_BUFFER_SIZE * 2]; // 双缓冲USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t adc_dma_buffer[ADC_DMA_BUFFER_SIZE * 2]; // 双缓冲USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t usb_send_buffer[USB_AUDIO_PACKET_SIZE];USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t temp_buffer[DAC_DMA_BUFFER_SIZE];
  • 在ADC的传输完成或半传输完成中断中, 主要填充ringbuffer, 并释放信号量, 通知adc线程接收数据.
  • 因为adc接收的数据是16bits无符号的, 而音频数据是16bits有符号的. 因此我们需要将其转换为16bits有符号的, 所以需要减去32768.
  • 注意ringbuffer是以uint8_t存储数据的
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;  // 写入ringbuffer 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_sem_release(adc_data_ready_sem); }}
  • 在dac的传输完成或半传输完成中断中, 主要是释放信号量, 通知dac线程填充数据.
  • 通过设置标志buffer_ready_flag, 来指示dac线程需要填充的是前半缓冲区还是后半缓冲区.
void HAL_DACEx_ConvHalfCpltCallbackCh2(DAC_HandleTypeDef* hdac){ // 前半缓冲区已传输完成,准备填充后半缓冲区 buffer_ready_flag = 1; // 标记需要填充前半缓冲区 rt_sem_release(dac_data_req_sem);}void HAL_DACEx_ConvCpltCallbackCh2(DAC_HandleTypeDef* hdac){ // 后半缓冲区已传输完成,准备填充前半缓冲区  buffer_ready_flag = 2; // 标记需要填充后半缓冲区 rt_sem_release(dac_data_req_sem);}
  • dac线程的处理逻辑是, 会等待信号量dac_data_req_sem, 然后决定从usb_to_dac_ringbuffer中读取数据, 并填充到dac_dma_buffer中.
  • 因为dac_dma_buffer是双缓冲的, 所以需要根据buffer_ready_flag来确定填充的是前半缓冲区还是后半缓冲区.
  • 有一个特别的是, 当数据不足的时候, 会填充静音数据. 因为DMA一旦启动, 就是连续不停的running, 它无法等待,你必须要给它数据, 否则它播放的可能是之前的数据或是乱的, 这是不允许的. 这种情况下, 最好的方式给他填充静音数据.
  • 从ringbuffer取到的数据, 转成int16_t后, 注意是有符号的, 这是从usb uac接收到音频数据. 所以需要加上32768, 才能转换成16bits无符号的. 又由于我们的DAC是12位的, 所以需要右移4bits转成12位。
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]; // 前半缓冲区 else target_buffer = &dac_dma_buffer[DAC_DMA_BUFFER_SIZE]; // 后半缓冲区 // 从USB ringbuffer读取数据 if (rt_ringbuffer_data_len(&usb_to_dac_ring) >= DAC_DMA_BUFFER_SIZE * 2) {  size_t read_len = rt_ringbuffer_get(&usb_to_dac_ring, (uint8_t *)temp_buffer, DAC_DMA_BUFFER_SIZE * 2);  // 数据格式转换并填充目标缓冲区  for (int i = 0; i < read_len/2; i++)  {  int16_t audio_sample = ((int16_t *)temp_buffer)[i];  target_buffer[i] = (audio_sample + 32768) >> 4;  } } else { // 数据不够时填充静音 memset(target_buffer, 0x80, DAC_DMA_BUFFER_SIZE * 2); } } }}
  • adc线程的处理逻辑是, 会等待信号量adc_data_ready_sem, 然后从adc_to_usb_ringbuffer中读取数据, 并填充到usb_send_buffer中.
  • 然后开启usb的发送, 等待发送完成.
  • usb发送完成后, 会在usbd_audio_in_callback中设置标志ep_tx_busy_flag为0, 于是adc线程可以发送下一批数据.
static void adc_to_usb_thread_entry(void *parameter){ extern volatile bool tx_flag; while (1) { if (tx_flag) { while (rt_ringbuffer_data_len(&adc_to_usb_ring) < sizeof(usb_send_buffer)) { rt_sem_take(adc_data_ready_sem, RT_WAITING_FOREVER); } size_t read_len = rt_ringbuffer_get(&adc_to_usb_ring, usb_send_buffer, sizeof(usb_send_buffer)); ep_tx_busy_flag = 1; usbd_ep_start_write(0, AUDIO_IN_EP, usb_send_buffer, read_len); while(ep_tx_busy_flag){ } } else { rt_thread_delay(10); } }}
  • 初始化的过程, 主要是初始化dma, adc, dac, 定时器. 然后初始化ringbuffer, 信号量. 最后创建线程.
  • 然后启动定时器, adc_dma, dac_dma.
  • 这个初始化与前面的TIM + DAC +DAC的回环试验基本是一样的. 前面的文章是这一篇的基础.
int ADC_DAC_DMA_Init(void){ MX_DMA_Init(); MX_ADC1_Init(); MX_DAC1_Init(); MX_TIM2_Init(); rt_ringbuffer_init(&usb_to_dac_ring, usb_to_dac_buf, sizeof(usb_to_dac_buf)); rt_ringbuffer_init(&adc_to_usb_ring, adc_to_usb_buf, sizeof(adc_to_usb_buf)); adc_data_ready_sem = rt_sem_create(\"adc_ready\", 0, RT_IPC_FLAG_FIFO); dac_data_req_sem = rt_sem_create(\"dac_buf\", 0, RT_IPC_FLAG_FIFO); memset(dac_dma_buffer, 0x80, sizeof(dac_dma_buffer)); usb_to_dac_thread = rt_thread_create(\"usb2dac\",  usb_to_dac_thread_entry,  RT_NULL,  2048, 15, 10); adc_to_usb_thread = rt_thread_create(\"adc2usb\",  adc_to_usb_thread_entry,  RT_NULL,  2048, 15, 10); if (usb_to_dac_thread && adc_to_usb_thread) { rt_thread_startup(usb_to_dac_thread); rt_thread_startup(adc_to_usb_thread); } HAL_TIM_Base_Start(&htim2); HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_2, (uint32_t *)dac_dma_buffer, DAC_DMA_BUFFER_SIZE * 2, DAC_ALIGN_12B_R); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer, ADC_DMA_BUFFER_SIZE * 2); return 0;}INIT_APP_EXPORT(ADC_DAC_DMA_Init);

4. 效果演示

  • 播放效果。上抖音搜索仙剑奇侠传主题曲《此生不换》,通过板子播放。最初是用PA5脚直推一个喇叭, 发现推不动,声音非常非常小。后来换了一个自带功放的喇叭,效果不错。
    Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放_基于CherryUSB的UAC音频传输与STM32 ADC DAC应用
    Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放_基于CherryUSB的UAC音频传输与STM32 ADC DAC应用

  • 录音效果。打开录音机, 录一段我说话的声音, 然后播放出来, 效果不错.
    Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放_基于CherryUSB的UAC音频传输与STM32 ADC DAC应用

  • 播放的同时录音。在通过板子播放《此生不换》的时候,打开录音机也同步录音。录音完毕,用板子再播放,杂音较大(这个喇叭好像不太给力); 用电脑播放, 声音很不错, 这说明录音的文件应该没有问题.
    Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放_基于CherryUSB的UAC音频传输与STM32 ADC DAC应用

5.附录1: audio_v1_mic_speaker_multichan_template.c 完整代码

/* * Copyright (c) 2024, sakumisu * * SPDX-License-Identifier: Apache-2.0 */#include \"usbd_core.h\"#include \"usbd_audio.h\"#include \"trace_log.h\"#include #include  #define USING_FEEDBACK 0#define USBD_VID  0xffff#define USBD_PID  0xffff#define USBD_MAX_POWER 100#define USBD_LANGID_STRING 1033#ifdef CONFIG_USB_HS#define EP_INTERVAL  0x04#define FEEDBACK_ENDP_PACKET_SIZE 0x04#else#define EP_INTERVAL  0x01#define FEEDBACK_ENDP_PACKET_SIZE 0x03#endif#define AUDIO_IN_EP 0x81#define AUDIO_OUT_EP 0x02#define AUDIO_OUT_FEEDBACK_EP 0x83#define AUDIO_IN_FU_ID 0x02#define AUDIO_OUT_FU_ID 0x05/* AUDIO Class Config */#define AUDIO_SPEAKER_FREQ 16000U#define AUDIO_SPEAKER_FRAME_SIZE_BYTE 2u#define AUDIO_SPEAKER_RESOLUTION_BIT 16u#define AUDIO_MIC_FREQ 16000U#define AUDIO_MIC_FRAME_SIZE_BYTE 2u#define AUDIO_MIC_RESOLUTION_BIT 16u#define AUDIO_SAMPLE_FREQ(frq) (uint8_t)(frq), (uint8_t)((frq >> 8)), (uint8_t)((frq >> 16))/* AudioFreq * DataSize (2 bytes) * NumChannels (Stereo: 2) */#define AUDIO_OUT_PACKET ((uint32_t)((AUDIO_SPEAKER_FREQ * AUDIO_SPEAKER_FRAME_SIZE_BYTE * 2) / 1000))/* 16bit(2 Bytes) 双声道(Mono:2) */#define AUDIO_IN_PACKET ((uint32_t)((AUDIO_MIC_FREQ * AUDIO_MIC_FRAME_SIZE_BYTE * 2) / 1000))#if USING_FEEDBACK == 0#define USB_AUDIO_CONFIG_DESC_SIZ (unsigned long)(9 +  \\ AUDIO_AC_DESCRIPTOR_INIT_LEN(2) + \\ AUDIO_SIZEOF_AC_INPUT_TERMINAL_DESC + \\ AUDIO_SIZEOF_AC_FEATURE_UNIT_DESC(2, 1) + \\ AUDIO_SIZEOF_AC_OUTPUT_TERMINAL_DESC + \\ AUDIO_SIZEOF_AC_INPUT_TERMINAL_DESC + \\ AUDIO_SIZEOF_AC_FEATURE_UNIT_DESC(2, 1) + \\ AUDIO_SIZEOF_AC_OUTPUT_TERMINAL_DESC + \\ AUDIO_AS_DESCRIPTOR_INIT_LEN(1) + \\ AUDIO_AS_DESCRIPTOR_INIT_LEN(1))#else#define USB_AUDIO_CONFIG_DESC_SIZ (unsigned long)(9 +  \\ AUDIO_AC_DESCRIPTOR_INIT_LEN(2) + \\ AUDIO_SIZEOF_AC_INPUT_TERMINAL_DESC + \\ AUDIO_SIZEOF_AC_FEATURE_UNIT_DESC(2, 1) + \\ AUDIO_SIZEOF_AC_OUTPUT_TERMINAL_DESC + \\ AUDIO_SIZEOF_AC_INPUT_TERMINAL_DESC + \\ AUDIO_SIZEOF_AC_FEATURE_UNIT_DESC(2, 1) + \\ AUDIO_SIZEOF_AC_OUTPUT_TERMINAL_DESC + \\ AUDIO_AS_DESCRIPTOR_INIT_LEN(1) + \\ AUDIO_AS_FEEDBACK_DESCRIPTOR_INIT_LEN(1))#endif#define AUDIO_AC_SIZ (AUDIO_SIZEOF_AC_HEADER_DESC(2) + \\AUDIO_SIZEOF_AC_INPUT_TERMINAL_DESC + \\AUDIO_SIZEOF_AC_FEATURE_UNIT_DESC(2, 1) + \\AUDIO_SIZEOF_AC_OUTPUT_TERMINAL_DESC + \\AUDIO_SIZEOF_AC_INPUT_TERMINAL_DESC + \\AUDIO_SIZEOF_AC_FEATURE_UNIT_DESC(2, 1) + \\AUDIO_SIZEOF_AC_OUTPUT_TERMINAL_DESC)#ifdef CONFIG_USBDEV_ADVANCE_DESCstatic const uint8_t device_descriptor[] = { USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xef, 0x02, 0x01, USBD_VID, USBD_PID, 0x0001, 0x01)};static const uint8_t config_descriptor[] = { USB_CONFIG_DESCRIPTOR_INIT(USB_AUDIO_CONFIG_DESC_SIZ, 0x03, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), AUDIO_AC_DESCRIPTOR_INIT(0x00, 0x03, AUDIO_AC_SIZ, 0x00, 0x01, 0x02), AUDIO_AC_INPUT_TERMINAL_DESCRIPTOR_INIT(0x01, AUDIO_INTERM_MIC, 0x02, 0x0003), AUDIO_AC_FEATURE_UNIT_DESCRIPTOR_INIT(0x02, 0x01, 0x01, 0x03, 0x00, 0x00), AUDIO_AC_OUTPUT_TERMINAL_DESCRIPTOR_INIT(0x03, AUDIO_TERMINAL_STREAMING, 0x02), AUDIO_AC_INPUT_TERMINAL_DESCRIPTOR_INIT(0x04, AUDIO_TERMINAL_STREAMING, 0x02, 0x0003), AUDIO_AC_FEATURE_UNIT_DESCRIPTOR_INIT(0x05, 0x04, 0x01, 0x03, 0x00, 0x00), AUDIO_AC_OUTPUT_TERMINAL_DESCRIPTOR_INIT(0x06, AUDIO_OUTTERM_SPEAKER, 0x05),#if USING_FEEDBACK == 0 AUDIO_AS_DESCRIPTOR_INIT(0x01, 0x04, 0x02, AUDIO_SPEAKER_FRAME_SIZE_BYTE, AUDIO_SPEAKER_RESOLUTION_BIT, AUDIO_OUT_EP, 0x09, AUDIO_OUT_PACKET, EP_INTERVAL, AUDIO_SAMPLE_FREQ_3B(AUDIO_SPEAKER_FREQ)),#else AUDIO_AS_FEEDBACK_DESCRIPTOR_INIT(0x01, 0x04, 0x02, AUDIO_SPEAKER_FRAME_SIZE_BYTE, AUDIO_SPEAKER_RESOLUTION_BIT, AUDIO_OUT_EP, AUDIO_OUT_PACKET, EP_INTERVAL, AUDIO_OUT_FEEDBACK_EP, AUDIO_SAMPLE_FREQ_3B(AUDIO_SPEAKER_FREQ)),#endif AUDIO_AS_DESCRIPTOR_INIT(0x02, 0x03, 0x02, AUDIO_MIC_FRAME_SIZE_BYTE, AUDIO_MIC_RESOLUTION_BIT, AUDIO_IN_EP, 0x05, AUDIO_IN_PACKET, EP_INTERVAL, AUDIO_SAMPLE_FREQ_3B(AUDIO_MIC_FREQ))};static const uint8_t device_quality_descriptor[] = { /////////////////////////////////////// /// device qualifier descriptor /////////////////////////////////////// 0x0a, USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,};static const char *string_descriptors[] = { (const char[]){ 0x09, 0x04 }, /* Langid */ \"CherryUSB\",  /* Manufacturer */ \"CherryUSB UAC DEMO\", /* Product */ \"2022123456\",  /* Serial Number */};static const uint8_t *device_descriptor_callback(uint8_t speed){ return device_descriptor;}static const uint8_t *config_descriptor_callback(uint8_t speed){ return config_descriptor;}static const uint8_t *device_quality_descriptor_callback(uint8_t speed){ return device_quality_descriptor;}static const char *string_descriptor_callback(uint8_t speed, uint8_t index){ if (index > 3) { return NULL; } return string_descriptors[index];}const struct usb_descriptor audio_v1_descriptor = { .device_descriptor_callback = device_descriptor_callback, .config_descriptor_callback = config_descriptor_callback, .device_quality_descriptor_callback = device_quality_descriptor_callback, .string_descriptor_callback = string_descriptor_callback};#elseconst uint8_t audio_v1_descriptor[] = { USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xef, 0x02, 0x01, USBD_VID, USBD_PID, 0x0001, 0x01), USB_CONFIG_DESCRIPTOR_INIT(USB_AUDIO_CONFIG_DESC_SIZ, 0x03, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), AUDIO_AC_DESCRIPTOR_INIT(0x00, 0x03, AUDIO_AC_SIZ, 0x00, 0x01, 0x02), AUDIO_AC_INPUT_TERMINAL_DESCRIPTOR_INIT(0x01, AUDIO_INTERM_MIC, 0x02, 0x0003), AUDIO_AC_FEATURE_UNIT_DESCRIPTOR_INIT(0x02, 0x01, 0x01, 0x03, 0x00, 0x00), AUDIO_AC_OUTPUT_TERMINAL_DESCRIPTOR_INIT(0x03, AUDIO_TERMINAL_STREAMING, 0x02), AUDIO_AC_INPUT_TERMINAL_DESCRIPTOR_INIT(0x04, AUDIO_TERMINAL_STREAMING, 0x02, 0x0003), AUDIO_AC_FEATURE_UNIT_DESCRIPTOR_INIT(0x05, 0x04, 0x01, 0x03, 0x00, 0x00), AUDIO_AC_OUTPUT_TERMINAL_DESCRIPTOR_INIT(0x06, AUDIO_OUTTERM_SPEAKER, 0x05),#if USING_FEEDBACK == 0 AUDIO_AS_DESCRIPTOR_INIT(0x01, 0x04, 0x02, AUDIO_SPEAKER_FRAME_SIZE_BYTE, AUDIO_SPEAKER_RESOLUTION_BIT, AUDIO_OUT_EP, 0x09, AUDIO_OUT_PACKET, EP_INTERVAL, AUDIO_SAMPLE_FREQ_3B(AUDIO_SPEAKER_FREQ)),#else AUDIO_AS_FEEDBACK_DESCRIPTOR_INIT(0x01, 0x04, 0x02, AUDIO_SPEAKER_FRAME_SIZE_BYTE, AUDIO_SPEAKER_RESOLUTION_BIT, AUDIO_OUT_EP, AUDIO_OUT_PACKET, EP_INTERVAL, AUDIO_OUT_FEEDBACK_EP, AUDIO_SAMPLE_FREQ_3B(AUDIO_SPEAKER_FREQ)),#endif AUDIO_AS_DESCRIPTOR_INIT(0x02, 0x03, 0x02, AUDIO_MIC_FRAME_SIZE_BYTE, AUDIO_MIC_RESOLUTION_BIT, AUDIO_IN_EP, 0x05, AUDIO_IN_PACKET, EP_INTERVAL, AUDIO_SAMPLE_FREQ_3B(AUDIO_MIC_FREQ)), /////////////////////////////////////// /// string0 descriptor /////////////////////////////////////// USB_LANGID_INIT(USBD_LANGID_STRING), /////////////////////////////////////// /// string1 descriptor /////////////////////////////////////// 0x14, /* bLength */ USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ \'C\', 0x00,  /* wcChar0 */ \'h\', 0x00,  /* wcChar1 */ \'e\', 0x00,  /* wcChar2 */ \'r\', 0x00,  /* wcChar3 */ \'r\', 0x00,  /* wcChar4 */ \'y\', 0x00,  /* wcChar5 */ \'U\', 0x00,  /* wcChar6 */ \'S\', 0x00,  /* wcChar7 */ \'B\', 0x00,  /* wcChar8 */ /////////////////////////////////////// /// string2 descriptor /////////////////////////////////////// 0x26, /* bLength */ USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ \'C\', 0x00,  /* wcChar0 */ \'h\', 0x00,  /* wcChar1 */ \'e\', 0x00,  /* wcChar2 */ \'r\', 0x00,  /* wcChar3 */ \'r\', 0x00,  /* wcChar4 */ \'y\', 0x00,  /* wcChar5 */ \'U\', 0x00,  /* wcChar6 */ \'S\', 0x00,  /* wcChar7 */ \'B\', 0x00,  /* wcChar8 */ \' \', 0x00,  /* wcChar9 */ \'U\', 0x00,  /* wcChar10 */ \'A\', 0x00,  /* wcChar11 */ \'C\', 0x00,  /* wcChar12 */ \' \', 0x00,  /* wcChar13 */ \'D\', 0x00,  /* wcChar14 */ \'E\', 0x00,  /* wcChar15 */ \'M\', 0x00,  /* wcChar16 */ \'O\', 0x00,  /* wcChar17 */ /////////////////////////////////////// /// string3 descriptor /////////////////////////////////////// 0x16, /* bLength */ USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ \'2\', 0x00,  /* wcChar0 */ \'0\', 0x00,  /* wcChar1 */ \'2\', 0x00,  /* wcChar2 */ \'2\', 0x00,  /* wcChar3 */ \'1\', 0x00,  /* wcChar4 */ \'2\', 0x00,  /* wcChar5 */ \'3\', 0x00,  /* wcChar6 */ \'4\', 0x00,  /* wcChar7 */ \'5\', 0x00,  /* wcChar8 */#if USING_FEEDBACK == 0 \'1\', 0x00,  /* wcChar9 */#else \'2\', 0x00,  /* wcChar9 */#endif#ifdef CONFIG_USB_HS /////////////////////////////////////// /// device qualifier descriptor /////////////////////////////////////// 0x0a, USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,#endif 0x00};#endifUSB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer[AUDIO_OUT_PACKET];USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer[AUDIO_IN_PACKET];USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t s_speaker_feedback_buffer[4];volatile bool tx_flag = 0;volatile bool rx_flag = 0;volatile bool ep_tx_busy_flag = false;static void usbd_event_handler(uint8_t busid, uint8_t event){ switch (event) { case USBD_EVENT_RESET: break; case USBD_EVENT_CONNECTED: break; case USBD_EVENT_DISCONNECTED: break; case USBD_EVENT_RESUME: break; case USBD_EVENT_SUSPEND: break; case USBD_EVENT_CONFIGURED: break; case USBD_EVENT_SET_REMOTE_WAKEUP: break; case USBD_EVENT_CLR_REMOTE_WAKEUP: break; default: break; }}void usbd_audio_open(uint8_t busid, uint8_t intf){ if (intf == 1) { rx_flag = 1; /* setup first out ep read transfer */ usbd_ep_start_read(busid, AUDIO_OUT_EP, read_buffer, AUDIO_OUT_PACKET); uint32_t feedback_value = AUDIO_FREQ_TO_FEEDBACK_FS(AUDIO_SPEAKER_FREQ); AUDIO_FEEDBACK_TO_BUF_FS(s_speaker_feedback_buffer, feedback_value); /* uac1 can only use 10.14 */ usbd_ep_start_write(busid, AUDIO_OUT_FEEDBACK_EP, s_speaker_feedback_buffer, FEEDBACK_ENDP_PACKET_SIZE); printf(\"OPEN1\\r\\n\"); } else { tx_flag = 1; ep_tx_busy_flag = false; printf(\"OPEN2\\r\\n\"); }}void usbd_audio_close(uint8_t busid, uint8_t intf){ if (intf == 1) { rx_flag = 0; printf(\"CLOSE1\\r\\n\"); } else { tx_flag = 0; ep_tx_busy_flag = false; printf(\"CLOSE2\\r\\n\"); }}void usbd_audio_out_callback(uint8_t busid, uint8_t ep, uint32_t nbytes){ // USB_LOG_RAW(\"actual out len:%d\\r\\n\", nbytes); /* 写入 ring-buffer */ extern struct rt_ringbuffer usb_to_dac_ring; rt_ringbuffer_put(&usb_to_dac_ring, read_buffer, nbytes); /* 继续启动下一次 USB 读取 */ usbd_ep_start_read(busid, AUDIO_OUT_EP, read_buffer, AUDIO_OUT_PACKET);}void usbd_audio_in_callback(uint8_t busid, uint8_t ep, uint32_t nbytes){ // USB_LOG_RAW(\"actual in len:%d\\r\\n\", nbytes); ep_tx_busy_flag = false;}#if USING_FEEDBACK == 1void usbd_audio_iso_out_feedback_callback(uint8_t busid, uint8_t ep, uint32_t nbytes){ USB_LOG_RAW(\"actual feedback len:%d\\r\\n\", nbytes); uint32_t feedback_value = AUDIO_FREQ_TO_FEEDBACK_FS(AUDIO_SPEAKER_FREQ); AUDIO_FEEDBACK_TO_BUF_FS(s_speaker_feedback_buffer, feedback_value); usbd_ep_start_write(busid, AUDIO_OUT_FEEDBACK_EP, s_speaker_feedback_buffer, FEEDBACK_ENDP_PACKET_SIZE);}#endifstatic struct usbd_endpoint audio_in_ep = { .ep_cb = usbd_audio_in_callback, .ep_addr = AUDIO_IN_EP};static struct usbd_endpoint audio_out_ep = { .ep_cb = usbd_audio_out_callback, .ep_addr = AUDIO_OUT_EP};#if USING_FEEDBACK == 1static struct usbd_endpoint audio_out_feedback_ep = { .ep_cb = usbd_audio_iso_out_feedback_callback, .ep_addr = AUDIO_OUT_FEEDBACK_EP};#endifstruct usbd_interface intf0;struct usbd_interface intf1;struct usbd_interface intf2;struct audio_entity_info audio_entity_table[] = { { .bEntityId = AUDIO_IN_FU_ID, .bDescriptorSubtype = AUDIO_CONTROL_FEATURE_UNIT, .ep = AUDIO_IN_EP }, { .bEntityId = AUDIO_OUT_FU_ID, .bDescriptorSubtype = AUDIO_CONTROL_FEATURE_UNIT, .ep = AUDIO_OUT_EP },};void audio_v1_init(uint8_t busid, uintptr_t reg_base){#ifdef CONFIG_USBDEV_ADVANCE_DESC usbd_desc_register(busid, &audio_v1_descriptor);#else usbd_desc_register(busid, audio_v1_descriptor);#endif usbd_add_interface(busid, usbd_audio_init_intf(busid, &intf0, 0x0100, audio_entity_table, 2)); usbd_add_interface(busid, usbd_audio_init_intf(busid, &intf1, 0x0100, audio_entity_table, 2)); usbd_add_interface(busid, usbd_audio_init_intf(busid, &intf2, 0x0100, audio_entity_table, 2)); usbd_add_endpoint(busid, &audio_in_ep); usbd_add_endpoint(busid, &audio_out_ep);#if USING_FEEDBACK == 1 usbd_add_endpoint(busid, &audio_out_feedback_ep);#endif usbd_initialize(busid, reg_base, usbd_event_handler);}

6.附录2: tim_adc_dac_dma_usb.c完整代码

  • 这个代码包含了完整的tim+adc+dac+dma的初始化代码, 不再需要在stm32h7xx_hal_msp.c中进行初始化.
  • 注意需要在rt-thread的menuconfig中开启cherryusb, 以及adc和dac和硬件timer的驱动框架 (只有开启了相应的驱动框架, stm32h7xx_hal_adc.h, stm32h7xx_hal_dac.h等HAL库文件, 才会被纳入到编译中)
#include #include \"usbd_core.h\"#include \"trace_log.h\"#define USB_NOCACHE_RAM_SECTION __attribute__((section(\".noncacheable\")))#define USB_MEM_ALIGNX __attribute__((aligned(32)))ADC_HandleTypeDef hadc1;DMA_HandleTypeDef hdma_adc1;DAC_HandleTypeDef hdac1;DMA_HandleTypeDef hdma_dac1_ch2;TIM_HandleTypeDef htim2;// 定义USB音频参数#define USB_AUDIO_SAMPLE_RATE 16000#define USB_AUDIO_CHANNELS 2#define USB_AUDIO_BYTES_PER_SAMPLE 2 // 16bit#define USB_AUDIO_PACKET_SIZE 64 // 与USB定义匹配// 定义ringbuffer大小#define USB_TO_DAC_BUFFER_SIZE 4096 // USB→DAC缓冲#define ADC_TO_USB_BUFFER_SIZE 4096 // ADC→USB缓冲// 创建ringbufferstruct rt_ringbuffer usb_to_dac_ring;static struct rt_ringbuffer adc_to_usb_ring;static uint8_t usb_to_dac_buf[USB_TO_DAC_BUFFER_SIZE];static uint8_t adc_to_usb_buf[ADC_TO_USB_BUFFER_SIZE];// 信号量和线程static rt_sem_t adc_data_ready_sem = RT_NULL;static rt_sem_t dac_data_req_sem = RT_NULL;static volatile uint8_t buffer_ready_flag = 0;static rt_thread_t usb_to_dac_thread = RT_NULL;static rt_thread_t adc_to_usb_thread = RT_NULL;// 修改缓冲区定义#define DAC_DMA_BUFFER_SIZE (USB_AUDIO_PACKET_SIZE*10/2) // 320个16位样本#define ADC_DMA_BUFFER_SIZE (USB_AUDIO_PACKET_SIZE*10/2) // 320个16位样本USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t dac_dma_buffer[DAC_DMA_BUFFER_SIZE * 2]; // 双缓冲USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t adc_dma_buffer[ADC_DMA_BUFFER_SIZE * 2]; // 双缓冲USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t usb_send_buffer[USB_AUDIO_PACKET_SIZE];USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint16_t temp_buffer[DAC_DMA_BUFFER_SIZE];static void MX_ADC1_Init(void);static void MX_DAC1_Init(void);static void MX_TIM2_Init(void);static void MX_DMA_Init(void);extern volatile uint8_t ep_tx_busy_flag;#define AUDIO_IN_EP 0x81#define AUDIO_OUT_EP 0x02void 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;  // 写入ringbuffer 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_sem_release(adc_data_ready_sem); }}void HAL_DACEx_ConvHalfCpltCallbackCh2(DAC_HandleTypeDef* hdac){ // 前半缓冲区已传输完成,准备填充后半缓冲区 buffer_ready_flag = 1; // 标记需要填充前半缓冲区 rt_sem_release(dac_data_req_sem);}void HAL_DACEx_ConvCpltCallbackCh2(DAC_HandleTypeDef* hdac){ // 后半缓冲区已传输完成,准备填充前半缓冲区  buffer_ready_flag = 2; // 标记需要填充后半缓冲区 rt_sem_release(dac_data_req_sem);}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]; // 前半缓冲区 else target_buffer = &dac_dma_buffer[DAC_DMA_BUFFER_SIZE]; // 后半缓冲区 // 从USB ringbuffer读取数据 if (rt_ringbuffer_data_len(&usb_to_dac_ring) >= DAC_DMA_BUFFER_SIZE * 2) {  size_t read_len = rt_ringbuffer_get(&usb_to_dac_ring, (uint8_t *)temp_buffer, DAC_DMA_BUFFER_SIZE * 2);  // 数据格式转换并填充目标缓冲区  for (int i = 0; i < read_len/2; i++)  {  int16_t audio_sample = ((int16_t *)temp_buffer)[i];  target_buffer[i] = (audio_sample + 32768) >> 4;  } } else { // 数据不够时填充静音 memset(target_buffer, 0x80, DAC_DMA_BUFFER_SIZE * 2); } } }}static void adc_to_usb_thread_entry(void *parameter){ extern volatile bool tx_flag; while (1) { if (tx_flag) { while (rt_ringbuffer_data_len(&adc_to_usb_ring) < sizeof(usb_send_buffer)) { rt_sem_take(adc_data_ready_sem, RT_WAITING_FOREVER); } size_t read_len = rt_ringbuffer_get(&adc_to_usb_ring, usb_send_buffer, sizeof(usb_send_buffer)); ep_tx_busy_flag = 1; usbd_ep_start_write(0, AUDIO_IN_EP, usb_send_buffer, read_len); while(ep_tx_busy_flag){ } } else { rt_thread_delay(10); } }}int ADC_DAC_DMA_Init(void){ MX_DMA_Init(); MX_ADC1_Init(); MX_DAC1_Init(); MX_TIM2_Init(); rt_ringbuffer_init(&usb_to_dac_ring, usb_to_dac_buf, sizeof(usb_to_dac_buf)); rt_ringbuffer_init(&adc_to_usb_ring, adc_to_usb_buf, sizeof(adc_to_usb_buf)); adc_data_ready_sem = rt_sem_create(\"adc_ready\", 0, RT_IPC_FLAG_FIFO); dac_data_req_sem = rt_sem_create(\"dac_buf\", 0, RT_IPC_FLAG_FIFO); memset(dac_dma_buffer, 0x80, sizeof(dac_dma_buffer)); usb_to_dac_thread = rt_thread_create(\"usb2dac\",  usb_to_dac_thread_entry,  RT_NULL,  2048, 15, 10); adc_to_usb_thread = rt_thread_create(\"adc2usb\",  adc_to_usb_thread_entry,  RT_NULL,  2048, 15, 10); if (usb_to_dac_thread && adc_to_usb_thread) { rt_thread_startup(usb_to_dac_thread); rt_thread_startup(adc_to_usb_thread); } HAL_TIM_Base_Start(&htim2); HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_2, (uint32_t *)dac_dma_buffer, DAC_DMA_BUFFER_SIZE * 2, DAC_ALIGN_12B_R); HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer, ADC_DMA_BUFFER_SIZE * 2); return 0;}INIT_APP_EXPORT(ADC_DAC_DMA_Init);/** * @brief ADC MSP Initialization * This function configures the hardware resources used in this example * @param hadc: ADC handle pointer * @retval None */ void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) { if(hadc->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspInit 0 */ /* USER CODE END ADC1_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_ADC12_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**ADC1 GPIO Configuration PA1_C ------> ADC1_INP1 */ HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PA1, SYSCFG_SWITCH_PA1_OPEN); /* ADC1 DMA Init */ /* ADC1 Init */ hdma_adc1.Instance = DMA1_Stream0; hdma_adc1.Init.Request = DMA_REQUEST_ADC1; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode = DMA_CIRCULAR; hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_ENABLE; hdma_adc1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL; hdma_adc1.Init.MemBurst = DMA_MBURST_SINGLE; hdma_adc1.Init.PeriphBurst = DMA_PBURST_INC4; if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1); /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_IRQn, 0, 0); HAL_NVIC_EnableIRQ(ADC_IRQn); /* USER CODE BEGIN ADC1_MspInit 1 */ /* USER CODE END ADC1_MspInit 1 */ } } /** * @brief ADC MSP De-Initialization * This function freeze the hardware resources used in this example * @param hadc: ADC handle pointer * @retval None */ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc) { if(hadc->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspDeInit 0 */ /* USER CODE END ADC1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_ADC12_CLK_DISABLE(); /* ADC1 DMA DeInit */ HAL_DMA_DeInit(hadc->DMA_Handle); /* ADC1 interrupt DeInit */ HAL_NVIC_DisableIRQ(ADC_IRQn); /* USER CODE BEGIN ADC1_MspDeInit 1 */ /* USER CODE END ADC1_MspDeInit 1 */ } } /** * @brief DAC MSP Initialization * This function configures the hardware resources used in this example * @param hdac: DAC handle pointer * @retval None */ void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(hdac->Instance==DAC1) { /* USER CODE BEGIN DAC1_MspInit 0 */ /* USER CODE END DAC1_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_DAC12_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**DAC1 GPIO Configuration PA5 ------> DAC1_OUT2 */ GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* DAC1 DMA Init */ /* DAC1_CH2 Init */ hdma_dac1_ch2.Instance = DMA1_Stream1; hdma_dac1_ch2.Init.Request = DMA_REQUEST_DAC2; hdma_dac1_ch2.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_dac1_ch2.Init.PeriphInc = DMA_PINC_DISABLE; hdma_dac1_ch2.Init.MemInc = DMA_MINC_ENABLE; hdma_dac1_ch2.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_dac1_ch2.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_dac1_ch2.Init.Mode = DMA_CIRCULAR; hdma_dac1_ch2.Init.Priority = DMA_PRIORITY_LOW; hdma_dac1_ch2.Init.FIFOMode = DMA_FIFOMODE_ENABLE; hdma_dac1_ch2.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL; hdma_dac1_ch2.Init.MemBurst = DMA_MBURST_INC4; hdma_dac1_ch2.Init.PeriphBurst = DMA_PBURST_SINGLE; if (HAL_DMA_Init(&hdma_dac1_ch2) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(hdac,DMA_Handle2,hdma_dac1_ch2); /* USER CODE BEGIN DAC1_MspInit 1 */ /* USER CODE END DAC1_MspInit 1 */ } } /** * @brief DAC MSP De-Initialization * This function freeze the hardware resources used in this example * @param hdac: DAC handle pointer * @retval None */ void HAL_DAC_MspDeInit(DAC_HandleTypeDef* hdac) { if(hdac->Instance==DAC1) { /* USER CODE BEGIN DAC1_MspDeInit 0 */ /* USER CODE END DAC1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_DAC12_CLK_DISABLE(); /**DAC1 GPIO Configuration PA5 ------> DAC1_OUT2 */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5); /* DAC1 DMA DeInit */ HAL_DMA_DeInit(hdac->DMA_Handle2); /* USER CODE BEGIN DAC1_MspDeInit 1 */ /* USER CODE END DAC1_MspDeInit 1 */ } } /** * @brief TIM_Base MSP Initialization * This function configures the hardware resources used in this example * @param htim_base: TIM_Base handle pointer * @retval None */void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base){ if(htim_base->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ }}/** * @brief TIM_Base MSP De-Initialization * This function freeze the hardware resources used in this example * @param htim_base: TIM_Base handle pointer * @retval None */void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base){ if(htim_base->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspDeInit 0 */ /* USER CODE END TIM2_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_TIM2_CLK_DISABLE(); /* USER CODE BEGIN TIM2_MspDeInit 1 */ /* USER CODE END TIM2_MspDeInit 1 */ }} /** * @brief ADC1 Initialization Function * @param None * @retval None */ static void MX_ADC1_Init(void) { /* USER CODE BEGIN ADC1_Init 0 */ /* USER CODE END ADC1_Init 0 */ ADC_MultiModeTypeDef multimode = {0}; ADC_ChannelConfTypeDef sConfig = {0}; /* USER CODE BEGIN ADC1_Init 1 */ /* USER CODE END ADC1_Init 1 */ /** Common config */ hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2; hadc1.Init.Resolution = ADC_RESOLUTION_16B; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; hadc1.Init.LowPowerAutoWait = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.NbrOfConversion = 1; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T2_TRGO; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED; hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE; hadc1.Init.OversamplingMode = DISABLE; hadc1.Init.Oversampling.Ratio = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } /** Configure the ADC multi-mode */ multimode.Mode = ADC_MODE_INDEPENDENT; if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_8CYCLES_5; sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; sConfig.OffsetSignedSaturation = DISABLE; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN ADC1_Init 2 */ /* USER CODE END ADC1_Init 2 */ }/** * @brief DAC1 Initialization Function * @param None * @retval None */static void MX_DAC1_Init(void){ /* USER CODE BEGIN DAC1_Init 0 */ /* USER CODE END DAC1_Init 0 */ DAC_ChannelConfTypeDef sConfig = {0}; /* USER CODE BEGIN DAC1_Init 1 */ /* USER CODE END DAC1_Init 1 */ /** DAC Initialization */ hdac1.Instance = DAC1; if (HAL_DAC_Init(&hdac1) != HAL_OK) { Error_Handler(); } /** DAC channel OUT2 config */ sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE; sConfig.DAC_Trigger = DAC_TRIGGER_T2_TRGO; sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE; sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY; if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_2) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN DAC1_Init 2 */ /* USER CODE END DAC1_Init 2 */}/** * @brief TIM2 Initialization Function * @param None * @retval None */ static void MX_TIM2_Init(void) { /* USER CODE BEGIN TIM2_Init 0 */ /* USER CODE END TIM2_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM2_Init 1 */ /* USER CODE END TIM2_Init 1 */ htim2.Instance = TIM2; htim2.Init.Prescaler = 11 /*240MHz/12=20MHz*/; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 625 /*20MHz/16kHz/2=625, here 2 is stereo*/; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM2_Init 2 */ /* USER CODE END TIM2_Init 2 */ } /** * @brief This function handles DMA1 stream0 global interrupt. */void DMA1_Stream0_IRQHandler(void){ /* USER CODE BEGIN DMA1_Stream0_IRQn 0 */ /* USER CODE END DMA1_Stream0_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_adc1); /* USER CODE BEGIN DMA1_Stream0_IRQn 1 */ /* USER CODE END DMA1_Stream0_IRQn 1 */}/** * @brief This function handles DMA1 stream1 global interrupt. */void DMA1_Stream1_IRQHandler(void){ /* USER CODE BEGIN DMA1_Stream1_IRQn 0 */ /* USER CODE END DMA1_Stream1_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_dac1_ch2); /* USER CODE BEGIN DMA1_Stream1_IRQn 1 */ /* USER CODE END DMA1_Stream1_IRQn 1 */}/** * @brief This function handles ADC1 and ADC2 global interrupts. */ void ADC_IRQHandler(void) { /* USER CODE BEGIN ADC_IRQn 0 */ /* USER CODE END ADC_IRQn 0 */ HAL_ADC_IRQHandler(&hadc1); /* USER CODE BEGIN ADC_IRQn 1 */ /* USER CODE END ADC_IRQn 1 */ }static void MX_DMA_Init(void){ /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Stream0_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn); /* DMA1_Stream1_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);}