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 */
}