前言$ o/ \0 ?4 ~+ m X
在音频开发中,I2S(Inter-IC Sound)接口被广泛采用。大部分 STM32 集成了 I2S 接口。本文主要为了让 STM32 使用者了解 I2S 音频接口,及快速实现 I2S 接口的音频应用开发。 首先,对 STM32 的 I2S 接口进行简单介绍,然后描述了几种常见 I2S 音频应用架构及每种架构音频部分的电路图,最后围绕每种架构给出实现例,以便读者进行参考理解。其中,实现例会围绕 STM32CubeMX 展开,以便开发者能够参考并快速、简便地实现软件开发。除此之外,在 Cube 软件包中有 I2S 外设应用例程,提供了更完善的实现参考。, q/ I: g9 m4 e7 F5 M) m/ `
6 C" Z, P; A7 W, u一 STM32 I2S 接口简介$ C: y" g$ e3 W( y/ U
I2S(Inter-IC Sound)是飞利浦公司针对数字音频设备之间的音频数据传输,制定的一种总线标准。3 d+ m3 ]* _5 S) ]1 d! _7 @( {
STM32 I2S 接口信号线构成如下表:
; C5 k6 k$ B% q7 N: }
* s3 h4 U( K+ W- x# M4 ~
% i9 z( n3 d% u+ T4 h
6 _, _# A; G8 F6 @3 B0 A9 q
其中,SD 和 SD_Ext 信号线可分别配置为发送或者接收。在 Cube 驱动库中已对其进行封装,例如当配置 SD 信号线为发送端时,SD_Ext 自动被配置为接收端;配置 SD 为接收端时,SD_Ext 自动被配置为发送端。
* d8 o- b( j9 |; ]2 k V* R全双工 I2S 是由两个 I2S 外设组成,如下图所示。; k9 v5 J2 R' w. u6 A" l: `, o6 i: ^9 T
5 O( L* y6 K0 u% l1 f2 z
/ E I1 n# N) i. u% P; ?- {$ [. {& j
对于构成全双工 I2S 的每个 I2S 外设,都具有单独的寄存器组,如下表所示(以 STM32F413xG/H 为例)。在 Cube 驱动库中,全双工下的两个 I2S 外设操作已经被封装,用户只需像配置一个全双工 SPI 一样,对一个全双工 I2S 的 API 进行调用即可。后续会以实例形式进行描述。
' Y6 S0 x# m3 ]7 E外设 寄存器地址空间
* g3 |+ _. Y4 g9 j. H& r& N
- ~- J: H d' D" W9 M
: B7 w- a5 x0 |" ^0 s7 k5 K& _
$ ^( [; U7 Q" u2 t5 w
STM32 I2S 支持四种接口标准和数据格式,如下表。更多内容请参考对应型号 STM32 的参考手册。
" K6 d7 z9 J9 ~# Y6 [* o# v
8 [# T2 ]9 Q6 |
" ~' S, Q# h/ Y$ j6 G1 D2 i( a: h, f% d, z3 |0 y) a: E
由表可看出,STM32 I2S 支持音频分辨率可为 16,24 和 32 位。I2S 时钟配置及数据格式选择决定了音频采样率,时钟产生架构如下图所示。不同系列 STM32 I2S 接口能够支持的最大音频采样率有差异,更多采样率支持情况请参考对应型号STM32 的参考手册。
9 h$ z8 C8 Y3 T' I! C1 C# q. @4 J3 H$ ]6 s: j+ G. G
% b% a- }: X# T4 k6 Y/ X
2 q7 \: J2 W+ {! x( m图中 MCK、CK 分别对应 I2S 总线上的主时钟和总线时钟。其中 I2SxCLK 获取路径如下图所示(对应于右侧的 I2Sclocks)。红色线路或者绿色线路可选,本文中以红色线路为例,利用 PLL 时钟源获取 I2SxCLK 时钟。
7 n6 |3 G" p" B- o注:下图是 STM32F429 时钟配置图的部分。不同型号 STM32 的时钟树存在差异,具体以实际采用型号的时钟树为准。2 A2 Q, ?4 @% y
) n6 T5 Q4 _: O4 {1 K! J" B
! s& n% p& D5 M! G* C
5 s) z& N6 K9 h( g在遵循 I2S 标准的实现方案中,采样率公式如下 (注:Fs 为采样率,得益于 Cube 驱动库中的良好 API 实现,可以直接设置采样率,使用者不需要按照下述公式进行 I2SDIV 和 IDD 的计算及配置。):
4 \2 `/ Q4 R) o
* @6 Z& O8 a* V1 I5 _
# [$ X) x3 |+ j- ~' m( c5 S
6 q! r: @% o7 k& T" L% c# M
上述采样率公式不能直接用于 PDM 输出的 MEMS 麦克风,通过后一节中介绍可知,PDM 麦克风访问只是利用了 I2S 的数据和时钟线,并且在采集到麦克风位流数据后,需要经过降频操作(PDM 转 PCM,ST 提供了 PDM 转 PCM 库支持,更多介绍可参考 AN3998),从而获得 PCM 数据。所以,在这种情况下,主时钟配置为失能,数据位宽需要与帧位宽相同。折算后的采样率为:
, D! g/ Y+ g1 q1 B! h( ~3 A5 j1 X% K3 b' V. ^# c
/ y9 u- r' i6 L; ]
- b5 E+ c# W$ N: \/ ?1 l7 N% [
0 x+ a+ b c% c/ @$ f. l其中,DIV 为 PDM 转 PCM 的降频因子,由调用的 API 决定。5 _. R" B1 d4 i
$ [$ b" s9 N. w4 s9 R# D" n" [- ~& {( B9 {$ r
) |& q7 b' T( Y
二 常见 I2S 接口音频应用实现. g l. c0 ~$ d* c! o! P# a
I2S 接口应用相对固定,整理两种音频支持结构如下。
7 @0 B! K( ]- H6 e& R; e# p/ [! w! V/ d! q6 F# F! K8 k1 W, n
T7 z2 V" c4 }2 Z7 D
+ q0 i6 ^3 Y( ~- `9 x% {, q- m1 J其中,麦克风与播放器功能的实现相互独立。可根据实现需要决定采用的实现架构。& ?0 M( `1 o0 l B$ Y! ^$ a# I+ v
实现 1 参考电路如下图。原理图摘自 STM32F413H-DISCO 板,可在 ST 官网获取完整的原理图及 BOM 表等资源。其中CODEC_MCK、CODEC_CK、CODEC_WS、CODEC_SD、CODEC_ext_SD 分别对应 I2S 的 MCK、CLK、WS、SD 和ext_SD 信号线。& d. {9 _1 L0 C7 _( }* V3 {: P
/ W, D* l. H/ f2 L2 R# A
5 v8 O R0 D/ c6 U
, ^: R. l" \. _+ D
* O$ b$ R+ E% s/ V: _实现 2 参考电路如下图。其中单麦电路和双麦电路同时存在仅为读者参考理解,实际开发时可根据应用需要选择单麦或者双麦实现。原理图摘自 STM32F411E-DISCO 和 STM32429I-EVAL 板,可在 ST 官网获取完整的原理图及 BOM 表等资源。
; X3 V( v; z. a5 p i8 w8 q# t5 M; {/ _8 u4 K+ |
) ]6 q' H' I, ?4 w4 t4 K% x" E8 N9 c
' h3 U' C, W- |3 S! [2 ^, h* b- K* E, L% |, K w5 a4 w
在实现 2 中,直接采集麦克风数据。市面上 MEMS 麦克风有 PCM 输出和 PDM 输出之分,其中 PDM 麦克风由于内部结构相对简单,成本更低,被大量采用。图中 MP45DT02 和 MP34DT01TR 都为 PDM 输出的 MEMS 麦克风。PDM 数据不能直接使用,需要经过滤波,降频等操作获得 PCM 数据。! k% B! C4 S9 Z S# s( X
另外,I2S 对双麦克风的支持需要结合定时器及 2 个 IO 复用引脚。实现框架如下图。! R5 ` |4 _( v; }- Z( }/ ?% M! m
1 H0 H/ y+ G! i. c1 R0 D+ A7 m6 }1 d- c8 T9 G
% j+ S* v# f, R5 J" v5 W8 w1 r# s2 k
' K. W/ e; a* E8 j; n/ S1 h% r, }5 y, }
5 s) ^' q2 ^0 q8 z& k通过定时器对 I2S_CLK 信号进行两分频输出,然后将获得的信号提供给 MEMS 麦克风的数据线。实现时序图如下所示。依据 I2S 标准(Pilips 标准、左对齐标准和右对齐标准)时序, I2S_CLK 的上升沿获取数据。而对于文中提及的两种 MEMS麦克风,输入时钟(TIM_CLK_OUT)的下降沿使得左通道麦克风(LR 引脚下拉)输出有效数据,右通道麦克风(LR 引脚上拉)数据线进入高阻态;输入时钟的上升沿,左通道麦克风数据线进入高阻态,右通道麦克风输出有效数据。从而实现双麦克风采集。) [1 d( ?5 V% Z1 b
* R$ D7 l9 ^" M+ ^4 s
' j0 v' B g- ]2 s8 @. ~2 H+ x, l3 j
! p* W# W" _8 q; R N) |
' V$ m6 a' U4 S' x! b三 应用实现例
! ^0 t6 ~& v0 \$ I" \7 f3 c本节围绕上述介绍的两种典型实现架构,结合 ST 评估板,介绍 I2S 接口应用在 STM32CubeMX 工具上配置实现,以及在生成工程后的 API 调用,最终实现基于 I2S 接口的音频数据传输。利用 STM32CubeMX,能够更快的实现针对自定义STM32 平台的开发。实现流程如下。3 s1 }8 N+ |: U' Y w) m
( u* m; }8 e# X- ]. K# B' S3 M/ j
2 n9 V z- j% K. f: M7 Z$ B
3 G6 V; Z8 O P9 n% }
4 A x$ {6 o8 N# C" U0 ?% a+ [3 j4.1 前期准备# S& {* q, c- _- Y' d+ C
; F! P/ `: L& N1 J9 Z
- E4 F0 C8 u& b# V$ J6 L: j4 a+ M! R7 W% R" U5 L; A6 N
4.2 应用实现( k. r8 Y3 X6 }+ J
4.2.1 实现 1
: _' n5 @3 p0 R& m ~0 p结合 STM32CubeMX 的软件开发流程如下图。6 J/ U! B1 D+ p5 M& P
* w" {2 C8 u1 x: x3 A
+ A. Z& q+ b/ o; i, y# J& j
- v5 n: ~' O4 G9 q0 H3 x9 |. V
8 c% B1 {! U( e9 O/ J Z$ Q! f接下来一步一步呈现实现过程。% N) x; {/ J. a! o
步骤 1:在 STM32CubeMX 中根据硬件选择 STM32F413ZHTx、外部时钟、调试接口、I2C 通道和 I2S 通道(利用 I2C接口配置和控制编解码器),如下截图。硬件电路原理图可以在上节的链接网址中获取。其中,I2S 工作于主模式0 x( b& {5 I, ~/ D9 I
9 y( @( S! [+ }6 Y# V
9 N M' N# W0 t4 L* t- F
8 d% G6 f$ g5 X$ x; ~' ^9 H* _- Y
1 V; u; S4 J8 M, C0 a
选择各外设后,由于外设功能可关联到不同的引脚,自动分配的引脚可能与硬件连接的引脚不一致。此时,可以在需要重新关联的引脚上按住 Ctrl 键+鼠标左键按下,出现支持相同功能的全部复用引脚,将其拖动到与硬件设计一致的引脚上。如上图步骤 5 所示。
3 B' w+ s8 D9 H( D, d* P5 L* N: t3 t步骤 2:时钟配置。时钟配置涉及环节较多,STM32CubeMX 提供了便捷的时钟配置实现,如下图,只需简单的几步,即可获得最高主频运行的时钟配置。需要注意“ Input Frequency ”值,应保持与外部高速时钟一致。& h+ O* v* I% _- i- ~
尽管在上述 I2S 接口介绍中, I2S 采样率与时钟配置有关联,但在 HAL 库实现中会根据 I2S 中的采样率参数,自动完成时钟参数配置。/ D3 j% {' i e' T8 {) m
- H- B3 @! p1 c; y' @" M1 I+ ?
+ L- ^5 N% M* X' [% H4 @8 `# R
. B4 z; _$ D! h7 ?0 F- k: {1 l" }2 B
步骤 3:I2S 配置。点击切换到 Configuration 标签页,按照如下步骤进入 I2S 配置界面
# n' G# J4 z- C7 j$ Y, y
- q/ n- t! S3 A; j
& l7 J. G H; \& f3 q8 z2 h' k
% K9 L6 p0 { u, k* dI2S 参数配置。配置后截图如下。9 ^- M0 B" m+ M! @# y" \* u( b
% U( v( q6 P0 ^9 K" w
+ `$ }$ j) `' x( x# p6 ?
Transmission Mode: 传输模式。决定 SD 数据线传输方向(SD_Ext 方向相反)。根据硬件设计, I2Sx_SD 向编解码器输出数据,所以选择发送模式。
+ |4 i( U" O) r2 V5 R6 SCommunication Standard: 传输标准。本文中采用 I2S Philips 标准(需要利用 I2C 发送命令配置 WM8994 工作于相同标准)。
4 V$ p- d/ @& [8 p+ {% _( bData and Frame Format: 数据位宽和帧位宽。如同“传输标准”的配置,保持与编解码器配置一致。
$ }' W' W* n! S6 i9 vSelected Audio Frequency: 音频频率。可选频率 8KHz、11KHz、16KHz、22KHz、32KHz、44KHz、48KHz、96KHz、192KHz,这里选择 48KHz。如同“传输标准”的配置,保持与编解码器配置一致。
6 r* y6 G, @/ n, `% e3 @) v$ gClock polarity:时钟极性(非激活态时)。
; R) r) o; m6 i* kI2S DMA 设置。切换到 DMA Settings 标签页,按照下图步骤设置。(图为设置完成后截图。) f& A# h$ c; J! X
8 [& L: E0 n4 U$ l7 h1 x+ V: H# H. s( D* l. j& }5 E+ R0 `8 E2 g0 x% B
( h _9 `8 r/ P+ z
N& t/ r1 j/ f' p步骤 4:I2C 配置。点击 FMPI2C1 图标进入 FMPI2C 配置界面,参数配置如下图。参数介绍请参考对应型号的参考手册。% f& J, x. @9 w2 G! y' L0 V1 X* o
/ g) s* W: p, @$ r: Y( c& n5 |8 U# n
步骤 5:生成 IAR 工程文件。在菜单栏 \ Project \ Settings 打开工程设置界面,设置工程名、工程存放位置以及对应的IDE 工具(本文中采用 IAR EWARM)。其他保持默认,更多参数介绍请参考 UM1718。点击菜单栏 \ Project \ Generate code 生成工程。STM32CubeMX 生成工程中包含了时钟、外设等初始化。开发者可以在此基础上增加函数调用实现应用开发。3 R7 z, w2 H8 [* |+ v: J; m4 o
步骤 6:利用 IAR EWARM 打开工程,添加 API 调用,实现音频数据传输,具体步骤如下。3 i# W- r2 h1 @' k: ?8 \3 x/ F
1. 添加编解码器 wm8994 的驱动函数。为简化操作,直接采用 Cube 软件包中提供的 wm8994 驱动文件 wm8994.c,wm8994.h 和 audio.h(文件位于 STM32Cube_FW_F4_V1.16.0\Drivers\BSP\Components\wm8994 和STM32Cube_FW_F4_V1.16.0\Drivers\BSP\Components\Common)。其中 wm8994.c 复制到 src 文件夹中,wm8994.h 和 audio.h 复制到 inc 文件夹中,并添加到工程中,如下图。" G9 w4 L+ \7 d, B8 y
9 F3 U5 c4 f, R5 ` T8 {
% L5 {/ Q+ _9 T2 x/ j- s t# d
4 _: ^+ E) B5 N" Q! j4 d, V3 S: S# C9 b% Y5 h+ f
2. 按照下表增添应用代码。实现如下音频数据流向。为简便起见,直接将 I2S 接收和发送关联到同一个存储空间,并同时执行,在实际应用中,应加以优化完善,避免读写位置交错引起的错误。另外,由于硬件上仅有一路麦克风通道输入,尽管采用双麦克风通道配置,但有一路麦克风通道无有效数据,体现在耳机输出上,有一路无有效输出。9 n6 e2 @6 |* S- z" m& x6 T
5 O: V5 W/ o; u: ^; `2 |* Q% J1 m/ @ v, {# x5 J5 T$ ~: o' E
: F$ F9 ^( b+ N! J3 x( }
% I5 [/ d- r: O! x: {$ a3. 编译生成执行文件,下载运行。
- Z9 a3 x4 `& L* G& k# x注:下表中省略号表示,之间有其他未列出的代码部分$ K% {, z% k8 N$ X) c
9 S4 |# s$ @; f* U* X. \8 V: A n% N% _* {
main.h
$ _: |7 }: S" Q; N* M9 T" ?- /* 增加如下代码 */! e' \; g; c7 W3 U
- #include <stdint.h>
m- H+ j/ Y) W5 y - /**
- c0 Q3 } \- h2 W2 Z& | - * @brief Audio I2C Slave address$ R2 K2 i" U) R, ^% @! P* m
- */
: U1 p0 a9 ~; o/ G m. C - #define AUDIO_I2C_ADDRESS ((uint16_t)0x34)+ W$ X; ^7 U% @) \3 U2 e$ [7 G
- /* Codec output DEVICE */
9 j. A& v- |/ n } - #define OUTPUT_DEVICE_SPEAKER ((uint16_t)0x0001)& }: U) l9 s2 Z% I, _
- #define OUTPUT_DEVICE_HEADPHONE ((uint16_t)0x0002)
! c4 y& w4 w4 `0 M. k/ Z" u+ n! h - #define OUTPUT_DEVICE_BOTH ((uint16_t)0x0003)
( y' ^8 V4 R- n k: O. L9 f6 C - #define OUTPUT_DEVICE_AUTO ((uint16_t)0x0004). l$ g$ e) A Z0 X1 e& J' X
- #define INPUT_DEVICE_DIGITAL_MICROPHONE_1 ((uint16_t)0x0100)! t) |! X& U) m$ a& ~6 q
- #define INPUT_DEVICE_DIGITAL_MICROPHONE_2 ((uint16_t)0x0200)# y9 d+ Y/ g1 W/ B
- #define INPUT_DEVICE_INPUT_LINE_1 ((uint16_t)0x0300)# R! t) U* k. S
- #define INPUT_DEVICE_INPUT_LINE_2 ((uint16_t)0x0400)% y7 `) Q Q4 e0 q
- #define INPUT_DEVICE_DIGITAL_MIC1_MIC2 ((uint16_t)0x0800)- w, H! N$ t" F) W
- #define AUDIO_VOLUME 80
+ m( L2 N; m. ^6 I8 V: M5 m - void AUDIO_IO_Write(uint8_t Addr, uint16_t Reg, uint16_t Value);
9 z) A. A% a# _# g - uint16_t AUDIO_IO_Read(uint8_t Addr, uint16_t Reg);7 w5 r W7 u$ x6 j3 l" y: c
- uint8_t WM8994_Config(void);1 e! g! g+ |, D' J ]
复制代码
- B; L. b5 h- \. Lmain.c1 _. [* x' @, T/ X% i$ R( U' B* o
( U* Y' t7 k+ F! g/ p- ]- /* 添加 wm8994 驱动头文件 */# s3 _+ S6 V7 g1 [3 O) L5 D
- /* USER CODE BEGIN Includes */
) w) X/ b) t8 S% s; L - #include "wm8994.h"+ M P3 X$ h! R! P! t6 N, n# w
- ……3 R+ x& I6 r! ~: w. s5 a/ \! o! m
- /* 增加音频数据流暂存空间 */% U; A2 C u% Z
- /* USER CODE BEGIN PV */6 r; t3 r" r: t, l- Y" z) i6 J
- /* Private variables -------------------------------------------
( R# J+ P; G( i& j6 G9 }7 @0 D" p - --------------*/
. M8 q3 g! Z5 u- {( E% R6 r# } - uint8_t AudioBuf[2*2*48] = {0}; //2 CH*16bit*48KHz' W2 }! t$ ~/ ~, s& K; c& v; U3 M
- /* USER CODE BEGIN 4 */3 K; C6 ?& G' W
- /**, a# o9 O6 ~( C' I3 z2 p3 Y
- * @brief Writes a single data.
8 N9 I2 [+ L" i - * @param Addr: I2C address
* ^3 n9 F* Y- n; P( b - * @param Reg: Reg address. o- i' e o5 y0 [7 x! [ E' r/ ?# j. P
- * @param Value: Data to be written0 `" k/ Q. o6 M/ ~& v) B
- * @retval None
& G7 g4 U3 C3 i/ P3 z5 H - */5 X, Q2 m& n/ E
- void AUDIO_IO_Write(uint8_t Addr, uint16_t Reg, uint16_t Value)
5 c/ C: M; ^6 j2 E+ M6 r0 \: t - {
* j$ [# P7 C& r4 ?5 o - uint16_t tmp = Value;3 S" @1 R$ F. T3 z6 {) R6 `
- Value = ((uint16_t)(tmp >> 8) & 0x00FF);6 m" Y; [; J- M
- Value |= ((uint16_t)(tmp << 8)& 0xFF00);
3 l6 W% D F; v" D* E - /* Check the communication status */ r( V- G' Y' s( ^4 f
- if(HAL_FMPI2C_Mem_Write(&hfmpi2c1, Addr, (uint16_t)Reg,( `/ G% L& p/ T- X9 n+ m6 [
- FMPI2C_MEMADD_SIZE_16BIT, (uint8_t*)&Value, 2, 1000) != HAL_OK)
$ z' E( `. M' W6 c - {* V$ b5 m6 a; ]5 a8 @) \0 P
- /* De-initialize the I2C communication bus */8 U, b$ Z9 m& p1 ~
- HAL_FMPI2C_DeInit(&hfmpi2c1);
; F7 K6 k% Y. k5 k& d* v - /* Re-Initialize the I2C communication bus */) S- H- c+ Q9 g0 n4 D1 D% W
- MX_FMPI2C1_Init();0 R, S2 C6 s5 ?
- }
: J2 X, W2 h- [& D, { - }* X* @& m) L5 m. D0 I
- /**0 m, Y! `6 p6 a. G
- * @brief Reads a single data.
; B" D; T# @" S' c4 Y - * @param Addr: I2C address3 P T# m( \& Z4 p6 Y& S" Y5 ]
- * @param Reg: Reg address
9 t4 X4 O1 A. {1 I! m& t/ r0 [ - * @retval Data to be read4 x- c; ~/ h0 P
- */3 J2 J8 R) o. A" f4 C" B/ h: M( w
- uint16_t AUDIO_IO_Read(uint8_t Addr, uint16_t Reg)2 B' V# ?8 t* v! T
- {
~% Z' N5 z; M, C& o - uint16_t read_value = 0, tmp = 0;
6 V2 [; k, c1 @- z% V! @$ ?9 K - if(HAL_FMPI2C_Mem_Read(&hfmpi2c1, Addr, (uint16_t)Reg,/ L: k% l% t" J1 J* v9 v" L
- FMPI2C_MEMADD_SIZE_16BIT, (uint8_t*)&read_value, 2,$ P* P4 o. X5 K/ P- d( `0 y
- 1000) !=HAL_OK)) @# H/ W9 X# E3 p3 t, x
- {* Y3 I1 |9 |% P& a# }
- /* De-initialize the I2C communication bus */5 p O7 B7 [0 f# T% n6 f
- HAL_FMPI2C_DeInit(&hfmpi2c1);' I9 {2 v: g7 T+ ]# k2 L! ]
- /* Re-Initialize the I2C communication bus *// a+ K7 O' k; `. R7 J3 c
- MX_FMPI2C1_Init();
1 w1 j" D0 x, h# E) Y - } 9 V2 M/ ?% T: ^* U! S8 ?
- tmp = ((uint16_t)(read_value >> 8) & 0x00FF);
, J- f6 V7 M; }1 q: i8 Z - tmp |= ((uint16_t)(read_value << 8)& 0xFF00);; v$ r7 b, L5 B, I# A
- read_value = tmp;7 z. m+ j F8 H; d8 I- a1 b
- return read_value;7 U x, U& H! O% N1 W4 O0 I
- }
- K; u, R' {* X# Q+ L - /**
$ @$ F9 U9 j2 B9 [2 t# \3 \$ m - * @brief Initializes Audio low level.
. K; B( c0 X( Z. ]5 Z# [) z - */
3 R2 p: [% F0 v - void AUDIO_IO_Init(void)! c3 r3 Y, W0 M, j @
- {$ m1 F. ` U% A' m* [
- //Already defined in MX_FMPI2C1_Init().
5 P/ l9 A. X Q0 {/ j - }8 E) ]4 p5 J- K) ^, ?* o4 t4 Y( Z
- void AUDIO_IO_Delay(uint32_t Delay)* \0 ]* H5 M! y3 F9 y
- {9 N4 g9 H7 b( [# T9 L# }
- HAL_Delay(Delay);. q9 C4 G" q Q5 N) h; C
- }
* `2 p1 r+ F. {& G* A7 h3 S! u) f - uint8_t WM8994_Config(void)* y$ g5 e! F9 q* h( |$ g0 u1 Q
- {$ {! x/ q$ T3 @ x
- uint8_t ret = HAL_ERROR;
1 D; A6 b" G6 {5 o: @. J# A+ R2 w l - uint32_t deviceid = 0x00;% V3 H2 A: H& ~5 E3 u: Q
- /* wm8994 codec initialization */6 q9 y! v$ T2 B' T
- deviceid = wm8994_ReadID(AUDIO_I2C_ADDRESS);
/ y' ~, O) q% z3 ^3 N6 U - if((deviceid) == WM8994_ID)9 a7 a- l, c8 ~$ x( o3 i
- {% c% U. \0 V, g% v
- /* Reset the Codec Registers */
/ G! y: R/ e# h1 @7 J% V7 U- _4 \ - wm8994_Reset(AUDIO_I2C_ADDRESS);
4 g4 j9 c! S0 `- G! k! U6 g - ret = HAL_OK;
1 { C% T: }& E+ a5 r! y/ Y - }; Y! C+ T" \ g# J
- else
3 o9 i0 y @. M. j5 a( M - {
& m" ?, V: Y& E) s - ret = HAL_ERROR;
" I1 N! _9 P$ Y2 Q1 }. N - }$ h4 M" `8 u8 d9 H1 y, s) z, f1 ~
- if(ret == HAL_OK): X3 I. r0 H) R3 y
- {
4 M- a1 h# i( k5 E3 ]. y - /* Initialize the codec internal registers */
% O8 ]) I6 Z+ E6 e3 R) p - wm8994_Init(AUDIO_I2C_ADDRESS,
! h/ L& Z7 U# U0 \5 S - INPUT_DEVICE_INPUT_LINE_1|OUTPUT_DEVICE_AUTO, AUDIO_VOLUME,
4 ~% I/ U! ^6 Z6 f$ \1 y+ J - I2S_AUDIOFREQ_48K);
, |3 e( w, C+ n* Y8 ~ - }
& N" K7 R- w5 L3 f; N1 S - if(wm8994_SetMute(AUDIO_I2C_ADDRESS, AUDIO_MUTE_OFF) != 0)
: J1 s. r9 y; T* ]/ T$ l - {1 D2 z! x: Y3 a
- ret = HAL_ERROR;( Z! Z3 m0 A6 \) {& H/ ]
- }, A9 e6 a; q# }% K) m9 _
- return ret;0 p: P5 h( S, u* `" l
- }8 R$ P) W% E0 j4 ?, M( K
- /* USER CODE END 4 */
9 j J# g5 U0 y) `/ t0 W6 U - HAL_I2SEx_TransmitReceive_DMA(&hi2s2,(uint16_t
6 ~) t. J* L( G |- @/ f+ N - *)AudioBuf,(uint16_t *)AudioBuf,(sizeof(AudioBuf)/2));
: O# S1 l: d$ y7 z6 q - ; d& L; g9 S2 \" G, Z. v* ^
- if( WM8994_Config() != HAL_OK)9 q. S+ }% V) P6 }6 ~8 M+ q
- {
/ _$ I, h: l% b - Error_Handler();
* K5 \1 t1 m" Z1 r; z0 b" E. c- B - }
& Q/ a" o, w! v2 j3 \
复制代码
7 ^2 ]$ ~" H/ V# R0 qwm8994.h. x2 s! }1 o2 }" h7 c+ z; P. p2 v
- /* 修改头文件路径 */
! B5 E9 J4 u2 s9 w1 Z* x - //#include "../Common/audio.h"* ~4 k E5 W$ T" z+ b" L5 [; u. |
- #include "audio.h"! Q; ^) w- ? O4 t/ S3 w" r- D
- #include "main.h"+ ~/ T3 |2 m2 {. ~6 F
- ……
3 }$ M5 q8 @8 W1 U( f" `0 \ - /* 修改函数声明 */! l9 `- C6 p7 Y7 U9 T9 J
- //uint8_t AUDIO_IO_Read(uint8_t Addr, uint16_t Reg);
* N( \: y% s) Z- h8 V" n* E7 t - uint16_t AUDIO_IO_Read(uint8_t Addr, uint16_t Reg);
复制代码 & y: V, t- g' g2 {# f9 Q k! j
* G ^& l1 a, c1 u6 }6 H2 wwm8994.c w c wm8994_Init $ X% m8 N' Z$ P: m) b1 b" Q/ T
, p4 T$ x9 Z1 M; T6 j e7 j- /* 修改参数,降低放大倍数*/: f' u/ v' W6 p' j, K
- case INPUT_DEVICE_INPUT_LINE_1 :( w2 K N4 w9 q; g# H
- ……
8 N$ \ T0 o( z0 G, X - /* Disable mute on IN1L_TO_MIXINL and +30dB on IN1L PGA
* \3 Y2 {$ p8 H - output */; t, d* |6 o" `! D: T0 [
- //counter += CODEC_IO_Write(DeviceAddr, 0x29, 0x0035);
* w1 s# E+ L5 N5 Y - counter += CODEC_IO_Write(DeviceAddr, 0x29, 0x0033);, K: j4 k( ?- {- _
- /* Disable mute on IN1R_TO_MIXINL, Gain = +30dB */
- J! w1 [* v0 s/ v% ? - //counter += CODEC_IO_Write(DeviceAddr, 0x2A, 0x0035);
. X' n) d) \; e+ d$ `" {+ b - counter += CODEC_IO_Write(DeviceAddr, 0x2A, 0x0033);1 B- i# N8 F, ], P- @/ n
- ……" m! b' K, Z7 K+ A* o6 S
- /* 修改参数,避免后续操作将麦克风偏置 1 关闭*/
0 E! w4 n. W+ ~0 g1 Z* L3 N. l - /* Enable bias generator, Enable VMID, Enable HPOUT1 (Left)
" F* \3 c; A: |5 j5 k4 S - and Enable HPOUT1 (Right) input stages */
6 s7 l( K9 s2 p - /* idem for Speaker */+ Z0 d. C# C( m, {& k
- //power_mgnt_reg_1 |= 0x0303 | 0x3003;2 ]2 f, O5 G: ]; J* d: F
- if(input_device > 0)# _5 @) U; f) W6 T4 J
- power_mgnt_reg_1 |= 0x0303 | 0x3003 | 0x0010;/ N$ P5 ?+ B W' C: e9 V: ]6 l& v
- else
, ?; Z% d# p6 i( V* v; X* A. P - power_mgnt_reg_1 |= 0x0303 | 0x3003;
复制代码 3 c6 ^, G0 k% d* V' p0 H
& u" ^3 Q0 V+ F; L5 @7 G
由上述添加及修改,可知在利用 STM32CubeMX 配置、生成工程后,I2S 数据接收和发送实现方便,只需要调用 HAL 库提供的 API 即可。工作较多集中在 STM32 的音频接口了解和编解码器功能配置方面。编解码器方面,一般编解码器厂商会有文档、配套工具或者配置例程提供。: @2 z) g, ]4 N: K5 j
0 |5 @7 W& @1 _3 z5 q4 }# C
4.2.2 实现 2
, d- S) ~( {- b# }这种架构实现例,可参考 Cube 软件包中提供的例程,不再做展开介绍。例程路径如下:
% H2 o) Y/ J/ g1.STM32Cube_FW_F4_V1.16.0\Projects\STM32F401-Discovery\Applications\Audio\Audio_playback_and_record4 G# a! ]: X/ E% \* ^0 @6 q# k% ?
2.STM32Cube_FW_F4_V1.16.0\Projects\STM32F411E-Discovery\Applications\Audio\Audio_playback_and_record4 T$ D! n2 l& m* p" }
& P1 h8 s- Q" O3 P
1 \: {5 E' Z; E- N8 O9 M四 低功耗设计' I6 |/ ]$ s0 d0 S- T- i( l
不同功耗模式下,I2S 工作情况如下表。
. Q# ?, q) V, {1 V& d' ?) P* w3 x: ?( U: n8 I |$ D) G
& M1 }$ a! C7 }6 z3 K) D6 [) K+ Z
" z" u1 B3 U7 D; ?* ^
3 n2 \& v2 z. O注1. 不同系列 STM32,低功耗模式有差异,具体以参考手册为准。
9 Z- c H( F' B% p! O9 i注2. 批处理模式(BAM)并非所有 STM32 产品都支持,支持情况请以对应型号的 STM32 参考手册中描述为准。BAM能够实现在睡眠模式下,批量获取数据,然后再退出睡眠模式进行运算处理等操作。能够进一步实现功耗的降低。更多关于 BAM 介绍可参考 RM0430。
" C$ o" x3 v# g9 p1 a4 J% B( B. C/ x2 o# v0 B. B4 S; ?
; O' P2 A) g1 P7 ^( o
五 小结4 M# P$ s8 L% L% W# M! W4 Z
在 STM32 音频开发中,结合 STM32CubeMX 和 Cube 软件包中提供的例程,容易完成基于 STM32 的音频平台搭建。
4 O/ b9 v6 O/ N: b; L( M& RI2S 各协议时序清晰,开发过程中,出现异常时,开发者可以先利用示波器,判断波形对应时序是否正确,以此缩小问题范围,加快问题定位及解决。- }; Y+ v& c+ D' q7 g( c8 b2 _
; Z- x* `' a- b7 i/ Z
9 b2 P: e' t5 z7 d- R& K' ~; V9 H
0 y+ @7 X6 o) \8 T- p T! u1 X) |+ b" ]' `
1 V6 A7 p) \ T7 y. N
2 H9 }3 j- g, H* K0 @) g9 j; q |