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