完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
第五十七章 USB 声卡(Slave)实验 上一章我们向大家介绍了如何利用 STM32F4 的 USB 接口来做一个 USB 读卡器,本章我们 将利用 STM32F4 的 USB 来做一个声卡。本章分为如下几个部分: 57.1 USB 声卡简介 57.2 硬件设计 57.3 软件设计 57.4 下载验证 57.1 USB 声卡简介 ALIENTEK 探索者 STM32F4 开发板板载了一颗高性能 CODEC 芯片:WM8978,我们可 以利用 STM32F4 的 IIS,控制 WM8978 播放音乐,同样,如果结合 STM32F4 的 USB 功能,就 可以实现一个 USB 声卡。 同上一章一样,我们直接移植官方的 USB AUDIO 例程,官方例程路径:8,STM32 参考 资料→STM32 USB 学习资料→ STM32_USB-Host-Device_Lib_V2.1.0→Project→USB_De vice_Examples→AUDIO,该例程采用 USB 同步传输来传输音频数据流并且支持某些控制命令 (比如静音控制),例程仅支持 USB FS 模式(不支持 HS),同时例程不需要特殊的驱动支持, 大多数操作系统直接就可以识别。 57.2 硬件设计 本节实验功能简介:开机的时候先显示一些提示信息,之后开始 USB 配置,在配置成功 之后就可以在电脑上发现多出一个 USB 声卡。我们用 DS1 来指示 USB 是否连接成功,并在 液晶上显示 USB 连接状况,如果成功连接,我们可以将耳机插入开发板的 PHONE 端口(或 者喇叭接 P1(SPK)端子也行),听到来自电脑的音频信号。同样我们还是用 DS0 来指示程 序正在运行。 所要用到的硬件资源如下: 1) 指示灯 DS0 、DS1 2) 串口 3) TFTLCD 模块 4) USB SLAVE 接口 5) WM8978 这几个部分,在之前的实例中都已经介绍过了,我们在此就不多说了。这里再次提醒大家, P11 的连接,要通过跳线帽连接 PA11 和 D-以及 PA12 和 D+。 57.3 软件设计 本章,我们在第四十八章实验 (实验 43 )的基础上修改,先打开实验 43 的工程,在 HARDWARE 文件夹所在文件夹下新建一个 USB 的文件夹,同上一章一样,对照官方 AUDIO 例子,将相关文件拷贝到 USB 文件夹下。 然后,我们在工程里面去掉一些不必要的代码,并添加 USB 相关代码,最终得到如图 57.3.1 所示的工程: 图 57.3.1 USB 声卡工程截图 可以看到,USB 部分代码,同上一章的在结构上是一模一样的,只是.c 文件稍微有些变化。 同样,我们移植需要修改的代码,就是 USB_APP 里面的这四个.c 文件了。 其中 u***_bsp.c 和 u***d_usr.c 的代码,和上一章基本一样,可以用上一章的代码直接替换即 可正常使用。 u***_desc.c 代码,同上一章不一样,上一章描述符是大容量存储设备,本章变成了 USB 声 卡了,所以直接用 ST 官方的就行。 最后 stm324xg_u***_audio_codec.c,这里面的代码,是重点要修改的,该文件是配合 USB 声卡的 CS43L22 底层驱动相关代码,官方 STM32F4xG 的板子,用的是 CS43L22,而我们用的 是 WM8978,所以这里面代码要大改,修改后代码如下: u8 volume=0; //当前音量 vu8 audiostatus=0; //bit0:0,暂停播放;1,继续播放 vu8 i2splaybuf=0; //即将播放的音频帧缓冲编号 vu8 i2ssavebuf=0; //当前保存到的音频缓冲编号 #define AUDIO_BUF_NUM 100 //由于采用的是 USB 同步传输数据播放 //而 STM32 IIS 的速度和 USB 传送过来数据的速度存在差异,比如在 48Khz 下,实际 IIS 是 //低于 48Khz(47.991Khz)的,所以电脑送过来的数据流,会比 STM32 播放速度快,缓冲区写 //位置追上播放位置(i2ssavebuf==i2splaybuf)时,就会出现混叠.设置尽量大 //的 AUDIO_BUF_NUM 值,可以尽量减少混叠次数. u8 *i2***uf[AUDIO_BUF_NUM]; //音频缓冲帧,占用内存数=AUDIO_BUF_NUM*AUDIO_OUT_PACKET 字节 //音频数据 I2S DMA 传输回调函数 void audio_i2s_dma_callback(void) { if((i2splaybuf==i2ssavebuf)&&audiostatus==0) I2S_Play_Stop(); else { i2splaybuf++;//指向下一个 buf if(i2splaybuf>(AUDIO_BUF_NUM-1))i2splaybuf=0;//溢出 if(DMA1_Stream4->CR&(1<<19)) DMA_MemoryTargetConfig(DMA1_Stream4,(u32)i2***uf[i2splaybuf], DMA_Memory_0); else DMA_MemoryTargetConfig(DMA1_Stream4,(u32)i2***uf[i2splaybuf], DMA_Memory_1); } } //配置音频接口 //OutputDevice:输出设备选择,未用到. //Volume:音量大小,0~100 //AudioFreq:音频采样率 uint32_t EVAL_AUDIO_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq) { u16 t=0; for(t=0;t { i2***uf[t]=mymalloc(SRAMIN,AUDIO_OUT_PACKET); } if(i2***uf[AUDIO_BUF_NUM-1]==NULL) //内存申请失败 { printf("Malloc Error!rn"); for(t=0;t } I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low, I2S_DataFormat_16bextended); //飞利浦标准,主机发送,时钟低电平有效,16 位扩展帧长度 I2S2_SampleRate_Set(AudioFreq); //设置采样率 EVAL_AUDIO_VolumeCtl(Volume); //设置音量 I2S2_TX_DMA_Init(i2***uf[0],i2***uf[1],AUDIO_OUT_PACKET/2); i2s_tx_callback=audio_i2s_dma_callback; //回调函数指 wav_i2s_dma_callback I2S_Play_Start(); //开启 DMA printf("EVAL_AUDIO_Init:%d,%drn",Volume,AudioFreq); return 0; } //开始播放音频数据 //pBuffer:音频数据流首地址指针 //Size:数据流大小(单位:字节) uint32_t EVAL_AUDIO_Play(uint16_t* pBuffer, uint32_t Size) { printf("EVAL_AUDIO_Play:%x,%drn",pBuffer,Size); return 0; } //暂停/恢复音频流播放 //Cmd:0,暂停播放;1,恢复播放 //Addr:音频数据流缓存首地址 //Size:音频数据流大小(单位:harf word,也就是 2 个字节 //返回值:0,成功 // 其他,设置失败 uint32_t EVAL_AUDIO_PauseResume(uint32_t Cmd, uint32_t Addr, uint32_t Size) { u16 i;u8 *p=(u8*)Addr; if(Cmd==AUDIO_PAUSE) audiostatus=0; else { audiostatus=1; i2ssavebuf++; if(i2ssavebuf>(AUDIO_BUF_NUM-1))i2ssavebuf=0; for(i=0;i I2S_Play_Start();//开启 DMA } return 0; } //停止播放 //Option:控制参数,1/2,详见:CODEC_PDWN_HW 定义 //返回值:0,成功 // 其他,设置失败 uint32_t EVAL_AUDIO_Stop(uint32_t Option) { printf("EVAL_AUDIO_Stop:%drn",Option); audiostatus=0;return 0; } //音量设置 //Volume:0~100 //返回值:0,成功 // 其他,设置失败 uint32_t EVAL_AUDIO_VolumeCtl(uint8_t Volume) { volume=Volume; WM8978_HPvol_Set(volume*0.63,volume*0.63); WM8978_SPKvol_Set(volume*0.63); return 0; } //静音控制 //Cmd:0,正常 // 1,静音 //返回值:0,正常 // 其他,错误代码 uint32_t EVAL_AUDIO_Mute(uint32_t Cmd) { if(Cmd==AUDIO_MUTE_ON) { WM8978_HPvol_Set(0,0); WM8978_SPKvol_Set(0);} else { WM8978_HPvol_Set(volume*0.63,volume*0.63); WM8978_SPKvol_Set(volume*0.63); } return 0; } //播放音频数据流 //Addr:音频数据流缓存首地址 //Size:音频数据流大小(单位:harf word,也就是 2 个字节) void Audio_MAL_Play(uint32_t Addr, uint32_t Size) { u16 i; u8 t=i2ssavebuf; u8 *p=(u8*)Addr; u8 curplay=i2splaybuf; //当前正在播放的缓存帧编号 if(curplay)curplay--; else curplay=AUDIO_BUF_NUM-1; audiostatus=1; t++; if(t>(AUDIO_BUF_NUM-1))t=0; if(t==curplay) //写缓存碰上了当前正在播放的帧,跳到下一帧 { t++; if(t>(AUDIO_BUF_NUM-1))t=0; printf("bad position:%drn",t); } i2ssavebuf=t; for(i=0;i I2S_Play_Start(); //开启 DMA }这里特别说明一下,USB AUDIO 我们使用的是 USB 同步数据传输,音频采样率固定为: 48Khz(通过 USBD_AUDIO_FREQ 设置,在 u***d_conf.h 里面),这样,USB 传输过来的数据 都是 48Khz 的音频数据流,STM32F4 必须以同样的频率传输数据给 IIS,以同步播放音乐。 但是,STM32F4 我们采用的是内部 8M 时钟倍频后分频作为 IIS 时钟的,在使能主时钟 (MCK)输出的时候,只能以 47.991Khz 频率播放,稍微有点误差,这样,导致 USB 送过来 的数据,会比传输给 IIS 的数据快一点点,如果不做处理,就很容易产生数据混叠,产生噪音。 因此,我们这里提供了一个简单的解决办法:建立一个类似 FIFO 结构的缓冲数组,USB 传输过来的数据全部存放在这些数组里面,同时通过 IIS DMA 双缓冲机制,播放这些数组里面 的音频数据,当混叠发生时(USB 传过来的数据,赶上 IIS 播放的数据了),直接越过当前正在 播放的数组,继续保存。这样,虽然会导致一些数据丢失(混叠时),但是避免了混叠,保证了 良好的播放效果(听不到噪音),同时,数组个数越多,效果就越好(越不容易混叠)。 以上代码 AUDIO_BUF_NUM 就是我们定义的 FIFO 结构数组的大小,越大,效果越好, 这里我们定义成 100,每个数组的大小由音频采样率和位数决定,计算公式为: (USBD_AUDIO_FREQ * 2 * 2) /1000 单位为字节,其中 USBD_AUDIO_FREQ 即音频采样率:48Khz,这样,每个数组大小就 是 192 字节。100 个数组,我们总共用了 19200 字节。 audio_i2s_dma_callback 函数是 IIS 播放音频的回调函数,完成 IIS 数据流的发送,其他函 数则基本都是在 u***d_audio_out_if.c 里面被调用,这里就不再详细介绍了。 最后在 main.c 里面,我们修改 main 函数如下: USB_OTG_CORE_HANDLE USB_OTG_dev; extern vu8 bDeviceState; //USB 连接 情况 extern u8 volume; //音量(可通过按键设置) int main(void) { u8 key; u8 t=0;u8 Divece_STA=0XFF; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz delay_init(168); //初始化延时函数 uart_init(115200); //初始化 USART usmart_dev.init(84); //初始化 USMART LED_Init(); //初始化 LED KEY_Init(); //初始化 KEY LCD_Init(); //初始化 LCD SRAM_Init(); //初始化外部 SRAM W25QXX_Init(); //初始化 W25Q128 WM8978_Init(); //初始化 WM8978 WM8978_ADDA_Cfg(1,0); //开启 DAC WM8978_Input_Cfg(0,0,0); //关闭输入通道 WM8978_Output_Cfg(1,0); //开启 DAC 输出 my_mem_init(SRAMIN); //初始化内部内存池 my_mem_init(SRAMEX); //初始化外部内存池 my_mem_init(SRAMCCM); //初始化 CCM 内存池 POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(30,50,200,16,16,"Explorer STM32F4"); LCD_ShowString(30,70,200,16,16,"USB Sound Card TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2017/7/16"); LCD_ShowString(30,130,200,16,16,"KEY2:Vol- KEY0:vol+"); POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(30,160,200,16,16,"VOLUME:"); //音量显示 LCD_ShowxNum(30+56,160,DEFAULT_VOLUME,3,16,0X80);//显示音量 LCD_ShowString(30,180,200,16,16,"USB Connecting...");//提示正在建立连接 USBD_Init(&USB_OTG_dev,USB_OTG_FS_CORE_ID,&USR_desc,&AUDIO_cb, &USR_cb); while(1) { key=KEY_Scan(1);//支持连按 if(key) { if(key==KEY0_PRES) //KEY0 按下,音量增加 { volume++; if(volume>100)volume=100; }else if(key==KEY2_PRES)//KEY2 按下,音量减少 { if(volume)volume--; else volume=0; } EVAL_AUDIO_VolumeCtl(volume); LCD_ShowxNum(30+56,160,volume,3,16,0X80);//显示音量 delay_ms(20); } if(Divece_STA!=bDeviceState)//状态改变了 { if(bDeviceState==1) { LED1=0; LCD_ShowString(30,180,200,16,16,"USB Connected "); //提示 USB 连接已经建立 }else { LED1=1; LCD_ShowString(30,180,200,16,16,"USB DisConnected "); //提示 USB 连接失败 } Divece_STA=bDeviceState; } delay_ms(20); t++; if(t>10) { t=0; LED0=!LED0; } } }此部分代码比较简单,同上一章一样定义了 USB_OTG_dev 结构体,然后通过 USBD_Init 初始化 USB,不过本章实现的是 USB 声卡功能。本章我们保留了原例程(实验 43)的 USMART 部分,同样可以通过串口 1 设置 WM8978 相关参数。 其他部分我们就不详细介绍了,软件设计部分就为大家介绍到这里。 57.4 下载验证 在代码编译成功之后,我们通过下载代码到探索者 STM32F4 开发板上,在 USB 配置成功 后(注意:USB 数据线,要插在 USB_SLAVE 端口!不是 USB_232 端口!另外,USB_HOST 接口不要插任何外设!),LCD 显示效果如图 57.4.1 所示: 图 57.4.1 USB 连接成功 此时,电脑提示发现新硬件,并自动完成驱动安装,如图 57.4.2 所示: 图 57.4.2 电脑找到 ALIENTEK USB 声卡 等 USB 配置成功后,DS1 常亮,DS0 闪烁,并且在设备管理器→声音、视频和游戏控制器 里面看到多了 ALIENTEK STM32F407 USB AUDIO 设备,如图 57.4.3 所示: 图 57.4.3 设备管理器找到 ALIENTEK USB 声卡 此时,电脑的所有音频输出都被切换到 USB 声卡输出,将耳机插入探索者 STM32F4 开发 板的 PHONE 端口(或者接喇叭到 P1 端子(SPK)),即可听到来自电脑的声音。 通过按键 KEY0/KEY2 可以增大/减少音量,默认音量设置的是 65,大家可以自己调节(范 围:0~100)。 |
|
相关推荐
|
|
2162 浏览 1 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
1997 浏览 3 评论
4593 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
2140 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
2659 浏览 1 评论
浏览过的版块 |
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-24 13:57 , Processed in 0.678806 second(s), Total 71, Slave 53 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号