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