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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑
1 [. I; D6 Q4 O
: m, {, U" x$ y( P# e1 前言9 G" K8 k. H% g( X/ o" z
本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
8 A% h% {7 r' U# x& ~) J  \! d
6 E  O; Q$ Y5 s% V2 设计构思$ A# q' X* b% B) J* x6 s
所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。
( f+ r7 a' ]9 }* m2 m" P( m  K! M$ Q- k) Z3 S( F
2.1 从原理框图开始& b/ G( F0 X1 g' k' k2 Z0 @
1.png
; Q8 y0 v  Y' E* Z& N' E如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。) f6 ^! R2 I- o

. l; j+ e# _  X( s7 z2.2 硬件支撑
% V/ T8 w  F- B$ Q6 L这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。2 ~) n: b, u. a! C& H& {# _# ]
8 s3 {4 c+ C& x$ G  ?3 C3 o8 {- ]! \
2.2.1 USB接口
. _( y' ^# X! g4 ~( v; `如下图为USB接口部分的电路:
" }3 C" A" D: [  O- f  K/ i  t 2.png
) Y' _9 f& r6 S3 i+ ?6 s) j. m这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。
" P3 O3 o. r: w, ]3 E1 E; v
2 y* P( ?9 E; Y  Y* V" `! k2.2.2 Codec部分
- U/ d2 s9 ]/ u9 N1 y如下图所示:
8 W$ _7 o6 V- V7 X, q# k' e 3.png ( x6 F4 C2 \4 [
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。
& j/ f# ~1 u- b: y
9 T- W# T/ r- G; _' P2.3 软件设计
$ x, a" d" L5 p9 g* A; L为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。
( z+ {$ H3 B& B$ @0 K 4.png + T! I" r3 D& K" U/ k% V
4 j, \- X4 C- G4 v. g7 U
如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。# e5 [7 Z4 B) J1 T) v
* i( J7 V  @5 ]2 Z
各个模块的工作流程如下设计:- W& v. ^5 u# N1 J4 [- h1 _
% a% x0 a' `  X7 U6 q8 k' h, g' C
初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
) @9 l5 d" v! r- yUSB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
- D( B0 O: ~" I4 gUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。* w; S$ R* Y& I9 Y1 T
从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。
! V4 T) B" G0 v) m* q) T" i2 I) D! ^7 D: ?
接下来,我们来看看软件层面上的实现。
# Z" R0 \8 E, `) L. g& R# g
6 v( G- X& [! C8 u1 F$ K. K3 软件实现
6 e0 Z8 o/ ?( s: Q还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。/ i# F+ |% i; @2 N# i) s

! d3 D8 D* ^" @2 T! H. Y" S3.1 创建CubeMx工程
# M9 h  j& s6 e' c$ E9 E. I( k由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。
; b" Z% ~+ f. o5 Q' H8 A
( ~' w& p+ I, C+ e3 K7 M) |! P5 G! rpinout:
+ J+ W  ]' D! V0 {7 k) f9 s+ D! I2 f( Y# y' I9 Z* Y
外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:
+ E+ K) ~& j2 W 5.png
! |3 Z2 m$ e& u; r. C
% W3 q2 o: \% z* X: M/ kClock configuration:& j8 C" x; n$ E. B3 ^: G6 @' J+ p
6.png
+ b3 j* R5 }" L+ e! ^+ S* E' j. A$ Z时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。
) {# b" x% H+ s9 j0 r2 u( l% ?. q# F% q
Configuration:7 o) D1 k: x3 J7 k% ^

/ X2 E" V- w/ N5 X  HAL层:. h9 y8 w  d7 z" u# H, f$ O0 ^
Usb_FS:使用默认参数。9 W, M, I4 Q8 [$ D

5 ^. I/ H# ?7 h+ J( g; JI2C:100K速率,7位地址宽度,使用默认参数。
0 d4 Z0 J# R6 }
% P: @* q  ~) |8 ]) C5 VI2S:主发模式,标准16位宽,默认音频为48K,如下图:
! w8 O% Y/ \" W5 N5 a8 q 7.png # l& p# V) ]+ y
并为I2S发送添加DMA,半字位宽:
% R' r5 T: I1 A9 g! L. q, D 8.png : h6 K* p1 k: I, [

' ?. U* H% j7 K9 _   MiddleWares:
8 C0 l' v3 D8 |3 [$ e
  [/ ]% i% |3 U8 aUSB选择Audiodevice class,其配置参数如下:/ b) v$ \) k9 Q9 ?2 U. M0 w' m
9.png + U: [( Q3 c; h% D7 {% [; x3 F

2 G8 M+ Z! a+ u  E$ L$ O9 f2 y这里都是默认参数。; t; W/ I8 d) _
10.png
, R" {6 a$ K) e2 @在描述符参数内得为usb audio class修改两个参数:
: t# i! a, A- t$ Z6 y; ~0 w& q6 f: M. N  }: j. z
PID得修改为0x5730(否则windows驱动会加载出错), ~# G' Q3 ^1 ?2 G& A8 n
序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。3 x, |9 S: m* Z1 d3 C) L3 h
最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:
9 w: f$ A. z' ?3 s6 ^ 11.png
8 l. c4 n& ^/ ~6 r如此就可以生成工程了,我们生成IAR工程。
' h( V( v2 M# }& l. _9 P
2 S3 z/ j8 x  O' L" m0 V& m- x3.2 生成的IAR工程介绍, e& {3 M) l9 r' C; k0 j
12.png
: {8 I( r5 i3 C  x- P如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。. Y8 u. T% o" X  `8 [) @
$ Q( d; Y8 \  E3 T+ H+ \5 A8 e* m5 g
User目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。: K6 \" L: s% }' Z- I- ]; H$ W+ G
Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。% h; S# F& W( u  J" ~6 o% W  }2 b
Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。
: H  Z  j! T5 g: ^3 J) \% `% C3 t2 E8 M2 g2 I
3.3 开发' f2 T; L/ D8 N
3.3.1 初次编译测试
7 V7 F0 x& O6 u+ k5 U% Z/ {首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
/ {# p$ i4 O/ C! o5 } 13.png
& P3 ]7 t4 D% }7 w) D1 r4 D这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:0 E( L; |* J! b6 m
14.png $ w4 X* J. Z$ s# [- b+ e7 e
这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.
3 \! H% H2 A% x4 a- W& I. ]- u2 J: x& }7 K( Z' l: D% _5 R
3.3.2 添加codec驱动和audio bsp模块# }9 d% e( [1 m3 x
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:. ?/ o2 n: m; C- D+ g4 j6 w) Z
15.png 2 `$ o5 h/ d1 v  x; N9 J* t
其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。; d( P- x) Q( V; r
# r9 L, R# N: g; L0 |8 `7 p
3.3.2.1 Codec与HAL的对接$ h& w( U8 o  X1 C$ y8 e4 K1 \
首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:
7 P6 o3 v# T& T( n* x
  1. [cpp] view plain copy
    - s, J+ m  V& }8 c
  2. AUDIO_IO_Init()  
    9 ^1 A* Z  L7 L7 I+ Z
  3. AUDIO_IO_DeInit()  
    % H& b# }* A# m" K6 o) m3 u, y
  4. AUDIO_IO_Write()  
    % [6 e0 g; b" v0 ~  n
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:
) r+ {* F5 F% v+ K! z: k
  1. [cpp] view plain copy
    8 b4 ?5 J6 E+ O$ w, L, ^8 O( F
  2. //---------------------for c43l22 port--------------------------//  
    5 Q: o  w( |$ _3 l
  3. static void I2Cx_Error(uint8_t Addr)  
    2 W4 k1 u$ H5 s( y7 T8 t
  4. {  , }: m' F* `, K# E- l4 S
  5.   /* De-initialize the IOE comunication BUS */  $ w! U1 d4 B' f0 ~5 U/ K  _
  6.   HAL_I2C_DeInit(&hi2c1);  
    ( Y' H1 D9 Z. z% L5 W1 H
  7.   
    6 S5 F+ Z- e/ Y) w5 E) A( B
  8.   /* Re-Initiaize the IOE comunication BUS */  , ^/ j4 Z3 |* m9 b
  9.   //I2Cx_Init();  : _9 d6 B, S/ u1 U+ u/ U9 a, n  R$ V
  10.   //MX_I2C1_Init();  2 b* ^: U" ?% q) D0 L0 d
  11. }  3 @6 R$ ^) m) b3 ~. O5 c
  12. static void CODEC_Reset(void)  - T8 `, i: y# M
  13. {  : X. j3 n( W9 h8 a, t+ c
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  1 Y* O- j% V: W* }. j8 [) ]
  15.     HAL_Delay(5);  
    " v$ A0 U! l' O9 D
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  
    ) t8 I- }* q! B- Q$ j
  17.     HAL_Delay(5);  
    , J2 Y4 g  o7 R6 S% M
  18. }  
    " k" w* K- u+ t. y4 l0 v
  19. void AUDIO_IO_Init(void)    Y. I8 Q7 D0 A! Y' [
  20. {  5 g  e! c: ]* w: E9 o# |; ]  t
  21.   //I2Cx_Init();  
    + o& z% U+ e8 C# r: I
  22. }    W% c4 a" h* L* z! O
  23. void AUDIO_IO_DeInit(void)  % |2 m; s8 o0 A" f- ?+ H
  24. {  
    ; e1 P% c; q3 t7 G; G
  25.   - B' k1 ]% Q$ u5 [% }: o
  26. }  
    + W: l! A/ V2 F! r4 h. `- V9 B
  27. /**
    6 v# y4 N8 x9 m0 z
  28.   * @brief  Writes a single data.
    5 r7 d. s6 p% I
  29.   * @param  Addr: I2C address
    . L7 \, o8 h, z4 l/ P7 Z2 M
  30.   * @param  Reg: Reg address
    ; y9 W& U, }1 o) g0 T4 f
  31.   * @param  Value: Data to be written
    - i! m% D7 u5 ~; l" e
  32.   */  
    9 f/ \) W8 R, g4 o/ Z; V0 R5 M
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  2 F, t- s  \' F* A' ]
  34. {  
    $ M$ t3 F. d3 m3 c( q. @+ _
  35.   HAL_StatusTypeDef status = HAL_OK;  ! e& P8 i3 n. E5 E  x$ M
  36.   
    7 c: J/ C1 S3 b6 ^7 _! ^
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  , s8 `; e/ Y1 d& x
  38.   
    0 c: {1 N! j8 U! F+ S) @! N
  39.   /* Check the communication status */  
    ) n+ Z. x5 N# J0 w; m& P  E! p
  40.   if(status != HAL_OK)  6 P3 G: o. C; N% N- L
  41.   {  / U6 B2 f& F5 i+ ]8 i
  42.     /* I2C error occured */  
    # _4 E4 G7 W" G3 d$ K! F% Y
  43.     I2Cx_Error(Addr);  
    % g4 J4 E' b' a0 B3 i& U1 k
  44.   }  5 G: A  S# M# `  w1 c. E
  45. }  
    + c! _6 `9 V3 v7 F4 u
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)    i. |5 M' ~  T# X9 F
  47. {  
    " `4 d3 A0 ^7 Q3 U' I: O/ U' O* H
  48.   I2Cx_Write(Addr, Reg, Value);  ; p! V. z( Q' S1 c/ h/ i3 w
  49. }  
    ! x  d; u, m3 n( K
  50.   ' @5 @( K# ^/ m* k, Y
  51. /**
    8 f7 C& N5 F2 T9 z6 x6 b5 c
  52.   * @brief  Reads a single data.
    * x& E" H+ {8 r! N$ j
  53.   * @param  Addr: I2C address . I9 n7 p. Y! h9 Q: c8 e
  54.   * @param  Reg: Reg address
    ( g0 k0 B# x- m0 u
  55.   * @retval Data to be read - _3 V) j. b4 }: _* F2 U+ [/ t
  56.   */  
    : V! f- H: ~  D, V9 b) q4 H
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  : w, l9 W* F' Z3 ~) V$ z2 w
  58. {  ' }9 s+ z" W% ~+ b# a2 c* E8 e
  59.   HAL_StatusTypeDef status = HAL_OK;  " G9 E; S* f7 ~2 y+ c
  60.   uint8_t Value = 0;  ! N4 |1 D4 X0 v; S
  61.   6 }; d( A' D9 F' u& q: N% h  B
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    2 E& w8 \2 z6 j+ {" F9 J
  63.   
    & n& }/ A5 D" Y
  64.   /* Check the communication status */  
    9 C/ ^8 C5 ?# J
  65.   if(status != HAL_OK)  6 `  k* n( R) H  S! [, r- @9 B+ L
  66.   {  
    0 `, M! a+ V! A  g, T
  67.     /* Execute user timeout callback */  
    1 c7 l" c0 b% Q* n/ \* P
  68.     I2Cx_Error(Addr);  8 E- b/ H8 X+ T6 x/ h
  69.   }  
    4 U( `, y* F0 I6 w$ B& ~( ~: D
  70.   
    6 ?+ P4 c& X2 ]2 s" a
  71.   return Value;  + z. e9 u( F7 ?8 _( |0 K
  72. }  
    ' B& l4 y+ O3 M' K  ?* W7 h
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    ( z; O6 R. h! ?) l, M
  74. {  
    1 ]: J8 J  I/ x+ z2 r/ Z) N* z
  75.   return I2Cx_Read(Addr, Reg);  
    - N. ]  g3 C7 C  @, O# a
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。
9 [  @/ E2 E& @, n- ]; }! z
$ s2 m7 S* k. n, Y3 [2 H, @3 w3 e就这样,就完成了Codec驱动与与HAL的对接。
5 e. E. x/ l) s* ?5 T9 Q: C* F
! S9 i" r9 [8 o7 n( J. v5 {, Y& Q3.3.2.2 usb audiointerface与codec的对接. g- ?: }6 }, I- p. V5 y
我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。; }& |: |$ I9 _& u  \$ W8 `
1 R! M* q7 v" }. K$ U8 T
按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:9 \  g- G2 P$ a' x9 t
  1. [cpp] view plain copy
    2 T+ A% `) w* O. @6 D8 K
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  8 r, A( R; c; t9 P  r+ b
  3. {   
    : A, y  a2 d- e4 l- ?
  4.   /* USER CODE BEGIN 0 */  ! T- W5 L# h) U* `/ [6 H
  5.   return (USBD_OK);  
    3 C: I' x3 }' R) @' f% Q. `
  6.   /* USER CODE END 0 */  $ h" k# K: K5 }. y/ U& P
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:
8 r& y" J  z0 I7 H% Y
  1. [cpp] view plain copy
    ' _$ E- S/ `# N) v/ b
  2. static void I2Sx_Init(uint32_t AudioFreq)  
    ) }) V1 x  J4 e$ q" S+ L" g" M0 D# A
  3. {  - A( i) x& S, |- C& _. P, e
  4.   /* Initialize the haudio_i2s Instance parameter */  # _/ ^( Z5 b/ x1 Y$ B+ f
  5.   hi2s3.Instance = SPI3;  
    % z# c. u' Q" l! }3 S, a
  6.   
    6 m  n; u$ V" F2 v- D9 J: N0 h2 ^5 i
  7. /* Disable I2S block */  4 e8 g7 t& x( x1 g7 B# w
  8.   __HAL_I2S_DISABLE(&hi2s3);  
    % F8 \) V+ g# b- P% ~  D  _! Y
  9.   8 H( f$ y4 f2 m7 M. D4 K; X5 h; @
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  
    - x% l0 Q: J# @
  11.   hi2s3.Init.Standard = I2S_STANDARD;  ! V8 Y0 X1 N5 W
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  6 F' u/ O5 u8 H: U% t% z2 @( U# i
  13.   hi2s3.Init.AudioFreq = AudioFreq;  - m* t& c2 u1 E$ ?7 \5 a7 |
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  ! E" X- p  ]7 B, I" a' K9 E3 w- A( G
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  
    / l/ [8 g' R0 ~
  16.   
    & C. P( b  O( @; m. ]+ N
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  - \' e2 E/ ]9 z# t
  18.   {  2 z7 S' o* M- q! R/ G
  19.     HAL_I2S_MspInit(&hi2s3);  
    2 G! x& [5 j; r: W6 D# Z
  20.   }  
    ) f" Z0 V4 R; C& S1 E4 e- W
  21.   /* Init the I2S */  5 L. |2 y+ ]( @& l: h4 a( l
  22.   HAL_I2S_Init(&hi2s3);  * i2 {, ^) B$ @8 r
  23. }  4 c) o: V( b$ I- N
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  
    9 v5 n* `) R8 _" Y5 T& X+ C- h2 ]" f
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  ; N/ h) B! d  B8 W9 M
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  * T% H- u; w0 k6 {3 x% m4 _* x
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  * ]: J2 X" E: P6 u  i+ D. m
  28. {  0 l$ [: i7 u3 H% }
  29.     uint32_t deviceid = 0x00;  ; q' c+ F+ D5 B$ P; g% O
  30.     uint8_t ret = AUDIO_ERROR;  
    ' \, P- \6 q" u; e- a
  31.     uint8_t index = 0, freqindex = 0xFF;  8 o( a& `, [' u
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  ) L( O+ a5 P6 |3 B& \7 `' I% F* |
  33.   ' R- r/ u$ @1 ?" ?& u
  34.     //get the according P,N value and set into config,this is for audio clock provide  
    6 p) M" O6 o3 a0 C  j
  35.     for(index = 0; index < 8; index++)  $ A2 z8 {7 |- `4 ~7 u2 ~
  36.     {  ! [+ x, w3 I" a
  37.         if(I2SFreq[index] == AudioFreq)  ; X" G) `1 i8 P
  38.         {  & v5 ?( x9 o4 x
  39.             freqindex = index;  
    $ |2 B  q* S* F9 d/ o% m
  40.         }  9 e+ V. c0 E9 E# P) o
  41.     }  
    4 @( s% s3 m& v( |4 A
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  
    / X6 M" R  x8 r' D) m# I
  43.     if(freqindex != 0xFF)  
    ! j0 v1 @' ]2 W$ i! ]
  44.     {    C3 r9 Y  o& j! V* G8 m; x4 d) S
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
      M; w1 Y& @; Y$ N
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  7 ^+ O: l3 u' M
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  
    - M* H6 p/ z1 [9 L3 E
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    ' a+ Z: e1 K5 k6 Y
  49.     }  
    5 _' v0 ?- a5 G  T3 F) ^
  50.     else  
    , g3 A1 d* l4 S
  51.     {  
    3 I8 r7 A9 H  D) W8 ^
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    ! Q/ Y3 }+ ^- D: m8 x( V, w
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  
    # A* D4 e$ E6 c/ a
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  
    , J. Y  Z& ~! q, m. C; C
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  * m5 ]3 y. N/ {, `7 f; B
  56.     }  
    ! z: H% @- @) N0 X) W  I0 N0 w
  57.   
    0 d3 `% E$ x% h0 Q6 R0 w5 O
  58.     //reset the Codec register  
    ' N* n  c- L2 t6 o
  59.     CODEC_Reset();  
    " j6 Z* ]0 U+ B
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  
    ( A! L$ y7 n5 `8 t& A) q2 q
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  0 g  K8 J  R% _% {7 a
  62.       {  
    / E# i7 V7 @; }# I5 r% p
  63.         /* Initialize the audio driver structure */  4 F  a7 J5 y+ T* x
  64.         audio_drv = &cs43l22_drv;  9 j- p8 }$ u2 c0 e/ i# l
  65.         ret = AUDIO_OK;  
    / u, \& ~7 x$ Q* t) @
  66.       }  * |3 |4 ~5 q- o* |5 T
  67.       else  * j3 p9 O7 o0 q" d# E3 w
  68.       {  
    5 Y5 A' a; o; a$ p1 |( z4 ]
  69.         ret = AUDIO_ERROR;  
    . e8 b- u9 j0 L7 a
  70.       }  
    # ?" ^. Z0 l6 u+ p! |5 ]
  71.   
    5 J' }1 B, S' x- t. _- Y& L
  72.      if(ret == AUDIO_OK)  2 }" i0 C# l" X9 n# _
  73.       {  
    9 e8 M+ H4 W9 ?4 ]) o6 T
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  
    + q0 W$ u- P3 S: ~) i
  75.         /* I2S data transfer preparation:
    ) Y9 X1 f# y- m. q
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  
    " O* T/ P' g& t: `9 v* ~* T2 P
  77.         /* Configure the I2S peripheral */  
    5 f: p0 A' M+ A9 [6 |
  78.         I2Sx_Init(AudioFreq);  
    # ]. q& ^) ~$ j! K0 E  ]5 B4 D
  79.       }  
    , P: z: J1 M& {4 E  l/ ~
  80.     return AUDIO_OK;  
    ( s- {1 F0 t6 H+ m( v& E7 m! v
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。/ `) x7 v; Z  @2 f: O: P
4 r  `/ G1 }0 P7 n' O
这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:! e$ z" t7 c/ H4 O" y; Z6 }
16.png
2 \  p, }9 a1 x4 Q
; s) @& v( q1 n  @- p; ^! X/ ^. ^8 T在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
/ C2 {: s' v3 J% W) M. Z% | 17.png ' y" Q" a$ c2 l2 U& J  [
也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。
& O4 G; e, s$ Z- U0 |( z3 L* c, Z9 ~
PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
( f5 G0 j9 w9 t' }/ A
$ Z9 F0 f$ A! w: D搞懂了这些之后,我们马上将其代码进行对接:
6 `" z1 S; o5 r8 U: I6 H. g; b
  1. [cpp] view plain copy( K4 e! u& Q! {9 _
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
    : ^( i8 s- Q4 j) [# V) h" K
  3. {   
    ) B8 Z/ t$ @7 y* }$ L
  4.   /* USER CODE BEGIN 0 */  ) H4 I" E2 h& p% M7 M
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  1 l0 l  G. _4 x& j# K& o
  6.   return (USBD_OK);  - y! Y+ ~- ]( j0 Q  V) q5 k
  7.   /* USER CODE END 0 */  5 k) @& I3 O$ q9 m% E
  8. }  
复制代码
接下来下一个需要对接的接口:
8 U. `6 y9 J6 v
  1. [cpp] view plain copy' b2 i& {5 q1 y  m9 M; Q
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  
    . i: ?! X4 h# P( V4 t4 Q
  3. {  
    , W" }. `7 m1 N* z
  4.   /* USER CODE BEGIN 1 */   % e1 R1 z( H! ~  A/ _* a
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  , @# H7 H0 @- i; M  _" @9 }' O
  6.   return (USBD_OK);  
    ) Y$ X: C, m8 g% n, ]( f" g6 V
  7.   /* USER CODE END 1 */  
    % X* X, x. g  U7 E1 k
  8. }  
    " {( J- V' M, Z
  9. 很明显,这个个反初始化的接口,它的具体实现如下:
      a* ]+ g5 b0 Y# s4 G* [4 a( e5 V
  10.   r3 H- Z# p; w' S8 C* E" _2 `
  11. [cpp] view plain copy
    ' o  x7 J" A6 V) v+ y
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  
    6 E& k) W3 n& t3 U. a- ]
  13. {  , e. \8 J! w! d4 f
  14.   /* Call the Media layer stop function */  1 k# N9 S" j& _$ Y) l: W
  15.   HAL_I2S_DMAStop(&hi2s3);  # g6 Z2 S+ b6 i/ c, }+ w
  16.   
    9 Q/ L: P% x# l
  17.   /* Call Audio Codec Stop function */  2 i3 J8 g- \/ U
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  9 A" a8 `8 ?" S3 c$ o( X! ~
  19.   {  
    1 [6 @# x# E8 K) _  B
  20.     return AUDIO_ERROR;  + p! G: a! k/ }6 j
  21.   }  ; w# O( [: E" i2 E8 S* Y( E) ^' p
  22.   else  + J" J0 g8 T8 V& @; N1 Y; @% `, I
  23.   {  
    1 y& N# Z9 `( v6 U2 b
  24.     if(Option == CODEC_PDWN_HW)  
    ) s) Q% a) p8 v3 Q6 a5 i
  25.     {  
    & N- ^- V2 r0 b( O
  26.       /* Wait at least 1ms */  
    ' _5 ~  j7 q0 p6 X/ F: E& x
  27.       HAL_Delay(1);  
    # ~; L. `9 {9 I+ a  t3 C
  28.   
    0 J* b/ Y; x, X$ n
  29.       /* Reset the pin */  5 _3 z  `! _: V3 h3 e
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  , ?. r2 u9 V9 U1 a
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  " w# C0 l# p% B6 ]. m5 F: A
  32.     }  / G0 O7 e, d5 t* t7 g  V
  33.   
    2 w1 w* c3 N) p# k/ L0 h
  34.     /* Return AUDIO_OK when all operations are correctly done */    X% ~" h# k  q1 t6 E! ]
  35.     return AUDIO_OK;  2 B' X# [5 ~7 j7 D
  36.   }  * s% Z+ g! O, {
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。
- }  E) U0 o( _# eOK,下一个:
. i9 x6 X2 u. x/ l! S
  1. [cpp] view plain copy* S- T4 r8 \7 |% h. D) }8 n( I2 s
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  
    ! _7 i  u0 j' y7 _; W! M- ?# a
  3. {  
    . ?( ^/ R! j4 m8 U! L& o/ k
  4.   /* USER CODE BEGIN 2 */  
    # R& z" _0 ]8 Y( T: R5 X% d! s$ k2 v
  5.   switch(cmd)  / r0 o3 \: B: ?7 t4 |, h
  6.   {  8 B! f5 e4 D3 q# p. W  }5 o
  7.     case AUDIO_CMD_START:    Z3 F, y6 }/ M3 {" f3 d4 W
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  
    3 Y1 y( }+ h  V
  9.     break;  
    1 O" r6 r4 s3 I4 c1 _
  10.   ( y* A8 r, a  t9 `  y+ ]7 H; y
  11.     case AUDIO_CMD_PLAY:  + a9 J+ b3 B6 C  [
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  $ Q2 K4 M4 c. I9 [+ v
  13.     break;  
    # R1 S% l, ?/ q2 |- w5 I
  14.   }  7 O# q9 t( J& E. ]9 e, I3 A9 X2 _
  15.   return (USBD_OK);  & o. `7 ^& R7 }" K6 y8 b/ r
  16.   /* USER CODE END 2 */  
    5 V+ j7 q, v' i8 K) z- t$ ~0 H
  17.     1 t( |$ L. ^8 E3 v+ U
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:2 c* A( X6 n$ X& s3 h" g4 j
  1. [cpp] view plain copy* u: X. T; L, p5 C
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  
    0 v5 u; v. c4 T5 {  q7 u
  3. {  
    # n% k+ ^: ]" Y5 {3 b
  4.   /* Call the audio Codec Play function */  " ]/ E, a) A0 m8 n8 _! \  u% N* y8 x
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  
    # _* e6 r+ |1 [& E6 k& ]
  6.   {  1 e' [4 S# r2 x
  7.     return AUDIO_ERROR;  
    % b3 I) {' b4 r
  8.   }  " f& u* y5 ?$ p
  9.   else  
    6 o4 d3 L+ M' F7 v! z3 l
  10.   {  
    * o# j: D( @! v, O
  11.     /* Update the Media layer and enable it for play */  
    8 T4 {9 N2 Y( B( c6 T
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  5 _/ l' \) e6 b" H9 B
  13.     return AUDIO_OK;  ) ~0 d- R- w# d. ^6 Q! @
  14.   }  
    5 P+ A7 A1 }+ v4 x% A1 }7 i
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。
7 x& H  m4 z- H" w% k) L2 x7 d8 H1 t
然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
2 }8 x1 n* O$ w4 Z+ \9 w4 g
  1. [cpp] view plain copy7 P6 x5 }" G6 _" f6 P9 J" w0 [3 r+ P
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  % M2 B0 `  v( z/ t4 ?, M$ E1 e
  3. {  
    9 @3 n7 ]1 g" D  |* R
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  
      y7 c2 y, A* |7 M0 o! N, k+ \
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
4 @+ \/ `# r0 m' u8 N2 v( Q2 d' g6 m+ E6 v: V3 k& q3 M
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:5 g7 D1 j$ C1 e9 V
  1. [cpp] view plain copy6 e) H7 W8 e% X/ L- A
  2. void TransferComplete_CallBack_FS(void)  8 H8 h; m3 n$ m, B3 K  q
  3. {  
    ) n: a+ t1 a8 j! U, F  q
  4.   /* USER CODE BEGIN 7 */   7 V6 N* ~! M" s! ^' j# l
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  3 K4 J1 |8 ?( d9 F+ w; H+ L2 L( X& Z4 K
  6.   /* USER CODE END 7 */    a+ b) T( B; S# z
  7. }  
      \/ G  Q3 {& \% v: r; O, `
  8. void HalfTransfer_CallBack_FS(void)  
    + ^% O/ [5 T* I
  9. {   
    9 u- F  ?1 B8 v/ H) x- e! i$ p
  10.   /* USER CODE BEGIN 8 */   
    6 A8 Q, |9 }; y: u, }' I) O( x
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  
    + s* O' o$ g7 d
  12.   /* USER CODE END 8 */  
    ! l* ]/ V. |3 C) w+ W: I, ~) e7 |
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。
6 v( m( x0 d6 h0 n1 @
) l. B7 H) q+ A5 T; T8 H7 [此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。
% L8 `1 S" y4 N3 ~0 w6 b% Q2 p! V% f% H7 \: b; S4 g
接下来看下一个usbd_audio_if接口函数对接:5 S  g0 a0 W( m( }0 w. S3 u5 `4 _
  1. [cpp] view plain copy% O1 y  u+ r6 V* b" b* \3 u: j
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  6 c2 t! n9 N6 T  Q* ~& E2 g3 W
  3. {  
    * u$ e8 C9 Y) }
  4.   /* USER CODE BEGIN 3 */   + {- V  J& S. R( b8 O& m0 p
  5.   BSP_AUDIO_OUT_SetVolume(vol);  
    . o1 w1 ]8 E7 d
  6.   return (USBD_OK);  
    0 G8 Q& d$ n' s: ?8 \1 f5 A+ R( U
  7.   /* USER CODE END 3 */  - _8 K: z2 d/ @2 G
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:
8 A  R9 y! K. W' Y8 [$ F5 g
  1. [cpp] view plain copy
    4 f6 A/ E% ?# g+ [& {& r
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  1 @8 }9 A( B' O+ Z0 t
  3. {  8 d# T, t6 o* v6 o
  4.   /* Call the codec volume control function with converted volume value */  
      y; D2 e, j8 H8 s( R. L- N+ u
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  8 [" Q/ e$ N4 n$ y$ v+ j, X" O
  6.   {  ) u) i* J0 a8 @# o6 [; ]$ h
  7.     return AUDIO_ERROR;  / M% z% }- u. h; ?  Q$ a, K
  8.   }  
    7 T2 Z0 a8 a' \! c
  9.   else  
    2 Q8 h) N% L; ^6 a' r
  10.   {  
    $ u1 x0 k# J/ [
  11.     /* Return AUDIO_OK when all operations are correctly done */  & x: |: |, I. t' N
  12.     return AUDIO_OK;  2 y0 V, d& b" _9 y3 d
  13.   }  % [$ I9 [0 w4 l. s
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。
+ E" q3 `: C" U! ?4 c* R3 n$ S3 x5 w& y
下一个:6 @0 E: K+ s4 r, T2 x
  1. [cpp] view plain copy
    5 T8 @- f9 n9 K
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  ! P! T0 p! ?) W$ X1 M
  3. {  " s* @( k; Y6 x; [# r$ k" B6 h
  4.   /* USER CODE BEGIN 4 */   & i5 D) Z# p$ R0 P0 @% }/ B3 z
  5.   BSP_AUDIO_OUT_SetMute(cmd);  $ o& i7 i2 P* b# q6 N" x
  6.   return (USBD_OK);  
    7 p) B; B3 ?6 ?5 f: s: s
  7.   /* USER CODE END 4 */  ! I3 |) E/ I: u) I/ p1 R
  8. }  
复制代码
静音控制,其实现为:4 D. A4 l, x0 s) \
  1. [cpp] view plain copy
    ! v+ w7 Y7 f) l3 ]5 p, N
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  ( ~7 _/ P( n; C; K9 G! W% v* i
  3. {  
    % r: t0 I! \% b% P3 `8 u: ?
  4.   /* Call the Codec Mute function */  
    . q3 _; [5 v2 d0 p# W0 J! F
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  % i1 K1 H- O- r) a
  6.   {  # {2 p' A1 }! Y. ]! m+ U
  7.     return AUDIO_ERROR;  
      t" \# ~' W6 T/ F. O, v) G
  8.   }  * o/ Q* H' X$ N9 v4 b: z) h
  9.   else  9 F  `! M# C- ^, E0 h* j0 h
  10.   {    B4 M' f: x. j$ @, O) ?
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    0 g4 @4 g6 ~  @* O' I! }1 F! s+ _! W& ^
  12.     return AUDIO_OK;  , ^7 m& b3 N3 O% b  i" z) ]
  13.   }  
    # {7 i% d( _! R; [
  14. }  
    . w+ W' M" p5 Q* C  V
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。
% Q/ t" D+ o( K4 |  b6 F5 v
" G! W; A6 y9 H1 n  }/ ROK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。, H; }: X0 N# j$ n7 o
3 t% d: {* z6 W) r% A* ^( k& F2 q
4    测试验证
) J% {5 Y8 O4 k3 k将代码编译后烧录进STM32F4DISCVOERY板进行验证。
7 M; u, Q( E/ {& h; Y 18.png 0 m! M. U* N, S  G6 B. G
最终验证是OK的,可以从耳机上听到PC端播放的音乐。
1 i, q; C# \3 q
3 v: e" c3 t( Y; Q5    结束语3 D! j/ I+ b" b8 }
在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。
) p; o/ j" |) ~  X% r9 A
5 L  d, ^, P# k6 fI2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。( Y+ a" V4 K1 s  Q9 V- ?' y" h% J

# O2 d* P/ a. d" ]3 X在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。
8 V, x1 K$ W0 F" P# C: P; d5 }* P/ u  E& x% c( a

" g8 h) N; \. h. d1 b- v7 J5 v! T
9 |# {( k" l% d* p) y) o; D
+ }( b, K( ~( {. t8 p, x5 q

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?# u$ e! G5 N. ^* q0 y
static uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,0 s6 m' B. M9 \4 R, r. x) Z! a  o4 J
                              uint8_t epnum)  W+ \. Y2 B0 E; k* J; m6 x
{
- u) Y2 }6 `; E, _4 l6 a  USBD_AUDIO_HandleTypeDef   *haudio;
* c; b8 z  m0 Q! o; ^- t4 B. r  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;
% a& ~; d- R9 `: ^/ r7 [% g+ V6 K* {
  if (epnum == AUDIO_OUT_EP)
; v' q+ j5 [: ?% W  {
9 K+ [' v1 j4 T6 F& h- \. a    /* Increment the Buffer pointer or roll it back when all buffers are full */
, s  f2 m& P; `& i1 {/ E1 P! Z
% ^7 N0 `- L  K& J% E( V    haudio->wr_ptr += AUDIO_OUT_PACKET;# M" B- ]1 G; m' Q6 e+ s2 y

8 @8 \: ]# m+ I9 F    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
0 ]4 K0 g7 b6 t& _" X1 @4 n* }    {
$ z# Z0 o7 ?, _$ H$ c* H      /* All buffers are full: roll back */& A- m& e( |/ v5 ?
      haudio->wr_ptr = 0U;8 {6 k2 ^, G# {3 A5 D7 x, P
    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
) `0 Y5 n( T2 r7 m* W& r  b                                                 AUDIO_TOTAL_BUF_SIZE / 2U,% Z0 Z0 r  r. x3 X, l
                                                 AUDIO_CMD_PLAY);* n# J1 }, j+ K. L8 q  ^3 f
#if 0( A& U8 U, C( j4 `. m
      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
% U7 x/ I1 K! i# y# D      {) _' `9 h) @0 `  g8 Q
        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],4 g- u7 M( g6 f) r
                                                             AUDIO_TOTAL_BUF_SIZE / 2U,
7 F/ n6 }  f- v* t! o$ ]& J0 I+ R                                                             AUDIO_CMD_START);
0 U' p) _3 ]. {. U          haudio->offset = AUDIO_OFFSET_NONE;
# L7 X- u6 i8 t5 o; m/ o9 {# s8 p5 ]1 ]      }7 w0 {5 M2 o# S) B0 H; L5 K& H
#endif& D5 V5 `& E* c, a8 U; e
    }9 e9 ~( ?1 l' z( Z0 I: |0 j) W! ]
3 ~9 H& N! L# D! I& |
    if(haudio->rd_enable == 0U)/ V3 O3 z/ u: t8 ]- K5 s" g
    {! z0 P' V2 a. y
      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))
- V$ h; B7 k) C" l7 v9 m4 }- E      {
& p- e, [2 G" }! J* x' c1 G        haudio->rd_enable = 1U;" A( `4 W% W" T+ J# @, N9 A& N
      }
& J: x5 \3 ?3 F4 I    }
+ x; B# a) ], K/ t6 w! z$ ~4 }* A+ J, m9 n
    /* Prepare Out endpoint to receive next audio packet */; U' K/ o3 R. O9 m/ |2 l, t2 [
    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],; \. W' k2 I" N2 @5 V" ^: K
                           AUDIO_OUT_PACKET);4 b6 C( @9 ~0 a! k0 [. W) U+ R
  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?
7 e/ Q8 j. w8 }% }& o6 E我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,' R4 b2 N( i& |
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。' C- f5 G: E% y, ^# Y% M! l5 L
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,
+ W: N# }: s: ~但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,9 w+ ^& @! h! O+ p) z9 `
而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20
3 _/ ]9 ~8 x+ @3 F; ?这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
3 u% `" }$ Q2 ~  s+ l" R4 A8 ]1 H& b- J5 d
该设备无法启动。 (代码 10)

- k- C9 j( I  y: m, q5 y/ o9 G出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
# S+ i# c! \1 K8 `* z' i- O! n非常好的帖子,谢谢分享!!!

1 f+ m1 c' i+ g- R" y+ z# l希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
5 K4 ]" T4 u5 M* O非常好的帖子,谢谢分享!!!
9 |( o( H5 X- f: j% a4 A+ r
希望能有帮助
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
' C& |8 h) h4 ~4 k; x$ o  U, S
非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
7 y/ w' G2 O& U/ g, V$ ]* G& y) ~0 F  ?

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:18
, m# N4 x5 d5 U: ?, x楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
- N: J$ ~- ?% Y9 E8 U6 ?
6 Q& S+ X% n9 H5 J" g ...
* ~3 P( c2 x( ^; r
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示2 {- s! L5 f& @1 y" J" D
! {, x8 I! v' b$ f: @9 ]; M6 D; H& x  W
该设备无法启动。 (代码 10)
' ~9 f. u% b, {" K2 k$ l- n3 t5 g1 T
2 Z. |0 X' R* F( o- P. L0 cI/O 请求已取消。
, Q' l" j0 A5 `; A" q
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20
' N- K3 i, Q9 i4 H" s这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
& l# t. F( G) @- C5 l& l& Z& J4 ]( E: ?% s/ u: v, M
该设备无法启动。 (代码 10)
+ G& m6 o# w6 J# T
经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页

所属标签

相似分享

官网相关资源

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