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