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