本帖最后由 aimejia 于 2018-5-23 14:32 编辑
7 B$ G d+ Y: w# h) `" s* f, C" l; k+ ^
1 前言
3 U! r/ ~) k5 R7 l& a$ X本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
( @$ X2 {2 N5 a( Z. P4 Y0 _; h& N- e2 ^* }- ?! `; t
2 设计构思# ?0 }0 {" |4 U+ t; S' \
所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。, G! m: X% H J7 l. Q
' w+ j! C V6 j" e6 Z* d
2.1 从原理框图开始
+ q1 N" J1 N% W& W& T- _8 M/ y
' t Q% q& G6 q/ D* U如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。; S6 s$ o3 g# j# w
& P& e; Y# G: y4 d# P+ f& U2.2 硬件支撑
/ b8 P8 l% Z7 h7 k, ]这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。
D7 m0 y. S: Y3 @* t* _, m' b& ~' U1 A! _' s
2.2.1 USB接口/ Z* v: T- D7 P' G
如下图为USB接口部分的电路:0 }( Q) l& f" M- I& v* @- p! G1 V/ c
, L9 v) X6 Y! L3 V+ o6 W( v这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。
# ~" J2 \6 a) v9 c" [& f# c: e3 e) i' K' a
2.2.2 Codec部分
) ~) V$ M7 f% Z$ k! o如下图所示:
\$ v2 w) S6 M2 C2 R& F% d7 d
; p( v: C% h/ \. [9 T, Z
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。' |4 M$ m: M% j& t+ ?
" e' ?" ]; z% U" v O1 b& C2.3 软件设计, I1 S: w% l! p j; u h
为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。
) J0 @- k- \, T9 \' h
5 }+ X( Q) S% Y( v% l3 e, K' u7 Q2 p2 h0 U' o1 {
如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。. M! _/ ?5 U4 c+ {' x
* y& `6 q$ y' V9 I0 {, T* L各个模块的工作流程如下设计:/ N; ^& o: A& M* Q! S3 m
# N" q6 | X0 K, H! r& `
初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
]" m& Z/ C& _) I: NUSB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
: E, c/ g2 I; X! L5 N+ Z7 D8 ~8 J# j' bUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。2 ?+ [, n; |( R1 E
从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。" H6 |* c. _* S7 M% M. ~" V# Z
; Z7 q4 ~$ `( m0 R; I
接下来,我们来看看软件层面上的实现。
7 G: w8 {4 P' i- m) R
" H9 d9 Z t }' u# b2 ]3 软件实现& e- L' S' c6 @; _
还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。
% ]$ ?6 D- m1 {8 u9 s# S0 z8 `. A O& a/ p
3.1 创建CubeMx工程
% e- W( o3 a) s6 x5 n3 x, n由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。6 M" r# p: h x9 f
# t9 `- \6 i, x* j
pinout:3 c* e) o! {# ^" _6 B3 z/ c
5 I* [0 X% `/ L. A: z# B$ c- R
外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:6 |4 C$ h) Y' S
, f* C% [ C3 f, {( E! E
! r! a9 ?% H6 H1 z7 N G, }
Clock configuration:9 T; b9 @4 S% n1 ^# t
6 ~+ Z) {! o6 q. ^2 o+ f时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。
- K& |& s! j; ^% g$ G5 x, R U8 x
Configuration:
' X) A+ T& b) Q7 b
2 h+ p$ f6 ^3 c+ a$ v' h2 F& F HAL层:+ g" L3 c4 y! o' v& Z5 f' `
Usb_FS:使用默认参数。
* a. r' K1 Q% `' ?3 O
! Y: w- o( g: n8 \. n' o/ aI2C:100K速率,7位地址宽度,使用默认参数。
4 \) O* Q* @5 r6 Z7 V8 g- j
: w, n3 k, m& ]3 ]+ i& s wI2S:主发模式,标准16位宽,默认音频为48K,如下图:" p9 j) e4 n) e- Y
( j1 T, l0 }! @0 b
并为I2S发送添加DMA,半字位宽:+ K, x$ {2 m$ C% a
7 z% L" |% G0 w' W# b+ P1 n
- Q& a# B- M6 ~: J% L+ X
MiddleWares:
) N9 Q( Z3 G! j5 M
. h5 @2 y2 d- g O/ a# pUSB选择Audiodevice class,其配置参数如下:1 O/ U$ D- X- |
, E: x* `6 a n0 _3 g$ W9 z* R( u8 ?( c1 R
这里都是默认参数。
$ \2 }4 h2 o, y" X" _; {: ]
, {0 h3 @- |# Y7 P
在描述符参数内得为usb audio class修改两个参数:
2 z+ i9 |4 O" w; N5 G1 Y: z
/ Z0 \9 x* g7 E" H0 j; f5 b1 @PID得修改为0x5730(否则windows驱动会加载出错)# A) b" k) [$ [ Z$ V) [+ x! N
序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。0 L* H) Y& Y9 s
最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:% r0 W1 b( B: h
! d% i' L( g& h0 u$ D如此就可以生成工程了,我们生成IAR工程。& a1 o; S/ G f0 \
/ k! l6 M& s( q0 r1 S
3.2 生成的IAR工程介绍
; v: a! N6 h. e4 {
9 D. ?' p+ R# s0 G9 ?如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。
+ r2 k. }1 }9 {% g# r1 e1 y0 m' M6 |* d3 Y2 S7 b
User目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。 T: _ z5 n# P
Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。( ?; I+ Q' p3 b, i/ {7 u
Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。2 d4 z4 i" x' i& p# Z
3 X+ K' C j L0 y, Y; Z3.3 开发
! i9 X6 [3 G% f8 Z" u- y2 R3.3.1 初次编译测试
+ L9 ?7 L& Y& e$ g首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
- u( L+ k/ H1 y- {( V) O: Y2 {
1 ~! q( L! R$ m* B这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
0 N# t7 C: h3 \
( |: Q- X" M+ m% M& n! g& t
这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.) o# A7 w* l8 x7 U- h
/ @& \1 P9 ]1 a1 i: m t/ [8 c$ z9 k
3.3.2 添加codec驱动和audio bsp模块5 P3 a0 [/ l5 c4 u& r/ X) {
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:' v# G: c# X# i/ r
! r8 G' H8 f3 H" `9 C8 ?. M5 ~其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。& J- b; I- J& W" Q. a/ w
! x8 |! c3 l; z3 n3.3.2.1 Codec与HAL的对接
$ ~8 I4 f/ P/ r% Q) F, \首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:5 r3 w& J3 @- r
- [cpp] view plain copy
5 [& u, p: b5 x# [% ? @) D5 }9 e - AUDIO_IO_Init() # G: [4 q' N& j' L* e' y$ G& q
- AUDIO_IO_DeInit() - ?' }+ @8 r( G2 w& z
- AUDIO_IO_Write()
) a. H$ D" o. a4 X - AUDIO_IO_Read()
复制代码 这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:7 ]/ a% _9 n) H: H0 X
- [cpp] view plain copy
0 j" N3 e. N4 a& u - //---------------------for c43l22 port--------------------------// % N' m( g. T2 h; x
- static void I2Cx_Error(uint8_t Addr)
( @7 ]' P) k1 o2 j+ g6 O! M - { 6 h& U6 ^; o6 e7 j
- /* De-initialize the IOE comunication BUS */
. Z/ |$ r* M2 y& i - HAL_I2C_DeInit(&hi2c1);
# m4 c- A5 u) v1 ?2 l7 t -
; P' k2 N+ z" s - /* Re-Initiaize the IOE comunication BUS */ : E/ @; {; R; H& l) M% n; R: ?3 H
- //I2Cx_Init(); 7 F8 }7 E# y# D1 U' ]5 {- i, R
- //MX_I2C1_Init();
: R# @# ]2 Y) C9 i, w - } / P' S2 q& f' e. K/ a# O* p' x
- static void CODEC_Reset(void)
" G& w* b- k# z - { 5 w) \4 y7 K6 ~
- HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);
R5 r4 I3 n8 c2 g- R4 L, H4 ? - HAL_Delay(5);
- O$ J3 u. y" [# N5 L% A - HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET); 7 H: \7 S; s+ R* s% Y; ?
- HAL_Delay(5); 2 X& X+ k* P1 [5 q3 G1 A
- } & V6 w# n1 C) w- G
- void AUDIO_IO_Init(void) 0 f1 g2 B) [' v' Z5 f, |
- {
; ]- L, G! F9 y ]) M3 E/ I - //I2Cx_Init(); u3 W, }3 u; \9 i1 N+ s# y5 r
- }
- h7 E& U' i+ K5 K3 _" U - void AUDIO_IO_DeInit(void) $ J7 ?5 n9 I; D
- { F% w$ C5 a9 e9 H5 h
-
0 y' V/ a% q; e - }
5 Y& J; M, D9 ]+ S - /** . f" b3 {$ K! P6 P) D; X
- * @brief Writes a single data.
- S" V& ]% q5 s$ f! g - * @param Addr: I2C address
9 J" j L, @! f% I4 M: \ - * @param Reg: Reg address
& [" X% ]" ^8 J& D - * @param Value: Data to be written
. o8 _2 p8 z8 ~: R! W - */ 4 Z9 [8 T1 i# D
- static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value) % e6 M: u1 T y! |2 U( j
- { ) I m3 o; [2 A8 a
- HAL_StatusTypeDef status = HAL_OK;
8 k8 m9 H4 C+ p$ A -
8 F; Q/ L c' a, o8 k3 q: `" z; r( q; I - status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT); 1 O* j9 o; \2 y$ b, N
-
7 R& a5 y) B2 v+ H - /* Check the communication status */
5 k" z# D. y/ M - if(status != HAL_OK) 6 x7 Q: ]8 M; A9 a+ m
- { 6 g" |$ S2 e) B! k
- /* I2C error occured */
$ r8 F$ P, E, |! j, I E. P - I2Cx_Error(Addr);
+ r: I: b/ w8 x/ V2 w1 C - } # O# V, h L$ m0 d0 k# |# }! Q
- } : _* _) }/ \' u5 H
- void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value) * d: _7 w2 z2 G
- { 2 p z4 B8 U: T3 f
- I2Cx_Write(Addr, Reg, Value);
. `' l3 K4 S- K* I- v - }
8 }. u) t! j+ [ -
: O; G/ b3 u' d- T& F- n7 I- _ - /**
# G. @* s: A) h) K# O8 E - * @brief Reads a single data.
2 e. i2 q" d. I* c3 y8 c! i4 d - * @param Addr: I2C address ! m1 }2 O; w& d3 }, N" d6 F, W
- * @param Reg: Reg address
% ]. o+ o5 W X0 h3 Q U G$ ?) b - * @retval Data to be read . @* H; ?5 q- F. m: l: V
- */
7 F) U, J1 y% p$ B' u) { - static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)
( R& n! J- e% c3 w - { 4 {+ N$ p0 X% X$ [, O7 ~3 w
- HAL_StatusTypeDef status = HAL_OK; . [( @7 \: i& n" s ]2 A( u
- uint8_t Value = 0; , }! W/ P0 ~9 M* U. g* z3 e
- ' ? j2 X+ m5 Z) D/ N
- status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);
+ x& s9 ~" V& j- O6 a - 6 I o* C: ]2 t6 r9 j& l/ D- B
- /* Check the communication status */ 1 ]- h7 m: M" K/ Q4 V
- if(status != HAL_OK)
$ u/ I2 S0 W3 Z6 G! n - {
) t2 x$ X5 F' B: R0 u0 M - /* Execute user timeout callback */ ! H1 p+ @9 S3 \4 ?- S, A
- I2Cx_Error(Addr); 3 Q4 j( o; s' k& }4 I) N E# `$ @
- } 2 r* U4 v1 g, B- w& h% ]
- * [# a+ H1 H/ C3 `3 y. i1 ?
- return Value; }. W2 I9 S% W; s ?
- }
: E% c+ D3 _. X; o3 F5 j6 k - uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)
* h- o: {) u% w0 ?7 }* ?7 v - {
, m7 \! ?* t( z' r1 o8 G3 a - return I2Cx_Read(Addr, Reg); 5 k- B6 K4 s% S
- }
复制代码 由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。
7 Y% X: U& u3 r/ h, \5 N. v& L% k( ^7 @0 c7 e1 S
就这样,就完成了Codec驱动与与HAL的对接。" y! K! u9 y8 ^" F. {7 @
4 m4 c8 m7 z# c( r* l8 ` o% V5 t3.3.2.2 usb audiointerface与codec的对接 w* j5 B& i+ }9 l: {2 ^
我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。
) s1 [0 |! \1 g% a$ {) {4 s& V0 S' L1 x
按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:
. T5 \: J0 i7 v( w+ ?" L- [cpp] view plain copy
: ?1 P$ p: }3 @) P$ F - static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options)
/ L2 r1 @+ L8 s/ _; Z, N - {
% U }$ c4 T# |# n: o9 Y - /* USER CODE BEGIN 0 */
9 ]' q' e" }1 M5 S* g. N5 u4 V - return (USBD_OK); ( R: D# Y, {! P$ u3 X4 s
- /* USER CODE END 0 */ 7 `; b5 q, b, C3 `& G
- }
复制代码 这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:" I" k& D& x, v) z W1 P
- [cpp] view plain copy" D+ f- F6 u/ d. H/ B* O
- static void I2Sx_Init(uint32_t AudioFreq) * L& b1 g6 c; B3 L0 m' Z
- {
. }+ A3 n! D) y" E: j/ V - /* Initialize the haudio_i2s Instance parameter */
6 ^3 K( L+ q7 H2 p - hi2s3.Instance = SPI3;
" \, [- q* h2 r$ q# w0 _: \ - " B& P3 t6 x' [% g1 J0 V8 U1 D1 ^
- /* Disable I2S block */ / y8 V' M0 k, @' q* ?% h( Q
- __HAL_I2S_DISABLE(&hi2s3);
! a' ]8 K+ e: M/ B, ^$ a! M7 D5 V# e -
. v3 S! ^3 Z7 j - hi2s3.Init.Mode = I2S_MODE_MASTER_TX;
9 s0 _% ~" R# x+ R8 ^! J8 T - hi2s3.Init.Standard = I2S_STANDARD; ; [( W! j0 r7 Q* ` n5 l& x
- hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
4 e% ^5 z1 L) B& P' a% U( i+ K - hi2s3.Init.AudioFreq = AudioFreq; + f0 i- c- W1 t: G1 Y. c) T2 a( w) h
- hi2s3.Init.CPOL = I2S_CPOL_LOW; . u, N4 V' J8 d3 o
- hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
" r' l3 P& b3 d. c Y# X - 9 f& l4 x( Q" ^: h) ^3 Q. l3 Z
- if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET) " S8 G$ M6 b( G" \0 n1 {
- { 5 L! _3 r( @5 Q" H& \/ \
- HAL_I2S_MspInit(&hi2s3); : G$ T0 u& e( P6 G
- } . N' A5 z+ |! ?8 M3 h, x
- /* Init the I2S */
# f# U1 S' m9 ], Y: A - HAL_I2S_Init(&hi2s3); " w1 W0 r. X, e2 q
- } ! @: N! J% p5 H, G$ B
- const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};
( D# h& \8 ~( A v: l - const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344}; ) y7 `1 L* O. H5 y* c
- const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};
! O4 Z7 s5 P v& m! [ - uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)
& V* b0 E Z; K0 ]7 T, k% F - { . o9 d' p. l! y) H5 e9 y
- uint32_t deviceid = 0x00; ' X9 l+ i7 w; v' x9 n2 ~+ S6 F, B
- uint8_t ret = AUDIO_ERROR; 8 L6 {" j6 `7 b7 ~! B
- uint8_t index = 0, freqindex = 0xFF;
! o0 k* c! S; h/ J - RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;
+ S( N1 Q. e( X/ M6 [& a; l% { - ( V9 m- n( S! w0 S8 M7 m
- //get the according P,N value and set into config,this is for audio clock provide 0 J: V7 r. j3 t1 n# g4 [ g
- for(index = 0; index < 8; index++)
1 C% O2 _! G" x7 l& o - {
$ w) |8 ` Z! x! q( d7 ^ - if(I2SFreq[index] == AudioFreq) " g* Z% B8 a- ?+ o
- {
: P3 @& F. s- v - freqindex = index; 2 r1 v$ I7 |) {9 I
- } ( d+ H' l) C: q5 G9 y9 r
- }
% {+ l% A; [% s8 `$ [ - HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct); + k0 ] F( Y! H- c
- if(freqindex != 0xFF)
6 p+ V) C9 I* `/ C7 p- n; \3 M( p - {
( E( I% f7 X' k3 |- z - RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
3 x& E3 L2 a/ B+ \, a - RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex]; ; \: S0 }9 b$ {6 f9 w9 g
- RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex]; 7 k! m' ^1 ~- p
- HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);
) g, z& `, Z5 P. b s - } 7 e* c) g3 A. Z6 J; D G# f8 d' d
- else
; y2 m4 e1 ~2 o - { . E3 D0 K& @' r7 ?9 k+ N) Y
- RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S; . f7 v" a# a/ [% y' ^. Q
- RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258; 6 N4 A9 B2 [+ d4 L% \; S7 k
- RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3; * f+ g1 t5 c, S y. }6 e
- HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct); ) O$ n. ] F" S* Z) y
- } 0 b3 I- t+ F0 s+ u, F
-
" Z: L6 `5 f( R" t - //reset the Codec register # a9 s4 t& ]1 g9 b7 H
- CODEC_Reset(); 1 c8 ], c/ {" [- l/ g
- deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);
5 v% T( g! t" u - if((deviceid & CS43L22_ID_MASK) == CS43L22_ID) 9 ~3 J) ^$ ~6 c8 g: z
- {
2 k' {* ?- T0 a. p: f: ] - /* Initialize the audio driver structure */ : a$ d) y/ h: K F+ c2 C. _
- audio_drv = &cs43l22_drv; 4 m: n" _8 q. a; C; ]4 s
- ret = AUDIO_OK; 1 G, s2 q9 R2 l) Y4 V5 N9 N! e
- } 3 @8 a6 {/ i L6 @4 p. ]
- else ' p& X, e1 C* j; g/ L% U( @+ j
- { - N3 J! }3 y% h# w
- ret = AUDIO_ERROR;
* T) }4 b" d- Q4 `; j9 G - }
& T2 O# K& n! S/ j+ X -
% O/ X; c4 T* @( u5 S - if(ret == AUDIO_OK) ! J u+ Y. L0 I- k$ d. j
- {
0 a' S6 O+ J% \7 s; s - audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);
5 t- m5 Y3 j3 V - /* I2S data transfer preparation:
1 g( W! s' N( x- z7 x/ A - Prepare the Media to be used for the audio transfer from memory to I2S peripheral */
/ w; i5 K! T7 {1 L K# I3 z - /* Configure the I2S peripheral */ 9 R) X/ l$ w: F" V- e- i! ?- B
- I2Sx_Init(AudioFreq); , i* q* I0 v1 }4 d
- } ; M( c( v5 { `
- return AUDIO_OK; 9 v6 J% M. w! D( k# m
- }
复制代码 在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。
; t3 V6 k' F( P# G7 K7 o/ D. o0 T! I/ q7 o* n z' d
这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:* N3 C4 {" J0 Z% g2 `! E6 t9 E
+ e! t P7 b& A8 P4 \. O6 P" g# \ a1 k1 l- r* s" f3 V/ x: B# m' T
在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
7 `( b5 `8 S# X9 h% }- r! a5 S
" j1 J6 Y/ U5 ]+ c也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。
" M; x" a& K0 E8 L# h& u& A0 j6 e) \* U- L+ U
PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
! k- e: O2 l8 s, C+ H8 d3 C+ x0 }, `9 H
搞懂了这些之后,我们马上将其代码进行对接:5 }% A* n2 S: b& C( C# l
- [cpp] view plain copy/ Y8 R i, m5 W. {6 G9 l) t4 @
- static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options) ! z2 `3 Y/ l* u; ]$ y
- {
, l, u2 R# Y$ R6 C - /* USER CODE BEGIN 0 */
$ _6 C2 M) Q5 U( _& h6 H - BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq); * B: G+ n6 o# a2 T, s
- return (USBD_OK); ( ^4 B* {6 ?( v; b2 V# q
- /* USER CODE END 0 */
/ z3 L1 D% s9 k/ T: ]4 X - }
复制代码 接下来下一个需要对接的接口:% {' G( ]2 _+ Q
- [cpp] view plain copy2 `, V }: `1 z: z
- static int8_t AUDIO_DeInit_FS(uint32_t options)
+ l2 A* {, D1 x- [$ f1 Q/ D8 G+ `3 Y# o2 w - { $ p, j: M3 D! ]8 c: M
- /* USER CODE BEGIN 1 */
/ a% m" b8 _' R+ d" _! b/ [ - BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW); 5 o) l3 Q0 D/ {$ A: E
- return (USBD_OK);
' Z" D) g' U) {' E$ e7 @. E - /* USER CODE END 1 */ ' q3 K1 D9 T$ G, o% d
- }
# r6 Y3 r( }# F - 很明显,这个个反初始化的接口,它的具体实现如下:
6 c' x1 d X* z
- M# d3 Y$ O0 I% _3 R- P) W# b- [cpp] view plain copy& u- E, K, A2 r* f$ Y8 m
- uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)
: l( V! X5 G( g E; a5 u - { , D) D( j ~, a t) v
- /* Call the Media layer stop function */
) e v- g3 M; T2 _1 C - HAL_I2S_DMAStop(&hi2s3);
) h2 U0 I# v \& t8 Z8 f! D/ O - 2 D1 t6 L1 l+ J- |0 U! ^: o* P
- /* Call Audio Codec Stop function */ * P8 S3 w8 J2 c6 s) b% o6 D6 ~0 G3 P
- if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)
0 Y+ p3 K' k3 S4 Y' Y: b - { / w( d# z1 {+ }% m0 u$ y' U' m
- return AUDIO_ERROR;
& n s7 h2 K1 P+ S. S- H - } ; s7 c5 [- @6 v8 C, T& j& i0 T# C
- else
k }& i7 N( @# h( }+ c - { R _2 H$ d% Y7 D
- if(Option == CODEC_PDWN_HW)
0 i+ s+ a# ?3 u5 R. o7 @( h - { % ~' p% v) x# C' t/ Z$ v' [2 N
- /* Wait at least 1ms */
j. Z7 p* ]; J. `" C - HAL_Delay(1); : \: j2 L; \8 R5 T2 m2 T
-
1 G/ ~% E5 `- p6 m# ^7 M) G - /* Reset the pin */
4 P+ C" D- m* i( s. Q. x - //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);
; G) t' \/ R2 `% q! t - HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);
: o. l% R6 \. I8 ^3 ^7 n - }
' V% W( N1 R0 t; A( u7 t -
+ ^2 }4 y( x$ {2 B' K' w: { - /* Return AUDIO_OK when all operations are correctly done */
3 [& g* P2 ^, O5 O- F4 V - return AUDIO_OK; $ d( L$ m4 w3 {9 m9 }! N" ]
- }
! `+ j0 ^5 R! b - }
复制代码 先关闭I2S的DMA,在调用Codec的停止接口。
" r$ y: \5 _) B# w$ S! DOK,下一个:
2 O$ q* E' y- {- [cpp] view plain copy
. `' V3 [6 n& a3 [ - static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)
5 f' u2 j5 f. p" t3 ?, f& [ - { ) k) Z. ?# D) p' D; c* E6 }$ Z
- /* USER CODE BEGIN 2 */ 2 m* H! `! E- K
- switch(cmd) ) S+ ^9 z7 v/ f
- { . C- ^6 s3 O8 y. S
- case AUDIO_CMD_START:
* M7 j( \* X6 x+ ^ - BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size); & R- |$ l/ m% G T. C
- break; $ v* Q+ r* f# n
- 5 Y7 y: C2 i- W% v/ w/ w4 p4 X. j
- case AUDIO_CMD_PLAY:
7 e( K L" P& K- \7 p - BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size); . ], {' o/ {6 q( a5 [2 V
- break;
0 o v9 ^2 j; B - }
: t% x& d* P) r; ^; A6 i - return (USBD_OK); 4 B U% M8 j. i1 f6 O* A
- /* USER CODE END 2 */ 4 m8 o0 X7 }% I5 V( ^+ q
- 7 S r; I- Z2 U4 j2 y& k
- }
复制代码 第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:4 i/ B/ M2 L5 ]0 M6 w
- [cpp] view plain copy. e6 e7 A5 d1 r5 h
- uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)
0 `4 b; m2 W4 q1 W$ R# J5 a, U - {
! a3 d j r; c2 S& M+ P- Y' `, y - /* Call the audio Codec Play function */ 7 F2 W9 h# l2 l4 o5 a f3 h5 I" b
- if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)
7 C9 \. m, i: n+ U1 l# X$ s - {
2 q: Z7 p+ Q5 y0 g# g - return AUDIO_ERROR; ( e' f0 [8 l" _. @, m
- }
5 ~! j. W) `6 P: C: J1 L: f - else
: Z# E, }0 d: w3 B - {
. f2 D2 Y9 _+ z - /* Update the Media layer and enable it for play */ 7 A; Q) U8 E) E% `$ ?- @; ]
- HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE)); : P$ L3 a3 d9 @: O) O% s Y$ E
- return AUDIO_OK;
. v4 I0 d- ^8 {/ @7 Z - }
7 m8 D) a* I; ?6 R - }
复制代码 很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。 l5 c$ h% C5 M
# R+ t# G: j; {* r) e- c) s然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
a5 l) B6 e- |, O7 Y2 \$ L; |$ P- [cpp] view plain copy
n6 n1 ?7 M+ h7 f - void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)
! d7 P5 e8 Y% I. u+ s - {
; S9 z, C8 U9 c8 p9 C3 `4 _ - HAL_I2S_Transmit_DMA(&hi2s3, pData, Size); + e9 G# q( X0 g" y8 P! A! l' G4 W
- }
复制代码 也是通过I2S的DMA将数据传输给外部Codec。
V) Z6 A8 J; J8 o6 S3 O6 b. g1 B6 E6 Q2 i4 u! f; O& F- O4 F. Z
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:; O. @ x/ ~0 N6 @& A: Z3 x
- [cpp] view plain copy
5 g5 l9 E' w# x - void TransferComplete_CallBack_FS(void) 7 a5 f% e* S) u% _; l% [
- { z1 g. e% C; b- F7 o9 T k4 H& P
- /* USER CODE BEGIN 7 */
! m5 v6 n- n6 ]! I } - USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL); / O- y3 ?! l5 q% x2 D
- /* USER CODE END 7 */
* C6 O, G) `" `. z - } 6 O. Q$ U3 x, N$ M
- void HalfTransfer_CallBack_FS(void)
# U \- A8 z6 o - { 1 {8 w) z2 ~# v+ Q# y
- /* USER CODE BEGIN 8 */ " c: L1 k' Y, m. Q, l' D* `
- USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);
{$ J, C* `0 }' W - /* USER CODE END 8 */
( ]9 |# S8 H4 r" d# R4 Q% S - }
复制代码 此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。
; U7 l( ~' ~% H' x7 |' j! G# `) y; i) Z1 f
此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。6 _- p$ C; h3 y( x& M5 D
) Q N, [! t0 B2 `接下来看下一个usbd_audio_if接口函数对接:
. c4 E% ^! I5 e- |- [cpp] view plain copy4 x$ v H8 O R4 B0 t2 P# q
- static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)
& B; H) O5 H& Z( P ?$ T - { ; C9 y% M$ s, y) R* L
- /* USER CODE BEGIN 3 */
' c2 I) B; O, `1 K" X - BSP_AUDIO_OUT_SetVolume(vol);
7 F* E# {; x9 W - return (USBD_OK);
* }# Q* [$ U( p9 n* ` - /* USER CODE END 3 */ 7 I1 r+ M7 P/ D* \7 l
- }
复制代码 很明显,这个是音量控制接口,也对接下:
7 S# R# O' `& l% v+ F7 U- \: u- [cpp] view plain copy8 }! f8 }% N" E) x; P
- uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume) : r! d4 L+ m6 P0 S g! @ T
- {
$ I! G$ }8 N7 W! L9 _ - /* Call the codec volume control function with converted volume value */ 1 r5 g! ^$ R, `. d/ P. r
- if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)
/ j8 y' E5 N% [, S, N+ A - { 1 q4 i2 C) F @6 c4 {
- return AUDIO_ERROR;
) o% r) P! Y2 O4 r ]; C - } z3 ^4 m* ^8 b
- else : d1 K( m. w4 j H2 w
- {
1 X! l) i( h1 v" L7 e' f - /* Return AUDIO_OK when all operations are correctly done */
& d T! R3 @( n# E; A" V - return AUDIO_OK;
, q4 ], V6 d) x5 F2 m4 @ - } 1 l7 |, S W; ?
- }
复制代码 直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。7 k" d. W( w0 Q' b
v0 w4 O" j/ e& U4 m5 x
下一个:
9 L4 I) p# w* U2 Q- [cpp] view plain copy* b0 p, p4 G$ I
- static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)
) Z" ?2 D, q+ r1 H- ^$ c! u& E) v4 ` - { 1 @' g' f! C, w( n1 z
- /* USER CODE BEGIN 4 */
8 f. ^: j9 ]) H$ z" x+ E - BSP_AUDIO_OUT_SetMute(cmd);
$ l9 }+ ^) ^ W! | h+ _3 V# M/ l; E q0 R - return (USBD_OK);
; T$ }; x4 m) K1 j) F2 m$ r. i- e - /* USER CODE END 4 */
- ?0 \: q3 S( q8 U" Q - }
复制代码 静音控制,其实现为:
% L( _/ m# b- `( Y- [cpp] view plain copy' S" `' o7 t1 V/ }4 A2 w/ F
- uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd) # N4 l+ K! _! S' Z) ^4 a
- { % \% Y) t$ @. e- D
- /* Call the Codec Mute function */ 2 \/ k% X! J0 j. W
- if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0) ( g5 k5 I" u7 j; K$ g+ Q/ T
- {
1 x7 C3 @9 E1 a6 b1 i8 T: @7 E; Q - return AUDIO_ERROR;
, m' q& X) i5 F - }
. @5 {, o9 y& S3 } - else + _. w5 A% h( E& ?+ X
- {
, ~/ w/ A* Z" G9 C$ G4 V - /* Return AUDIO_OK when all operations are correctly done */
! M$ i; \, X+ C D - return AUDIO_OK; ( g! t7 m. T0 |1 |+ ?) A; V
- }
1 `- K& f# j+ P) Y - } 7 J/ q) b% W' ~( q
复制代码 很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。
$ [# Z8 A/ E( e( K# i) ]' c2 [0 ?+ ~
OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。
$ k' h4 D6 ?0 G/ S
' A, k& H5 r( b+ d# r: v4 测试验证
. j1 ^2 w% n3 Z3 X! f$ p4 U将代码编译后烧录进STM32F4DISCVOERY板进行验证。. f0 B- ~) B0 ?; G0 o8 Z
% ]. n: N7 H K" a: o, m0 W1 B最终验证是OK的,可以从耳机上听到PC端播放的音乐。
' B" V1 l6 ?3 m5 ]
' L7 A' m5 y& A+ D, ^6 s# n" A( D5 结束语9 o2 x/ K4 F+ K" h
在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。2 D; D+ x, C! }" |; B& k( C! V3 v
% t4 e3 w% P1 R3 x4 i
I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。
$ [+ H5 b/ H7 ? k& Z, N; ^$ G# N9 G! f( @# f+ ]! C q
在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。% L+ u9 m0 M3 t* H
' C' N) @! N6 d9 H; h. ?
; |% W3 b) ~& P- T: V; E7 ]
6 k9 l; l7 H& j( E3 {& Z0 R
" Z. }( F# z8 w9 ~9 `# k |
static uint8_t USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,
uint8_t epnum) h# R& V8 n! ~, f
{# c% k" X) x C* B; }- I' }# }
USBD_AUDIO_HandleTypeDef *haudio;
haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;# d" W1 j, O0 |5 r
) T9 i" T0 |- e+ I+ h
if (epnum == AUDIO_OUT_EP)2 R6 y" U: T Z: r. M" R' k
{
/* Increment the Buffer pointer or roll it back when all buffers are full */
2 W: _2 V+ h% d2 R$ S+ T8 T X
haudio->wr_ptr += AUDIO_OUT_PACKET;
if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
{
/* All buffers are full: roll back */
haudio->wr_ptr = 0U;1 Q; _% `2 b! ?) u
((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
AUDIO_TOTAL_BUF_SIZE / 2U,, S: j) D/ ` F/ X. W& {
AUDIO_CMD_PLAY);2 R2 R. h7 g% N* Z6 T C
#if 0
if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
{* }) D# G: b E: x$ @; j0 Y$ ~. \/ F
((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],8 d# z. p! \5 h+ d; i% W
AUDIO_TOTAL_BUF_SIZE / 2U,& d( l4 g: i+ N& V
AUDIO_CMD_START);
haudio->offset = AUDIO_OFFSET_NONE;" j V4 ]& K. X7 }8 o) h# v
}. ]) Q- t) S' R) t1 D* y. q
#endif1 [' p$ s1 }3 X4 f' L! ~
}
& e L5 u( s4 w m6 b6 E' \3 j" ?
if(haudio->rd_enable == 0U)1 s5 }7 S' k& g
{$ V' M7 w2 B, e* g
if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))0 c, Q4 a6 }& ~9 | X& } h8 {6 C
{4 s6 a0 F& ~% L6 W
haudio->rd_enable = 1U;
}
}
% w% P: M; K, Y; ~5 j& Q: I
/* Prepare Out endpoint to receive next audio packet */' W3 { _/ S- q) t
USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],
AUDIO_OUT_PACKET); |* q0 v. o' z) P, G
}
我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。* U; M( Q5 U; Z: F! ~
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,
但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,7 k0 v% J% g/ D- G
而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
希望能有帮助
希望能有帮助
非常好的帖子,谢谢分享!!!
8 \9 r8 P. d7 D* F0 `0 D; X
驱å¨å®è£ 失败
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
, f" K6 |2 s/ R
该设备无法启动。 (代码 10)
) B$ f5 t1 Q4 {
I/O 请求已取消。
经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!