LPC3130/31 I2S接口与DMA音频传输实战配置详解
1. 项目概述
如果你正在基于NXP的LPC3130/31系列微控制器开发音频应用,那么深入理解其内置的I2S接口是绕不开的一环。我接触过不少项目,从简单的语音播报到复杂的多声道音频处理,但凡涉及到高保真、低延迟的音频数据流,I2S+DMA的组合几乎是标准答案。LPC3130/31的I2S模块功能相当完整,支持主从模式、多种数据格式,并且与DMA控制器深度集成,能极大减轻CPU负担。但官方手册内容庞杂,寄存器描述分散,初次上手容易在时钟配置、数据对齐和DMA触发这几个关键点上栽跟头。本文旨在为你拆解LPC3130/31 I2S接口的核心机制,聚焦于寄存器配置的实战细节和DMA传输的高效实现,让你能快速搭建稳定可靠的音频数据通道,避免在底层调试上耗费过多时间。
2. I2S接口架构与核心模块解析
LPC3130/31的I2S接口并非一个单一模块,而是一个由多个子模块协同工作的系统。理解这个架构,是进行正确配置的前提。
2.1 模块组成与数据流
根据手册描述,I2S接口主要包含以下几个部分:
- I2SRX0/1接口:即I2S接收器。它的核心功能是将外部音频编解码器(Codec)发送过来的串行I2S数据流,解码成并行的左右声道数据,并通过APB总线接口提供给处理器。它会生成一个关键的
NEWSAM信号,这是一个锁存使能信号,在每个新的音频样本数据就绪时拉高一个时钟周期,用于同步内部逻辑。 - I2STX0/1接口:即I2S发送器。功能与接收器相反,它将处理器通过APB总线写入的并行音频数据,按照配置的格式(I2S或LSB对齐)编码成串行I2S数据流发送出去。
- 配置寄存器模块:这是一个APB从设备接口,用于访问所有的I2S配置寄存器,例如
I2S_FORMAT_SETTINGS和I2S_CFG_MUX_SETTINGS。它是软件控制I2S模块的入口。 - 边沿检测器:这个模块比较隐蔽但很重要。它利用时钟生成单元(CGU)产生的
I2S_EDGE_DETECT_CLK来生成NEWSAM信号。手册特别指出,这个时钟并不连接到I2STX接口的引脚上,意味着它是一个内部时钟,用于同步采样时刻。
数据流可以这样理解:对于播放(TX),CPU或DMA将音频样本数据写入I2STX的数据寄存器(如LEFT_16BIT),数据进入内部的FIFO。发送器模块根据配置的位时钟(BCLK)和字选择(WS)信号,将FIFO中的数据串行移出到I2STX_DATA引脚。对于录音(RX),过程相反,串行数据从I2SRX_DATA引脚移入,由接收器模块重组为并行数据存入FIFO,CPU或DMA再从相应寄存器中读取。
2.2 时钟树与电源管理要点
音频系统的时钟是基石。LPC3130/31的I2S时钟源于系统主PLL(PLL0)和后续的分频器。手册的编程指南部分给出了关键线索:我们需要配置Audio PLL (PLL0) 和分数分频器来产生所需的位时钟(BCLK)和字选择时钟(WS,即采样率Fs)。
这里有一个非常实用的表格(手册中的Table 583),它直接给出了常见采样率(如44.1kHz, 48kHz, 96kHz)下,PLL0和分数分频器(FracDiv17, FracDiv18)的具体参数。例如,要得到44.1kHz的采样率,需要将PLL0输出频率Fout配置为11.2896 MHz,并将分数分频器17(FracDiv17)的值设为256。这个256值直接决定了WS的频率。而BCLK通常由另一个分频器(如FracDiv18)产生,其值一般为FracDiv17 / (声道数 * 每声道位数)。例如,对于立体声、32位每声道,就是256 / (2*32) = 4。
注意:手册第6章“电源优化”中特别提醒:为了节省功耗,不使用的模块时钟应当被禁用。这意味着在初始化时,除了配置CGU开启I2S相关时钟,在音频任务结束后或进入低功耗模式前,也应通过CGU关闭这些时钟,这是一个良好的编程习惯。
3. 寄存器详解与配置策略
寄存器是驱动硬件的直接手段。LPC3130/31的I2S寄存器布局清晰,但每个比特位都需谨慎对待。
3.1 核心配置寄存器
I2S_FORMAT_SETTINGS (地址: 0x1600 0000)这是格式设置的“总开关”。它是一个32位寄存器,但高20位保留,仅使用低12位,每3位一组,分别控制I2STX0、I2STX1、I2SRX0、I2SRX1四个接口的数据格式。
| 比特位 | 符号 | 描述 |
|---|---|---|
| 2:0 | I2STX0_format | I2STX0接口输入格式 |
| 5:3 | I2STX1_format | I2STX1接口输入格式 |
| 8:6 | I2SRX0_format | I2SRX0接口输出格式 |
| 11:9 | I2SRX1_format | I2SRX1接口输出格式 |
每个3位字段的值对应不同的音频格式,手册Table 581给出了定义:
- 0x3: 标准I2S格式。这是最常用的格式,数据在WS变化后的第二个BCLK上升沿有效。
- 0x4: LSB对齐,16位。数据帧内,数据位左对齐,最低有效位(LSB)位置固定。
- 0x5: LSB对齐,18位。用于连接18位精度的音频编解码器。
- 0x6: LSB对齐,20位。用于连接20位精度的音频编解码器。
- 0x7: LSB对齐,24位。这也是一个常用格式,可用于传输24位样本数据,或者当连接18/20位编解码器时也使用此设置,但数据需写入24位寄存器。
配置心得:务必确保处理器端的数据格式、I2S接口的格式设置以及外部音频编解码器期待的格式三者完全一致。最常见的错误是I2S和LSB justified格式混用,导致听到的完全是噪音或音调异常。
I2S_CFG_MUX_SETTINGS (地址: 0x1600 0004)这个寄存器主要控制I2S接收器的主从模式。对于发送器(TX),LPC3130/31通常作为主设备(Master)提供BCLK和WS。对于接收器(RX),它可以配置为主设备或从设备。
| 比特位 | 符号 | 描述 |
|---|---|---|
| 1 | I2SRX0_oe_n | 控制I2SRX0模式:0-从设备,1-主设备 |
| 2 | I2SRX1_oe_n | 控制I2SRX1模式:0-从设备,1-主设备 |
配置策略:在典型的播放场景中,LPC3130/31的I2STX作为主设备,产生BCLK和WS给外部DAC。在录音场景中,如果外部ADC能提供时钟,则可将I2SRX设为从模式;如果需要芯片主动控制采样节奏,则需将I2SRX设为主模式,并确保时钟配置正确。
3.2 数据寄存器与FIFO操作
I2STX和I2SRX各有两组数据寄存器,分别对应16位、24位和32位数据宽度,以及一个特殊的交错(Interleaved)数据寄存器区。以I2STX0为例(基址0x1600 0080):
LEFT_16BIT/RIGHT_16BIT:用于16位音频数据。写入LEFT_16BIT的数据进入左声道FIFO。LEFT_24BIT/RIGHT_24BIT:用于24位音频数据。当配置为18/20/24位LSB格式时使用。LEFT_32BIT_0~LEFT_32BIT_7:这是一组8个寄存器,每个用于存放2个16位样本。手册注明“16 LSB’s represent the first sample”,这意味着低16位是第一个样本,高16位是第二个样本。这适用于需要批量写入的场景。INTERLEAVED_0~INTERLEAVED_7:这是实现DMA传输的关键。每个寄存器32位,高16位(bits 31:16)代表右声道样本,低16位(bits 15:0)代表左声道样本。DMA控制器可以配置为以“Interleaved”模式工作,即一次传输32位数据,同时更新左右声道,效率最高。
FIFO状态与中断:INT_STATUS寄存器反映了FIFO的各种状态(空、半满、满、上溢、下溢)。INT_MASK寄存器用于使能或屏蔽这些状态产生的中断。例如,你可以使能“FIFO左空”中断,当发送FIFO快空时触发中断,提醒软件及时填充数据;或者使能“FIFO右溢出”中断,在接收时发现数据丢失及时告警。
避坑指南:操作数据寄存器前,务必先理解FIFO的深度。手册指出TX和RX FIFO都被配置为4个32位字。对于16位立体声数据,这意味着FIFO可以缓存
4 * (32位 / 16位/样本) = 8个样本(左右声道各4个)。在编写中断服务程序或DMA传输长度时,必须考虑这个深度,避免溢出或欠载。
4. DMA传输机制深度剖析
使用CPU轮询或中断方式搬运音频数据,在高速、连续的场景下会消耗大量资源。DMA才是实现流畅音频播放/录音的“王道”。
4.1 DMA请求与应答信号
手册的DMA部分列出了每个I2S TX/RX FIFO块对应的DMA信号。这是硬件连接DMA控制器的桥梁。
对于I2STX0_FIFO(发送):
I2STX0_dma_req_left:左声道FIFO的DMA请求信号(从设备号6)。I2STX0_dma_req_right:右声道FIFO的DMA请求信号。
对于I2SRX0_FIFO(接收):
I2SRX0_dma_req_left:左声道FIFO的DMA请求信号(从设备号10)。I2SRX0_dma_req_right:右声道FIFO的DMA请求信号。
I2STX1和I2SRX1也有对应的信号(从设备号8和12)。每个请求信号都对应一个清除(DMA_CLR)信号作为应答。
关键说明:手册特别指出,当使用DMA向I2STX0块传输交错数据(即使用INTERLEAVED_x地址作为DMA通道目标)时,应使用I2STX0_dma_req_left信号。对于I2STX1、I2SRX0和I2SRX1块同理。这意味着在交错传输模式下,硬件以左声道请求来代表整个立体声数据块的传输请求。
4.2 DMA传输模式实战配置
LPC3130/31的DMA控制器通常支持多种传输模式(如单次、循环),并需要配置源地址、目标地址、传输数据量等。结合I2S,主要有两种思路:
1. 双通道模式(非交错):
- 配置两个DMA通道:通道A链接到
I2STX0_dma_req_left,目标地址为LEFT_16BIT;通道B链接到I2STX0_dma_req_right,目标地址为RIGHT_16BIT。 - 需要准备两份独立的音频数据缓冲区,一份是纯左声道数据,一份是纯右声道数据。
- 优点:逻辑清晰,符合常规思维。
- 缺点:需要管理两个DMA通道,数据在内存中非连续存放,不利于从标准音频文件(通常是交错格式)直接传输。
2. 单通道交错模式(推荐):
- 配置一个DMA通道,链接到
I2STX0_dma_req_left。 - 目标地址设置为
INTERLEAVED_0(或该区域的任一寄存器地址)。 - 内存中的音频数据缓冲区必须是交错格式:
[L0, R0, L1, R1, L2, R2, ...],每个样本16位,则每个32位字包含一对LR样本。 - DMA传输宽度设置为32位(字)。
- 优点:单通道管理,效率高。内存数据布局与常见音频文件格式(如WAV)一致,无需额外转换。
- 缺点:需要确保音频数据源本身就是交错格式。
配置步骤示例(以I2STX0交错DMA传输为例):
- 初始化I2S:配置时钟、格式(如I2S格式)、主从模式。
- 准备数据缓冲区:在内存中开辟一段空间,存放交错格式的立体声PCM数据。
- 配置DMA通道:
- 设置通道的源地址为内存缓冲区地址。
- 设置通道的目标地址为
I2STX0的INTERLEAVED_0寄存器地址(0x1600 0060)。 - 设置传输数据总量(字节数)。注意是字节数,例如传输N个立体声样本(每个样本32位),则总字节数为
N * 4。 - 设置传输宽度为32位(Word)。
- 设置传输模式为循环模式(Circular),以实现连续播放。
- 将DMA请求源映射到
I2STX0_dma_req_left(从设备号6)。
- 启动:先使能DMA通道,然后启动I2S TX(通常通过使能相关时钟或开始发送命令)。
- 传输过程:当I2STX0的左声道FIFO非满(或达到预设阈值)时,硬件自动拉高
dma_req_left信号,DMA控制器响应,从内存读取一个32位字(包含一对LR样本)写入INTERLEAVED_0寄存器。写入后,硬件清除请求,等待下一次FIFO需要数据时再次发起。
5. 完整编程流程与实战代码框架
理论需要结合实践。下面我将基于常见的44.1kHz、16位立体声、I2S格式、TX为主模式的播放场景,梳理一个完整的软件初始化流程和代码框架。请注意,以下代码为伪代码风格,展示逻辑和关键寄存器操作,实际开发需结合具体的硬件抽象层(HAL)或寄存器定义头文件。
5.1 系统时钟与I2S时钟配置
这是第一步,也是最容易出错的一步。目标是产生准确的BCLK和WS。
// 假设系统已有基本的时钟初始化函数 // 1. 配置PLL0,产生Fout = 11.2896 MHz (对应44.1kHz采样率) // 根据手册Table 583,参数为:Ndec=131, Mdec=29784, Pdec=7, SELR=0, SELI=8, SELP=31 configure_pll0(131, 29784, 7, 0, 8, 31); // 2. 配置分数分频器FracDiv17,产生WS (Fs) // FracDiv17 = 256 (来自Table 583) set_fractional_divider(FRAC_DIV_17, 256); // 3. 配置分数分频器FracDiv18,产生BCLK // BCLK = Fs * 声道数 * 每声道位数 = 44.1kHz * 2 * 16 = 1.4112 MHz // 但分频器输入是Fout (11.2896MHz),分频比 = Fout / BCLK = 8 // 另一种算法:手册指出 FracDiv18 = FracDiv17 / 64 = 256 / 64 = 4 // 注意:这里存在理解关键点!手册中的“64”来源于 2声道 * 32位/声道。 // 对于16位/声道,实际需要的BCLK频率是 Fs * 2 * 16 = Fs * 32。 // 因此,如果FracDiv17产生的是256倍Fs的时钟,那么用于BCLK的分频系数应为 256 / 32 = 8。 // 然而,手册示例直接用了4,这意味着它默认以32位/声道(即64倍Fs)来计算BCLK。 // 在实际16位应用中,我们通常仍配置BCLK为64倍Fs(即32位时隙),但数据只在低16位有效。 // 为兼容性,我们遵循手册:FracDiv18 = 4。 set_fractional_divider(FRAC_DIV_18, 4); // 产生 11.2896MHz / 4 = 2.8224MHz 的BCLK // 这个BCLK频率对应 2.8224MHz / (2*32) = 44.1kHz 的Fs。注意分母是64,因为硬件按32位时隙处理。 // 4. 在CGU中使能I2S相关时钟(I2S_TX0, I2S_RX0等) enable_clock(CLK_I2S_TX0); // 如果使用I2SRX,也需要使能 enable_clock(CLK_I2S_RX0);5.2 I2S模块初始化
配置格式、主从模式、中断等。
// 定义寄存器基址 #define I2S_CONFIG_BASE 0x16000000 #define I2S_TX0_BASE 0x16000080 // 1. 配置数据格式:I2S格式,16位(实际使用16位寄存器,硬件按I2S格式发送) volatile uint32_t *i2s_format = (uint32_t *)(I2S_CONFIG_BASE + 0x00); // 设置I2STX0为I2S格式 (值0x3),其他接口可根据需要设置,这里先清零 *i2s_format = (0x3 << 0); // Bits[2:0] = 0x3 // 2. 配置主从模式(通过I2S_CFG_MUX_SETTINGS) volatile uint32_t *i2s_mux = (uint32_t *)(I2S_CONFIG_BASE + 0x04); // 假设I2STX0为主(通常TX为主),I2SRX0为从(如果使用) // 寄存器bit1控制I2SRX0: 1=主,0=从。我们设其为从。 // 寄存器bit2控制I2SRX1。我们暂不使用,保持默认。 // 先读取再修改,避免影响其他位 uint32_t mux_val = *i2s_mux; mux_val &= ~(1 << 1); // 清除bit1,设置I2SRX0为从模式 *i2s_mux = mux_val; // 3. 配置中断(如果需要) volatile uint32_t *i2s_int_mask = (uint32_t *)(I2S_TX0_BASE + 0x14); // 例如,使能“FIFO左空”中断,当FIFO空时请求DMA或CPU填充 // 查看手册Table 572,中断号6对应“FIFO left empty” *i2s_int_mask = (1 << 6); // 使能第6号中断(注意:有些寄存器是写1使能,有些是写1清除,需确认!通常INT_MASK是写1使能对应中断) // 更常见的DMA场景下,我们可能使用半满或空中断来触发DMA。需要查证具体位定义。5.3 DMA控制器配置(以交错传输为例)
假设DMA控制器的基址和通道API已知。
// 准备音频数据缓冲区:交错立体声,16位采样,44.1kHz,1秒时长 #define AUDIO_SAMPLE_RATE 44100 #define AUDIO_NUM_SAMPLES (AUDIO_SAMPLE_RATE * 1) // 1秒的样本数 int16_t audio_buffer[AUDIO_NUM_SAMPLES * 2]; // 左、右样本交错存放 // 填充音频数据(此处省略,可能是从存储设备加载或算法生成) // load_audio_data(audio_buffer, sizeof(audio_buffer)); // 配置DMA通道(假设使用通道0) dma_channel_config_t dma_cfg; dma_cfg.src_addr = (uint32_t)audio_buffer; // 源地址:内存缓冲区 dma_cfg.dst_addr = (uint32_t)(I2S_TX0_BASE + 0x60); // 目标地址:INTERLEAVED_0 dma_cfg.transfer_size = sizeof(audio_buffer); // 传输总字节数 dma_cfg.src_width = DMA_WIDTH_WORD; // 源数据宽度:32位(字) dma_cfg.dst_width = DMA_WIDTH_WORD; // 目标数据宽度:32位(字) dma_cfg.src_inc = DMA_ADDR_INCREMENT; // 源地址自增 dma_cfg.dst_inc = DMA_ADDR_NO_CHANGE; // 目标地址固定(总是写入同一寄存器) dma_cfg.mode = DMA_MODE_CIRCULAR; // 循环模式,播放完后从头开始 dma_cfg.transfer_type = DMA_TRANSFER_M2P; // 内存到外设 dma_cfg.dma_request = 6; // 请求源:I2STX0_dma_req_left (从设备号6) // 初始化并启动DMA通道 dma_channel_init(DMA_CH0, &dma_cfg); dma_channel_enable(DMA_CH0);5.4 启动传输与流程控制
// 在DMA和I2S都配置好后,启动I2S传输 // 通常需要确保FIFO中有初始数据,否则可能因欠载而产生错误。 // 一种方法是先手动写入几个样本到FIFO,或者依赖DMA请求机制自动开始。 // 检查I2S TX FIFO状态,如果非满,手动触发一次DMA传输(可选) // volatile uint32_t *i2s_status = (uint32_t *)(I2S_TX0_BASE + 0x10); // if (!(*i2s_status & (1 << 4))) { // 假设bit4是“FIFO left full”标志 // // FIFO未满,可以手动启动一次DMA单次传输来填充初始数据 // } // 最后,确保I2S TX模块的时钟和功能已完全开启(可能涉及更底层的电源/时钟控制) // 例如,有些芯片需要设置某个控制位来“使能”I2S发送器。 // 根据手册,配置好时钟和寄存器后,写入数据到FIFO,在有时钟信号的情况下,发送会自动开始。 // 主循环或任务中,可以监控播放状态或处理其他事务 while (1) { // 例如,检查DMA传输完成标志(在非循环模式下),或响应停止播放的用户事件 // if (dma_transfer_complete(DMA_CH0)) { // // 处理播放结束逻辑 // } // 或者,在循环播放模式下,可以在这里实现音频流的动态控制(如切换歌曲、调节音量) }6. 常见问题排查与调试技巧
在实际开发中,你几乎一定会遇到问题。以下是我总结的几个典型问题及其排查思路。
6.1 无声问题排查清单
这是最常见的问题。请按照以下顺序检查:
时钟信号是否存在?这是首要条件。使用示波器或逻辑分析仪测量
I2STX_BCK0和I2STX_WS0引脚。- 无任何波形:检查CGU配置,确认PLL0已锁定,分数分频器已正确设置且时钟已使能。确认I2S模块的电源和时钟门控已打开。
- 有波形但频率不对:核对BCLK和WS的频率计算。WS应为音频采样率(如44.1kHz),BCLK应为
WS * 声道数 * 每时隙位数。常见错误是时隙位数理解有误(标准I2S每声道数据长度可大于实际样本位数,空缺位补0)。
数据信号是否活动?测量
I2STX_DATA0引脚。- 始终为高或低:检查是否确实有数据写入TX FIFO。检查DMA是否配置正确并已启动。检查CPU写入数据寄存器的代码路径是否执行。可以通过在初始化后,手动向
LEFT_16BIT和RIGHT_16BIT写入一个非零测试值,观察DATA线是否有对应脉冲。 - 有数据但看起来是乱码:检查数据格式(
I2S_FORMAT_SETTINGS)是否与音频编解码器期望的格式匹配。I2S和LSB对齐格式的数据相位不同。
- 始终为高或低:检查是否确实有数据写入TX FIFO。检查DMA是否配置正确并已启动。检查CPU写入数据寄存器的代码路径是否执行。可以通过在初始化后,手动向
硬件连接是否正确?确认MCU的I2S引脚与音频编解码器的对应引脚(BCLK, WS, DATA, MCLK)已正确连接。确认共地。检查编解码器本身是否需要配置(通过I2C/SPI),是否已使其脱离复位状态、选择正确的时钟源和从模式。
FIFO状态与中断/DMA:查询
INT_STATUS寄存器,检查是否有FIFO上溢或下溢错误。如果使用DMA,确认DMA请求线(dma_req_left)是否被正确映射到DMA通道,DMA通道的优先级和使能位是否正确。
6.2 音频失真或噪音问题
- 数据位宽不匹配:这是导致“破音”或高频噪音的常见原因。例如,音频数据是16位有符号整数(范围-32768~32767),但你错误地将其作为24位数据写入了
LEFT_24BIT寄存器,而未将数据左对齐到高16位或进行符号扩展。对于16位数据使用LEFT_16BIT寄存器最安全。 - 采样率不匹配:虽然WS频率正确,但如果音频文件本身的采样率与配置的WS不匹配,会导致音调变化(变快或变慢)。确保音频数据的采样率与I2S配置的Fs一致。
- 电源噪声:模拟音频电路对电源噪声非常敏感。确保为音频编解码器提供了干净、稳定的模拟电源,并与数字电源进行适当的隔离(使用磁珠或LC滤波)。
6.3 DMA传输不连续或卡顿
- 缓冲区大小与DMA传输长度:在循环DMA模式下,如果音频缓冲区太小,DMA很快完成一轮传输并重新开始,如果软件处理不及时(例如从SD卡读取下一段数据),可能导致断流。缓冲区应足够大,通常容纳几十到几百毫秒的音频数据。
- 内存对齐:确保DMA源地址(音频缓冲区)是字对齐的(32位边界)。非对齐访问在某些架构上会导致性能下降或错误。
- 系统总线竞争:如果DMA和CPU激烈竞争系统总线带宽(例如CPU正在执行大量内存操作),可能导致DMA偶尔无法及时获取数据,引起音频卡顿。可以考虑优化CPU代码,或使用带缓存的内存区域(如果DMA支持访问缓存)。
- 中断延迟:如果使用FIFO半满中断来填充数据,需要确保中断服务程序(ISR)执行时间足够短,能在下一个半满事件到来前完成数据搬运。否则会导致FIFO下溢。
6.4 调试工具与手段
- 逻辑分析仪:是调试I2S通信的利器。可以同时捕获BCLK、WS、DATA三条线,直观地查看数据帧、对齐方式和实际传输的数据值。许多逻辑分析仪软件支持I2S协议解码,能直接显示十六进制或十进制的音频样本值。
- 寄存器查看:在调试初期,频繁地读取关键寄存器(如
INT_STATUS,I2S_FORMAT_SETTINGS)的值,与预期值对比。 - 软件模拟:在硬件就绪前,可以先编写一个模拟的I2S数据发送函数,用GPIO模拟BCLK、WS和DATA信号,验证音频编解码器的基础功能和数据流是否正确。这能有效隔离MCU I2S模块配置问题。
