Cherryusb UAC例程對接STM32 SAI播放音樂和錄音(下)=>USB+SAI+TX+RX+DMA控制WM8978播放和錄音實驗

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布局的元素叫…