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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑 $ Y: @: K# X- L* L" s8 p* Z& G" U
) T( D* M2 Y1 A8 c! q
1 前言
  ]" s8 U% J3 c& n. I本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
- O+ u" b9 t) e8 A( v& ^
( s7 R" z, d* ?  H- @2 设计构思* W3 k* k6 F# C% ^
所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。
1 m3 p1 V  ~$ d" K% z
, H' U6 D- Y/ ?2.1 从原理框图开始
; B: I0 Q6 [: e2 @. C) [ 1.png
. Q1 {# e! d' d' |1 b7 A6 [如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。
+ P+ q8 b' R2 }' l& y
* F- v' a3 @# Z; q0 a$ f2.2 硬件支撑8 ]) {' H7 g. D) g
这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。9 j) v+ x/ n1 s8 N3 V" V

4 H3 ?. b$ V4 ?, n  T! [+ w2.2.1 USB接口
9 X" m, M: O/ u+ ?: Z, i3 b如下图为USB接口部分的电路:, L& C$ n5 D1 Y9 i5 s* N
2.png
8 I$ M' r4 n6 w1 F5 q! a这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。4 t+ r/ t  }+ M* r  e" h

: g# e6 S3 k. c- k; g7 O, M5 e2.2.2 Codec部分% x% f7 f; J, I% S& i  `8 _+ _. X5 W
如下图所示:2 J0 k6 _: J, g
3.png
, V4 M" X8 a. ~. t如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。" h/ w" c5 e7 r1 }
  e4 Y3 j; a# i! Y
2.3 软件设计; C5 z5 f, s, e  d
为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。
: I& |9 y) X  z& T: R! C- W 4.png
7 q3 O! Z8 W; U9 ]7 Q
3 ^8 j% B1 f+ i' D. H如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。$ v) b% A3 m. z) @  U

8 ^/ V$ B+ n. K7 u$ V* H- }各个模块的工作流程如下设计:* g% _% t8 X+ m
- [& B  O. S$ Q* |
初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
2 \' V! K: O" `! tUSB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
' J2 w. C9 D& D+ @+ ZUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。
7 Z5 V) q7 E1 B3 s6 v% [从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。
' c" `' G, W0 u8 x6 `: B( Y4 d: y, z3 V6 i3 s& V' d$ h9 r
接下来,我们来看看软件层面上的实现。9 g4 I! J) u/ F3 B$ v% L/ [

# ~9 G& b; M2 |* K3 e1 E3 软件实现$ g* B# k3 z! E# M* a! w' D# {
还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。
3 h$ f3 r2 g- Q4 V3 c/ [! K) H9 S( C- ?# u6 S9 G3 t
3.1 创建CubeMx工程
* w, `/ [! l! i; Y0 x0 b由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。2 C* J0 L( u  U& }8 v
3 F- F  a% i/ T3 |% C8 m4 v
pinout:# s# D/ C; K0 Q( Z' P9 z

5 d1 _4 ^, g% z9 [# p外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:
6 B( F6 k% `* X  W. j 5.png
: g' a$ [! ]) {$ x* q3 u/ i* r) ^& x- i5 n
Clock configuration:
1 F: {( v3 ?" a& |9 R 6.png - z* z* u# B+ F' U
时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。( r3 s# ?! t# s

! Q  k1 W& z; H- u0 I3 n& W. bConfiguration:
8 i; }/ ?1 N; E, h& a" K" w' i
' [4 C1 U5 [7 q1 I& M  ~  HAL层:3 I8 W# n3 C( s$ B, k. ~$ I
Usb_FS:使用默认参数。; R7 G& `$ o4 i2 A0 |' M4 T8 P' ?9 e

- q1 t0 D, C, g) Q! j& B! DI2C:100K速率,7位地址宽度,使用默认参数。# O$ W, j' |) D) ^

+ T; j8 @: H% a! E6 j2 N# }( `I2S:主发模式,标准16位宽,默认音频为48K,如下图:
" a( n  i  q( A1 n 7.png : g+ i6 r7 T" A
并为I2S发送添加DMA,半字位宽:
9 x$ f! M: ]" J; c0 \" I; H0 T 8.png ' x, T& g, m  j: w7 s' R

! W' ^8 B/ w2 [, n7 D( X: u& O7 S9 s+ m   MiddleWares:
5 P. t; ?" Q" j
. U7 z) u( F1 C: ]* _4 Z. JUSB选择Audiodevice class,其配置参数如下:8 Q" a% g1 R; ^/ Y& F: {& t3 R
9.png : V2 e" l, G, \0 h
4 v, j6 e  z2 b  q! y& ^# Z, l4 k9 @
这里都是默认参数。
/ C) S" t6 D  w6 d" a# f 10.png
9 c" B. @6 J  W, M) a在描述符参数内得为usb audio class修改两个参数:4 y: ~5 S' T" Y

4 k& l+ T: t" l7 _. yPID得修改为0x5730(否则windows驱动会加载出错)
- q3 r, W! O6 q0 C/ D, Y" e 序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。* l; ~/ ]" Y6 T* j: m: ^% M
最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:
6 s9 r' z1 d. n: S* x 11.png
: ^0 i3 x( `5 D! @1 K9 B; f+ L! u3 G如此就可以生成工程了,我们生成IAR工程。5 h" n! o! [6 R6 Q) j3 L9 W
! {8 o9 N; L" O/ [7 ?
3.2 生成的IAR工程介绍
8 N- ]0 S  h) f8 y* C 12.png
& A& V$ x8 M/ O$ I5 Q& {如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。
3 L0 H4 e4 Y7 S* i' R# B
- o9 X. ^8 h" O$ uUser目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。0 M9 X7 a6 {! R7 s$ g. p' x  `
Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。
4 _% `+ m' f* z  C" [Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。
* x( r* A  n$ {% J1 n9 T8 j, d
1 S4 Z4 Y1 }: x+ b  H3.3 开发
: V  A5 U6 w# q5 i0 `3.3.1 初次编译测试' L, n1 Z0 W" ~' `. h! [+ {
首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
! D: H# [8 b, g/ }, C# c% x% H4 z 13.png
7 K& Y! Y  `- h这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
. F  P2 _: ]: c: s: ?- r: | 14.png 3 J( a3 v3 a8 T8 Z; I
这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.% t- L8 @7 ~+ ^* W
4 e1 r# C# w3 l7 F
3.3.2 添加codec驱动和audio bsp模块- F* U8 ]! l& @  w
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:
- A' p2 l/ i* c( h 15.png
9 }, f/ K# y2 }3 C) ]其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。  Q, ?3 C% H. E; J. \; y
3 S" ~6 e1 e3 O6 P
3.3.2.1 Codec与HAL的对接( n6 ]# l; Z( O9 z# k
首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:
( B6 Z, S" b  K, K
  1. [cpp] view plain copy
    / ]1 a* p  u& X# w+ R
  2. AUDIO_IO_Init()  $ f5 V( m# V; G# t' x7 \- P
  3. AUDIO_IO_DeInit()  
    - G  b( g/ M, p% h
  4. AUDIO_IO_Write()  
    0 T9 K- R; P( E$ _  j8 x3 T
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:$ f9 e( F+ l8 V; J& R) p
  1. [cpp] view plain copy8 W7 u1 F: {+ a2 ^% [( Y
  2. //---------------------for c43l22 port--------------------------//  
    & q+ H2 ^1 s/ y
  3. static void I2Cx_Error(uint8_t Addr)  ) J& @! b1 ~' o/ V, V9 l
  4. {  - W) @: b/ {& E+ W4 |% X) t
  5.   /* De-initialize the IOE comunication BUS */  % e2 I  _; H. d7 C0 A5 E
  6.   HAL_I2C_DeInit(&hi2c1);  
    ! J/ p! R/ G7 L3 t1 X2 V+ A7 [7 U
  7.   1 W) M) g: a) c3 A' L- `
  8.   /* Re-Initiaize the IOE comunication BUS */  
    . {, b* x6 s3 [/ f* u0 u/ E. j
  9.   //I2Cx_Init();  9 e) H8 T0 k: o  ~* U
  10.   //MX_I2C1_Init();  
    & i( C5 _1 G# Q+ j& M, P0 y% ?( ?
  11. }  ! U$ Y/ L# b. n: u# y: ]0 t, w
  12. static void CODEC_Reset(void)  
    0 b  \5 z" J5 \' I2 ^+ S0 ]
  13. {  
    " Z& [3 S1 |. o7 G
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    ! K; }. q. E# M& z" w( n( ]7 o) V
  15.     HAL_Delay(5);  . \& \! B+ F! }& z
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  
    ' @/ W* w& e3 q$ |8 U* `
  17.     HAL_Delay(5);  
    % [% f) h& @% ]
  18. }  0 U0 \1 C3 V/ K# M/ _5 \! q' ^! x
  19. void AUDIO_IO_Init(void)  
    2 i& d6 e# o7 ^9 t2 P. H5 Q  }
  20. {  
    2 u* q/ ]8 c- @
  21.   //I2Cx_Init();  $ d. U+ {) S* M, ~/ Q# N- }# Q
  22. }  % |! O" g' R" [
  23. void AUDIO_IO_DeInit(void)  
    / ?+ x: h( [) @- A6 v6 @
  24. {  
    . T3 ~3 ]. t/ C9 E+ z; z+ x! @" u
  25.   2 `4 p* x7 s# b) y: K
  26. }  ' L5 Z% z. w9 I! E; `
  27. /**
    * V4 \2 f/ F. Z  Q0 h
  28.   * @brief  Writes a single data.
    # f9 f$ p9 _) X7 G
  29.   * @param  Addr: I2C address
    , U% l; A2 B" N+ B0 [1 \% `" C
  30.   * @param  Reg: Reg address & g) {" D, M) `) ?7 s  ?6 u- c
  31.   * @param  Value: Data to be written . \7 P. }3 d2 r
  32.   */  5 q, D# D" k' e! w' I" n
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    : J3 P$ f1 x$ S) V
  34. {  
    & E  o) O7 w+ a0 ]8 a
  35.   HAL_StatusTypeDef status = HAL_OK;  ! j: f2 \2 f) W% v9 [- k4 ?- C+ l
  36.   
    3 X6 f, J0 |' V; n) S
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  + {' [7 ^# t" [' N! ]
  38.   9 [% W8 d1 [8 {  G; e
  39.   /* Check the communication status */    J/ l' X2 V5 z
  40.   if(status != HAL_OK)  
      k6 r4 n5 K* T
  41.   {  , C: x$ ~7 a- Y
  42.     /* I2C error occured */  
    , W$ R; @# v6 ]  h' V
  43.     I2Cx_Error(Addr);  
      |4 w# }7 v* Q5 F+ }8 f4 Y$ z) d
  44.   }  
    5 h& M; g0 {* U
  45. }  
    $ n0 |+ L# w8 ~- x
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    / B* _3 P" y( t
  47. {  
    & ]8 D6 z! W& u3 U  H1 e3 d) f
  48.   I2Cx_Write(Addr, Reg, Value);  
    5 Z6 b8 v* j  P3 L2 G' ~& ?
  49. }  
    . t4 M. o! Y( R% e
  50.   
    % R0 v1 X& \( i2 _+ }
  51. /**
    ! b  m" O' O; s2 r- G) M
  52.   * @brief  Reads a single data.
    ( A' ?' {: G% p: o- I
  53.   * @param  Addr: I2C address , u( f& W" T8 ?# H# q* a0 l( C6 \
  54.   * @param  Reg: Reg address 9 O9 b) u2 R4 F% o
  55.   * @retval Data to be read 4 `) a& `2 b  y4 l" J
  56.   */  
    . }) q; ?9 y# R& C5 w* n* L
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  ! _7 ~" P8 B$ K: v* ~6 X
  58. {  
      ]; I. V- ?, B9 c% w2 q
  59.   HAL_StatusTypeDef status = HAL_OK;  ! |! f% i- z) j% j8 {# Y
  60.   uint8_t Value = 0;  9 @! @3 j& d/ T8 {
  61.   5 U6 k& T# b' U% o
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    ; X6 n! o+ C! {/ P- D) Z/ g& w, `
  63.   . }9 J, X6 p3 d7 A% r# s2 }
  64.   /* Check the communication status */  
    , D$ e# L( i4 b2 Y& y, @
  65.   if(status != HAL_OK)  
    * \, ]6 l% O& n6 @* J; k
  66.   {  ' f, o# t! M& ]2 S4 H- q/ d/ I
  67.     /* Execute user timeout callback */  ! y, M3 r- z$ S. A3 D: b
  68.     I2Cx_Error(Addr);  + f( i" f- m; s
  69.   }  
    # i" k" c% |9 ^0 h" d: b  b7 I, t
  70.   
    0 }3 t- ?2 {: K% R9 J8 V% L3 f. |5 p
  71.   return Value;  
    7 p% v" h% a1 ?4 m1 l
  72. }  3 I- f3 I' r/ D& T' B0 E% a2 v
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    8 k. a  m) I4 k8 a  l
  74. {  
    , H+ f) U% A1 h7 G1 `
  75.   return I2Cx_Read(Addr, Reg);  
    2 `6 B4 M8 {. D
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。0 ^. y/ m7 n7 K
* u2 A+ r" w3 r
就这样,就完成了Codec驱动与与HAL的对接。4 P1 F8 L) O7 Y/ l. Y0 E6 b& b0 P

/ W1 H* `9 J; C" I; Y3.3.2.2 usb audiointerface与codec的对接
& q& s! Z4 S; @+ F) V我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。
3 q) K8 S, O; G2 e: L1 l! a/ e+ U8 l  H
按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:
4 q5 J& t8 J! c
  1. [cpp] view plain copy2 I# f+ Z( W8 {
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)    W9 A8 q) }8 A! U2 [) \
  3. {   ( z( ]* \2 q, Q2 ?+ c
  4.   /* USER CODE BEGIN 0 */  7 ~- _! l# `; c
  5.   return (USBD_OK);  8 r& a5 e, X9 q7 A) R
  6.   /* USER CODE END 0 */  % C. S" B: y" V5 p7 w" J: U
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:2 n; n$ z$ `" L$ r& S
  1. [cpp] view plain copy
    7 P8 z" Y3 {3 k/ Q% }- F
  2. static void I2Sx_Init(uint32_t AudioFreq)  
    6 N  T/ D0 @7 j3 N
  3. {  
    & ]! Q7 O9 |) C& J( z. t
  4.   /* Initialize the haudio_i2s Instance parameter */  + k( N! g, ]& |+ {& H- E. m
  5.   hi2s3.Instance = SPI3;  
    * _( n; n$ `' w- D2 v0 s7 E( T% D
  6.   & M$ P0 B! Y# q4 Q& ]! @
  7. /* Disable I2S block */  & ^. i7 }9 [- A- E
  8.   __HAL_I2S_DISABLE(&hi2s3);  
    , w+ _% v( y( v" u3 C
  9.   . M2 _  }/ E: e8 T
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  # q! M% i) u5 s, @; C( _
  11.   hi2s3.Init.Standard = I2S_STANDARD;  
    # n" z9 ~0 z7 X# h
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    5 Q5 g8 n* y8 |1 D# P7 J
  13.   hi2s3.Init.AudioFreq = AudioFreq;  ) c) u# }' [$ Q3 Q9 {' J8 o/ r
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  
    & v9 e* V2 s: @5 S2 j' C
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  ) n4 V& f; j" u/ v& K
  16.   
    ( n! X7 Z% C8 ~* Q
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  
    : Q( Q4 D. d2 O
  18.   {  # \4 q( t" a" D* N: Z5 P' \9 T0 z
  19.     HAL_I2S_MspInit(&hi2s3);  
    % P  c3 n0 G1 V0 n' L
  20.   }  5 T% B4 O7 a- |& r5 H: ?+ r) G
  21.   /* Init the I2S */  8 Z/ Q5 y5 z/ h; b% m
  22.   HAL_I2S_Init(&hi2s3);  
    ' K6 I" p! v. T) j/ s
  23. }  5 C! f0 y! E3 x+ g8 Z  v3 c
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  ; Z7 v1 V9 l. x5 e- f0 x0 j. r
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  ) o0 B3 V9 ~* @9 L. Q. y3 @" i
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  % ?. x9 M/ J" W9 \: s7 ^
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  
    9 f+ r# ~1 n; E& |
  28. {  # z7 w5 p5 a( S, F
  29.     uint32_t deviceid = 0x00;  2 R% Q8 z+ k$ W0 v) L/ S! F/ R
  30.     uint8_t ret = AUDIO_ERROR;  
    ) [  N) }5 h5 D
  31.     uint8_t index = 0, freqindex = 0xFF;  
    $ \7 S0 `# Y, \- k, s
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  
    ; S: `# v4 Y' S8 \- x
  33.   9 Y4 d5 C$ W6 a- g
  34.     //get the according P,N value and set into config,this is for audio clock provide  
    * S: h+ O5 k, B4 M& }% @
  35.     for(index = 0; index < 8; index++)  
    ! O: Z. ?0 d, h# n
  36.     {  
    + l7 |, S1 ?/ L2 b) a
  37.         if(I2SFreq[index] == AudioFreq)  
    , V. Q+ j( i, V
  38.         {  - Q! _" w* T' C/ w! {1 ]
  39.             freqindex = index;  
    + t* R; [) |! j: V% x% v; k- X- j
  40.         }  
    8 k$ B" h7 v$ K% j6 P: l
  41.     }  
    7 M& o/ b5 A( U9 L5 ~
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  
    4 j$ u0 O! H! n
  43.     if(freqindex != 0xFF)  
    : v- Z. o6 w* U7 k
  44.     {  
    / ~% i5 s) i8 Z4 \& E' A: P( J" M
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    4 \8 Y/ A" s  X, e
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  
    1 n; N! j4 l3 X$ T1 y) l" |8 [
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  
    ; v8 Z8 R: l) @5 o& x
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    0 ?% ^' y5 c: D4 y6 {
  49.     }  
    3 ^6 }0 n1 r; F
  50.     else  
    - _1 X8 E: b' T. u' z& p
  51.     {  5 a' p" D- K+ E2 _, z
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    ; ?3 x4 y$ F5 E$ P. ~
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  
    2 q( N( Y' q6 r8 @
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  / w3 [- s4 t, m, y* I% ^
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    & L) D. c7 h- a$ _- E
  56.     }  7 [4 g$ F1 X( i& _
  57.   % T) y; F  Z* u! ?6 @
  58.     //reset the Codec register  
    ; I% V( t' [& D1 d3 }# S
  59.     CODEC_Reset();  
    7 e; ?" ]1 ^" N/ K% H1 I$ r
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  
    & g: Z2 p4 I8 y1 B' k, |
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  . r* M  I; z- s, B
  62.       {  9 L, R/ d' y& ?* R3 f9 r
  63.         /* Initialize the audio driver structure */  
    3 @' ?0 j7 B) V- E8 {1 D6 P8 U; w
  64.         audio_drv = &cs43l22_drv;  
    8 F6 s+ V* q! I% K' r3 y% C7 j4 L1 Y
  65.         ret = AUDIO_OK;  ; @1 W6 W+ W# R7 }6 L4 U
  66.       }  
    & Y4 D) L3 c. t3 Z- l
  67.       else  
    # n2 w# L% X* E3 O
  68.       {  & T2 O, ?# v8 Y" g. i7 y1 m
  69.         ret = AUDIO_ERROR;  
    3 {3 |8 C9 L0 p' |* Q: j; X
  70.       }  . {; B+ {; T: m2 q
  71.   
    : Y" a5 |2 V' z' R
  72.      if(ret == AUDIO_OK)  
    2 B: `% _. |4 ]
  73.       {  
    % K7 x% _' t& x! o' U1 s: f( p4 E
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  
    " C0 ~, U2 B; u3 Y: O5 C! R
  75.         /* I2S data transfer preparation:
    ) P9 K0 E: s7 c1 d
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  + l3 G' S. c: B, z# p
  77.         /* Configure the I2S peripheral */  7 Q. \5 G1 o: G) \# t8 U4 ?# M
  78.         I2Sx_Init(AudioFreq);  ! a! r6 _/ r  b0 F  Q9 j8 o% _
  79.       }  
    ( N& K$ W' G0 J! D& W3 v
  80.     return AUDIO_OK;  
    / j& I: q% M3 {) r  H, P7 Z
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。3 N5 I9 n+ q' G0 r
6 e7 l- g+ N# p, Q& Z2 z6 i& J1 i
这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:, i2 y* M1 j" U$ d, p
16.png
' L2 C5 V+ x, H# w
" q7 i$ A8 [5 r9 U8 ^; d1 {- a在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
: v3 H- B# Q9 E1 X6 L( K% F+ G 17.png
* j  i4 \5 |4 @4 f5 L5 m& K7 z/ P也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。" C2 o4 N9 L0 L% K

' e8 z. _# V( @6 A9 b! XPLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
8 _; Z. X' I# X: {; {3 M. w9 J' j5 r
搞懂了这些之后,我们马上将其代码进行对接:1 l' _& R/ I1 G  }
  1. [cpp] view plain copy  D8 c% {6 i7 |, q# Y1 m, {
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
    5 {8 k" W: b$ x3 s/ u% m/ t1 V
  3. {   
    0 [; A6 |! i% h4 K
  4.   /* USER CODE BEGIN 0 */  
      o7 X* r2 z4 T
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  
    ' @7 A9 r. {- ~. v  F/ r" v* Q
  6.   return (USBD_OK);  # k7 g+ @  H; L; a3 Q/ b
  7.   /* USER CODE END 0 */  7 q, c& \! z* @+ @2 ?
  8. }  
复制代码
接下来下一个需要对接的接口:+ b4 R# b% {- V3 J# Z. f: s
  1. [cpp] view plain copy
    6 `1 _- q( x% Z' A
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  / D) t" `; ^8 s5 g1 U, k
  3. {  
    ; Q: Z9 J7 H7 W& k
  4.   /* USER CODE BEGIN 1 */   
    : B0 U9 ~2 Z# L6 m
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  3 t5 l  w/ F9 X. \
  6.   return (USBD_OK);  $ J$ e2 ^8 w1 Y3 l% X
  7.   /* USER CODE END 1 */  
    ' C$ ^4 _' r6 x. S7 R8 `
  8. }  / V4 j# p2 S% a$ j+ z) A$ L; F
  9. 很明显,这个个反初始化的接口,它的具体实现如下:
    ' m$ W9 F: X% T$ w5 ?

  10. 4 o! ?; F4 X/ t4 K( P; t- \# Z
  11. [cpp] view plain copy
    * ^; K6 Z/ i7 _
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  $ }2 t8 ?8 e1 ^3 j. g
  13. {  " {3 t3 z' W0 l. P7 ?
  14.   /* Call the Media layer stop function */  
    6 A1 ~9 a0 F6 k  D/ y2 k* V. `
  15.   HAL_I2S_DMAStop(&hi2s3);  
    $ Z( u/ A) s% E! b3 F; C/ d
  16.   
    / b" k( \8 ~, r3 r% Q4 M
  17.   /* Call Audio Codec Stop function */  ( @( K/ B0 V# q* x" Y, C
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  
    6 g! X* u, Y, }- E
  19.   {  ; l! x2 s7 I. f3 m
  20.     return AUDIO_ERROR;  9 Y5 W  E5 S4 b) a3 H
  21.   }  # \8 T0 ^8 F7 R0 z- G/ ?) |
  22.   else  ( k5 S: d+ R8 X6 G  V6 ?( @8 j
  23.   {  6 w# l$ k. v( E! o' r
  24.     if(Option == CODEC_PDWN_HW)  - x. i, ~# _  ?
  25.     {  ; t4 S  M. l8 B( ~
  26.       /* Wait at least 1ms */  : `" {: J$ ?* r! B7 v# w
  27.       HAL_Delay(1);  
    7 i. m6 {. J/ o  ~: @. G, v5 i
  28.   * _: x, A( N7 m+ V7 V+ v
  29.       /* Reset the pin */  
    4 S, v9 w/ P: I: T
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  & z( N3 B5 T2 q$ y  b
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  4 ]' F( x) N+ c) ^( A* b9 U# h
  32.     }  
    ' d: q& y& ~2 m  ?6 P' a7 K+ R
  33.   
    1 p7 r9 g7 D6 ?5 w5 V/ ?5 ~$ S
  34.     /* Return AUDIO_OK when all operations are correctly done */  
    " i3 _. [, X, F9 l4 U; D
  35.     return AUDIO_OK;  ! c) s+ S$ T6 V' U+ Z
  36.   }  
    1 y. a5 h! x. e8 \* c
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。7 O- @4 H+ E8 @: a+ B
OK,下一个:
- O5 I& s- s& L6 F
  1. [cpp] view plain copy
    $ j- @* f  B& S1 d4 I5 ^" ~' ]5 w
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  
    5 B3 B& o7 K7 T
  3. {  ( a7 l1 h0 `4 P  E0 ^
  4.   /* USER CODE BEGIN 2 */  - H2 h4 W) I3 f# {/ J
  5.   switch(cmd)  
    - g9 K$ @0 u; L* s* {
  6.   {  " _9 N, e# E1 _' q* m8 r
  7.     case AUDIO_CMD_START:  . d& a* B$ i" o
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  , {0 d# M' V" i8 k8 Y; E  a
  9.     break;  
    ! c: n) {* j" k
  10.   3 i# }0 s5 v3 J
  11.     case AUDIO_CMD_PLAY:  
    ' ^8 K8 T" H4 o; E2 T* m  [  k/ H" k! v7 H. V
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  2 i, u1 R$ d+ Q7 v
  13.     break;  
    . u# X6 I# E& g: v1 t/ l5 K8 |
  14.   }  
    ( n" k2 F( `& G5 J6 e* o
  15.   return (USBD_OK);  
    - ]6 _5 n9 A7 J
  16.   /* USER CODE END 2 */  $ W% h0 G) `4 t# |
  17.    
    : z6 t  i% x: Z- P
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:& q1 R( L3 l' {; ^
  1. [cpp] view plain copy9 ~4 z% u: P+ Y4 V: ]7 x' N
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  7 r$ P9 a: y. b
  3. {  
    5 G8 f$ T% P( d  w! H4 [
  4.   /* Call the audio Codec Play function */  % a8 C, w* J0 Q' }
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  ) b& n  p! V/ V1 M9 F0 C
  6.   {    F  v: w  Z/ M  Q# ^, Z6 ~
  7.     return AUDIO_ERROR;  9 \0 R% }; S, Q: J# {1 I
  8.   }  
    8 n& r6 ~! Z$ i2 x* U
  9.   else  7 k) {$ c5 T2 v3 E9 n/ B7 M
  10.   {  % r9 Q9 m) @) h+ q
  11.     /* Update the Media layer and enable it for play */  : J# Q, T/ X4 j, q* f3 e4 e4 U/ V
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  
    ' I  ^4 @, S6 W' |
  13.     return AUDIO_OK;  ( M+ Q! r+ g, A
  14.   }  
    " N/ h1 b9 v! U, Y& T* e5 R
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。1 L  T( n* s: ], N9 X  }

1 R( D  f  r+ }- c3 E然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
" g* b+ w/ l! u1 X+ g* u& l
  1. [cpp] view plain copy
    ) k) Y, r7 y. U3 i* K% h
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  
    3 ^2 Y8 Q, e( a
  3. {  
    ( D. I. u9 ^- N3 D2 @
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  
    % F( q7 W( P8 ?5 a2 L# S% i
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
( C9 Q' B% g0 ?: O7 Q1 ~/ T( ^; t$ G( ?( Y3 ~/ I
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:
5 D! T/ }3 K% n1 h$ S$ p4 p
  1. [cpp] view plain copy
    + a, m+ n# p8 k" i! K: h' X. F/ q
  2. void TransferComplete_CallBack_FS(void)  + t- _2 Q: D! A4 v. K& s3 O1 y
  3. {  
      _* S  d2 y& r
  4.   /* USER CODE BEGIN 7 */   
    * T2 U3 c7 Q. y! f
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  
    8 i9 }: O- u8 Z# H5 j" Y# g
  6.   /* USER CODE END 7 */  
    , S1 N/ u4 H. `+ y' U% J8 p. F
  7. }  
    6 K* e0 w- i, H+ ~! ?6 l
  8. void HalfTransfer_CallBack_FS(void)  ( u% B, j& g  \) Q2 b
  9. {   4 C  z5 M& j4 I1 ?! H
  10.   /* USER CODE BEGIN 8 */   ; @" t+ C4 _4 b* }2 ]/ d
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  # V; e! G- @; f; r3 y# s" z
  12.   /* USER CODE END 8 */  ! @8 A$ N. g+ I
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。
! n, F8 x  m6 T4 Z
: L# B- x2 d. S/ a3 x# M3 F0 z9 ^此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。0 P, K* E' n8 C6 Y/ }" u
/ G  x! b# i8 ^
接下来看下一个usbd_audio_if接口函数对接:
# o1 ^/ W+ [9 u  ?" X9 @9 [1 \; z
  1. [cpp] view plain copy8 y" h8 E+ s0 O; a
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  
    # n+ K! L" c3 ?7 s  u
  3. {  
    $ Z' n' J( B' c+ E. E8 s
  4.   /* USER CODE BEGIN 3 */   . U0 O6 l) f) }$ Z
  5.   BSP_AUDIO_OUT_SetVolume(vol);  # f& m7 n0 _6 f3 R& z
  6.   return (USBD_OK);  
    1 N- n1 Q$ u' `* m% r* b) f
  7.   /* USER CODE END 3 */  
    - q! D5 f: E& P" t7 o
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:
6 e+ {. [* a/ b
  1. [cpp] view plain copy: @+ Z0 R6 C1 Q3 X, I: X$ H
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  ( g* l2 }0 V# r# q  s0 M
  3. {  
    6 o/ q) f' ~2 ]4 W& _$ \$ i
  4.   /* Call the codec volume control function with converted volume value */  
    8 D, E# e, K7 `) c
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  
    0 V: y% ^: n; \7 x# C% ?0 s
  6.   {  " S' c6 p* a' D0 D/ ?
  7.     return AUDIO_ERROR;    G( ?& [2 b& h. k5 X: L0 `2 `
  8.   }  
    ) E) N, o0 f' n- a( h- v; s; W
  9.   else  ! D  S9 ]  G. c& U0 A! |2 @- a
  10.   {  5 W" e5 F- c2 x6 c( r0 \8 k
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    . T: E* Y2 q0 ]+ M0 `( }
  12.     return AUDIO_OK;  
    - o1 k. E+ w. ^0 e" y' x" I( Y
  13.   }  
    ! V, o$ H$ h5 d2 U" d
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。, ]( K  H. l5 i% R1 |) A: V

/ a& o+ R: M# a0 ~下一个:
* G8 V+ Q' t- ?/ h! w5 h1 k* f
  1. [cpp] view plain copy
    1 C0 f& ^0 K8 b& r
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  
    ' w3 t. b* P! q! A* a3 P
  3. {  
    ' q. ]) w% H8 }" ~+ p- i
  4.   /* USER CODE BEGIN 4 */   ' d( i# N( y0 g7 [) x# u
  5.   BSP_AUDIO_OUT_SetMute(cmd);  
    0 B6 P9 k9 m, v, b9 D
  6.   return (USBD_OK);  
    6 t0 P8 m7 [# W( A' ?! B  B
  7.   /* USER CODE END 4 */  
    / V1 w6 Z, E8 \5 q5 P% [' W& M+ |
  8. }  
复制代码
静音控制,其实现为:
: Y! H  {" A4 _  T1 R' J  h
  1. [cpp] view plain copy
    7 _8 l3 r6 ~9 b$ `, W
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  ( F! z) h, E; S' \- o: g6 X) y
  3. {  
    2 U6 ~1 p4 e7 h5 C% k" ]" a& \2 T8 e
  4.   /* Call the Codec Mute function */  . h0 V( ^. r! U5 H4 h
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  
    9 R" R+ q9 ^1 _* g0 T
  6.   {  
    ; c3 J7 y9 E/ s+ T' ~8 m( q
  7.     return AUDIO_ERROR;  # V9 w3 l% p0 g! J( Y( A
  8.   }  8 |! F( ?+ ~! B0 x: P2 v
  9.   else  0 c, O2 d* o( H4 ^' A
  10.   {  
    / h' c1 [' N' q! p
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    , \2 e) ^) g" v5 x: h+ K
  12.     return AUDIO_OK;  
    4 p) S4 E; \# r+ L6 a$ n
  13.   }  
    / z: r5 u3 o6 i) \4 ~. T
  14. }    [' f0 [9 A$ {1 M2 {4 v4 T
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。& R6 ~2 j; Z) {9 G. [- E$ k4 C
3 m9 Y( c; Y8 |" z* [
OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。1 m" ^9 j. o3 A* i  `" Z3 `
! a; S) G' l! m) [
4    测试验证6 Z% F9 D/ r, T! N
将代码编译后烧录进STM32F4DISCVOERY板进行验证。
5 i+ y" E' u+ ]& D6 c 18.png 0 S( A! }% l7 m  o
最终验证是OK的,可以从耳机上听到PC端播放的音乐。. q' t- H* M; L' s# w

8 A$ K5 X3 P3 o& L% v6 d# q5    结束语# e3 Y, E( a$ w+ c
在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。
. H( \- E0 j' J, r5 x
5 n. R! ~$ z4 ^! ~9 mI2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。. ?) Z; }& N! w- K$ `% N1 F3 b7 X3 N

' p" v! \" _$ V2 @0 z6 i& M% V在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。. }9 o" D2 o) T+ ?

# d* _6 \: `1 t7 U1 t# v
, l$ `; e# J  x1 C1 J5 G% }6 Q/ y: E& U4 v8 A
  D# m/ o  ]/ `! d% l7 U0 X1 W

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?
6 i! g: z9 }8 N  O6 c- I0 Vstatic uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,
' J7 T2 I5 m8 t                              uint8_t epnum). P) v% J1 ^1 T9 P
{' z$ n  c3 u6 n
  USBD_AUDIO_HandleTypeDef   *haudio;' n: x2 U! }" h2 @* H# B. i# T
  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;
% p. W' ^# K2 _- n$ K
7 m2 D, Y+ p' z4 o  if (epnum == AUDIO_OUT_EP)
/ p( @6 V! C. [5 C: m  {
4 k3 k: X! f" F* K9 K* c    /* Increment the Buffer pointer or roll it back when all buffers are full */
3 @. d% @0 O" k& }, w: V* Z8 C; u3 D! P, H$ [% a% I! [: V/ ]4 U6 G
    haudio->wr_ptr += AUDIO_OUT_PACKET;
6 u, G6 z: k$ a* u4 t6 `4 e
5 [6 {" b4 H8 P' k* U0 ^1 u    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
/ t; a  m. P+ W    {
2 b) S& d* _  D2 \6 r      /* All buffers are full: roll back */
! S. [% D) ]! p9 _      haudio->wr_ptr = 0U;
2 T1 D( ]6 [4 K0 N    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],- n* X" ?0 J' O. X- T
                                                 AUDIO_TOTAL_BUF_SIZE / 2U,
) ]6 w$ k9 Q! Y3 h9 Y2 J4 U* X                                                 AUDIO_CMD_PLAY);
* x2 Q" J/ t! T7 O! z#if 02 F7 G2 l2 `/ D, a; m% ?4 Y; C
      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
5 @7 X1 c1 ]' z$ _/ V      {
- ~6 C, ^$ o, j, }4 `        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
/ Q" a- A& {& ?) w; S                                                             AUDIO_TOTAL_BUF_SIZE / 2U,
& B1 K: h- b) p                                                             AUDIO_CMD_START);/ W; ]' p( ~4 L8 X
          haudio->offset = AUDIO_OFFSET_NONE;
$ h% S  o1 S8 ]# E( |. _/ c# f      }8 s. t% t* f5 @) q4 O
#endif5 [3 a) G" q- k
    }
* u( r, a5 F5 G( ~) D- [$ Q5 U" D  G
    if(haudio->rd_enable == 0U)! A5 w) M( @7 s# v
    {
. ^6 p* C6 I2 V8 e) a      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))7 G* B$ o1 K' F
      {
" D7 U0 c3 m* F" ~5 t* V        haudio->rd_enable = 1U;6 @4 g9 u6 l5 \  l) n7 J
      }$ S% B' v6 \. |1 f
    }8 o' G2 O, Y% ?) g5 L. O5 v
6 V( s2 f+ B) H& B
    /* Prepare Out endpoint to receive next audio packet */
2 x5 d$ k. J4 t/ {4 H1 h    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],0 ~8 j% n3 `* V8 k; w8 C/ l
                           AUDIO_OUT_PACKET);
& V& o( B1 L* X1 ^' x7 ~$ F' `9 [  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?! Z" A6 m+ \+ S; o. N8 Y
我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,
! O, Q9 d; k: i9 ?9 ?1 Q* M我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。
7 M4 V) i! g6 E# L' n现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,/ ~* Q8 c) Q# S! g4 o
但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,
% h' m0 p* G4 X- y4 A1 h# u而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20- c6 k  \# u1 @  J! y; d+ t* J
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示0 d- H- [: a: x2 ]! ?

& r; k9 u6 ~, n  F  H+ j该设备无法启动。 (代码 10)

+ }* c( h9 X7 }6 a) C) [* m  F/ ^. b出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
" [: G$ l1 h$ U. e$ _8 W& n* A非常好的帖子,谢谢分享!!!

& n  s# v$ \2 w' f4 @8 P希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
2 m2 z& F7 H) z/ B% l: Z* N非常好的帖子,谢谢分享!!!

1 ?$ L8 I8 F! S' e0 O* H2 z希望能有帮助
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
3 b4 k) q; s: z+ L+ C) i. W, [
非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!9 c9 E) Z3 k" h: u. R
% j2 M. W4 h" a) P

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:18/ h0 M3 c0 ~3 Y3 [9 W) p0 J) g
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!' x2 r5 w7 C  {! |
! q2 y! g3 L% l. _
...
' j- P9 Y/ q6 @
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示6 S) B! O% a# U

! ?  w* p0 o6 y* }; s4 l该设备无法启动。 (代码 10)5 ]5 \( e* P/ S6 L, a$ c

( ~, I4 I* n' C" q( @4 PI/O 请求已取消。
: |  H4 P8 B4 q7 B2 V! L& z
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20# E- }. N2 |7 b
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示) m; s* X/ V6 f: D7 q
( N0 L  _' |: H& ?3 S
该设备无法启动。 (代码 10)

% d' l6 ]  ^) |. B  M1 O2 Q# E- K经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版