Funpack5-4活动的开发板是恩智浦的FRDM-MCXN236,实现USB数字麦克风,并在上位机采集音频数据并保存。开发板芯片支持PDM音频数据采集和高速USB通讯,板载有数字麦克风和高速USB接口,在以上的硬件基础上实现USB麦克风的功能。并通过上位机人机界面查看USB麦克风设备,并显示实时的音频信号和音频的录制。系统的运行框图如下:

1、MCUXpresso SDK配置工具
FRDM-MCXN236开发板配套的开发工具是MCUXpresso SDK,集成包括编译器、构建系统、开发环境等开发所需要的工具。使用NXP提供的MCUXpresso Installer对所需的工具安装进行管理。


使用VSCode+MCUXpresso IDE For VSCode插件搭建开发环境,通过MCUXpresso IDE For VSCode的Import Repository导入FRDM-MCXN236的SDK。

使用NXP提供的工具,很快完成开发环境的搭建。
2、工程管理和串口输出
根据使用的SDK类型、开发板型号创建FRDM-MCXN236的工程。

在创建的工程目录使用VSCode打开工程,在左侧的侧边栏中选择MCUXpresso插件图标,切换到插件的管理界面,对工程进行管理。关于插件提供的功能使用,可以在官网的介绍页面查看。
https://docs.mcuxpresso.nxp.com/mcuxsdk/latest/html/gsd/vscode.html
工程的源代码管理使用CMake工具,可以方便地管理工程中的代码,相关的使用说明可以参考官网的使用示例。
https://docs.mcuxpresso.nxp.com/mcuxsdk/latest/html/gsd/cmake_project_walkthrough.html
NXP提供MCUXpresso Config Tools用于配置MCU的引脚、时钟等信息。通过工程管理的右键菜单可以打开相应的配置工具,设置MCU的引脚和时钟。

查看原理图可知,MCU的调试串口的引脚连接到P1_8(RX)和P1_9(TX)。

MCUXpresso Config Tools中勾选使能相应的引脚

通过右上角的图标切换到时钟配置界面,MCUXpresso Config Tools可以配置多个时钟树配置,并选择一个作为默认的时钟配置。

设置系统时钟生成器(System Clock Generator,SCG)的模式、输入时钟、分频和倍频系数,使系统时钟达到MCU的最大主频。

同样地,设置FLEXCOMM4的时钟用于为FLEXCOMM4外设提供时钟。

修改完成后,保存并更新代码。

工程中frdmmcxn236\frdmmcxn236\board.h文件包含板载的外设宏定义,用于串口输出相关的配置。
/*! @brief The UART to use for debug messages. */
#define BOARD_DEBUG_UART_TYPE kSerialPort_Uart
#define BOARD_DEBUG_UART_BASEADDR (uint32_t) LPUART4
#define BOARD_DEBUG_UART_INSTANCE 4U
#define BOARD_DEBUG_UART_CLK_FREQ 12000000U
#define BOARD_DEBUG_UART_CLK_ATTACH kFRO12M_to_FLEXCOMM4
#define BOARD_DEBUG_UART_RST kFC4_RST_SHIFT_RSTn
#define BOARD_DEBUG_UART_CLKSRC kCLOCK_FlexComm4
#define BOARD_UART_IRQ_HANDLER LP_FLEXCOMM4_IRQHandler
#define BOARD_UART_IRQ LP_FLEXCOMM4_IRQn
#ifndef BOARD_DEBUG_UART_BAUDRATE
#define BOARD_DEBUG_UART_BAUDRATE 115200U
#endif
MCUXpresso SDK的组件通过KConfig工具进行管理,关于KConfig工具的具体信息,可以在NXP的官网找到相应的说明。
https://mcuxpresso.nxp.com/mcuxsdk/latest/html/develop/build_system/Configuration_System.html
工程右键菜单打开KConfig GUI配置工具。

KConfig GUI工具用于查看和修改工程的配置,默认开启debug_console_lite组件用于串口信息输出。

工程的全部配置项也可以在debug/.config文件中查看。
工程中frdmmcxn236\frdmmcxn236\board.c文件中,定义串口时钟初始化以及debug_console lite组件的初始化代码。
/* Initialize debug console. */
void BOARD_InitDebugConsole(void)
{
/* attach 12 MHz clock to FLEXCOMM4 (debug console) */
CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1u);
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
RESET_ClearPeripheralReset(BOARD_DEBUG_UART_RST);
uint32_t uartClkSrcFreq = BOARD_DEBUG_UART_CLK_FREQ;
DbgConsole_Init(BOARD_DEBUG_UART_INSTANCE, BOARD_DEBUG_UART_BAUDRATE, BOARD_DEBUG_UART_TYPE, uartClkSrcFreq);
}
代码中调用引脚、时钟、DebugConsole组件的初始化,并调用字符串输出函数。
void BOARD_InitHardware(void)
{
BOARD_InitPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
}
int main(void)
{
/* Board pin init */
BOARD_InitHardware();
DbgConsole_Printf("Funpack5_4 USB Device App\r\n");
while (1)
{
}
}
调试并下载代码,串口终端接收到开发板发出的字符串。


3、PDM数据采集
板载有数字麦克风,使用PDM外设用于采集音频信号,硬件连接的原理图如下。

使用MCUXpresso Config Tools配置相应的PDM外设引脚。

PDM采集音频信号的采样频率和时钟源的频率相关,PDM外设的时钟源设置如下,配置PLL1使用外部高速晶振作为时钟,PLL1的CLK0输出作为PDM的时钟源,时钟频率为12.288MHz。
CLOCK_SetupExtClocking(24000000U);
/* Set up PLL1 */
const pll_setup_t pll1Setup = {.pllctrl = SCG_SPLLCTRL_SOURCE(0U) | SCG_SPLLCTRL_SELI(15U) | SCG_SPLLCTRL_SELP(31U),
.pllndiv = SCG_SPLLNDIV_NDIV(25U),
.pllpdiv = SCG_SPLLPDIV_PDIV(2U),
.pllmdiv = SCG_SPLLMDIV_MDIV(512U),
.pllRate = 122880000U};
/* Configure PLL1 to the 122.88MHz */
CLOCK_SetPLL1Freq(&pll1Setup);
/* Set PLL1CLK0DIV divider to value 10, PLL1_CLK0 = 12.288MHz */
CLOCK_SetClkDiv(kCLOCK_DivPLL1Clk0, 10U);
/* attach PLL1_CLK0 to PDM, PDM_CLK = 12.288MHz */
CLOCK_SetClkDiv(kCLOCK_DivMicfilFClk, 1U);
CLOCK_AttachClk(kPLL1_CLK0_to_MICFILF);
参考MCXN23x的参考手册的Chapter 58 PDM Microphone Interface (MICFIL)对PDM(pluse density modulated)的外设的功能说明,PDM模块实现将麦克风生成的PDM比特流转换为24位的PCM(pluse code modulation)信号,音频流数据的采集采用EDMA+PDM模块的方式进行采集,PDM模块设置为单声道、16kHz的采样设置,PDM的采样位数固定为24bit,相关的外设和控制的宏定义如下。
#define DEVICE_PDM PDM
#define DEVICE_PDM_CLK_FREQ CLOCK_GetMicfilClkFreq()
#define DEVICE_PDM_FIFO_WATERMARK (FSL_FEATURE_PDM_FIFO_DEPTH / 2)
#define DEVICE_PDM_QUALITY_MODE kPDM_QualityModeHigh
#define DEVICE_PDM_CIC_OVERSAMPLE_RATE (0U)
#define DEVICE_PDM_ENABLE_CHANNEL (DEVICE_PDM_ENABLE_CHANNEL_RIGHT)
#define DEVICE_PDM_ENABLE_CHANNEL_LEFT (0U)
#define DEVICE_PDM_ENABLE_CHANNEL_RIGHT (1U)
#define DEVICE_PDM_CHANNEL_GAIN kPDM_DfOutputGain4
#define DEVICE_PDM_DMA DMA0
#define DEVICE_PDM_EDMA_CHANNEL 0
#define DEVICE_PDM_EDMA_SOURCE kDma0RequestMuxMicfil0FifoRequest
#define DEVICE_AUDIO_SAMPLE_RATE 16000
根据上述的参数,初始化EDMA控制的PDM数据采集,设置EDMA的采集通道和PDM模块的数据采集请求,由于音频流是连续的数据,EDMA的传输缓存区采用两块缓存区构成一个ringbuffer的环形数据缓存区用于接收PCM数据。启动PDM数据的采集。
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) uint8_t s_wavBuff[AUDIO_ENDPOINT_MAX_PACKET_SIZE];
static pdm_edma_transfer_t pdmXfer[2] = {
{
.data = s_buffer,
.dataSize = BUFFER_SIZE,
.linkTransfer = &pdmXfer[1],
},
{
.data = &s_buffer[BUFFER_SIZE],
.dataSize = BUFFER_SIZE,
.linkTransfer = &pdmXfer[0]
}
};
static const pdm_config_t pdmConfig = {
#if defined(FSL_FEATURE_PDM_HAS_DECIMATION_FILTER_BYPASS) && FSL_FEATURE_PDM_HAS_DECIMATION_FILTER_BYPASS
.enableFilterBypass = false,
#endif /* FSL_FEATURE_PDM_HAS_DECIMATION_FILTER_BYPASS */
.enableDoze = false,
.fifoWatermark = DEVICE_PDM_FIFO_WATERMARK,
.qualityMode = DEVICE_PDM_QUALITY_MODE,
.cicOverSampleRate = DEVICE_PDM_CIC_OVERSAMPLE_RATE,
};
static const pdm_channel_config_t channelConfig = {
#if (defined(FSL_FEATURE_PDM_HAS_DC_OUT_CTRL) && (FSL_FEATURE_PDM_HAS_DC_OUT_CTRL))
.outputCutOffFreq = kPDM_DcRemoverCutOff40Hz,
#else
.cutOffFreq = kPDM_DcRemoverCutOff152Hz,
#endif /* FSL_FEATURE_PDM_HAS_DC_OUT_CTRL */
#ifdef DEVICE_PDM_CHANNEL_GAIN
.gain = DEVICE_PDM_CHANNEL_GAIN,
#else
.gain = kPDM_DfOutputGain7,
#endif /* DEVICE_PDM_CHANNEL_GAIN */
};
void pdmEdmallback(PDM_Type *base, pdm_edma_handle_t *handle, status_t status, void *userData)
{
if (first_int == 0U)
{
audioPosition = 0U;
first_int = 1U;
}
}
void Board_PDM_EDMA_Init(void)
{
edma_config_t dmaConfig = {0};
/* Create EDMA handle */
/*
* dmaConfig.enableRoundRobinArbitration = false;
* dmaConfig.enableHaltOnError = true;
* dmaConfig.enableContinuousLinkMode = false;
* dmaConfig.enableDebugMode = false;
*/
EDMA_GetDefaultConfig(&dmaConfig);
EDMA_Init(DEVICE_PDM_DMA, &dmaConfig);
EDMA_CreateHandle(&s_pdmDmaHandle, DEVICE_PDM_DMA, DEVICE_PDM_EDMA_CHANNEL);
#if defined(FSL_FEATURE_EDMA_HAS_CHANNEL_MUX) && FSL_FEATURE_EDMA_HAS_CHANNEL_MUX
EDMA_SetChannelMux(DEVICE_PDM_DMA, DEVICE_PDM_EDMA_CHANNEL, DEVICE_PDM_EDMA_SOURCE);
#endif /* FSL_FEATURE_EDMA_HAS_CHANNEL_MUX */
/* Set up pdm */
PDM_Init(DEVICE_PDM, &pdmConfig);
PDM_TransferCreateHandleEDMA(DEVICE_PDM, &s_pdmRxHandle, pdmEdmallback, NULL, &s_pdmDmaHandle);
PDM_TransferInstallEDMATCDMemory(&s_pdmRxHandle, s_edmaTcd, 2);
#if defined DEVICE_PDM_ENABLE_CHANNEL
PDM_TransferSetChannelConfigEDMA(DEVICE_PDM, &s_pdmRxHandle, DEVICE_PDM_ENABLE_CHANNEL, &channelConfig);
#else
PDM_TransferSetChannelConfigEDMA(DEVICE_PDM, &s_pdmRxHandle, DEVICE_PDM_ENABLE_CHANNEL_LEFT, &channelConfig);
#endif
PDM_SetSampleRateConfig(DEVICE_PDM, DEVICE_PDM_CLK_FREQ, DEVICE_AUDIO_SAMPLE_RATE);
PDM_Reset(DEVICE_PDM);
PDM_TransferReceiveEDMA(DEVICE_PDM, &s_pdmRxHandle, pdmXfer);
}
定义获取EDMA+PDM模块采集到的PCM数据的函数如下,用于后续USB Auido实现中用于读取PDM数据。
void USB_AudioRecorderGetBuffer(uint8_t *buffer, uint32_t size)
{
uint8_t k;
/* copy audio wav data from flash to buffer */
for (k = 0U; k < size; audioPosition += 4U)
{
if (audioPosition > (BUFFER_SIZE * BUFFER_NUM - 1U))
{
audioPosition = 0U;
}
*(buffer + k++) = s_buffer[audioPosition + 1U];
*(buffer + k++) = s_buffer[audioPosition + 2U];
*(buffer + k++) = s_buffer[audioPosition + 3U];
}
}
4、USB Audio
FRDM-MCXN236带有高速USB接口,USB接口原理图如下:


MCUXpresso SDK中集成有USB Middleware组件,mcuxsdk中的KConfig文件的组成结构如下,通过修改工程中的frdmmcxn236\prj.conf和prj.conf两个配置文件,可以永久修改工程的SDK配置,为工程添加RTOS和USB组件支持。

frdmmcxn236\prj.conf中的定义如下
CONFIG_MCUX_PRJSEG_module.board.suite=y
CONFIG_MCUX_PRJSEG_module.board.use_board_clock=y
CONFIG_MCUX_PRJSEG_module.board.pinmux_customize_folder=y
CONFIG_MCUX_PRJSEG_project.hw_app_customize_folder=y
CONFIG_MCUX_COMPONENT_driver.clock=y
CONFIG_MCUX_COMPONENT_driver.edma_soc=y
CONFIG_MCUX_COMPONENT_driver.inputmux_connections=y
CONFIG_MCUX_COMPONENT_driver.reset=y
CONFIG_MCUX_COMPONENT_device.CMSIS=y
CONFIG_MCUX_COMPONENT_device.system=y
CONFIG_MCUX_COMPONENT_device.startup=y
CONFIG_MCUX_COMPONENT_driver.common=y
CONFIG_MCUX_COMPONENT_driver.edma4=y
CONFIG_MCUX_COMPONENT_driver.gpio=y
CONFIG_MCUX_COMPONENT_driver.lpflexcomm=y
CONFIG_MCUX_COMPONENT_driver.lpflexcomm_lpuart=y
CONFIG_MCUX_COMPONENT_driver.mcx_spc=y
CONFIG_MCUX_COMPONENT_driver.pdm_edma=y
CONFIG_MCUX_COMPONENT_driver.pdm=y
CONFIG_MCUX_COMPONENT_driver.port=y
CONFIG_MCUX_COMPONENT_utility.assert_lite=y
CONFIG_MCUX_COMPONENT_utilities.misc_utilities=y
CONFIG_MCUX_COMPONENT_component.lists=y
CONFIG_MCUX_COMPONENT_utility.str=y
CONFIG_MCUX_COMPONENT_utility.debug_console_lite=y
CONFIG_MCUX_COMPONENT_component.lpuart_adapter=y
CONFIG_MCUX_COMPONENT_component.osa_template_config=y
CONFIG_MCUX_COMPONENT_component.osa=y
CONFIG_MCUX_COMPONENT_component.osa_free_rtos=y
CONFIG_MCUX_PRJSEG_component.osa_interface.generated_config=y
CONFIG_MCUX_PRJSEG_component.osa_interface.osa_macro_used=y
CONFIG_MCUX_COMPONENT_middleware.usb.common_header=y
CONFIG_MCUX_COMPONENT_middleware.usb.device.common_header=y
CONFIG_MCUX_COMPONENT_middleware.usb.device.ehci=y
CONFIG_MCUX_COMPONENT_middleware.usb.device.audio=y
CONFIG_MCUX_COMPONENT_middleware.freertos-kernel=y
CONFIG_MCUX_COMPONENT_middleware.freertos-kernel.heap_4=y
CONFIG_MCUX_PRJSEG_middleware.freertos-kernel.generated_config=y
CONFIG_MCUX_COMPONENT_middleware.freertos-kernel.config=y
CONFIG_MCUX_COMPONENT_middleware.freertos-kernel.cm33_non_trustzone=y
CONFIG_MCUX_COMPONENT_middleware.freertos-kernel.extension=y
CONFIG_MCUX_PRJSEG_module.board.boardfile=n
CONFIG_MCUX_PRJSEG_module.board.clock=n
CONFIG_MCUX_PRJSEG_module.board.clock_customize_folder=y
CONFIG_MCUX_PRJSEG_module.board.console_lite=n
prj.conf中的定义如下
CONFIG_FSL_OSA_TASK_ENABLE=n
CONFIG_MCUX_COMPONENT_middleware.freertos-kernel=y
CONFIG_MCUX_COMPONENT_middleware.freertos-kernel.heap_4=y
CONFIG_MCUX_COMPONENT_middleware.usb.common_header=y
CONFIG_MCUX_COMPONENT_middleware.usb.device.common_header=y
CONFIG_MCUX_PRJSEG_component.osa_interface.generated_config=y
CONFIG_MCUX_PRJSEG_middleware.freertos-kernel.generated_config=y
CONFIG_MCUX_PRJSEG_module.board.pinmux_customize_folder=y
CONFIG_MCUX_PRJSEG_project.hw_app_customize_folder=y
CONFIG_USB_DEVICE_CONFIG_AUDIO=1
CONFIG_USB_DEVICE_CONFIG_ROOT2_TEST=y
CONFIG_configENABLE_MPU=n
CONFIG_configENABLE_TRUSTZONE=n
CONFIG_configMAX_PRIORITIES=18
CONFIG_configTICK_RATE_HZ=1000
CONFIG_configTIMER_QUEUE_LENGTH=10
CONFIG_configTOTAL_HEAP_SIZE=32768
CONFIG_configUSE_TIME_SLICING=y
使用GUIKconfig工具或者查看debug\.config文件,确认构建的目标中配置项。


在工程引入相应组件的头文件,即可组件提供的功能。
4.1 USB设备描述符
USB设备的功能使用USB描述符进行定义。USB Audio 设备的规范说明书在USB-IF联盟的官网下载。音频设备的USB设备描述参照以下三个标准文档。
- Universal Serial Bus Specification Revision 2.0
- USB ECN : Interface Association Descriptor
- USB Device Class Definition for Audio Devices
实现USB麦克风功能,首先需要定义USB Audio Device的描述符,定义音频设备的控制接口和音频流接口的信息。USB麦克风设备描述的框架如下。

Device Descriptor用于定义设备的类型、设备配置描述符的个数等信息,相关的定义如下。
uint8_t g_UsbDeviceDescriptor[] = {
USB_DESCRIPTOR_LENGTH_DEVICE, /* Size of this descriptor in bytes */
USB_DESCRIPTOR_TYPE_DEVICE, /* DEVICE Descriptor Type */
USB_SHORT_GET_LOW(USB_DEVICE_SPECIFIC_BCD_VERSION),
USB_SHORT_GET_HIGH(USB_DEVICE_SPECIFIC_BCD_VERSION), /* USB Specification Release Number in
Binary-Coded Decimal (i.e., 2.10 is 210H). */
USB_DEVICE_CLASS, /* Class code (assigned by the USB-IF). */
USB_DEVICE_SUBCLASS, /* Subclass code (assigned by the USB-IF). */
USB_DEVICE_PROTOCOL, /* Protocol code (assigned by the USB-IF). */
USB_CONTROL_MAX_PACKET_SIZE, /* Maximum packet size for endpoint zero
(only 8, 16, 32, or 64 are valid) */
USB_SHORT_GET_LOW(USB_DEVICE_VID),
USB_SHORT_GET_HIGH(USB_DEVICE_VID), /* Vendor ID (assigned by the USB-IF) */
USB_SHORT_GET_LOW(USB_DEVICE_PID),
USB_SHORT_GET_HIGH(USB_DEVICE_PID), /* Product ID (assigned by the manufacturer) */
USB_SHORT_GET_LOW(USB_DEVICE_DEMO_BCD_VERSION),
USB_SHORT_GET_HIGH(USB_DEVICE_DEMO_BCD_VERSION), /* Device release number in binary-coded decimal */
0x01U, /* Index of string descriptor describing manufacturer */
0x02U, /* Index of string descriptor describing product */
0x00U, /* Index of string descriptor describing the
device's serial number */
USB_DEVICE_CONFIGURATION_COUNT, /* Number of possible configurations */
};
Configurator Descriptor用于描述USB设备的Interace数量,麦克风设备中包含用于音频控制的Audio Control Interface和音频流数据流传输的Audio Stream Interface。
uint8_t g_UsbDeviceConfigurationDescriptor[] = {
#if (USB_DEVICE_CONFIG_AUDIO_CLASS_2_0)
USB_DESCRIPTOR_LENGTH_CONFIGURE, /* Size of this descriptor in bytes */
USB_DESCRIPTOR_TYPE_CONFIGURE, /* CONFIGURATION Descriptor Type */
USB_SHORT_GET_LOW(USB_DESCRIPTOR_LENGTH_CONFIGURE + 0x08U + USB_DESCRIPTOR_LENGTH_INTERFACE +
USB_AUDIO_CONTROL_INTERFACE_HEADER_LENGTH + 0x08U + 0x11U + 0x0EU + 0x0CU +
USB_DESCRIPTOR_LENGTH_INTERFACE + USB_DESCRIPTOR_LENGTH_INTERFACE + 0x10U + 0x06U +
USB_AUDIO_STANDARD_AS_ISO_DATA_ENDPOINT_LENGTH + USB_AUDIO_CLASS_SPECIFIC_ENDPOINT_LENGTH),
USB_SHORT_GET_HIGH(
USB_DESCRIPTOR_LENGTH_CONFIGURE + 0x08U + USB_DESCRIPTOR_LENGTH_INTERFACE +
USB_AUDIO_CONTROL_INTERFACE_HEADER_LENGTH + 0x08U + 0x11U + 0x0EU + 0x0CU + USB_DESCRIPTOR_LENGTH_INTERFACE +
USB_DESCRIPTOR_LENGTH_INTERFACE + 0x10U + 0x06U + USB_AUDIO_STANDARD_AS_ISO_DATA_ENDPOINT_LENGTH +
USB_AUDIO_CLASS_SPECIFIC_ENDPOINT_LENGTH), /* Total length of data returned for this configuration. */
USB_AUDIO_GENERATOR_INTERFACE_COUNT, /* Number of interfaces supported by this configuration */
USB_AUDIO_GENERATOR_CONFIGURE_INDEX, /* Value to use as an argument to the
SetConfiguration() request to select this configuration */
0x00U, /* Index of string descriptor describing this configuration */
(USB_DESCRIPTOR_CONFIGURE_ATTRIBUTE_D7_MASK) |
#if defined(USB_DEVICE_CONFIG_SELF_POWER) && (USB_DEVICE_CONFIG_SELF_POWER > 0U)
(1U << USB_DESCRIPTOR_CONFIGURE_ATTRIBUTE_SELF_POWERED_SHIFT) |
#endif
#if defined(USB_DEVICE_CONFIG_REMOTE_WAKEUP) && (USB_DEVICE_CONFIG_REMOTE_WAKEUP > 0U)
(1U << USB_DESCRIPTOR_CONFIGURE_ATTRIBUTE_REMOTE_WAKEUP_SHIFT) |
#endif
0U,
/* Configuration characteristics
D7: Reserved (set to one)
D6: Self-powered
D5: Remote Wakeup
D4...0: Reserved (reset to zero)
*/
0xFAU, /* Maximum power consumption of the USB
* device from the bus in this specific
* configuration when the device is fully
* operational. Expressed in 2 mA units
* (i.e., 50 = 100 mA).
*/
}
USB Audio规格书中指出使用IAD(Interface Associate Descriptor)将Audio Control Interface和Audio Stream Interface集合为一个设备功能。相关的代码如下:
0x08U, /* Descriptor size is 8 bytes */
USB_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, /* INTERFACE_ASSOCIATION Descriptor Type */
0x00U, /* The first interface number associated with this function is 0 */
0x02U, /* The number of contiguous interfaces associated with this function is 2 */
USB_AUDIO_CLASS, /* The function belongs to the Audio Interface Class */
0x00U, /* The function belongs to the SUBCLASS_UNDEFINED Subclass */
USB_AUDIO_PROTOCOL, /* Protocol code = 32 */
0x00U, /* The Function string descriptor index is 0 */
Audio Control Interface的描述符定义Interface的索引,协议类型等信息,相关的代码如下。
USB_DESCRIPTOR_LENGTH_INTERFACE, /* Size of the descriptor, in bytes */
USB_DESCRIPTOR_TYPE_INTERFACE, /* INTERFACE Descriptor Type */
USB_AUDIO_CONTROL_INTERFACE_INDEX, /* The number of this interface is 0 */
USB_AUDIO_GENERATOR_CONTROL_INTERFACE_ALTERNATE_0, /* The value used to select the alternate setting for this interface is 0 */
0x00U, /* The number of endpoints used by this interface is 0 (excluding endpoint zero) */
USB_AUDIO_CLASS, /* The interface implements the Audio Interface class */
USB_SUBCLASS_AUDIOCONTROL, /* The interface implements the AUDIOCONTROL Subclass */
USB_AUDIO_PROTOCOL, /* The Protocol code is 32 */
0x02U, /* The interface string descriptor index is 2 */
USB Audio Class的规格书中定义Class Specific Interface用于描述Interface的具体功能,比如音频设备类型(麦克风、扬声器)、音频的音频的采样时钟源、Input Terminal设置、音频设备特性、Output Terminal设置等,用于定义USB音频的参数以及音频链路。
USB_AUDIO_CONTROL_INTERFACE_HEADER_LENGTH, /* Size of the descriptor, in bytes */
USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE, /* CS_INTERFACE Descriptor Type */
USB_DESCRIPTOR_SUBTYPE_AUDIO_CONTROL_HEADER, /* HEADER descriptor subtype */
0x00U,
0x02U, /* Audio Device compliant to the USB Audio specification version 2.00 */
0x03U, /* MICROPHONE(0x03) : Indicating the primary use of this audio function */
0x3CU,
0x00U, /* Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes
the combined length of this descriptor header and all Unit and Terminal descriptors. */
0x00U, /* D1..0: Latency Control */
0x08U, /* Size of the descriptor, in bytes */
USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE, /* CS_INTERFACE Descriptor Type */
USB_DESCRIPTOR_SUBTYPE_AUDIO_CONTROL_CLOCK_SOURCE_UNIT, /* CLOCK_SOURCE descriptor subtype */
USB_AUDIO_RECORDER_CONTROL_CLOCK_SOURCE_ENTITY_ID, /* Constant uniquely identifying the Clock Source Entity within
the audio funcion */
0x01U, /* D1..0: 01: Internal Fixed Clock
D2: 0 Clock is not synchronized to SOF
D7..3: Reserved, should set to 0 */
0x07U, /* D1..0: Clock Frequency Control is present and Host programmable
D3..2: Clock Validity Control is present but read-only
D7..4: Reserved, should set to 0 */
0x00U, /* This Clock Source has no association */
0x02U, /* Index of a string descriptor, describing the Clock Source Entity */
0x11U, /* Size of the descriptor, in bytes */
USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE, /* CS_INTERFACE Descriptor Type */
USB_DESCRIPTOR_SUBTYPE_AUDIO_CONTROL_INPUT_TERMINAL, /* INPUT_TERMINAL descriptor subtype */
USB_AUDIO_RECORDER_CONTROL_INPUT_TERMINAL_ID, /* Constant uniquely identifying the Terminal within the audio
function. This value is used in all requests to address this Terminal. */
0x01U,
0x02U, /* A generic microphone that does not fit under any of the other classifications. */
0x00U, /* This Input Terminal has no association */
USB_AUDIO_RECORDER_CONTROL_CLOCK_SOURCE_ENTITY_ID, /* ID of the Clock Entity to which this Input Terminal is
connected. */
0x01U, /* This Terminal's output audio channel cluster has 1 logical output channels */
0x01U,
0x00U,
0x00U,
0x00U, /* Describes the spatial location of the logical channels:: Mono, no spatial location */
0x00U, /* Index of a string descriptor, describing the name of the first logical channel. */
0x00U,
0x00U, /* bmControls D1..0: Copy Protect Control is not present
D3..2: Connector Control is not present
D5..4: Overload Control is not present
D7..6: Cluster Control is not present
D9..8: Underflow Control is not present
D11..10: Overflow Control is not present
D15..12: Reserved, should set to 0*/
0x02U, /* Index of a string descriptor, describing the Input Terminal. */
0x0EU, /* Size of the descriptor, in bytes : 6 + (1 + 1) * 4 */
USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE, /* CS_INTERFACE Descriptor Type */
USB_DESCRIPTOR_SUBTYPE_AUDIO_CONTROL_FEATURE_UNIT, /* FEATURE_UNIT descriptor subtype */
USB_AUDIO_RECORDER_CONTROL_FEATURE_UNIT_ID, /* Constant uniquely identifying the Unit within the audio function.
This value is used in all requests to address this Unit. */
USB_AUDIO_RECORDER_CONTROL_INPUT_TERMINAL_ID, /* ID of the Unit or Terminal to which this Feature Unit is connected.
*/
0x0FU,
0x00U,
0x00U,
0x00U, /* logic channel 0 bmaControls(0)(0x0000000F): D1..0: Mute Control is present and host
programmable D3..2: Volume Control is present and host programmable D5..4: Bass
Control is not present D7..6: Mid Control is not present D9..8: Treble Control is not
present D11..10: Graphic Equalizer Control is not present D13..12: Automatic Gain
Control is not present D15..14: Delay Control is not present D17..16: Bass Control is
not present D19..18: Loudness Control is not present D21..20: Input Gain Control is
not present D23..22: Input Gain Pad Control is not present D25..24: Phase Inverter
Control is not present D27..26: Underflow Control is not present D29..28: Overflow
Control is not present D31..30: Reserved, should set to 0 */
0x00U,
0x00U,
0x00U,
0x00U,
0x00U, /* Index of a string descriptor, describing this Feature Unit. */
0x0CU, /* Size of the descriptor, in bytes */
USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE, /* CS_INTERFACE Descriptor Type */
USB_DESCRIPTOR_SUBTYPE_AUDIO_CONTROL_OUTPUT_TERMINAL, /* OUTPUT_TERMINAL descriptor subtype */
USB_AUDIO_RECORDER_CONTROL_OUTPUT_TERMINAL_ID, /* Constant uniquely identifying the Terminal within the audio
function. This value is used in all requests to address this Terminal. */
0x01U,
0x01U, /* A Terminal dealing with a signal carried over an endpoint in an AudioStreaming interface. The
AudioStreaming interface descriptor points to the associated Terminal through the bTerminalLink field. */
0x00U, /* This Output Terminal has no association */
USB_AUDIO_RECORDER_CONTROL_FEATURE_UNIT_ID, /* ID of the Unit or Terminal to which this Terminal is connected. */
USB_AUDIO_RECORDER_CONTROL_CLOCK_SOURCE_ENTITY_ID, /* ID of the Clock Entity to which this Output Terminal is
connected */
0x00U,
0x00U, /* bmControls: D1..0: Copy Protect Control is not present
D3..2: Connector Control is not present
D5..4: Overload Control is not present
D7..6: Underflow Control is not present
D9..8: Overflow Control is not present
D15..10: Reserved, should set to 0 */
0x00U, /* Index of a string descriptor, describing the Output Terminal. */
Audio Stream Interface用于定义USB Audio设备的音频流数据的发送,通过Alternate选项,可以为该接口定义两种不同的配置,用于快速切换。
/* Audio Class Specific INTERFACE Descriptor, alternative interface 0 */
USB_DESCRIPTOR_LENGTH_INTERFACE, /* Descriptor size is 9 bytes */
USB_DESCRIPTOR_TYPE_INTERFACE, /* INTERFACE Descriptor Type */
USB_AUDIO_STREAM_INTERFACE_INDEX, /* The number of this interface is 1. */
USB_AUDIO_GENERATOR_STREAM_INTERFACE_ALTERNATE_0, /* The value used to select the alternate setting for this interface is 0 */
0x00U, /* The number of endpoints used by this interface is 0 (excluding endpoint zero) */
USB_AUDIO_CLASS, /* The interface implements the Audio Interface class */
USB_SUBCLASS_AUDIOSTREAM, /* The interface implements the AUDIOSTREAMING Subclass */
USB_AUDIO_PROTOCOL, /* The Protocol code is 32 */
0x02U, /* The device doesn't have a string descriptor describing this iInterface */
/* Audio Class Specific INTERFACE Descriptor, alternative interface 1 */
USB_DESCRIPTOR_LENGTH_INTERFACE, /* Descriptor size is 9 bytes */
USB_DESCRIPTOR_TYPE_INTERFACE, /* INTERFACE Descriptor Type */
USB_AUDIO_STREAM_INTERFACE_INDEX, /*The number of this interface is 1. */
USB_AUDIO_GENERATOR_STREAM_INTERFACE_ALTERNATE_1, /* The value used to select the alternate setting for this interface is 1 */
0x01U, /* The number of endpoints used by this interface is 1 (excluding endpoint zero) */
USB_AUDIO_CLASS, /* The interface implements the Audio Interface class */
USB_SUBCLASS_AUDIOSTREAM, /* The interface implements the AUDIOSTREAMING Subclass */
USB_AUDIO_PROTOCOL, /* The Protocol code is 32 */
0x02U, /* The device doesn't have a string descriptor describing this iInterface */
USB Audio中为Audio Stream Interface定义的Class Sepecific Interface,用于和Audio Control Interface中的Output Terminal定义相关联,构成音频链路。并根据具体的物理音频外设指定音频数据的格式的描述符索引。
0x10U, /* Size of the descriptor, in bytes */
USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE, /* CS_INTERFACE Descriptor Type */
USB_DESCRIPTOR_SUBTYPE_AUDIO_STREAMING_AS_GENERAL, /* AS_GENERAL descriptor subtype */
USB_AUDIO_RECORDER_CONTROL_OUTPUT_TERMINAL_ID, /* The Terminal ID of the terminal to which this interface is
connected */
0x00U, /* bmControls : D1..0: Active Alternate Setting Control is not present
D3..2: Valid Alternate Settings Control is not present
D7..4: Reserved, should set to 0 */
USB_AUDIO_FORMAT_TYPE_I, /* The format type AudioStreaming interfae using is FORMAT_TYPE_I (0x01) */
0x01U,
0x00U,
0x00U,
0x00U, /* The Audio Data Format that can be Used to communicate with this interface */
AUDIO_FORMAT_CHANNELS, /* Number of physical channels in the AS Interface audio channel cluster */
0x01U,
0x00U,
0x00U,
0x00U, /* Describes the spatial location of the logical channels: */
0x00U, /* Index of a string descriptor, describing the name of the first physical channel */
音频具体的采样率和采样位数,通过Audio Streaming Format Type的Class Specific接口定义。
0x06U, /* Size of the descriptor, in bytes */
USB_DESCRIPTOR_TYPE_AUDIO_CS_INTERFACE, /* CS_INTERFACE Descriptor Type */
USB_DESCRIPTOR_SUBTYPE_AUDIO_STREAMING_FORMAT_TYPE, /* FORMAT_TYPE descriptor subtype */
USB_AUDIO_FORMAT_TYPE_I, /* The format type AudioStreaming interfae using is FORMAT_TYPE_I (0x01) */
#if defined(AUDIO_DATA_SOURCE_DMIC) && (AUDIO_DATA_SOURCE_DMIC > 0U)
0x02U, /* The number of bytes occupied by one audio subslot. Can be 1, 2, 3 or 4. */
0x10U, /* The number of effectively used bits from the available bits in an audio subslot */
#elif defined(AUDIO_DATA_SOURCE_PDM) && (AUDIO_DATA_SOURCE_PDM > 0U)
0x03U, /* The number of bytes occupied by one audio subslot. Can be 1, 2, 3 or 4. */
0x18U, /* The number of effectively used bits from the available bits in an audio subslot */
#else
0x01U, /* The number of bytes occupied by one audio subslot. Can be 1, 2, 3 or 4. */
0x08U, /* The number of effectively used bits from the available bits in an audio subslot */
#endif
Endpoint Interface是描述硬件中使用的USB外设的通道的相关信息。
/* ENDPOINT Descriptor */
USB_AUDIO_STANDARD_AS_ISO_DATA_ENDPOINT_LENGTH, /* Descriptor size is 7 bytes */
USB_DESCRIPTOR_TYPE_ENDPOINT, /* ENDPOINT Descriptor Type */
USB_AUDIO_STREAM_ENDPOINT | (USB_IN << 7), /* This is an IN endpoint with endpoint number 2 */
0x0DU, /* Types -
Transfer: ISOCHRONOUS
Sync: Sync
Usage: Data EP */
USB_SHORT_GET_LOW(FS_ISO_IN_ENDP_PACKET_SIZE),
USB_SHORT_GET_HIGH(FS_ISO_IN_ENDP_PACKET_SIZE), /* Maximum packet size for this endpoint */
ISO_IN_ENDP_INTERVAL, /* The polling interval value is every 1 Frames. If Hi-Speed, every 1 uFrames */
/* Audio Class Specific ENDPOINT Descriptor */
USB_AUDIO_CLASS_SPECIFIC_ENDPOINT_LENGTH, /* Size of the descriptor, in bytes */
USB_AUDIO_STREAM_ENDPOINT_DESCRIPTOR, /* CS_ENDPOINT Descriptor Type */
USB_AUDIO_EP_GENERAL_DESCRIPTOR_SUBTYPE, /* AUDIO_EP_GENERAL descriptor subtype */
0x00U,
0x00U,
0x00U,
0x00U,
0x00U,
USB描述符通过拓扑结构的方式描述设备的特性和功能,通过USB描述符的配置为USB主机控制该设备提供信息。
4.2 USB协议栈和相关的回调函数
NXP提供的SDK中包括USB协议栈中间件,通过定义USB的设备事件和音频事件的回调函数处理相关的USB数据请求,调用USB协议栈提供的注册函数注册定义的回调函数,即可实现相应的USB设备功能。
USB的设备事件回调函数,用于处理接收的主机请求,返回设备描述等信息,根据接收到的USB通讯事件,执行相应的功能。
usb_status_t USB_DeviceCallback(usb_device_handle handle, uint32_t event, void *param)
{
usb_status_t error = kStatus_USB_InvalidRequest;
uint8_t *temp8 = (uint8_t *)param;
uint16_t *temp16 = (uint16_t *)param;
uint8_t count = 0U;
switch (event)
{
case kUSB_DeviceEventBusReset:
{
/* The device BUS reset signal detected */
for (count = 0U; count < USB_AUDIO_GENERATOR_INTERFACE_COUNT; count++)
{
s_audioGenerator.currentInterfaceAlternateSetting[count] = 0U;
}
s_audioGenerator.attach = 0U;
s_audioGenerator.currentConfiguration = 0U;
error = kStatus_USB_Success;
#if (defined(USB_DEVICE_CONFIG_LPCIP3511HS) && (USB_DEVICE_CONFIG_LPCIP3511HS > 0U))
#if !((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U))
/* The work-around is used to fix the HS device Chirping issue.
* Please refer to the implementation for the detail information.
*/
USB_DeviceHsPhyChirpIssueWorkaround();
#endif
#endif
#if (defined(USB_DEVICE_CONFIG_EHCI) && (USB_DEVICE_CONFIG_EHCI > 0U)) || \
(defined(USB_DEVICE_CONFIG_LPCIP3511HS) && (USB_DEVICE_CONFIG_LPCIP3511HS > 0U))
/* Get USB speed to configure the device, including max packet size and interval of the endpoints. */
if (kStatus_USB_Success == USB_DeviceClassGetSpeed(CONTROLLER_ID, &s_audioGenerator.speed))
{
USB_DeviceSetSpeed(handle, s_audioGenerator.speed);
}
#endif
}
break;
#if (defined(USB_DEVICE_CONFIG_DETACH_ENABLE) && (USB_DEVICE_CONFIG_DETACH_ENABLE > 0U))
case kUSB_DeviceEventDetach:
{
#if (defined(USB_DEVICE_CONFIG_LPCIP3511HS) && (USB_DEVICE_CONFIG_LPCIP3511HS > 0U))
#if !((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U))
USB_DeviceDisconnected();
#endif
#endif
error = kStatus_USB_Success;
}
break;
#endif
case kUSB_DeviceEventSetConfiguration:
if (0U == (*temp8))
{
s_audioGenerator.attach = 0U;
s_audioGenerator.currentConfiguration = 0U;
error = kStatus_USB_Success;
}
else if (USB_AUDIO_GENERATOR_CONFIGURE_INDEX == (*temp8))
{
/* Set the configuration request */
s_audioGenerator.attach = 1U;
s_audioGenerator.currentConfiguration = *temp8;
error = kStatus_USB_Success;
}
else
{
/* no action */
}
break;
case kUSB_DeviceEventSetInterface:
if (0U != s_audioGenerator.attach)
{
/* Set alternateSetting of the interface request */
uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
uint8_t alternateSetting = (uint8_t)(*temp16 & 0x00FFU);
if (USB_AUDIO_CONTROL_INTERFACE_INDEX == interface)
{
if (alternateSetting < USB_AUDIO_GENERATOR_CONTROL_INTERFACE_ALTERNATE_COUNT)
{
s_audioGenerator.currentInterfaceAlternateSetting[interface] = alternateSetting;
error = kStatus_USB_Success;
}
}
else if (USB_AUDIO_STREAM_INTERFACE_INDEX == interface)
{
if (alternateSetting < USB_AUDIO_GENERATOR_STREAM_INTERFACE_ALTERNATE_COUNT)
{
s_audioGenerator.currentInterfaceAlternateSetting[interface] = alternateSetting;
error = kStatus_USB_Success;
if (USB_AUDIO_GENERATOR_STREAM_INTERFACE_ALTERNATE_1 == alternateSetting)
{
USB_AudioRecorderGetBuffer(s_wavBuff, (USB_SPEED_HIGH == s_audioGenerator.speed) ?
HS_ISO_IN_ENDP_PACKET_SIZE :
FS_ISO_IN_ENDP_PACKET_SIZE);
error = USB_DeviceAudioSend(
s_audioGenerator.audioHandle, USB_AUDIO_STREAM_ENDPOINT, s_wavBuff,
(USB_SPEED_HIGH == s_audioGenerator.speed) ? HS_ISO_IN_ENDP_PACKET_SIZE :
FS_ISO_IN_ENDP_PACKET_SIZE);
}
}
}
else
{
/* no action */
}
}
break;
case kUSB_DeviceEventGetConfiguration:
if (NULL != param)
{
/* Get the current configuration request */
*temp8 = s_audioGenerator.currentConfiguration;
error = kStatus_USB_Success;
}
break;
case kUSB_DeviceEventGetInterface:
if (NULL != param)
{
uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
if (interface < USB_AUDIO_GENERATOR_INTERFACE_COUNT)
{
*temp16 = (*temp16 & 0xFF00U) | s_audioGenerator.currentInterfaceAlternateSetting[interface];
error = kStatus_USB_Success;
}
}
break;
case kUSB_DeviceEventGetDeviceDescriptor:
if (NULL != param)
{
/* Get the device descriptor request */
error = USB_DeviceGetDeviceDescriptor(handle, (usb_device_get_device_descriptor_struct_t *)param);
}
break;
case kUSB_DeviceEventGetConfigurationDescriptor:
if (NULL != param)
{
/* Get the configuration descriptor request */
error = USB_DeviceGetConfigurationDescriptor(handle,
(usb_device_get_configuration_descriptor_struct_t *)param);
}
break;
case kUSB_DeviceEventGetStringDescriptor:
if (NULL != param)
{
/* Get the string descriptor request */
error = USB_DeviceGetStringDescriptor(handle, (usb_device_get_string_descriptor_struct_t *)param);
}
break;
default:
/* no action */
break;
}
return error;
}
USB音频数据的上传是通过USB的音频事件回调函数实现,音频事件回调函数还包括设置音频的配置项,具体的定义如下。
usb_status_t USB_DeviceAudioRequest(class_handle_t handle, uint32_t event, void *param)
{
usb_device_control_request_struct_t *request = (usb_device_control_request_struct_t *)param;
usb_status_t error = kStatus_USB_Success;
switch (event)
{
case USB_DEVICE_AUDIO_FU_GET_CUR_MUTE_CONTROL:
#if (USB_DEVICE_CONFIG_AUDIO_CLASS_2_0)
request->buffer = (uint8_t *)&s_audioGenerator.curMute20;
request->length = sizeof(s_audioGenerator.curMute20);
#else
request->buffer = &s_audioGenerator.curMute;
request->length = sizeof(s_audioGenerator.curMute);
#endif
break;
case USB_DEVICE_AUDIO_FU_GET_CUR_VOLUME_CONTROL:
#if (USB_DEVICE_CONFIG_AUDIO_CLASS_2_0)
request->buffer = (uint8_t *)&s_audioGenerator.curVolume20;
request->length = sizeof(s_audioGenerator.curVolume20);
#else
request->buffer = s_audioGenerator.curVolume;
request->length = sizeof(s_audioGenerator.curVolume);
#endif
break;
case USB_DEVICE_AUDIO_FU_GET_CUR_BASS_CONTROL:
request->buffer = &s_audioGenerator.curBass;
request->length = sizeof(s_audioGenerator.curBass);
break;
case USB_DEVICE_AUDIO_FU_GET_CUR_MID_CONTROL:
request->buffer = &s_audioGenerator.curMid;
request->length = sizeof(s_audioGenerator.curMid);
break;
case USB_DEVICE_AUDIO_FU_GET_CUR_TREBLE_CONTROL:
request->buffer = &s_audioGenerator.curTreble;
request->length = sizeof(s_audioGenerator.curTreble);
break;
case USB_DEVICE_AUDIO_FU_GET_CUR_AUTOMATIC_GAIN_CONTROL:
request->buffer = &s_audioGenerator.curAutomaticGain;
request->length = sizeof(s_audioGenerator.curAutomaticGain);
break;
case USB_DEVICE_AUDIO_FU_GET_CUR_DELAY_CONTROL:
request->buffer = s_audioGenerator.curDelay;
request->length = sizeof(s_audioGenerator.curDelay);
break;
case USB_DEVICE_AUDIO_FU_GET_MIN_VOLUME_CONTROL:
request->buffer = s_audioGenerator.minVolume;
request->length = sizeof(s_audioGenerator.minVolume);
break;
case USB_DEVICE_AUDIO_FU_GET_MIN_BASS_CONTROL:
request->buffer = &s_audioGenerator.minBass;
request->length = sizeof(s_audioGenerator.minBass);
break;
case USB_DEVICE_AUDIO_FU_GET_MIN_MID_CONTROL:
request->buffer = &s_audioGenerator.minMid;
request->length = sizeof(s_audioGenerator.minMid);
break;
case USB_DEVICE_AUDIO_FU_GET_MIN_TREBLE_CONTROL:
request->buffer = &s_audioGenerator.minTreble;
request->length = sizeof(s_audioGenerator.minTreble);
break;
case USB_DEVICE_AUDIO_FU_GET_MIN_DELAY_CONTROL:
request->buffer = s_audioGenerator.minDelay;
request->length = sizeof(s_audioGenerator.minDelay);
break;
case USB_DEVICE_AUDIO_FU_GET_MAX_VOLUME_CONTROL:
request->buffer = s_audioGenerator.maxVolume;
request->length = sizeof(s_audioGenerator.maxVolume);
break;
case USB_DEVICE_AUDIO_FU_GET_MAX_BASS_CONTROL:
request->buffer = &s_audioGenerator.maxBass;
request->length = sizeof(s_audioGenerator.maxBass);
break;
case USB_DEVICE_AUDIO_FU_GET_MAX_MID_CONTROL:
request->buffer = &s_audioGenerator.maxMid;
request->length = sizeof(s_audioGenerator.maxMid);
break;
case USB_DEVICE_AUDIO_FU_GET_MAX_TREBLE_CONTROL:
request->buffer = &s_audioGenerator.maxTreble;
request->length = sizeof(s_audioGenerator.maxTreble);
break;
case USB_DEVICE_AUDIO_FU_GET_MAX_DELAY_CONTROL:
request->buffer = s_audioGenerator.maxDelay;
request->length = sizeof(s_audioGenerator.maxDelay);
break;
case USB_DEVICE_AUDIO_FU_GET_RES_VOLUME_CONTROL:
request->buffer = s_audioGenerator.resVolume;
request->length = sizeof(s_audioGenerator.resVolume);
break;
case USB_DEVICE_AUDIO_FU_GET_RES_BASS_CONTROL:
request->buffer = &s_audioGenerator.resBass;
request->length = sizeof(s_audioGenerator.resBass);
break;
case USB_DEVICE_AUDIO_FU_GET_RES_MID_CONTROL:
request->buffer = &s_audioGenerator.resMid;
request->length = sizeof(s_audioGenerator.resMid);
break;
case USB_DEVICE_AUDIO_FU_GET_RES_TREBLE_CONTROL:
request->buffer = &s_audioGenerator.resTreble;
request->length = sizeof(s_audioGenerator.resTreble);
break;
case USB_DEVICE_AUDIO_FU_GET_RES_DELAY_CONTROL:
request->buffer = s_audioGenerator.resDelay;
request->length = sizeof(s_audioGenerator.resDelay);
break;
#if (USB_DEVICE_CONFIG_AUDIO_CLASS_2_0)
case USB_DEVICE_AUDIO_CS_GET_CUR_SAMPLING_FREQ_CONTROL:
request->buffer = (uint8_t *)&s_audioGenerator.curSampleFrequency;
request->length = sizeof(s_audioGenerator.curSampleFrequency);
break;
case USB_DEVICE_AUDIO_CS_SET_CUR_SAMPLING_FREQ_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = (uint8_t *)&s_audioGenerator.curSampleFrequency;
request->length = sizeof(s_audioGenerator.curSampleFrequency);
}
break;
case USB_DEVICE_AUDIO_CS_GET_CUR_CLOCK_VALID_CONTROL:
request->buffer = &s_audioGenerator.curClockValid;
request->length = sizeof(s_audioGenerator.curClockValid);
break;
case USB_DEVICE_AUDIO_CS_SET_CUR_CLOCK_VALID_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.curClockValid;
request->length = sizeof(s_audioGenerator.curClockValid);
}
break;
case USB_DEVICE_AUDIO_CS_GET_RANGE_SAMPLING_FREQ_CONTROL:
request->buffer = (uint8_t *)&s_audioGenerator.freqControlRange;
request->length = sizeof(s_audioGenerator.freqControlRange);
break;
case USB_DEVICE_AUDIO_FU_GET_RANGE_VOLUME_CONTROL:
request->buffer = (uint8_t *)&s_audioGenerator.volumeControlRange;
request->length = sizeof(s_audioGenerator.volumeControlRange);
break;
#else
case USB_DEVICE_AUDIO_EP_GET_CUR_SAMPLING_FREQ_CONTROL:
request->buffer = s_audioGenerator.curSamplingFrequency;
request->length = sizeof(s_audioGenerator.curSamplingFrequency);
break;
case USB_DEVICE_AUDIO_EP_GET_MIN_SAMPLING_FREQ_CONTROL:
request->buffer = s_audioGenerator.minSamplingFrequency;
request->length = sizeof(s_audioGenerator.minSamplingFrequency);
break;
case USB_DEVICE_AUDIO_EP_GET_MAX_SAMPLING_FREQ_CONTROL:
request->buffer = s_audioGenerator.maxSamplingFrequency;
request->length = sizeof(s_audioGenerator.maxSamplingFrequency);
break;
case USB_DEVICE_AUDIO_EP_GET_RES_SAMPLING_FREQ_CONTROL:
request->buffer = s_audioGenerator.resSamplingFrequency;
request->length = sizeof(s_audioGenerator.resSamplingFrequency);
break;
case USB_DEVICE_AUDIO_EP_SET_CUR_SAMPLING_FREQ_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.curSamplingFrequency;
request->length = sizeof(s_audioGenerator.curSamplingFrequency);
}
break;
case USB_DEVICE_AUDIO_EP_SET_RES_SAMPLING_FREQ_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.resSamplingFrequency;
request->length = sizeof(s_audioGenerator.resSamplingFrequency);
}
break;
case USB_DEVICE_AUDIO_EP_SET_MAX_SAMPLING_FREQ_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.maxSamplingFrequency;
request->length = sizeof(s_audioGenerator.maxSamplingFrequency);
}
break;
case USB_DEVICE_AUDIO_EP_SET_MIN_SAMPLING_FREQ_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.minSamplingFrequency;
request->length = sizeof(s_audioGenerator.minSamplingFrequency);
}
break;
#endif
case USB_DEVICE_AUDIO_FU_SET_CUR_VOLUME_CONTROL:
#if (USB_DEVICE_CONFIG_AUDIO_CLASS_2_0)
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.curVolume20;
request->length = sizeof(s_audioGenerator.curVolume20);
}
else
{
uint16_t volume = (uint16_t)((uint16_t)s_audioGenerator.curVolume20[1] << 8U);
volume |= (uint8_t)(s_audioGenerator.curVolume20[0]);
}
#else
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.curVolume;
request->length = sizeof(s_audioGenerator.curVolume);
}
else
{
uint16_t volume = (uint16_t)((uint16_t)s_audioGenerator.curVolume[1] << 8U);
volume |= (uint8_t)(s_audioGenerator.curVolume[0]);
}
#endif
break;
case USB_DEVICE_AUDIO_FU_SET_CUR_MUTE_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.curMute;
request->length = sizeof(s_audioGenerator.curMute);
}
else
{
}
break;
case USB_DEVICE_AUDIO_FU_SET_CUR_BASS_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.curBass;
request->length = sizeof(s_audioGenerator.curBass);
}
break;
case USB_DEVICE_AUDIO_FU_SET_CUR_MID_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.curMid;
request->length = sizeof(s_audioGenerator.curMid);
}
break;
case USB_DEVICE_AUDIO_FU_SET_CUR_TREBLE_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.curTreble;
request->length = sizeof(s_audioGenerator.curTreble);
}
break;
case USB_DEVICE_AUDIO_FU_SET_CUR_AUTOMATIC_GAIN_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.curAutomaticGain;
request->length = sizeof(s_audioGenerator.curAutomaticGain);
}
break;
case USB_DEVICE_AUDIO_FU_SET_CUR_DELAY_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.curDelay;
request->length = sizeof(s_audioGenerator.curDelay);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MIN_VOLUME_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.minVolume;
request->length = sizeof(s_audioGenerator.minVolume);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MIN_BASS_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.minBass;
request->length = sizeof(s_audioGenerator.minBass);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MIN_MID_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.minMid;
request->length = sizeof(s_audioGenerator.minMid);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MIN_TREBLE_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.minTreble;
request->length = sizeof(s_audioGenerator.minTreble);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MIN_DELAY_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.minDelay;
request->length = sizeof(s_audioGenerator.minDelay);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MAX_VOLUME_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.maxVolume;
request->length = sizeof(s_audioGenerator.maxVolume);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MAX_BASS_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.maxBass;
request->length = sizeof(s_audioGenerator.maxBass);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MAX_MID_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.maxMid;
request->length = sizeof(s_audioGenerator.maxMid);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MAX_TREBLE_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.maxTreble;
request->length = sizeof(s_audioGenerator.maxTreble);
}
break;
case USB_DEVICE_AUDIO_FU_SET_MAX_DELAY_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.maxDelay;
request->length = sizeof(s_audioGenerator.maxDelay);
}
break;
case USB_DEVICE_AUDIO_FU_SET_RES_VOLUME_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.resVolume;
request->length = sizeof(s_audioGenerator.resVolume);
}
break;
case USB_DEVICE_AUDIO_FU_SET_RES_BASS_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.resBass;
request->length = sizeof(s_audioGenerator.resBass);
}
break;
case USB_DEVICE_AUDIO_FU_SET_RES_MID_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.resMid;
request->length = sizeof(s_audioGenerator.resMid);
}
break;
case USB_DEVICE_AUDIO_FU_SET_RES_TREBLE_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = &s_audioGenerator.resTreble;
request->length = sizeof(s_audioGenerator.resTreble);
}
break;
case USB_DEVICE_AUDIO_FU_SET_RES_DELAY_CONTROL:
if (request->isSetup == 1U)
{
request->buffer = s_audioGenerator.resDelay;
request->length = sizeof(s_audioGenerator.resDelay);
}
break;
default:
error = kStatus_USB_InvalidRequest;
break;
}
return error;
}
usb_status_t USB_DeviceAudioCallback(class_handle_t handle, uint32_t event, void *param)
{
usb_status_t error = kStatus_USB_InvalidRequest;
usb_device_endpoint_callback_message_struct_t *ep_cb_param;
ep_cb_param = (usb_device_endpoint_callback_message_struct_t *)param;
switch (event)
{
case kUSB_DeviceAudioEventStreamSendResponse:
if ((0U != s_audioGenerator.attach) &&
(ep_cb_param->length == ((USB_SPEED_HIGH == s_audioGenerator.speed) ? HS_ISO_IN_ENDP_PACKET_SIZE :
FS_ISO_IN_ENDP_PACKET_SIZE)))
{
USB_AudioRecorderGetBuffer(s_wavBuff, (USB_SPEED_HIGH == s_audioGenerator.speed) ?
HS_ISO_IN_ENDP_PACKET_SIZE :
FS_ISO_IN_ENDP_PACKET_SIZE);
error = USB_DeviceAudioSend(handle, USB_AUDIO_STREAM_ENDPOINT, s_wavBuff,
(USB_SPEED_HIGH == s_audioGenerator.speed) ? HS_ISO_IN_ENDP_PACKET_SIZE :
FS_ISO_IN_ENDP_PACKET_SIZE);
}
break;
default:
if ((NULL != param) && (event > 0xFFU))
{
error = USB_DeviceAudioRequest(handle, event, param);
}
break;
}
return error;
}
以上定义中USB_DeviceAudioCallback会回调函数处理调用NXP提供的USB协议栈中音频相关的接口函数完成数据发送,音频配置项的设置。
4.3 启动USB协议栈
完成上述USB设备描述符、USB协议栈的相关回调函数,在程序中初始化USB外设,并启动USB协议栈,实现该功能的函数如下。
void USB_DeviceApplicationInit(void)
{
USB_DeviceClockInit();
#if (defined(FSL_FEATURE_SOC_SYSMPU_COUNT) && (FSL_FEATURE_SOC_SYSMPU_COUNT > 0U))
SYSMPU_Enable(SYSMPU, 0);
#endif /* FSL_FEATURE_SOC_SYSMPU_COUNT */
#if defined(USB_DEVICE_AUDIO_USE_SYNC_MODE) && (USB_DEVICE_AUDIO_USE_SYNC_MODE > 0U)
SCTIMER_CaptureInit();
#endif
if (kStatus_USB_Success != USB_DeviceClassInit(CONTROLLER_ID, &s_audioConfigList, &s_audioGenerator.deviceHandle))
{
usb_echo("USB device failed\r\n");
return;
}
else
{
usb_echo("USB device audio generator demo\r\n");
s_audioGenerator.audioHandle = s_audioConfigList.config->classHandle;
}
USB_DeviceIsrEnable();
/*Add one delay here to make the DP pull down long enough to allow host to detect the previous disconnection.*/
SDK_DelayAtLeastUs(5000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
USB_DeviceRun(s_audioGenerator.deviceHandle);
}
5、QT上位机
QT是图形界面上位机常用的开发框架,利用Code Agent能很方便地生成具有简单功能的工具,比如采集麦克风数据、实时显示音频波形以及分析音频频谱,用作实现相应功能。

利用AI Agent生成的录音工具结合开发板的USB麦克风功能,能够完成音频数据的抓取,完成一个完整麦克风采集的示例项目。
6、总结
通过本次活动,从使用MCUXpresso SDK工具创建芯片工程开始,配置MCU外设、组件以及Kconfig选项等,添加USB Audio协议栈实现USB麦克风功能,最后利用AI Agent生成QT上位机实现麦克风音频数据的抓取和显示,熟悉USB设备中的音频设备的描述符的定义以及USB协议栈的使用,收获颇丰。