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