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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑 & k3 j. e0 B) X# t7 `! r
7 c+ k+ Z6 R- f1 @5 i
1 前言
1 ~& j/ m+ s+ o9 `0 n7 O  ]本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。' R+ H: d) E8 h$ G6 t6 y
8 y( e6 v5 S" S1 h% p
2 设计构思$ u% t0 s/ @2 c& M+ e, e
所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。
1 F* ~8 e9 W: C. C) x, D6 p) C) h1 y
2.1 从原理框图开始
, y" H+ b6 Y" X5 n/ L 1.png " x# u1 r2 A# O! L7 C8 b5 p" Z6 q
如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。
  w7 k% R9 p. n# f# Y
" ^( H* h7 M, u: Z9 i% k  ?2.2 硬件支撑
3 G1 s6 G: h5 ~' j' v4 ~这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。: q: N% n; [, q( a/ J. [

1 u, \; o' G+ o9 ~( O7 ?% v2.2.1 USB接口
! O: K: M* l' c4 t/ d如下图为USB接口部分的电路:3 W& L5 ~, }; k' F6 ]8 H
2.png 5 e0 @! y( x% X
这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。8 ]1 h" B$ Z9 y. }: n/ v
" p, _2 w6 s1 x$ g) ?
2.2.2 Codec部分' [1 H2 }6 h1 y& ?- b
如下图所示:/ N1 Q5 t, s$ A5 a# c, |
3.png " a  c- r: w: O4 y/ d
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。
+ c" }$ x/ _+ Q5 C0 Y" a  E2 D! G# z! o" h
2.3 软件设计
' h' |# C* A* f: P# \; W5 ~为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。
4 M$ o4 o9 ~6 y; V 4.png
! |3 o/ _" l" @9 e: r0 O
# S1 G( ], \0 o3 `如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。
  n1 j4 q: u" Y/ r: b" v) f) Y3 ?# E0 P7 J! e6 o
各个模块的工作流程如下设计:
& y3 E9 `" }, G, ~- x
$ u  @3 h: R6 d# ^+ F 初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
3 R& Z3 H+ ]: CUSB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
. i# v5 _8 v) EUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。) |0 Q, g" Q! P) t
从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。! @9 p6 b! @9 [9 ?0 ?/ t0 Q. X

/ _- U: t) o7 e7 E7 f2 R# y接下来,我们来看看软件层面上的实现。' K6 o: i  m9 i( X

# ]8 E' G/ h2 u9 K. H' v7 K3 软件实现% M/ J$ J. l! z& T1 Q0 K; q4 n
还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。
. X* t: |! Z5 P4 w! g+ g
7 B, o: M5 s! u3.1 创建CubeMx工程
+ v1 T1 p% G1 @  _8 I8 M/ X由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。0 w, E! R0 b5 ]" L2 N8 D

9 L0 t" m: Y/ ?5 a6 Z$ t9 ipinout:1 e- ]4 q: P* [2 t" E
" X3 h! h' q" J& r% y
外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:+ {6 C" [9 ?# N8 \1 x5 m+ E5 q
5.png
$ L. z( y% p! T* W0 N# d
+ C8 p9 j: l# e2 p4 S/ SClock configuration:
$ w7 V: d7 v5 E 6.png
; S9 `/ c, ?  R" P! p( E9 D& I时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。
- I$ X6 Y1 N- o8 b! n3 W1 W: p2 G7 u  h+ [/ D: L+ O$ e, f
Configuration:, B$ R; x% C1 D

2 _; z0 U8 i6 F4 b/ l9 a/ e  HAL层:5 J5 M  B  }5 L
Usb_FS:使用默认参数。7 G4 q" J% K, R) ^$ S

) X! Y! f# v9 x) v( O; {4 KI2C:100K速率,7位地址宽度,使用默认参数。
9 Z. b4 q% c) d6 j
3 k& c/ ^0 }3 L0 g8 ^% o, p; M8 W3 HI2S:主发模式,标准16位宽,默认音频为48K,如下图:
- U, i( x$ F7 J+ Y# _ 7.png
- {7 c  h, J6 H! V. p. d+ p并为I2S发送添加DMA,半字位宽:
4 @: e7 @! c. V% Y; t7 X 8.png 7 f1 {7 f9 ^2 r  h, r2 h4 W8 E& X
  U% K2 i  S2 k- R
   MiddleWares:
. G( N: t9 i9 D& T2 m. O
) }8 o& W, u& C0 J/ FUSB选择Audiodevice class,其配置参数如下:
! l  L3 ]. ^9 V, s$ e 9.png 3 x+ B4 y9 W% N8 t' E

! f/ j# ^/ x3 e这里都是默认参数。! y0 _0 n# Q: O. M% o" U
10.png + v7 w! S. |# \% V! |. F! A9 ~
在描述符参数内得为usb audio class修改两个参数:
1 B) l3 v2 d: {$ m
8 e, A0 J5 v* d8 G3 uPID得修改为0x5730(否则windows驱动会加载出错)
' @+ @" d! N" B 序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。9 N& ^: i; @0 s& q
最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:' T0 ]$ s, l! ?% ^5 S' `
11.png
4 ]) E# N* m2 T" Y0 p# P如此就可以生成工程了,我们生成IAR工程。
3 r) P$ p0 E' r  F
( N) M/ u$ Q+ ]. L8 a8 u4 i3.2 生成的IAR工程介绍
5 k4 n0 q; z* e. ]$ | 12.png - i" X& N8 V3 H1 J8 ?9 K
如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。
; t: W4 X% r/ y% U% a" T1 N3 v
: F7 U5 Q/ e; H! L' a& P# XUser目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。
" B1 q; ?$ U0 S! r$ d/ [Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。
( {* a9 P! `$ m. I3 D, W, ~, ?Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。+ \2 p% Z8 j. @4 j4 x' f, {

( }2 j+ R5 f  G) Q7 k3.3 开发+ J# z. R! J& c; U
3.3.1 初次编译测试
( `, l+ [  n+ w首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:9 t- p# Y1 E* v" H, ~0 F9 w
13.png $ }5 E- ]% E2 d! \/ ~  X
这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
$ f% r) o  c5 u, r! ~ 14.png 0 C' H" ?8 E$ r$ E
这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.1 O# Q& Y* H( e# y% @) s0 N5 P: o

5 |# j* E/ C4 e& z( f3.3.2 添加codec驱动和audio bsp模块. S0 u" g( V! z2 F. k* Y
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:
4 [6 z' {$ h) a0 `6 M! G8 A 15.png $ t) }/ B' _+ r, m* q( N& G
其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。- r# }/ k1 C+ I) N" J$ p6 [5 z
, m' L. V- A7 j& A& C% ^
3.3.2.1 Codec与HAL的对接
  n: m+ E7 D" Y% I9 W; N/ ^1 r. r首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:1 z3 u" E! o2 m* e! d- j
  1. [cpp] view plain copy
    ; c' t# u3 N2 ?8 C- |+ m- G
  2. AUDIO_IO_Init()  4 n' R! q, y2 ]' i0 T
  3. AUDIO_IO_DeInit()  
    6 ]8 i- P% r( U. g* C5 ?6 q
  4. AUDIO_IO_Write()  
    2 X0 e% M) C9 v4 M) f
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:% R0 q3 K+ C5 u# L8 A
  1. [cpp] view plain copy9 I- o+ P" g+ N" h8 d1 ^% E
  2. //---------------------for c43l22 port--------------------------//  
    , i+ r6 j( ^: O; G7 _- _. |4 d" C$ ^
  3. static void I2Cx_Error(uint8_t Addr)  ( v6 z$ y& E- P7 \* l) c+ @8 _
  4. {  , H$ W  l% o) h) E$ {
  5.   /* De-initialize the IOE comunication BUS */  4 W) j, h/ E( |2 _7 {, w
  6.   HAL_I2C_DeInit(&hi2c1);  ; ?) f1 K$ k/ q' s
  7.   
    9 \: \8 U: W* D$ m3 _
  8.   /* Re-Initiaize the IOE comunication BUS */  
      l" K9 G" A$ j1 }8 B: t3 I
  9.   //I2Cx_Init();  % @/ x) J( }- g4 w( P& z/ M9 }) j
  10.   //MX_I2C1_Init();  $ s1 F: Y5 n- I& F" k! K
  11. }  ; o: L$ F+ z, s2 W5 E: ]4 o
  12. static void CODEC_Reset(void)  
    & Q3 c7 o/ Z6 L5 \. X( c# A5 }" I  P
  13. {  $ ?5 c* v' I- }/ }
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    ; }' R* w) ]; m4 K6 }
  15.     HAL_Delay(5);  
    ) v' G0 Y  g6 V' N1 B! f1 |
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  ( S2 W1 m5 K7 c# n
  17.     HAL_Delay(5);  
    7 r4 L: R* I; r# [
  18. }  
    3 W! n- B8 |0 A2 {0 v
  19. void AUDIO_IO_Init(void)  
    - T. R+ M, `1 V, F- j; I, M, h& x. c
  20. {  9 r6 T1 h% F+ _8 ]9 c
  21.   //I2Cx_Init();  
    . I8 ~1 i) f/ j2 Z9 c+ B) ^
  22. }  + ?5 ~9 J+ h: N2 X8 y6 h
  23. void AUDIO_IO_DeInit(void)  
    & e9 k; ?+ ?: ?. G) h
  24. {  / Z' m: o& F. m4 N6 J
  25.   
    % h- B; z$ q' n# |- R
  26. }  * k/ J  j3 w' L5 g  h3 Z
  27. /**
    ( l# {; n5 h6 F/ ~: d
  28.   * @brief  Writes a single data. 2 s3 L2 T! w+ {( }- F, k3 T, k
  29.   * @param  Addr: I2C address 9 N% h: s! N! o0 v( m
  30.   * @param  Reg: Reg address
    ' C2 ~( ?4 g: _  x$ V
  31.   * @param  Value: Data to be written
    ! i% q/ D. b5 M- j4 n( j. c
  32.   */  5 A1 W1 H2 P3 |$ P+ {5 y
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  ' C& N8 `7 q. B
  34. {  
    8 u  E& r$ p, a3 d. _: @3 l' c6 i
  35.   HAL_StatusTypeDef status = HAL_OK;  + D7 ]7 ^6 P( S1 q1 `
  36.   8 v2 s  I1 `6 `2 M0 C3 q4 W
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  6 ^4 S4 G# m8 q. ~- R
  38.   
    . O4 s' b/ X" s6 D& ~2 ~3 z
  39.   /* Check the communication status */  
    3 |: n9 [1 c& r; h' o3 u* N
  40.   if(status != HAL_OK)  
    6 W  I. j* z& e8 J  L$ k' x+ L
  41.   {  
    % j: r6 L) z& x: B7 v/ b1 _0 N/ e) N
  42.     /* I2C error occured */  6 F" q/ F! w# U+ \
  43.     I2Cx_Error(Addr);  
    & q% Y$ P3 p7 N8 }
  44.   }  
    . s0 z8 \* Y7 M7 @! f% j
  45. }  
      R  ?1 V( H2 ?5 H. d
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    4 u/ L6 Y8 M4 s+ f0 y0 }- `
  47. {  / ]; c& t) R* N1 ]
  48.   I2Cx_Write(Addr, Reg, Value);  
    3 e/ ~: F& F/ ~8 k0 M
  49. }  
    ) A' T( M, j6 O- o$ X0 C9 v0 L5 S7 o
  50.   ! l) d! \" u% D0 U! K) Z
  51. /** $ u- ]/ Z, s( `4 a2 l$ ]% D- x
  52.   * @brief  Reads a single data.
    " I) Q) H' B# p: J4 F; w) A
  53.   * @param  Addr: I2C address
    & R6 M8 c  a  ]% G
  54.   * @param  Reg: Reg address
    + x* _* a3 l6 j7 w' K- ?
  55.   * @retval Data to be read
    ; @! _0 {; d4 K5 ^7 m
  56.   */  
    & r) g" b2 {" j
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  6 f) z% O* L: {
  58. {  , r- W; O3 Z- ~, w) w2 I8 C. r
  59.   HAL_StatusTypeDef status = HAL_OK;  
    / [0 @) w# I. d  b* A
  60.   uint8_t Value = 0;  
    & b0 y. x& c- J( {) e
  61.   9 ^$ O$ N# C* b$ ]# ?
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);    \) @4 Z# I  U- y, ~3 c! U
  63.   ! D* `6 J& a' k3 Z
  64.   /* Check the communication status */  ( L; o1 _: v; Y6 U
  65.   if(status != HAL_OK)  
    4 m! Z6 J3 R' m2 f, @
  66.   {  
    4 K, J( J2 s: z) @4 V
  67.     /* Execute user timeout callback */  
    " X: R- K9 i" n. u9 I0 p
  68.     I2Cx_Error(Addr);  2 P+ c9 a/ E& ?5 V! j  ?$ h
  69.   }    \& O, Z9 a' }& [- T/ F6 p
  70.   
    : t7 B# t$ j" v; I* y5 P
  71.   return Value;  
    ' B$ N* X* L5 M1 w- J& @
  72. }  
    # z  o3 S  G" M& L& R" L9 m1 ?6 f
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    3 z2 ?+ F: _( x) s! e
  74. {  # I3 ~- `, S7 c+ N4 E% ]9 @. V% D% _; _
  75.   return I2Cx_Read(Addr, Reg);  ' [+ a1 q7 G: r8 k& A  ~
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。
% Y" h. H7 n; \; c
0 K0 z; S3 E4 \3 Z就这样,就完成了Codec驱动与与HAL的对接。
  D/ c- E% f+ I7 V
6 f0 F+ J* v. a. [' u1 q3.3.2.2 usb audiointerface与codec的对接' S* w- }1 r' \' C5 ~# k
我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。/ X: R; I1 M1 \

7 e6 E; |) A, T* V& o按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:/ w$ F4 K3 i' ^& s* O
  1. [cpp] view plain copy' a) h. h0 h" I3 K
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  4 B4 I1 I& w: t8 V6 {6 }
  3. {   
    4 c+ G6 o$ @" E  ^3 {& t$ w( I
  4.   /* USER CODE BEGIN 0 */  
    ! X$ w$ c1 @) G! P3 a1 s
  5.   return (USBD_OK);  . X! R$ V, j# Z/ o3 ^+ D6 J
  6.   /* USER CODE END 0 */  * e: g9 J: q+ x: f; S8 J
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:
" p, j5 q9 u& c, B/ A4 F
  1. [cpp] view plain copy
    ( F$ f! E0 d* Q3 s( u( }
  2. static void I2Sx_Init(uint32_t AudioFreq)  
    4 J) V* s$ {+ r3 B& b" z
  3. {  
    . z- q! D7 u% H7 r  j; `* p3 i
  4.   /* Initialize the haudio_i2s Instance parameter */  
    ; r7 N$ a$ ]6 x3 q8 H% e
  5.   hi2s3.Instance = SPI3;  0 n9 A9 g) I1 M0 z: s
  6.   ! \1 K3 f- M9 y0 i; K
  7. /* Disable I2S block */  
    * }9 ]9 b( ]2 k0 v
  8.   __HAL_I2S_DISABLE(&hi2s3);  
    ; F! y& h) r* G# {- H
  9.   9 }9 M$ D2 p' o+ F7 h0 t( a
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  6 i0 g, O) W4 l1 J) @( o- I
  11.   hi2s3.Init.Standard = I2S_STANDARD;  + N1 ]+ }, v; V/ D" S) S
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    9 S) s& Z" x( Z6 V/ G/ |2 O( b" f9 X
  13.   hi2s3.Init.AudioFreq = AudioFreq;  
    % C/ ^  a- `: r3 A6 ~0 t  n2 H2 Z
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  
    8 p$ Y! Y# `* Z! ?: K
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  3 G+ E) ~" K' P$ d8 V% h
  16.   5 W* l$ r3 Y2 ^
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  + R1 V- D) \% [( ]3 F" W
  18.   {  - v4 \+ I/ i; n& Q
  19.     HAL_I2S_MspInit(&hi2s3);  
    0 k: ~$ u3 @* {
  20.   }  
      N0 Y5 d' A/ j6 E" X: L
  21.   /* Init the I2S */  + U( _; U  o) a3 n; T
  22.   HAL_I2S_Init(&hi2s3);  ; C" e, D/ q# Z% Z! ~* m2 D
  23. }  
    . p1 m. ]8 g& J6 t" z4 t
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  
    * t. S4 K2 t, U6 Y6 A# K1 v8 r9 ?
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  : V  X9 D; D9 G+ E1 O$ R( Y; r
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  
    ; B4 r& k' A# S) J6 C( j3 z' F. c
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  0 a* L0 x" `" z0 ?  d
  28. {  7 z% X' x1 ^' B7 B
  29.     uint32_t deviceid = 0x00;  
    7 H$ ~5 S/ |9 O9 j
  30.     uint8_t ret = AUDIO_ERROR;  
    - j5 W# B1 j  j
  31.     uint8_t index = 0, freqindex = 0xFF;  4 _2 z/ M; z8 o2 I* w
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;    G# _/ r, J3 j
  33.   
    0 ~' K5 {* K' ]5 a; n: L- d
  34.     //get the according P,N value and set into config,this is for audio clock provide  
    ; I, L# `' ]1 N/ m
  35.     for(index = 0; index < 8; index++)  
    ! T7 o6 b" ~) U# G& z1 `. Y/ ]& t
  36.     {  1 I9 E3 z2 m- f, I/ b2 M
  37.         if(I2SFreq[index] == AudioFreq)  8 u1 [2 I2 }1 m  I; a' Z
  38.         {  
    9 j" l3 \/ F: B6 w3 ~
  39.             freqindex = index;  
    % ~/ N- w& K  w6 q
  40.         }  % r- B6 `2 i2 {- M! d0 Q
  41.     }  
    . ^4 b1 `4 M$ i6 A9 m
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  
    8 o" ^, R+ |% a* p" M' f
  43.     if(freqindex != 0xFF)  
    6 D6 d% S' @6 a% r
  44.     {  7 H1 Z5 G* d3 g; z" i  p& B
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    3 B6 U7 h/ T; h5 V* [( K
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  . I0 g* j! v. V: l/ r
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  
    . A8 Q1 z' T  ^
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  6 ]; m& d! d- t1 b7 W
  49.     }  
    ) d* I0 G% U& a. B* T; e1 _- I
  50.     else  : r" `9 Z" K2 u5 W0 G/ i
  51.     {  , {$ }1 E( \0 V& _* H7 [2 E" p
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  + |8 ^: Y" v5 }
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  
    ' b$ s) [- q* u& O% W; y
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  
    * O+ m( }* t! W  U& M' v, |
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    3 R0 c% F) h0 p! @4 N4 T
  56.     }  
    & `$ v) t# Q/ g* J6 t
  57.   6 b3 V/ g7 J' ?
  58.     //reset the Codec register  
    2 o7 {  f- b( f$ j
  59.     CODEC_Reset();  4 J) M" K! u1 o( d: i4 ?
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  + f) ^/ D( Y# B+ _9 i0 h
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  
    $ t7 h. E6 d- I$ `3 q; c9 }
  62.       {  
    6 N' [' F) k, ]
  63.         /* Initialize the audio driver structure */  
    - K8 R$ y% r6 D9 t6 T. E
  64.         audio_drv = &cs43l22_drv;  
    ' j3 X  S4 S  B( ^* [0 Z
  65.         ret = AUDIO_OK;  ; Y5 w, Y# [8 G2 W) T2 D
  66.       }  
    1 M# n6 ~; G  y5 I5 D' _: S
  67.       else  
    3 x0 p9 V! o7 L: c
  68.       {  5 u5 ]1 \$ O( z% _
  69.         ret = AUDIO_ERROR;  
    / Q+ N1 u$ R) I2 H+ u
  70.       }  
    ! q% R- C9 z4 p5 }
  71.   
    4 f6 R1 ~- a, ]9 f1 v5 Q
  72.      if(ret == AUDIO_OK)  
    5 f9 ~  X/ b+ m
  73.       {  
    : c6 E4 p$ h2 q3 w& W
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  9 K0 t! |$ A! [) ~8 L+ w; @* p2 o
  75.         /* I2S data transfer preparation: , l+ i: k1 v6 a0 p/ s
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  : _) q1 e: @! U5 f' b4 l9 Z% }6 O
  77.         /* Configure the I2S peripheral */  * I6 o" {- B7 L, e* A
  78.         I2Sx_Init(AudioFreq);  5 ~+ J" A" O8 f  g6 x8 G+ R
  79.       }  : V, {, q7 c# f( X+ K
  80.     return AUDIO_OK;    R7 b' @- {0 K+ l
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。4 a1 E' s" G! B  d1 a: M& b
4 _: B( l0 W# ~: h9 H
这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:
8 Q3 ~1 x# O( ]3 Z) N% C' Z8 n 16.png % e6 K! C) [$ ^/ l! R; D- D$ @

. r6 y2 l; J' `( _* l( V在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:2 _) V/ `1 h' D3 e% s  s; o0 Z$ S
17.png . m" i* H8 |: G% g7 |8 O% P
也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。
2 W, W5 {: n% e0 c5 m/ @+ r8 H! p9 N: f! `$ c
PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
* m6 i1 ^5 n2 T  g! U" e" ~2 r" N) X# V& T0 d' [
搞懂了这些之后,我们马上将其代码进行对接:. Q0 f4 {# w1 B$ s
  1. [cpp] view plain copy; X! r2 A4 m+ g2 ]
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  / I! s7 M2 `- l. A! \
  3. {   
    ' o/ B) E5 c+ E. C, c
  4.   /* USER CODE BEGIN 0 */  
    ; G* C% @. W# `( o& Y0 w" ^
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  
    - K9 D* O! k2 Q3 Y, J9 S
  6.   return (USBD_OK);  
    ' ]9 w9 j" K  k
  7.   /* USER CODE END 0 */  ( V6 P8 @( l  M$ ]" h  g
  8. }  
复制代码
接下来下一个需要对接的接口:+ S. H9 I+ _1 T' [7 a% L9 r1 [
  1. [cpp] view plain copy/ Z4 I! Y3 b( o- N8 c. q& h- h$ {- i9 T
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  
    % P  [1 q  F, D
  3. {  . J1 y5 ]: S! ^. E( ]2 N% T
  4.   /* USER CODE BEGIN 1 */   
    % g+ Q  a6 Z0 V9 I2 N- o
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  + U& k% U# s1 E: Z% D( Q* U  Y
  6.   return (USBD_OK);  , P; y% i8 A/ K. `
  7.   /* USER CODE END 1 */  
    : d) `: F; ]: w. @) w
  8. }  
    ( w/ f. W/ B3 M; ]* f1 x
  9. 很明显,这个个反初始化的接口,它的具体实现如下:) O4 d# B, Q! V+ L
  10. & Y4 T$ i+ p/ k6 \# e; z
  11. [cpp] view plain copy
    8 H0 q  `. I$ H
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  * Q0 x5 `! j9 ~, F3 X
  13. {  & U: I  X. Z6 z( \" Z0 ?
  14.   /* Call the Media layer stop function */  * U, L5 J, ~/ J8 l5 f% M
  15.   HAL_I2S_DMAStop(&hi2s3);  * Z: P5 q) d1 K# k
  16.   2 z; U3 M: Y, N9 q) H5 w
  17.   /* Call Audio Codec Stop function */  
    & j8 z% T; |" w' i( l  T
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  3 \* n$ C" _4 C/ p+ d0 J4 _! E: a
  19.   {  ! H, l& e! m( L; N4 x! h, O2 m. }$ S
  20.     return AUDIO_ERROR;  7 b# E7 I$ `5 ?* w  [1 \& X
  21.   }  
    + P+ |4 p3 n% |" e
  22.   else  9 ^" e: O% S7 D% h) E
  23.   {  $ u+ z: h' W* U5 H! @! H) Q0 a
  24.     if(Option == CODEC_PDWN_HW)  
    ) H  C6 V, F" F3 T( `) o8 c
  25.     {  ; s6 l" j, n0 x  A' h% l+ i6 \% b
  26.       /* Wait at least 1ms */  
    0 U9 S: G$ m* O" S- S3 ^$ l+ \& y
  27.       HAL_Delay(1);    n2 n  j! _) b$ S3 Z9 Q
  28.   
    + W* o7 O& D$ {$ F7 y
  29.       /* Reset the pin */  
    9 |0 I  B, }5 L: W
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  # U! j* X1 r* {" n' o5 U2 ^% x2 F
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  ; s/ H! u! k' X# u9 P/ j
  32.     }  7 t: e+ Z9 o4 u- U, p$ q  [' Z
  33.   
    , g/ i' m; `; S9 L* c& h
  34.     /* Return AUDIO_OK when all operations are correctly done */  4 C* s" ^* x% [4 h- N( A
  35.     return AUDIO_OK;  ) X$ E7 m2 R* a# l( Z: U
  36.   }  - E9 i1 `( R+ M1 C9 T+ B
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。
& P9 C1 a7 e$ c9 E( l2 Y  }OK,下一个:" u1 R- n4 Y! k* [% H5 F- [: {% A# e$ a
  1. [cpp] view plain copy2 y$ r3 k5 O+ V, @9 D. k1 M6 ?4 \
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  * J9 H+ [( V1 p1 U2 J% v" I% B; N
  3. {  
    ( j2 J5 H# }% `+ N9 d
  4.   /* USER CODE BEGIN 2 */  * z9 W) R% u) z1 u, `
  5.   switch(cmd)  - o1 O8 a- m, ~7 @! M2 J
  6.   {  
    : q- j% f4 p( m: H$ O- R' P
  7.     case AUDIO_CMD_START:  
    ; r- E& r0 O9 R! F
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  ! p# u( _# j* P6 X; P; I8 ]. o. C
  9.     break;  & a8 F- y  E: _( O" E! @" i6 K
  10.   / J0 }0 f& V. M1 C4 O9 Q  M
  11.     case AUDIO_CMD_PLAY:  % ^% u. f- q3 A7 s; c' N' X
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  $ P, w4 z: X) w# e+ W
  13.     break;  
    : V  d$ K2 x- I2 B
  14.   }  ' ^3 J$ T. ^! s9 S+ V8 p
  15.   return (USBD_OK);  " c4 z7 n2 r% E4 @# {
  16.   /* USER CODE END 2 */  7 i: A2 X7 P4 ]( c% ?7 V# r4 w% p! [- d
  17.    
    0 I. p: q$ ?( {( a: R1 p
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:
0 v- i1 j9 Y: Q
  1. [cpp] view plain copy
    # e$ [' U/ N# |8 ^8 {  `5 d+ c
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  * R1 G) _4 j" E, Q0 G6 A. p
  3. {  
    : [/ P) \- i6 i" I! V
  4.   /* Call the audio Codec Play function */  0 }0 }/ ~% J7 \, k- [
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  
    : b) d- u4 t" f- t, U
  6.   {  ! ~$ `  }2 K2 V- ?
  7.     return AUDIO_ERROR;  
    ( S- d" E6 ?( C! H
  8.   }  7 ~. J/ C! X0 p! a& p4 m# e1 j6 u7 }! J
  9.   else  
    1 }5 S+ h0 Y9 ^0 K; c. H3 X$ C1 R
  10.   {  9 R# u3 T3 h2 u3 t* w/ _/ u
  11.     /* Update the Media layer and enable it for play */  + X$ y" \! C9 z4 o
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  
    $ l$ N2 ]" Q6 a3 q
  13.     return AUDIO_OK;  
    2 p" j! c* h  E! l  q# j1 v1 A; i
  14.   }  
    * ^: t: ^9 @3 S# T1 ]3 U6 \! Y' `
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。
" }: Q' y* m$ k- B
* @! R4 @# n% y7 W# A/ B然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
8 d2 X& i% y. L# O1 f0 `
  1. [cpp] view plain copy) `# }. `1 p3 I- T; F5 ^
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  3 Y8 @1 \1 ]2 U# o5 [9 K7 V$ F8 R
  3. {  
    / N* G: o" C% ]! @4 K5 Y
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  
    " h& C6 m; N0 J" e6 O; e, b
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
! P1 S# G. i' |: q$ E* q& [( g0 o% W+ M) A0 {$ Z8 H
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:
2 r5 V; k% T8 x/ |  J2 k
  1. [cpp] view plain copy
      K5 v8 ~' M1 [( ]
  2. void TransferComplete_CallBack_FS(void)  ) s* P) i# u8 b$ g2 C
  3. {  3 h+ M* l& s4 Z4 B; B
  4.   /* USER CODE BEGIN 7 */   
    6 B6 H; s  d8 R9 e
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  
    4 ]  @1 i# j5 ^+ `7 a7 C* U8 Y
  6.   /* USER CODE END 7 */  - m& W  A# m) q: J7 {+ F' u- c1 R$ @
  7. }  
    8 B; O+ H% b! N7 |: P! ~
  8. void HalfTransfer_CallBack_FS(void)  9 h2 u. H7 f6 H4 ?/ `1 P
  9. {   
    $ ]/ F6 A: y* e
  10.   /* USER CODE BEGIN 8 */   7 Q0 |, P8 @0 [' f, A8 J8 B
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  $ Y- n! b. _& E& }
  12.   /* USER CODE END 8 */  
    & z: M* X8 A. E' W: o
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。
/ U) [( A' I6 H6 k0 U6 v* R/ p/ R
+ f) b! R1 Z- R) u) M2 q此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。
& m" A7 @2 g& Z5 s. A4 W
+ J6 y) f2 l: L  r* F5 J接下来看下一个usbd_audio_if接口函数对接:
+ I' @  u- F& \, P
  1. [cpp] view plain copy
    3 V0 M: m/ K' G+ @  i2 n3 p+ F
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  
    3 h/ v, x, p1 B7 B
  3. {  
    ! N+ I0 `; r% r8 s: H
  4.   /* USER CODE BEGIN 3 */   
      S6 ^, {5 y$ `- T8 ]; f  ]
  5.   BSP_AUDIO_OUT_SetVolume(vol);  
    , h1 t, z+ n* ]0 k
  6.   return (USBD_OK);  
    & p- V$ I$ ?5 L+ i3 X5 w  e
  7.   /* USER CODE END 3 */  
    $ x  ]  L; s4 ]+ V
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:
7 Y+ y: n% p1 }
  1. [cpp] view plain copy  l) O2 g6 e' e0 k, Y+ p. J4 t0 t
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  4 k3 ]  X1 u& M& V$ _
  3. {  * n3 A  K- z) m$ R
  4.   /* Call the codec volume control function with converted volume value */  
    - }% B- y  {$ Y9 m1 I3 m$ c, j
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  # y( ?6 I/ y2 j( y/ v' a7 |; v9 ^
  6.   {  2 o& o) d6 W- ]
  7.     return AUDIO_ERROR;  
    7 e1 r* A$ b! s4 H& N9 c+ Z  W
  8.   }  
    ( v' `$ x. U0 M3 H* i
  9.   else  6 R5 j! f# X& x7 P. m# M0 Q
  10.   {  
    4 ^1 x+ i* X- [+ F9 D: U+ `
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    2 m5 x3 z( i' v: Q8 s
  12.     return AUDIO_OK;  2 o3 L1 u* |  c8 w
  13.   }  
    8 m; ^& }- P* T5 k2 `
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。
7 w1 I$ H% Z* u5 I6 W1 p, @4 R. D7 g  _3 r, s4 Q7 S
下一个:, t& N0 i  G7 f5 M! I8 L- `: l
  1. [cpp] view plain copy2 p" s% q* d0 y
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  
    * w" u) u" V, `6 |
  3. {  
    & @& T2 K+ y3 q9 Q5 n3 n
  4.   /* USER CODE BEGIN 4 */   ; W$ G' W6 Y0 X) S5 b% G
  5.   BSP_AUDIO_OUT_SetMute(cmd);  6 P$ p8 @3 r" C7 E8 l, w  m" W$ v
  6.   return (USBD_OK);  , P* W0 e; B# t" ~
  7.   /* USER CODE END 4 */  : M& U6 a7 q7 [/ O
  8. }  
复制代码
静音控制,其实现为:
! L4 }$ |: F0 a. V* N, i; {/ g
  1. [cpp] view plain copy0 ^/ Q4 z& z4 H. F/ P
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  
    5 y9 C8 F+ R& f6 w! P; ?
  3. {  " @3 v$ l6 }" L0 i- g
  4.   /* Call the Codec Mute function */  
    % Z. a8 V! \( @8 Y
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  5 |: v, G& e$ ^/ S; Q5 s
  6.   {  ( ?5 y/ H& H( A+ o) J
  7.     return AUDIO_ERROR;  ' ~6 ^  U$ j  X: U6 A
  8.   }  1 ?, ]! Q; r: u( \. l2 p( c
  9.   else  ( K& O1 }+ ?! D
  10.   {  ; @; @/ x$ J- z6 R
  11.     /* Return AUDIO_OK when all operations are correctly done */  - E" A0 \) r- t6 u
  12.     return AUDIO_OK;  0 z8 v6 Y! i/ i3 h4 M
  13.   }  2 s6 ]  C- \4 T. e" i' g
  14. }  - Z' J( I/ C- R+ ?% P0 j
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。
) j2 X9 M2 o$ E. u* p- h2 Y4 e" q4 Z3 C9 V& r
OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。, W) b! f+ J9 k. B6 B9 k% E8 F) a
9 b$ c" `, m! i4 B% Q6 {- C
4    测试验证8 ]7 N6 j0 F* N3 e& {8 D2 O
将代码编译后烧录进STM32F4DISCVOERY板进行验证。5 A' ]* V) L2 s3 W2 S7 }
18.png
2 S. z' R9 X1 V3 W7 w- X" g最终验证是OK的,可以从耳机上听到PC端播放的音乐。
/ ]5 K# \6 X  L2 q6 k4 p: T2 V* r8 C4 C: P
5    结束语
6 ]" B) i; n, U0 a  r9 I在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。
: ~# P& X; h2 e- T$ l- P5 a1 a, `! p/ O1 A+ n
I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。
" o6 ~$ a! V4 |, Y# D
0 A) Y4 U+ g3 q5 l7 h在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。
. V+ i5 D4 ?% M3 u" x( t- H7 g- t& N9 x" ~8 j+ L- g

  h/ o: t" x  J6 `
, K" t: D* r7 u% A% \4 u
( J* K, E7 ^* |

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?
" G$ _6 E5 K" n  ?# J" C/ S# v1 |static uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,' o/ I5 W1 {; X, N- C" Z
                              uint8_t epnum)) e% g' k8 W  }3 r% j6 m, A1 P
{$ e. @7 _, ~1 E5 g
  USBD_AUDIO_HandleTypeDef   *haudio;
! z" W2 ?- l* `; k5 [$ ]$ d1 r! }* Z  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;
0 x& \* a9 f# l% O1 \
  b/ w2 o& h1 t- `& y- O  if (epnum == AUDIO_OUT_EP), Y/ ~0 L7 ~4 x6 ~
  {
: T2 }- s' ~* F    /* Increment the Buffer pointer or roll it back when all buffers are full */2 J. b% u' F  A, {: X6 S: `8 @
. H0 w+ |* K9 Q: g2 ]% C
    haudio->wr_ptr += AUDIO_OUT_PACKET;$ B* C7 J) L$ k$ C& r  Y

/ ?" s- H3 A. P# ^( Z    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)" H: q, W, c( v# B- w
    {
" z$ x3 H. Z9 H- k& p6 ^- i8 A      /* All buffers are full: roll back */2 Z) B) e" h8 v2 ~* {
      haudio->wr_ptr = 0U;
6 }& |5 v% }. V9 ?: Z  [    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],1 r4 i0 H0 {4 N0 z
                                                 AUDIO_TOTAL_BUF_SIZE / 2U,
7 C# n3 T: B$ x) P7 h1 u                                                 AUDIO_CMD_PLAY);
* W3 u! O6 W2 f, W5 ~8 P% l* w5 ]#if 0
; r; w% R5 I3 p" j2 w3 Q      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)! j- z( ^2 ?& `/ n# x
      {
( ?# B1 q# b& K, X4 k1 |        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],1 T! {8 G- e4 ]# `4 }7 K6 I
                                                             AUDIO_TOTAL_BUF_SIZE / 2U,1 c" Z5 |6 F: F9 O
                                                             AUDIO_CMD_START);" m( g' X( y3 w' {
          haudio->offset = AUDIO_OFFSET_NONE;) L! B" q$ Q7 J, k4 |
      }; z% f) _: o+ `$ P# p( Q1 k
#endif
1 g' q& g# `/ i5 M    }
8 o% N  }. Y# i3 s5 e! K: R% ~" i( P- O: t) p
    if(haudio->rd_enable == 0U)* s1 e! I9 \; k! h
    {9 I" E/ T  p, ~; t' E% w! w* S% V+ \- H
      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))* w+ c. \- x/ o3 q# W( E! b8 b
      {8 `0 R2 t$ E1 ~9 H
        haudio->rd_enable = 1U;
) @3 j# ^% Q: f) \) G/ n0 m3 r      }2 Q& d- |0 d- u/ [% l( A& u
    }9 B2 ]2 P; c4 T2 a; A" b7 g

+ a1 v6 [3 B, _2 M- x( g' d    /* Prepare Out endpoint to receive next audio packet */
- d$ J; P& I# f    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],
* Q1 L/ N! `& q                           AUDIO_OUT_PACKET);
- d* r  O$ @6 |' j  g  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?
3 J6 T6 b8 [+ p- w$ S' _: E我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,0 Y) F* k* {9 z
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。9 {4 G+ y7 t  r- C2 J
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,1 W! P9 Y8 P! G+ U8 Z1 f( C8 N% P
但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,
0 a4 |# u* q, {; f- i& i而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20
! B  @, l! D' w' n, |这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
* G! K& N6 W1 p  Z0 X& Y* N
' G0 j/ X# y+ d" W: V' @该设备无法启动。 (代码 10)
2 r8 f3 O1 H+ |* U' l( Y+ d
出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
* u+ q# I& O3 f* ], \3 P' T非常好的帖子,谢谢分享!!!
5 ~( s; C" I1 L5 g5 R- P" `
希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
* f1 `2 A  T% ]& _, [' h( d5 M+ L8 R9 r非常好的帖子,谢谢分享!!!
3 m% V' \" S2 R6 R7 s! b
希望能有帮助
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

- u' y  x! @2 q* Y1 n- k非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!' }' v$ G1 }6 u/ j

, S5 q: g1 ^: X+ v+ q, ~

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:180 G% k4 Q+ y$ `. L" b
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!5 z/ ?0 }9 @3 s

7 N' @0 [' \4 g! ~' \$ k ...

5 z1 @/ i) n7 x' Q( U2 d这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示, z" `* X. `. p3 w: t& S) ~

! u3 f- g6 o- D8 t- O  G/ A该设备无法启动。 (代码 10)  l, k7 Q! l$ D
) C, Q! Z7 p7 P0 A* y
I/O 请求已取消。& d, d8 |) i! b. r" y
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20+ \# ?  _( N# ^+ s
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示" J' P, j/ n# ]( Y
+ k0 ]7 w0 E8 T$ b
该设备无法启动。 (代码 10)
% A1 ^: r& r) Y2 M: \2 ^0 k
经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版