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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑
, U; Z) |3 l+ j0 P  D
! y) v$ u% L! m1 y6 @1 前言
# j/ g' R4 [, u# q2 R: U) A1 }本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
  \0 ?; x% x* S  u8 [. z) E6 M  t
2 设计构思
% @/ J3 F" E6 r3 M0 m5 z( ]; i所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。
# S& e, e" q' _$ A' j4 Z
7 ?) b4 M) D4 {; ^" V& D9 y- V+ t: {2.1 从原理框图开始  y, R; w& h3 v$ a& g$ _
1.png 1 W4 y4 o% A" e/ u9 [  D
如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。# S; Q* l( ^6 l- k

  W- K/ p3 }9 }1 u( h. v( b6 d2.2 硬件支撑. E  @8 j6 I# `# r
这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。2 z  J( J2 i% ~" S1 \

8 r% Q5 J! X0 d* G  A6 s/ d* [7 `2.2.1 USB接口" \/ e( Y, z6 M( Q" C6 |
如下图为USB接口部分的电路:! X2 X7 |' Q$ e, n( d0 I
2.png $ P2 i2 Y' t& d' o- N3 `# ~# T
这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。: {" g% F/ k" J: I

' h: Y8 B  r& y% @2.2.2 Codec部分
1 w: c2 A. P: ~; l0 |8 Z如下图所示:
3 j2 N( A( d% r& G" u& I 3.png   A2 A, y- X/ H; [$ u0 S- H: V
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。1 n/ M3 h1 G0 v  X0 _
; H1 @# a6 U, j: U" a
2.3 软件设计
& l1 r9 B5 {( W  ~, r8 _为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。
; e& U; M& s1 | 4.png # `/ w0 H' m6 e/ D# X) x& ~* z

& X6 V& Q8 m0 w如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。
! q! ?; |) I- g+ @. n
& s7 X/ q2 Z' b" e& d各个模块的工作流程如下设计:
& y2 M3 j+ z% w/ u
# b6 i& X& ~* @6 F 初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。3 w  D0 B" t+ y) m6 Z" x
USB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。* O  t6 c" ?( _) e& J9 }6 R
USB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。  r4 V& e/ p# U% s" f
从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。2 A3 G0 r, M$ c" H6 Z' R

$ R: [0 g: ], V6 Z接下来,我们来看看软件层面上的实现。, I0 ]. P# L5 W$ F$ ], [# I; a
( Y/ _0 n7 u2 t2 U
3 软件实现
. o$ I3 `4 y9 t4 |9 \还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。2 J+ z% o6 M; H4 Q7 }! {0 `
1 M  d3 ]6 V6 d. e( |, `1 i
3.1 创建CubeMx工程
3 `! Y3 F" b2 g& w# |: J  o& U; q  ]& q由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。7 `, T7 {6 v4 R) o
: @' @2 |- u5 l' J- ~/ S2 t
pinout:& C: I" A7 r; F' t

1 M1 w/ P% }! d( w外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:
: t, r( [3 x9 M  ^ 5.png
! J- Y2 P$ H+ B* a. Y( K# c& y' J* M# a& L+ k7 i" P& F# {% q
Clock configuration:# z- C. K! I0 z. L5 F+ \$ ]0 l
6.png
$ c- ?  V+ T  w+ h( D0 ?( Q# T时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。- p/ L% t7 e, s3 C8 g

% w; _1 l) R! A; c# h0 C: kConfiguration:# X( Y+ ?( T* j) j5 ~- ?

9 ?2 q0 h0 K( `- Y7 S# {. l7 ?  HAL层:6 F) |( j# i8 o
Usb_FS:使用默认参数。
" x. E* D# K4 ~8 i1 a& M) |8 r8 L1 S0 B( V+ f# a7 d* o& v: d1 U
I2C:100K速率,7位地址宽度,使用默认参数。
- v. g; q8 b! c0 F
  u7 ^9 G' D) f# }6 tI2S:主发模式,标准16位宽,默认音频为48K,如下图:" S1 S3 o% A( ?, G' ~) q
7.png
/ s, o6 G! X( E( [& O0 K" \并为I2S发送添加DMA,半字位宽:
9 \2 w8 o9 ~3 m. ?( t, s 8.png : h# R, a; I3 e2 }( F& k$ @7 n6 j

4 X1 X2 ?, x$ i   MiddleWares: ; F3 ^1 |( g# L
9 m9 n+ Q( |" u6 S. V/ Q
USB选择Audiodevice class,其配置参数如下:( E* M1 L2 i1 |4 ]
9.png
4 W% m* a- _' P6 p7 E6 h
. X! v6 |9 o" q9 [+ @+ l. t这里都是默认参数。1 Q! W8 |* U6 V1 K5 E# `
10.png $ c* [0 l. K8 P* }8 J
在描述符参数内得为usb audio class修改两个参数:
( q3 N0 O$ c# ^/ r5 w% Q7 D6 H; I' s  K" a
PID得修改为0x5730(否则windows驱动会加载出错)( L/ `& D9 ?: J, q5 d
序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。6 F* L3 }9 B% Y- q+ {
最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:
2 c1 w: [5 n+ d! |9 I* g1 N 11.png
" @) ?1 K7 Y; ?- K如此就可以生成工程了,我们生成IAR工程。% T: A& v0 _8 S$ b/ u- v

: {/ ~2 E# J! {0 Y: B$ q3.2 生成的IAR工程介绍- f# m( m3 R& i% Z7 t$ k5 A
12.png
  R( y4 Q, o) R6 J7 N4 Z如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。
# ~2 e: i" e) W  I4 x
, F8 x, |$ R( @User目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。1 D) O! D- D8 g. W
Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。3 a7 q: W, T$ {- x5 L! r7 p! }% A
Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。
  j8 D# J! d. x9 U) V9 S' f+ J+ j" B. m
3.3 开发( P' c& n, a8 y6 r# b
3.3.1 初次编译测试
# _$ g* a9 f: n# x! i& d6 k* y首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
& g& g- L/ A4 {' Y. e 13.png
4 q2 o9 _5 @+ l$ _, v这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
/ Y. T3 C% m  \1 t' R* K+ W% s 14.png
6 I! t% e" u+ o. M( @1 f这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.
& P( q' @4 B4 P" [# g$ ^3 X& f% H8 P. f
3.3.2 添加codec驱动和audio bsp模块
. S2 }) L6 q" n# ~# G$ q% t我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:
$ J( f; w$ Y9 M 15.png
: Q- l: ~3 I" z! I其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。0 T% V  ], l) V# L

3 o3 f  `& F- K; g0 A3.3.2.1 Codec与HAL的对接, ^' M* {0 O4 S$ `" O6 }
首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:
* U4 P- q, b1 B4 j2 J7 R, i7 f9 @
  1. [cpp] view plain copy; v3 w/ P6 |0 y: Y
  2. AUDIO_IO_Init()  * j' f4 p; X8 |! Z$ K* `
  3. AUDIO_IO_DeInit()  , r" x% I5 j4 L; `
  4. AUDIO_IO_Write()  ! Z& Y) z0 K7 c4 I, J+ L# l" l
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:& c6 S" R- a0 S' d
  1. [cpp] view plain copy
    : H6 [( ?# K) f. [* O5 J# E
  2. //---------------------for c43l22 port--------------------------//    m; n+ W) X6 u# J; O6 Y3 n
  3. static void I2Cx_Error(uint8_t Addr)  
    " z6 A" {& B2 s. S# e! x( V
  4. {  
    . r$ C6 C1 v6 v
  5.   /* De-initialize the IOE comunication BUS */  3 A" u( }. w6 I: O
  6.   HAL_I2C_DeInit(&hi2c1);  8 F+ f$ q) ^8 k* l* v+ R9 Q
  7.   # s! u2 R9 q* q- ^* |- o
  8.   /* Re-Initiaize the IOE comunication BUS */  , L; F1 y; a" {/ h- g; ~8 t
  9.   //I2Cx_Init();  
    4 o8 B4 f& s6 s. ?
  10.   //MX_I2C1_Init();  2 j$ V3 w% W+ g# m8 j, W4 K
  11. }  ! _& e; [8 o! q% ^
  12. static void CODEC_Reset(void)  
    * t3 X' c" R1 _
  13. {  
    7 @+ W! D6 D3 L7 P9 E- l8 i1 `1 d
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  6 p9 O/ w# \% b" X9 [
  15.     HAL_Delay(5);  
    * {% \8 t9 @% d+ r% \
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  
    3 q: B6 T1 ?4 G- Y& n: m8 }
  17.     HAL_Delay(5);  
    ; ^7 z- f" c, t: O* j* @
  18. }  
    - Y/ T' }, _3 V$ ]4 u0 D& O4 W6 A1 g
  19. void AUDIO_IO_Init(void)  " F' n' O1 j7 r" D
  20. {  3 Q0 u! n9 E% z& n" h6 _1 v  D; I
  21.   //I2Cx_Init();  8 ]+ s8 ?8 i6 N' L
  22. }  
    - {2 K. z, N/ N) \( U
  23. void AUDIO_IO_DeInit(void)  
    : D7 [6 F- O! P4 [+ G% U
  24. {  $ h. ?' i& g9 v3 v  P5 M5 h
  25.   ) l6 L! i" P( _& X
  26. }  " M( K* j5 I7 E
  27. /** 7 s: o6 X9 {' J% X; s2 o
  28.   * @brief  Writes a single data. . h+ X0 T* U+ U2 I. L, C/ p: C' Z) Q
  29.   * @param  Addr: I2C address ) \6 \$ ~9 b( X1 ]
  30.   * @param  Reg: Reg address
    + h1 U% a* c5 m. J- C  R, P! `. f
  31.   * @param  Value: Data to be written
    4 p/ x8 J7 j1 X' n4 B# }/ d. P. q
  32.   */  - C, |0 Z0 j* Y/ t
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  9 y; [5 J( r$ B8 `! H, |% J
  34. {  
    " s8 T( y8 m% p1 l  T
  35.   HAL_StatusTypeDef status = HAL_OK;  
    ! K. o9 `5 F" c% R2 i
  36.   
    6 P/ n6 Q' p; `' q; ~; I0 F) V- _
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    1 P% N6 u! \2 y& L4 d8 [
  38.   ) E( s. N4 [2 p' ^4 K
  39.   /* Check the communication status */  
    $ j6 o- s( N6 {6 n
  40.   if(status != HAL_OK)  8 R1 r& r. T! X: o; K5 m; W# n, _  K
  41.   {  5 _+ P9 Y9 ~5 {! V
  42.     /* I2C error occured */  ) Q0 B0 f( z; I. E4 A
  43.     I2Cx_Error(Addr);  
    ; O9 x" ^6 V' A; N6 V& k
  44.   }  
    6 R: V0 E$ _5 W/ ^  h
  45. }  
    0 [! ]% ^( b. ?* {3 r3 e" d1 L* z; X: N
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    + C( e6 q: g7 v
  47. {  
    3 q5 e+ w( }" o0 g) [6 `, f  b
  48.   I2Cx_Write(Addr, Reg, Value);  ) n' {; b4 B7 F9 f
  49. }  & E' @7 B: q' W# i( {" r: s
  50.   1 S( {9 V; ^; c) E) o
  51. /** : S7 x' N% O0 \* o2 e  J- h
  52.   * @brief  Reads a single data.
    ) w$ t& C8 I8 }/ s7 e! s/ k
  53.   * @param  Addr: I2C address : K* y8 m6 f( r) m
  54.   * @param  Reg: Reg address
    % h' d6 z1 [' F  W+ v+ A' _# r; ?
  55.   * @retval Data to be read , d0 U5 a0 e& m: d4 z0 e+ B, u! t  v$ g
  56.   */  
    + H; C' J+ G  U$ X1 v
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  3 n7 V, {, a: }! [* i, w
  58. {  + o) H# f$ @  o. Z4 e  ]* Z8 Q( @
  59.   HAL_StatusTypeDef status = HAL_OK;  
    ; }( }2 n: |- z% B8 U6 b3 |' K
  60.   uint8_t Value = 0;  
    ' g* E# O. V8 o: y
  61.   
    8 ?5 C6 U, `# P
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    4 ^: _4 e: C' _% p1 L
  63.   
    8 E& K$ C6 h! Q. c7 r
  64.   /* Check the communication status */  ) x1 ?8 U: ]& n1 r0 D% |( I9 e
  65.   if(status != HAL_OK)  
    4 k) q3 @4 Y& O3 W0 J
  66.   {  4 A3 w& f# _& \2 A' [4 W
  67.     /* Execute user timeout callback */  * Z" J  N& K6 V4 r6 ?
  68.     I2Cx_Error(Addr);  * u, V- l" J0 U0 i9 `
  69.   }    f+ U9 g6 ^+ o9 E8 j' P  Z
  70.   / C  J  k4 p2 `5 i# ?% K" @2 ?
  71.   return Value;  
    ; e/ o% t3 ~; ]; \0 f! u: J
  72. }  
    : _' [- Z. b3 f% Q8 b, h
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    & j) B4 X. s6 K# T% k3 z' ^; i
  74. {  
    0 s6 q& q, V- {. @8 q5 I) u- d
  75.   return I2Cx_Read(Addr, Reg);  - y6 s/ v7 s, @) |
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。
# O3 [0 J! \+ j# W) ~2 F1 j# L* I9 ~0 p4 h/ P* z
就这样,就完成了Codec驱动与与HAL的对接。
: s5 Z' Z+ @8 ~
! C! _; R0 @: d5 X3.3.2.2 usb audiointerface与codec的对接1 \. y: X9 Z6 b% a: ^
我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。
, }& V! {: a* Y: L- J2 N, o' d# m, E* Y, u- h' _6 t' c, l$ C
按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:
; _& M7 R- N0 {$ A* a
  1. [cpp] view plain copy/ \( u  W; g% g, D! ~# V% {' X' R# s
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  3 ], c6 ]/ }( m( _- D  @! _) `6 E
  3. {   
    * a) \7 a, ?  l$ N/ k4 B4 @5 G: D
  4.   /* USER CODE BEGIN 0 */  1 S6 B5 {6 k7 J2 K' `; B
  5.   return (USBD_OK);  ( s) C4 U* l8 E2 e% J3 C' D
  6.   /* USER CODE END 0 */  
    , J) |, o6 `* I" z
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:
, E  p4 ^7 w: D( F3 X3 [3 G! b! _% a
  1. [cpp] view plain copy/ M! K0 k* ?, m* o# [
  2. static void I2Sx_Init(uint32_t AudioFreq)  4 I- S/ F0 w( Z; p, j7 V* J
  3. {  + c/ G+ l& O6 h  H, c
  4.   /* Initialize the haudio_i2s Instance parameter */  1 z2 w) U& |# e0 l* x
  5.   hi2s3.Instance = SPI3;  
    " A& D4 p8 B7 u# Q" z5 s1 G4 ^$ B8 y
  6.   6 R) p1 v3 z0 k( W6 G
  7. /* Disable I2S block */  1 L; A! p+ v  T6 _2 a" @/ A
  8.   __HAL_I2S_DISABLE(&hi2s3);  
    , V$ W5 `, I7 S, x+ X1 o9 n
  9.   
    ; v* Y% M- p8 a7 _$ E6 V
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  ! K1 W. Y) s) @: K$ D6 B! D# \1 L  Z
  11.   hi2s3.Init.Standard = I2S_STANDARD;  
    6 Y8 _0 y: a1 b  j. q6 a
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  2 _/ s' L% U) q- U0 H/ C4 ]) a
  13.   hi2s3.Init.AudioFreq = AudioFreq;  1 `* p9 q; g* c
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  
    . Z9 [1 A- |% G
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  
    ; K2 ?9 ?) a  _. k$ A2 p
  16.   
    5 y& E0 F( {" r8 V/ y
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  
    7 Z' s' s- L# g0 \/ m1 r0 K
  18.   {  " F) A: i$ F  K
  19.     HAL_I2S_MspInit(&hi2s3);  % V- ?; v; N/ \9 d
  20.   }  $ x4 u4 w5 K. h/ x0 g
  21.   /* Init the I2S */  
    2 l( a$ ^1 e* B* m
  22.   HAL_I2S_Init(&hi2s3);  
      L& Z3 {9 t; ^( q3 G# I) D6 S( m8 Y. f8 G/ ^
  23. }  
    " K+ y! }& z# L, z
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  ' C4 h- e7 _9 c2 B3 H
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  
    + G5 c/ h) ]3 g$ `; L6 |0 K$ T* {
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  
    9 e" l# {( @. p2 A( {) \
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  " y4 G* o( R8 l- t4 `8 z1 v
  28. {  6 b# [/ Q& j& }, U
  29.     uint32_t deviceid = 0x00;  7 K1 |' P8 S1 c# l1 L; k9 m
  30.     uint8_t ret = AUDIO_ERROR;  ! p+ P4 @7 ^- [. ~" e1 |, r* {
  31.     uint8_t index = 0, freqindex = 0xFF;  : }( |4 Q/ K2 j9 L) G- ^0 e' j
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  % i# t- f' L: ?4 B( E
  33.   . @. p- X" Q/ T, ?& l! Y
  34.     //get the according P,N value and set into config,this is for audio clock provide  , P6 i. l7 {7 m/ i0 K
  35.     for(index = 0; index < 8; index++)  8 E4 G. r/ A8 b+ g" B; I' J* J+ s& `
  36.     {  $ P+ T# p2 }  W8 G
  37.         if(I2SFreq[index] == AudioFreq)  
    9 U+ q7 T& _1 J: M& K4 U6 m3 h! c' B! y
  38.         {  $ ]4 _" P0 T+ S, n0 n8 R
  39.             freqindex = index;  
    % O/ M3 I* J! K' X1 z
  40.         }  
    7 ^: Z( {4 h4 n8 \7 ?8 c2 ~& {0 A* J
  41.     }  : L5 Q1 k: y0 G' V" _( w# ~
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  - Q! h& F$ P- n( ^  [  O1 ^# e* J
  43.     if(freqindex != 0xFF)  
    - L  x, w( F* v9 ^) c
  44.     {  9 Z/ \; r$ L8 S, i& a
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  - |' Q+ l7 s+ X+ ~; ~$ @0 h
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  
    6 e. ^, ]+ _% [  o! d0 a
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  ! s( \, ]/ ^: ~* u
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    - V" N2 ~/ L. h. m6 s9 E5 e
  49.     }  
    3 l! M1 T1 u4 O, [2 ?! r
  50.     else  , j3 O/ P& G  u" n8 w8 |
  51.     {  0 f3 ~& ~1 A2 [: P3 U, q' `) Y
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    # u/ T/ [1 u9 F6 V8 e. r  J
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  5 f( D5 L9 \0 Z% w3 _( q
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  
    ) b0 t; L6 h1 b. _8 x3 f: ^
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    , d  ^$ }4 k' f9 `! V( @$ N
  56.     }  . F4 p/ J* F4 \# n( y
  57.   
    # h1 {7 Q1 V6 N7 c3 C
  58.     //reset the Codec register  
    7 t7 |) J& x7 I( [) W6 ?. H
  59.     CODEC_Reset();  ; Q+ A9 M+ w! I) i7 }! \
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  
    2 C: R. H4 _+ T
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  # a# P2 ]3 t$ q
  62.       {  8 I& o. J) _5 s' h+ F
  63.         /* Initialize the audio driver structure */  2 I1 P9 s3 W8 c% B
  64.         audio_drv = &cs43l22_drv;  ; K1 Y9 Y& h2 P8 B3 B
  65.         ret = AUDIO_OK;  
    # g2 y" p& {" I6 U. a. K2 w
  66.       }  % A% b+ I" m6 e# c0 x7 q
  67.       else  
    . S7 x$ g' ?: L, e7 e3 t. Z
  68.       {  0 O) @& ^. p' s# x- Z0 y
  69.         ret = AUDIO_ERROR;  8 X+ B9 P0 o7 f1 `, a3 _1 `
  70.       }  1 H2 W3 d: e& Q3 t
  71.   - q2 ?, h5 n0 U( x2 g  ]2 B
  72.      if(ret == AUDIO_OK)  . V) V* H' i1 Y) Z
  73.       {  
    ) V! z' X$ h+ f+ ~- C3 j
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  9 a# N( O1 \3 _8 j7 H5 ?7 Y# Q& h
  75.         /* I2S data transfer preparation:
      `& f& V0 O. ]+ T/ P2 L
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  
    0 y6 D$ g; t9 h6 \
  77.         /* Configure the I2S peripheral */  
    . v; g/ w, }# E8 w# c: }6 M
  78.         I2Sx_Init(AudioFreq);  ) `$ i2 M8 ^3 i6 z7 Z. Q
  79.       }  2 M6 k- y' d# g! X& h! u4 b
  80.     return AUDIO_OK;  ! N% {6 r  `+ b9 b# b
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。9 {+ v& N0 n% ?+ A8 {

1 c. @1 N* P3 r8 q1 F0 E2 j这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:! L6 \# q6 y& M+ R  |% S1 p
16.png 8 q0 o% ~4 m7 [1 s

9 }7 n: E; k' H' w  n5 ~在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:' w* b: l' Y+ U5 l6 U! \
17.png
/ A7 x3 x& z3 N' z% a! B也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。$ I9 I/ z# i4 N; n. M# B3 Q
4 l7 N9 @6 b+ |5 H8 D
PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
- ^+ \9 c: ^; \, o: M8 ^8 X( }+ T' |) U7 t7 M& G% U# {
搞懂了这些之后,我们马上将其代码进行对接:1 A, X1 F( S" y
  1. [cpp] view plain copy' z0 ~9 t3 J0 p, {" z$ A( }
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  / A" d! v; G: E: C+ f$ O4 D: T
  3. {   
    6 c2 q2 ^6 F. q, ~: K, f
  4.   /* USER CODE BEGIN 0 */  
    : V$ O% c3 A! G8 Z* T( K# b' O
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  
    * G4 T7 I- J0 l, f: A2 j
  6.   return (USBD_OK);  8 W# u2 S% F# I6 q% a
  7.   /* USER CODE END 0 */  
    7 I! u3 u  R" Q% E* V
  8. }  
复制代码
接下来下一个需要对接的接口:
& X. U# o- G; Q" x: O0 @
  1. [cpp] view plain copy
    4 P# _; o8 u1 Z
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  
    & T9 [0 Y! D! \8 K+ t
  3. {  
    $ m0 d. Z. W* w+ T3 [
  4.   /* USER CODE BEGIN 1 */   
    ' B8 ]7 T& k: i
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  + {2 r; ~' H" Z( n
  6.   return (USBD_OK);  ( P7 z  ]0 o8 p6 y# H$ d0 X
  7.   /* USER CODE END 1 */  . O- ^7 Z6 E: N; ~; U. \
  8. }  
    5 U# e) q, b0 ?( s0 w' q
  9. 很明显,这个个反初始化的接口,它的具体实现如下:0 \( Q! B& r) T  U. K% ]

  10. . \1 i' R) f6 r3 Z) K2 D: ?
  11. [cpp] view plain copy. P  l+ k+ C9 l; h1 G
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  
    9 [( |; |. }) V; N& S9 L3 K* G
  13. {  
    , U8 D$ e. f6 Z4 s$ T7 m
  14.   /* Call the Media layer stop function */  / G  E6 t% ^/ J: M/ t
  15.   HAL_I2S_DMAStop(&hi2s3);  
    : b8 n0 ?9 r: q
  16.   
    : u3 ~3 G% D& V2 I
  17.   /* Call Audio Codec Stop function */  
    2 W0 c. E; T% d
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  2 z& v" _. x, A7 j5 M9 I1 l! w
  19.   {  & l! s0 a0 _% m( t& N6 M
  20.     return AUDIO_ERROR;  5 }- K) R3 D7 V  ~
  21.   }  . c( T; k. e* B  L' Q- H
  22.   else  ) U1 |/ q7 h2 B: M+ I
  23.   {  ' |& x6 n9 t5 Q$ O
  24.     if(Option == CODEC_PDWN_HW)  
    5 [; e. w9 @' ^) _) u
  25.     {  
    8 ?+ E. h1 p) n
  26.       /* Wait at least 1ms */  
    % H. `$ t/ J7 t
  27.       HAL_Delay(1);  
    ' q. G- }5 J4 n# _2 [; b9 [9 G
  28.   : e9 T3 R3 a9 |
  29.       /* Reset the pin */  - o$ N% a# E% {& u/ U
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  2 S8 e8 `  p- j! t
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    ' E" Q' h% N/ \$ U
  32.     }  ' q/ m7 p0 G* J- i0 D
  33.     Y0 E7 P& p+ _8 K2 E2 q' w
  34.     /* Return AUDIO_OK when all operations are correctly done */  
    + B2 _0 g; @- N" n4 Q
  35.     return AUDIO_OK;  
    + _" G. z8 G( \
  36.   }  2 S, m) Y) A; a( ?2 J! Z
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。
6 K, g- _/ R! [9 S  U6 J1 x1 T+ @OK,下一个:* i; N9 X8 r/ q- \( f* X5 @1 U
  1. [cpp] view plain copy
    / a3 X% o* V. J5 A) b
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  
    1 U+ u  N+ Y0 C) w* v- m
  3. {  
    5 c' L/ ^! {* _0 `4 `8 s# K, y
  4.   /* USER CODE BEGIN 2 */  . T9 X  _" y" r7 v  u' C
  5.   switch(cmd)  5 H+ B6 U  s8 ~8 Q9 a1 _
  6.   {  8 e+ F( w: r7 Z+ ^) p
  7.     case AUDIO_CMD_START:  5 f1 o0 D! r) i7 T4 V
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  6 h/ l* n' _7 ^: B* ?
  9.     break;  5 q, H( B7 s2 |
  10.   
    ) b5 z4 I3 I7 b" a7 e- e$ e4 l) L
  11.     case AUDIO_CMD_PLAY:  
    : G" {* C; ~6 S! |6 [( q& y
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  
    6 j/ V6 @7 ~+ }
  13.     break;  
    3 K! a7 b4 ?( ]1 x
  14.   }  4 }, O3 f3 F  {/ z/ Q. ]/ ^
  15.   return (USBD_OK);  
    # w, Y. J" f1 e8 E7 t: P- k. U3 ?6 s
  16.   /* USER CODE END 2 */  
    5 t7 {& z- ^& s1 }/ P' X, ]" z+ k
  17.     : S; M% d# T  I8 P3 ^  z  J. Z
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:7 T' N1 D/ G% O5 w  U
  1. [cpp] view plain copy' |( p7 _' M+ W3 L! @: L. n; F( G; [
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  
    % o4 B: ]2 k, {  [  a- z
  3. {  
    2 F3 k, o6 p3 d
  4.   /* Call the audio Codec Play function */  
    9 j1 O1 Z% p5 I' h
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  6 o1 E$ G' B6 r5 o3 {1 B( J
  6.   {  8 j, X/ s/ K  x" f9 v( M
  7.     return AUDIO_ERROR;  
    $ u/ V5 l  v/ r# M
  8.   }  4 {* A9 F7 P6 Q* ?4 z, P
  9.   else  + W6 ~, ~, f. K. |( [" O$ O
  10.   {  
    . J/ b8 O$ M9 G, {- z" H
  11.     /* Update the Media layer and enable it for play */    d& B9 }- Z( h+ w# j, ?; ?
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  
    " A/ s- h4 u: u! R# x
  13.     return AUDIO_OK;  
    + @# K2 ]4 E9 X2 V4 D4 j7 @; M
  14.   }  % h6 l- r6 C( ]
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。
4 a3 O  E) S+ f; N4 V- P2 W7 ]5 k1 U* [
然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
& v% b1 r; @& |$ F3 X) K) L4 [; [
  1. [cpp] view plain copy/ ^2 p: ]. j' z; {; c
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  & Z1 m- v" h1 j4 A7 Z  v# N
  3. {  
    ! X! b' U3 r; r0 M2 f
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  
    ; b9 z& X2 f8 J0 G' h
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
) Q$ _" ]! Z6 I( s3 u6 G- C9 N0 u* |0 B; x" u* \. c
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:; q$ c% b9 o  N8 a. ^# H
  1. [cpp] view plain copy
    3 k1 }3 U9 b, Y
  2. void TransferComplete_CallBack_FS(void)  2 u( Q8 k- O; y1 Q# e8 @5 i: j) Z/ ~
  3. {  6 Q; O- V3 \  x. U) u
  4.   /* USER CODE BEGIN 7 */   
    : B# e$ ~& V) r6 X$ D
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  
    1 x1 L& D" e1 }& F; S
  6.   /* USER CODE END 7 */  " s: t( `3 S* W% b8 M6 O# V3 `
  7. }  ) |3 K. A- a6 x! W; {1 T8 H8 e/ o  r
  8. void HalfTransfer_CallBack_FS(void)  
    6 W& E& u4 i7 d/ y8 s, J
  9. {   
    ( y6 A" s: u. n7 m. X6 S/ n
  10.   /* USER CODE BEGIN 8 */   . G9 [  W0 }4 c3 i: ]3 F
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  * y8 b0 |" U) F& r) d) c
  12.   /* USER CODE END 8 */  
    / D8 Q- E) b5 ?/ X
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。, a+ D, I. `1 [+ w
" N+ ^' p% _; t4 `0 ?
此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。
5 O/ W" w% X7 ~* G3 f7 @! s. H; P% _7 r, a9 L
接下来看下一个usbd_audio_if接口函数对接:" z3 a. `' ^; ^% I
  1. [cpp] view plain copy: Y% w4 H& L# @
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  / H: M! r  H7 B5 D
  3. {  : Y; G2 c3 E0 t2 `: O0 P8 N# \
  4.   /* USER CODE BEGIN 3 */   
    . L" c0 |; P  J4 _* ]% X1 Q
  5.   BSP_AUDIO_OUT_SetVolume(vol);  % M0 Y# J( W- o: e. D9 j$ L6 z; `+ l
  6.   return (USBD_OK);  % i' H0 Q- a: m. {' S  _, \3 q- S
  7.   /* USER CODE END 3 */  5 i9 x1 n8 ], V+ V( ^. [# a% v
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:4 O* E. I$ L8 x0 s
  1. [cpp] view plain copy
    2 Y. B/ {, }$ J* `6 a
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  
    5 H: B: L( m# S# t. _( y' O
  3. {  & `4 R1 I% L( v! R8 V( n3 N
  4.   /* Call the codec volume control function with converted volume value */  
    " l/ ^* D2 \$ s" p
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  , U# |/ ^& l* u* ?* ^) C
  6.   {  6 [! T# h9 M4 z- u
  7.     return AUDIO_ERROR;  
    3 O* j! ]% Y6 N8 f0 z1 }3 e
  8.   }  
    7 [" N0 W% k  h- u3 @5 \1 a- g
  9.   else  
    1 U! F+ P! z/ H
  10.   {  
    $ X$ A$ Q* v  d) m
  11.     /* Return AUDIO_OK when all operations are correctly done */  7 C' }* j) l5 w! m# ?, @9 q3 l
  12.     return AUDIO_OK;    E6 O& [3 b1 f! M- i
  13.   }  % ~' d6 C. B5 t3 }. A  L, H
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。
. Q' Z/ D. P& K9 n  P& E+ j
$ y$ c$ N! e4 }# D下一个:
  J& I5 i$ `( N+ _9 l
  1. [cpp] view plain copy
    1 n6 I& l7 x0 p4 J4 s
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  2 f, l5 x# \; n- c( K) L
  3. {  , y* A. u$ Q5 q  n! Y
  4.   /* USER CODE BEGIN 4 */   
    & x! ~' T% e9 q
  5.   BSP_AUDIO_OUT_SetMute(cmd);  , N* D- s4 l5 O& g+ c3 q
  6.   return (USBD_OK);  4 ^" |, Q" v" U( X
  7.   /* USER CODE END 4 */  
      a- c8 D- ~6 p" P& G8 R3 N! u* X' K
  8. }  
复制代码
静音控制,其实现为:  V+ ?' i8 y1 x" J7 a2 J
  1. [cpp] view plain copy2 L- }2 f& r* a: S) i! I6 w
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  
    " a! b; F3 G7 Q" }; |, ^  a( f4 n" o
  3. {  % j3 f, K1 c" S, r5 Q
  4.   /* Call the Codec Mute function */  ; r( J, h: f6 H  Q
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  7 ]* i% m6 |) V+ M+ t
  6.   {  
    , l9 u# s' W9 q& T* _
  7.     return AUDIO_ERROR;  - V/ |2 Q( O3 m( j6 g. _/ c0 l
  8.   }  
    : R' [! D" v" D) M! V/ M. w% T
  9.   else  
    1 T7 K/ s* s. x2 u9 l- ^* d
  10.   {  ( I7 p  X0 o9 U2 J1 {
  11.     /* Return AUDIO_OK when all operations are correctly done */  ' C1 \& }2 W& }# O( O) ~. H
  12.     return AUDIO_OK;  
    # L% Z5 Y2 l$ N" f4 ^
  13.   }  
    - B& y- O- x6 D5 J3 N$ J7 u
  14. }  
    & O7 z+ Z5 D# _( z
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。7 q) \0 @, `/ s, ?
. `9 ~/ o, f: u" |( i5 m
OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。# C$ }! i7 q1 [% [+ @- }; R

. @( G  ?6 x8 }* |) u' q; w/ e4    测试验证2 g7 S/ z( d8 e7 f$ C4 ^
将代码编译后烧录进STM32F4DISCVOERY板进行验证。& x- f3 B. \- E; ~2 Y7 E
18.png
' z4 c6 g7 Q  O: `最终验证是OK的,可以从耳机上听到PC端播放的音乐。
7 N- m+ f4 {0 x. A
" k. T5 @1 K; `# g" p( k0 [+ ?5    结束语
, I/ ]" B4 v: Y4 R7 u& F. O在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。
% B: |; z! b/ h0 J. G4 g2 |+ x! A2 F9 E% q- Y7 }
I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。
5 A6 o: {9 [9 m7 p. Y1 ~- r( v- B( k) M9 p
在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。
) d7 e1 v% V5 B& K) V5 m& H& k% d$ n& N- Z

$ C2 C5 |7 H( |* t: M! y
* c% R% X4 h6 ~. u2 Y1 H7 p
; d# P" A2 x- h0 U

评分

参与人数 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& \+ O+ L8 {  o( y" o9 H* Vstatic uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,; g& P8 }0 x3 [! b- ]6 Z
                              uint8_t epnum)
: v, Q+ v- R9 A7 d; |# \{0 v2 \9 s/ i! |1 l- n; i# b1 w
  USBD_AUDIO_HandleTypeDef   *haudio;
# @& q2 u! j, Y( ^* `5 ^/ Z  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;
8 B5 h! y* c2 I1 {! p! {4 I
2 w+ T$ e' a( L3 c- y  if (epnum == AUDIO_OUT_EP)
, R, I: ?. w3 a- T  {
+ P- g( Z: G2 Y    /* Increment the Buffer pointer or roll it back when all buffers are full */: T, C, q5 m/ V

2 J$ P: y0 S# I" S    haudio->wr_ptr += AUDIO_OUT_PACKET;
3 x6 Z2 q+ a, ~
' ^6 K6 |' p, `% o% B9 p3 G7 o. N    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
$ i; S3 q' I+ ]! \' _! t$ l    {$ h0 e7 `* V3 l! j
      /* All buffers are full: roll back */8 h% E' Y& F2 Z9 z/ Z" k
      haudio->wr_ptr = 0U;& o/ v5 M- `' ?. Z# p8 u
    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],! v- e5 `  k# H
                                                 AUDIO_TOTAL_BUF_SIZE / 2U,, w  H/ ^1 o" i7 E7 G' m0 i
                                                 AUDIO_CMD_PLAY);- |" o5 g* ^: c2 I% ?
#if 0/ K8 N5 h1 o8 @: s1 R
      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
8 r, b7 j6 L- \      {; X, i. |, Y9 t2 n2 A
        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],8 R& [* q( s  G4 n4 y
                                                             AUDIO_TOTAL_BUF_SIZE / 2U,7 m5 r5 l6 M- O. z6 f% V
                                                             AUDIO_CMD_START);1 B& m2 c8 A, x# t, Y* p
          haudio->offset = AUDIO_OFFSET_NONE;
4 e  W6 F, w* j0 w9 l, u1 L, |& W      }! ?( X' _0 @# s1 W6 B. ^1 v+ N
#endif# }  v/ E- n& y* @* I3 s1 j
    }0 T  B4 [2 G4 q1 k+ c# D& U# I# q/ x/ k; L1 e

4 f$ w9 ~9 }; u- I+ {: G* ]( U    if(haudio->rd_enable == 0U)
3 C6 d  Q7 j6 ]  }5 o2 s    {- b# ]% l: z9 x! w5 A, Z
      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))
: c/ C9 R7 ?! O, D9 a. ^$ l      {
9 A* e1 T$ d, `; g/ B0 Z* b        haudio->rd_enable = 1U;
7 o# E5 K( V: O* b3 i      }) d2 b0 M) `8 L6 T0 I
    }# u8 A3 u3 j3 Q1 f+ `0 T9 }

, t6 S: P( x, g    /* Prepare Out endpoint to receive next audio packet */' ?) G7 P; i/ L0 z' K
    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],
5 P& t* I) k( q  b! p4 F9 ^2 S                           AUDIO_OUT_PACKET);
: ^! T. ^7 |. V2 V, ^  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?3 Z/ Z; {6 ^0 g* z. x* ~
我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,; A3 \) ?0 K% N5 A6 n; c6 Y8 T4 d
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。4 F  |5 V0 Z: B9 N' w$ m; L, V
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,, N) `0 D) @. G6 |
但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,
( H" Y  X0 j! L而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20
& O- N4 J7 j, Q) E. a1 G这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示: b4 ]" v5 v/ g. Q& N: _, l# b. z
8 |) P; S1 P8 H0 k4 P, P
该设备无法启动。 (代码 10)

( i; \( O& R# y出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
; e8 y& @% Q% a5 z非常好的帖子,谢谢分享!!!

( ~9 N4 j: v3 R% w+ `. `& j3 N& ?希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
- J8 W8 Q7 `1 h, R7 w! Y. C8 h非常好的帖子,谢谢分享!!!

3 C6 g; a& u2 q" I: X/ l; a希望能有帮助
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
  k) }6 ~! n- B9 }6 |2 R9 G( v
非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!* E+ {4 `0 X3 M+ F! p: x

  d6 o& Y/ ~6 @6 X

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:18
2 Q' c5 B6 k: h% M, ^楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
' J% i& z1 f4 m& H# [1 K# @0 m5 U0 U! X
...

8 i; ^  U" }3 V这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示$ P( \5 e  x; p6 v" g  m& r
1 K5 A' {. a% B1 a
该设备无法启动。 (代码 10)
: p' z! k: u+ e/ Y
1 t1 @6 H9 U; hI/O 请求已取消。% _+ G  C0 w- Y. H* J
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20
" Y2 ~/ \, D' }8 n0 l+ Y# b- W这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示# J# R5 g$ Q" ]0 Q
4 B6 _) q8 J6 E
该设备无法启动。 (代码 10)
1 L9 {" ?9 Q, H2 T" r8 K8 c, L' c" T3 @
经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页

所属标签

相似技术帖

官网相关资源

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