第五十七章 USB声卡(Slave)实验
上一章我们向大家介绍了如何利用STM32H562的USB接口来做一个USB读卡器,本章我们将利用STM32H562的USB来做一个声卡。本章分为如下几个部分:
57.1 USB声卡简介
57.2 硬件设计
57.3 软件设计
57.4 下载验证
57.1 USB声卡简介
USB声卡依赖于USB音频类(USB Audio Class),一个像USB通用的数据接口,可以实现数字音频的数据传输。
正点原子Mini STM32H562开发板板载了一颗高性能CODEC芯片:ES8388,我们可以利用STM32H562的SAI接口,控制ES8388播放音乐,同样,如果结合STM32H562的USB功能,就可以实现一个USB声卡。
57.2 硬件设计
1. 例程功能
本节实验功能简介:先将开发板通过USB_SLAVE连接电脑,开发板开机的时候先提示一些信息,然后开始USB配置,在配置成功之后就可以在电脑上发现多出一个声卡。此时,我们可以通过耳机或开发板板载喇叭,听到来自电脑的音频信号。LED0闪烁,提示程序运行。
2. 硬件资源
1)LED灯 LED0 - PB4
2)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
3)USB_SLAVE接口(D-/D+连接在PA11/PA12上)
4)ES8388音频CODEC芯片,通过SAI驱动
5)SAI音频接口
57.3 程序设计
57.3.1 程序流程图

图57.3.1.1 USB声卡实验程序流程图
57.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
本章,我们在实验37 音乐播放器实验的基础上修改,把不需要的文件从工程中移除,并移植USB AUDIO相关驱动,最终得到如图57.3.2.1所示的工程:

图57.3.2.1 USB声卡(Slave)实验工程分组截图
1. USB驱动代码
app_usbx_device.c代码,和上一章基本一样,具体请查看源码,这里就不贴出来了。
ux_device_descriptors.c代码,同上一章不一样,上一章描述符是大容量存储设备,本章变成了USB声卡了,所以直接用ST官方例程的就行。
ux_device_audio.c代码,是USB声卡的接口文件,具体源码如下:
/**
* @brief AUDIO流读变更
* @param stream: Audio流实例
* @param alternate_setting: 状态
* @retval 无
*/
VOID ux_device_audio_stream_read_change(struct
UX_DEVICE_CLASS_AUDIO_STREAM_STRUCT *stream, ULONG alternate_setting)
{
if (alternate_setting == 0)
{
sai1_play_stop();
}
else
{
sai1_play_start();
ux_device_class_audio_reception_start(stream);
}
}
/**
* @brief SAI DMA节点传输完成回调函数
* @param node: 传输完成的DMA节点编号
* @retval 无
*/
void ux_device_audio_sai_dma_callback(uint8_t node)
{
audio_play_buffer_write_index = node + 1;
if (audio_play_buffer_write_index == (sizeof(audio_play_buffer)
/ sizeof(audio_play_buffer[0])))
{
audio_play_buffer_write_index = 0;
}
}
/**
* @brief AUDIO流读完成
* @param stream: Audio流实例
* @param length: 数据长度
* @retval 无
*/
VOID ux_device_audio_stream_frame_read_done(struct
UX_DEVICE_CLASS_AUDIO_STREAM_STRUCT *stream, ULONG length)
{
UCHAR *frame_data;
ULONG frame_length;
ux_device_class_audio_read_frame_get(stream, &frame_data, &frame_length);
if (length != 0)
{
ux_utility_memory_copy(audio_play_buffer[audio_play_buffer_write_index],
frame_data, frame_length);
}
ux_device_class_audio_read_frame_free(stream);
}
/**
* @brief 激活AUDIO类
* @param audio: AUDIO类实例
* @retval 无
*/
VOID ux_device_audio_instance_activate(VOID *audio)
{
UX_PARAMETER_NOT_USED(audio);
/* 配置ES8388 */
es8388_adda_cfg(1, 0);
es8388_output_cfg(1, 1);
es8388_hpvol_set(25);
es8388_spkvol_set(25);
es8388_sai_cfg(0, 3);
/* 配置SAI */
sai1_saia_init(SAI_MODEMASTER_TX, SAI_CLOCKSTROBING_RISINGEDGE,
SAI_DATASIZE_16);
sai1_samplerate_set(USB_AUDIO_CONFIG_PLAY_SAMPLING_FREQUENCY);
sai1_tx_callback = ux_device_audio_sai_dma_callback;
sai1_tx_dma_init(audio_play_buffer[0], audio_play_buffer[1],
AUDIO_PLAY_BUFFER_SIZE / 2, 1);
}
/**
* @brief 停用AUDIO类
* @param audio: AUDIO类实例
* @retval 无
*/
VOID ux_device_audio_instance_deactivate(VOID *audio)
{
UX_PARAMETER_NOT_USED(audio);
}
/**
* @brief AUDIO类控制
* @param audio: AUDIO类实例
* @param transfer: 控制请求
* @retval 状态
*/
UINT ux_device_audio_control_process(struct UX_DEVICE_CLASS_AUDIO_STRUCT
*audio, UX_SLAVE_TRANSFER *transfer)
{
UX_DEVICE_CLASS_AUDIO20_CONTROL audio_control[1];
UX_DEVICE_CLASS_AUDIO20_CONTROL_GROUP group;
audio_control[0].ux_device_class_audio20_control_cs_id =
USB_AUDIO_CONFIG_PLAY_CLOCK_SOURCE_ID;
audio_control[0].ux_device_class_audio20_control_sampling_frequency =
USB_AUDIO_CONFIG_PLAY_SAMPLING_FREQUENCY;
audio_control[0].ux_device_class_audio20_control_fu_id =
USB_AUDIO_CONFIG_PLAY_UNIT_FEATURE_ID;
audio_control[0].ux_device_class_audio20_control_mute[0] = 0;
audio_control[0].ux_device_class_audio20_control_volume_min[0] = 0;
audio_control[0].ux_device_class_audio20_control_volume_max[0] = 100;
audio_control[0].ux_device_class_audio20_control_volume_res[0] = 1;
audio_control[0].ux_device_class_audio20_control_volume[0] = 70;
group.ux_device_class_audio20_control_group_controls_nb = 1;
group.ux_device_class_audio20_control_group_controls = audio_control;
if (ux_device_class_audio20_control_process(audio, transfer, &group) !=
UX_SUCCESS)
{
return UX_ERROR;
}
switch (audio_control[0].ux_device_class_audio20_control_changed)
{
case UX_DEVICE_CLASS_AUDIO20_CONTROL_MUTE_CHANGED:
{
es8388_output_cfg(0, 0);
break;
}
case UX_DEVICE_CLASS_AUDIO20_CONTROL_VOLUME_CHANGED:
{
es8388_output_cfg(1, 1);
es8388_hpvol_set(audio_control[0]
.ux_device_class_audio20_control_volume[0] * 0.3);
es8388_spkvol_set(audio_control[0]
.ux_device_class_audio20_control_volume[0] * 0.3);
break;
}
case UX_DEVICE_CLASS_AUDIO20_CONTROL_FREQUENCY_CHANGED:
default:
{
break;
}
}
return UX_SUCCESS;
}
该部分代码包括激活AUDIO类、AUDIO类控制和AUDIO流控制等函数。
2. main.c代码
下面是main.c的代码,具体如下:
int main(void)
{
uint8_t res;
uint32_t t = 0;
HAL_Init(); /* HAL库初始化 */
HAL_ICACHE_Enable(); /* 使能指令缓存 */
sys_stm32_clock_init(250, 4, 2, 2); /* 配置时钟,250MHz */
delay_init(250); /* 初始化延时 */
usart_init(115200); /* 初始化串口 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
es8388_init(); /* 初始化ES8388 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "USB Audio Card TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
/* USB设备初始化 */
res = app_usbx_device_init();
while (res != 0)
{
lcd_show_string(30, 110, 200, 16, 16, "USB Device Init Failed!", RED);
delay_ms(500);
lcd_show_string(30, 110, 200, 16, 16, "Please Check! ", RED);
delay_ms(500);
}
while (1)
{
/* 运行独立模式下的USBX系统任务 */
ux_system_tasks_run();
if (++t == 100000)
{
t = 0;
LED0_TOGGLE();
}
}
}
该部分代码功能,先进行系统和外设初始化,然后一定时间就运行独立模式下的USBX系统任务。
57.4 下载验证
在代码编译成功之后,我们通过下载代码到Mini STM32H562开发板上,在USB配置成功后(注意:USB数据线,要插在USB_SLAVE端口!不是USB_UART端口!另外,USB_HOST接口不要插任何外设!),LCD显示效果如图57.4.1所示:

图57.4.1 USB声卡实验
此时,LED0闪烁,表示程序在运行中。电脑提示发现新硬件,并自动完成驱动安装。等USB配置成功后,可以在设备管理器→音频输入和输出里面看到多了设备,如图57.4.2所示:

图57.4.2 设备管理器找到STM32 USB声卡
然后,设置STM32 AUDIO20 STREAMING为电脑的默认播放设备,则电脑的所有声音都被切换到开发板输出,将耳机插入Mini STM32H562开发板的PHONE端口,即可听到来自电脑的声音,开发板板载喇叭也可以听到来自电脑的声音。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3107621/ ,如需转载,请注明出处,否则将追究法律责任。