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