你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 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
1.png
  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
2.png
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
3.png
% 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 4.png ) 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 5.png ( ^8 \- b4 X; f6 _

8 q, k/ }1 s) yClock configuration:
7 Y, N! X7 Q: P5 O% y 6.png 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 7.png ; s( O0 Z0 h+ r9 L* i1 \- P
并为I2S发送添加DMA,半字位宽:
$ y8 G+ n9 d0 M 8.png
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 9.png   }: b4 v! X& h$ F( {& {4 |# E2 I
2 i- S3 B  }1 s" p9 J) z
这里都是默认参数。# S- u) F+ q) A9 V# p
10.png
" 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 11.png ' [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 12.png ; 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 13.png , E9 {* g: _" C, B$ _
这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:( h6 L9 w, x# ?8 ]9 |
14.png 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 15.png
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
  1. [cpp] view plain copy
    - F$ ?. X, O4 c% u- r* J
  2. AUDIO_IO_Init()  
    2 K+ V4 r% B- e4 R# ^
  3. AUDIO_IO_DeInit()  
    1 ~$ G3 ~0 h) d1 H, g$ |9 ]: y% @
  4. AUDIO_IO_Write()  
    0 }9 q: Q( y% Z: k! W, I
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:
: {  S. Q0 [2 e- @4 A) _$ a1 ~  }& `
  1. [cpp] view plain copy
    9 u( Y8 `2 y2 y# i
  2. //---------------------for c43l22 port--------------------------//  $ }, {( ~3 O  ?- m5 n& |2 {' d1 u: I
  3. static void I2Cx_Error(uint8_t Addr)  
    : {6 ]/ z" z/ x4 o3 E
  4. {  ; |" F* G7 K$ a4 L
  5.   /* De-initialize the IOE comunication BUS */  
    " S& X. m3 F# H: }
  6.   HAL_I2C_DeInit(&hi2c1);  
      h5 R% m% j! X
  7.   
    & i* W0 |0 f1 B, c1 X, e5 y
  8.   /* Re-Initiaize the IOE comunication BUS */  7 n- c$ z# x3 y; y& F8 W
  9.   //I2Cx_Init();  
    6 a) ], {) O* o0 c8 i) d
  10.   //MX_I2C1_Init();  
    : Q$ Y3 V- ~. k% I* w) L! O3 ?
  11. }  
    * Y# f7 n# p! K, ^: y7 m
  12. static void CODEC_Reset(void)  1 W4 G* \5 M. W/ w6 @  Z& q) b; ~
  13. {  ; k) {0 [. c. O8 [
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  " r3 r1 y* I/ j' ~$ u1 l
  15.     HAL_Delay(5);  
    9 a0 _. t3 b/ s( g! b. d  X
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  , S  G8 s5 K+ t/ r* C
  17.     HAL_Delay(5);  
    5 d. ]2 i8 {( A6 g
  18. }  
    ! A4 T0 [+ d$ \8 n! g, ?- q$ z
  19. void AUDIO_IO_Init(void)  ) K2 u8 Q7 L9 |! H1 S- ]2 b0 u4 k
  20. {  
    2 U% T; u7 X  K5 i
  21.   //I2Cx_Init();  7 i6 s3 E1 J1 E$ F
  22. }  
    # {9 |; P4 f6 X1 k
  23. void AUDIO_IO_DeInit(void)  5 d6 ]4 P3 q7 h% t
  24. {  
    / ^: P9 C& T- S- F& `
  25.     V6 q; J- b- g! B; H/ F0 i& U5 ]
  26. }  
    5 @5 v. C+ [, l" ?9 b3 f+ A
  27. /** 7 B1 L; f4 G. p8 G3 K8 x6 J
  28.   * @brief  Writes a single data. . }) ]/ C" }. m  L' z3 N  ?+ C
  29.   * @param  Addr: I2C address
    ) E% l% b+ K4 w! r0 F7 f) h
  30.   * @param  Reg: Reg address
    5 v2 l) h  T: A
  31.   * @param  Value: Data to be written
    ; V, P1 N$ P) d( b
  32.   */  ' C% d% D  u1 V% [( x
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  ; z  [4 t; P* e" W2 z7 \
  34. {  9 O: y/ A- B8 w+ }5 \5 ?
  35.   HAL_StatusTypeDef status = HAL_OK;  1 z, ]/ R. U, r" n. x/ C, Z
  36.   
    5 p0 |+ \# Z/ g& w4 |3 N
  37.   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
  38.   
    " y/ @% U2 W2 b, Y! _: h
  39.   /* Check the communication status */  0 A" C+ M; G: c8 }) F
  40.   if(status != HAL_OK)  
    , w4 S% P) [  `- j* b) w
  41.   {  
    & l' x) B5 h$ m* J! J! b
  42.     /* I2C error occured */    F4 F3 t+ i, Q& b) G  d$ d
  43.     I2Cx_Error(Addr);  ) G( ?5 l2 l  J& R2 Z
  44.   }  
    # A; t: ^2 }" J2 a; E. e
  45. }  + U0 C' i& d0 l6 u" B# E$ O
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    ) r( {% |" {4 E9 _$ b4 N& G
  47. {  - X1 ^9 H4 `" W
  48.   I2Cx_Write(Addr, Reg, Value);  
    ) E8 _( F" F3 F6 `- N9 }4 ~
  49. }  
    , X; D/ j; S+ L& S
  50.   # b! X/ W' l- }% P  C
  51. /** ) r8 w) S, W2 C+ D
  52.   * @brief  Reads a single data.
    + q: O/ q) W" j
  53.   * @param  Addr: I2C address
    ; s3 w' p/ i/ y9 w
  54.   * @param  Reg: Reg address . k$ V  o: q2 [/ A9 g" T3 n( b
  55.   * @retval Data to be read
    7 e* d4 p0 Z& l. |+ h
  56.   */  
    - W8 v  B# H8 r( W
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  
    3 a) ]/ q5 e: n! a3 K! J5 z: W
  58. {  
    3 R( R, q' n+ L9 t% d
  59.   HAL_StatusTypeDef status = HAL_OK;  , Y  N5 C5 S& c4 l7 b, T
  60.   uint8_t Value = 0;  3 g% C! K; L  l  Z# R& \9 p
  61.   
    7 p8 B. G  C$ T$ E& t4 @" Q5 i
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  5 X' a3 h1 c0 f
  63.   ! r4 \% e# `5 a7 N) y% W
  64.   /* Check the communication status */  + K6 o& w% @; b, Y. W) T, P
  65.   if(status != HAL_OK)  
    ; t2 N$ @* P. p
  66.   {  
    4 F4 U$ g% N- m4 S# ^: w2 f
  67.     /* Execute user timeout callback */  
    & T7 X$ P" d% J( T3 d  |
  68.     I2Cx_Error(Addr);  
    % ^/ l5 R6 S8 [7 q# K3 U
  69.   }  : k' y0 A) P/ Z/ R
  70.     f* @/ |$ W/ G- b0 `9 @8 N
  71.   return Value;  3 n% v5 r) C+ V0 `% {( z- L
  72. }  
    $ J8 H% _) e7 J% I# U" A4 s3 ]
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    * ]" a* r3 Z; S' B0 m, U7 G
  74. {  
    ' r7 Z+ U5 c" ~8 F* u: l) A
  75.   return I2Cx_Read(Addr, Reg);  : n& p) W1 C4 A7 @, s  d
  76. }  
复制代码
由于在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
  1. [cpp] view plain copy! p( u! A4 g9 J! L: u, `  b: K- C0 d
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  2 F7 `% B! ^+ A3 ~$ x# c
  3. {   " T9 ~3 o6 ~5 m+ A3 E
  4.   /* USER CODE BEGIN 0 */  9 p' F1 J6 D  N- [9 i1 o
  5.   return (USBD_OK);  8 X  y, j9 V  \: @) @1 E; @
  6.   /* USER CODE END 0 */  ( @: p7 R% e7 {' h! O8 p
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:
# p/ v! r/ N9 `2 Z. e
  1. [cpp] view plain copy
    ! d8 i# O1 n+ ]& C! a* j% l1 L" Z
  2. static void I2Sx_Init(uint32_t AudioFreq)  ! n- S3 h& ?' q' W( q2 d  x
  3. {  : L* Q" P. c. |/ B
  4.   /* Initialize the haudio_i2s Instance parameter */  : a/ F7 v: G9 O' g3 f% n
  5.   hi2s3.Instance = SPI3;  0 k& O0 A1 P  l( `
  6.   
    ' e0 e' |5 p  |) ^- O1 t7 u
  7. /* Disable I2S block */  * H9 O$ b3 K' O  |% z6 j( E
  8.   __HAL_I2S_DISABLE(&hi2s3);  , f, u9 [- t: n3 Y  P9 ~1 t8 P
  9.   0 R, S0 P% G5 {# A, G5 j7 s/ c
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  9 T# }1 K0 m$ d9 V8 _8 V
  11.   hi2s3.Init.Standard = I2S_STANDARD;  4 ^' x4 u( P2 K# b$ e) u
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    ' `' k7 O& X/ d
  13.   hi2s3.Init.AudioFreq = AudioFreq;  * Z& t0 ~/ y: s0 W$ E
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  / x# y* h+ b  r% V
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  ; z+ f7 ?1 k3 G% ^/ ^5 e* u; p
  16.   ( @" ~  r! T! q
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  $ X% P3 \0 s0 Q
  18.   {  
    ) T) l! g8 A* M5 L3 T$ n# ?$ t+ v
  19.     HAL_I2S_MspInit(&hi2s3);  
    . x/ \8 c: U3 V; A; t
  20.   }  
    % C  }" l1 B- g' ~
  21.   /* Init the I2S */  
    . \+ ^; f/ R4 h
  22.   HAL_I2S_Init(&hi2s3);  
    # ?8 S2 b! N- p2 x0 O% y9 B" B
  23. }  : F8 d" `% K8 I& o1 z
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  
    # ^! T8 N% p# [6 X* T+ p0 [
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  8 ]3 d2 W0 h, }" |, m% T
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  
    ( C0 {  V) k% q# o
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  
    6 w/ d( w2 b2 K' [( X$ q. G
  28. {  
    ; K- M8 |6 q1 R) R- S: V+ k
  29.     uint32_t deviceid = 0x00;  8 y. Z9 ~4 ^6 E1 Z' c
  30.     uint8_t ret = AUDIO_ERROR;  5 ?# T! Y+ t3 @- A$ ~6 I3 D! V
  31.     uint8_t index = 0, freqindex = 0xFF;  5 K; i+ q2 Q7 w- N
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  
    7 j! {6 i/ I, ]
  33.   + c4 j: `- x5 \. \
  34.     //get the according P,N value and set into config,this is for audio clock provide  
    + H; d7 ?8 y2 ]" i" k# Z
  35.     for(index = 0; index < 8; index++)  / I. V4 J. l$ F7 K
  36.     {  
    ; A! P! [& S7 }; O
  37.         if(I2SFreq[index] == AudioFreq)  
    2 @7 t0 W( t5 k. i6 S& e
  38.         {  
    $ Y& ^& s9 K0 t4 f7 r' q
  39.             freqindex = index;  
    5 I. e0 ]1 T/ Z; i6 n
  40.         }  7 N/ p- s  M: |) c. C
  41.     }  
    8 K8 a% e4 q$ D1 b
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  + [1 U* ]& p: R: h: b# N! ?
  43.     if(freqindex != 0xFF)  5 U3 ]# w7 L% M, Q% h1 j
  44.     {  4 [) ~! J0 Y9 u8 G
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  ' Y7 ]; e; d' {1 d9 C# e  Y! J
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  9 Y5 R/ M2 m6 m
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  " ^/ K" d' n6 L
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    2 s* `" n% z+ M
  49.     }  
    9 @8 T* i, F3 Z1 N# y; L9 K
  50.     else  
    + Q3 R$ C& {* k1 w$ `; J; \
  51.     {  
    7 P" N$ v- L3 ?' y) ?
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    & ^5 C) H3 ?( t2 r: P; h( ]& s7 N
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  ' a' [, X  ~: B
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  
    ! Z5 u5 J( H! z' \) i- w- C
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  ) ]5 @- S4 p8 a
  56.     }  
    $ b$ S' H, B2 e; _
  57.   
    1 j  C1 ~& |/ u6 R& Q
  58.     //reset the Codec register  
    1 f! y2 O2 v4 {  G# t
  59.     CODEC_Reset();    f) r0 b8 u" ?3 {3 w, G
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  
    7 x) o( C4 a8 y
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  
    " t4 a3 f, \" A0 A& K4 |
  62.       {  
    8 }) P0 {3 h/ v" j
  63.         /* Initialize the audio driver structure */  6 F: |, Y( J9 p( h, f
  64.         audio_drv = &cs43l22_drv;  # c  y4 @. P! `6 m; d4 i
  65.         ret = AUDIO_OK;  / k# y+ N/ p  u, F) y: P
  66.       }  
    % t5 a6 t- O( [  w8 S/ L7 y4 K
  67.       else  : X; M" E  G* c1 _
  68.       {  
    : d8 D  y& g& c7 |
  69.         ret = AUDIO_ERROR;  8 K( p4 s9 `8 k+ V" K
  70.       }  7 u" M6 f. c  C; H
  71.   
    . X! P. W! F) \# u1 R8 s2 `2 h& ?+ D
  72.      if(ret == AUDIO_OK)  
    1 k/ f2 W8 T8 Q9 d7 V  e
  73.       {  
    # z1 c$ @- p. D; J  @
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  . d) ]0 S3 j% p* O& t* K
  75.         /* I2S data transfer preparation:
    , ^% O" B( u5 }9 R5 P5 W) J
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  
    4 _8 R9 g3 W2 X, i6 L
  77.         /* Configure the I2S peripheral */  # A+ e5 [7 i2 H0 W2 s% {! L
  78.         I2Sx_Init(AudioFreq);  
    & q0 K+ q8 W4 W% _# ?; q
  79.       }    q3 r8 H4 ~% E
  80.     return AUDIO_OK;  
    " Z9 J% L4 V% {
  81. }  
复制代码
在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 @
16.png , 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: {
17.png " 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
  1. [cpp] view plain copy
    ' H) b$ P. G+ t, m2 N2 C( H7 G
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
    7 h4 M* i' n% ]7 E2 ?' s
  3. {   / e# J# U- u, i
  4.   /* USER CODE BEGIN 0 */  
    & I  z8 X3 M! ^" r' s+ o4 T/ ?
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  
    ) o' b& H3 k% D; `( s
  6.   return (USBD_OK);  9 Y2 w1 k$ \1 H
  7.   /* USER CODE END 0 */  
    9 D3 q9 \8 c9 r* r4 i# f9 P2 ?( ?
  8. }  
复制代码
接下来下一个需要对接的接口:
0 J3 g( _  ]4 k4 b
  1. [cpp] view plain copy
    ; k9 q2 i2 t  U8 C9 t/ u
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  2 P; I! B/ D  h6 ]* d( K1 C
  3. {  5 V: Q; a  ~: z: M. Z
  4.   /* USER CODE BEGIN 1 */   
    * Z: e2 t4 n- y  v+ n+ q+ h
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  & F: X- X: ^4 g
  6.   return (USBD_OK);  . B; W8 J% y8 O. Q4 [4 U
  7.   /* USER CODE END 1 */  
    7 A* H' H# C, p9 a$ x2 n
  8. }  
    4 B* Z* |& C2 X" P: Q
  9. 很明显,这个个反初始化的接口,它的具体实现如下:! q2 s4 F4 H4 f* \+ g( V* i9 Z, m6 e/ F

  10. 2 ]4 m8 \; j: J
  11. [cpp] view plain copy4 y: ]' }- K/ t3 ~
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  ; z4 j. I1 M0 N- |% e; s
  13. {    Z+ v' M' a; H, Z
  14.   /* Call the Media layer stop function */  
    / `; \# x6 `1 q6 ]
  15.   HAL_I2S_DMAStop(&hi2s3);  
    ' }4 b9 m7 k8 n/ f  q8 w$ N' r; h9 z
  16.   
    . K2 v6 C  p  M% n  }! }; k1 d( H
  17.   /* Call Audio Codec Stop function */  " U) y! [6 P/ X! K
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  ! L; a; ^* h6 M
  19.   {  3 i( L4 `. N' F6 T9 |% K
  20.     return AUDIO_ERROR;  
    4 C3 P9 }7 ]! m  J- s& B; t  J
  21.   }  
    6 t9 J4 q! i. e! ?0 l, e0 D
  22.   else  ) f, l/ C; @, S
  23.   {  
    / u4 s) N/ V: u: Q; m3 p$ k
  24.     if(Option == CODEC_PDWN_HW)  
    0 P0 [. \! G5 {  B0 J+ b/ W
  25.     {  , L( w3 A1 i. I+ b4 Z! C. Y4 |' U! k) m
  26.       /* Wait at least 1ms */  
    1 P! v0 ~7 F6 q# s. S
  27.       HAL_Delay(1);  + U- k6 w& W4 W* Q5 o' }
  28.   / o/ D& Q, }5 y9 a, ~& [4 v
  29.       /* Reset the pin */  ( b) o% R" |- `8 r& M. o
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  9 Z2 C+ \8 I( H% R% C5 Y
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  ) a: E9 u: K0 t6 }) p9 b. z$ g
  32.     }  : p' g  ~6 k8 a6 j) @9 V; J/ g) }7 b
  33.   9 P1 C! x4 Z6 Q  L; R( `
  34.     /* Return AUDIO_OK when all operations are correctly done */  * `# N5 [. \4 a7 y
  35.     return AUDIO_OK;  
    ) A! H8 [$ Z& H8 ]. A) i- A$ q
  36.   }  
    4 d+ q% z  J$ S9 C/ K4 F8 h
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。
& b) K7 j" @2 i8 Y* M) w; EOK,下一个:
/ ^6 q& z: J1 F" C3 U
  1. [cpp] view plain copy
    ( l) C7 `) G0 x& V' A& d
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  % q0 y, J+ ]) T" x
  3. {  
    ; q" E* o" H2 ^
  4.   /* USER CODE BEGIN 2 */  
    - c2 ^! {1 x/ S1 K( f4 b; H
  5.   switch(cmd)  
    8 G# ^% o6 H# F* M5 B, D; W: X
  6.   {  ) g0 J" H" c7 L3 r. F! _% A
  7.     case AUDIO_CMD_START:  6 c. M( |3 X0 E8 I1 Y
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  7 y9 l. x# c- k2 j
  9.     break;  ' M1 r5 \9 `. L
  10.   
    4 Y. W8 ~2 ?) M5 S- C9 h: Y& K
  11.     case AUDIO_CMD_PLAY:  * f' ?! n  h1 A: s2 H5 X
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  7 ^: U/ a0 [& J( X+ ]) Z) z
  13.     break;  
    . |8 X! P1 V$ j4 \0 L6 W
  14.   }  
    6 l  s1 Z4 p8 }" C8 D9 I
  15.   return (USBD_OK);  
    4 q. V: c) V$ u0 X4 O& |( b
  16.   /* USER CODE END 2 */  
    2 x, u. [5 }$ Q$ u
  17.     ( m" D; {/ h! U, V5 S5 B. [
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:
/ }) _6 e5 n* L, l, [
  1. [cpp] view plain copy
    5 ^8 a+ `) P) q2 o& X, ]( t
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  % ~) j# b+ a  }* f- M; B4 R0 F6 ]
  3. {  
    7 \2 A" w% ^8 F8 U( C& @
  4.   /* Call the audio Codec Play function */  . g# l7 ~4 O. a1 d# O0 ?
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  & V) a+ \5 R8 o, y9 M: m+ y
  6.   {  
    $ \8 ~& x6 R6 H  z5 @! f  c
  7.     return AUDIO_ERROR;  " E. y; E: v, M# R: Y+ A
  8.   }  - @! j- [$ ]) |: s4 n9 }% ^
  9.   else  - a" D: i, A0 u! l1 i5 G
  10.   {  
    # @* H. S. c0 U0 i- B
  11.     /* Update the Media layer and enable it for play */  $ R' q! K  {- {7 b( u$ v4 y
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  
    ( a2 n+ o8 K, N( v7 K3 g
  13.     return AUDIO_OK;  3 z* z2 t3 b9 G% z* n, C" n$ W& \
  14.   }  ( ~! S) ^) F' {2 i' B
  15. }  
复制代码
很明显,它是调用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
  1. [cpp] view plain copy
    7 |$ f+ u$ m- d* i
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  3 Y$ U7 V: Q) {: M  U. K+ B
  3. {  
    / s/ U1 P8 H; s" v- }% p
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  
    4 G- @" R! U3 M
  5. }  
复制代码
也是通过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
  1. [cpp] view plain copy5 n+ i4 K. {0 P8 W' z' K9 p% S
  2. void TransferComplete_CallBack_FS(void)  
    & {- r( r$ I- k
  3. {  
    . C# Z3 ]; T" J: v- ^
  4.   /* USER CODE BEGIN 7 */   
    ) j3 O# S/ {- G- T& Z& N1 \: k8 {# U
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  
    5 h3 W( Y3 h( V5 g/ ~3 ~8 |
  6.   /* USER CODE END 7 */  
    & t2 ?0 r6 n4 a$ B' ^! f7 W/ ?
  7. }  
    * t; }  G$ k7 @
  8. void HalfTransfer_CallBack_FS(void)  
    7 ^: v' o( K1 Z+ m2 X
  9. {   8 V1 E5 H6 @6 k% r7 ]. d" E
  10.   /* USER CODE BEGIN 8 */   
    ( l+ G  p2 `5 l& V8 l
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  0 u& v# y5 p+ m& j, X7 T9 B
  12.   /* USER CODE END 8 */  ) d) z+ x# R! z# V" M8 Y
  13. }  
复制代码
此代码为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
  1. [cpp] view plain copy, m! D) I9 t1 [  Y2 I- L) a  {
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  
    - m7 a4 v0 ]1 W9 I6 t3 G8 @8 |+ D
  3. {  * B7 R' V# }& U( P7 L4 G9 y
  4.   /* USER CODE BEGIN 3 */   6 W- }: A# b. |6 \
  5.   BSP_AUDIO_OUT_SetVolume(vol);  
    ( w' j- v4 ?! D" K
  6.   return (USBD_OK);  
    9 Y9 o( L9 N' u) d: G- V8 `- S
  7.   /* USER CODE END 3 */  ! [$ S" G" S& l: S* P7 r" W
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:
' c6 g" T9 q6 X) V5 \8 y; F
  1. [cpp] view plain copy
    * A) Z9 Q% ~% g6 x2 f
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  
    ! Y8 G( M7 W# V! N8 A
  3. {  $ @+ ]& c$ r( w* d+ k- z
  4.   /* Call the codec volume control function with converted volume value */  . ^+ K6 T* s. b2 l! X3 Y  I% s
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  + F+ D* m3 U! I- c3 G
  6.   {  
    7 g% b/ n- A0 n5 {' _. G: X8 K6 w
  7.     return AUDIO_ERROR;  
    + k5 e  L- |# B; `+ W
  8.   }    y* R" e8 Z* N9 u6 v' K0 T
  9.   else  
    2 }. _! @) p+ _' Q0 D9 |
  10.   {  9 G- p9 r! Y# @6 l8 ]
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    ! ~3 D% }7 A4 E' t) c' J
  12.     return AUDIO_OK;  9 Q, e  m' f: L5 E
  13.   }  
    $ K6 K/ ?* o4 S/ o$ Y
  14. }  
复制代码
直接调用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
  1. [cpp] view plain copy  _2 k! E9 l/ o- P$ E
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  
    7 \0 \5 l4 |% b! s
  3. {  
    / O( @5 }" [/ L% B
  4.   /* USER CODE BEGIN 4 */   
    8 e" n/ s1 e# T3 w% }
  5.   BSP_AUDIO_OUT_SetMute(cmd);  : g4 Y- I$ V0 e+ E4 n; _
  6.   return (USBD_OK);  
    9 Q3 G# t: p! h8 o6 T
  7.   /* USER CODE END 4 */  : h. d# A6 T1 A, l: P; J) l$ S
  8. }  
复制代码
静音控制,其实现为:0 s8 n, M$ r3 i1 b0 o# w6 H
  1. [cpp] view plain copy2 M. u( p5 s( {$ I6 e, y
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  ; Z: n; ^8 [* f, H9 I2 T" N& M* L
  3. {  
    9 J. P0 k9 S( P) q, t
  4.   /* Call the Codec Mute function */  + O# z# E0 j) R/ ~3 N/ [/ r9 a" \
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  / Y4 @& n' a- E* p, c
  6.   {  
    " P. O# j3 W) p. `/ l3 a
  7.     return AUDIO_ERROR;  
    4 a0 Y; J( ^! h# I. p/ D
  8.   }  ' }: N9 s/ o/ A: E, n% s
  9.   else  
    . h5 ]: \& g) I
  10.   {  
    & ~" ?! o: ^! d# J4 Q+ n
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    . g# T3 Y: q" g  Q
  12.     return AUDIO_OK;  5 R, E- O  y+ u% {' S
  13.   }  
    , X) l3 J, d3 _3 N. B
  14. }  
    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 18.png
# 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

评分

参与人数 1 ST金币 +16 收起 理由
wofei1314 + 16 很给力!

查看全部评分

赞 1 收藏 5 评论21 发布时间:2018-5-23 14:28

举报

21个回答
wxdjss 回答时间:2019-6-11 15:01:09
楼主问下,你这个会有杂音吗?我这边测试了下,会有杂音的,如果根据接受AUDIO_TOTAL_BUF_SIZE大小去传输,usb中断影响HAL_I2S_Transmit_DMA(&hi2s1, (uint16_t *)pbuf, (size))传输结果,显示为hal_busy,忙等待,传输数据这块你有做特殊处理吗?, [. b/ s* A' Y! j+ d1 I7 y3 h/ ?
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;
3 f8 F3 p% W8 Y4 [' {0 ?) W  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;5 s# }/ o) D2 m0 S* d4 a

3 Y" y" ~, O8 H! _  if (epnum == AUDIO_OUT_EP)
- U" d4 }% m1 U$ A; U$ _  {& {: }! R8 q& B6 ?8 D
    /* Increment the Buffer pointer or roll it back when all buffers are full */. [, o* c" l0 ?5 m+ l

' S5 u9 t; G$ T( _8 x: u    haudio->wr_ptr += AUDIO_OUT_PACKET;, e4 I: L- l8 y/ J

! }) d4 s8 g( M1 E. L    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
# z/ l3 k9 D9 i7 J    {) t' [8 r0 T# J
      /* All buffers are full: roll back */
2 u. s- i3 [) _& ~; W      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,
  t( B, r. y0 Y( _                                                 AUDIO_CMD_PLAY);
2 ]9 S# e7 [3 B5 m: v6 }' H#if 0- h# Q0 q+ m) K4 @. G) [4 A5 N
      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
- B+ L2 W$ ^; \4 Z' m* \% }      {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;
* {  }2 m' u" i) M9 N0 j+ p, {      }
7 Y/ b0 |. E; p9 {0 w) ^#endif+ \; p2 f7 ~& N
    }
, f, O* N5 a0 t" i( l
3 o; V: n! d! y+ t    if(haudio->rd_enable == 0U)
: c  V3 H8 |3 @6 A$ R5 o0 K    {* }# K7 c$ }: j. p% H- J7 x
      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))
9 h3 J' L7 C% J/ u4 d! ]      {0 `) e( e' P) {& l3 ?$ w
        haudio->rd_enable = 1U;: U: Q: e. i2 l  L$ m/ t+ y/ T
      }
( k% `8 }0 I4 B" z& j5 D* _! J    }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
  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?  g- N. ^% S5 S& z
我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,
  U: l( _2 G: H! o# U% _! S我先用 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" 设备,
1 J' e- C) _; a( i$ G( B( }- l0 V但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,
1 N& t( C& F1 m) X7 M& \而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20  I. W* o& {% m  r5 E4 B
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示9 t2 n5 [8 {! E: Y) W! ]$ T  {. U
( Q: U; v3 k  c. {9 ^2 P
该设备无法启动。 (代码 10)
. o2 z4 n% E! x/ _
出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
" n0 I. G$ d! x( E; t# c' X非常好的帖子,谢谢分享!!!
& `1 o& `( |7 ^! W9 N! ?& V
希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:453 o. P' T9 W. O' ]
非常好的帖子,谢谢分享!!!

8 K* B% V$ z6 G# Z- c希望能有帮助
listenmaxwell 回答时间:2018-9-26 23:00:58
非常好的帖子,楼主可以发一份工程的代码吗?非常感谢,18056453597@qq.com
wofei1314 回答时间:2018-9-27 09:24:26
666,好贴,顶起来~
Kevin_G 回答时间:2019-7-20 17:40:01
收藏
ccg12138 回答时间:2019-7-21 15:19:43

. i* a, L8 I9 \8 z* O: c$ M& p非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
: R2 |9 |; e( ^
' s8 V1 A8 i2 i/ D  Z/ m

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:18# w, j( u( `, r' C* }1 c5 s3 B* X6 Y
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
6 H( H" x' S# i. v, Z% g) g# O9 T
& d5 M6 l$ ~2 x  v. K3 T ...

& H- y5 d# [7 Y+ }4 a2 Y7 y这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
9 g7 g5 H$ {; d" B0 @# W7 h# T
1 m0 {  C: Z' _' o8 C! o该设备无法启动。 (代码 10)9 m5 x% O) y: k9 k- Y4 c

; N0 p+ E7 q4 Y! ]4 fI/O 请求已取消。
8 V) F9 _9 t* t7 `
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20
- @4 V) y4 c1 H" L9 E这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
7 J; k) _& E& @, ]' c+ t9 l+ M/ t; A6 i. q
该设备无法启动。 (代码 10)

+ |' r% R4 I; J3 L6 f; G经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版