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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑
8 |9 L& ]4 B* f
$ Y# _- E5 @8 f- B2 `4 ~1 前言8 N$ R- E8 x! k$ W! F
本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。- l7 n5 i* p, c5 J5 Q

3 N; l# e( @* X1 ^1 J1 A& q! Z2 {2 设计构思
6 t  K0 z  D1 I; G所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。  c# C- t( i8 v) V& @. n

7 _' c6 U9 H1 P/ E6 d2.1 从原理框图开始5 f! V" E$ C3 |# d
1.png
6 p! k$ x& z7 x如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。0 Y: [  w* }) f3 D
9 a+ ?, e$ y! s- T. u8 S6 `
2.2 硬件支撑
# q4 c* ~% R4 R/ K! l- \- H这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。
; }; h0 s2 G9 f4 Z. g; G9 g, ^# o3 \% _8 P, ]2 ~7 |( I
2.2.1 USB接口
1 X$ R$ D+ k% W. f3 M( U9 @如下图为USB接口部分的电路:
+ P# X4 i  o3 P3 L5 s: B 2.png 9 v! [+ A* t1 d, x$ {, X
这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。
  d+ c6 G# a0 s3 H3 N  O2 D; c# F* g# K4 ^
2.2.2 Codec部分" ]# t) E. j" P
如下图所示:
; E' h  h3 \; B5 D% R2 q3 R" S( P 3.png + G6 L; |- i7 w" u
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。
( Y% h. n% X1 z$ T, g
7 v3 i+ b& y% h2 A2.3 软件设计! \( t" b& C" E* g
为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。, a6 ^4 \! c1 n9 f
4.png 9 F. E) ~) S% o1 S. T* e1 f7 L+ ]
( h/ U. O0 o9 c8 n4 Q( \9 _: d
如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。
% o+ M; z: [/ z+ R) _" n- C8 ~# s* Z) b0 w. d) l
各个模块的工作流程如下设计:
7 R# ]) n( E8 \% C- w+ x4 E* P& i; |. M) G
初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
# i7 n8 d  p7 V; {USB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
) [  K' E  h7 k6 C  ^1 bUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。
. _7 M: k  X4 ?# Q# f, ?从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。
' F% A6 S1 V9 U, d: w: S: d$ z8 V4 z! E! y
接下来,我们来看看软件层面上的实现。' N* [4 G* s5 l; r8 L3 I6 q
6 c/ b8 D  b& `  n+ k# m
3 软件实现
8 I; q# z3 v# _% j7 }还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。, k5 E% }' i# d' [8 }/ g" Q* N

4 r" N" D' Q: p3 B1 r! X7 H' l3.1 创建CubeMx工程
" u) ?, ^- l# q, J1 Q! j由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。
: a6 N  D. }( \: _& E" @( P
3 O$ Z! R$ j1 o8 u; t( p  {pinout:
" H& i# Q! G7 t$ I
% }. r6 Z  Y; F. M5 W) ]; g6 z外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:3 @; V; p, v( Z" @/ b' h. G4 N
5.png . H0 c$ Q: U: e
* S& E* M; ^( A, [: {, E
Clock configuration:
: z  W8 e9 |9 ?8 j 6.png 7 Y$ T) h7 Z. A7 J# G7 k
时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。4 ]' P! V- C: y4 d" k7 X0 v: P

+ m0 F( O( \1 c  I. UConfiguration:
0 |+ w% j. \6 @( h4 d1 t
, v+ @+ Q; \6 a  HAL层:( r$ g# Y2 X: ?% o6 g2 ~
Usb_FS:使用默认参数。/ V  D5 l( e. b( @8 g1 c
1 k# ]$ P- f; w3 z1 f( }; m
I2C:100K速率,7位地址宽度,使用默认参数。+ }) u4 R+ ?0 \& r
$ H6 }5 {1 B; z6 Y  @% X& `6 A# V
I2S:主发模式,标准16位宽,默认音频为48K,如下图:
  F' A) K, R( p. H, Q2 C/ H3 { 7.png
9 u8 U+ i( f3 B4 M) \8 T并为I2S发送添加DMA,半字位宽:
. P$ w7 S; k" U. O9 O 8.png & h& v5 Z0 R* b! B
  T3 M) X8 W6 s6 [6 T8 {" A- {
   MiddleWares:
( q* B6 i% o, B' y, x' ]% y; ]9 V
  b4 d4 j$ @1 `6 G: K7 N9 S2 @& jUSB选择Audiodevice class,其配置参数如下:" x. B& w( n+ E7 c- @
9.png ' f9 Q7 h" j; X; ?9 D9 X+ y

+ Z( T! p( x. w0 ~) g) V这里都是默认参数。% e% r( g8 {: o
10.png
: Z9 e' A( f+ C/ a4 n在描述符参数内得为usb audio class修改两个参数:+ j9 o  G3 q/ P, c( \' t
, D# {& L* @6 @+ L+ {4 |
PID得修改为0x5730(否则windows驱动会加载出错)
6 ]" }' p$ q$ k  z0 W 序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。
3 R. X2 C! n+ Q* ^+ x6 }最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:
1 a+ [2 {0 X: `# r& S3 t 11.png 0 v+ }5 f% a, j
如此就可以生成工程了,我们生成IAR工程。
) B7 F0 e6 A6 f+ w5 M# t. |
0 c  f) y/ E. ~# `( M+ F+ y! i3.2 生成的IAR工程介绍
" W0 j& d2 S: s0 P  X+ G3 x4 F8 o. n8 J 12.png + {+ s& B3 K' W8 r3 E
如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。" T. [6 B3 ?( X9 m

% V* I2 d* g; \) m9 }User目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。8 O% `) y  b) L+ w4 W5 R+ s' o; U
Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。7 u% U& I8 j5 X- f8 X
Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。0 Q# J4 J5 p2 }' c/ P0 |

( r; x$ M6 S4 K$ ^3.3 开发! G& o4 B- {7 V* g: y/ `9 @
3.3.1 初次编译测试! p+ s6 @, }& {- P  `
首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
& M! W2 R1 j& v2 F1 m 13.png
5 P, `6 q) v9 v9 T) B. M4 D这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
  r+ T& e  V- N$ r' p% ? 14.png 1 v/ A+ m' }5 D& U7 b+ K" n
这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.
" ?; Z+ I( A$ a1 G
$ g3 D- J) ?9 y" X6 F* J$ a8 r3.3.2 添加codec驱动和audio bsp模块
1 Y$ }( o% P% H+ I5 p& Y) `- x我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:5 F+ \! c" e6 u+ k! w
15.png 8 H( Q0 C( V% Z! p
其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。
  W+ h2 N# p$ r! k+ j: @; v5 g6 N2 l! b" q6 y5 c5 \
3.3.2.1 Codec与HAL的对接
( u6 l* D, H4 X/ q6 L$ w; w- x( c# M首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:, a# [! p* b4 z$ r
  1. [cpp] view plain copy
    " c: q: Q1 ]5 Z! @: `
  2. AUDIO_IO_Init()  
    0 D' i2 s. P, b% c0 l
  3. AUDIO_IO_DeInit()  ; a. y" w( E3 I6 E, t& B
  4. AUDIO_IO_Write()  
    ; d/ y" W5 K* Q/ y
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:" d0 y3 p+ W' a! |0 I3 c4 R
  1. [cpp] view plain copy
    . \1 H1 W# r1 r, }) f* k
  2. //---------------------for c43l22 port--------------------------//  
    ( B4 {. N* m# W2 a' }
  3. static void I2Cx_Error(uint8_t Addr)  
    ' S$ ]/ B4 E2 e' L' C+ D/ A+ j9 \
  4. {  3 Y- Z, g0 I" S# ~& L
  5.   /* De-initialize the IOE comunication BUS */  
    3 C; I/ B( A6 n( A5 |' R
  6.   HAL_I2C_DeInit(&hi2c1);  0 _% V6 u% V+ c6 Z
  7.   
    ) S) _  r# H1 r3 O+ Z, K6 }8 E; v2 {
  8.   /* Re-Initiaize the IOE comunication BUS */  5 H) Y: w+ @: \% \
  9.   //I2Cx_Init();  
    1 L* V. U! B; |: h
  10.   //MX_I2C1_Init();  / G+ `6 O: Z) G( N; {! `: t
  11. }  ' W) A, Z. e& V
  12. static void CODEC_Reset(void)  
    3 C; }0 A; B. E, Q  ^+ v( z" i- S
  13. {  0 |) e3 n2 [1 t( b* a
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    + E7 g% Q( Y0 c" W  [: z4 D/ D
  15.     HAL_Delay(5);  + I0 `% d9 n9 v
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  
      Q: W& G& F, C& K9 z0 K% S. j: {
  17.     HAL_Delay(5);  , M6 Z' C/ ^+ o) O
  18. }  " S. r& B% R! o" F0 }
  19. void AUDIO_IO_Init(void)  
    + o& r' s' m# Q5 J
  20. {  
    : c0 x3 d( O! T* t% Q
  21.   //I2Cx_Init();  
    . }' o. ~; k& X. G$ B. v% V/ E: p
  22. }  
    & ]8 }, H! p$ u
  23. void AUDIO_IO_DeInit(void)  
    0 Z) I" b/ x6 p8 p* |  C2 E, C
  24. {  " t9 n8 l2 V. R7 O* q& Y
  25.   9 e, d3 T8 R& [  G/ l# ]
  26. }  ) g1 h6 p+ k( P& ]4 N: \0 [6 y
  27. /**
    1 W/ z7 A1 A2 r# O" c3 B0 B
  28.   * @brief  Writes a single data. ( `2 X0 F% E% F* R2 N
  29.   * @param  Addr: I2C address
    , ~/ c! k4 r' z
  30.   * @param  Reg: Reg address
    ! ?% Q) \  h2 ?) ^0 Y* N) ^4 P
  31.   * @param  Value: Data to be written ' f$ o  [% K+ ?9 p
  32.   */  
    ! M1 X8 p5 B% s2 `- i; P5 {! a
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
      |; e! m3 R: |5 l2 F- E5 e+ [. a
  34. {  
    & X/ J+ @- k; c7 {
  35.   HAL_StatusTypeDef status = HAL_OK;  ; z4 |* U5 h& v2 p4 z
  36.   
    ! B: v' D5 G/ g1 `7 a
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    7 Y% u" B& i. |& z0 N$ u( d
  38.   
    - Q& _4 A: u) ?( {( Z& i  G2 |
  39.   /* Check the communication status */  / `( g7 }9 A4 j- ~3 P- r
  40.   if(status != HAL_OK)  5 p1 D- |% R) I) B
  41.   {  
    / \1 S  o8 t6 O" F  ?& s+ q
  42.     /* I2C error occured */  
    / N2 r# [# ?0 u8 b" w3 S* X; A
  43.     I2Cx_Error(Addr);  3 ~, I0 b: `3 Y) o' e* e
  44.   }  . `- f* t1 H( ]
  45. }  + b6 ?( m1 i/ N% s# a, r) V6 ~
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  ; l/ X8 V7 y6 u) Y5 k5 I/ ?
  47. {  
      \# a9 s4 ?; @1 t
  48.   I2Cx_Write(Addr, Reg, Value);  * J# |6 N' y  {) O* @4 Q% i& P
  49. }  
      O: l8 @! A" \0 B* j5 ]3 N
  50.   # K/ N" ?! I8 `7 W
  51. /**
    . \5 w# z! k6 x
  52.   * @brief  Reads a single data. ) q3 a* k5 u5 o1 g/ i6 x& g
  53.   * @param  Addr: I2C address 8 f- e: \. W6 A; k& g
  54.   * @param  Reg: Reg address 2 I4 k7 T% M) C3 f7 u, X5 J0 k: V
  55.   * @retval Data to be read ) ~: i3 f. Z# {& _1 h
  56.   */  & _# N4 P- T1 k
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  
    ' M+ {4 U! x+ G/ x
  58. {  
    $ I9 N# B: G1 |- M8 e/ T& V+ e
  59.   HAL_StatusTypeDef status = HAL_OK;  
    $ m/ i6 p' k# p7 N) H. e
  60.   uint8_t Value = 0;  
    2 s5 _9 _/ K: U3 n/ q7 i5 l
  61.   $ z6 Q) c/ O1 q/ H* \
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    $ N3 r; {; a* V: M, Q! H
  63.   
    2 n5 L- t, U2 y
  64.   /* Check the communication status */  
    / `. ]; Q* e( d/ R
  65.   if(status != HAL_OK)  4 U# C9 S3 Q" _$ ^6 i
  66.   {  ! t7 u4 g) F& N- S
  67.     /* Execute user timeout callback */  ( q3 P2 |7 X. {' R! `
  68.     I2Cx_Error(Addr);  7 C- s' F3 `& R( {1 R+ j0 U  L; p
  69.   }  0 [/ F9 B3 n0 k! q
  70.   1 g9 _4 T7 [  c2 O0 i* F2 k4 H# [
  71.   return Value;  ) A2 @6 {" N) K* f% p
  72. }  ' F  S' T% p% u3 Y2 ?& ~$ q
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  ( `! b0 }- M5 n% o
  74. {  
    1 h7 u& I' [' ?- Z- g9 ]$ t# W
  75.   return I2Cx_Read(Addr, Reg);  
    / c2 E1 Q8 q" c; J& d
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。* v" Y( {* _2 n' [# k% X& s5 d' o
, g) u9 s1 m2 d3 r+ x+ c! H+ F, B
就这样,就完成了Codec驱动与与HAL的对接。% o1 o' c' M' k% ]

$ W, E7 j# M+ W$ ]' h+ m2 A3.3.2.2 usb audiointerface与codec的对接. S! U% F5 O5 F
我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。% f  S6 t9 |. B# p" }. P* ]

) m( @+ ~9 q/ h- p按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:/ x+ L7 w0 I  T3 m4 P5 k; N- z
  1. [cpp] view plain copy0 ]7 X& |! l+ n# g5 S$ \
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
      P# o! M' N, E& k- O. M7 G
  3. {   
    ! q6 f6 u4 p8 p) h
  4.   /* USER CODE BEGIN 0 */  ( _- V: Q( C+ t+ Y
  5.   return (USBD_OK);  
    1 M, B: ]: G: w( B- w( H# B: o
  6.   /* USER CODE END 0 */  
    ! N; ^1 ^! }% \0 Z8 I# b; M! z* c
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:7 p- E9 f" m+ @
  1. [cpp] view plain copy
    / j! _) T, P$ f0 r
  2. static void I2Sx_Init(uint32_t AudioFreq)  
    - ]. a  |/ V( Y9 \' |5 Y* z
  3. {  0 }( U0 ~+ l# Y1 z+ i1 I
  4.   /* Initialize the haudio_i2s Instance parameter */  9 L& Q& X" ?' I# {3 _1 I
  5.   hi2s3.Instance = SPI3;  
    1 w) I$ a/ i# m. {+ x" d
  6.   
    ' a) r  J; J! [- X/ X3 U: ]' m
  7. /* Disable I2S block */  - ^. @" G; _0 E% O% [& j$ c3 x
  8.   __HAL_I2S_DISABLE(&hi2s3);  
    ) C  t) ]: I3 c) s5 H2 U# W
  9.   , X: W# o7 E) a. g' T$ p
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  
    + l3 p$ W# p- J8 O; |" A7 W' l: T# h) h
  11.   hi2s3.Init.Standard = I2S_STANDARD;  
    7 V& O" L/ c. H: I  e
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    7 g* a/ i5 j4 q. S
  13.   hi2s3.Init.AudioFreq = AudioFreq;  4 S" O5 B0 Y3 `) ?6 f( E
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  ; Z0 E1 m  ?0 t4 U( B
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;    [2 S. i# }1 p7 l+ u, r
  16.   ! K4 N7 }; y6 O1 ~0 e& J" }/ g
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  ! d2 e; S+ |, R2 |2 U
  18.   {  
    ( C/ b/ o  \0 v5 ^
  19.     HAL_I2S_MspInit(&hi2s3);  5 a5 ?2 S4 M( e+ |& v- f
  20.   }  ' g6 `/ R+ s- ]- |! \
  21.   /* Init the I2S */  
    0 m; U! s* `% Z  |% {0 K
  22.   HAL_I2S_Init(&hi2s3);  
    ! k" j6 N7 |# X; H& ?  A( I
  23. }  / r% x, o) `! T# [5 M2 U. g! G  o
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  
    3 L* B$ ?% \3 B6 l- C# `# B
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};    g- t* L/ i' m
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  6 e" |4 v4 b7 K+ T) p/ u( s! h
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  , O% f2 T/ C& V* \7 o
  28. {  : a. @2 d" [( c' O9 p9 G- I+ ~% n
  29.     uint32_t deviceid = 0x00;  
    0 l$ O' r, U" z, A" K* n
  30.     uint8_t ret = AUDIO_ERROR;  
    4 F$ e3 z- {, g% p% L" B
  31.     uint8_t index = 0, freqindex = 0xFF;  
    5 p& `. {9 F9 ^% H; u  G
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  8 J, b* b- k9 \  n; N: Q
  33.   $ x* ^6 e! L% J- i
  34.     //get the according P,N value and set into config,this is for audio clock provide  . V  k* j% i+ a1 h. h! }% k$ f; I
  35.     for(index = 0; index < 8; index++)  6 Q9 a' K7 v8 }: [2 T& j( @$ ]! _
  36.     {  
    2 n  L: w  j0 a% b) {- q
  37.         if(I2SFreq[index] == AudioFreq)  
    & a! |7 J4 Q' M$ M2 p7 V; z& n4 g
  38.         {  
    + U# r0 E* y8 H
  39.             freqindex = index;  + r- q: [0 W5 L! C5 w2 }
  40.         }  
    . A6 c5 N& S1 H) e7 s0 y
  41.     }  
    % X3 ^- T& V6 ~. L/ a! |4 }) W
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  
    - X# x3 O( f0 [  V0 N
  43.     if(freqindex != 0xFF)  
    - y" p$ V+ ^, X) Q8 y6 i
  44.     {  
    " Q6 F* y' ]( H
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    0 k9 c. }4 E0 b( {8 T
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  0 A1 M# L5 z! x9 m
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  7 p6 t. M+ Q7 i/ a. k+ c/ i9 X0 @) g
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    * b7 e! U  L: j
  49.     }  / z, X. {( l# P4 y2 A' Z: H
  50.     else  
    & ]" i" l) L6 o7 F7 S
  51.     {  * f% d/ Z& X9 A) ?5 x
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  , w3 ^$ x* k# G* X: s( k3 G2 v
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  
    & ~# }# Y8 U1 J
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  
    7 q6 K. \4 g1 I( p
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  7 N% c1 k: I7 v( n
  56.     }  * r! s) l" V) r2 T- P& r  [
  57.   ! a% \: [: G' h6 V$ k! s+ _' `
  58.     //reset the Codec register  6 f$ O& z3 z1 x
  59.     CODEC_Reset();  
    3 E9 R+ U( S  Y# Y+ _
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  ( V- ]) b3 s3 I6 j
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  . d& z1 d& I. J( B3 `. S
  62.       {  
    # t/ {) C7 x$ n1 P: Q+ q5 R: J4 h
  63.         /* Initialize the audio driver structure */    |! E) s$ o3 V6 E0 `4 \  H% l
  64.         audio_drv = &cs43l22_drv;  
    ; k. ~9 j. S% Q! u( c% F% g
  65.         ret = AUDIO_OK;  
    ( h4 i; C) p6 {' D  w* x  ~
  66.       }  
    7 B% _$ |  e# L9 @
  67.       else  # J' m) A* v6 n7 b- k3 D
  68.       {  5 }2 y5 K  \" d+ O# r5 x% W
  69.         ret = AUDIO_ERROR;  ( C$ C' Z: f$ C; I; r& ]  ?
  70.       }  
    ( r1 |* O0 ^( c3 Q; N- w
  71.   
    + N( m+ q: X: s4 k) ?; C+ F$ e& H
  72.      if(ret == AUDIO_OK)  
    * D9 c" @+ e  N/ t. V
  73.       {  * m1 p" ~5 l0 t" T0 G3 Y) D
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  
    $ r5 O' x) ?* ~
  75.         /* I2S data transfer preparation:
    5 A! i6 V! ^$ ?6 I3 F5 |) q: t
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  
    4 j6 C9 O' e6 }+ r
  77.         /* Configure the I2S peripheral */  
    / G5 j/ W1 k$ h
  78.         I2Sx_Init(AudioFreq);  
    ' Y0 w0 E/ ^- G0 E% d5 v) `
  79.       }  
    % U4 `# A, R6 g$ p8 B9 v# {
  80.     return AUDIO_OK;  6 ?8 h0 i  h& x% `
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。
. }: \- R( `8 }& @- w3 E
+ X2 H' e3 U8 ?" G  V) O  q这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:# ]2 P* o5 ~" Q9 \: x; ?
16.png
5 K; f+ a, `- d; K5 A5 k- @* [% R
4 m  g% l) g6 \) D9 ?- c5 V. S在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
2 F0 ?" h: u! a4 ?8 Z$ o 17.png
8 y6 Q5 T5 B3 n2 V9 V2 E: d也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。" @: K# B5 L8 I- }6 Y; P  C
3 e$ {' L9 V3 v- X$ \) `8 U
PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。2 Q8 d! F9 T. H" a( i% N) ]
9 K% C- U, a& q/ M9 u. Q
搞懂了这些之后,我们马上将其代码进行对接:
! \7 U1 Z) G' W
  1. [cpp] view plain copy
    0 q4 O& R1 t. ^. t' w. k4 r; `1 L
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)    @1 S8 w2 X" m
  3. {   
    5 N5 [3 @/ ]9 r. K- t8 G
  4.   /* USER CODE BEGIN 0 */  - Y, B* G" \+ [6 Y& m
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  
    . H) `& c7 P8 M1 K" K9 y
  6.   return (USBD_OK);  # |' l% J. a& a* n7 l, i
  7.   /* USER CODE END 0 */  
    " g; ~9 |% r( T
  8. }  
复制代码
接下来下一个需要对接的接口:
2 }8 V+ [9 r6 F# x  J
  1. [cpp] view plain copy$ ]. E4 ~! k$ M# w, M
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  + S, B: |2 b4 `# V
  3. {  ' l1 r: o1 ^: [: R* @/ Z
  4.   /* USER CODE BEGIN 1 */   
    3 s) E; H. R) Y' O4 D, H& I9 i1 G% o
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  5 d6 P5 }- u$ h4 W
  6.   return (USBD_OK);  
    5 f9 [/ l5 K( `% j' D
  7.   /* USER CODE END 1 */  
    8 [* \! G+ A; Q' y3 P9 ]: E
  8. }  : S* Z, q9 Z. U: Q
  9. 很明显,这个个反初始化的接口,它的具体实现如下:* c+ G4 W" V9 L! k0 M+ N
  10. 6 P6 x, u; D9 b: {; Z4 C( y9 h
  11. [cpp] view plain copy
    + `8 H1 `1 s  G% b
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  # M9 t$ l: f  e
  13. {  ) [" T1 G2 {) u! [8 T$ N
  14.   /* Call the Media layer stop function */  . r& k# c- R; a: X0 h# }$ x3 `: q3 D8 L
  15.   HAL_I2S_DMAStop(&hi2s3);  9 }# t# O) I+ q& A- @" |( f
  16.   9 _1 P: H: l; [' R2 s# ]1 p, E
  17.   /* Call Audio Codec Stop function */  * k3 r6 f7 R& f* \4 w5 G8 L
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  $ A. x8 r! ~4 ?) {1 j$ e: J
  19.   {  
    ( a* S8 c* S/ O/ |$ Q- z! ?( O
  20.     return AUDIO_ERROR;  . Y. f1 `, _* B3 K
  21.   }  / t) s$ a) X0 l5 E" S0 x3 c
  22.   else  & a' k1 k% F) ^$ a
  23.   {  2 }+ ^. i" _9 x2 h: p
  24.     if(Option == CODEC_PDWN_HW)  
    $ K: B$ E, l1 z- }! q1 _( A
  25.     {  
    ) ?+ V. e; J5 K9 ~6 V
  26.       /* Wait at least 1ms */  
    . T3 u, N: g* A7 @2 u/ j9 @
  27.       HAL_Delay(1);  
    ' A3 x5 ]; S; h1 W, x2 t
  28.   + m- r* }2 V: e; h, J2 G
  29.       /* Reset the pin */  ' |4 y& V" d4 m, A' N; @& s
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  
    , f, c, j6 |3 _/ A4 j$ }, B
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    0 n. T5 u  |2 S/ o0 I& {& @
  32.     }  
    5 C' w1 W8 t7 i
  33.   / r4 e5 s# T+ B, ]" J
  34.     /* Return AUDIO_OK when all operations are correctly done */  5 j( z; }+ c0 G" x
  35.     return AUDIO_OK;  & R( a+ g( ?! _- B
  36.   }  4 T' }0 q. c6 F0 i+ U7 S6 W
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。
7 B1 ]. L5 J. O2 ~+ M& j) BOK,下一个:: _; G$ c& x/ F3 k! |! V; ~
  1. [cpp] view plain copy- G1 c  S- C$ j$ `; f0 |
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  ! r& q' A* T  _% r: m
  3. {  ! b3 ^. p* g/ v" F7 U
  4.   /* USER CODE BEGIN 2 */  * A  F: g9 i" s. b9 B( x3 l, p# w
  5.   switch(cmd)  
    ) X  l$ R5 i8 `0 L' u+ x
  6.   {  2 C, N# M9 `, c9 d) d; N
  7.     case AUDIO_CMD_START:  
    ! t8 n6 k. [. p7 _
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  
    * b# U5 s: f. ~7 h
  9.     break;  
    % P  Q: Q5 U* Y6 K
  10.   
    % @, v' {, c* g7 \" }5 w! b
  11.     case AUDIO_CMD_PLAY:  3 f$ q; D% G  q' j: X, Q
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  
    3 i- H1 K. _; U
  13.     break;  
    5 d9 w- F" ^/ B; [0 E
  14.   }  
    . B/ x+ q5 x; U% h! d( f" G
  15.   return (USBD_OK);  6 F* z# o) j) z# i; D1 P$ Q7 A
  16.   /* USER CODE END 2 */  
    ! F/ P; ?0 r3 ~- W( D& s) t; @$ }
  17.    
    # b  g4 N+ c9 W, q( b( _! h
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:9 h6 t: ^& Q9 u
  1. [cpp] view plain copy2 \. Q' J7 a/ }2 i0 u- j7 T/ c
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  0 t  Q8 \& r+ p
  3. {  + r9 {7 ^2 P" {9 N$ R! I
  4.   /* Call the audio Codec Play function */  
    1 i6 l8 @$ I# d2 [4 k! g0 L( H
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  & ?) |, T* N1 I
  6.   {  8 B: I( s3 X5 y: c; r* M
  7.     return AUDIO_ERROR;  
      D. w, o: B" V+ V; P
  8.   }  
    * l% C0 U& b- o' Y4 ]; F7 v  x
  9.   else  
    & r# A6 A  o0 i$ H9 z8 I
  10.   {  
    7 \6 z& K8 v" n
  11.     /* Update the Media layer and enable it for play */  
    5 F# J. v& _$ k6 N$ G* Q5 u
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  
    - h0 u8 L; W5 t$ }! ~( V; L
  13.     return AUDIO_OK;  
    7 ]/ t- t& J$ ]3 L. z1 U1 y6 N
  14.   }  - t$ {( I9 t/ l, U6 i- B4 t6 b
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。) _" n6 t# |% {/ P: J, m

8 {9 Y8 _6 O. r4 m; x" t+ Q. M然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:( b- W  L1 J! c2 U
  1. [cpp] view plain copy
    4 x+ z2 {0 W6 b: M3 c" z
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  
    + ^6 w& K6 {, j3 W" }
  3. {  ( j! G3 M, t' o' Q
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  4 b  G9 d! y: Y6 p8 F$ A
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
4 j* }% t+ @7 Q* u: l! _. `( }$ }1 r& Q! z
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:
; D9 z# _, ?( ^' R  M
  1. [cpp] view plain copy1 l& ]* M( B- i3 y
  2. void TransferComplete_CallBack_FS(void)  
    3 [; N0 g1 t* b$ {0 D1 f0 H" C
  3. {  
    / x1 ]+ e$ h4 q$ B" j
  4.   /* USER CODE BEGIN 7 */   
    " {* o" @9 A" s
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  7 l& j5 }: D5 e" G' {% ~3 N: v0 {. E
  6.   /* USER CODE END 7 */  
    6 b# X: P" G% J& r% B" o* q
  7. }  
    4 d# A# s2 L9 V  ]/ a
  8. void HalfTransfer_CallBack_FS(void)  
    $ i) p; p# }+ u$ c$ s3 j+ g  P4 O6 [
  9. {   3 u3 `- e' Z( K6 X9 K4 u
  10.   /* USER CODE BEGIN 8 */   
    : k" z  P3 ^  u- g" d3 F
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  ) q0 S3 z7 V7 f1 w6 Z9 T* \! I
  12.   /* USER CODE END 8 */  5 f- s) V8 y: g4 U$ G! o& E
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。& V' b9 Z% J, Q9 |  l2 z
4 Y3 }3 o+ |6 D4 L9 r8 D# A
此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。
( n' q2 g* W' n* `$ M9 o/ e2 a1 o4 t- f- D
接下来看下一个usbd_audio_if接口函数对接:; e$ D  b& o- ?' {" I9 A* H
  1. [cpp] view plain copy% X4 o5 {& }) o! j% j$ s
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  7 N+ p$ v- u0 r8 J
  3. {  ! D1 t! p4 l8 k( g. y
  4.   /* USER CODE BEGIN 3 */   
    - C& x# O( Y: G
  5.   BSP_AUDIO_OUT_SetVolume(vol);  
    9 Z7 _2 l8 M" w. ~
  6.   return (USBD_OK);  
    ; c6 j& X9 y" _; A+ P4 w
  7.   /* USER CODE END 3 */  
    ' R, b, e- Y# w2 @+ {% I* e
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:
2 ^( z3 M6 @9 v' Z3 B
  1. [cpp] view plain copy
    . v- _* m5 Q- @0 d
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  
    4 Y7 I2 y. Y  j3 }+ S8 L
  3. {  & f4 @" i( W1 v/ q3 W" H
  4.   /* Call the codec volume control function with converted volume value */  
    0 d4 g8 C0 O; `' O1 @8 e
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  
    ! e2 s- U$ R2 H
  6.   {  
    2 M0 F; L3 @! y
  7.     return AUDIO_ERROR;  ! i5 Z8 u6 u  A
  8.   }  
    9 ?6 p/ U, u, ~0 Q7 R8 M- R
  9.   else  ' x0 g# a" }' H
  10.   {  
    8 e7 H. H; d- K( K/ _+ b* _7 y
  11.     /* Return AUDIO_OK when all operations are correctly done */  % i0 ]6 \' j/ U0 A" P
  12.     return AUDIO_OK;  ) g4 B  |' `6 }" q
  13.   }  ! q3 x9 U) B  a( M: t" K
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。
- p2 L# A1 _) Z+ \" l; S/ U9 y5 x& |
下一个:0 [% H$ P# o/ k# b, v3 E
  1. [cpp] view plain copy$ \9 }4 C+ M2 F8 X4 s
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  % P9 O; x) \$ Q- ?3 S) ?
  3. {  
    4 w6 _! q' ?3 n& ?( H, a& c
  4.   /* USER CODE BEGIN 4 */   
    6 S$ q# _5 H( O$ {
  5.   BSP_AUDIO_OUT_SetMute(cmd);  0 H+ ~$ n0 ^' H& W' |, s
  6.   return (USBD_OK);  % t! g5 W+ j  }9 _% K  V
  7.   /* USER CODE END 4 */  
    1 k, O7 p4 i. W; F
  8. }  
复制代码
静音控制,其实现为:0 r( k, x0 A0 \$ o" s% B5 i
  1. [cpp] view plain copy
    " b& l; r" m: p
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  / ]0 S( p* e  s1 ^3 k
  3. {  
    % C* p4 s2 D1 S' g8 m
  4.   /* Call the Codec Mute function */  8 t3 ?0 a3 ^$ H3 L8 D
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  4 G& ~$ F) V8 ?# T% V: i; }
  6.   {  
    + j  ^9 g3 v. u& U# y- X0 l  U
  7.     return AUDIO_ERROR;  & D' G2 L5 t) f% h; _4 m
  8.   }  
    6 R1 o$ S# n& c- b% c
  9.   else  . ]/ r0 g" [8 d' |+ {
  10.   {  / q( t6 L) l3 Q3 w
  11.     /* Return AUDIO_OK when all operations are correctly done */  6 Y6 a- G9 f& V
  12.     return AUDIO_OK;  
    + \3 L/ n% b# g, e; H# U4 y
  13.   }  
      V: F) v) T2 D: g, m
  14. }  - P' h; x: p' [6 X7 q2 e
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。! ?, d4 B* p8 y- a+ i& O  n3 E- h! F

. g- O6 d2 F& i' Y+ TOK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。
1 ^, Z( C  Z8 c8 L2 a7 u
' _2 L. c) \3 k! x5 y4    测试验证: T3 x9 A9 k1 b/ ^) I
将代码编译后烧录进STM32F4DISCVOERY板进行验证。
( b+ ~' \  C4 ~ 18.png 4 x/ g, \5 t5 d. z" Y8 Z
最终验证是OK的,可以从耳机上听到PC端播放的音乐。
: H7 ~0 t' D5 ~) W
; ?/ w: n+ b  g( O7 H  N5    结束语
0 p  F, C, e6 p6 E在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。  W" P1 x: g3 n: B+ a
3 H8 x, p. o* s( V
I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。9 z# s; u+ z$ M5 Q5 a
& p* `7 }" V5 e
在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。0 Y  ~- Q3 o* F, v- y8 o
9 ?. g! n$ T) `+ L

6 G. s/ l7 i; s5 b6 a' f% @$ n9 z+ B/ f9 ~) B
, b" S- j6 J0 {! `8 g

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?4 b& ]/ D  ]$ Q1 [
static uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,
+ E' J! l4 K0 s+ B% f3 F; [                              uint8_t epnum)
7 l$ o- @" v3 z% g. q1 p5 Z$ ]4 d{
# @- }+ H0 V* O' u# X. N1 E  USBD_AUDIO_HandleTypeDef   *haudio;
) {( _8 B) |+ Y0 z7 n8 f  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;
+ h' l2 u2 }5 T3 o( f: W3 M3 U0 v3 N
  if (epnum == AUDIO_OUT_EP)' D# V! x1 w! O
  {) T8 A: B  o8 I  M! P. x! v! O
    /* Increment the Buffer pointer or roll it back when all buffers are full */
/ H9 P! x$ [4 E0 O8 j6 a9 ], N4 l2 ^' B5 {( _
    haudio->wr_ptr += AUDIO_OUT_PACKET;# {1 T+ m6 R2 K3 e

/ T$ p0 j6 B; h; c    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)% n! ~- ^3 F6 P# U
    {
( Z2 L9 Y8 b  `: ^  B. o' B* Z# ^      /* All buffers are full: roll back */
/ k( @- p# E% b5 V8 \3 i      haudio->wr_ptr = 0U;1 h# ?/ b7 S. X! B
    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
( c0 x* t- b2 B) c0 ^2 s                                                 AUDIO_TOTAL_BUF_SIZE / 2U,: {! b1 G% q# s& v+ x3 Q' \! X7 w
                                                 AUDIO_CMD_PLAY);
8 o; ~! H1 B  o% z% O. H- h  {4 |#if 0, y/ L; t- s) S1 o, H
      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
. R. Z5 g2 l' a- o      {
4 y: V/ y: l) d( U        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
: k& R7 }+ w  G) z& g( i  g$ P                                                             AUDIO_TOTAL_BUF_SIZE / 2U,
3 o! E; [' F; x6 k                                                             AUDIO_CMD_START);  X7 R. i1 A& E8 P; y: H+ w9 l* @
          haudio->offset = AUDIO_OFFSET_NONE;
% f& |) O7 ?4 S: ^. w      }
2 f8 l  x1 V+ u6 N#endif
; ?3 @& M  B7 N% t* ]3 @( q5 C    }4 W! y. h8 T) t; p+ j" j
1 t; P' B; |8 O) z' F, h5 ~
    if(haudio->rd_enable == 0U)
% b+ u$ H. z; v  c: N    {* q. m% s4 G9 F
      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))  P2 C+ l5 t' n  y$ ^
      {, Q5 N; w! B( z- n5 d: B
        haudio->rd_enable = 1U;
+ f0 k8 Q" T9 ]3 k3 k0 g      }
7 L6 ^$ P. q6 c: z$ `! k8 [    }) y! G+ n5 f5 j) w  V& K; J3 n

( C" i9 Q& c+ ?1 V    /* Prepare Out endpoint to receive next audio packet */6 \' ~1 v; A4 M* |0 d0 ?
    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],/ t  A2 ~1 f* q
                           AUDIO_OUT_PACKET);
3 x! ?- M/ s6 q2 g; a$ I% ~! a( L  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?6 v6 @0 C' T- [- @
我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,  e' g0 x( d- {8 K) C
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。! ]: [3 M4 u5 _; N7 }1 ]. b5 j/ F
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,3 I/ d1 B  k# W
但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,- `* W2 R6 h+ K9 B) b
而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20  e( Q% A9 n. u, s9 s
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
$ A( h( b) d: F
! k3 Z! v8 l$ m7 f2 r! a1 s4 g7 B该设备无法启动。 (代码 10)

7 @7 R' ~# m2 z. h$ G4 ?6 O4 s* N出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45- k; k1 F% H6 |2 q9 y+ g, G
非常好的帖子,谢谢分享!!!

9 P% O; i7 ~1 G3 `: Z+ J希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45# P# S( _8 A$ `7 S
非常好的帖子,谢谢分享!!!
& {7 Y: E8 T) o. V: y, c
希望能有帮助
listenmaxwell 回答时间:2018-9-26 23:00:58
非常好的帖子,楼主可以发一份工程的代码吗?非常感谢,18056453597@qq.com
wofei1314 回答时间:2018-9-27 09:24:26
666,好贴,顶起来~
Kevin_G 回答时间:2019-7-20 17:40:01
收藏
ccg12138 回答时间:2019-7-21 15:19:43

" F0 z1 O" e" O2 I/ {非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
* A0 Y* b! N1 ?* y8 h% o2 ?+ a  N
2 C& v* w: i0 Z. W6 E8 ]- q, q$ m

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:18
. k4 _" P- n) u% ^8 p楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!" h2 r, W! T+ q5 T
9 R) L9 U4 `+ a
...
/ l: t3 G- i3 t+ d7 J2 J/ F5 J( v
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示" P6 S2 y& @$ P
: W$ }* D! c' e. U
该设备无法启动。 (代码 10)  v. D7 t, \1 E1 o
$ r- @/ E$ V/ X/ v/ I- a1 U
I/O 请求已取消。
5 }' _* z/ H1 z3 N
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20
; F  R! `: J: Z; z: U* t这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示- ?& p  g' a  a
( I; n- a3 ^: m/ C" P6 q: Z7 f
该设备无法启动。 (代码 10)

& ]( {  _; e" u经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版