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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑
7 B$ G  d+ Y: w# h) `" s* f, C" l; k+ ^
1 前言
3 U! r/ ~) k5 R7 l& a$ X本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
( @$ X2 {2 N5 a( Z. P4 Y0 _; h& N- e2 ^* }- ?! `; t
2 设计构思# ?0 }0 {" |4 U+ t; S' \
所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。, G! m: X% H  J7 l. Q
' w+ j! C  V6 j" e6 Z* d
2.1 从原理框图开始
+ q1 N" J1 N% W& W& T- _8 M/ y 1.png
' t  Q% q& G6 q/ D* U如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。; S6 s$ o3 g# j# w

& P& e; Y# G: y4 d# P+ f& U2.2 硬件支撑
/ b8 P8 l% Z7 h7 k, ]这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。
  D7 m0 y. S: Y3 @* t* _, m' b& ~' U1 A! _' s
2.2.1 USB接口/ Z* v: T- D7 P' G
如下图为USB接口部分的电路:0 }( Q) l& f" M- I& v* @- p! G1 V/ c
2.png
, L9 v) X6 Y! L3 V+ o6 W( v这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。
# ~" J2 \6 a) v9 c" [& f# c: e3 e) i' K' a
2.2.2 Codec部分
) ~) V$ M7 f% Z$ k! o如下图所示:
  \$ v2 w) S6 M2 C2 R& F% d7 d 3.png ; p( v: C% h/ \. [9 T, Z
如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。' |4 M$ m: M% j& t+ ?

" e' ?" ]; z% U" v  O1 b& C2.3 软件设计, I1 S: w% l! p  j; u  h
为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。
) J0 @- k- \, T9 \' h 4.png
5 }+ X( Q) S% Y( v% l3 e, K' u7 Q2 p2 h0 U' o1 {
如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。. M! _/ ?5 U4 c+ {' x

* y& `6 q$ y' V9 I0 {, T* L各个模块的工作流程如下设计:/ N; ^& o: A& M* Q! S3 m
# N" q6 |  X0 K, H! r& `
初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
  ]" m& Z/ C& _) I: NUSB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
: E, c/ g2 I; X! L5 N+ Z7 D8 ~8 J# j' bUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。2 ?+ [, n; |( R1 E
从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。" H6 |* c. _* S7 M% M. ~" V# Z
; Z7 q4 ~$ `( m0 R; I
接下来,我们来看看软件层面上的实现。
7 G: w8 {4 P' i- m) R
" H9 d9 Z  t  }' u# b2 ]3 软件实现& e- L' S' c6 @; _
还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。
% ]$ ?6 D- m1 {8 u9 s# S0 z8 `. A  O& a/ p
3.1 创建CubeMx工程
% e- W( o3 a) s6 x5 n3 x, n由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。6 M" r# p: h  x9 f
# t9 `- \6 i, x* j
pinout:3 c* e) o! {# ^" _6 B3 z/ c
5 I* [0 X% `/ L. A: z# B$ c- R
外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:6 |4 C$ h) Y' S
5.png , f* C% [  C3 f, {( E! E
! r! a9 ?% H6 H1 z7 N  G, }
Clock configuration:9 T; b9 @4 S% n1 ^# t
6.png
6 ~+ Z) {! o6 q. ^2 o+ f时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。
- K& |& s! j; ^% g$ G5 x, R  U8 x
Configuration:
' X) A+ T& b) Q7 b
2 h+ p$ f6 ^3 c+ a$ v' h2 F& F  HAL层:+ g" L3 c4 y! o' v& Z5 f' `
Usb_FS:使用默认参数。
* a. r' K1 Q% `' ?3 O
! Y: w- o( g: n8 \. n' o/ aI2C:100K速率,7位地址宽度,使用默认参数。
4 \) O* Q* @5 r6 Z7 V8 g- j
: w, n3 k, m& ]3 ]+ i& s  wI2S:主发模式,标准16位宽,默认音频为48K,如下图:" p9 j) e4 n) e- Y
7.png ( j1 T, l0 }! @0 b
并为I2S发送添加DMA,半字位宽:+ K, x$ {2 m$ C% a
8.png 7 z% L" |% G0 w' W# b+ P1 n
- Q& a# B- M6 ~: J% L+ X
   MiddleWares:
) N9 Q( Z3 G! j5 M
. h5 @2 y2 d- g  O/ a# pUSB选择Audiodevice class,其配置参数如下:1 O/ U$ D- X- |
9.png
, E: x* `6 a  n0 _3 g$ W9 z* R( u8 ?( c1 R
这里都是默认参数。
$ \2 }4 h2 o, y" X" _; {: ] 10.png , {0 h3 @- |# Y7 P
在描述符参数内得为usb audio class修改两个参数:
2 z+ i9 |4 O" w; N5 G1 Y: z
/ Z0 \9 x* g7 E" H0 j; f5 b1 @PID得修改为0x5730(否则windows驱动会加载出错)# A) b" k) [$ [  Z$ V) [+ x! N
序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。0 L* H) Y& Y9 s
最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:% r0 W1 b( B: h
11.png
! d% i' L( g& h0 u$ D如此就可以生成工程了,我们生成IAR工程。& a1 o; S/ G  f0 \
/ k! l6 M& s( q0 r1 S
3.2 生成的IAR工程介绍
; v: a! N6 h. e4 { 12.png
9 D. ?' p+ R# s0 G9 ?如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。
+ r2 k. }1 }9 {% g# r1 e1 y0 m' M6 |* d3 Y2 S7 b
User目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。  T: _  z5 n# P
Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。( ?; I+ Q' p3 b, i/ {7 u
Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。2 d4 z4 i" x' i& p# Z

3 X+ K' C  j  L0 y, Y; Z3.3 开发
! i9 X6 [3 G% f8 Z" u- y2 R3.3.1 初次编译测试
+ L9 ?7 L& Y& e$ g首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
- u( L+ k/ H1 y- {( V) O: Y2 { 13.png
1 ~! q( L! R$ m* B这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
0 N# t7 C: h3 \ 14.png ( |: Q- X" M+ m% M& n! g& t
这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.) o# A7 w* l8 x7 U- h
/ @& \1 P9 ]1 a1 i: m  t/ [8 c$ z9 k
3.3.2 添加codec驱动和audio bsp模块5 P3 a0 [/ l5 c4 u& r/ X) {
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:' v# G: c# X# i/ r
15.png
! r8 G' H8 f3 H" `9 C8 ?. M5 ~其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。& J- b; I- J& W" Q. a/ w

! x8 |! c3 l; z3 n3.3.2.1 Codec与HAL的对接
$ ~8 I4 f/ P/ r% Q) F, \首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:5 r3 w& J3 @- r
  1. [cpp] view plain copy
    5 [& u, p: b5 x# [% ?  @) D5 }9 e
  2. AUDIO_IO_Init()  # G: [4 q' N& j' L* e' y$ G& q
  3. AUDIO_IO_DeInit()  - ?' }+ @8 r( G2 w& z
  4. AUDIO_IO_Write()  
    ) a. H$ D" o. a4 X
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:7 ]/ a% _9 n) H: H0 X
  1. [cpp] view plain copy
    0 j" N3 e. N4 a& u
  2. //---------------------for c43l22 port--------------------------//  % N' m( g. T2 h; x
  3. static void I2Cx_Error(uint8_t Addr)  
    ( @7 ]' P) k1 o2 j+ g6 O! M
  4. {  6 h& U6 ^; o6 e7 j
  5.   /* De-initialize the IOE comunication BUS */  
    . Z/ |$ r* M2 y& i
  6.   HAL_I2C_DeInit(&hi2c1);  
    # m4 c- A5 u) v1 ?2 l7 t
  7.   
    ; P' k2 N+ z" s
  8.   /* Re-Initiaize the IOE comunication BUS */  : E/ @; {; R; H& l) M% n; R: ?3 H
  9.   //I2Cx_Init();  7 F8 }7 E# y# D1 U' ]5 {- i, R
  10.   //MX_I2C1_Init();  
    : R# @# ]2 Y) C9 i, w
  11. }  / P' S2 q& f' e. K/ a# O* p' x
  12. static void CODEC_Reset(void)  
    " G& w* b- k# z
  13. {  5 w) \4 y7 K6 ~
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
      R5 r4 I3 n8 c2 g- R4 L, H4 ?
  15.     HAL_Delay(5);  
    - O$ J3 u. y" [# N5 L% A
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  7 H: \7 S; s+ R* s% Y; ?
  17.     HAL_Delay(5);  2 X& X+ k* P1 [5 q3 G1 A
  18. }  & V6 w# n1 C) w- G
  19. void AUDIO_IO_Init(void)  0 f1 g2 B) [' v' Z5 f, |
  20. {  
    ; ]- L, G! F9 y  ]) M3 E/ I
  21.   //I2Cx_Init();    u3 W, }3 u; \9 i1 N+ s# y5 r
  22. }  
    - h7 E& U' i+ K5 K3 _" U
  23. void AUDIO_IO_DeInit(void)  $ J7 ?5 n9 I; D
  24. {    F% w$ C5 a9 e9 H5 h
  25.   
    0 y' V/ a% q; e
  26. }  
    5 Y& J; M, D9 ]+ S
  27. /** . f" b3 {$ K! P6 P) D; X
  28.   * @brief  Writes a single data.
    - S" V& ]% q5 s$ f! g
  29.   * @param  Addr: I2C address
    9 J" j  L, @! f% I4 M: \
  30.   * @param  Reg: Reg address
    & [" X% ]" ^8 J& D
  31.   * @param  Value: Data to be written
    . o8 _2 p8 z8 ~: R! W
  32.   */  4 Z9 [8 T1 i# D
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  % e6 M: u1 T  y! |2 U( j
  34. {  ) I  m3 o; [2 A8 a
  35.   HAL_StatusTypeDef status = HAL_OK;  
    8 k8 m9 H4 C+ p$ A
  36.   
    8 F; Q/ L  c' a, o8 k3 q: `" z; r( q; I
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  1 O* j9 o; \2 y$ b, N
  38.   
    7 R& a5 y) B2 v+ H
  39.   /* Check the communication status */  
    5 k" z# D. y/ M
  40.   if(status != HAL_OK)  6 x7 Q: ]8 M; A9 a+ m
  41.   {  6 g" |$ S2 e) B! k
  42.     /* I2C error occured */  
    $ r8 F$ P, E, |! j, I  E. P
  43.     I2Cx_Error(Addr);  
    + r: I: b/ w8 x/ V2 w1 C
  44.   }  # O# V, h  L$ m0 d0 k# |# }! Q
  45. }  : _* _) }/ \' u5 H
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  * d: _7 w2 z2 G
  47. {  2 p  z4 B8 U: T3 f
  48.   I2Cx_Write(Addr, Reg, Value);  
    . `' l3 K4 S- K* I- v
  49. }  
    8 }. u) t! j+ [
  50.   
    : O; G/ b3 u' d- T& F- n7 I- _
  51. /**
    # G. @* s: A) h) K# O8 E
  52.   * @brief  Reads a single data.
    2 e. i2 q" d. I* c3 y8 c! i4 d
  53.   * @param  Addr: I2C address ! m1 }2 O; w& d3 }, N" d6 F, W
  54.   * @param  Reg: Reg address
    % ]. o+ o5 W  X0 h3 Q  U  G$ ?) b
  55.   * @retval Data to be read . @* H; ?5 q- F. m: l: V
  56.   */  
    7 F) U, J1 y% p$ B' u) {
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  
    ( R& n! J- e% c3 w
  58. {  4 {+ N$ p0 X% X$ [, O7 ~3 w
  59.   HAL_StatusTypeDef status = HAL_OK;  . [( @7 \: i& n" s  ]2 A( u
  60.   uint8_t Value = 0;  , }! W/ P0 ~9 M* U. g* z3 e
  61.   ' ?  j2 X+ m5 Z) D/ N
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    + x& s9 ~" V& j- O6 a
  63.   6 I  o* C: ]2 t6 r9 j& l/ D- B
  64.   /* Check the communication status */  1 ]- h7 m: M" K/ Q4 V
  65.   if(status != HAL_OK)  
    $ u/ I2 S0 W3 Z6 G! n
  66.   {  
    ) t2 x$ X5 F' B: R0 u0 M
  67.     /* Execute user timeout callback */  ! H1 p+ @9 S3 \4 ?- S, A
  68.     I2Cx_Error(Addr);  3 Q4 j( o; s' k& }4 I) N  E# `$ @
  69.   }  2 r* U4 v1 g, B- w& h% ]
  70.   * [# a+ H1 H/ C3 `3 y. i1 ?
  71.   return Value;    }. W2 I9 S% W; s  ?
  72. }  
    : E% c+ D3 _. X; o3 F5 j6 k
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    * h- o: {) u% w0 ?7 }* ?7 v
  74. {  
    , m7 \! ?* t( z' r1 o8 G3 a
  75.   return I2Cx_Read(Addr, Reg);  5 k- B6 K4 s% S
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。
7 Y% X: U& u3 r/ h, \5 N. v& L% k( ^7 @0 c7 e1 S
就这样,就完成了Codec驱动与与HAL的对接。" y! K! u9 y8 ^" F. {7 @

4 m4 c8 m7 z# c( r* l8 `  o% V5 t3.3.2.2 usb audiointerface与codec的对接  w* j5 B& i+ }9 l: {2 ^
我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。
) s1 [0 |! \1 g% a$ {) {4 s& V0 S' L1 x
按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:
. T5 \: J0 i7 v( w+ ?" L
  1. [cpp] view plain copy
    : ?1 P$ p: }3 @) P$ F
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
    / L2 r1 @+ L8 s/ _; Z, N
  3. {   
    % U  }$ c4 T# |# n: o9 Y
  4.   /* USER CODE BEGIN 0 */  
    9 ]' q' e" }1 M5 S* g. N5 u4 V
  5.   return (USBD_OK);  ( R: D# Y, {! P$ u3 X4 s
  6.   /* USER CODE END 0 */  7 `; b5 q, b, C3 `& G
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:" I" k& D& x, v) z  W1 P
  1. [cpp] view plain copy" D+ f- F6 u/ d. H/ B* O
  2. static void I2Sx_Init(uint32_t AudioFreq)  * L& b1 g6 c; B3 L0 m' Z
  3. {  
    . }+ A3 n! D) y" E: j/ V
  4.   /* Initialize the haudio_i2s Instance parameter */  
    6 ^3 K( L+ q7 H2 p
  5.   hi2s3.Instance = SPI3;  
    " \, [- q* h2 r$ q# w0 _: \
  6.   " B& P3 t6 x' [% g1 J0 V8 U1 D1 ^
  7. /* Disable I2S block */  / y8 V' M0 k, @' q* ?% h( Q
  8.   __HAL_I2S_DISABLE(&hi2s3);  
    ! a' ]8 K+ e: M/ B, ^$ a! M7 D5 V# e
  9.   
    . v3 S! ^3 Z7 j
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  
    9 s0 _% ~" R# x+ R8 ^! J8 T
  11.   hi2s3.Init.Standard = I2S_STANDARD;  ; [( W! j0 r7 Q* `  n5 l& x
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    4 e% ^5 z1 L) B& P' a% U( i+ K
  13.   hi2s3.Init.AudioFreq = AudioFreq;  + f0 i- c- W1 t: G1 Y. c) T2 a( w) h
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  . u, N4 V' J8 d3 o
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  
    " r' l3 P& b3 d. c  Y# X
  16.   9 f& l4 x( Q" ^: h) ^3 Q. l3 Z
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  " S8 G$ M6 b( G" \0 n1 {
  18.   {  5 L! _3 r( @5 Q" H& \/ \
  19.     HAL_I2S_MspInit(&hi2s3);  : G$ T0 u& e( P6 G
  20.   }  . N' A5 z+ |! ?8 M3 h, x
  21.   /* Init the I2S */  
    # f# U1 S' m9 ], Y: A
  22.   HAL_I2S_Init(&hi2s3);  " w1 W0 r. X, e2 q
  23. }  ! @: N! J% p5 H, G$ B
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  
    ( D# h& \8 ~( A  v: l
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  ) y7 `1 L* O. H5 y* c
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  
    ! O4 Z7 s5 P  v& m! [
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  
    & V* b0 E  Z; K0 ]7 T, k% F
  28. {  . o9 d' p. l! y) H5 e9 y
  29.     uint32_t deviceid = 0x00;  ' X9 l+ i7 w; v' x9 n2 ~+ S6 F, B
  30.     uint8_t ret = AUDIO_ERROR;  8 L6 {" j6 `7 b7 ~! B
  31.     uint8_t index = 0, freqindex = 0xFF;  
    ! o0 k* c! S; h/ J
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  
    + S( N1 Q. e( X/ M6 [& a; l% {
  33.   ( V9 m- n( S! w0 S8 M7 m
  34.     //get the according P,N value and set into config,this is for audio clock provide  0 J: V7 r. j3 t1 n# g4 [  g
  35.     for(index = 0; index < 8; index++)  
    1 C% O2 _! G" x7 l& o
  36.     {  
    $ w) |8 `  Z! x! q( d7 ^
  37.         if(I2SFreq[index] == AudioFreq)  " g* Z% B8 a- ?+ o
  38.         {  
    : P3 @& F. s- v
  39.             freqindex = index;  2 r1 v$ I7 |) {9 I
  40.         }  ( d+ H' l) C: q5 G9 y9 r
  41.     }  
    % {+ l% A; [% s8 `$ [
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  + k0 ]  F( Y! H- c
  43.     if(freqindex != 0xFF)  
    6 p+ V) C9 I* `/ C7 p- n; \3 M( p
  44.     {  
    ( E( I% f7 X' k3 |- z
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    3 x& E3 L2 a/ B+ \, a
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  ; \: S0 }9 b$ {6 f9 w9 g
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  7 k! m' ^1 ~- p
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    ) g, z& `, Z5 P. b  s
  49.     }  7 e* c) g3 A. Z6 J; D  G# f8 d' d
  50.     else  
    ; y2 m4 e1 ~2 o
  51.     {  . E3 D0 K& @' r7 ?9 k+ N) Y
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  . f7 v" a# a/ [% y' ^. Q
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  6 N4 A9 B2 [+ d4 L% \; S7 k
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  * f+ g1 t5 c, S  y. }6 e
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  ) O$ n. ]  F" S* Z) y
  56.     }  0 b3 I- t+ F0 s+ u, F
  57.   
    " Z: L6 `5 f( R" t
  58.     //reset the Codec register  # a9 s4 t& ]1 g9 b7 H
  59.     CODEC_Reset();  1 c8 ], c/ {" [- l/ g
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  
    5 v% T( g! t" u
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  9 ~3 J) ^$ ~6 c8 g: z
  62.       {  
    2 k' {* ?- T0 a. p: f: ]
  63.         /* Initialize the audio driver structure */  : a$ d) y/ h: K  F+ c2 C. _
  64.         audio_drv = &cs43l22_drv;  4 m: n" _8 q. a; C; ]4 s
  65.         ret = AUDIO_OK;  1 G, s2 q9 R2 l) Y4 V5 N9 N! e
  66.       }  3 @8 a6 {/ i  L6 @4 p. ]
  67.       else  ' p& X, e1 C* j; g/ L% U( @+ j
  68.       {  - N3 J! }3 y% h# w
  69.         ret = AUDIO_ERROR;  
    * T) }4 b" d- Q4 `; j9 G
  70.       }  
    & T2 O# K& n! S/ j+ X
  71.   
    % O/ X; c4 T* @( u5 S
  72.      if(ret == AUDIO_OK)  ! J  u+ Y. L0 I- k$ d. j
  73.       {  
    0 a' S6 O+ J% \7 s; s
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  
    5 t- m5 Y3 j3 V
  75.         /* I2S data transfer preparation:
    1 g( W! s' N( x- z7 x/ A
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  
    / w; i5 K! T7 {1 L  K# I3 z
  77.         /* Configure the I2S peripheral */  9 R) X/ l$ w: F" V- e- i! ?- B
  78.         I2Sx_Init(AudioFreq);  , i* q* I0 v1 }4 d
  79.       }  ; M( c( v5 {  `
  80.     return AUDIO_OK;  9 v6 J% M. w! D( k# m
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。
; t3 V6 k' F( P# G7 K7 o/ D. o0 T! I/ q7 o* n  z' d
这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:* N3 C4 {" J0 Z% g2 `! E6 t9 E
16.png
+ e! t  P7 b& A8 P4 \. O6 P" g# \  a1 k1 l- r* s" f3 V/ x: B# m' T
在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
7 `( b5 `8 S# X9 h% }- r! a5 S 17.png
" j1 J6 Y/ U5 ]+ c也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。
" M; x" a& K0 E8 L# h& u& A0 j6 e) \* U- L+ U
PLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。
! k- e: O2 l8 s, C+ H8 d3 C+ x0 }, `9 H
搞懂了这些之后,我们马上将其代码进行对接:5 }% A* n2 S: b& C( C# l
  1. [cpp] view plain copy/ Y8 R  i, m5 W. {6 G9 l) t4 @
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  ! z2 `3 Y/ l* u; ]$ y
  3. {   
    , l, u2 R# Y$ R6 C
  4.   /* USER CODE BEGIN 0 */  
    $ _6 C2 M) Q5 U( _& h6 H
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  * B: G+ n6 o# a2 T, s
  6.   return (USBD_OK);  ( ^4 B* {6 ?( v; b2 V# q
  7.   /* USER CODE END 0 */  
    / z3 L1 D% s9 k/ T: ]4 X
  8. }  
复制代码
接下来下一个需要对接的接口:% {' G( ]2 _+ Q
  1. [cpp] view plain copy2 `, V  }: `1 z: z
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  
    + l2 A* {, D1 x- [$ f1 Q/ D8 G+ `3 Y# o2 w
  3. {  $ p, j: M3 D! ]8 c: M
  4.   /* USER CODE BEGIN 1 */   
    / a% m" b8 _' R+ d" _! b/ [
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  5 o) l3 Q0 D/ {$ A: E
  6.   return (USBD_OK);  
    ' Z" D) g' U) {' E$ e7 @. E
  7.   /* USER CODE END 1 */  ' q3 K1 D9 T$ G, o% d
  8. }  
    # r6 Y3 r( }# F
  9. 很明显,这个个反初始化的接口,它的具体实现如下:
    6 c' x1 d  X* z

  10. - M# d3 Y$ O0 I% _3 R- P) W# b
  11. [cpp] view plain copy& u- E, K, A2 r* f$ Y8 m
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  
    : l( V! X5 G( g  E; a5 u
  13. {  , D) D( j  ~, a  t) v
  14.   /* Call the Media layer stop function */  
    ) e  v- g3 M; T2 _1 C
  15.   HAL_I2S_DMAStop(&hi2s3);  
    ) h2 U0 I# v  \& t8 Z8 f! D/ O
  16.   2 D1 t6 L1 l+ J- |0 U! ^: o* P
  17.   /* Call Audio Codec Stop function */  * P8 S3 w8 J2 c6 s) b% o6 D6 ~0 G3 P
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  
    0 Y+ p3 K' k3 S4 Y' Y: b
  19.   {  / w( d# z1 {+ }% m0 u$ y' U' m
  20.     return AUDIO_ERROR;  
    & n  s7 h2 K1 P+ S. S- H
  21.   }  ; s7 c5 [- @6 v8 C, T& j& i0 T# C
  22.   else  
      k  }& i7 N( @# h( }+ c
  23.   {    R  _2 H$ d% Y7 D
  24.     if(Option == CODEC_PDWN_HW)  
    0 i+ s+ a# ?3 u5 R. o7 @( h
  25.     {  % ~' p% v) x# C' t/ Z$ v' [2 N
  26.       /* Wait at least 1ms */  
      j. Z7 p* ]; J. `" C
  27.       HAL_Delay(1);  : \: j2 L; \8 R5 T2 m2 T
  28.   
    1 G/ ~% E5 `- p6 m# ^7 M) G
  29.       /* Reset the pin */  
    4 P+ C" D- m* i( s. Q. x
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  
    ; G) t' \/ R2 `% q! t
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    : o. l% R6 \. I8 ^3 ^7 n
  32.     }  
    ' V% W( N1 R0 t; A( u7 t
  33.   
    + ^2 }4 y( x$ {2 B' K' w: {
  34.     /* Return AUDIO_OK when all operations are correctly done */  
    3 [& g* P2 ^, O5 O- F4 V
  35.     return AUDIO_OK;  $ d( L$ m4 w3 {9 m9 }! N" ]
  36.   }  
    ! `+ j0 ^5 R! b
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。
" r$ y: \5 _) B# w$ S! DOK,下一个:
2 O$ q* E' y- {
  1. [cpp] view plain copy
    . `' V3 [6 n& a3 [
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  
    5 f' u2 j5 f. p" t3 ?, f& [
  3. {  ) k) Z. ?# D) p' D; c* E6 }$ Z
  4.   /* USER CODE BEGIN 2 */  2 m* H! `! E- K
  5.   switch(cmd)  ) S+ ^9 z7 v/ f
  6.   {  . C- ^6 s3 O8 y. S
  7.     case AUDIO_CMD_START:  
    * M7 j( \* X6 x+ ^
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  & R- |$ l/ m% G  T. C
  9.     break;  $ v* Q+ r* f# n
  10.   5 Y7 y: C2 i- W% v/ w/ w4 p4 X. j
  11.     case AUDIO_CMD_PLAY:  
    7 e( K  L" P& K- \7 p
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  . ], {' o/ {6 q( a5 [2 V
  13.     break;  
    0 o  v9 ^2 j; B
  14.   }  
    : t% x& d* P) r; ^; A6 i
  15.   return (USBD_OK);  4 B  U% M8 j. i1 f6 O* A
  16.   /* USER CODE END 2 */  4 m8 o0 X7 }% I5 V( ^+ q
  17.     7 S  r; I- Z2 U4 j2 y& k
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:4 i/ B/ M2 L5 ]0 M6 w
  1. [cpp] view plain copy. e6 e7 A5 d1 r5 h
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  
    0 `4 b; m2 W4 q1 W$ R# J5 a, U
  3. {  
    ! a3 d  j  r; c2 S& M+ P- Y' `, y
  4.   /* Call the audio Codec Play function */  7 F2 W9 h# l2 l4 o5 a  f3 h5 I" b
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  
    7 C9 \. m, i: n+ U1 l# X$ s
  6.   {  
    2 q: Z7 p+ Q5 y0 g# g
  7.     return AUDIO_ERROR;  ( e' f0 [8 l" _. @, m
  8.   }  
    5 ~! j. W) `6 P: C: J1 L: f
  9.   else  
    : Z# E, }0 d: w3 B
  10.   {  
    . f2 D2 Y9 _+ z
  11.     /* Update the Media layer and enable it for play */  7 A; Q) U8 E) E% `$ ?- @; ]
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  : P$ L3 a3 d9 @: O) O% s  Y$ E
  13.     return AUDIO_OK;  
    . v4 I0 d- ^8 {/ @7 Z
  14.   }  
    7 m8 D) a* I; ?6 R
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。  l5 c$ h% C5 M

# R+ t# G: j; {* r) e- c) s然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
  a5 l) B6 e- |, O7 Y2 \$ L; |$ P
  1. [cpp] view plain copy
      n6 n1 ?7 M+ h7 f
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  
    ! d7 P5 e8 Y% I. u+ s
  3. {  
    ; S9 z, C8 U9 c8 p9 C3 `4 _
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  + e9 G# q( X0 g" y8 P! A! l' G4 W
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
  V) Z6 A8 J; J8 o6 S3 O6 b. g1 B6 E6 Q2 i4 u! f; O& F- O4 F. Z
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:; O. @  x/ ~0 N6 @& A: Z3 x
  1. [cpp] view plain copy
    5 g5 l9 E' w# x
  2. void TransferComplete_CallBack_FS(void)  7 a5 f% e* S) u% _; l% [
  3. {    z1 g. e% C; b- F7 o9 T  k4 H& P
  4.   /* USER CODE BEGIN 7 */   
    ! m5 v6 n- n6 ]! I  }
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  / O- y3 ?! l5 q% x2 D
  6.   /* USER CODE END 7 */  
    * C6 O, G) `" `. z
  7. }  6 O. Q$ U3 x, N$ M
  8. void HalfTransfer_CallBack_FS(void)  
    # U  \- A8 z6 o
  9. {   1 {8 w) z2 ~# v+ Q# y
  10.   /* USER CODE BEGIN 8 */   " c: L1 k' Y, m. Q, l' D* `
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  
      {$ J, C* `0 }' W
  12.   /* USER CODE END 8 */  
    ( ]9 |# S8 H4 r" d# R4 Q% S
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。
; U7 l( ~' ~% H' x7 |' j! G# `) y; i) Z1 f
此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。6 _- p$ C; h3 y( x& M5 D

) Q  N, [! t0 B2 `接下来看下一个usbd_audio_if接口函数对接:
. c4 E% ^! I5 e- |
  1. [cpp] view plain copy4 x$ v  H8 O  R4 B0 t2 P# q
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  
    & B; H) O5 H& Z( P  ?$ T
  3. {  ; C9 y% M$ s, y) R* L
  4.   /* USER CODE BEGIN 3 */   
    ' c2 I) B; O, `1 K" X
  5.   BSP_AUDIO_OUT_SetVolume(vol);  
    7 F* E# {; x9 W
  6.   return (USBD_OK);  
    * }# Q* [$ U( p9 n* `
  7.   /* USER CODE END 3 */  7 I1 r+ M7 P/ D* \7 l
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:
7 S# R# O' `& l% v+ F7 U- \: u
  1. [cpp] view plain copy8 }! f8 }% N" E) x; P
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  : r! d4 L+ m6 P0 S  g! @  T
  3. {  
    $ I! G$ }8 N7 W! L9 _
  4.   /* Call the codec volume control function with converted volume value */  1 r5 g! ^$ R, `. d/ P. r
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  
    / j8 y' E5 N% [, S, N+ A
  6.   {  1 q4 i2 C) F  @6 c4 {
  7.     return AUDIO_ERROR;  
    ) o% r) P! Y2 O4 r  ]; C
  8.   }    z3 ^4 m* ^8 b
  9.   else  : d1 K( m. w4 j  H2 w
  10.   {  
    1 X! l) i( h1 v" L7 e' f
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    & d  T! R3 @( n# E; A" V
  12.     return AUDIO_OK;  
    , q4 ], V6 d) x5 F2 m4 @
  13.   }  1 l7 |, S  W; ?
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。7 k" d. W( w0 Q' b
  v0 w4 O" j/ e& U4 m5 x
下一个:
9 L4 I) p# w* U2 Q
  1. [cpp] view plain copy* b0 p, p4 G$ I
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  
    ) Z" ?2 D, q+ r1 H- ^$ c! u& E) v4 `
  3. {  1 @' g' f! C, w( n1 z
  4.   /* USER CODE BEGIN 4 */   
    8 f. ^: j9 ]) H$ z" x+ E
  5.   BSP_AUDIO_OUT_SetMute(cmd);  
    $ l9 }+ ^) ^  W! |  h+ _3 V# M/ l; E  q0 R
  6.   return (USBD_OK);  
    ; T$ }; x4 m) K1 j) F2 m$ r. i- e
  7.   /* USER CODE END 4 */  
    - ?0 \: q3 S( q8 U" Q
  8. }  
复制代码
静音控制,其实现为:
% L( _/ m# b- `( Y
  1. [cpp] view plain copy' S" `' o7 t1 V/ }4 A2 w/ F
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  # N4 l+ K! _! S' Z) ^4 a
  3. {  % \% Y) t$ @. e- D
  4.   /* Call the Codec Mute function */  2 \/ k% X! J0 j. W
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  ( g5 k5 I" u7 j; K$ g+ Q/ T
  6.   {  
    1 x7 C3 @9 E1 a6 b1 i8 T: @7 E; Q
  7.     return AUDIO_ERROR;  
    , m' q& X) i5 F
  8.   }  
    . @5 {, o9 y& S3 }
  9.   else  + _. w5 A% h( E& ?+ X
  10.   {  
    , ~/ w/ A* Z" G9 C$ G4 V
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    ! M$ i; \, X+ C  D
  12.     return AUDIO_OK;  ( g! t7 m. T0 |1 |+ ?) A; V
  13.   }  
    1 `- K& f# j+ P) Y
  14. }  7 J/ q) b% W' ~( q
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。
$ [# Z8 A/ E( e( K# i) ]' c2 [0 ?+ ~
OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。
$ k' h4 D6 ?0 G/ S
' A, k& H5 r( b+ d# r: v4    测试验证
. j1 ^2 w% n3 Z3 X! f$ p4 U将代码编译后烧录进STM32F4DISCVOERY板进行验证。. f0 B- ~) B0 ?; G0 o8 Z
18.png
% ]. n: N7 H  K" a: o, m0 W1 B最终验证是OK的,可以从耳机上听到PC端播放的音乐。
' B" V1 l6 ?3 m5 ]
' L7 A' m5 y& A+ D, ^6 s# n" A( D5    结束语9 o2 x/ K4 F+ K" h
在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。2 D; D+ x, C! }" |; B& k( C! V3 v
% t4 e3 w% P1 R3 x4 i
I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。
$ [+ H5 b/ H7 ?  k& Z, N; ^$ G# N9 G! f( @# f+ ]! C  q
在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。% L+ u9 m0 M3 t* H
' C' N) @! N6 d9 H; h. ?
; |% W3 b) ~& P- T: V; E7 ]

6 k9 l; l7 H& j( E3 {& Z0 R
" Z. }( F# z8 w9 ~9 `# k

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?" r* ?. g; x/ F! u
static uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,
) S8 D! x% ~7 C/ H3 D( y. j                              uint8_t epnum)  h# R& V8 n! ~, f
{# c% k" X) x  C* B; }- I' }# }
  USBD_AUDIO_HandleTypeDef   *haudio;
: m* s" S; Y# P% h9 N% Y  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;# d" W1 j, O0 |5 r
) T9 i" T0 |- e+ I+ h
  if (epnum == AUDIO_OUT_EP)2 R6 y" U: T  Z: r. M" R' k
  {
1 \' j1 u. c1 M) G    /* Increment the Buffer pointer or roll it back when all buffers are full */
6 m$ C6 Q/ [0 d) q2 W: _2 V+ h% d2 R$ S+ T8 T  X
    haudio->wr_ptr += AUDIO_OUT_PACKET;
+ W/ t) Z0 J1 Z! |
! I  L" A6 y# Q! ~5 ^- Z1 M3 V0 `    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)
, u+ S0 J/ _4 o% h$ Q    {
6 y) y' y, t) \      /* All buffers are full: roll back */
5 s0 M5 H+ r" L* S8 y8 N      haudio->wr_ptr = 0U;1 Q; _% `2 b! ?) u
    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
& {  ?3 p0 e/ }9 m; W4 Y( W3 _0 u. p7 V                                                 AUDIO_TOTAL_BUF_SIZE / 2U,, S: j) D/ `  F/ X. W& {
                                                 AUDIO_CMD_PLAY);2 R2 R. h7 g% N* Z6 T  C
#if 0
0 J7 p+ H( H# p5 L! G' a      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)
1 H) R$ N- }3 V" s7 ]      {* }) D# G: b  E: x$ @; j0 Y$ ~. \/ F
        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],8 d# z. p! \5 h+ d; i% W
                                                             AUDIO_TOTAL_BUF_SIZE / 2U,& d( l4 g: i+ N& V
                                                             AUDIO_CMD_START);
  t2 Z2 x* T4 E$ h          haudio->offset = AUDIO_OFFSET_NONE;" j  V4 ]& K. X7 }8 o) h# v
      }. ]) Q- t) S' R) t1 D* y. q
#endif1 [' p$ s1 }3 X4 f' L! ~
    }
# y" @( F2 n! Z& e  L5 u( s4 w  m6 b6 E' \3 j" ?
    if(haudio->rd_enable == 0U)1 s5 }7 S' k& g
    {$ V' M7 w2 B, e* g
      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U))0 c, Q4 a6 }& ~9 |  X& }  h8 {6 C
      {4 s6 a0 F& ~% L6 W
        haudio->rd_enable = 1U;
) X' H8 b& [& b; G. E4 i6 t      }
. k# W/ P7 B- X0 E- Q. Q    }
7 i9 v. \7 _. L( S5 T% w% P: M; K, Y; ~5 j& Q: I
    /* Prepare Out endpoint to receive next audio packet */' W3 {  _/ S- q) t
    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],
9 E: b3 p: M; a* K5 p: l6 p8 U" O                           AUDIO_OUT_PACKET);  |* q0 v. o' z) P, G
  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?# [: k* W' C! K( R6 a, z0 a
我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,
4 n% e' v* }0 b* A我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。* U; M( Q5 U; Z: F! ~
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,
% I# K+ L% G8 q$ X. s, k但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,7 k0 v% J% g/ D- G
而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:20/ Q7 ]/ W3 @) ~4 Y) K
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示5 ?& T# ~7 T! }, R! J; ~7 u

( F2 a. M) i( ^, n5 A" [& J8 U该设备无法启动。 (代码 10)

3 z1 F& A1 N! Y6 J6 N' o3 M出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
8 A! k( [# n- L4 X+ V非常好的帖子,谢谢分享!!!
/ {; R! N& m+ L6 ?- r- ?, u4 \8 E6 F
希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45# M" i! s' Y! f; E, ~- U1 Q6 b
非常好的帖子,谢谢分享!!!
8 L6 O6 ?' s0 P1 g" j
希望能有帮助
listenmaxwell 回答时间:2018-9-26 23:00:58
非常好的帖子,楼主可以发一份工程的代码吗?非常感谢,18056453597@qq.com
wofei1314 回答时间:2018-9-27 09:24:26
666,好贴,顶起来~
Kevin_G 回答时间:2019-7-20 17:40:01
收藏
ccg12138 回答时间:2019-7-21 15:19:43
5 f# B+ K  f- W+ N. D" g
非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!$ U+ D+ Z7 S# m4 z) ^3 B% ~
8 \9 r8 P. d7 D* F0 `0 D; X

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:184 j/ H$ F7 R2 H: j( t; z
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!- w6 M' Z8 D( w; i$ [$ s
0 ]1 }8 e' M6 f/ o
...

- L- d+ `: X! O这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
5 s  Q8 ]8 \* X3 n  b- M( q, f" K6 |2 s/ R
该设备无法启动。 (代码 10)
8 R8 Y# K4 U5 d* _# R( |  y) B$ f5 t1 Q4 {
I/O 请求已取消。
! w2 A+ d, A5 u. D8 ~0 [
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20$ T" |- C8 _/ \6 K9 G
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
, C( _4 T# v- l/ j: I
9 |8 t: u5 r9 ^! Q; l该设备无法启动。 (代码 10)

8 P# O/ x# @6 T# l+ Q经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!
12下一页

所属标签

相似技术帖

官网相关资源

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