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