本帖最后由 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, ?
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$ }
) 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
* 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
, 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, [
% ` 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/ ^
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& |
. v1 [$ M5 B9 E6 m: Y+ p, m
并为I2S发送添加DMA,半字位宽:! c" d$ N: [: G u; f4 k2 q! d
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
. n* r5 F. g2 I# v0 g& L
% A. `7 C5 J6 }0 l6 B( Q0 K这里都是默认参数。
) ~9 d! X9 _3 d
! 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
) 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
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
! 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
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: {
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
- [cpp] view plain copy
9 J- C9 d$ |$ r- Z% f: d2 S - AUDIO_IO_Init() 2 R# B$ ^* t; N4 ^3 x, I A& r
- AUDIO_IO_DeInit()
8 s8 {2 {. u" h2 y& w: Y - AUDIO_IO_Write() 6 s( k6 V+ D5 A; t' G/ |7 }
- AUDIO_IO_Read()
复制代码 这个都是Codec的基本控制接口,是通过I2C来控制的。都是需要用户在驱动外部来提供这些接口给到驱动,于是,我们在bsp_audio.c文件中来提供这个接口的实现:
5 ?5 A$ k; P( a! H- [cpp] view plain copy) R! ~0 M3 O) @$ ~9 u/ f) M
- //---------------------for c43l22 port--------------------------// , ~9 A/ L% |; ~) F/ S8 }# ^
- static void I2Cx_Error(uint8_t Addr) : V# t6 L' }: W8 t& N- K
- {
" T4 \- t H$ ^ - /* De-initialize the IOE comunication BUS */
8 B |2 E+ [7 K S3 s) H3 g* M6 o8 ? - HAL_I2C_DeInit(&hi2c1);
( V, D: V- n6 o7 A& R. Y -
; V9 Y; @- [ r$ f: r$ l - /* Re-Initiaize the IOE comunication BUS */
& v) s/ h, C. I1 b - //I2Cx_Init(); " _: v% ?, e$ e! d3 B
- //MX_I2C1_Init();
5 z9 Z. z+ ^' g/ l# R - }
3 D( i+ X9 y/ J9 t% r8 g# | - static void CODEC_Reset(void) z0 z1 g6 C, u4 n) X8 w2 T
- { 1 j' G$ [, l( Z; K W
- HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET); $ y" R/ R+ D8 e ^
- HAL_Delay(5); 1 E0 q/ r1 G$ q+ o! w/ J0 }
- HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_SET);
" G- O; P4 F5 K; M6 c2 {% M - HAL_Delay(5);
/ Q/ j1 c! K4 L( {. J* D - } 5 _/ V% F7 l7 g. Y- l4 M# S
- void AUDIO_IO_Init(void)
3 ?& o9 \3 m! m5 c - { 7 _* m V$ i1 h6 K2 c4 `. k
- //I2Cx_Init();
% W3 L1 h7 h; J- d - } 3 }4 [; f0 u5 ?& f
- void AUDIO_IO_DeInit(void) ; w$ k- H. m7 M2 m8 y7 k2 A
- { 5 j) Z6 T/ M8 h0 o6 i
-
8 P! W3 n5 r4 o& g5 {' d' J- O - } 9 Z& \9 i( ?* k2 ^) B
- /** 8 O" G' p2 B, G! `, U# r0 V9 K
- * @brief Writes a single data.
/ T3 p8 q6 X' B' k$ O& q8 V - * @param Addr: I2C address
: P+ ]9 K/ H6 ]' ?0 w8 o" ~ - * @param Reg: Reg address 4 d0 u/ [. D# Z& W. h# p
- * @param Value: Data to be written $ m4 A: ^3 q! u, A" X$ r
- */
3 `3 [( F, x: |7 l2 t6 {3 y! w5 E, j - static void I2Cx_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)
T) q9 G7 T. q3 ?( d4 b7 J - { 6 x6 B1 P1 R Y0 j. `
- HAL_StatusTypeDef status = HAL_OK;
" L+ ~' U: B: O - * T1 S! P* x" ^* P: u- ^7 v
- status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);
: e( V Q0 n; g" N - - c) r6 R ?& Y( V! L
- /* Check the communication status */
. {) A8 _$ j% \2 o' _ - if(status != HAL_OK)
& |, L. b' G1 }6 x - {
. b) G$ O9 r* |( K4 u4 p$ C1 g - /* I2C error occured */ & E0 \( q. i) w; u9 c# y- u2 s
- I2Cx_Error(Addr); 6 V' G6 k1 N! ^
- } 8 U/ Z: L- ^; X7 L: u9 m i
- } % d5 g: X5 P9 C+ E g$ s
- void AUDIO_IO_Write(uint8_t Addr, uint8_t Reg, uint8_t Value)
" j; N2 e) }$ z: v - { 9 z7 z" E+ n6 K
- I2Cx_Write(Addr, Reg, Value); 1 \& d4 w# ~. [( O. j4 j1 K) Y1 f
- } 1 h$ G: G6 ?& v" N0 i
- ! [8 w8 P( J: f" T8 {
- /**
' n# K; R$ \+ p7 W% | - * @brief Reads a single data. 8 `9 V% H) |: J3 E; V3 d" u
- * @param Addr: I2C address
4 o) A3 o1 q4 P - * @param Reg: Reg address ; n2 N% [! W2 y4 \, ?$ t! V/ ?
- * @retval Data to be read
2 L! m* Y+ K+ D# o* A* H - */
' g0 v9 \; D, A8 f+ |) `7 c - static uint8_t I2Cx_Read(uint8_t Addr, uint8_t Reg)
9 S$ u& \ d' x: i6 X" q2 b) \ - {
2 b, s$ H, D9 b$ D) G% w9 x3 C9 E - HAL_StatusTypeDef status = HAL_OK;
7 T; C4 ]* u) n7 ?& E2 p - uint8_t Value = 0;
' l. t; X( X" o$ I! {0 ^: G3 w# N -
+ o. K0 X' e3 t- M% F - status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, I2C_TIMEOUT);
. Z$ c3 {1 ?; X - ( j+ P$ V8 x B$ X) n. t
- /* Check the communication status */
% x0 v7 {5 ?& D. [2 ^: L - if(status != HAL_OK)
6 W% s e# ~7 T& E# Z3 i, z - {
8 }# q9 I. A/ X4 H. J5 u& O - /* Execute user timeout callback */
5 I1 _7 ?7 y8 e9 s' G - I2Cx_Error(Addr);
; n, g# }% Y/ e - }
p7 R! e$ B2 U$ v n -
( h: N i9 x4 p9 Z% l+ r! z& O3 K - return Value;
2 H6 T, C) U: D* k- y. v0 O - } 2 w J9 |+ q- x B% T) T! L
- uint8_t AUDIO_IO_Read(uint8_t Addr, uint8_t Reg)
5 g9 X$ @% p* o5 c - {
- Y- s; r8 U7 f( f7 L+ m5 Z- w - return I2Cx_Read(Addr, Reg);
& S) o' O( t' A! n" z - }
复制代码 由于在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- [cpp] view plain copy
5 g4 E M2 X3 G, q4 U/ r - static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options)
0 x7 W7 i" a' I/ c( O - { * O5 F) c4 f; |2 y6 t) ]
- /* USER CODE BEGIN 0 */ " U: z, [0 y7 }
- return (USBD_OK); 7 \( a) ` e% [2 V8 a `
- /* USER CODE END 0 */
! Y5 X3 |. [0 k+ Y/ P& W9 j - }
复制代码 这是个空白函数,它是在USB枚举结束并收到set_configuration消息时会被调用到,我们利用他来实现对codec的初始化。在bsp_audio.c文件中,我们添加一个函数,如下:
% d5 s4 w% Z0 t' p# N- [cpp] view plain copy
! C, e1 h, C9 k5 V) X$ v2 U$ S - static void I2Sx_Init(uint32_t AudioFreq) 2 p% C1 h: _+ x7 ]
- {
0 F m- o% d% K - /* Initialize the haudio_i2s Instance parameter */
8 E. U3 A; S- \7 ?9 P - hi2s3.Instance = SPI3;
) ^& W+ [ g, ~. a0 w' g. @( R -
5 v' [* g$ R' p- m. v3 w# C4 M - /* Disable I2S block */
% z1 x- G8 |: X - __HAL_I2S_DISABLE(&hi2s3); # p1 O% G% | Y4 j
- 7 b7 r. O7 ~0 z9 h. E: ?
- hi2s3.Init.Mode = I2S_MODE_MASTER_TX; 8 D* K' f& p! f* y
- hi2s3.Init.Standard = I2S_STANDARD; 8 o" p0 W) X t+ M
- hi2s3.Init.DataFormat = I2S_DATAFORMAT_16B;
! F( b- c" e* t6 R$ S, S - hi2s3.Init.AudioFreq = AudioFreq; 1 P: d4 }/ Q. } ]
- hi2s3.Init.CPOL = I2S_CPOL_LOW; , z2 q# b5 v' i0 B# l# T; f
- hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE; : x& V1 J4 W; O8 X* s: H5 ?; }
-
1 h3 r& @+ h9 U) t D - if(HAL_I2S_GetState(&hi2s3) == HAL_I2S_STATE_RESET)
1 t0 i Q. d0 O2 V0 W c - {
3 n6 }5 r0 r+ i9 U% D - HAL_I2S_MspInit(&hi2s3); 0 M% o P. q) {
- } # Z6 S; h/ J8 R& p/ A
- /* Init the I2S */
5 z( ^- p+ c' v% G$ a6 ] - HAL_I2S_Init(&hi2s3); 6 ^7 T3 o6 j: ^. M; k( T; e1 K h0 g
- } * U' I/ g- C% G, e8 P& s& ~
- const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000}; ) M" _ f! t# i
- const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344}; ' u+ l- Y, i& s
- const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1}; 3 `/ G2 h Y$ a6 h0 h) f
- 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
- {
6 \) a& ]4 `0 L1 `) H; Z - uint32_t deviceid = 0x00; : Z# p3 n7 N {
- uint8_t ret = AUDIO_ERROR;
: r' n' I6 F8 c* b( |1 w3 i - uint8_t index = 0, freqindex = 0xFF;
" W. Q* M& i7 h% B& ]2 L2 L. S6 F+ z - RCC_PeriphCLKInitTypeDef RCC_ExCLKInitStruct; ; q! w ` A7 ^. M5 W# V) {& y, y4 e
-
- `+ I& C9 q8 W! X7 x" ]* y- ?) |% } - //get the according P,N value and set into config,this is for audio clock provide $ b$ q- R% t: d$ `
- for(index = 0; index < 8; index++)
, N# V8 f% _2 O3 r0 m) x4 I5 V+ e - {
- b. o9 F" B4 M9 y+ b - if(I2SFreq[index] == AudioFreq) 4 [7 t0 X, y) e9 i' B" f6 N- B
- { ) B8 T- v: ^) A" o2 e! p* h" Z
- freqindex = index;
8 R4 r* Z& C. X" p6 P! [: f) I - } 0 g1 S; y9 T O! i, w# q+ c5 j. J
- }
: _& K n" m' n+ G' m# v7 c - HAL_RCCEx_GetPeriphCLKConfig(&RCC_ExCLKInitStruct); 6 [5 E0 b4 p& g g
- if(freqindex != 0xFF) 0 e. l9 S4 z ?' g+ {+ P/ z$ {8 e
- { 0 n u# L# n0 o$ x4 `: l+ I
- RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
* g4 z& j/ w7 b$ s - RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = I2SPLLN[freqindex];
$ C! z# c% B3 a! Z. ? - RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = I2SPLLR[freqindex]; ( p' t+ q3 P2 w8 M
- HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct); 9 Y: C/ I4 ]8 o+ H
- } 7 K8 r2 N- b, \6 d/ K' m7 @ k4 u
- else $ U! `" t5 U. ?9 u" _
- {
3 D* m& s' M I1 q - RCC_ExCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
; y6 r# `% J' f8 | - RCC_ExCLKInitStruct.PLLI2S.PLLI2SN = 258;
9 I$ w6 @. Y8 u3 c - RCC_ExCLKInitStruct.PLLI2S.PLLI2SR = 3; ; |8 U/ O7 I" C0 w
- HAL_RCCEx_PeriphCLKConfig(&RCC_ExCLKInitStruct);
6 O+ R9 i5 F# M& v E8 ~ - }
6 q4 R1 M2 u. { -
, D" U; G& W/ d# t- m - //reset the Codec register
' I2 L! R/ ? S! S: ] - CODEC_Reset(); 5 j( I# z$ }5 F
- deviceid = cs43l22_drv.ReadID(AUDIO_I2C_ADDRESS);
& C- @# Q1 N% ^5 a - if((deviceid & CS43L22_ID_MASK) == CS43L22_ID)
/ a7 u }( j/ U2 W# l - {
7 c' ~% P8 U n* J- U4 g - /* Initialize the audio driver structure */
. ?; A7 T1 V4 p. P+ i9 u - audio_drv = &cs43l22_drv;
) d' O1 X- ~# k N6 l) n; y - ret = AUDIO_OK;
) d# j7 d) t- Q3 R4 ~ - } + _9 d& Z* h0 d1 R8 Z; p0 E
- else 7 j, N* W, S2 W W
- { 3 T4 q2 @: W, c4 p
- ret = AUDIO_ERROR;
/ R" S3 V }/ j- I& ] - } 0 e6 ~) s( L2 v: L" |' l" Q% p
-
8 {8 \. |2 ^, Q8 L - if(ret == AUDIO_OK) * c. Q4 l/ U' f
- { . l$ M' G% d. a( t' N$ _) N
- audio_drv->Init(AUDIO_I2C_ADDRESS, OutputDevice, Volume, AudioFreq);
7 w) s' O' }" v7 J9 v3 p - /* I2S data transfer preparation: ; ~7 ]9 X6 o+ \; h% A6 A3 z
- Prepare the Media to be used for the audio transfer from memory to I2S peripheral */ ' ?- q; A- ]. @0 p R% o
- /* Configure the I2S peripheral */
i* o1 Q, Q4 H2 V/ J8 g! ~9 c - I2Sx_Init(AudioFreq); 3 C8 Y; I" r$ w! f5 B9 U7 ]
- } 1 i+ @, ?' j! ]5 Y2 t0 W
- return AUDIO_OK;
, m. t/ j) w- g. ?& t H( V7 |0 V0 P - }
复制代码 在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
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
/ 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
- [cpp] view plain copy2 T& J( j1 J# `: I* h. ?
- static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options) % I5 W* n& C6 ?2 k6 r( P
- { 4 [# |* q9 d! S9 {
- /* USER CODE BEGIN 0 */ . @& Z, Y/ i3 U& u8 p5 A
- BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_AUTO, Volume, AudioFreq);
% ^+ `0 s1 k y2 f) ? - return (USBD_OK); + H5 {; E: [1 D7 ~. ]5 @1 {
- /* USER CODE END 0 */ ; @( V( `+ L9 C' [7 _
- }
复制代码 接下来下一个需要对接的接口:
9 |" q) s. f1 s5 N+ Z- [cpp] view plain copy" C9 U$ m% [7 G' f
- static int8_t AUDIO_DeInit_FS(uint32_t options)
2 [' }6 G( X1 I. A* k - { p1 i( W' e) D* E% i; s9 e- F
- /* USER CODE BEGIN 1 */
( { h- o% R: u4 ^) a( Y3 _6 Q T - BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
/ W, n! C* q( ^5 I - return (USBD_OK); ) u3 y: _- [" d& n
- /* USER CODE END 1 */ & S$ ^( b8 K$ c- `4 l P
- } ' t* h" R2 X+ f( H w( X6 B8 F+ m
- 很明显,这个个反初始化的接口,它的具体实现如下:
2 m; I/ _6 I7 {9 r" a
1 v4 L6 l; h4 e/ B! b* W" s% G9 c- [cpp] view plain copy
) U& s( [' K/ i. i- W) ?5 H - uint8_t BSP_AUDIO_OUT_Stop(uint32_t Option) / B8 R0 X( x9 [
- { : u/ Y1 c. z7 \1 W9 `' O
- /* Call the Media layer stop function */ 7 l- F2 H3 |9 H3 _1 ]3 x
- HAL_I2S_DMAStop(&hi2s3);
+ R6 n3 N: t! R8 ^ -
7 F& ^, O/ T3 a3 f7 H1 ] - /* Call Audio Codec Stop function */
: I! w0 c2 u1 n4 J! G* N; i$ N - if(audio_drv->Stop(AUDIO_I2C_ADDRESS, Option) != 0) 7 ?1 O4 d- P# {1 o( v# a! V. h! J( f/ a
- { 9 v7 Y! `) ]6 n5 L# n
- return AUDIO_ERROR;
' G+ Q* C- z+ Y8 X - } |: F$ V) R2 Y$ {' t
- else
& u" y7 a @0 h( p2 @* h( l$ ?$ W - { : \* r' Q4 ^ G
- if(Option == CODEC_PDWN_HW)
( z! | S) x/ M. U3 t6 j$ R G2 ^ - { ) {. f& x5 B5 x2 f( U$ J Z2 e3 l
- /* Wait at least 1ms */
. b( E% b( `% S. s - HAL_Delay(1);
# z+ K/ n2 g5 |6 m, I# | - 0 W1 ]& O+ W% K! x
- /* Reset the pin */
6 U; z- T( ^/ S7 @' Z6 }3 } A! i7 c9 b4 N/ ? - //BSP_IO_WritePin(AUDIO_RESET_PIN, RESET);
$ @ H( |; {1 j. d( [# z3 U - HAL_GPIO_WritePin(AUDIO_RESET_GPIO_Port, AUDIO_RESET_Pin, GPIO_PIN_RESET);
; }+ V8 y# \2 T/ s1 { - }
$ k" J$ ?! d4 v u. }) m t( t -
# Z: L5 j% R# z6 w - /* Return AUDIO_OK when all operations are correctly done */ 3 w3 Y0 T( z: S2 h& R; C3 |$ f
- return AUDIO_OK;
3 c# t7 u4 { \+ Z. C4 w; F - } 7 E4 {" G. @2 Z
- }
复制代码 先关闭I2S的DMA,在调用Codec的停止接口。
% P/ V% X2 F! ?( t& I* p) N: w" {$ NOK,下一个:$ k, ]9 {" X% T5 Y" C
- [cpp] view plain copy _, Q; m; {: c4 B/ M
- static int8_t AUDIO_AudioCmd_FS (uint8_t* pbuf, uint32_t size, uint8_t cmd)
0 m( O' q) i6 g9 c - { 0 S9 _3 g, F; x3 Y
- /* USER CODE BEGIN 2 */
- u( y+ ~) p' g, M - switch(cmd) . ]: y k+ a, _0 [2 v. B4 S
- {
z! G) y! s: Y1 B - case AUDIO_CMD_START: 7 c5 E4 R* J8 f
- BSP_AUDIO_OUT_Play((uint16_t *)pbuf, size); . A( ?$ {, o* ]5 {; {4 Z6 a8 v
- break;
1 @/ `9 P" p, \; W3 t4 ^ -
3 a! n/ S, y" C5 N3 ` - case AUDIO_CMD_PLAY: + d; W* S, n: G, d0 \
- BSP_AUDIO_OUT_ChangeBuffer((uint16_t *)pbuf, size);
) I3 w. C2 R$ ~2 h2 ^4 i0 J2 F' Y - break;
* C- w$ b8 V* c0 [% K - }
0 N, f F K% p. c% T - return (USBD_OK);
9 R6 m# k) T6 ~ - /* USER CODE END 2 */ 6 J2 L: h: z0 T6 W3 a1 C: \ a* n* L
- ! F+ | S5 b: Z4 |( o. G) D+ g0 o
- }
复制代码 第一次USB audio stack接收到USB OUT数据时会回调这个接口并传入AUDIO_CMD_START参数,这里的处理代码是:% o4 ^# K2 U I" ~6 a
- [cpp] view plain copy3 ^. ^) W1 {0 @7 ]; l+ r
- uint8_t BSP_AUDIO_OUT_Play(uint16_t* pBuffer, uint32_t Size) / _- q( v, I& a$ S, }' v' v
- {
- G* Q/ }; R) [1 [# }3 \ - /* Call the audio Codec Play function */
0 R, Q7 ^4 |+ j* V - if(audio_drv->Play(AUDIO_I2C_ADDRESS, pBuffer, Size) != 0) 7 h1 w, t! B1 C2 J
- { 8 j) U; }; H- s; M+ c E3 n M2 o
- return AUDIO_ERROR; - I7 T% e2 V8 Y* }3 A5 v( K
- } # m; o0 w! r1 }1 c) e' P5 v3 j
- else 2 }7 t h2 U0 `! J
- { 1 u3 W E* S, M: D% ^, |4 Z5 Z
- /* Update the Media layer and enable it for play */ . m+ j( B8 Z5 v O5 {; n; _; }7 y/ N
- HAL_I2S_Transmit_DMA(&hi2s3, pBuffer, DMA_MAX(Size/AUDIODATA_SIZE)); 1 I! K) J9 W" h( I H7 L/ V
- return AUDIO_OK;
3 z, j: s. g0 J) U$ o; t - } ; S0 K, l2 u% e0 w+ f* F o4 B
- }
复制代码 很明显,它是调用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- [cpp] view plain copy: d- {$ P. {* j, F7 V4 u$ M
- void BSP_AUDIO_OUT_ChangeBuffer(uint16_t *pData, uint16_t Size)
$ a# X/ ?2 V% V: V6 T - { " c1 Y' t9 U3 k2 K3 F: h0 c4 i2 g
- HAL_I2S_Transmit_DMA(&hi2s3, pData, Size); 0 q" C8 o6 x9 T8 }3 `4 I
- }
复制代码 也是通过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
- [cpp] view plain copy
! K6 c* C8 ]+ v/ J* `1 G9 a - void TransferComplete_CallBack_FS(void) ; W- u: O/ o+ H3 x4 _( h
- {
5 {5 ? e5 ^, m9 A* F" I# H% ^ - /* USER CODE BEGIN 7 */
' E4 r, ^$ { j9 d8 V9 g! t! h s, B - USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL); , R6 U8 y- p& a1 z/ y" x
- /* USER CODE END 7 */
3 ~/ Z! W) Y0 c# K# U" h6 s - } 9 ~) H S C4 y F e0 l, K' J
- void HalfTransfer_CallBack_FS(void)
$ r+ b- y: Y' U! J4 \6 x9 }( f - { / q3 L4 ?( q2 a0 \; f" p# @
- /* USER CODE BEGIN 8 */ ( A$ |3 l% l# d* z+ E# S0 S
- USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);
) V ]# s: [/ P - /* USER CODE END 8 */ & H2 {0 m2 k: g* Y' S! f" }
- }
复制代码 此代码为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- [cpp] view plain copy
% r: D( `" u$ F' ?' w% o5 q+ ?9 E - static int8_t AUDIO_VolumeCtl_FS (uint8_t vol)
( w7 _: Q: B+ }& N - { 3 g, d( i' F2 v0 f* V2 G
- /* USER CODE BEGIN 3 */
7 t) h- d6 q- [, z* R8 a* F" i - BSP_AUDIO_OUT_SetVolume(vol); 7 f, ~( @- s$ G, F- @5 \) l
- return (USBD_OK);
$ Y: w( H; k/ m" h - /* USER CODE END 3 */
% e7 d q/ G- r3 q: {. B# e - }
复制代码 很明显,这个是音量控制接口,也对接下:7 J- P: M1 P1 p2 ^* y, v1 r8 V
- [cpp] view plain copy3 r- F& b7 W# _% H& T+ l5 O" G
- uint8_t BSP_AUDIO_OUT_SetVolume(uint8_t Volume)
6 n' N4 \1 }) z! s0 ]/ r - {
& p8 X6 T9 i3 }; E9 o0 k - /* Call the codec volume control function with converted volume value */ ( {# z! m2 M$ M- _( m; F* S+ w
- if(audio_drv->SetVolume(AUDIO_I2C_ADDRESS, Volume) != 0) 6 }# m- P! `: O& `
- {
+ ^: q; q5 o. }. j% I - return AUDIO_ERROR; / E }+ j& E2 R e: B
- }
7 e" {7 r- M" _$ a - else ' U( m; u5 o" ~/ q" o3 V" r
- { 3 X" ?! S; {8 L; K+ a3 X- t
- /* Return AUDIO_OK when all operations are correctly done */
4 j" S6 d; D" x# @: r9 i - return AUDIO_OK; 2 w0 S' S: P3 [: f
- } 7 W, d3 r z/ {+ \
- }
复制代码 直接调用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- [cpp] view plain copy' ?+ U' W3 f( z2 a! m8 @1 L
- static int8_t AUDIO_MuteCtl_FS (uint8_t cmd)
( V8 [" P4 M. B, m - {
" }- L- E2 y3 I" P+ z - /* USER CODE BEGIN 4 */
6 V$ b, N9 T) B# \ - BSP_AUDIO_OUT_SetMute(cmd);
6 S& |3 [( f5 r: k - return (USBD_OK);
( `% A8 B. Z0 f) h( X$ m - /* USER CODE END 4 */
, i$ r! i* Y+ n' @ - }
复制代码 静音控制,其实现为:; p5 s3 H4 Z% R& q0 s2 t
- [cpp] view plain copy3 C' L5 O% J' [2 h& B" t" T' @: Y
- uint8_t BSP_AUDIO_OUT_SetMute(uint32_t Cmd) 7 O. R3 v% }9 Z" V" h7 Q2 C
- { 9 o, a4 ?5 V) W2 K
- /* Call the Codec Mute function */
" L& ?3 L6 @2 } - if(audio_drv->SetMute(AUDIO_I2C_ADDRESS, Cmd) != 0) % e7 ?: H' x$ D5 T, Y d% ~3 l0 T l
- { & V1 R# T7 g9 r; a2 R8 S
- return AUDIO_ERROR;
# Y7 K& W `# O3 \ - }
- ^( D5 X# ^# G8 B5 p# k - else
8 h' u# Z" Q. `" L' [0 Y - { + t* B/ o# R( y
- /* Return AUDIO_OK when all operations are correctly done */ " N3 G% N9 e: o5 K0 L
- return AUDIO_OK;
2 P& T% D4 m: X/ V% g - } , _, Q1 G( x" c* _ C
- } # ^; 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
. |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 ` |
static uint8_t USBD_AUDIO_DataOut (USBD_HandleTypeDef *pdev,3 C6 N9 N! y% t6 I, j
uint8_t epnum)
{% d5 n7 f4 g0 q1 t, j; `/ n1 g
USBD_AUDIO_HandleTypeDef *haudio;
haudio = (USBD_AUDIO_HandleTypeDef*) pdev->pClassData;
1 b( S8 T* J0 L" R# T
if (epnum == AUDIO_OUT_EP)
{, 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
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;
((USBD_AUDIO_ItfTypeDef *)pdev->pUserData)->AudioCmd(&haudio->buffer[0],
AUDIO_TOTAL_BUF_SIZE / 2U," B! @3 ?8 m/ N; H( h/ R8 J; @
AUDIO_CMD_PLAY);
#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],
AUDIO_TOTAL_BUF_SIZE / 2U,
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
if(haudio->rd_enable == 0U)
{; Q1 R% U+ D, A$ j! L& `
if (haudio->wr_ptr == (AUDIO_TOTAL_BUF_SIZE / 2U)), |. h0 y1 o3 g! l
{
haudio->rd_enable = 1U;
}3 H# \! ]- @/ ^
}
/* 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);
}
我参考了您的这篇文章,在 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 的执行过程没发现问题,
而且这个函数返回值也是OK,但是紧接着 AUDIO_DeInit_FS 就被调了,也跟踪了 USB 中断和 DMA 中断都有,查了好几天查不出原因,时钟配置好像也没问题,楼主您能指点一下吗?多谢!
出现这种情况要保证描述符是不是正确的,如果描述符正确在看看内存是否溢出,如果都正确,那就要一步步找问题了,就是把个单片机外设相关的代码想注释掉,基本上就找到原因了。
希望能有帮助
希望能有帮助
非常好的帖子,谢谢分享!!!
: x; B% m3 k3 a! T9 i" H
驱å¨å®è£ 失败
这个问题您解决了吗,我这也遇到了这个问题,有个小感叹号,然后提示
1 c/ i6 ?- Z: T- q
该设备无法启动。 (代码 10)
' s& K! e9 T. B: @6 L
I/O 请求已取消。1 Q8 |$ w5 G0 h( @6 n0 J
经测,不要用最新的库版本,用1.21.0的测试直接生成,正常识别!