USB的UAC设备开发(STM32)
目录
参考链接1:
参考链接2:
参考链接3:
1.前言
2.建立模板工程(以F4为例)
2.1未完成同步 IN 传输中断详解
3.验证现象
4.修改工程添加麦克风功能
4.1端点的FIFO
4.2描述符修改
4.2.1配置描述符修改
4.2.2UAC 类特定音频控制接口头描述符修改
4.2.3终端描述符修改
4.2.4UAC类特定音频流接口描述符修改
4.2.4端点描述符修改
4.3修改完成的描述符(麦克风+扬声器)
4.4修改AUDIO设备初始化函数
4.5验证枚举情况
4.6添加麦克风数据上传功能
5.查看现象
6.问题记录
7.USB IP实现的注意事项以F1为例
7.1端点缓冲区设置
7.2中断设置
参考链接1:
stm32 USB系列-UAC1.0-声卡(录放)-上_哔哩哔哩_bilibili
参考链接2:
UAC设备描述符 – USB中文网
参考链接3:
I2S音频开发(使用USB音频进行验证)_i2s pdm麦克风-CSDN博客
1.前言
UAC是USB Audio Class的缩写,有时也叫UAD,UAD是USB Audio Device的缩写。从名字就可以看出UAC就是USB下的音频类
在本文中关注的主要是使用UAC可以实现读取电脑音频到STM32,也可以发送麦克风的声音到电脑。由于UAC的相关描述符较多所以建议到参考链接2处仔细查看,本文重点关注如何进行编程。
2.建立模板工程(以F4为例)
本文只关注USB相关,I2S相关请关注另一篇文章:
I2S音频开发(使用USB音频进行验证)_i2s pdm麦克风-CSDN博客
我们先看一下UAC设备相关的参数,
使用的端点是0x01,是一个OUT端点传输方式是同步传输。
音频频率我们刚刚设置的是16khz,工程默认的音频数据是立体声,且音频数据位数是16位即两个uint8_t数据
所以有了
所以一包数据(1ms)有16*2*2=64个数据。
2.1未完成同步 IN 传输中断详解
相比于HID设备UAC设备多了一个USBD_AUDIO_IsoOutIncomplete这个函数,是用来处理未完成的同步传输的。
static uint8_t USBD_AUDIO_IsoOutIncomplete(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
USBD_AUDIO_HandleTypeDef *haudio;
if (pdev->pClassDataCmsit[pdev->classId] == NULL)
{
return (uint8_t)USBD_FAIL;
}
haudio = (USBD_AUDIO_HandleTypeDef *)pdev->pClassDataCmsit[pdev->classId];
/* Prepare Out endpoint to receive next audio packet */
(void)USBD_LL_PrepareReceive(pdev, epnum,
&haudio->buffer[haudio->wr_ptr],
AUDIO_OUT_PACKET);
return (uint8_t)USBD_OK;
}
从中可以看出这个例程对这种现象的处理是直接再次调用一下USBD_LL_PrepareReceive函数
这个函数的作用是:准备输出端点接收下一个音频数据包
如果是数据正常传输完成,则是调用的USBD_AUDIO_DataOut函数,里面也调用了USBD_LL_PrepareReceive函数用于接收。
那么为什么未完成的同步传输需要再次调用这个函数呢?
我们可以看一下ST提供的F4参考手册中的建议处理方式
由于提供的建议是针对IN端点的所以我们以IN端点的同步数据传输未完成为例进行分析
在中断函数中可以看到上面提到的未完成同步 IN 传输中断
相当于将上面的处理前5步进行完了,
然后就要接着找到端点禁止中断,只有这个中断响应了,才证明端点被禁止掉了。
我们在手册中的OTG_HS 设备端点 x 中断寄存器 (OTG_HS_DIEPINTx)
中可以找到端点禁止中断位是
然后在中断函数中就不难找到对应的处理如下所示
在前文中对此端点进行了中止操作并且刷新过了FIFO,所以理论上该端点已经有了再次发送数据的能力。从图中可以看到最后一步调用的回调函数其实就是USBD_AUDIO_IsoINIncomplete这个函数,如果这个函数也像USBD_AUDIO_IsoOutIncomplete函数那样再次调用USBD_LL_Transmit
就相当于将端点再次启动重新应用于数据传输。这也就是为什么USBD_AUDIO_IsoOutIncomplete函数中的处理仅仅是再次调用USBD_LL_PrepareReceive。(至于前面提到的禁止端点,前文中的图片中也说到了在进入端点禁止中断之前就已经将禁止位清零了所以应用程序不需要去清除禁止位。)
3.验证现象
播放音乐时可以看到软件中捕获的音频流数据,在不断刷新,并且数据包长度也和之前计算的吻合。
4.修改工程添加麦克风功能
具体如何修改?可以查看参考链接1也可以看下面我的实操。
修改之前我们可以先整理一下思路,当前工程已经可以被电脑的USB端口识别为扬声器,并能够接收到电脑发送的音频数据,所以抛开音频数据不管,理论上我们只需要修改对应的配置描述符让电脑识别单片机为麦克风即可。
4.1端点的FIFO
这里需要注意的是,在修改之前需要先确定IN端点,因为USB麦克风需要使用IN端点向电脑发送音频流数据。
在示例工程中端点分配在这个位置
MX_USB_DEVICE_Init—》USBD_Init–》USBD_LL_Init
在USBD_LL_Init这个函数的后面可以看到端点的FIFO分配情况
具体如何分配可以参考此分类专栏中的STM32USB设备库简介。
从上图我们可以看到工程中已经分配了一个序号为1的端点的TXFIFO
那么这个端点的地址就是0x81//位7为1代表是IN端点,
4.2描述符修改
当然了修改方法分两种,一种是将麦克风的描述符添加进去,保留原来的扬声器的描述符,还有一种呢就是直接将描述符替换为麦克风的。
这里我们选择添加麦克风的描述符,这样比较好验证。,并且与参考链接1的方法保持一致。
需要注意如果使用此修改方式需要将前文中在cubemx中的设置的最大接口数改为3
因为会有 :麦克风+扬声器+音频控制 共三个接口
这里仅说明需要配置描述符中需要重点关注的地方,其他的描述符可以自行了解然后改写
4.2.1配置描述符修改
变更接口数量,预期的接口:扬声器接口+控制接口+麦克风接口
4.2.2UAC 类特定音频控制接口头描述符修改
1.bLength 该接口本身的大小,固定为8+N。N即为音频接口数量
2.wTotalLength 总长度这一项也应特别注意,是此描述符的长度加上后面所有的终端描述符的长度
3.修改音频流数量以及对应音频流的接口号索引
4.2.3终端描述符修改
主要是修改为终端输入为麦克风,输出为USB流,注意终端号在描述符中必须是唯一的
4.2.4UAC类特定音频流接口描述符修改
主要需要注意bTerminalLink,绑定的终端ID一定是接到USB流的终端,
比如上面设定的麦克风的输出终端绑定USB流,并且终端ID为6所以此处为6
再比如扬声器的输入终端绑定USB流,并且终端ID为1,所以扬声器的对应位置应为1
即绑定的终端ID与输入输出无关只关心是否为USB流
4.2.4端点描述符修改
将扬声器的OUT端点地址,更换为麦克风的IN端点地址
4.3修改完成的描述符(麦克风+扬声器)
/* USB AUDIO device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_AUDIO_CfgDesc[USB_AUDIO_CONFIG_DESC_SIZ] __ALIGN_END =
{
/* Configuration 1 */
0x09, /* bLength *///配置描述符的字节数大小,固定为9字节
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType *///描述符类型编号,为0x02
LOBYTE(USB_AUDIO_CONFIG_DESC_SIZ), /* wTotalLength *///配置描述符+接口描述符+端点描述符的总大小
HIBYTE(USB_AUDIO_CONFIG_DESC_SIZ),
0x03, /* bNumInterfaces *///此配置所支持的接口数量//扬声器接口+控制接口+麦克风接口
0x01, /* bConfigurationValue *///
0x00, /* iConfiguration */
#if (USBD_SELF_POWERED == 1U)
0xC0, /* bmAttributes: Bus Powered according to user configuration */
#else
0x80, /* bmAttributes: Bus Powered according to user configuration */
#endif /* USBD_SELF_POWERED */
USBD_MAX_POWER, /* MaxPower (mA) */
/* 09 byte*/
/*
UAC音频控制描述符
1.UAC标准音频控制接口描述符
2.UAC类特定音频控制接口头描述符
*/
#if (9+10)
/* USB Speaker Standard interface descriptor */
AUDIO_INTERFACE_DESC_SIZE, /* bLength */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType */
0x00, /* bInterfaceNumber *///接品描述符的编号。从0开始编号
0x00, /* bAlternateSetting *///接口的转换接口号
0x00, /* bNumEndpoints *///该接口下含有端点数。如果没有数据端点则为0.
USB_DEVICE_CLASS_AUDIO, /* bInterfaceClass *///接口的类型:0x01=AUDIO,表示音频类。
AUDIO_SUBCLASS_AUDIOCONTROL, /* bInterfaceSubClass *///接口子类型:音频控制类型 AUDIOCONTROL=0x01
AUDIO_PROTOCOL_UNDEFINED, /* bInterfaceProtocol *///接口协议号,UAC1.0此值未用,必须设置为0.
0x00, /* iInterface *///接口的字符串索引.
/* 09 byte*/
/* USB Speaker Class-specific AC Interface Descriptor *///UAC 类特定音频控制接口头描述符
8+2, /* bLength 该接口本身的大小,固定为8+N。*///
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType 属于接口描述符类型,CS_INTERFACE,值为0x24*///
AUDIO_CONTROL_HEADER, /* bDescriptorSubtype 描述符的子类型,属于头描述符子类型=0x01*/
0x00, /* 1.00 */ /* bcdADC UAC协议版本*/
0x01,
0x46, ///* wTotalLength 总长度,包括此描述符及后续的如时钟描述符,单元描述符,终端描述符。*///10+(12+9+9)*2
0x00,
0x02, /* bInCollection 所有的音频流接口数量N*/
0x01, /* baInterfaceNr 音频流的接口索引,多个依次后续连接*/
0x02, /* baInterfaceNr 音频流的接口索引,多个依次后续连接*/
/* 10 byte*/
/*
bInCollection从规范上来讲,代表数据流的个数。
当为1时,代表只有1个数据流,只为2时代表有2个数据流。
所以我们上面为1时,是一个USB小音响的内容,由于其只有一个输出音频流,故为1。
当为2时,是我们的华为耳机的内容,由于我们既是一个麦克风同时也有一个耳机,故数据流为2。
*/
#endif
/*
扬声器终端描述符
1.输入终端
2.特性单元
3.输出终端
*/
#if (12+9+9)
/* USB Speaker Input Terminal Descriptor */
AUDIO_INPUT_TERMINAL_DESC_SIZE, /* bLength 该描述符的结构体大小,固定为12字节长度*/
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType 描述符类型,描述符类型CS_INTERFACE,值为0x24*/
AUDIO_CONTROL_INPUT_TERMINAL, /* bDescriptorSubtype 描述符的子类型 INPUT_TERMINAL = 0x02.*/
0x01, /* bTerminalID 输入终端的ID,惟一*/
0x01, /* wTerminalType AUDIO_TERMINAL_USB_STREAMING 0x0101 输入终端的类型 USB流*/
0x01,
/*UAC 音频通道集描述符Audio Channel Cluster Descriptor*/
0x00, /* bAssocTerminal 关联的输出终端ID.*/
0x01, /* bNrChannels 音频输出集的逻辑通道个数*/
0x00, /* wChannelConfig 0x0000 Mono 音频通道的位置标识*/
0x00,
0x00, /* iChannelNames 未预设的通道名字符串起始索引*/
0x00, /* iTerminal 该输入终端的字符串索引*/
/* 12 byte*/
/* USB Speaker Audio Feature Unit Descriptor */
0x09, /* bLength 描述符总长度。7+(ch+1)*n*/
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_CONTROL_FEATURE_UNIT, /* bDescriptorSubtype */
AUDIO_OUT_STREAMING_CTRL, /* bUnitID 惟一的标识ID*/
0x01, /* bSourceID 数据源ID*/
0x01, /* bControlSize 每个bControlSize的字节数*/
AUDIO_CONTROL_MUTE, /* bmaControls(0) 偏移为6,长度为bControlSize。位设为1表示主通道0支持上述控制*/
0, /* bmaControls(1) 偏移为6+bControlSize,长度为bControlSize,表示逻辑通道1支持的特性*/
0x00, /* iTerminal */
/* 09 byte */
/* USB Speaker Output Terminal Descriptor */
0x09, /* bLength */
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_CONTROL_OUTPUT_TERMINAL, /* bDescriptorSubtype */
0x03, /* bTerminalID 输出终端的ID,惟一*/
0x01, /* wTerminalType 0x0301 输出终端的类型:扬声器*/
0x03,
0x00, /* bAssocTerminal 关联的输入终端ID.如果不存在,设为0。*/
0x02, /* bSourceID 该输出终端连接的单元/终端ID*/
0x00, /* iTerminal */
/* 09 byte */
#endif
/*
麦克风终端描述符
1.输入终端
2.特性单元
3.输出终端
*/
#if (12+9+9)
/* USB MIC Input Terminal Descriptor */
AUDIO_INPUT_TERMINAL_DESC_SIZE, /* bLength 该描述符的结构体大小,固定为12字节长度*/
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType 描述符类型,描述符类型CS_INTERFACE,值为0x24*/
AUDIO_CONTROL_INPUT_TERMINAL, /* bDescriptorSubtype 描述符的子类型 INPUT_TERMINAL = 0x02.*/
0x04, /* bTerminalID 输入终端的ID,惟一*/
0x01, /* wTerminalType AUDIO_TERMINAL_USB_STREAMING 0x0201 输入终端的类型 麦克风*/
0x02,
/*UAC 音频通道集描述符Audio Channel Cluster Descriptor*/
0x00, /* bAssocTerminal 关联的输出终端ID.*/
0x01, /* bNrChannels 音频输出集的逻辑通道个数*/
0x00, /* wChannelConfig 0x0000 Mono 音频通道的位置标识*/
0x00,
0x00, /* iChannelNames 未预设的通道名字符串起始索引*/
0x00, /* iTerminal 该输入终端的字符串索引*/
/* 12 byte*/
/* USB Speaker Audio Feature Unit Descriptor */
// 0x09, /* bLength 描述符总长度。7+(ch+1)*n*/
// AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
// AUDIO_CONTROL_FEATURE_UNIT, /* bDescriptorSubtype */
// AUDIO_IN_STREAMING_CTRL, /* bUnitID 惟一的标识ID*/
// 0x04, /* bSourceID 数据源ID*///即输入终端的ID
// 0x01, /* bControlSize 每个bControlSize的字节数*/
// AUDIO_CONTROL_MUTE, /* bmaControls(0) 偏移为6,长度为bControlSize。位设为1表示主通道0支持上述控制*/
// 0, /* bmaControls(1) 偏移为6+bControlSize,长度为bControlSize,表示逻辑通道1支持的特性*/
// 0x00, /* iTerminal */
/* 09 byte */
/* USB Speaker Output Terminal Descriptor */
0x09, /* bLength */
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_CONTROL_OUTPUT_TERMINAL, /* bDescriptorSubtype */
0x06, /* bTerminalID 输出终端的ID,惟一*/
0x01, /* wTerminalType 0x0101 输出终端的类型:USB*/
0x01,
0x00, /* bAssocTerminal 关联的输入终端ID.如果不存在,设为0。*/
0x04, /* bSourceID 该输出终端连接的单元/终端ID*/
0x00, /* iTerminal */
/* 09 byte */
#endif
/*
UAC音频流接口描述符//扬声器
1.UAC标准音频流接口描述符//音频流零带宽
2.UAC标准音频流接口描述符//音频流有带宽
3.UAC类特定音频流接口描述符//
3.UAC音频数据格式描述符//离散的采样率数据结构
4.标准等时音频数据端点描述符//扬声器
5.类特定等时音频数据端点描述符
*/
#if (9+9+7+11+9+7)
/* USB Speaker Standard AS Interface Descriptor - Audio Streaming Zero Bandwidth */
/* Interface 1, Alternate Setting 0 */
AUDIO_INTERFACE_DESC_SIZE, /* bLength */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType */
0x01, /* bInterfaceNumber 接品描述符的编号。从0开始编号*/
0x00, /* bAlternateSetting 接口的转换接口号*/
0x00, /* bNumEndpoints 该接口下含有端点数。如果没有音频流端点,则为0.*/
USB_DEVICE_CLASS_AUDIO, /* bInterfaceClass接口的类型:0x01=AUDIO,表示音频类。 */
AUDIO_SUBCLASS_AUDIOSTREAMING, /* bInterfaceSubClass 接口子类型:音频控制类型 AUDIO_STREAMING =0x02*/
AUDIO_PROTOCOL_UNDEFINED, /* bInterfaceProtocol 接口协议号*/
0x00, /* iInterface 接口的字符串索引*/
/* 09 byte*/
/* USB Speaker Standard AS Interface Descriptor - Audio Streaming Operational */
/* Interface 1, Alternate Setting 1 */
AUDIO_INTERFACE_DESC_SIZE, /* bLength */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType */
0x01, /* bInterfaceNumber */
0x01, /* bAlternateSetting */
0x01, /* bNumEndpoints */
USB_DEVICE_CLASS_AUDIO, /* bInterfaceClass */
AUDIO_SUBCLASS_AUDIOSTREAMING, /* bInterfaceSubClass */
AUDIO_PROTOCOL_UNDEFINED, /* bInterfaceProtocol */
0x00, /* iInterface */
/* 09 byte*/
/* USB Speaker Audio Streaming Interface Descriptor */
AUDIO_STREAMING_INTERFACE_DESC_SIZE, /* bLength */
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_STREAMING_GENERAL, /* bDescriptorSubtype */
0x01, /* bTerminalLink 该接口内的端点连接的终端ID.*/
0x01, /* bDelay 延迟*/
0x01, /* wFormatTag AUDIO_FORMAT_PCM 0x0001 与此接口通信的音频数据格式*/
0x00,
/* 07 byte*/
/* USB Speaker Audio Type I Format Interface Descriptor */
0x0B, /* bLength 结构体的大小:8+(ns*3)*/
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_STREAMING_FORMAT_TYPE, /* bDescriptorSubtype */
AUDIO_FORMAT_TYPE_I, /* bFormatType 音频数据格式,这里为FORMAT_TYPE_I*/
0x02, /* bNrChannels 音频数据的通道数*/
0x02, /* bSubFrameSize : 2 Bytes per frame (16bits) 每通道数据的字节数,可以1,2,3,4。*/
16, /* bBitResolution (16-bits per sample)bSubframeSize中的有效位数。 */
0x01, /* bSamFreqType only one frequency supported 采样类型,表示支持的离散采样频率个数,非0。*/
AUDIO_SAMPLE_FREQ(USBD_AUDIO_FREQ), /* Audio sampling frequency coded on 3 bytes */
/* 11 byte*/
/* Endpoint 1 - Standard Descriptor *///扬声器
AUDIO_STANDARD_ENDPOINT_DESC_SIZE, /* bLength */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType */
AUDIO_OUT_EP, /* bEndpointAddress 1 out endpoint 端点地址。BIT7=1表示输入端点,BIT7=0表示输出端点。BIT4-6:保留,置为0。BIT0-3:表示端点地址。*/
USBD_EP_TYPE_ISOC, /* bmAttributes BIT1-0:必须为01表示等时传输。BIT3-2为01表示为异步,为10表示自适应,为11时表示同步。*/
AUDIO_PACKET_SZE(USBD_AUDIO_FREQ), /* wMaxPacketSize in Bytes (Freq(Samples)*2(Stereo)*2(HalfWord)) 端点的数据传输的最大包长度。*/
AUDIO_FS_BINTERVAL, /* bInterval */
0x00, /* bRefresh */
0x00, /* bSynchAddress 用于传输同步信息的端点地址。如果不需要置为0。*/
/* 09 byte*/
/* Endpoint - Audio Streaming Descriptor */
AUDIO_STREAMING_ENDPOINT_DESC_SIZE, /* bLength */
AUDIO_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_ENDPOINT_GENERAL, /* bDescriptor */
0x00, /* bmAttributes BIT0代表是否支持采样率调整(Sampling Frequency)。BIT1代表是否支持高音调整(PITCH)。BIT7代表是否只支持wMaxPacketSize的传输(MaxPacketsOnly)。其他BIT保留*/
0x00, /* bLockDelayUnits wLockDelay的单位,0为未定义(Undefined),1为毫秒(Milliseconds),2为PCM采样(Decoded PCM samples)。其他值保留。*/
0x00, /* wLockDelay 代表该USB设备在主机设置后需要多久才能达到时钟稳定。*/
0x00,
/* 07 byte*/
#endif
/*
UAC音频流接口描述符//麦克风
1.UAC标准音频流接口描述符//音频流零带宽
2.UAC标准音频流接口描述符//音频流有带宽
3.UAC类特定音频流接口描述符//
3.UAC音频数据格式描述符//离散的采样率数据结构
4.标准等时音频数据端点描述符//麦克风
5.类特定等时音频数据端点描述符
*/
#if (9+9+7+11+9+7)
/* USB Speaker Standard AS Interface Descriptor - Audio Streaming Zero Bandwidth */
/* Interface 1, Alternate Setting 0 */
AUDIO_INTERFACE_DESC_SIZE, /* bLength */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType */
0x02, /* bInterfaceNumber 接品描述符的编号。从0开始编号*/
0x00, /* bAlternateSetting 接口的转换接口号*/
0x00, /* bNumEndpoints 该接口下含有端点数。如果没有音频流端点,则为0.*/
USB_DEVICE_CLASS_AUDIO, /* bInterfaceClass接口的类型:0x01=AUDIO,表示音频类。 */
AUDIO_SUBCLASS_AUDIOSTREAMING, /* bInterfaceSubClass 接口子类型:音频控制类型 AUDIO_STREAMING =0x02*/
AUDIO_PROTOCOL_UNDEFINED, /* bInterfaceProtocol 接口协议号*/
0x00, /* iInterface 接口的字符串索引*/
/* 09 byte*/
/* USB Speaker Standard AS Interface Descriptor - Audio Streaming Operational */
/* Interface 1, Alternate Setting 1 */
AUDIO_INTERFACE_DESC_SIZE, /* bLength */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType */
0x02, /* bInterfaceNumber */
0x02, /* bAlternateSetting */
0x01, /* bNumEndpoints */
USB_DEVICE_CLASS_AUDIO, /* bInterfaceClass */
AUDIO_SUBCLASS_AUDIOSTREAMING, /* bInterfaceSubClass */
AUDIO_PROTOCOL_UNDEFINED, /* bInterfaceProtocol */
0x00, /* iInterface */
/* 09 byte*/
/* USB Speaker Audio Streaming Interface Descriptor */
AUDIO_STREAMING_INTERFACE_DESC_SIZE, /* bLength */
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_STREAMING_GENERAL, /* bDescriptorSubtype */
0x06, ///* bTerminalLink 该接口内的端点连接的终端ID.*/
0x01, /* bDelay 延迟*/
0x01, /* wFormatTag AUDIO_FORMAT_PCM 0x0001 与此接口通信的音频数据格式*/
0x00,
/* 07 byte*/
/* USB Speaker Audio Type I Format Interface Descriptor */
0x0B, /* bLength 结构体的大小:8+(ns*3)*/
AUDIO_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_STREAMING_FORMAT_TYPE, /* bDescriptorSubtype */
AUDIO_FORMAT_TYPE_I, /* bFormatType 音频数据格式,这里为FORMAT_TYPE_I*/
0x02, ///* bNrChannels 音频数据的通道数*/
0x02, /* bSubFrameSize : 2 Bytes per frame (16bits) 每通道数据的字节数,可以1,2,3,4。*/
16, /* bBitResolution (16-bits per sample)bSubframeSize中的有效位数。 */
0x01, /* bSamFreqType only one frequency supported 采样类型,表示支持的离散采样频率个数,非0。*/
AUDIO_SAMPLE_FREQ(USBD_AUDIO_FREQ), /* Audio sampling frequency coded on 3 bytes */
/* 11 byte*/
/* Endpoint 1 - Standard Descriptor *///麦克风
AUDIO_STANDARD_ENDPOINT_DESC_SIZE, /* bLength */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType */
AUDIO_IN_EP, /* bEndpointAddress 1 out endpoint 端点地址。BIT7=1表示输入端点,BIT7=0表示输出端点。BIT4-6:保留,置为0。BIT0-3:表示端点地址。*/
USBD_EP_TYPE_ISOC, /* bmAttributes BIT1-0:必须为01表示等时传输。BIT3-2为01表示为异步,为10表示自适应,为11时表示同步。*/
AUDIO_PACKET_SZE(USBD_AUDIO_FREQ), /* wMaxPacketSize in Bytes (Freq(Samples)*2(Stereo)*2(HalfWord)) 端点的数据传输的最大包长度。*/
AUDIO_FS_BINTERVAL, /* bInterval */
0x00, /* bRefresh */
0x00, /* bSynchAddress 用于传输同步信息的端点地址。如果不需要置为0。*/
/* 09 byte*/
/* Endpoint - Audio Streaming Descriptor */
AUDIO_STREAMING_ENDPOINT_DESC_SIZE, /* bLength */
AUDIO_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
AUDIO_ENDPOINT_GENERAL, /* bDescriptor */
0x00, /* bmAttributes BIT0代表是否支持采样率调整(Sampling Frequency)。BIT1代表是否支持高音调整(PITCH)。BIT7代表是否只支持wMaxPacketSize的传输(MaxPacketsOnly)。其他BIT保留*/
0x00, /* bLockDelayUnits wLockDelay的单位,0为未定义(Undefined),1为毫秒(Milliseconds),2为PCM采样(Decoded PCM samples)。其他值保留。*/
0x00, /* wLockDelay 代表该USB设备在主机设置后需要多久才能达到时钟稳定。*/
0x00,
/* 07 byte*/
#endif
} ;
4.4修改AUDIO设备初始化函数
即USBD_AUDIO_Init函数
由于我们是加入的麦克风,所以可以基于原有的扬声器的内容(OUT端点)加入麦克风(IN端点)的相关初始化即可,
完整代码如下
/**
* @brief USBD_AUDIO_Init
* Initialize the AUDIO interface
* @param pdev: device instance
* @param cfgidx: Configuration index
* @retval status
*/
static uint8_t USBD_AUDIO_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
UNUSED(cfgidx);
USBD_AUDIO_HandleTypeDef *haudio;
/* Allocate Audio structure */
haudio = (USBD_AUDIO_HandleTypeDef *)USBD_malloc(sizeof(USBD_AUDIO_HandleTypeDef));
if (haudio == NULL)
{
pdev->pClassDataCmsit[pdev->classId] = NULL;
return (uint8_t)USBD_EMEM;
}
pdev->pClassDataCmsit[pdev->classId] = (void *)haudio;
pdev->pClassData = pdev->pClassDataCmsit[pdev->classId];
#ifdef USE_USBD_COMPOSITE
/* Get the Endpoints addresses allocated for this class instance */
AUDIOOutEpAdd = USBD_CoreGetEPAdd(pdev, USBD_EP_OUT, USBD_EP_TYPE_ISOC, (uint8_t)pdev->classId);
#endif /* USE_USBD_COMPOSITE */
if (pdev->dev_speed == USBD_SPEED_HIGH)
{
pdev->ep_out[AUDIOOutEpAdd & 0xFU].bInterval = AUDIO_HS_BINTERVAL;
pdev->ep_in[AUDIOInEpAdd & 0xFU].bInterval = AUDIO_HS_BINTERVAL;
}
else /* LOW and FULL-speed endpoints */
{
pdev->ep_out[AUDIOOutEpAdd & 0xFU].bInterval = AUDIO_FS_BINTERVAL;
pdev->ep_in[AUDIOInEpAdd & 0xFU].bInterval = AUDIO_FS_BINTERVAL;
}
/* Open EP OUT */
(void)USBD_LL_OpenEP(pdev, AUDIOOutEpAdd, USBD_EP_TYPE_ISOC, AUDIO_OUT_PACKET);
pdev->ep_out[AUDIOOutEpAdd & 0xFU].is_used = 1U;
/* Open EP IN */
(void)USBD_LL_OpenEP(pdev, AUDIOInEpAdd, USBD_EP_TYPE_ISOC, AUDIO_IN_PACKET);
pdev->ep_in[AUDIOInEpAdd & 0xFU].is_used = 1U;
haudio->alt_setting = 0U;
haudio->offset = AUDIO_OFFSET_UNKNOWN;
haudio->wr_ptr = 0U;
haudio->rd_ptr = 0U;
haudio->rd_enable = 0U;
/* Initialize the Audio output Hardware layer */
if (((USBD_AUDIO_ItfTypeDef *)pdev->pUserData[pdev->classId])->Init(USBD_AUDIO_FREQ,
AUDIO_DEFAULT_VOLUME,
0U) != 0U)
{
return (uint8_t)USBD_FAIL;
}
/* Prepare Out endpoint to receive 1st packet */
(void)USBD_LL_PrepareReceive(pdev, AUDIOOutEpAdd, haudio->buffer,
AUDIO_OUT_PACKET);
/* Prepare In endpoint to send 1st packet */
(void)USBD_LL_Transmit(pdev, AUDIOInEpAdd, haudio->buffer,
AUDIO_IN_PACKET);
return (uint8_t)USBD_OK;
}
其中函数末尾初始化后先向FIFO读取或写入数据,目的就是为了防止由于收到IN令牌时FIFO中没有数据而导致未完成同步传输中断的产生。
当然如果需要的话USBD_AUDIO_DeInit函数也可以顺便更改一下。
4.5验证枚举情况
编译下载之后连接到电脑USB口之后,可以先将此设备卸载,然后重新插入,让电脑对此设备重新进行枚举,因为电脑在识别到两次设备的PID和VID一致时自动默认为同一设备就不会再次获取配置了,所以容易出现枚举不成功或者功能不一致等情况(听说的)
以上准备完成之后可以打开bushound查看此设备的数据情况
我们选择电脑扬声器为STM32,并播放音乐可以看到与上面的情况一致即电脑向STM32发送着包长为64byte的数据包,但是如果关闭音乐,并打开录音功能会发现下面这种情况
即上传的数据包长度为0
出现这个情况的原因时我们并未对数据上传做处理所以USB检测到FIFO中无数据所以只能返回包长为0的数据包。
4.6添加麦克风数据上传功能
添加的方法可以参照扬声器接收音频流的函数
USBD_AUDIO_IsoOutIncomplete;
USBD_AUDIO_DataOut;
我们需要编写的是
USBD_AUDIO_IsoINIncomplete;
USBD_AUDIO_DataIn
其中为了验证所以我们将USBD_AUDIO_DataIn函数的上传数据指定为扬声器接收到的数据,借助rd_ptr指针读取并上传到电脑.
5.查看现象
此时可以使用bushound看到录音的时候会有包长为64byte的数据包
虽然全为0,(因为没有开启音乐向扬声器发送数据)。
可以指定STM32为扬声器之后开启音乐,然后开启录音机,录到的声音可以听出来是电脑放的音乐。
6.问题记录
当配置描述符中同时加入扬声器和麦克风,连接电脑之后需要很长时间才能枚举出扬声器和麦克风,但是如果只是添加扬声器/麦克风其中的一个就很快。暂时不清楚是为什么
7.USB IP实现的注意事项以F1为例
7.1端点缓冲区设置
见STM32USB设备库简介_stm32 usb-CSDN博客
7.2中断设置
F1的USBIP我们可以使用的有两个中断,在使用同步端点时需要同时开启两个中断,
参考手册说明如下
然后在cubemx中可以看到
使用F103在CUBEMX中打开USB设备库中间件的时候默认并不会打开高优先级中断,默认的扬声器是否能用我忘记了,反正更改为麦克风的时候需要记住开启不然会有问题。
作者:可乐苏打水