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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑
/ S0 L. X+ m( ~+ T* Z7 n/ p5 e( Z4 ~. v$ P
1 前言
! b/ T; r( M# P本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
! a: e# v2 E1 G% ?/ w4 c$ }
2 M3 P( b& o, b2 设计构思. d" k4 u$ Y2 G; L2 i/ v0 L3 @
所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。
9 J/ B2 B6 u) W6 C6 @7 ]5 s# N, C0 z1 U' d& u$ O& Z! g
2.1 从原理框图开始" ?, n: y; q( r
1.png ; `4 Y$ c. r' m+ m* T' X2 ^
如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。' p9 [: q+ j. I9 B3 v4 [' y' D

7 l0 h; L- V$ o% o4 ~4 a2.2 硬件支撑
: \- B& L, I! A这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。
1 S6 N, x1 [  P8 f5 Q
) l! }6 T: W9 n3 ~7 F2.2.1 USB接口
0 J, d3 m" ~* Z) J/ @如下图为USB接口部分的电路:
& _. w* R0 I0 L  r# g2 c 2.png
0 [" {: u# }4 `5 W9 o1 M% ^1 h这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。
6 D4 q3 b* {& R9 h# a2 r1 e' k/ w- ^
2.2.2 Codec部分3 f# T' z0 V2 {
如下图所示:
# O: z, g/ p! C( R8 r4 @ 3.png ! T* r; F$ ^$ p0 N
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。
+ z% B5 l/ e0 N5 F" |/ Y
6 w, `3 e5 B$ ?* P2.3 软件设计( F8 b0 ]" c( {$ {8 T# a# c/ y$ A& Z
为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。" t9 L7 S- l) x! j. h
4.png
. z4 q: J1 A6 \4 Z! ~4 n( o+ |0 C! C7 s) a8 ]) J9 {5 t- m
如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。
) }' y" f( {* B* H$ l# K7 F
* s( `* e2 L8 x4 c0 E- f各个模块的工作流程如下设计:0 v/ u5 J2 M* e% o+ m# w. x

5 ^' o( X/ p  h% d7 |$ u; Z 初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。; ^! C# y, @  n/ r! E; F
USB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
& _# n4 k% I4 I1 S; @8 K  CUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。
; N0 b3 v5 H# j8 c; O( b% ^从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。
7 \3 a5 D% P4 |( W" X! g6 t# r+ q# E! f' y8 \
接下来,我们来看看软件层面上的实现。
( f3 D1 r; u# i9 @8 F; ]7 v+ [' F  ]/ x3 z1 V+ K* C
3 软件实现
# H* m1 }/ X" A. d- ]还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。3 O6 [8 X- R& Z+ t0 L' l

6 z; s5 b- E. j# q" p4 H' v3.1 创建CubeMx工程; q% Z6 _' \) ^- A5 a1 I. R5 X$ k
由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。) e, I9 z, @4 Z& B

1 h! Y  h, X" R( `pinout:
" x, g5 Q2 g. C
1 z' C- F! ^- w0 P: S- g6 M( ^外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:6 `3 L% M8 @2 K2 H+ @) c
5.png
7 B# g" e2 S* K1 b# D1 e7 ~8 ^
3 v# k0 G. D9 j+ D# J1 L9 X" ?Clock configuration:# C, l  H9 ^5 m5 Z1 ?
6.png
: a7 b* t8 j+ l/ O$ x时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。
9 ~; M4 M1 S: a3 G7 t, i4 s- C% p  {% d, A
Configuration:- S" z& A- k5 U( L/ t
8 {0 I, f; i. `( g
  HAL层:) e% ~" D! U+ K* [3 s# F# c
Usb_FS:使用默认参数。! D" D4 q8 U+ v! q/ h0 {
* N4 V3 X2 h& K  y( l& k
I2C:100K速率,7位地址宽度,使用默认参数。
  w/ V# Z" V+ }9 c- y9 }. i2 ?/ `9 J. g$ E* P
I2S:主发模式,标准16位宽,默认音频为48K,如下图:
  B' \9 }  S! ?- A8 V 7.png 1 t' w  T0 \, L3 P  t
并为I2S发送添加DMA,半字位宽:
0 \# ?6 N& L2 l  w/ G" a 8.png " g2 P  J" ?* e! x5 |  ]
8 j, s% [2 a# J# c3 W  _) O
   MiddleWares:
  D# D* g4 j4 t( v! ^3 O; p2 l) M5 s4 `; _
USB选择Audiodevice class,其配置参数如下:
# q% o$ B* t% _6 \" k: S9 s, m 9.png
' a6 R9 X; I3 Y& A' b
7 q7 _; A4 l8 S( g+ m$ L这里都是默认参数。
& e/ e! y2 b0 S" b7 t 10.png
, x  X) p! B. _# \在描述符参数内得为usb audio class修改两个参数:
) X: k- B0 ]. ^8 K6 T! ?4 d* H. W8 \& T0 B* Y8 x/ [' ]7 p1 e
PID得修改为0x5730(否则windows驱动会加载出错)
; ?1 ~+ ?2 {5 H' I4 j 序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。
4 v) ?: ?( e: t4 p- A5 Y) k最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:4 H, N  D0 F: e6 I& ]/ U
11.png $ ?7 [. J2 F$ r  `
如此就可以生成工程了,我们生成IAR工程。/ f) e, A" p- _! h! e
2 |" r+ A1 C3 I, I5 e$ c
3.2 生成的IAR工程介绍/ V3 E7 g# q/ }. E( z! U7 ^) k0 v: u
12.png
, e, W! S5 x. e% R! V如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。
, @  ^7 [9 [: V* j8 X; {. {$ ^' K
5 f5 ?; B0 @1 _4 y! B# T9 C' m  B9 m9 J. i" SUser目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。
8 s5 m* x( Y/ u6 pMiddlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。
3 V+ ?8 T2 b' D( s5 x# y$ u- V" Z, kDrivers目录对应着HAL层,它包含CMSIS,HAL驱动。
: O7 M# U& z- t0 Y
( w5 k, S. l0 i2 ^+ {2 B, n3.3 开发4 Q  M  r( R/ w# R+ \
3.3.1 初次编译测试
! B% c; Y' c9 B$ |0 Q- }9 O首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
% [) [0 m( g; K4 v0 r$ u) z! @ 13.png
0 P% g' T& {* O& \这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
/ j3 {' g6 `% m+ z" q( l0 j8 o 14.png 1 e+ v+ H0 J1 u% f) J
这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.
6 @2 N) P; b  }
$ R$ g) C5 S8 V1 L: o5 i! Z# s3.3.2 添加codec驱动和audio bsp模块2 [; e5 U0 {: W
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:
$ i3 V3 m% V) a+ x5 r 15.png
1 E) y& l! C5 _其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。( J1 j3 i% g' ~' j7 i; B
" u+ O( V+ D( z2 h% x4 d
3.3.2.1 Codec与HAL的对接
0 X5 E$ R) w! ]$ L/ A首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:% f, C1 v' V) |* O! P: }' \" f0 B0 I9 k
  1. [cpp] view plain copy
    2 l" Z. F0 W- \' T: U
  2. AUDIO_IO_Init()  
    1 m5 w, D1 ^' }0 @! F
  3. AUDIO_IO_DeInit()  $ ~" D. }. G- W- [2 M' x) ^6 I
  4. AUDIO_IO_Write()  3 M7 _$ Z; ^5 M4 U! s. k
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:; X( J+ i8 {) @% y$ Z: J0 Q
  1. [cpp] view plain copy
    ( q  U! A* D7 ]! C2 Z- ?
  2. //---------------------for c43l22 port--------------------------//  
    ) }3 |. E9 u& X: ^* P1 p
  3. static void I2Cx_Error(uint8_t Addr)  
    2 A% n( o8 T6 T, z& z% Y
  4. {  ; e# N  G8 l' H+ y6 i, x  y
  5.   /* De-initialize the IOE comunication BUS */  
    7 c2 T  y' K9 X  j' t
  6.   HAL_I2C_DeInit(&hi2c1);  0 r( b: W4 R( Q3 G8 [
  7.   . N+ }9 r' G* |! @9 k# Y: d4 C( z
  8.   /* Re-Initiaize the IOE comunication BUS */  . {# Q  g0 Q5 y# _" n! H  a8 F
  9.   //I2Cx_Init();  : n' _* R5 q  ^# P% W) P$ X
  10.   //MX_I2C1_Init();  
    4 D! L" R3 m) B/ L
  11. }  
    * u0 [' L# H; T
  12. static void CODEC_Reset(void)  
    1 C" x+ U- M' ~9 C2 r- f
  13. {  % d& }9 ~1 X! P
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  # g) n1 ?9 G# m7 K$ F5 q
  15.     HAL_Delay(5);  " \) |+ `' L& x2 q& C
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  
    $ e, v; X$ b; {5 A
  17.     HAL_Delay(5);  4 c. y! j9 d9 j* m. T
  18. }  # z) s8 z0 }3 f' j- j1 Q
  19. void AUDIO_IO_Init(void)  
    ) d/ G5 k* |7 Y$ r1 q: x1 X4 W6 q% j
  20. {  4 j; t; o1 }* `& q" m* m
  21.   //I2Cx_Init();  
    1 b$ i" R1 B' H! G( s- Q! `
  22. }  2 P: l$ j8 U/ A9 g
  23. void AUDIO_IO_DeInit(void)    O, f9 o; v8 x( c
  24. {  ( ]$ q* P  }6 A) t0 A7 u6 [
  25.   , f  a+ {  Y/ u& H5 n4 A
  26. }  & ~: `. x; P) R- T% F
  27. /** + x* H1 s% s4 a7 A, Z- ?) }
  28.   * @brief  Writes a single data. 4 D; T% v# @% [
  29.   * @param  Addr: I2C address 8 \  G% w# }( ~% d* U
  30.   * @param  Reg: Reg address 8 u% A- P+ A7 j/ ~& J9 @
  31.   * @param  Value: Data to be written 5 U& C  G: c7 b0 y) l$ a
  32.   */  
    # P9 U) n; _  Q  J* L( E: G6 k
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  % Q/ n( w: I, g4 }/ B. [
  34. {  8 K3 S* X" C/ F0 a8 m
  35.   HAL_StatusTypeDef status = HAL_OK;  $ y5 j7 @0 P! K: W+ C4 }: H
  36.   / a$ }9 O5 S5 B8 \5 w  T4 e+ z$ f& q
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    0 g3 j0 l) G* d. Q# i( a! u$ G
  38.   5 K1 O% W# Q& X* d5 N( E  L9 Y# [
  39.   /* Check the communication status */  * H* `1 {( }3 |9 E# y
  40.   if(status != HAL_OK)  % F+ y9 ]1 v1 _( l
  41.   {  & q2 H! Q2 c# ^* x
  42.     /* I2C error occured */  0 X- `( U# [1 X: X  e$ b
  43.     I2Cx_Error(Addr);  : C( s, D1 ]& H8 o1 Y  B4 H5 R
  44.   }  9 U2 t7 N! K/ F( |) |
  45. }  9 l4 y( y2 j1 s* L
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    " W/ c2 K: a( Q% Y, ~
  47. {  $ f7 K/ \+ c! g) W, T( M9 T
  48.   I2Cx_Write(Addr, Reg, Value);  # R: N' a: }' N, S5 K" O
  49. }  
    4 X3 b( J3 \) c. T8 I+ V
  50.   
    : e9 I5 n( _, N
  51. /** + R: x3 S3 F* j" ~% U4 m1 d
  52.   * @brief  Reads a single data.
    4 F4 @) i2 t2 h- i6 K& g8 T
  53.   * @param  Addr: I2C address
    + A9 f: G! V/ R8 \1 `
  54.   * @param  Reg: Reg address
    / `. f& Q+ k  S0 b6 d% O
  55.   * @retval Data to be read $ L0 s0 J  m) Q! J: v/ I7 K8 v6 j
  56.   */  
    3 |: ~- m; V9 A( v, d
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  2 z; c- l. K1 C$ ]& W
  58. {  . e2 ~4 `4 x6 r( N
  59.   HAL_StatusTypeDef status = HAL_OK;  ) L; g0 P: |% w3 Y: Z, g
  60.   uint8_t Value = 0;  
    - N1 y/ ^* a2 _4 ?$ F: @
  61.     m# b. l3 n( X$ S
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  5 N8 k) b" P2 @
  63.   
    0 [5 Y5 C( @2 A1 A
  64.   /* Check the communication status */  8 |4 z2 W3 E- B8 K" ?$ o# c
  65.   if(status != HAL_OK)  
    " V* ]3 [1 Y1 I" r
  66.   {  # }3 O# t* q3 v1 m: `8 F! K
  67.     /* Execute user timeout callback */  ; E4 }6 R# O/ l& B) f7 d
  68.     I2Cx_Error(Addr);  
    ! t, i' P4 j  }7 e5 Q
  69.   }  % i: T3 O' |1 O+ l- d. J
  70.   / a" ]1 k$ T$ `4 k2 z, }
  71.   return Value;  
    . A2 ^; ~% r' S/ a$ w( G( x
  72. }  
    1 S' W! U2 F0 r9 u! s* n
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    2 o+ m7 ?5 N9 e' Y! G+ x( [8 l
  74. {  
    , f1 \$ k2 w+ `2 `
  75.   return I2Cx_Read(Addr, Reg);  ! j3 J% J; |  p% z+ [5 a3 K
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。
8 P% z1 ]- }. O7 f- e5 A& n
3 r8 c+ t+ T6 [1 [就这样,就完成了Codec驱动与与HAL的对接。
, j7 b, d8 x. S: [! J5 I3 r. U. j, B) a# F' l
3.3.2.2 usb audiointerface与codec的对接0 O9 K/ ~) H- [9 K' y
我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。
- G  ?7 Q1 B+ ?4 b0 z8 o/ R6 D% Q5 F
+ k' l8 u3 k8 S" F) p" N& x5 s按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:6 ?3 `/ L0 r" |* c* ]
  1. [cpp] view plain copy
    / l0 l/ ]5 }6 O: f0 D5 M1 M
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  1 S' K- _" ~: s" q
  3. {   : N- V, G. e6 i: r2 X4 j3 K3 R- J
  4.   /* USER CODE BEGIN 0 */  
    6 B6 T) U' r5 O* _8 T
  5.   return (USBD_OK);  ' e8 Q3 l* A' {( t( H" a2 S
  6.   /* USER CODE END 0 */  * |& E. r( w1 L* k3 t
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:2 `  {- K, Q) p6 C5 r' n6 ^, D
  1. [cpp] view plain copy# `8 h/ f; c# V; M
  2. static void I2Sx_Init(uint32_t AudioFreq)  1 l9 Q+ [# d: v& c) O
  3. {  
    ) _5 D0 j! o+ h5 J" ?  |8 j
  4.   /* Initialize the haudio_i2s Instance parameter */  
    ' C+ H: b- T, y/ B* Y; C
  5.   hi2s3.Instance = SPI3;  # W' p) L  q, U8 H) Q- ?: q) u
  6.   
    ) U; J, D$ W8 I+ R' X: c  H. ]  o/ J, Q- M
  7. /* Disable I2S block */  
    0 _4 A0 d  Q7 I; R9 v* Q" U
  8.   __HAL_I2S_DISABLE(&hi2s3);  4 m0 |$ |# g4 o+ T+ o+ k; W- k
  9.   8 v. P( B5 _1 o( T1 X4 y# z
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  & H5 i" ?7 [6 r" F8 ?
  11.   hi2s3.Init.Standard = I2S_STANDARD;  
    ' F. ]3 L% L( y1 y5 o- ~& @% K
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    " S( z- }! Q0 c) l$ @  p( m
  13.   hi2s3.Init.AudioFreq = AudioFreq;  
    2 L0 E9 I: W$ e& k
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  
    / g; v5 b' |! m0 D3 P5 ?# o
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  
      \: y/ @9 }* m5 I  o3 H
  16.   
    % o- U( W$ S) {  o
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  . d  Z6 O' f3 P8 _5 |' Q
  18.   {  . }* q7 c" l' W+ U8 G7 h
  19.     HAL_I2S_MspInit(&hi2s3);  0 x' f, N; P6 k5 P
  20.   }  " T# d0 O' m- e5 V% Q; y9 c* _
  21.   /* Init the I2S */  
    % K) \) `' N& ]/ \; Z- k* d) _# a
  22.   HAL_I2S_Init(&hi2s3);  8 I8 y; t* D; M
  23. }  
    5 i1 K3 G+ c4 s# {0 n% ?
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  5 i0 d1 U4 y1 N+ h2 w, o
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  4 e9 q7 T+ U4 ]6 z" n( |
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  
      _9 J0 H% E1 t; q( ?  M0 c% G& g
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  
    . i& H% P! u) K1 w! H$ V
  28. {  * E6 h" V2 ?9 Z$ p2 I0 y+ O
  29.     uint32_t deviceid = 0x00;  
    ' P- a0 ^" y. I$ j- U
  30.     uint8_t ret = AUDIO_ERROR;  
    ) c0 I/ f% I4 x  n! h. ^
  31.     uint8_t index = 0, freqindex = 0xFF;  5 ]. W$ H, T2 L2 V& s
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  
    ) E: _! |, t$ y  i  n
  33.   
    8 r7 X3 l% [! H) X& ?
  34.     //get the according P,N value and set into config,this is for audio clock provide  
    9 v9 S. g* Y1 {$ b" Z5 ?
  35.     for(index = 0; index < 8; index++)  & p, Z; A% h0 T) T$ v
  36.     {  
    & H0 \: z' R; W# T" T+ @
  37.         if(I2SFreq[index] == AudioFreq)  
    ! @; r  z' I2 H8 \7 J. G1 ], Q. \
  38.         {  $ Y0 D% t9 d% V2 J2 _7 ?+ G" b+ c
  39.             freqindex = index;  
    ( I# L( E  O' z% z* B
  40.         }  # u1 P% G0 R9 L: |8 f% x
  41.     }  - _# w/ _- c" g* d, ?9 c
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  6 j$ T7 Z. V9 a0 n8 z% t  D# P' Q  p
  43.     if(freqindex != 0xFF)  + F% W8 ~6 B5 z: @! y" J
  44.     {  + Q% T3 r) ]/ ]% h
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  $ J$ Y, j6 {3 t$ f4 k' n8 Z
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  & A+ M7 ^7 M0 e* L0 g( \! [4 X% l
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  % s' ^$ \4 i* J2 E
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  7 C- r  a4 q! {: ]: M6 \
  49.     }  9 a; k  I# K& O2 V! L
  50.     else  2 k4 V1 U7 ]# g3 o8 h' N2 s
  51.     {  ( \' U2 `0 o/ U9 l% d" a" l
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  ' f" M1 m8 H/ u# f, x1 U
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  + Q! i1 x7 N7 }( L1 n& s  w6 O5 H% L( Q
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  
    + ~5 V- N! M3 [2 `0 |! U' W7 ~- P
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    ) H4 h5 L' X: o* U& x
  56.     }  9 I$ u, i1 L5 r, ^; p4 x
  57.   0 i! |& ~4 F" k1 d% h% e! I9 H$ M
  58.     //reset the Codec register  % `1 N/ E4 H. d9 v# R5 A
  59.     CODEC_Reset();  
    6 o& X, c" A# W) R' x+ x5 W' ^/ O: @
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  & n% B4 i# ~- {; I3 P
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  ! z7 h* }( x! u
  62.       {  
    0 f4 W% H$ b& q: T# f
  63.         /* Initialize the audio driver structure */  
      ~! n0 d3 O8 H2 j; r" T
  64.         audio_drv = &cs43l22_drv;  ! C4 x! W. |+ `) t8 e$ q
  65.         ret = AUDIO_OK;  
    % @& v3 \4 I; W( r7 L8 o
  66.       }  2 X( S6 {" h% `3 s5 `; ]
  67.       else  # }- G" j6 C, L, S- T! b
  68.       {  
    4 [- X: o; e4 k: p
  69.         ret = AUDIO_ERROR;  " _# V% Y: }+ T) f
  70.       }  % i$ u7 Q" q* e* W  F# q: u
  71.   
    6 a8 k& \% \. a0 S; [
  72.      if(ret == AUDIO_OK)  
    ! ?/ o- m- E/ \5 R
  73.       {  
    0 y7 y4 i: m) D+ U; @  J
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  
    . k: g, m  `+ u- a: S6 c* V6 B) F
  75.         /* I2S data transfer preparation:
    ' f# U) K6 I9 Q0 N0 C- z0 k
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  
    2 M! ?5 y- q2 ?: k8 R
  77.         /* Configure the I2S peripheral */  : ~6 v4 U  P- @& w5 X+ h, Y6 J$ t
  78.         I2Sx_Init(AudioFreq);  9 R3 d- L, `0 g! q* {' C  g
  79.       }  - x4 x" ]- j5 ?" K. V
  80.     return AUDIO_OK;  
    3 y& m7 q) E6 e: K: W& |
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。
1 P0 _; T0 @% H: w, ~/ @7 Z2 y1 s- }
这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:
& a! {) U" a2 ^6 K7 A$ T6 U- y 16.png
/ K# A2 F3 G2 u% A. B! z% [
8 R% A& n! S8 ^2 ]* f在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
" V# W% Y5 y) y  t3 e 17.png
9 E1 `8 Z+ B0 p8 {& b也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。% S0 o6 C2 y: I5 d4 [3 j; E

5 D9 c- B0 P% |0 {- N4 gPLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
  X, h/ A3 [* t  z% |9 |. b: v" B* l5 Z1 ]  _: \) ^1 h# {+ D
搞懂了这些之后,我们马上将其代码进行对接:- D! f& |$ f9 Q$ [/ @! b# X: X
  1. [cpp] view plain copy
    - j4 {/ u. i; N3 e: I
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  " L  }9 {& O& i. L
  3. {   
    $ O: ?5 D+ Y. C& r' o
  4.   /* USER CODE BEGIN 0 */    h7 [9 c  D. u4 S  g$ h
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  . u% u7 s9 ]% _5 y; K; H6 ]
  6.   return (USBD_OK);  " Q) d5 y( _' R# Z
  7.   /* USER CODE END 0 */  - n+ a: ]8 V8 `: ^3 K
  8. }  
复制代码
接下来下一个需要对接的接口:$ ?6 ^" G. p" ~' y
  1. [cpp] view plain copy
    ( Q! k0 R- n7 H4 T" z) _3 q% W+ c( R
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  5 k) H) m& B2 u+ s* Z% x+ a% W
  3. {  ' \  _1 x6 p6 \* y
  4.   /* USER CODE BEGIN 1 */   
    ) P3 ~5 `. \' H/ O
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  : ?& q; [+ ?& r, A. o1 A
  6.   return (USBD_OK);  ' Y5 z' `, w. I4 `9 b6 u
  7.   /* USER CODE END 1 */  , p3 N: Q$ e: @& S6 [* |" T
  8. }  
    & D% j. V0 E* h$ V
  9. 很明显,这个个反初始化的接口,它的具体实现如下:" Q, S- p1 X+ d% }5 f  a4 t
  10. 7 |- J7 v* w% p8 a# s1 Y
  11. [cpp] view plain copy3 @) D; M" P5 _  ]- g. m* B
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  
    4 M0 c& g/ e9 n2 Z- K" h; r: w2 n
  13. {  
    * D2 L/ D8 w& D
  14.   /* Call the Media layer stop function */  
    7 ~; X+ U) q& }9 G) m
  15.   HAL_I2S_DMAStop(&hi2s3);  
    / [( q/ n1 g  b4 ^, H
  16.   / \$ {* x/ J" s  L: s
  17.   /* Call Audio Codec Stop function */  * F# e0 K+ C) |; a' v% R: N3 J1 Q0 d
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  3 Q' Q5 c* D4 e. `6 i# h7 b- G
  19.   {  $ m0 }) g" }; i
  20.     return AUDIO_ERROR;  & @* \: s1 |% ~9 Q* Q# @9 \
  21.   }  
    8 @* o/ M) f9 c
  22.   else  
    8 A- U0 I* q! z& u# L) T
  23.   {  
    & d  P" S5 o" i, ]
  24.     if(Option == CODEC_PDWN_HW)  * F* I; G+ I- P% X0 W$ V) B! P; ]$ W
  25.     {  
    9 I: e7 x2 D0 z, g3 Z
  26.       /* Wait at least 1ms */  
    8 C6 v9 z/ z* l) B, p& c
  27.       HAL_Delay(1);  . d9 {) k/ x% w1 ~8 D4 ], o5 E
  28.   
    7 _) W: ]* w( l# Y$ ]
  29.       /* Reset the pin */  
    # c) D* }2 Q+ c, {4 p" u3 ^
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  ! Z6 b# ?+ w2 I
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    # f2 R* m  A1 |: n. x8 Z
  32.     }  
    ' d! _4 M5 r( Y4 u+ D9 L' N
  33.   : W: ?8 i3 V+ z+ r# O) G  W" a2 J
  34.     /* Return AUDIO_OK when all operations are correctly done */  
    * z# i. Z' S# q9 H2 B* A& [
  35.     return AUDIO_OK;  9 t* D1 p7 m" ?. R) V+ l- i
  36.   }  2 V4 }- _) }3 L9 k& v
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。$ z5 W, R% v; q: ?/ M8 r
OK,下一个:
. s' z1 U, t4 S5 g
  1. [cpp] view plain copy$ I/ W! N! D3 k$ W% j/ k
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  
    + X7 n, k" T4 L. g% r# N
  3. {  
    5 L  l' q, v; N6 e; q8 T1 |
  4.   /* USER CODE BEGIN 2 */  " l! T' Q9 E: c: T9 l
  5.   switch(cmd)  # o- e) ?. d, F: y. Z3 S; P- n( \
  6.   {  1 y0 t! |! F( h! q2 L2 b
  7.     case AUDIO_CMD_START:  & r0 y9 T9 r9 S3 }& W
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  
    0 P% l* ?3 Z) ^& C
  9.     break;  : g0 l% q' c8 c* C2 N$ W' `3 z8 s
  10.   ! O' r! p! ^- S0 `
  11.     case AUDIO_CMD_PLAY:  
    ; J+ h) m6 ]2 K2 i$ N
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  
    ' Z( Z$ A7 V, @" M
  13.     break;  / @. D3 H( g1 Y( V6 P2 L( S
  14.   }  
    ( I4 h4 S* i" {  E
  15.   return (USBD_OK);  
      Q2 @' s& l9 n: F
  16.   /* USER CODE END 2 */  7 q; O- C; L* j5 |" |6 P) F' n
  17.     . P5 x- \8 I* I% J' p1 W
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:
' G" M$ K) t; M. S, g1 F* X+ T) G
  1. [cpp] view plain copy" J5 s2 t: W1 O0 E) Q( l
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  3 q6 O1 b6 g' E; x  y9 x+ g0 a
  3. {  
    8 Y$ n: H- v- R% i' z8 {  i7 T
  4.   /* Call the audio Codec Play function */  
    4 S$ q) u. |9 E& ~" V$ V2 e
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  - D+ d/ }# s. P1 z" L& {
  6.   {  % _( V5 q+ i- g* C  Q' {' U; i
  7.     return AUDIO_ERROR;  % F- }' m& J( h( M/ P* F$ b6 G- i
  8.   }  / e/ X5 ]1 |: G: F+ d$ N; P. e- S
  9.   else  
    9 ?  H' y( ]7 |, a1 N; `3 O! f
  10.   {  
    $ Y$ h$ ]% Q% p) T) x
  11.     /* Update the Media layer and enable it for play */  
      y" W: D* d% E. y# V
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  - g! K$ y0 `' L* O1 Z
  13.     return AUDIO_OK;  , ]1 l4 v& |' U; r- y/ ]) a
  14.   }  
    % a9 E* m5 p# y- ]- @- d* n
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。
- n! b% O% U0 z: N. ?% W( w5 T5 \; b( A' `. ^. E4 `& z- I0 B
然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:9 P3 U  G$ d3 M& i$ J% \: S) L
  1. [cpp] view plain copy" D. R( M* f$ W5 a- o7 a, t1 s+ z
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  
    ! d1 t- F) D+ x4 A
  3. {  
    $ ?  J' f4 f) i
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  3 B3 u6 I2 p+ I! b2 [  C6 x
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。& y- N6 h- `2 g" i* O- V
; o! _3 j1 m2 p9 k5 M) R8 t
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:+ Y- C8 k' L% _
  1. [cpp] view plain copy
    " a; \$ g- K; H5 [3 I  T
  2. void TransferComplete_CallBack_FS(void)  ; @: a; X) @$ o% q5 T' T  i  N% k4 ?
  3. {  , V9 _" W! Q$ p( ?) ~/ O4 i
  4.   /* USER CODE BEGIN 7 */   ' [) Y, s, W' a: B5 Q; W! I
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  
    1 V" o( x  l( m# W: W3 K
  6.   /* USER CODE END 7 */  
    / H4 W% F. p+ Z) R
  7. }  
      [) Y6 P) _8 @  ^- U
  8. void HalfTransfer_CallBack_FS(void)  
    5 H$ K& I8 x; }5 [$ ]9 L; Y
  9. {   
    8 P7 U; Z; j! k+ i+ X' \5 u! U( r
  10.   /* USER CODE BEGIN 8 */   
    + `3 c6 x; g- x4 M
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  : _& \0 [0 L$ K/ Y& B+ M
  12.   /* USER CODE END 8 */  
    % ?+ }, [$ g7 S
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。5 K& `6 w' i5 w4 w9 x  [

8 N  q1 z# d; U此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。
: f: Q) a: T) p+ R: G" }
2 a  T: `* M! ]8 L, k! |接下来看下一个usbd_audio_if接口函数对接:5 H0 R7 v. \: c8 i; b' Y
  1. [cpp] view plain copy3 y$ k; J9 k0 d% O& H, A7 i
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  ' `+ m/ Y5 n, V# J" o( X! u
  3. {  
      x5 n- Z- T/ e; B
  4.   /* USER CODE BEGIN 3 */   
    % y: v" U4 B/ i3 J; L* ~
  5.   BSP_AUDIO_OUT_SetVolume(vol);  
    7 H7 ?. I+ v3 _: w2 C
  6.   return (USBD_OK);  " S5 _8 _* j+ [. I) X9 P( j2 H
  7.   /* USER CODE END 3 */    U! b: I% v" J# U0 t8 T
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:
. y9 o8 U! x( n4 S
  1. [cpp] view plain copy2 ~, J  W6 u! e  M8 O# N
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  
    . V* H* ]4 E3 s4 b0 e+ g* n
  3. {  + j: r2 ^8 w* _( F- i& @- o
  4.   /* Call the codec volume control function with converted volume value */    o: o5 w4 M! o0 p
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  
      q$ R5 F( r3 b7 t. D5 n3 \
  6.   {  
    4 ?. D9 k' I! t
  7.     return AUDIO_ERROR;  
    ! t6 b- J5 @0 c" k7 Z- t
  8.   }  
    1 _  H" n% R9 B3 j0 o  B: n# E5 u
  9.   else  
    6 N. Y% ]) G- w6 A; U, i
  10.   {  , ^9 D* y9 [6 D, O/ E' g  a
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    $ }0 |2 J7 v) Y
  12.     return AUDIO_OK;  
    % {0 }7 C7 `2 G% f) S  J; y
  13.   }  3 ~" X, D- C: F( E: S* i
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。4 @5 f7 ^5 k+ m+ w9 o

5 \5 U! ~+ i9 _下一个:* [" D! v9 l& b4 z
  1. [cpp] view plain copy4 z( w; W6 w# I1 ]5 U
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  
    . v9 `( ?3 c! C2 B$ b+ J. c0 [3 P
  3. {  
    4 s% F$ a6 w. K" y
  4.   /* USER CODE BEGIN 4 */   
    * `! k- W9 O) f/ F5 S1 i
  5.   BSP_AUDIO_OUT_SetMute(cmd);  : ~: M& X3 k* V) L
  6.   return (USBD_OK);  : R; p3 B% y* J
  7.   /* USER CODE END 4 */  $ K! b( @0 P6 O6 _" l8 {
  8. }  
复制代码
静音控制,其实现为:
6 _+ b9 E& G" `1 i) W$ y' F
  1. [cpp] view plain copy
    3 f( Q3 t" Y4 S! {% ?( X$ c' R9 o
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  # x) K* ?: U8 h1 G% }+ w
  3. {  
    + X! ]  O; n* s+ R2 W/ g+ l
  4.   /* Call the Codec Mute function */  - o3 X. A8 X7 H+ d" Z- Q9 Q
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  7 J" c, k& V% }6 r
  6.   {  0 @- K( N/ k; ^# e, Y; o
  7.     return AUDIO_ERROR;  
    ! r/ z( a" Z7 y0 D; u
  8.   }  2 V  i% }: k2 _9 R2 a5 U
  9.   else  
    # D' a" T) h" ]( }; v* U$ i2 d
  10.   {  
    7 ^# \$ F& W4 h2 _# N5 m4 k
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    - I$ H0 c; u) w
  12.     return AUDIO_OK;  0 h* W% u- f1 A: e# k- l
  13.   }  
    : m1 E7 t$ `7 e0 F4 L
  14. }  
    ; x9 R8 ~* e0 i3 f
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。
* E) S. u1 I! N$ t
; W4 b$ \; N! y) a1 F1 wOK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。
4 J" i3 ]; ~: i* y# A
6 k4 E& o$ B" \3 C& d: b$ m4    测试验证
5 G6 _: j+ z& j3 n. m0 o: k' v将代码编译后烧录进STM32F4DISCVOERY板进行验证。. W2 M) {5 @. ]
18.png
1 e  M7 ~% u& \) A; j最终验证是OK的,可以从耳机上听到PC端播放的音乐。
5 J5 K. I* W0 e, p! d' z6 E9 T  h7 K0 p8 k, A8 d
5    结束语
3 l9 N4 U2 V1 K, k( Y* n0 I- Y在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。+ L6 D8 n' v. P) y
6 y/ F" i9 V. P* i2 L( o/ T
I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。
6 L6 `* T( J6 t8 c4 Y0 F
5 z9 V: \# o5 _! i- W在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。! ?2 }2 r4 H% l! P" U
* O! D6 X* G$ k0 O4 d, Z% e
# h) ]. f0 K# m) f, @! s) d
/ V4 `8 M; `& x' l+ w( ~

, o/ [7 x. j, q2 I0 ^# }* f! @

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?; ]% T3 w: x! K. D
static uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,
- E  ~# g$ P5 V" S9 ]; j                              uint8_t epnum)
1 u) B4 M+ ?, m/ s  U3 U" U" K{
! O5 _& Q! m! v5 H  USBD_AUDIO_HandleTypeDef   *haudio;0 U1 L/ r/ f6 c, Q$ q' W' ?$ j
  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;* [: s7 b( Q; f/ z$ z
2 p" B# M; d1 H1 O7 A
  if (epnum == AUDIO_OUT_EP)7 z* J# q; d5 y! }
  {
* Q& n$ u; ?3 Q7 m5 ~    /* Increment the Buffer pointer or roll it back when all buffers are full */
5 h5 ?- ]. q3 o0 a7 w. b6 ]0 f/ {2 q3 ?# R( W/ I* a( @: z, j
    haudio->wr_ptr += AUDIO_OUT_PACKET;
7 T/ p8 W. w  z* @! |7 K5 g" m
' [! C. z. j2 G, Z2 ]0 \    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
; x5 Y: V3 a6 U4 |: j    {- A* S; H! \  O" t$ s1 E' m* p
      /* All buffers are full: roll back */
; I2 P  F- s* s, O( ~0 c      haudio->wr_ptr = 0U;2 O5 L- q$ a/ i' h& H: [/ H
    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],4 E  b7 i2 w' |  n5 x
                                                 AUDIO_TOTAL_BUF_SIZE / 2U,
- V: }# @' g: T( S& F) A                                                 AUDIO_CMD_PLAY);+ h* `8 m- T& ?9 ]9 s2 e
#if 0
6 E6 d. @$ t" Y# `/ ?* [$ x      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
: @5 j1 y1 t% v9 G9 d6 L& U      {5 n/ ^( T% H$ L* V
        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],0 S# }3 c* }" _
                                                             AUDIO_TOTAL_BUF_SIZE / 2U,. a+ D$ A) [$ v
                                                             AUDIO_CMD_START);3 h7 Q+ ^1 y) u8 `& t
          haudio->offset = AUDIO_OFFSET_NONE;
6 M1 J; K  M* g      }
3 V) o7 B* I2 v: U8 T#endif5 U0 g' t( h# T5 t: f6 E2 n5 Q8 [
    }- u( R% R% J0 d. _! C( k

) x7 }: c) d$ i. E" p: o+ z    if(haudio->rd_enable == 0U)
- P" o  \" Q  L% j    {
& N/ @  t) n. x9 ~& ~2 b. ]. k3 d      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))+ @! Q; _+ t2 M; y
      {0 y- Q5 k- A( P; Y) B- G0 c+ ?
        haudio->rd_enable = 1U;* a7 I; N6 c4 Z1 j/ P6 r
      }  d% s$ K/ P" D, O5 [' [) K0 @. }
    }
$ Q5 {! A( a9 H; A
! |0 ?2 |  S+ w% y! c  d    /* Prepare Out endpoint to receive next audio packet */
* K+ o1 \8 l/ N& D# U$ G2 s7 B    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],0 [  j( o' h, c9 n) R1 R
                           AUDIO_OUT_PACKET);
" Q) c+ R" q( R) u8 U' B. \  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?
* T6 I1 l1 c/ v! d8 q* R# V我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,  B6 y" r4 \" C; V! h* H  E  x
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。
7 ]& o! u! l+ g) H8 q现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,
( n- s- l/ L3 J2 R! {但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,
1 ?9 b) k( w& @2 e: Z5 g而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20
1 T: p! o6 `, F" w这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
) f8 N' V# k9 W; s) E  n* N! H- y
  k1 C& g1 O0 @! ?, i0 Y0 h该设备无法启动。 (代码 10)

3 ^( Y8 c: n( Z+ s4 a( X7 F出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
( H. c; @6 {4 i' p! Y( `非常好的帖子,谢谢分享!!!

( o' t1 @. M- f) ~: D! y) z3 G希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
. \. D- \( y. |7 c非常好的帖子,谢谢分享!!!
' @2 F. n# O7 ^7 ~* {: P" V5 e: ?6 T
希望能有帮助
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

5 ]: v. d: w" [( x' n8 Q非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
: N9 q2 K# s2 ?) _; e! z
  o1 w: t" v1 b9 y1 k

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:187 v+ O, i6 ?8 o8 g* C$ M$ U3 |: ?- u
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!5 x2 P& Q2 i: A* K) o( ^$ r4 v) x

; v9 l# [0 g2 o8 Z3 d ...
* y; I9 }- _6 r4 z  N
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示* g% I/ V. }' v2 R% l" D; o1 n
: w& O+ I8 I3 [: H( z; t
该设备无法启动。 (代码 10)' P; O/ N7 h( x4 q5 S
# ]/ o( R' u- L% z4 `* F4 ^% j
I/O 请求已取消。8 L& h+ A$ ^* H$ D
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20
  d( |' S$ w/ u6 h& w1 E: q这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
7 W+ l9 E- U5 s2 ?+ ?0 Q# r- p2 L' |5 n, H7 p' P
该设备无法启动。 (代码 10)

  L) W& `, B3 M! ?- m  R; g8 F经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版