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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑 % E6 H* ^% K4 n$ A

+ D; `$ z* L5 v/ y! x2 O, T% k  d1 前言( J7 m+ A$ Y6 P( q0 J' r
本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
# j. }, J' t9 x2 t: {
$ k5 c! O. P2 j+ F2 设计构思0 l1 Y& v# B, h+ {1 ^2 s) j) E
所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。
# n1 y: X, W% T0 a; k6 C8 U0 d9 R6 Z. @$ }
2.1 从原理框图开始2 O, }5 S6 n- S6 R
1.png
+ S  z% Q, j* r9 N( M( Q如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。5 y$ K) u: g* b3 x

8 T+ \: P2 S* y2.2 硬件支撑
) f6 v% [9 k3 `7 s% @这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。+ {6 n9 F2 t7 p
' F4 J& [6 k; e
2.2.1 USB接口
# }% T, N9 F9 F0 y( @4 N$ G如下图为USB接口部分的电路:; t) C* A3 K; Y5 {
2.png
+ c# j3 w' L0 {4 i. p3 h这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。/ G( ?) k4 _7 E
" ^- B9 t- C6 _/ G: j, M
2.2.2 Codec部分
2 f( O$ h% M! a* ~  F6 v如下图所示:; l7 ^1 h) l5 O* D: @
3.png * S! A7 K- ^& T0 l2 a7 ]- _0 F- T7 j* D
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。7 t4 f& J6 n' f: j4 s+ l1 _

% k2 W% C% i. `* k9 G' A5 e, v2.3 软件设计3 o& w1 |& v. N- Q
为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。. ?/ d3 f; d. X+ ^  a1 o! J
4.png : R" ^: p0 Z; f& `8 [7 G
' g7 p" R" J, B
如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。
: G' I9 {! n9 d2 ~# x" S% Y6 \* R, ]9 k2 [
各个模块的工作流程如下设计:
8 c2 W1 A2 S( f$ B' z/ q
  J& e+ F: K3 Z! Y 初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
# c; U/ Q* O1 NUSB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。8 [- m2 ?% p) V' C
USB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。! U+ K" V8 ]& ~3 G! U3 I
从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。
, J# _& ^( o. k
" _+ Y3 {' {6 l5 L8 v& F0 K0 O7 m接下来,我们来看看软件层面上的实现。
* g6 g3 G0 R* e, r- j6 K
( t8 @4 p4 i1 ]5 S6 a" K( [3 软件实现
) K6 k: p  B4 w1 j' F) [+ R, W还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。8 o; N: o2 H4 ^5 y3 P
7 \9 k* f" B9 E+ ~* x: d. j& ?, x
3.1 创建CubeMx工程
4 f; C5 A8 J+ m) l6 j由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。
" J. B- ^- [! H$ g" m. c
/ X- M% x- I0 hpinout:
6 z. L: B( n6 S) A
$ w+ ?. g3 n; R" O外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:
, Q: o  x% S; f8 R; o* J; X 5.png . J- A# _0 P8 s: O6 k
8 X+ r& ~, i5 O  P  F& `3 B
Clock configuration:; v! ?3 t5 l1 T7 ~7 G' O
6.png
& K) A6 U( w! O) x时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。
* [2 T  |% k# T4 e& I; y
4 f$ L( M, q: h/ X- W* T- s7 iConfiguration:
5 \+ U. z) q. X. V5 z. c
9 k) t9 t2 \2 s+ i  HAL层:. l- u7 m9 Q# P
Usb_FS:使用默认参数。
  @, n# F  [2 u5 K" X8 v. l# E( p( _; O4 G* g! o
I2C:100K速率,7位地址宽度,使用默认参数。
* m; C2 o0 C! ^) g5 G7 n+ ~- [. g- E5 f1 M: u; R6 a/ G
I2S:主发模式,标准16位宽,默认音频为48K,如下图:
# V; ]: _2 ~& y# \$ w, W 7.png - u% d; A3 W1 n4 P" ^3 x3 Y
并为I2S发送添加DMA,半字位宽:# x7 Y5 x3 R- P# M( L
8.png 1 D! v  ?5 z6 N4 ^& @
  ]' a3 W  S+ L" Z; E
   MiddleWares:
1 u. J& ~+ K2 g8 u) @$ W9 y; Z% a$ h
USB选择Audiodevice class,其配置参数如下:6 {: T  t, y2 S' |0 M# e2 ^3 S" N
9.png 0 j8 _. I5 f( O$ d
; a9 v0 m! g: b
这里都是默认参数。2 g. Z5 k2 b% i+ ]/ R- |
10.png
2 \" _! I6 k# S' N5 [, g在描述符参数内得为usb audio class修改两个参数:
& q0 m4 q6 z# K! }" \1 ?
, H2 X: G5 H: t& ]) h8 }PID得修改为0x5730(否则windows驱动会加载出错)
( G  i/ Y; U4 c% U* F 序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。$ Y; {) V; W* A/ I# b  V* q1 r
最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:
* m# C. L# _9 V) a+ a 11.png
8 v) p$ J: f* a& j/ O如此就可以生成工程了,我们生成IAR工程。
2 c/ s5 O! U6 m2 w% s- [5 B: \* C6 @- o* o% `3 ?
3.2 生成的IAR工程介绍+ f) J0 a* I8 h  a7 v& B
12.png
5 d! c, x3 L9 w! v; M& z如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。; q2 S' S# L5 }- t/ J3 W, n

: H% F- M' Z: k7 L% _User目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。
8 _9 |2 M! E6 A4 w9 oMiddlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。
0 r( h: I# S1 u9 M# C* PDrivers目录对应着HAL层,它包含CMSIS,HAL驱动。
6 W9 \* {* I/ \# e+ e/ c
! ^- q# U" u' i3.3 开发
) p3 i* g1 A5 ?2 w1 t: |# L2 W/ O# H3.3.1 初次编译测试
! I% c: p. c! l5 Z/ e( x首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
: L# v' ~% e9 U 13.png
' L1 U8 q4 A0 X+ j这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:/ `+ J5 F, E/ Z7 M. a0 Z- G6 J$ r
14.png
1 E( W9 o" D7 ?3 `# Y0 j这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.! i! N0 W: E1 }7 K/ }, _! Y) o
2 l1 j) T. W/ ~8 [% _. [
3.3.2 添加codec驱动和audio bsp模块0 B8 b& T8 C. |8 j" }! Y- ~4 f
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:  p/ q1 I6 U) [% O9 y9 s
15.png
, a% g7 C2 v/ l% i9 S# U: t  i其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。
9 \9 M8 |& b+ j6 h) O+ W! V( j, O: p% a( Z# {  O7 b! ~4 e5 v
3.3.2.1 Codec与HAL的对接
8 x7 m( l4 ^( p2 J9 [首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:
# e2 y5 M) A$ s
  1. [cpp] view plain copy
    9 W9 e& x( |1 H; A0 M
  2. AUDIO_IO_Init()  - L$ B1 v5 Z! V- G1 i, S
  3. AUDIO_IO_DeInit()  
    $ W2 }/ c8 r  v0 I# n% A5 N, S
  4. AUDIO_IO_Write()  
    3 u8 W+ y; K& Q  O. e) d' v
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:  q  B2 {5 m. Q) A- V% r
  1. [cpp] view plain copy0 P1 v( Y) `! d
  2. //---------------------for c43l22 port--------------------------//  
    5 S9 j$ u7 _) @( e7 r6 X! [' b0 x
  3. static void I2Cx_Error(uint8_t Addr)  
    0 ~) h1 P1 x, q- \
  4. {  
    * m! z9 c5 F& F% d, x
  5.   /* De-initialize the IOE comunication BUS */  , ^2 R1 U: {8 t9 Q+ B- ~& [  I
  6.   HAL_I2C_DeInit(&hi2c1);  ' n4 |$ x) Z! R+ G) ~5 Q( B; {) Y
  7.   ! c! X0 u) |) N: N
  8.   /* Re-Initiaize the IOE comunication BUS */  % R3 I' {2 G2 l9 y. y( i: e
  9.   //I2Cx_Init();  - u3 T+ ^" m# I6 ~
  10.   //MX_I2C1_Init();  
    7 x2 d$ {4 B# x# ~
  11. }  1 {6 o1 W  k% l+ p2 E$ ?
  12. static void CODEC_Reset(void)  + f8 L2 f: v! U% `
  13. {  
    ' W. t# V; k. \: x8 y/ E
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    # R( o6 K! o) f7 |7 a( O! B; }
  15.     HAL_Delay(5);  
    0 K. |9 R7 p+ ^) w2 P3 S
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  * m1 ^' b! p5 `! X
  17.     HAL_Delay(5);  
    5 M# @$ w, T! |1 S4 x( a: a
  18. }  ) W* z$ e7 C, s8 ^8 U( h# q* C
  19. void AUDIO_IO_Init(void)  2 N' X% G( B5 N0 C1 T, I, X; |8 O6 v
  20. {  
    + _/ \7 W+ H; L( t8 ~* p- r+ d
  21.   //I2Cx_Init();    ?! g" X5 d% e$ Q2 P
  22. }  
    9 }4 Y2 M3 H2 D. a0 D& p
  23. void AUDIO_IO_DeInit(void)  
    / E4 m" f, W* U, {! c$ {
  24. {  
    ( R; A( w) a7 _+ }/ s( ?# K
  25.   / Y4 N* [* n$ v6 A' y, j
  26. }  ; |4 C1 M$ x2 d- A, D
  27. /**
    2 p+ x0 S3 u/ ?! @& ~$ I. j8 j. {: Z; s
  28.   * @brief  Writes a single data. 0 s/ S9 J' N! P$ m& Y
  29.   * @param  Addr: I2C address / R9 k5 f# P1 y& B: l# P
  30.   * @param  Reg: Reg address - F" [6 c6 b  a5 c6 M" Q
  31.   * @param  Value: Data to be written
    , p. r* \) z- p7 P' |
  32.   */  ! o* |8 G. P% c) [' v
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  . _) b' X0 N& j3 [. ?5 I. x+ ?
  34. {  $ d; c% V% |9 ^) o+ u
  35.   HAL_StatusTypeDef status = HAL_OK;  : U, r% z* T8 ]) w& Y6 p
  36.   9 L* m9 M0 b. a2 i. M1 [
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  2 e8 Z3 ]; D4 c: }% r9 f3 S5 g* \4 {
  38.   
    " ~$ e6 q2 x% W+ A
  39.   /* Check the communication status */  
    , M5 {! [' P! |$ {
  40.   if(status != HAL_OK)  
    - |: ^3 I; ~) r2 f* T9 p) G  R
  41.   {  - q; d- O/ q$ M& J
  42.     /* I2C error occured */  4 b* w" _" c4 {9 e3 M
  43.     I2Cx_Error(Addr);  
    ' b7 [  K5 d2 _1 p. _% T/ G
  44.   }  
    - p. H. d; }; c) r' e( v9 f
  45. }    V* U& l: }2 Q
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    + q. A3 y0 I0 e- N" q% \& H# f
  47. {  
    4 X/ K- z4 O7 ?  h5 f5 V
  48.   I2Cx_Write(Addr, Reg, Value);  + U, k# m! ?- q" o
  49. }  9 i% Y; b, F: C7 a3 z
  50.   
    ! ]0 N1 }" \" d/ N. m; A
  51. /**
    ; ~& \- J% j/ \) I; S7 d
  52.   * @brief  Reads a single data. + @: X) E: t  s, m3 V- |
  53.   * @param  Addr: I2C address 2 U0 r* R+ X1 A/ N& N
  54.   * @param  Reg: Reg address 5 s" c! J- D3 o8 o& k( m
  55.   * @retval Data to be read 3 s- H5 e7 O; R, G, v3 I
  56.   */  / @) o( l2 _4 b% R- W2 Z+ K
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  " T. R1 V$ Z3 ]6 y4 \9 [! ?
  58. {  6 o3 @& \; |$ R' T! c1 N
  59.   HAL_StatusTypeDef status = HAL_OK;  
    5 m! W5 v# P; z6 \6 F% E. I
  60.   uint8_t Value = 0;  6 M( h' p. t% Z0 g
  61.   / O% H# D9 T. X8 @. y- B
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    : N$ u( M& R% l: z
  63.   + J( w) c% F6 p* A
  64.   /* Check the communication status */  $ l. \! l* }0 n- {6 P: V
  65.   if(status != HAL_OK)  
    : X" |: S( X5 w# Y% o
  66.   {  8 B9 V' ~3 w2 u
  67.     /* Execute user timeout callback */  
    3 |8 H! G2 j( T* t; o; P3 M. d
  68.     I2Cx_Error(Addr);  
    ' J7 r. N2 e% c- p! D
  69.   }  
    ' L8 W: N5 }, @( Y, R9 l( Z
  70.   ( ^, \, B; }- G* x7 `0 Y
  71.   return Value;  % v9 a* C7 ?. \4 S" J9 g
  72. }  8 m, n" o; ]! a" s' {# `5 u9 S
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    ; |7 x5 K4 `; A8 S+ @: ~" Q' L
  74. {  
    ) G, x. M, f3 O2 F; d$ B* [2 x
  75.   return I2Cx_Read(Addr, Reg);  
    0 E6 c+ m/ T6 H1 P* E, R$ V  {
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。3 p' r1 C- e: k* ?) A

1 U/ E1 D- t# L4 H  w  H就这样,就完成了Codec驱动与与HAL的对接。
5 ^2 h3 o1 h' w' i* K& P! S( U9 M; v" Q9 Y/ k
3.3.2.2 usb audiointerface与codec的对接
, p# l" U# v7 J* Z$ ]# V3 U我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。* }; d* f1 }9 v- l
1 r; l$ @# ]+ n
按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:
* q: x6 u) k" X9 I/ o
  1. [cpp] view plain copy; b7 g, Y& d: E: h2 _
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
    # j9 H, C" O9 s* s$ q0 A* y  D! g0 ~
  3. {   ; G' U* H% Y* O) G! _5 Y
  4.   /* USER CODE BEGIN 0 */  
    4 r" A" A2 M3 z
  5.   return (USBD_OK);  ( r* u& a' l, p
  6.   /* USER CODE END 0 */  
    9 H/ `3 ]$ U7 {" `# T
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:3 L8 W4 n: D0 \) d3 C/ R
  1. [cpp] view plain copy
    6 H; T1 @2 W" V0 }7 b3 z- y6 j6 R1 l
  2. static void I2Sx_Init(uint32_t AudioFreq)  
    & V- ^- [, ]+ e
  3. {  
    - \- M$ D2 O1 J: L; N, L  l( l
  4.   /* Initialize the haudio_i2s Instance parameter */  6 k; F4 W" s) q! P1 @2 U2 I
  5.   hi2s3.Instance = SPI3;  
    # c+ x! y! G1 p" W9 f& U0 ?
  6.   
    ! p1 y( W& M; z7 s9 H
  7. /* Disable I2S block */  ) b# o- B1 {6 b+ v; o& N: W% K' H
  8.   __HAL_I2S_DISABLE(&hi2s3);  
    0 O+ {  }# S* S0 t
  9.   2 @4 e5 ~! o0 i; g# \8 R
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  - M* z8 l4 H9 O8 m2 Q! a# p+ d
  11.   hi2s3.Init.Standard = I2S_STANDARD;  ' V( [1 X. E* I  k* I5 B+ g  t$ t
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    + {7 S9 k  U6 L! Z
  13.   hi2s3.Init.AudioFreq = AudioFreq;  6 q! C$ k- K! b0 M' A% a  s  N$ h
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  
    9 a2 y8 a  ~$ O+ f4 Y! M1 x" i5 M
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  # M- m4 `, ^, B- l' }
  16.   9 [( I% F# s) u7 s# k& W
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  9 ~5 A/ P( s9 V" E$ A' ?0 g
  18.   {  6 N1 U! a$ e% P# t, x
  19.     HAL_I2S_MspInit(&hi2s3);  8 M. o. U: Y. ?( v4 g& R+ o  r* x
  20.   }  
    & \' D; V/ w2 n4 C3 T- }
  21.   /* Init the I2S */  
    : {9 Q, ^7 f6 `- u) p( t
  22.   HAL_I2S_Init(&hi2s3);  . R" f5 s: ~/ Q5 U5 }  D9 P
  23. }  
    . N# P- |& M/ k& q
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  1 z9 F+ g6 w! |. j+ k+ N3 q
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  
    - v& A  e3 g/ j7 V. n
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  
    7 B5 f2 Q5 }! r0 Z) B
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  
    1 b- R# H) L3 W' B/ q4 }; B
  28. {  ; w5 ^7 J* v0 b1 c9 C8 u3 y
  29.     uint32_t deviceid = 0x00;  2 I: H" R& |, [
  30.     uint8_t ret = AUDIO_ERROR;  ' O8 O: K5 ^; x' i! T
  31.     uint8_t index = 0, freqindex = 0xFF;  * _* ]5 ~8 `# Z. e; I+ M" n
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  
    6 [" W* Q+ ~  Q0 U" i
  33.   
    6 ]2 i6 Q9 s- l' N6 P
  34.     //get the according P,N value and set into config,this is for audio clock provide  
    ' r- A, C5 [0 ]! J  z3 P- @% k
  35.     for(index = 0; index < 8; index++)  1 v3 W- d9 W, t2 S: A, c  ?0 J, k
  36.     {  , g/ U- T) _- o* k
  37.         if(I2SFreq[index] == AudioFreq)  
    - y7 ]3 J* Q& L( S- w3 K! p
  38.         {  
    $ J2 a/ [. B8 A1 ]1 x! z9 D
  39.             freqindex = index;  $ I0 R% o4 n; t
  40.         }  
    9 E# G9 G( w$ L( ~4 \
  41.     }  2 D: K# g; z0 [0 Z: I
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  
    * [/ L$ N+ P+ f% Y2 Y' n! S
  43.     if(freqindex != 0xFF)  
    % a4 r9 ]% ?6 I
  44.     {  # A* Y$ l$ L; w3 u! P' {4 e
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  $ W7 E; ~1 [/ _- q! ^) K3 k! K
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  
    5 ?- h9 h' X% X6 }! q4 g# J1 H# C
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  
    * e+ v3 e* @5 A+ }6 J
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    : w$ l6 N' s; L
  49.     }  $ \: S. t( ]/ p  f6 l) X$ |
  50.     else  
    $ ]% C3 |  S1 \( p6 A
  51.     {  % `# }% Q2 ~* u1 {
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    7 E3 n+ E4 b4 O) e2 M$ b4 v' z# s
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  
    $ B, ^8 P6 @6 l
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  % H! h! M# s& D; g! X
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  : A# y; l8 G4 ]- f' p0 u5 G& g2 d
  56.     }  2 L% U1 C. R# _. ?- s- X
  57.   
    6 X9 ^0 L* X& ?- n1 [! l4 G$ i
  58.     //reset the Codec register  
    - _" z" c- I* G0 N, I/ N. V
  59.     CODEC_Reset();  
    6 t9 k5 f9 r, q8 B. T* A, v" C$ x
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  
    7 r/ D* e7 x6 a8 c3 R
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  ; p, {8 R( C! X* _
  62.       {  
    ! o5 w5 {* @4 P6 w/ `2 `: L& t
  63.         /* Initialize the audio driver structure */  7 q3 x9 O4 z/ t( }
  64.         audio_drv = &cs43l22_drv;    z. }9 E4 k/ H  n
  65.         ret = AUDIO_OK;  ' I# e6 B, u6 S! |7 g
  66.       }  
    * v" E9 V1 Y2 n# K! d
  67.       else  
    1 P4 g2 G) c3 {
  68.       {  
    # N. G2 z. K% h; n( {
  69.         ret = AUDIO_ERROR;  
    9 d1 V, W6 V9 E7 S$ ^2 r# x8 {
  70.       }  
    : k. E3 T: f( d1 o2 D* J. \
  71.   
    ! K" z) }4 o" ~
  72.      if(ret == AUDIO_OK)  6 n8 z% }# O6 B6 j
  73.       {  " |( Z" q( _/ |: T7 i. j6 L( W
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  
    % ?! X& F! e2 k2 r
  75.         /* I2S data transfer preparation:
    1 y) T  _. S" `4 j7 y8 u/ v; J
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  
    - g# w) R) H1 {& g! j" c* |" W
  77.         /* Configure the I2S peripheral */  : w% L* ^! M2 ~
  78.         I2Sx_Init(AudioFreq);  + x/ A5 k+ y( @) c7 f: B
  79.       }  3 U6 s* K" G" R+ j
  80.     return AUDIO_OK;  9 P/ z* |6 O" x; ?: G
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。
* R: ?0 C5 @  S4 J: Y, y* K2 j
  C5 M- [- b) y这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:
# R  ~1 n2 ~7 n0 y8 Z- C 16.png 4 |# X& `, G  B5 j# F

4 x% G; q# T% ?$ M  f. \3 r在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
. G% r0 Q7 x( M' M6 A" k& l 17.png : [( e. q+ R$ X5 n5 X
也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。
6 C' |8 |6 h: ?0 @2 N* A5 p: X- p4 P3 ?; ?
PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
8 }, X1 G& u( J9 x+ \8 S* \& |( i" x0 Q7 x
搞懂了这些之后,我们马上将其代码进行对接:  W" S. k( w' w8 ^: j9 v: i
  1. [cpp] view plain copy
    , C* T' g  b9 Y, F6 _
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
    2 w  K; Q* v0 y3 O  L0 N- R0 t
  3. {   
    , P% W" t+ ?9 {/ ^- \
  4.   /* USER CODE BEGIN 0 */  " {/ Q$ Q, d3 \/ |5 \) @
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  ' d7 E; f. E4 d( E, X# o/ C
  6.   return (USBD_OK);  ( D( o; X" k" n0 H1 M5 p4 O) Y6 |
  7.   /* USER CODE END 0 */    n1 n' X! M( D2 ]
  8. }  
复制代码
接下来下一个需要对接的接口:, J$ y! |1 S0 d
  1. [cpp] view plain copy: j* `4 T8 A4 U5 @
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  9 @( o) Z* [, R
  3. {  
    . \( l; ?. V* j2 Q6 }' d
  4.   /* USER CODE BEGIN 1 */   ( t4 Z* q7 S9 p) d
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  
    + B& E. `, M3 L  |
  6.   return (USBD_OK);  
    $ ~/ ~# o" _- K( `  ^
  7.   /* USER CODE END 1 */  
    . y3 u. o! `/ J: E
  8. }  
    ' R% x, k4 T% K5 U( d( B
  9. 很明显,这个个反初始化的接口,它的具体实现如下:$ N$ g' L+ D, l3 r( F
  10. # T; {! [' X8 O. l
  11. [cpp] view plain copy& X6 M7 F0 d4 z2 C% N5 `
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  & h8 J5 e7 }, J! w) ?5 X) k
  13. {  $ B2 U8 W. P5 {$ a- S
  14.   /* Call the Media layer stop function */  
    / V: C- t  _' |& X1 d6 i
  15.   HAL_I2S_DMAStop(&hi2s3);  - n& d! O) S$ H; @8 P) U3 `
  16.   
    ; z5 V( {. |# D3 h; Y
  17.   /* Call Audio Codec Stop function */  
    " s/ v0 F! M. ]
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  
    . r8 @: W) ~% A# _8 H9 ~& \! ]: O7 ]
  19.   {  
    6 O+ Q1 D" e% }/ [. y
  20.     return AUDIO_ERROR;  
    7 l2 B1 E8 I& l7 ^" |
  21.   }  
    ) d( d# o/ Q; j4 x: a
  22.   else  1 e& _" w, C; e+ n/ }  O3 D3 k. U0 U
  23.   {  
    # C5 P( F9 L. z+ N: E' e& U
  24.     if(Option == CODEC_PDWN_HW)  
    ( K9 R, i" x5 W* H
  25.     {  + X( t: c7 k! u4 e" H1 M) e( G3 J
  26.       /* Wait at least 1ms */  
    0 Z% u' _! T; ~  W4 V
  27.       HAL_Delay(1);  
    4 U$ W$ H, ]! Y! P# p
  28.   
    8 L; S7 o8 @: z$ O. v0 P( B
  29.       /* Reset the pin */    ^2 `8 W7 G& i' i0 C
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  
    - R! ^. ~0 z! t/ E5 `3 ~
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  % w& B/ ~5 A. E6 ~. \+ I; W! G
  32.     }  6 }& c! j: D5 w' W# Q( L: G
  33.   
    2 q" R, x+ H/ V/ d. h& [* e
  34.     /* Return AUDIO_OK when all operations are correctly done */  / B7 m3 N7 \4 t  J! b  g$ E: ~0 @
  35.     return AUDIO_OK;  
      }- u6 v2 \- r0 h- b$ \8 Q& a! D
  36.   }  
    9 `  i% _# x  p; N
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。3 V/ j) J/ q  d5 u- V* C
OK,下一个:
$ H7 c8 E* R6 C  c  G$ i
  1. [cpp] view plain copy
    : o3 R. ~" \, Z3 ?: B
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  , s/ A5 K0 j5 k$ \6 Z
  3. {  
    & a; H) U' i, g% Y9 Q
  4.   /* USER CODE BEGIN 2 */  
    - O; T* H0 [5 k3 k
  5.   switch(cmd)  6 @/ [/ U" k  D% I& o5 t7 C
  6.   {  
    / u$ h" N- ]3 k( u: B
  7.     case AUDIO_CMD_START:  ! }- p) Y! p* {% a$ n
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  ' j6 T+ k9 }; V0 z1 [
  9.     break;  : U5 P( l# C- t& D9 {8 H9 b8 S* O4 k& S
  10.   
    9 o9 h1 Z/ v# A* F. X' z1 v, g2 T
  11.     case AUDIO_CMD_PLAY:  , J5 E2 w4 |4 y- ~& P
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  1 {8 V& u& x2 k6 B8 L
  13.     break;  
    5 Q3 R, M+ k. G* `4 |8 [- r/ r, P
  14.   }  
    3 f: Y9 b* O0 C, o* g3 e
  15.   return (USBD_OK);  
    9 ?, {& L1 b; W0 E# |. ]: y
  16.   /* USER CODE END 2 */  
    / m& @  L6 F# Q
  17.    
    + L7 `7 m; p& K2 a! J# b
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:
# U" j, _4 @2 I9 p0 ]" ^6 l4 B
  1. [cpp] view plain copy
    4 t" c$ a$ x4 E7 n# b
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  " Z: k9 c2 B. k0 _5 E1 d# u; d
  3. {  
    2 s% k; G0 A$ w, n* v1 b- S! O
  4.   /* Call the audio Codec Play function */  1 L+ T# {. E$ h5 M8 c# F: A
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  
    ( J9 N# x7 k! C+ `5 a
  6.   {  
      `/ i5 G$ K! V, X
  7.     return AUDIO_ERROR;  7 B3 V* {* r- W4 [( R  p+ m$ P
  8.   }  ! C3 I+ C: t5 F! @. ]  I+ y
  9.   else  
    ) j9 O% k# B9 b1 t* z; [
  10.   {  ; _4 a7 J4 {" b7 M7 G, ?! R8 E
  11.     /* Update the Media layer and enable it for play */  
    / a, h: c- l6 k! F* G( O
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  
    9 X5 `1 J" c6 O% \& z4 p# J3 T
  13.     return AUDIO_OK;  # E$ X) a9 [1 r- [
  14.   }  8 P* S7 y( _* m5 Q2 J1 B
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。
$ c7 M, y. s6 g
; h+ n# @0 l( @9 t然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
0 A7 x! m) m& ~* p* M% J9 y
  1. [cpp] view plain copy
    # u' `- R! O; S- r" b( G
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  
    4 e" q! m3 T' W$ K# n
  3. {  
    2 ~! q( T' ^: @9 B3 Q5 j& H
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  
    $ P  Y: U! k- r+ p3 r/ n+ C. O9 {; U
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
! T$ e" j. P% E7 p& `, L4 z" o, E: v: p9 w) e% R1 O; @1 G& h8 a  [
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:! W, c6 @2 a7 O3 }) ~2 C; u
  1. [cpp] view plain copy6 ~6 Y, U" M2 W% {4 b" h8 X/ U% {7 P
  2. void TransferComplete_CallBack_FS(void)  
    , T" R6 ~8 f5 D, f+ w9 M
  3. {  
    6 T9 l, x7 _- C
  4.   /* USER CODE BEGIN 7 */   3 a6 K0 b- ]1 }- d8 o+ Q, Q# v- \6 F
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  9 |3 T. g$ F: P
  6.   /* USER CODE END 7 */  
    ! l: N. d* ?3 @  f# W( X
  7. }  
    , S0 T/ ~( I* G5 m1 X& t
  8. void HalfTransfer_CallBack_FS(void)    N' C* x8 L  W
  9. {   
    9 T/ h( F- W$ d+ J& o9 h
  10.   /* USER CODE BEGIN 8 */   # S* Q; M/ G8 k9 _
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  
    , w2 }+ {4 n+ j9 Z
  12.   /* USER CODE END 8 */  / D2 D% R* e6 `6 Y7 s' M
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。9 a8 h& p! y- ~5 X2 q, c) u

" i% i9 g6 `* l2 d0 \& J此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。
) F0 H- A0 O$ k5 b( }' G5 e+ p! n' A- M. ^2 c( S
接下来看下一个usbd_audio_if接口函数对接:% i" L% z/ M6 @  W: o# i' R, Q
  1. [cpp] view plain copy
    ) ^( {: D" r) w2 i/ U& W6 |$ b1 N- n" V
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  
    4 d- ~. }% i" {3 K* w* y
  3. {  ; r- s. J- ~% d
  4.   /* USER CODE BEGIN 3 */   
    6 i; A* q& [( C1 w( m1 I! t$ v6 t
  5.   BSP_AUDIO_OUT_SetVolume(vol);  2 {" }) h3 g5 Z# g4 J' e& w8 p
  6.   return (USBD_OK);  
    / y/ t9 F9 U* g' ^
  7.   /* USER CODE END 3 */  
    + z1 r& H( w+ ~! e! ~
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:5 B6 y5 W1 `! a3 E
  1. [cpp] view plain copy
    . _+ A( m  c' K+ Z" v
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  
    7 B% j- w: l2 B: k$ A  A
  3. {  6 f9 u& i2 V; A. X; O9 j
  4.   /* Call the codec volume control function with converted volume value */  
    1 @. h: S4 T* W4 p2 A
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  
      S: f" a3 S4 r' i: K
  6.   {  7 b! `) ^4 S" `( ]" {
  7.     return AUDIO_ERROR;  8 q! H& L/ z) ?
  8.   }  - K7 N3 X$ m" c2 z" U
  9.   else  
    - F& h% U8 V. O/ K( E6 `( P
  10.   {  
    ' C% X/ {; w) h
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    * a- c5 R% D# Q$ g
  12.     return AUDIO_OK;  ; w; n% Z' w7 b7 g- K1 q
  13.   }  , Z. D* u0 Z6 @, L& \
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。# E& I+ ]0 s& u6 t( c& T
- B% Y+ M( E8 ]: a2 ^0 E1 {
下一个:
5 f7 z' T8 u' W0 e" K
  1. [cpp] view plain copy1 |) L7 ^( ^4 D$ s& i# D
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  
    $ `) ]# d; x, m5 B! l* n. J
  3. {  * L2 U. M1 x! O6 S$ Z+ b7 c; A: E  X
  4.   /* USER CODE BEGIN 4 */   
    * i- j  I6 X4 a
  5.   BSP_AUDIO_OUT_SetMute(cmd);  
    2 A" M3 I. f. o3 Q  _
  6.   return (USBD_OK);  
    9 S- }" l; d/ D2 V2 r. {; S' {
  7.   /* USER CODE END 4 */  
    , H8 u3 q$ S& @. [* r. t
  8. }  
复制代码
静音控制,其实现为:
5 _$ T) |! g: Z
  1. [cpp] view plain copy
    8 M7 ?* r0 L5 o
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  ) v7 c7 }% J; w& _* J
  3. {  . p) B  k" {  P3 u0 j
  4.   /* Call the Codec Mute function */  , w- [6 l1 V/ m2 f# F) _9 v8 ?
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  
    3 d, @1 A5 R  T. i+ y
  6.   {  7 l% ]: J" c0 d( \
  7.     return AUDIO_ERROR;  3 V: h) `' G! ?2 m
  8.   }  
    ' k: W: D/ k) u2 ~2 ?
  9.   else  
    ( U1 ?; r- [6 j
  10.   {  
    . g8 |* D# e9 M' n2 u- h" k
  11.     /* Return AUDIO_OK when all operations are correctly done */  $ o  {$ r# z3 l# J2 `
  12.     return AUDIO_OK;  # x0 {0 f$ J- w+ |- ^1 l
  13.   }  5 e6 i& ~, u2 j/ D5 [1 _9 c, u
  14. }  # _- ?; Z6 @. `6 V( W% |
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。5 E+ {6 @. {: n

8 ^5 @8 `6 o: p" F* M/ ^OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。
. ~$ o1 x+ g- x, ~* g! C! u9 Y, r; N# ~/ @8 T* V( d: Z
4    测试验证
* F- I& u1 E' h0 n将代码编译后烧录进STM32F4DISCVOERY板进行验证。
$ O3 s" Q. M1 ~! {5 A6 V" ?: W% Q4 \; K 18.png 5 Y' m4 N0 o0 g/ w7 W8 C
最终验证是OK的,可以从耳机上听到PC端播放的音乐。2 j6 l9 \+ w5 p
) f4 W7 w) l3 ~5 x2 W1 Y
5    结束语
& w. ]3 ~' _( U0 g在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。
9 H" u9 J$ {: n1 c# ]
9 J+ L& y  F) }( ^3 k+ O5 {" w+ c1 iI2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。7 D* d9 r/ Q' Z+ Y9 E4 _- W
$ @5 V$ T6 J* p5 U' C
在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。
. M$ f; c, I1 s" J* J( I# ^! B" x) Z
8 g& L# a- r# E! P( z8 i

9 _  Q# X# T' b+ R& {! n( q3 W4 d9 @  Q9 C$ R, [/ B

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?
% l/ K0 ]5 ~% r; ^4 F. k# Ostatic uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,6 k2 n2 R% S1 x7 c4 Z4 }( k& \
                              uint8_t epnum)$ w' B6 q5 h' o3 x* F
{$ J5 |4 v# @$ f4 T
  USBD_AUDIO_HandleTypeDef   *haudio;
& _) [9 e. k, J  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;% E# n. }" v! i; J, ?
$ A0 x# U  [& t4 E# _- y, Q+ l2 {
  if (epnum == AUDIO_OUT_EP)1 R  X* F5 n: t( {4 V+ N# K
  {7 u4 s8 b7 d& l3 H
    /* Increment the Buffer pointer or roll it back when all buffers are full */
$ W, G" e( u- v+ Q3 Z! b/ [' s6 g! Q' z
    haudio->wr_ptr += AUDIO_OUT_PACKET;" B8 J% b& E, ?+ Q7 t
* @: @  b) b9 w- m3 W( X
    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
" j8 L, w6 v! Q6 j; O5 \& D, f    {
+ V# p. _/ ~) {( B      /* All buffers are full: roll back */
- F# Y. ?( H! W  B      haudio->wr_ptr = 0U;/ ~& {& M$ o8 U& i8 o" @' Q
    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
8 ?1 s! Y2 h- P3 w' c5 y                                                 AUDIO_TOTAL_BUF_SIZE / 2U,
! t" f9 K' e7 G+ m3 ]' w                                                 AUDIO_CMD_PLAY);
' z, ~8 @$ m5 }9 K8 Y( F#if 0" a1 d2 T8 P$ n! U3 ?
      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
! u% b6 {& z! x: j      {
) g3 f9 E) t9 e        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],& U  E" R. H( K# I1 w- ~' [% s
                                                             AUDIO_TOTAL_BUF_SIZE / 2U,
7 F/ o' e1 M- z                                                             AUDIO_CMD_START);, ^! o/ O2 w2 ~. z3 F- c
          haudio->offset = AUDIO_OFFSET_NONE;
, a& o% P$ z2 L/ A0 B& @: F' y, ~      }& _. B- o" @3 j2 \8 k$ @+ D
#endif: A3 J! m0 A% U' e% ^
    }" e( {1 [- o9 T: o, N7 j
; Z/ W% W0 X: i3 g4 F
    if(haudio->rd_enable == 0U)
! K* l0 z6 A0 @: [) ]" x9 C    {
/ k1 J( K! n) p# _9 u9 j      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))2 l. b' O( |6 t, a3 }7 Z
      {" V7 @; Z( _' i4 W4 K4 x7 K5 }
        haudio->rd_enable = 1U;, @* G" b, a) w. u
      }
$ e- r% z/ N& g1 ^+ l1 a5 Y    }; V: x4 w0 M* K+ x
2 u1 F2 I$ ~+ v, _
    /* Prepare Out endpoint to receive next audio packet */5 k9 s8 B4 B' E
    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],
. P5 V( R- J& m9 }2 w- x                           AUDIO_OUT_PACKET);0 u/ B9 {9 }3 U0 D/ |6 O0 \
  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?, a; E+ M% L" R; E& q- }
我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,- K) Q: `6 t: Z6 K1 S- Z
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。+ a2 e$ t' `+ u0 P
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,; f; D5 G4 a7 q. j$ J4 t! K2 {
但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,9 C" z. [, o9 V$ T' p; J$ L9 a
而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20) o! Z+ c* C+ j+ F
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
3 |/ B/ o, E3 w' A9 ]3 U7 j/ }
" d; `8 }; y$ u" j' q, ~4 G) e. p: o该设备无法启动。 (代码 10)
7 s2 Z# V  X" w* h
出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
7 l, }7 C! O. \非常好的帖子,谢谢分享!!!
5 e& H# N' z6 h3 L2 H
希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45+ L- n' l2 @% V5 D& m( N
非常好的帖子,谢谢分享!!!
8 Z& F1 `( G& ]6 L0 I
希望能有帮助
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

( ?" x% i2 e7 N/ `) |5 E! n非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!% j7 ^9 j  T. j3 b  i

6 |. u8 I; f" U- I" A0 g: J. m+ H1 K

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:186 T  {( ~4 O% R* s1 g' e9 E# L
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
9 V* L" j/ `, s/ ~& s1 W8 g8 }6 k' K' ^, Z% Y' G7 C& x, l* i
...

2 P' @5 S% v' i0 q0 H# V, c% e这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示) E5 X; u# Z1 b8 p& w- s) e
1 c' t- D7 s* I
该设备无法启动。 (代码 10)
% n& N* F! t  I9 q3 i6 O& a* i
0 z0 F( n6 A6 h2 I* ]% D: a% {$ \I/O 请求已取消。! _0 L( ^. D* }: \2 J
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20
' Q$ |2 `' d/ Z/ s$ {这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
( E  h4 t: I: O' ~0 u# J+ r1 L0 d, R; s! K/ ^
该设备无法启动。 (代码 10)
6 ^6 m0 G, T6 T/ N
经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页

所属标签

相似技术帖

官网相关资源

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