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