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

USB Audio设计与实现  

[复制链接]
aimejia 发布时间:2018-5-23 14:28
本帖最后由 aimejia 于 2018-5-23 14:32 编辑 / J5 \$ a- Q. U
% {- k5 R: J& y7 A$ k7 T
1 前言: F  h# U. S1 l, r
本文将基于STM32F4 Discovery板,从零开始设计并实现一个USB Audio的例子。
( r! Z  C  a7 [# f: w
, L3 N  H5 [( f% r2 设计构思
. T' ]5 b5 W  M所谓的USB AUDIO就是制作一个盒子,这个盒子可以通过USB连接到PC,PC端将其识别为Audio设备,然后在PC端播放音乐的时候,声音可以通过盒子播放出来。
3 v( A, {" Z8 G# b* T
1 Q7 ^8 b8 R2 q/ W3 h7 j2 _$ n  o+ F2.1 从原理框图开始$ ]$ k6 D1 z3 T1 d# K, ?
1.png 5 C, n, C# i  t4 ]* |7 i' b
如上图所示,我们大概构思一下,为了实现USB AUDIO功能,我们使用一个MCU的USB外设连接PC端,整个流程是这样: PC端播放音乐时,代表音乐的数据流从PC端通过USB传输到MCU端,MCU端然后将其转发给一个外部Codec,最后通过Codec上连接的扬声器或耳机播放音乐。4 r; r+ V" V, s/ K
6 {7 v5 o7 q6 h$ _) g
2.2 硬件支撑$ k" G" t2 G/ j
这里选择ST官方的STM32F4-DISCOVERY板来实现,之所以选择这块板子,就是因为其上有USB接口和Codec,正好符合我们设计的要求。
& m" i$ g9 Q0 k& f( k( W6 S2 q  q
2.2.1 USB接口4 \( I) G8 T- r" s0 Y
如下图为USB接口部分的电路:& a1 ?& Y/ S- n* t+ j: o$ }
2.png ) G+ ~- c, C: O4 }. {# z, X8 P
这个一个将USB作为OTG的电路设计,在本设计中,我们只是将USB作为device来使用,因此,上图我们关注下面部分就可以了。在本设计中,我们使用到全速USB,从上图可以看出D+与D-引脚分别为PA12,PA11。
* j6 v3 v. L) ?, E2 N; Y+ n
8 R  X# O5 ]7 v9 c( p( @) Q) D- v8 n2.2.2 Codec部分$ `' x1 f2 ]! O1 y+ ?: ?) A
如下图所示:! d. `: k. y) d4 t; R
3.png
* A7 s& {1 w# o如上图所示,这里的Codec为具体型号为CS43L22,MCU通过I2C接口(PB9,PB6)连接Codec,作为其控制接口,使用I2S(PC7,PC10,PC12,PA4)作为数据通道,此外,MCU使用PD4这个IO管脚控制Codec的reset。CS43L22的14,15脚连接到外面的耳机插孔,也就是说,我们可以通过插入耳机线的方式来收听PC端播放的声音。6 w, O& a9 V$ u

: x" S! N0 m. [" W; q, Y3 ~8 u) c9 a2.3 软件设计
9 u* i- a+ ~: H为了简化开发流程,这里使用CubeMx自动生成代码工具来生成初始化代码,首先基于Cube库架构以及USB协议栈的特点,我们得先设计一个合理的软件框架。9 m& I$ @2 K5 s
4.png
, o7 [( C& q& q  ^/ F7 t5 P$ f$ ?( R1 _; Y, b. V
如上图,蓝色表示的模块为标准模块,不需要我们去修改它,将由CubeMx自动生成,而绿色部分则可能涉及到需要修改,其中BSP部分是需要自己添加的代码,其他的都是由CubeMx生成。# f- Q7 S1 K) |8 Q0 f2 q4 R$ w
; s6 m( X, F- {; i  B* G
各个模块的工作流程如下设计:$ O& F* |* R$ O8 T" f( S
0 b# K* K8 V9 E2 u9 F
初始化流程: 由main开始,它首先对将使用到的外设I2C,I2S初始化,这最终将调到HAL MSP底层部分实现对具体IO管脚和外设的初始化。同时main使用usb description的数据通过调用USB栈初始化接口来完成对USB接口的初始化,这一步还涉及到USB的枚举过程。
% {* v# M- y. c6 k$ \$ W) u. zUSB数据传输过程:PC端软件在播放音乐后,通过USB通道向MCU传输音频数据,音频数据到达MCU时,首先触发USB中断,然后进入到HAL driver层,在回调到 usb conf模块,接着进入到usb core,usb core再转给usb audio class,最后音频数据到达usb audio interface模块,到达这里,就只剩下对音频数据进行处理了。Usb audio interface模块是一个数据接收到数据处理的一个中间对接模块。
9 s$ _& @/ [! y- h$ b( B+ Z/ RUSB音频数据处理过程: usb audio interface 模块将从USB stack底层传上来的音频数据转发给Codec组件,最终通过Codec组件连接的耳机播放出来。) b4 i9 Z/ s1 @0 V, l
从以上的音频数据流程来看,最主要的就是usbaudio interface模块,它实现了从USB audio stack到codec驱动的对接。
9 Q0 T- z) x# W7 S
2 m; Q* R+ ^$ O- {接下来,我们来看看软件层面上的实现。; S* L& P6 e" q# r: R2 E
9 m0 S* E  U7 \5 o
3 软件实现' M! J: Y: C; H: z+ v4 G
还是老办法,采用CubeMx这个工具来生成初始化代码,这样可以节省我们花费在基本外设上的调试初始参数时间。
' t' e' u  i9 {- |% c0 ?
5 U: [( P9 b2 m+ d2 ^3.1 创建CubeMx工程8 M8 D; `3 @- {6 v9 S+ t$ ~3 B# X+ F
由于我们使用到的硬件平台是STM32F4Discovery板,上面搭载的MCU型号是STM32F407VGT6,我们就以此型号创建一个名为Audio_Test的工程。7 \( T2 N& R9 @
8 m# b7 M! j% G* l- {) \  B
pinout:
+ r5 A( h6 j$ S2 z. g
4 o! D  s% _$ l6 N' t: y! I; z5 G外设有用到USB_OTG_FS(PA11,PA12device模式),I2C1(PB6,PB9),I2S3(PC12,PA4,PC10,PC7,半双工主模式),此外Codec的reset使用PD4管脚控制,使用外部8M HSE。其pinout如下图所示:
* B% A" c5 w! G$ y, [ 5.png
% `  T  c; B% o' h
7 H" g* p' W4 j- D% `4 pClock configuration:
! K9 N1 K8 I$ S! J! W5 o6 |" M/ V/ ^ 6.png
5 t7 w* M/ j; B5 B3 o, ^; x时钟树如上设置,主频使用168M,I2S时钟输出初始化为96M。
5 M3 r4 g$ y6 w5 B5 Y+ j) `* K; N7 D- K4 n. e" q' q7 m6 B; k8 t7 u
Configuration:
5 z/ T8 g+ _/ a8 }6 d" v5 ]( o6 [, v
  HAL层:. |- P2 ~9 i% r  |2 k. P& `2 ?' t
Usb_FS:使用默认参数。
) e5 R* \" \3 I7 [  w7 w0 ?! D) \! [; O. f) {
I2C:100K速率,7位地址宽度,使用默认参数。& d. P0 F; m( ]  f1 i, V

5 ?; @3 X# [0 ^2 U" v- xI2S:主发模式,标准16位宽,默认音频为48K,如下图:
; b5 X6 ]$ _% `2 U8 o  W& | 7.png . v1 [$ M5 B9 E6 m: Y+ p, m
并为I2S发送添加DMA,半字位宽:! c" d$ N: [: G  u; f4 k2 q! d
8.png 1 |( K" z% l, X( ^* r* w
+ z2 g7 \  A$ l7 V
   MiddleWares: : B' L- a6 z. A9 ~8 n

" g: \9 f. @0 w: M& hUSB选择Audiodevice class,其配置参数如下:
1 |1 Y3 C2 Z* y, I: B/ c# K' m 9.png
. n* r5 F. g2 I# v0 g& L
% A. `7 C5 J6 }0 l6 B( Q0 K这里都是默认参数。
) ~9 d! X9 _3 d 10.png ! P0 B& c# y" p# y7 h, F
在描述符参数内得为usb audio class修改两个参数:
. }8 I- i* e3 a% z6 I4 E9 N/ _6 C3 J* \
PID得修改为0x5730(否则windows驱动会加载出错)3 Z! f" g, y! |$ o( O% R5 ]& o
序列号:序列号字符串内不能包含字母,只能是数据(否则windowsaudio驱动在枚举后也不会将音频数据传输下来)。
+ U' G# o) |) A4 A. b! z( j最后修改工程设置,将堆大小设为4K,栈大小设为1K,如下图:
; t0 R1 S! K$ K( S 11.png
) g3 O. j4 K( R9 O) `3 k如此就可以生成工程了,我们生成IAR工程。' r  E* {4 G+ Z" V! }

: C# z( z0 h  K- e/ c/ t  P3.2 生成的IAR工程介绍
6 d  y) B3 p( g- C 12.png 2 f" h9 `' f8 h$ N0 K
如上图所示,生成的IAR工程,主要有User,Drivers,Middleware3个目录。8 r& q  H" p0 K% Z. l, U/ V  D( V

0 \; \5 B+ _4 f4 J( Z; V5 ]& FUser目录下为用户源码文件,用户的主要修改也将集中在此目录下,在这里,我们的主要工作是集中在usbd_audio_if.c文件,它对应着之前软件框图中的usbaudio interface模块,主要是实现USB audio协议栈与Codec的对接。其他源文件都保持不变就可以了。
- e; e; P/ D! ~Middlewares目录对应着usb audio stack模块,它由CubeMx自动生成,保持原样就可以,不需要任何修改。* D1 V1 U5 Z$ ~/ Y" a* g4 }
Drivers目录对应着HAL层,它包含CMSIS,HAL驱动。
0 w$ a* _  x0 G; m, }( c* U" l+ O$ C% L4 {9 G! b9 ^- N
3.3 开发# v: P1 x4 U+ `8 B9 Q' I+ @
3.3.1 初次编译测试! U7 B% E; E* `5 ~5 C; y; _
首先我们不做任何修改,先编译一下工程,发现能顺利编译通过,并烧录进STM32F4DISCOVERY板,运行后通过USB连接上电脑,发现在设备管理器中能正常识别到这个USB AUDIO设备,如下图所示:
& ^! _$ L/ ]; y 13.png ! t' d2 S& u( _! R! u9 m, f9 I. R; c
这说明,USB与PC端的连接是OK的,但不知道具体有没有数据。我们使用USB分析仪TOTAL PHASE USB480这个设备对USB总线进行数据监控,能够正常采集USB枚举过程和播放音乐的通信数据,如下图所示:
" M9 @2 a# T1 f: S 14.png
9 S' e* p  ]( u这表明,到目前为止,从PC端到USB端都是能正常工作的,从PC端发送过来的音频数据已经到达usb audio interface模块,目前只不过还没有对这些数据进行处理,显然,接下来的工作,我们就需要将这些音频数据通过codec驱动发送出去,最终到达外部组件CS32L22.
* s% L' U! n; H: e; |# c
" c0 y2 P, p# A# z) W% t* x- R+ Z  C2 x3.3.2 添加codec驱动和audio bsp模块2 I; [, `$ }  ^5 U
我们已经知道,我们需要为audio添加BSP模块,在这里,我们将BSP归属于drivers类,因此,在drivers目录下添加BSP目录,通过之前的软件架构图我们可以知道,BSP包含Codec驱动(CS43L22)和Audio bsp模块,因此,我们在BSP目录下有添加了Codec的驱动源码cs43l22.c与bsp_audio.c,如下图所示:
9 P4 C8 ^/ p: v4 b5 B1 x8 i+ Q: { 15.png
5 N1 t/ ]6 p% j# B- p" o( t其中cs43l22.c为codec cs32l22的驱动,我们可以从ST的组件驱动中找到它,并copy过来直接使用,不需要修改任何代码。而bsp_audio.c是我们自己写的,它的任务是为usbd_audio_if.c与cs43l22.c提供服务,让这两个模块胜利对接。
% X' D+ o# O- L% }' {, C
1 i7 d8 \. R6 L3.3.2.1 Codec与HAL的对接2 D! O; |3 m7 L1 x- y
首先我们来看Codec驱动文件cs43l22.c源文件,这个文件需要使用这个外部需要提供的接口:( A: ~: M0 |' o- j% L9 w* C- Z
  1. [cpp] view plain copy
    9 J- C9 d$ |$ r- Z% f: d2 S
  2. AUDIO_IO_Init()  2 R# B$ ^* t; N4 ^3 x, I  A& r
  3. AUDIO_IO_DeInit()  
    8 s8 {2 {. u" h2 y& w: Y
  4. AUDIO_IO_Write()  6 s( k6 V+ D5 A; t' G/ |7 }
  5. AUDIO_IO_Read()  
复制代码
这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:
5 ?5 A$ k; P( a! H
  1. [cpp] view plain copy) R! ~0 M3 O) @$ ~9 u/ f) M
  2. //---------------------for c43l22 port--------------------------//  , ~9 A/ L% |; ~) F/ S8 }# ^
  3. static void I2Cx_Error(uint8_t Addr)  : V# t6 L' }: W8 t& N- K
  4. {  
    " T4 \- t  H$ ^
  5.   /* De-initialize the IOE comunication BUS */  
    8 B  |2 E+ [7 K  S3 s) H3 g* M6 o8 ?
  6.   HAL_I2C_DeInit(&hi2c1);  
    ( V, D: V- n6 o7 A& R. Y
  7.   
    ; V9 Y; @- [  r$ f: r$ l
  8.   /* Re-Initiaize the IOE comunication BUS */  
    & v) s/ h, C. I1 b
  9.   //I2Cx_Init();  " _: v% ?, e$ e! d3 B
  10.   //MX_I2C1_Init();  
    5 z9 Z. z+ ^' g/ l# R
  11. }  
    3 D( i+ X9 y/ J9 t% r8 g# |
  12. static void CODEC_Reset(void)    z0 z1 g6 C, u4 n) X8 w2 T
  13. {  1 j' G$ [, l( Z; K  W
  14.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  $ y" R/ R+ D8 e  ^
  15.     HAL_Delay(5);  1 E0 q/ r1 G$ q+ o! w/ J0 }
  16.     HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);  
    " G- O; P4 F5 K; M6 c2 {% M
  17.     HAL_Delay(5);  
    / Q/ j1 c! K4 L( {. J* D
  18. }  5 _/ V% F7 l7 g. Y- l4 M# S
  19. void AUDIO_IO_Init(void)  
    3 ?& o9 \3 m! m5 c
  20. {  7 _* m  V$ i1 h6 K2 c4 `. k
  21.   //I2Cx_Init();  
    % W3 L1 h7 h; J- d
  22. }  3 }4 [; f0 u5 ?& f
  23. void AUDIO_IO_DeInit(void)  ; w$ k- H. m7 M2 m8 y7 k2 A
  24. {  5 j) Z6 T/ M8 h0 o6 i
  25.   
    8 P! W3 n5 r4 o& g5 {' d' J- O
  26. }  9 Z& \9 i( ?* k2 ^) B
  27. /** 8 O" G' p2 B, G! `, U# r0 V9 K
  28.   * @brief  Writes a single data.
    / T3 p8 q6 X' B' k$ O& q8 V
  29.   * @param  Addr: I2C address
    : P+ ]9 K/ H6 ]' ?0 w8 o" ~
  30.   * @param  Reg: Reg address 4 d0 u/ [. D# Z& W. h# p
  31.   * @param  Value: Data to be written $ m4 A: ^3 q! u, A" X$ r
  32.   */  
    3 `3 [( F, x: |7 l2 t6 {3 y! w5 E, j
  33. static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
      T) q9 G7 T. q3 ?( d4 b7 J
  34. {  6 x6 B1 P1 R  Y0 j. `
  35.   HAL_StatusTypeDef status = HAL_OK;  
    " L+ ~' U: B: O
  36.   * T1 S! P* x" ^* P: u- ^7 v
  37.   status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    : e( V  Q0 n; g" N
  38.   - c) r6 R  ?& Y( V! L
  39.   /* Check the communication status */  
    . {) A8 _$ j% \2 o' _
  40.   if(status != HAL_OK)  
    & |, L. b' G1 }6 x
  41.   {  
    . b) G$ O9 r* |( K4 u4 p$ C1 g
  42.     /* I2C error occured */  & E0 \( q. i) w; u9 c# y- u2 s
  43.     I2Cx_Error(Addr);  6 V' G6 k1 N! ^
  44.   }  8 U/ Z: L- ^; X7 L: u9 m  i
  45. }  % d5 g: X5 P9 C+ E  g$ s
  46. void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)  
    " j; N2 e) }$ z: v
  47. {  9 z7 z" E+ n6 K
  48.   I2Cx_Write(Addr, Reg, Value);  1 \& d4 w# ~. [( O. j4 j1 K) Y1 f
  49. }  1 h$ G: G6 ?& v" N0 i
  50.   ! [8 w8 P( J: f" T8 {
  51. /**
    ' n# K; R$ \+ p7 W% |
  52.   * @brief  Reads a single data. 8 `9 V% H) |: J3 E; V3 d" u
  53.   * @param  Addr: I2C address
    4 o) A3 o1 q4 P
  54.   * @param  Reg: Reg address ; n2 N% [! W2 y4 \, ?$ t! V/ ?
  55.   * @retval Data to be read
    2 L! m* Y+ K+ D# o* A* H
  56.   */  
    ' g0 v9 \; D, A8 f+ |) `7 c
  57. static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)  
    9 S$ u& \  d' x: i6 X" q2 b) \
  58. {  
    2 b, s$ H, D9 b$ D) G% w9 x3 C9 E
  59.   HAL_StatusTypeDef status = HAL_OK;  
    7 T; C4 ]* u) n7 ?& E2 p
  60.   uint8_t Value = 0;  
    ' l. t; X( X" o$ I! {0 ^: G3 w# N
  61.   
    + o. K0 X' e3 t- M% F
  62.   status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);  
    . Z$ c3 {1 ?; X
  63.   ( j+ P$ V8 x  B$ X) n. t
  64.   /* Check the communication status */  
    % x0 v7 {5 ?& D. [2 ^: L
  65.   if(status != HAL_OK)  
    6 W% s  e# ~7 T& E# Z3 i, z
  66.   {  
    8 }# q9 I. A/ X4 H. J5 u& O
  67.     /* Execute user timeout callback */  
    5 I1 _7 ?7 y8 e9 s' G
  68.     I2Cx_Error(Addr);  
    ; n, g# }% Y/ e
  69.   }  
      p7 R! e$ B2 U$ v  n
  70.   
    ( h: N  i9 x4 p9 Z% l+ r! z& O3 K
  71.   return Value;  
    2 H6 T, C) U: D* k- y. v0 O
  72. }  2 w  J9 |+ q- x  B% T) T! L
  73. uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)  
    5 g9 X$ @% p* o5 c
  74. {  
    - Y- s; r8 U7 f( f7 L+ m5 Z- w
  75.   return I2Cx_Read(Addr, Reg);  
    & S) o' O( t' A! n" z
  76. }  
复制代码
由于在main函数中已经对I2C初始化过了,因此,在AUDIO_IO_Init函数中不需要再次初始化。+ _' y5 E6 w9 q* D& j

' q( }6 E$ y/ [1 `  Q! C+ n& t就这样,就完成了Codec驱动与与HAL的对接。
9 s3 |( |/ ?& k$ c6 s" S
* G+ f/ W% N- x# V! z1 W- B3.3.2.2 usb audiointerface与codec的对接
+ b3 A0 i1 x3 Z% m我们打开usb audio interface源码文件usbd_audio.if.c文件,此文件由CubeMx自动生成,已经自动给出了一些关于usb audio class的函数,且这些函数体内容都是空白的,毫无疑问,接下来的工作,我们就是要完成这个空白的内容,就好比做填空题一样,当然,在做这些”填空题”的过程中,我们将使用到Codec驱动提供的接口,这一过程,就是usb audiointerface与codec的对接过程。
9 V. B3 ?, I( p' d) [2 z- C! t/ u$ O  g0 j: n
按照这一清晰思路,我们首先找到usbd_audio_if.c的一个接口:
1 g% x7 Q+ R9 D+ ^) k$ w
  1. [cpp] view plain copy
    5 g4 E  M2 X3 G, q4 U/ r
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  
    0 x7 W7 i" a' I/ c( O
  3. {   * O5 F) c4 f; |2 y6 t) ]
  4.   /* USER CODE BEGIN 0 */  " U: z, [0 y7 }
  5.   return (USBD_OK);  7 \( a) `  e% [2 V8 a  `
  6.   /* USER CODE END 0 */  
    ! Y5 X3 |. [0 k+ Y/ P& W9 j
  7. }  
复制代码
这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:
% d5 s4 w% Z0 t' p# N
  1. [cpp] view plain copy
    ! C, e1 h, C9 k5 V) X$ v2 U$ S
  2. static void I2Sx_Init(uint32_t AudioFreq)  2 p% C1 h: _+ x7 ]
  3. {  
    0 F  m- o% d% K
  4.   /* Initialize the haudio_i2s Instance parameter */  
    8 E. U3 A; S- \7 ?9 P
  5.   hi2s3.Instance = SPI3;  
    ) ^& W+ [  g, ~. a0 w' g. @( R
  6.   
    5 v' [* g$ R' p- m. v3 w# C4 M
  7. /* Disable I2S block */  
    % z1 x- G8 |: X
  8.   __HAL_I2S_DISABLE(&hi2s3);  # p1 O% G% |  Y4 j
  9.   7 b7 r. O7 ~0 z9 h. E: ?
  10.   hi2s3.Init.Mode = I2S_MODE_MASTER_TX;  8 D* K' f& p! f* y
  11.   hi2s3.Init.Standard = I2S_STANDARD;  8 o" p0 W) X  t+ M
  12.   hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;  
    ! F( b- c" e* t6 R$ S, S
  13.   hi2s3.Init.AudioFreq = AudioFreq;  1 P: d4 }/ Q. }  ]
  14.   hi2s3.Init.CPOL = I2S_CPOL_LOW;  , z2 q# b5 v' i0 B# l# T; f
  15.   hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;  : x& V1 J4 W; O8 X* s: H5 ?; }
  16.   
    1 h3 r& @+ h9 U) t  D
  17.   if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)  
    1 t0 i  Q. d0 O2 V0 W  c
  18.   {  
    3 n6 }5 r0 r+ i9 U% D
  19.     HAL_I2S_MspInit(&hi2s3);  0 M% o  P. q) {
  20.   }  # Z6 S; h/ J8 R& p/ A
  21.   /* Init the I2S */  
    5 z( ^- p+ c' v% G$ a6 ]
  22.   HAL_I2S_Init(&hi2s3);  6 ^7 T3 o6 j: ^. M; k( T; e1 K  h0 g
  23. }  * U' I/ g- C% G, e8 P& s& ~
  24. const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};  ) M" _  f! t# i
  25. const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};  ' u+ l- Y, i& s
  26. const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};  3 `/ G2 h  Y$ a6 h0 h) f
  27. uint8_t BSP_AUDIO_OUT_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq)  3 J8 G( J% [  x; w6 x2 g0 X9 `) a
  28. {  
    6 \) a& ]4 `0 L1 `) H; Z
  29.     uint32_t deviceid = 0x00;  : Z# p3 n7 N  {
  30.     uint8_t ret = AUDIO_ERROR;  
    : r' n' I6 F8 c* b( |1 w3 i
  31.     uint8_t index = 0, freqindex = 0xFF;  
    " W. Q* M& i7 h% B& ]2 L2 L. S6 F+ z
  32.     RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct;  ; q! w  `  A7 ^. M5 W# V) {& y, y4 e
  33.   
    - `+ I& C9 q8 W! X7 x" ]* y- ?) |% }
  34.     //get the according P,N value and set into config,this is for audio clock provide  $ b$ q- R% t: d$ `
  35.     for(index = 0; index < 8; index++)  
    , N# V8 f% _2 O3 r0 m) x4 I5 V+ e
  36.     {  
    - b. o9 F" B4 M9 y+ b
  37.         if(I2SFreq[index] == AudioFreq)  4 [7 t0 X, y) e9 i' B" f6 N- B
  38.         {  ) B8 T- v: ^) A" o2 e! p* h" Z
  39.             freqindex = index;  
    8 R4 r* Z& C. X" p6 P! [: f) I
  40.         }  0 g1 S; y9 T  O! i, w# q+ c5 j. J
  41.     }  
    : _& K  n" m' n+ G' m# v7 c
  42.     HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct);  6 [5 E0 b4 p& g  g
  43.     if(freqindex != 0xFF)  0 e. l9 S4 z  ?' g+ {+ P/ z$ {8 e
  44.     {  0 n  u# L# n0 o$ x4 `: l+ I
  45.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    * g4 z& j/ w7 b$ s
  46.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];  
    $ C! z# c% B3 a! Z. ?
  47.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex];  ( p' t+ q3 P2 w8 M
  48.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  9 Y: C/ I4 ]8 o+ H
  49.     }  7 K8 r2 N- b, \6 d/ K' m7 @  k4 u
  50.     else  $ U! `" t5 U. ?9 u" _
  51.     {  
    3 D* m& s' M  I1 q
  52.         RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;  
    ; y6 r# `% J' f8 |
  53.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;  
    9 I$ w6 @. Y8 u3 c
  54.             RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3;  ; |8 U/ O7 I" C0 w
  55.             HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);  
    6 O+ R9 i5 F# M& v  E8 ~
  56.     }  
    6 q4 R1 M2 u. {
  57.   
    , D" U; G& W/ d# t- m
  58.     //reset the Codec register  
    ' I2 L! R/ ?  S! S: ]
  59.     CODEC_Reset();  5 j( I# z$ }5 F
  60.     deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);  
    & C- @# Q1 N% ^5 a
  61.     if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)  
    / a7 u  }( j/ U2 W# l
  62.       {  
    7 c' ~% P8 U  n* J- U4 g
  63.         /* Initialize the audio driver structure */  
    . ?; A7 T1 V4 p. P+ i9 u
  64.         audio_drv = &cs43l22_drv;  
    ) d' O1 X- ~# k  N6 l) n; y
  65.         ret = AUDIO_OK;  
    ) d# j7 d) t- Q3 R4 ~
  66.       }  + _9 d& Z* h0 d1 R8 Z; p0 E
  67.       else  7 j, N* W, S2 W  W
  68.       {  3 T4 q2 @: W, c4 p
  69.         ret = AUDIO_ERROR;  
    / R" S3 V  }/ j- I& ]
  70.       }  0 e6 ~) s( L2 v: L" |' l" Q% p
  71.   
    8 {8 \. |2 ^, Q8 L
  72.      if(ret == AUDIO_OK)  * c. Q4 l/ U' f
  73.       {  . l$ M' G% d. a( t' N$ _) N
  74.         audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);  
    7 w) s' O' }" v7 J9 v3 p
  75.         /* I2S data transfer preparation: ; ~7 ]9 X6 o+ \; h% A6 A3 z
  76.         Prepare the Media to be used for the audio transfer from memory to I2S peripheral */  ' ?- q; A- ]. @0 p  R% o
  77.         /* Configure the I2S peripheral */  
      i* o1 Q, Q4 H2 V/ J8 g! ~9 c
  78.         I2Sx_Init(AudioFreq);  3 C8 Y; I" r$ w! f5 B9 U7 ]
  79.       }  1 i+ @, ?' j! ]5 Y2 t0 W
  80.     return AUDIO_OK;  
    , m. t/ j) w- g. ?& t  H( V7 |0 V0 P
  81. }  
复制代码
在BSP_AUDIO_OUT_Init()这个函数内,根据所传入的采样率,程序在预定义的数组内选择出MCU内部时钟树对I2S时钟的一个合理的分频值,并设置进时钟树配置内,然后再对codec进行初始化,最后对I2S外设初始化。
( U- y2 U9 n' u8 D  s9 M- |% g6 u
1 J2 Z1 C0 U1 r2 k$ J0 h这个选择I2S时钟合理分频值的过程是根据STM32F407的参考手册中的建议来做的,如下参考手册中的28.4.4中表126:- A0 |6 b) ^! U6 l3 _" K
16.png
1 \9 S- k- f$ `" u0 X; C1 r. c/ t; g# G* j# w/ Z
在48K采样率下,假设时钟树下的PLLM VCO=1MHz情况下,且MCK使能,为了尽可能输出靠近期望的时钟,此时应该将时钟树内的PLL2SN设为258,且PLL2SR设为3,I2S内部的预分频因子I2SDIV设为3,以及零散因子I2SODD设为1。这个计算公式为:
6 m! B# _0 P0 u& q 17.png / O$ i4 m/ i) X& }/ [$ X
也就是(1M*258/3)/[(16*2)*((2*3)+1)]=47991.07142857143,差不多48K。
  \+ r( ?$ z7 |! _. O  I
1 n; n6 K2 j. c# i' w  I( w, p- D: bPLL2SN,与PLL2SR的设置在上述代码中都有所体现,但是,预分频因子I2SDIV和零散因子I2SODD又是在哪里设置的呢?答案是在代码调用HAL_I2S_Init()时,在这个HAL接口内部会根据I2S的Audio Frequency(CubeMx中的I2S的Configuration中配置的参数),以及I2S的输入时钟频率和MCK是否使能这些前提条件来自动计算出预分频因子I2SDIV和零散因子I2SODD的值,以此来尽可能匹配输出想要的位时钟,也对应着采样率48K。+ J2 x3 ]' s! S$ q
5 u9 f  i% |. R
搞懂了这些之后,我们马上将其代码进行对接:6 i4 f% h! i9 [$ w4 G. d
  1. [cpp] view plain copy2 T& J( j1 J# `: I* h. ?
  2. static int8_t AUDIO_Init_FS(uint32_t  AudioFreq, uint32_t Volume, uint32_t options)  % I5 W* n& C6 ?2 k6 r( P
  3. {   4 [# |* q9 d! S9 {
  4.   /* USER CODE BEGIN 0 */  . @& Z, Y/ i3 U& u8 p5 A
  5.   BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);  
    % ^+ `0 s1 k  y2 f) ?
  6.   return (USBD_OK);  + H5 {; E: [1 D7 ~. ]5 @1 {
  7.   /* USER CODE END 0 */  ; @( V( `+ L9 C' [7 _
  8. }  
复制代码
接下来下一个需要对接的接口:
9 |" q) s. f1 s5 N+ Z
  1. [cpp] view plain copy" C9 U$ m% [7 G' f
  2. static int8_t AUDIO_DeInit_FS(uint32_t options)  
    2 [' }6 G( X1 I. A* k
  3. {    p1 i( W' e) D* E% i; s9 e- F
  4.   /* USER CODE BEGIN 1 */   
    ( {  h- o% R: u4 ^) a( Y3 _6 Q  T
  5.   BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);  
    / W, n! C* q( ^5 I
  6.   return (USBD_OK);  ) u3 y: _- [" d& n
  7.   /* USER CODE END 1 */  & S$ ^( b8 K$ c- `4 l  P
  8. }  ' t* h" R2 X+ f( H  w( X6 B8 F+ m
  9. 很明显,这个个反初始化的接口,它的具体实现如下:
    2 m; I/ _6 I7 {9 r" a

  10. 1 v4 L6 l; h4 e/ B! b* W" s% G9 c
  11. [cpp] view plain copy
    ) U& s( [' K/ i. i- W) ?5 H
  12. uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option)  / B8 R0 X( x9 [
  13. {  : u/ Y1 c. z7 \1 W9 `' O
  14.   /* Call the Media layer stop function */  7 l- F2 H3 |9 H3 _1 ]3 x
  15.   HAL_I2S_DMAStop(&hi2s3);  
    + R6 n3 N: t! R8 ^
  16.   
    7 F& ^, O/ T3 a3 f7 H1 ]
  17.   /* Call Audio Codec Stop function */  
    : I! w0 c2 u1 n4 J! G* N; i$ N
  18.   if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0)  7 ?1 O4 d- P# {1 o( v# a! V. h! J( f/ a
  19.   {  9 v7 Y! `) ]6 n5 L# n
  20.     return AUDIO_ERROR;  
    ' G+ Q* C- z+ Y8 X
  21.   }    |: F$ V) R2 Y$ {' t
  22.   else  
    & u" y7 a  @0 h( p2 @* h( l$ ?$ W
  23.   {  : \* r' Q4 ^  G
  24.     if(Option == CODEC_PDWN_HW)  
    ( z! |  S) x/ M. U3 t6 j$ R  G2 ^
  25.     {  ) {. f& x5 B5 x2 f( U$ J  Z2 e3 l
  26.       /* Wait at least 1ms */  
    . b( E% b( `% S. s
  27.       HAL_Delay(1);  
    # z+ K/ n2 g5 |6 m, I# |
  28.   0 W1 ]& O+ W% K! x
  29.       /* Reset the pin */  
    6 U; z- T( ^/ S7 @' Z6 }3 }  A! i7 c9 b4 N/ ?
  30.       //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);  
    $ @  H( |; {1 j. d( [# z3 U
  31.       HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);  
    ; }+ V8 y# \2 T/ s1 {
  32.     }  
    $ k" J$ ?! d4 v  u. }) m  t( t
  33.   
    # Z: L5 j% R# z6 w
  34.     /* Return AUDIO_OK when all operations are correctly done */  3 w3 Y0 T( z: S2 h& R; C3 |$ f
  35.     return AUDIO_OK;  
    3 c# t7 u4 {  \+ Z. C4 w; F
  36.   }  7 E4 {" G. @2 Z
  37. }  
复制代码
先关闭I2S的DMA,在调用Codec的停止接口。
% P/ V% X2 F! ?( t& I* p) N: w" {$ NOK,下一个:$ k, ]9 {" X% T5 Y" C
  1. [cpp] view plain copy  _, Q; m; {: c4 B/ M
  2. static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)  
    0 m( O' q) i6 g9 c
  3. {  0 S9 _3 g, F; x3 Y
  4.   /* USER CODE BEGIN 2 */  
    - u( y+ ~) p' g, M
  5.   switch(cmd)  . ]: y  k+ a, _0 [2 v. B4 S
  6.   {  
      z! G) y! s: Y1 B
  7.     case AUDIO_CMD_START:  7 c5 E4 R* J8 f
  8.         BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size);  . A( ?$ {, o* ]5 {; {4 Z6 a8 v
  9.     break;  
    1 @/ `9 P" p, \; W3 t4 ^
  10.   
    3 a! n/ S, y" C5 N3 `
  11.     case AUDIO_CMD_PLAY:  + d; W* S, n: G, d0 \
  12.         BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);  
    ) I3 w. C2 R$ ~2 h2 ^4 i0 J2 F' Y
  13.     break;  
    * C- w$ b8 V* c0 [% K
  14.   }  
    0 N, f  F  K% p. c% T
  15.   return (USBD_OK);  
    9 R6 m# k) T6 ~
  16.   /* USER CODE END 2 */  6 J2 L: h: z0 T6 W3 a1 C: \  a* n* L
  17.     ! F+ |  S5 b: Z4 |( o. G) D+ g0 o
  18. }  
复制代码
第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:% o4 ^# K2 U  I" ~6 a
  1. [cpp] view plain copy3 ^. ^) W1 {0 @7 ]; l+ r
  2. uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size)  / _- q( v, I& a$ S, }' v' v
  3. {  
    - G* Q/ }; R) [1 [# }3 \
  4.   /* Call the audio Codec Play function */  
    0 R, Q7 ^4 |+ j* V
  5.   if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0)  7 h1 w, t! B1 C2 J
  6.   {  8 j) U; }; H- s; M+ c  E3 n  M2 o
  7.     return AUDIO_ERROR;  - I7 T% e2 V8 Y* }3 A5 v( K
  8.   }  # m; o0 w! r1 }1 c) e' P5 v3 j
  9.   else  2 }7 t  h2 U0 `! J
  10.   {  1 u3 W  E* S, M: D% ^, |4 Z5 Z
  11.     /* Update the Media layer and enable it for play */  . m+ j( B8 Z5 v  O5 {; n; _; }7 y/ N
  12.     HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE));  1 I! K) J9 W" h( I  H7 L/ V
  13.     return AUDIO_OK;  
    3 z, j: s. g0 J) U$ o; t
  14.   }  ; S0 K, l2 u% e0 w+ f* F  o4 B
  15. }  
复制代码
很明显,它是调用Codec驱动处理数据,也就是通过I2S的DMA方式发送给Codec。/ i1 p* h! V( j" N  K5 [' i7 X9 z
$ x5 |" Y1 n4 P& m6 p
然后I2S的DMA会产生传输完成中断和半传输完成中断,在这两个中断处理上,会回调到AUDIO_AudioCmd_FS()接口,并且此时传入的参数变为AUDIO_CMD_PLAY,此时,音频数据的处理函数为:
3 K5 O* T0 a: T
  1. [cpp] view plain copy: d- {$ P. {* j, F7 V4 u$ M
  2. void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)  
    $ a# X/ ?2 V% V: V6 T
  3. {  " c1 Y' t9 U3 k2 K3 F: h0 c4 i2 g
  4.   HAL_I2S_Transmit_DMA(&hi2s3, pData, Size);  0 q" C8 o6 x9 T8 }3 `4 I
  5. }  
复制代码
也是通过I2S的DMA将数据传输给外部Codec。
( o) D) i3 K; q5 I5 l! y/ {9 z+ A$ I: e2 [7 b- ~" g9 v+ `- [* A
上述过程涉及到另外两个usbd_audio_if接口函数,即I2S的DMA半传输完成和传输完成中断回调,如下所示:- {) O4 p2 o, N6 A* O) Q) W
  1. [cpp] view plain copy
    ! K6 c* C8 ]+ v/ J* `1 G9 a
  2. void TransferComplete_CallBack_FS(void)  ; W- u: O/ o+ H3 x4 _( h
  3. {  
    5 {5 ?  e5 ^, m9 A* F" I# H% ^
  4.   /* USER CODE BEGIN 7 */   
    ' E4 r, ^$ {  j9 d8 V9 g! t! h  s, B
  5.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);  , R6 U8 y- p& a1 z/ y" x
  6.   /* USER CODE END 7 */  
    3 ~/ Z! W) Y0 c# K# U" h6 s
  7. }  9 ~) H  S  C4 y  F  e0 l, K' J
  8. void HalfTransfer_CallBack_FS(void)  
    $ r+ b- y: Y' U! J4 \6 x9 }( f
  9. {   / q3 L4 ?( q2 a0 \; f" p# @
  10.   /* USER CODE BEGIN 8 */   ( A$ |3 l% l# d* z+ E# S0 S
  11.   USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);  
    ) V  ]# s: [/ P
  12.   /* USER CODE END 8 */  & H2 {0 m2 k: g* Y' S! f" }
  13. }  
复制代码
此代码为CubeMx自动生成,且在自动生成的代码中就已经调用了usb audio class函数USBD_AUDIO_Sync(),在这里,对于这个,我们是不需要添加任何额外代码的。之前我们说过,在USBD_AUDIO_Sync()函数内部,会实现对AUDIO_AudioCmd_FS()的回调,目的是,需要及时将数据缓冲中另一半准备好的数据也通过I2S的DMA传输给外部Codec,这个不间断的传输,才能实现音频播放的连贯性。
+ e1 L0 \  D& m5 W, k$ b9 g! Q2 |
此外,在USB设备端,后续接收到的音频数据会紧接着之前的数据进行存放,这里实现了一个数据环形缓冲区,来实现了USB接收端与I2S输出端数据有效的缓存。
0 _$ C  m, C6 |# ]# w7 r7 s
* o! n5 ~, E8 _$ G1 E接下来看下一个usbd_audio_if接口函数对接:
- j3 F% @9 U" x6 y: J6 Q
  1. [cpp] view plain copy
    % r: D( `" u$ F' ?' w% o5 q+ ?9 E
  2. static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)  
    ( w7 _: Q: B+ }& N
  3. {  3 g, d( i' F2 v0 f* V2 G
  4.   /* USER CODE BEGIN 3 */   
    7 t) h- d6 q- [, z* R8 a* F" i
  5.   BSP_AUDIO_OUT_SetVolume(vol);  7 f, ~( @- s$ G, F- @5 \) l
  6.   return (USBD_OK);  
    $ Y: w( H; k/ m" h
  7.   /* USER CODE END 3 */  
    % e7 d  q/ G- r3 q: {. B# e
  8. }  
复制代码
很明显,这个是音量控制接口,也对接下:7 J- P: M1 P1 p2 ^* y, v1 r8 V
  1. [cpp] view plain copy3 r- F& b7 W# _% H& T+ l5 O" G
  2. uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)  
    6 n' N4 \1 }) z! s0 ]/ r
  3. {  
    & p8 X6 T9 i3 }; E9 o0 k
  4.   /* Call the codec volume control function with converted volume value */  ( {# z! m2 M$ M- _( m; F* S+ w
  5.   if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0)  6 }# m- P! `: O& `
  6.   {  
    + ^: q; q5 o. }. j% I
  7.     return AUDIO_ERROR;  / E  }+ j& E2 R  e: B
  8.   }  
    7 e" {7 r- M" _$ a
  9.   else  ' U( m; u5 o" ~/ q" o3 V" r
  10.   {  3 X" ?! S; {8 L; K+ a3 X- t
  11.     /* Return AUDIO_OK when all operations are correctly done */  
    4 j" S6 d; D" x# @: r9 i
  12.     return AUDIO_OK;  2 w0 S' S: P3 [: f
  13.   }  7 W, d3 r  z/ {+ \
  14. }  
复制代码
直接调用Codec启动的相应接口。需要注意地是,实际上,在PC端进行音量的调节,并不会向USB端发送相应的音量调节指令,这里只是象征性的对接下,实际上在USB AUDIO中代码并不会允许到这里,音量的放大和变小直接体现在音频数据本身内。2 [+ q( n1 M9 R4 _# m
0 \3 {% m, N" v" l8 J
下一个:
1 A1 I, P4 f5 v& u( Z
  1. [cpp] view plain copy' ?+ U' W3 f( z2 a! m8 @1 L
  2. static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)  
    ( V8 [" P4 M. B, m
  3. {  
    " }- L- E2 y3 I" P+ z
  4.   /* USER CODE BEGIN 4 */   
    6 V$ b, N9 T) B# \
  5.   BSP_AUDIO_OUT_SetMute(cmd);  
    6 S& |3 [( f5 r: k
  6.   return (USBD_OK);  
    ( `% A8 B. Z0 f) h( X$ m
  7.   /* USER CODE END 4 */  
    , i$ r! i* Y+ n' @
  8. }  
复制代码
静音控制,其实现为:; p5 s3 H4 Z% R& q0 s2 t
  1. [cpp] view plain copy3 C' L5 O% J' [2 h& B" t" T' @: Y
  2. uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd)  7 O. R3 v% }9 Z" V" h7 Q2 C
  3. {  9 o, a4 ?5 V) W2 K
  4.   /* Call the Codec Mute function */  
    " L& ?3 L6 @2 }
  5.   if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0)  % e7 ?: H' x$ D5 T, Y  d% ~3 l0 T  l
  6.   {  & V1 R# T7 g9 r; a2 R8 S
  7.     return AUDIO_ERROR;  
    # Y7 K& W  `# O3 \
  8.   }  
    - ^( D5 X# ^# G8 B5 p# k
  9.   else  
    8 h' u# Z" Q. `" L' [0 Y
  10.   {  + t* B/ o# R( y
  11.     /* Return AUDIO_OK when all operations are correctly done */  " N3 G% N9 e: o5 K0 L
  12.     return AUDIO_OK;  
    2 P& T% D4 m: X/ V% g
  13.   }  , _, Q1 G( x" c* _  C
  14. }  # ^; O7 y, q* F  A
复制代码
很简单,直接调用codec驱动的静音接口。静音接口与音量控制不同,在PC端进行静音操作会发送相应的mute指令,进而运行到这里。; s9 q; N1 m& h* c& |3 p

* F# W* P" d* `0 Y6 g6 _8 [OK,就这样,usbd_audio_if模块的接口基本上对接到这样就可以了。0 i: q! E& t2 a3 y2 u8 r. l. b

( Z; j& {- s# `4 T& X3 w4    测试验证
7 p0 Z) S+ W$ x% }/ p将代码编译后烧录进STM32F4DISCVOERY板进行验证。( U  G' a' n$ {* W+ h  c" j/ x
18.png
. |9 d1 z, j' x0 H4 h+ Y' F最终验证是OK的,可以从耳机上听到PC端播放的音乐。7 [7 {+ S( s2 F: i
$ j( c8 D6 r9 Q3 G
5    结束语
+ Z6 S  d) H' s6 W' A) L$ p$ Y$ A. L在CubeMx上对中间件USB配置时,将USB audio class的音频采样率设置为48K,那个这个参数会再USB枚举期间会传递给windows的audio驱动,在枚举通过后,后续通过USB传输的音频数据都将是固定以48K采样率来的,也就是192bytes/ms,也就是说,不管PC端播放什么音乐,windows的audio驱动都会固定以48K采样率向USB端口进行传输。这种特性是由windows的audio驱动决定的。1 s& E# Q/ e5 s$ l6 b7 t% b7 _; h
! {" e5 I" W8 X* n4 u2 t' `$ o( {" `
I2S外设向codec传输的时钟是可以改变的,在本应用中是用不着改变,这个是因为USB端固定以48K采样率接收数据,那么I2S也可以固定以48K采样率所对应的速度向Codec传输速度,这个特点,正式因为USB audio的固定传输特性所决定的。若换成播放本地U盘音频文件或连接iPhone并播放iPhone的音乐时,则I2S外设的时钟是根据每次播放的具体音乐所对应的采样率来配置I2S的时钟的,这种机制稍微有所不同,这里只需注意下,理解了就可以了。7 M8 g6 K/ a# V3 p' m

" h$ j  v% {6 k7 X7 C/ p! s! J在本例中,从I2S传输数据的速率是48K的采样率,但实际精度却是47991.07142857143。这个与标准的48K还是有所偏差的,实际上,无论USB端和I2S端的传输速度在理论上有多匹配,在实际上,多少都会存在些偏差,这也就意味着,在USB与I2S这两个”入口”与”出口”之间的缓存,在随着时间流逝,如不进行任何处理,这个缓存理论上一定会爆掉或掏空。那么这里就需要针对这个缓存这种现象的一种处理,或者叫做算法,算法的好坏在一定程度上决定了音质的好坏。而本例中,我们使用的是CubeMx生成的默认的最简单的算法,我们不做深入讨论,只是让大家有这么一个概念即可。" U; X  Z5 r8 K; L7 F- G+ f
" b: b3 ?% ~8 N1 K2 F

  j6 u, G: W& [( I% M" I, |4 O- h$ ^' Y* s

9 ]# z9 ?, E$ Q9 a. N0 O9 `

评分

参与人数 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,忙等待,传输数据这块你有做特殊处理吗?
3 g6 a! l9 ?- ?7 x& {; Fstatic uint8_t  USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,3 C6 N9 N! y% t6 I, j
                              uint8_t epnum)
# k; p5 g) x8 w2 [% a{% d5 n7 f4 g0 q1 t, j; `/ n1 g
  USBD_AUDIO_HandleTypeDef   *haudio;
* j3 h6 t% c( O( _  haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;
: Q9 X6 K- W9 D2 L" R1 b( S8 T* J0 L" R# T
  if (epnum == AUDIO_OUT_EP)
1 d1 B+ R! U/ M  n; R$ F8 `  {, J) K& ^( Q. a( @
    /* Increment the Buffer pointer or roll it back when all buffers are full */3 `" ]5 e- _! X( G& o% `6 X

' Y; X/ F) }9 i/ f, x: `: z$ T! H    haudio->wr_ptr += AUDIO_OUT_PACKET;0 [' s3 X- s1 ?, t
1 X3 C7 ^2 O' o! c; E$ j! z, c
    if (haudio->wr_ptr == AUDIO_TOTAL_BUF_SIZE)& G& f$ T$ V& {! v/ F
    {0 ]! W" l$ o7 H- h) Y# _  ~5 D8 c
      /* All buffers are full: roll back */% y! S) o; _& o6 [8 @/ u
      haudio->wr_ptr = 0U;
, t: z& t' c& i; [4 I5 U% h; E    ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
1 u( a$ p# D9 J5 k                                                 AUDIO_TOTAL_BUF_SIZE / 2U," B! @3 ?8 m/ N; H( h/ R8 J; @
                                                 AUDIO_CMD_PLAY);
" `1 [7 W, `( W#if 0, c. t3 q* r4 T: j
      if(haudio->offset == AUDIO_OFFSET_UNKNOWN)9 b' s) m. N+ W, o" a( t; b
      {7 M2 V4 `$ E5 K9 A; U5 l9 @$ A
        ((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
1 ~; G- @1 L* O                                                             AUDIO_TOTAL_BUF_SIZE / 2U,
7 Y2 C& X, g' G' @6 I                                                             AUDIO_CMD_START);( p( O' ^; e/ K
          haudio->offset = AUDIO_OFFSET_NONE;( d6 F1 {9 ]% ], Q
      }- J# ]5 g# L$ D
#endif) j$ z4 c7 w. w" Y. C/ w2 [
    }. [/ A0 R% Q* v4 }" l& A

0 k% f" D! K" H( d: b    if(haudio->rd_enable == 0U)
; U" p/ n* u: V# V9 x, \    {; Q1 R% U+ D, A$ j! L& `
      if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U)), |. h0 y1 o3 g! l
      {
* J" c+ E: c+ B+ e9 w+ A        haudio->rd_enable = 1U;
# a' G2 V; @) ?* H      }3 H# \! ]- @/ ^
    }
* Q1 W7 \3 Z3 b! L+ t
4 J+ Z' k- ~9 t/ Y    /* Prepare Out endpoint to receive next audio packet */2 u( G$ _5 L4 b$ _5 A; p
    USBD_LL_PrepareReceive(pdev, AUDIO_OUT_EP, &haudio->buffer[haudio->wr_ptr],: P; _, t" D% P& _0 Z
                           AUDIO_OUT_PACKET);
- w! C  M; n  Z1 A$ h  }
zhuangwf 回答时间:2019-7-20 22:18:13
楼主还在吗?
3 o, ]7 S# F* `9 m我参考了您的这篇文章,在 STM32F413 discovery 板子上试验 USB Audio,2 k! f: v* B& k! D7 n
我先用 STM32CubeMX 5.2.1 生成代码框架,然后再把 STM32CubeF4 V1.24.1 里面的 stm32f413h_discovery.c, stm32f413h_discovery_audio.c, wm8994.c 这几个源文件添加到工程里,用的 toolchain 是 IAR 8.30。0 s: C6 W; C# V4 L) z0 D2 @
现在的问题是,如果在 usbd_audio_if.c 里面函数 AUDIO_Init_FS 里面什么都不调那么能成功地枚举出 "STM32 Audio Class" 设备,% V* z7 Z' ]* R4 m& x$ s/ p
但是只要 AUDIO_Init_FS 里面调了 BSP_AUDIO_OUT_Init 就会枚举失败,显示“未知 USB 设备”,跟踪 BSP_AUDIO_OUT_Init 的执行过程没发现问题,
6 R5 T" Y) y' g而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
xuqingli 回答时间:2019-10-14 16:47:30
梦中的飞鸿 发表于 2019-9-11 09:204 P* J1 g! j; A; l. o( _6 R. y( l
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
) B6 v4 e7 {/ k( Q7 r& K6 `2 m1 K
该设备无法启动。 (代码 10)
- S7 j* Y( r3 r
出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
anny 回答时间:2018-5-23 14:45:04
非常好的帖子,谢谢分享!!!
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:453 i6 M) a7 M) T* i# C
非常好的帖子,谢谢分享!!!

9 E! y( b* M4 D+ B0 ?8 n希望能有帮助
aimejia 回答时间:2018-5-23 14:52:00
anny 发表于 2018-5-23 14:45
& B0 a- m7 I+ C- C' G非常好的帖子,谢谢分享!!!

! E. R3 ^: e/ g& W希望能有帮助
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

' n; X( x1 f) e; \" k非常好的帖子,谢谢分享!!!
Delei 回答时间:2019-8-10 11:28:21
好贴!
Delei 回答时间:2019-8-12 11:18:15
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
  z4 a- S* t8 r9 v7 q! E: x; B% m3 k3 a! T9 i" H

驱动安装失败

驱动安装失败
梦中的飞鸿 回答时间:2019-9-11 09:20:50
delei 发表于 2019-8-12 11:18, X0 s9 a3 W9 N2 w3 x$ n5 o' Q
楼主,我想问下,stm32 Audio Class的PC端不需要驱动吗?我的提示能够正确识别设备,但是安装驱动失败!
9 H1 Z# T( K, p
1 D" e$ {- d- O9 S* d1 O1 N2 p  F ...

' {' a' N" Y5 t. P( U9 W这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
! m! G: P2 g+ @; m1 c/ i6 ?- Z: T- q
该设备无法启动。 (代码 10)
" O' a/ R  l5 z' S, i& d% p1 F8 J' s& K! e9 T. B: @6 L
I/O 请求已取消。1 Q8 |$ w5 G0 h( @6 n0 J
yukaigogogo 回答时间:2019-11-21 14:01:12
梦中的飞鸿 发表于 2019-9-11 09:20
. F5 @2 F* m+ J这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示, _& x" w1 N, `* x

2 x$ x8 T0 W* F. R' }6 p- T1 @该设备无法启动。 (代码 10)

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