1. 程序基本框架

整个程序框架, 与之前的一篇文章《Cherryusb UAC例程对接STM32内置ADC和DAC播放音乐和录音(中)=>UAC+STM32 ADC+DAC实现录音和播放》基本一致, 只是这次将ADC和DAC替换成了SAI TX/RX。因此这里不再赘述了。

在这里插入图片描述

2. sai_dma_wm8978_usb.c主程序的实现说明

  • 在menuconfig中开启soft-i2c, 使用i2c3, 引脚对应ART-PI P2排针上的PH11/PH12
  • soft-i2c使用非常方便,无需CubeMX配置硬件I2C等繁琐步骤。只需要menuconfig中开启并指定GPIO引脚即可(任意2个脚都可以)
#define CODEC_I2C_NAME  ("i2c3") /*直接使用rt-thread的soft-i2c来配置, 非常方便, 只要Menuconfig设置GPIO编号即可*/
  • Kconfig中对i2c3的定义
            menuconfig BSP_USING_I2C3bool "Enable I2C3 BUS (software simulation)"default nif BSP_USING_I2C3comment "Notice: PH12 --> 124; PH11 --> 123"config BSP_I2C3_SCL_PINint "i2c3 scl pin number"range 0 175default 123config BSP_I2C3_SDA_PINint "I2C3 sda pin number"range 0 175default 124endif
  • 定义了5个操作函数, 用于wm8978的控制和SAI的控制。代码比较简单。
  • 关于wm8978的操作函数,我们是直接调用了一个rt-thread工程中现成的库。这个库文件的路径为
    https://gitee.com/rtthread/rt-thread/tree/master/bsp/stm32/stm32f429-atk-apollo/board/ports/audio/drv_wm8978.c
  • 注意,如果是单独启动录音,需要先启动SAI TX发送时钟出来,然后SAI RX才能工作。可以通过HAL_SAI_Transmit(&hsai_BlockA2, (uint8_t[4]){0}, 4, 0)以发送空数据的形式开启时钟。但结合UAC使用后,不需要单独开启SAI TX时钟。因此在PC上无论开启录音或播放,都会发送audio_open命令打开SAI TX,时钟必然是会产生的。
struct rt_i2c_bus_device *codec_i2c_bus;
static rt_err_t stm32_player_init(void)
{codec_i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(CODEC_I2C_NAME);wm8978_init(codec_i2c_bus);return RT_EOK;
}rt_err_t stm32_player_start(void)
{HAL_SAI_Transmit_DMA(&hsai_BlockA2, (uint8_t *)dac_dma_buffer, DAC_DMA_BUFFER_SIZE * 2);wm8978_player_start(codec_i2c_bus);return RT_EOK;
}rt_err_t stm32_player_stop(void)
{HAL_SAI_DMAStop(&hsai_BlockA2);   return RT_EOK;    
}void start_record_mode(void)
{HAL_SAI_DMAStop(&hsai_BlockB2);// HAL_SAI_Transmit(&hsai_BlockA2, (uint8_t[4]){0}, 4, 0);   /*通过tx的方式启动时钟. 如果单独使用mic, 需要这么做. 但本文不需要这么做, 因为uac一旦打开, 就会默认先打开tx*/HAL_SAI_Receive_DMA(&hsai_BlockB2, (uint8_t *)adc_dma_buffer, ADC_DMA_BUFFER_SIZE * 2);
}rt_err_t stm32_mic_start(void)
{wm8978_record_start(codec_i2c_bus);start_record_mode();return RT_EOK;
}rt_err_t stm32_mic_stop(void)
{HAL_SAI_DMAStop(&hsai_BlockB2);HAL_SAI_DMAStop(&hsai_BlockA2);wm8978_mic_enabled(codec_i2c_bus, 0);return RT_EOK;
}
  • 创建了一个事件标志组,用于与usb中断回调函数进行同步
  • 新创建了一个独立的控制线程,用于实现open, close等操作
  • 用事件标志组比用全局变量更高明一些,因为它会触发线程睡眠,不会浪费cpu算力。实时性也会更好。
rt_event_t usb_event;
// 定义事件标志位, 用于控制audio open和close
#define USB_EVENT_AUDIO_OPEN    (1 << 0)
#define USB_EVENT_AUDIO_CLOSE   (1 << 1)static void cherryusb_thread_entry(void *parameter)
{rt_uint32_t recv_event;while (1){// 等待事件,永久阻塞直到事件发生// RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR选项表示任何一个标志来都会唤醒, rt_event_recv执行之后即clear掉//  rt_event_send在usbd_audio_open和usbd_audio_close中发送if (rt_event_recv(usb_event, USB_EVENT_AUDIO_OPEN | USB_EVENT_AUDIO_CLOSE,RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &recv_event) == RT_EOK){if (recv_event & USB_EVENT_AUDIO_OPEN){extern volatile bool tx_flag;extern volatile bool rx_flag;if (rx_flag) stm32_player_start();if (tx_flag) stm32_mic_start();}if (recv_event & USB_EVENT_AUDIO_CLOSE){extern volatile bool tx_flag;extern volatile bool rx_flag;if (!rx_flag) stm32_player_stop();if (!tx_flag) stm32_mic_stop();}}}
}
  • main函数,初始化SAI和DMA,初始化ringbuffer和信号量,event事件,创建线程
  • 最后启动stm32_player_init,初始化wm8978
int SAI_DMA_Init(void)
{MX_DMA_Init();MX_SAI2_Init();rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);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, 1, 10);/*数据流线程*/                                    adc_to_usb_thread = rt_thread_create("adc2usb", adc_to_usb_thread_entry, RT_NULL,2048, 15, 10);/*控制流线程*/                                    cherryusb_thread = rt_thread_create("cherryusb", cherryusb_thread_entry, RT_NULL,2048, 15, 10);if (usb_to_dac_thread && adc_to_usb_thread && cherryusb_thread){rt_thread_startup(usb_to_dac_thread);rt_thread_startup(adc_to_usb_thread);rt_thread_startup(cherryusb_thread);}usb_event = rt_event_create("usb_evt", RT_IPC_FLAG_FIFO);// HAL_SAI_Receive_DMA(&hsai_BlockB2, (uint8_t *)adc_dma_buffer, ADC_DMA_BUFFER_SIZE * 2);  // HAL_SAI_Transmit_DMA(&hsai_BlockA2, (uint8_t *)dac_dma_buffer, DAC_DMA_BUFFER_SIZE * 2);stm32_player_init();// stm32_player_start();// stm32_mic_start();return 0;
}
  • SAI中断回调函数
  • 对于Rx直接写ringbuffer,不用做任何处理,这个相比ADC/DAC/PWM就简单很多了。因为我们从wm8978接收的是标准的I2S格式的数据。
  • 对于Tx,我们通过设置一个buffer_ready_flag标志,然后发sem来给线程来请求数据。这个跟以前是一样的。
void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai)
{if(hsai->Instance == SAI2_Block_B){rt_enter_critical();// 处理前半部分ADC数据, 直接写入ringbufferrt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&adc_dma_buffer, 2*ADC_DMA_BUFFER_SIZE);rt_sem_release(adc_data_ready_sem);rt_exit_critical();}
}void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)
{if(hsai->Instance == SAI2_Block_B){rt_enter_critical();// 处理后半部分ADC数据, 直接写入ringbufferrt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&adc_dma_buffer[ADC_DMA_BUFFER_SIZE], 2*ADC_DMA_BUFFER_SIZE);rt_sem_release(adc_data_ready_sem);rt_exit_critical();}
}void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai)
{// rt_pin_write(LED0_PIN, PIN_HIGH);if(hsai->Instance == SAI2_Block_A){// 后半缓冲区已传输完成,准备填充前半缓冲区  buffer_ready_flag = 2;  // 标记需要填充后半缓冲区rt_sem_release(dac_data_req_sem);}
}void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef *hsai)
{// rt_pin_write(LED0_PIN, PIN_LOW);if(hsai->Instance == SAI2_Block_A){// 前半缓冲区已传输完成,准备填充后半缓冲区buffer_ready_flag = 1;  // 标记需要填充前半缓冲区rt_sem_release(dac_data_req_sem);}
}
  • 数据流线程处理函数
  • 对于Tx线程,我们从ringbuffer中读取数据,然后通过wm8978的I2S接口发送给wm8978。
  • 对于Rx线程,我们从ringbuffer中读取数据,然后通过usb发送给pc。
  • 无论TX/RX,都不用做任何格式转换,因为从usb收到的,以及通过i2s发送的,都是符合标准的I2S协议。
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 * 2){size_t read_len = rt_ringbuffer_get(&usb_to_dac_ring, (uint8_t *)temp_buffer, DAC_DMA_BUFFER_SIZE * 2);// 数据格式转换并填充目标缓冲区memcpy(target_buffer, temp_buffer, read_len);} else{rt_pin_write(LED0_PIN, !rt_pin_read(LED0_PIN));// 数据不够时填充静音for(int i = 0; i < DAC_DMA_BUFFER_SIZE; i++){target_buffer[i] = 32768;}// memset(target_buffer, 0x00, DAC_DMA_BUFFER_SIZE * 4);}}}
}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(1);}}
}

3. audio_v1_mic_speaker_multichan_template.c的修改说明

  • 主要就是增加了事件标志组,用于通知线程音频打开关闭
  • 必须得这么做,原因是wm8978_player_start, wm8978_record_start等函数里面使用了mutex,不能在中断里面调用。而usbd_audio_open和usbd_audio_close是在usb中断回调函数中执行的,因此没法运行必须得这么做,原因是wm8978_player_start和wm8978_record_start等函数的使用限制。
  • 为了解决这个问题,我们只能通过创建一个线程,以接收事件标志的方式执行wm8978_player_start, wm8978_record_start等函数。

extern rt_event_t usb_event;
// 定义事件标志位
#define USB_EVENT_AUDIO_OPEN    (1 << 0)
#define USB_EVENT_AUDIO_CLOSE   (1 << 1)void usbd_audio_open(uint8_t busid, uint8_t intf)
{rt_event_send(usb_event, USB_EVENT_AUDIO_OPEN); /* 发送音频打开事件 */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)
{rt_event_send(usb_event, USB_EVENT_AUDIO_CLOSE); /* 发送音频关闭事件 */if (intf == 1) {rx_flag = 0;printf("CLOSE1\r\n");} else {tx_flag = 0;ep_tx_busy_flag = false;printf("CLOSE2\r\n");}
}

4. drv_wm8978.c程序

https://gitee.com/rtthread/rt-thread/tree/master/bsp/stm32/stm32f429-atk-apollo/board/ports/audio/drv_wm8978.c

5. 效果演示

在这里插入图片描述

  • 电脑上打开录音机进行录音,然后再进行播放。整个效果不错。
  • 唯一存在的问题是,在音乐播放过程中,总是有一个很小的"咔咔咔咔"的杂音。这个在前面用DAC和PWM播放的时候没有出现过。这个原因待分析,不清楚是否与SAI或WM8978有关系?

6.附录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 <rtthread.h>
#include <rtdevice.h> #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_DESC
static 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 == 0AUDIO_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)),
#elseAUDIO_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)),
#endifAUDIO_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
};
#else
const 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 == 0AUDIO_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)),
#elseAUDIO_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)),
#endifAUDIO_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,
#endif0x00
};
#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;}
}extern rt_event_t usb_event;
// 定义事件标志位
#define USB_EVENT_AUDIO_OPEN    (1 << 0)
#define USB_EVENT_AUDIO_CLOSE   (1 << 1)void usbd_audio_open(uint8_t busid, uint8_t intf)
{rt_event_send(usb_event, USB_EVENT_AUDIO_OPEN);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)
{rt_event_send(usb_event, USB_EVENT_AUDIO_CLOSE);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 == 1
void 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 == 1
static 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_DESCusbd_desc_register(busid, &audio_v1_descriptor);
#elseusbd_desc_register(busid, audio_v1_descriptor);
#endifusbd_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 == 1usbd_add_endpoint(busid, &audio_out_feedback_ep);
#endifusbd_initialize(busid, reg_base, usbd_event_handler);
}

7.附录2: sai_dma_wm8978_usb.c完整代码

#include <drv_common.h>
#include "usbd_core.h"
#include "drv_wm8978.h"
#include "trace_log.h"#define USB_NOCACHE_RAM_SECTION __attribute__((section(".noncacheable")))
// #define USB_MEM_ALIGNX __attribute__((aligned(32)))SAI_HandleTypeDef hsai_BlockA2;
SAI_HandleTypeDef hsai_BlockB2;
DMA_HandleTypeDef hdma_sai2_a;
DMA_HandleTypeDef hdma_sai2_b;#define LED0_PIN    GET_PIN(G, 11)
#define CODEC_I2C_NAME  ("i2c3") /*直接使用rt-thread的soft-i2c来配置, 非常方便, 只要Menuconfig设置GPIO编号即可*/// 定义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缓冲// 创建ringbuffer
struct 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;
static rt_thread_t cherryusb_thread = RT_NULL;rt_event_t usb_event;
// 定义事件标志位, 用于控制audio open和close
#define USB_EVENT_AUDIO_OPEN    (1 << 0)
#define USB_EVENT_AUDIO_CLOSE   (1 << 1)// 修改缓冲区定义
#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_SAI2_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_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai)
{if(hsai->Instance == SAI2_Block_B){rt_enter_critical();// 处理前半部分ADC数据, 直接写入ringbufferrt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&adc_dma_buffer, 2*ADC_DMA_BUFFER_SIZE);rt_sem_release(adc_data_ready_sem);rt_exit_critical();}
}void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)
{if(hsai->Instance == SAI2_Block_B){rt_enter_critical();// 处理后半部分ADC数据, 直接写入ringbufferrt_ringbuffer_put(&adc_to_usb_ring, (uint8_t *)&adc_dma_buffer[ADC_DMA_BUFFER_SIZE], 2*ADC_DMA_BUFFER_SIZE);rt_sem_release(adc_data_ready_sem);rt_exit_critical();}
}void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai)
{// rt_pin_write(LED0_PIN, PIN_HIGH);if(hsai->Instance == SAI2_Block_A){// 后半缓冲区已传输完成,准备填充前半缓冲区  buffer_ready_flag = 2;  // 标记需要填充后半缓冲区rt_sem_release(dac_data_req_sem);}
}void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef *hsai)
{// rt_pin_write(LED0_PIN, PIN_LOW);if(hsai->Instance == SAI2_Block_A){// 前半缓冲区已传输完成,准备填充后半缓冲区buffer_ready_flag = 1;  // 标记需要填充前半缓冲区rt_sem_release(dac_data_req_sem);}
}struct rt_i2c_bus_device *codec_i2c_bus;
static rt_err_t stm32_player_init(void)
{codec_i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(CODEC_I2C_NAME);wm8978_init(codec_i2c_bus);return RT_EOK;
}rt_err_t stm32_player_start(void)
{HAL_SAI_Transmit_DMA(&hsai_BlockA2, (uint8_t *)dac_dma_buffer, DAC_DMA_BUFFER_SIZE * 2);wm8978_player_start(codec_i2c_bus);return RT_EOK;
}rt_err_t stm32_player_stop(void)
{HAL_SAI_DMAStop(&hsai_BlockA2);   return RT_EOK;    
}void start_record_mode(void)
{HAL_SAI_DMAStop(&hsai_BlockB2);// HAL_SAI_Transmit(&hsai_BlockA2, (uint8_t[4]){0}, 4, 0);   /*通过tx的方式启动时钟. 如果单独使用mic, 需要这么做. 但本文不需要这么做, 因为uac一旦打开, 就会默认先打开tx*/HAL_SAI_Receive_DMA(&hsai_BlockB2, (uint8_t *)adc_dma_buffer, ADC_DMA_BUFFER_SIZE * 2);
}rt_err_t stm32_mic_start(void)
{wm8978_record_start(codec_i2c_bus);start_record_mode();return RT_EOK;
}rt_err_t stm32_mic_stop(void)
{HAL_SAI_DMAStop(&hsai_BlockB2);HAL_SAI_DMAStop(&hsai_BlockA2);wm8978_mic_enabled(codec_i2c_bus, 0);return RT_EOK;
}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 * 2){size_t read_len = rt_ringbuffer_get(&usb_to_dac_ring, (uint8_t *)temp_buffer, DAC_DMA_BUFFER_SIZE * 2);// 数据格式转换并填充目标缓冲区memcpy(target_buffer, temp_buffer, read_len);} else{rt_pin_write(LED0_PIN, !rt_pin_read(LED0_PIN));// 数据不够时填充静音for(int i = 0; i < DAC_DMA_BUFFER_SIZE; i++){target_buffer[i] = 32768;}// memset(target_buffer, 0x00, DAC_DMA_BUFFER_SIZE * 4);}}}
}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(1);}}
}static void cherryusb_thread_entry(void *parameter)
{rt_uint32_t recv_event;while (1){// 等待事件,永久阻塞直到事件发生if (rt_event_recv(usb_event, USB_EVENT_AUDIO_OPEN | USB_EVENT_AUDIO_CLOSE,RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &recv_event) == RT_EOK){if (recv_event & USB_EVENT_AUDIO_OPEN){extern volatile bool tx_flag;extern volatile bool rx_flag;if (rx_flag) stm32_player_start();if (tx_flag) stm32_mic_start();}if (recv_event & USB_EVENT_AUDIO_CLOSE){extern volatile bool tx_flag;extern volatile bool rx_flag;if (!rx_flag) stm32_player_stop();if (!tx_flag) stm32_mic_stop();}}}
}int SAI_DMA_Init(void)
{MX_DMA_Init();MX_SAI2_Init();rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);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, 1, 10);/*数据流线程*/                                    adc_to_usb_thread = rt_thread_create("adc2usb", adc_to_usb_thread_entry, RT_NULL,2048, 15, 10);/*控制流线程*/                                    cherryusb_thread = rt_thread_create("cherryusb", cherryusb_thread_entry, RT_NULL,2048, 15, 10);if (usb_to_dac_thread && adc_to_usb_thread && cherryusb_thread){rt_thread_startup(usb_to_dac_thread);rt_thread_startup(adc_to_usb_thread);rt_thread_startup(cherryusb_thread);}usb_event = rt_event_create("usb_evt", RT_IPC_FLAG_FIFO);// HAL_SAI_Receive_DMA(&hsai_BlockB2, (uint8_t *)adc_dma_buffer, ADC_DMA_BUFFER_SIZE * 2);  // HAL_SAI_Transmit_DMA(&hsai_BlockA2, (uint8_t *)dac_dma_buffer, DAC_DMA_BUFFER_SIZE * 2);stm32_player_init();// stm32_player_start();// stm32_mic_start();return 0;
}INIT_APP_EXPORT(SAI_DMA_Init);/**
* @brief SAI2 Initialization Function
* @param None
* @retval None
*/
static void MX_SAI2_Init(void)
{/* USER CODE BEGIN SAI2_Init 0 *//* USER CODE END SAI2_Init 0 *//* USER CODE BEGIN SAI2_Init 1 *//* USER CODE END SAI2_Init 1 */hsai_BlockA2.Instance = SAI2_Block_A;hsai_BlockA2.Init.AudioMode = SAI_MODEMASTER_TX;hsai_BlockA2.Init.Synchro = SAI_ASYNCHRONOUS;hsai_BlockA2.Init.OutputDrive = SAI_OUTPUTDRIVE_ENABLE;hsai_BlockA2.Init.NoDivider = SAI_MCK_OVERSAMPLING_DISABLE;hsai_BlockA2.Init.MckOverSampling = SAI_MCK_OVERSAMPLING_DISABLE;hsai_BlockA2.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_HF;hsai_BlockA2.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_16K;hsai_BlockA2.Init.SynchroExt = SAI_SYNCEXT_DISABLE;hsai_BlockA2.Init.MonoStereoMode = SAI_STEREOMODE;hsai_BlockA2.Init.CompandingMode = SAI_NOCOMPANDING;hsai_BlockA2.Init.TriState = SAI_OUTPUT_NOTRELEASED;if (HAL_SAI_InitProtocol(&hsai_BlockA2, SAI_I2S_STANDARD, SAI_PROTOCOL_DATASIZE_16BIT, 2) != HAL_OK){Error_Handler();}hsai_BlockB2.Instance = SAI2_Block_B;hsai_BlockB2.Init.AudioMode = SAI_MODESLAVE_RX;hsai_BlockB2.Init.Synchro = SAI_SYNCHRONOUS;hsai_BlockB2.Init.OutputDrive = SAI_OUTPUTDRIVE_ENABLE;hsai_BlockB2.Init.MckOverSampling = SAI_MCK_OVERSAMPLING_DISABLE;hsai_BlockB2.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_HF;hsai_BlockB2.Init.SynchroExt = SAI_SYNCEXT_DISABLE;hsai_BlockB2.Init.MonoStereoMode = SAI_STEREOMODE;hsai_BlockB2.Init.CompandingMode = SAI_NOCOMPANDING;hsai_BlockB2.Init.TriState = SAI_OUTPUT_NOTRELEASED;if (HAL_SAI_InitProtocol(&hsai_BlockB2, SAI_I2S_STANDARD, SAI_PROTOCOL_DATASIZE_16BIT, 2) != HAL_OK){Error_Handler();}/* USER CODE BEGIN SAI2_Init 2 *//* USER CODE END SAI2_Init 2 */}static uint32_t SAI2_client =0;void HAL_SAI_MspInit(SAI_HandleTypeDef* hsai)
{GPIO_InitTypeDef GPIO_InitStruct;
/* SAI2 */if(hsai->Instance==SAI2_Block_A){/* Peripheral clock enable */if (SAI2_client == 0){__HAL_RCC_SAI2_CLK_ENABLE();/* Peripheral interrupt init*/HAL_NVIC_SetPriority(SAI2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(SAI2_IRQn);}SAI2_client ++;/**SAI2_A_Block_A GPIO ConfigurationPI6     ------> SAI2_SD_API5     ------> SAI2_SCK_API4     ------> SAI2_MCLK_API7     ------> SAI2_FS_A*/GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_5|GPIO_PIN_4|GPIO_PIN_7;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF10_SAI2;HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);/* Peripheral DMA init*/hdma_sai2_a.Instance = DMA1_Stream2;hdma_sai2_a.Init.Request = DMA_REQUEST_SAI2_A;hdma_sai2_a.Init.Direction = DMA_MEMORY_TO_PERIPH;hdma_sai2_a.Init.PeriphInc = DMA_PINC_DISABLE;hdma_sai2_a.Init.MemInc = DMA_MINC_ENABLE;hdma_sai2_a.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_sai2_a.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_sai2_a.Init.Mode = DMA_CIRCULAR;hdma_sai2_a.Init.Priority = DMA_PRIORITY_LOW;hdma_sai2_a.Init.FIFOMode = DMA_FIFOMODE_ENABLE;hdma_sai2_a.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL;hdma_sai2_a.Init.MemBurst = DMA_MBURST_INC4;hdma_sai2_a.Init.PeriphBurst = DMA_PBURST_SINGLE;if (HAL_DMA_Init(&hdma_sai2_a) != HAL_OK){Error_Handler();}/* Several peripheral DMA handle pointers point to the same DMA handle.Be aware that there is only one channel to perform all the requested DMAs. */__HAL_LINKDMA(hsai,hdmarx,hdma_sai2_a);__HAL_LINKDMA(hsai,hdmatx,hdma_sai2_a);}if(hsai->Instance==SAI2_Block_B){/* Peripheral clock enable */if (SAI2_client == 0){__HAL_RCC_SAI2_CLK_ENABLE();/* Peripheral interrupt init*/HAL_NVIC_SetPriority(SAI2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(SAI2_IRQn);}SAI2_client ++;/**SAI2_B_Block_B GPIO ConfigurationPG10     ------> SAI2_SD_B*/GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.Alternate = GPIO_AF10_SAI2;HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);/* Peripheral DMA init*/hdma_sai2_b.Instance = DMA1_Stream3;hdma_sai2_b.Init.Request = DMA_REQUEST_SAI2_B;hdma_sai2_b.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_sai2_b.Init.PeriphInc = DMA_PINC_DISABLE;hdma_sai2_b.Init.MemInc = DMA_MINC_ENABLE;hdma_sai2_b.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_sai2_b.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_sai2_b.Init.Mode = DMA_CIRCULAR;hdma_sai2_b.Init.Priority = DMA_PRIORITY_LOW;hdma_sai2_b.Init.FIFOMode = DMA_FIFOMODE_ENABLE;hdma_sai2_b.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL;hdma_sai2_b.Init.MemBurst = DMA_MBURST_SINGLE;hdma_sai2_b.Init.PeriphBurst = DMA_PBURST_INC4;if (HAL_DMA_Init(&hdma_sai2_b) != HAL_OK){Error_Handler();}/* Several peripheral DMA handle pointers point to the same DMA handle.Be aware that there is only one channel to perform all the requested DMAs. */__HAL_LINKDMA(hsai,hdmarx,hdma_sai2_b);__HAL_LINKDMA(hsai,hdmatx,hdma_sai2_b);}
}void HAL_SAI_MspDeInit(SAI_HandleTypeDef* hsai)
{
/* SAI2 */if(hsai->Instance==SAI2_Block_A){SAI2_client --;if (SAI2_client == 0){/* Peripheral clock disable */__HAL_RCC_SAI2_CLK_DISABLE();/* SAI2 interrupt DeInit */HAL_NVIC_DisableIRQ(SAI2_IRQn);}/**SAI2_A_Block_A GPIO ConfigurationPI6     ------> SAI2_SD_API5     ------> SAI2_SCK_API4     ------> SAI2_MCLK_API7     ------> SAI2_FS_A*/HAL_GPIO_DeInit(GPIOI, GPIO_PIN_6|GPIO_PIN_5|GPIO_PIN_4|GPIO_PIN_7);/* SAI2 DMA Deinit */HAL_DMA_DeInit(hsai->hdmarx);HAL_DMA_DeInit(hsai->hdmatx);}if(hsai->Instance==SAI2_Block_B){SAI2_client --;if (SAI2_client == 0){/* Peripheral clock disable */__HAL_RCC_SAI2_CLK_DISABLE();/* SAI2 interrupt DeInit */HAL_NVIC_DisableIRQ(SAI2_IRQn);}/**SAI2_B_Block_B GPIO ConfigurationPG10     ------> SAI2_SD_B*/HAL_GPIO_DeInit(GPIOG, GPIO_PIN_10);/* SAI2 DMA Deinit */HAL_DMA_DeInit(hsai->hdmarx);HAL_DMA_DeInit(hsai->hdmatx);}
}/**
* Enable DMA controller clock
*/
static void MX_DMA_Init(void)
{/* DMA controller clock enable */__HAL_RCC_DMA1_CLK_ENABLE();/* DMA interrupt init *//* DMA1_Stream2_IRQn interrupt configuration */HAL_NVIC_SetPriority(DMA1_Stream2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Stream2_IRQn);/* DMA1_Stream3_IRQn interrupt configuration */HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn);}/**
* @brief This function handles DMA1 stream2 global interrupt.
*/
void DMA1_Stream2_IRQHandler(void)
{/* USER CODE BEGIN DMA1_Stream2_IRQn 0 *//* USER CODE END DMA1_Stream2_IRQn 0 */rt_base_t level = rt_hw_interrupt_disable();HAL_DMA_IRQHandler(&hdma_sai2_a);rt_hw_interrupt_enable(level);/* USER CODE BEGIN DMA1_Stream2_IRQn 1 *//* USER CODE END DMA1_Stream2_IRQn 1 */
}/*** @brief This function handles DMA1 stream3 global interrupt.*/
void DMA1_Stream3_IRQHandler(void)
{/* USER CODE BEGIN DMA1_Stream3_IRQn 0 *//* USER CODE END DMA1_Stream3_IRQn 0 */rt_base_t level = rt_hw_interrupt_disable();HAL_DMA_IRQHandler(&hdma_sai2_b);rt_hw_interrupt_enable(level);/* USER CODE BEGIN DMA1_Stream3_IRQn 1 *//* USER CODE END DMA1_Stream3_IRQn 1 */
}/**
* @brief This function handles SAI2 global interrupt.
*/
void SAI2_IRQHandler(void)
{/* USER CODE BEGIN SAI2_IRQn 0 *//* USER CODE END SAI2_IRQn 0 */HAL_SAI_IRQHandler(&hsai_BlockA2);HAL_SAI_IRQHandler(&hsai_BlockB2);/* USER CODE BEGIN SAI2_IRQn 1 *//* USER CODE END SAI2_IRQn 1 */
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93469.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93469.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/93469.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker运行python项目:使用Docker成功启动FastAPI应用

根据昨天成功使用阿里云镜像加速后&#xff0c;我是根据windows本地的python项目&#xff0c;直接传到了centos&#xff0c;然后再导入到docker里面&#xff0c;然后进行运行&#xff0c;主要是发现运行的时候&#xff0c;老是提示一些库的问题&#xff0c;还有就是一些python老…

PowerShell来关闭 Windows 安全中心

你可以使用 PowerShell 来关闭 Windows 安全中心的盾牌图标&#xff08;通知&#xff09;。以下是几种方法&#xff0c;包括禁用通知、关闭 Windows Defender&#xff08;不推荐&#xff09;或调整注册表。方法 1&#xff1a;禁用 Windows 安全中心通知&#xff08;推荐&#x…

基于深度学习的老照片修复系统

背景随着时间的推移&#xff0c;老照片可能会因褪色、损坏或曝光不当而影响其视觉质量。这些珍贵的影像承载着历史和回忆&#xff0c;但由于物理损耗&#xff0c;它们的观赏价值和可读性逐渐下降。为了恢复这些照片的清晰度和色彩&#xff0c;本项目采用深度学习与先进的图像处…

深入解析Tomcat目录结构

Apache Tomcat 是一个强大的 Servlet 容器,它不仅支持 Java Servlet 和 JSP 技术,还提供了丰富的功能来帮助开发者构建和部署动态的 Web 应用。为了更好地理解和使用 Tomcat,了解其文件结构和组成部分是至关重要的。本文将深入探讨 Tomcat 的目录结构及其各个组件的作用。 …

专题:2025抖音电商与微短剧行业研究报告|附150+份报告PDF汇总下载

原文链接&#xff1a;https://tecdat.cn/?p43595 当618大促的硝烟散去&#xff0c;抖音电商的生态分化愈发刺眼&#xff1a;服饰内衣以27.5%的份额稳坐头把交椅&#xff0c;而无数中小商家却在“流量荒”中挣扎。这场看似繁荣的盛宴里&#xff0c;平台规则如同无形的手&#x…

3.Ansible自动化之-编写和运行playbook

3.Ansible编写和运行 Playbook Playbook 介绍 如果把 Ansible 的ad-hoc命令比作 “一次性脚本”&#xff08;适合临时执行单个简单任务&#xff09;&#xff0c;那么Playbook就是 “可重复执行的程序”&#xff08;适合复杂、多步骤的管理流程&#xff09;。 举个例子&#…

Vue实时刷新,比如我提交审核,审核页面还需要点查询才能看到最新数据

refreshTimer: null,lastRefreshTime: null}; }, created() {console.log(组件创建&#xff0c;初始化数据...);this.loadLatestData();this.setupAutoRefresh(); }, activated() {// 当使用keep-alive时&#xff0c;组件激活时刷新数据console.log(组件激活&#xff0c;刷新数…

Docker入门:容器化技术的第一堂课

Docker入门&#xff1a;容器化技术的第一堂课 &#x1f31f; 你好&#xff0c;我是 励志成为糕手 &#xff01; &#x1f30c; 在代码的宇宙中&#xff0c;我是那个追逐优雅与性能的星际旅人。 ✨ 每一行代码都是我种下的星光&#xff0c;在逻辑的土壤里生长成璀璨的银河&#…

【SLAM】不同相机模型及其常见的链式求导推导

【SLAM】不同相机模型及其常见的链式求导推导1. 鱼眼相机模型链式求导1. 鱼眼相机畸变模型2. 雅可比矩阵的推导畸变坐标相对于归一化坐标的雅可比矩阵 Hdz/dznH_{dz/dzn}Hdz/dzn​畸变坐标相对于相机内参的雅可比矩阵 Hdz/dzetaH_{dz/dzeta}Hdz/dzeta​3. 注意4. 输入输出含义5…

【人工智能】本地部署 KTransformers并加载大模型笔记

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G技术研究。 博客内容主要围绕…

TDengine IDMP 高级功能(3. 概念解释)

枚举集 为提升数据的可阅读性&#xff0c;IDMP 为数据提供枚举类型。您可以将一些整型数定义为一具有可读性的字符串。与其他软件一样&#xff0c;您可以定义多个枚举集&#xff0c;每个枚举集可以有多个枚举量。您可以增加、删除、修改、查询枚举集与枚举量。 但独特的是&am…

CUDA 入门教程(GPT优化版)

学习路径 一、环境准备与快速入门 搭建开发环境 ○ 安装 CUDA Toolkit,适用于 Windows(如 Visual Studio)或 Linux,确保你的设备为 NVIDIA GPU 并支持 CUDA。(wholetomato.com) ○ 如果你偏好轻量工具,也可用 VS Code + Nsight 开发环境进行 CUDA 编程。(wholetomato.com)…

react项目性能优化的hook

前言&#xff1a;在项目中开发中&#xff0c;性能优化是很重要的&#xff0c;react有提供专门的hook&#xff0c;useMemo 和useCallback 这里说一说他们。区别&#xff1a;特性useMemouseCallback返回值缓存一个 值&#xff08;计算结果&#xff09;缓存一个 函数依赖变化时重新…

Docker(springcloud笔记第三期)

p.s.这是萌新自己自学总结的笔记&#xff0c;如果想学习得更透彻的话还是请去看大佬的讲解 目录镜像与容器一些命令与镜像命名规范数据卷自定义镜像Dockerfile镜像与容器 当我们利用Docker安装应用时&#xff0c;Docker会自动搜索并下载应用镜像(image),镜像不仅包含应用本身&…

MySQL定时任务详解 - Event Scheduler 事件调度器从基础到实战

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

redis存储原理与对象模型

redis中的不同线程 redis单线程是指什么&#xff1f; redis的所有命令处理都在同一个线程中完成 redis为什么采用单线程&#xff1f; redis中存在多种数据结构存储value&#xff0c;如果采用多线程&#xff0c;加锁会很复杂、加锁力度不阿红控制&#xff0c;同时&#xff0c…

基于微信小程序的家教服务平台的设计与实现/基于asp.net/c#的家教服务平台/基于asp.net/c#的家教管理系统

基于微信小程序的家教服务平台的设计与实现/基于asp.net/c#的家教服务平台/基于asp.net/c#的家教管理系统

安全审计-iptales防火墙设置

文章目录一、iptales防火墙设置1.ip规则设置2.ip端口规则设置3.删除规则4.INPUT默认设置5.ping、本地访问规则6.保存还原规则7.查看清除规则一、iptales防火墙设置 1.ip规则设置 #允许ip访问本服务器 iptables -I INPUT -s 192.168.205.129 -p tcp -j ACCEPT#允许某IP或某网段…

Linux小白加油站,第二周

1.grep命令中哪个选项可以忽略大小写进行搜索?grep -i 2.如何用grep命令查找包含”error关键字的日志文件并返回文件名?grep -lr3.解释grep命令中^f...d$这个表达式的含义^f&#xff1a;以f开头..&#xff1a;任意两个字符d$&#xff1a;以d结尾4.如何过滤掉文件中的注释行以…

【前端基础】19、CSS的flex布局

一、FlexBox概念 FlexBox翻译为弹性盒子。 弹性盒子是一种用于按行或按列布局元素的一维布局方式。元素可以膨胀以填充额外的空间&#xff0c;收缩以适应更小的空间。我们使用FlexBox来进行布局的方案称为flex布局。二、flex布局的重要概念 两个重要的概念 开启flex布局的元素叫…