4. 音频接口类型

好,我们进入第4章。前面几章我们把Codec的硬件接口、模拟通路理了一遍。从这章开始,要跟Codec“对话”了——说白了,就是怎么通过控制接口去读写它内部的寄存器。

Codec本身是个混合信号芯片,但它的控制逻辑是纯数字的。你要配置采样率、增益、静音、路由……全靠往寄存器里写值。我见过不少硬件工程师,原理图画得漂亮,结果I2C波形不对,Codec死活不工作。嗯,这章就是帮你避开这些坑。

核心观点:Codec的控制接口(I2C/SPI)是数字世界与模拟世界的“翻译官”。读不懂它,Codec就是一块哑巴芯片。

4.1 I2C vs SPI:控制接口的选型博弈

绝大多数音频Codec都支持I2C,部分高端或专业级Codec会同时支持SPI。为什么会有两种?我个人的习惯是:低速、多设备、走线紧张用I2C;高速、少设备、实时性要求高用SPI。

先看一张对比表,心里有个底:

对比项 I2C SPI
信号线数量 2根(SCL, SDA) 4根(SCLK, MOSI, MISO, CS)
通信方式 半双工,主从架构 全双工,主从架构
速率 标准100kHz,快速400kHz,高速3.4MHz 通常10MHz~50MHz,甚至更高
设备寻址 7位或10位地址,总线可挂多设备 每个设备独占CS片选,总线可挂多设备
硬件复杂度 开漏输出,需要上拉电阻 推挽输出,不需要上拉
典型应用场景 消费电子Codec、传感器、EEPROM 专业音频Codec、ADC/DAC、FPGA配置

你想想看,I2C只用两根线就能挂一堆设备,省GPIO,省PCB空间。但它的开漏结构决定了速率上不去,而且每次通信都有地址、ACK这些开销。SPI就粗暴多了——全双工,速率随便上,但每加一个设备就要多一根CS线。

我在项目中遇到过一个坑:某款Codec同时支持I2C和SPI,但默认上电后检测CS引脚电平来决定用哪种模式。结果PCB布局时CS引脚悬空了,Codec一直卡在SPI模式,I2C怎么都扫不到。后来用示波器一看,CS引脚电平飘忽不定……加个10kΩ下拉电阻搞定。

4.2 Codec寄存器读写机制

不管用I2C还是SPI,Codec的寄存器读写都有固定的“套路”。我把它总结为三步:

  1. 发送设备地址(I2C)或拉低CS(SPI)
  2. 发送寄存器地址(通常是1~2字节)
  3. 发送或接收数据(通常是1~2字节)

但这里有个细节:寄存器地址的位宽。老一点的Codec(比如WM8731)用7位寄存器地址,每个寄存器8位数据。新一点的Codec(比如TLV320AIC3xxx系列)用8位或16位寄存器地址,数据也可能是16位。你查数据手册时第一件事就是确认这个。

我的习惯:拿到一款新Codec,先看数据手册的“Register Map”章节。把寄存器地址、位宽、默认值、读写属性列个表。调试时对照着看,事半功倍。

以I2C为例,写寄存器的典型时序是这样的:

// I2C写寄存器伪代码
START condition
发送设备地址 (7位地址 + 写位0)
等待ACK
发送寄存器地址高字节 (如果地址是16位)
等待ACK
发送寄存器地址低字节
等待ACK
发送数据字节
等待ACK
STOP condition

读寄存器稍微复杂一点——要先写寄存器地址,然后重新发送起始条件,再读数据:

// I2C读寄存器伪代码
START condition
发送设备地址 (7位地址 + 写位0)
等待ACK
发送寄存器地址
等待ACK
REPEATED START condition
发送设备地址 (7位地址 + 读位1)
等待ACK
读取数据字节 (主机发送NACK表示结束)
STOP condition

我曾经犯过一个低级错误:读寄存器时忘了发REPEATED START,直接发了STOP再发START。结果Codec不认,读回来的数据全是0xFF。后来查了逻辑分析仪波形才发现,多了一小段空闲时间,Codec的状态机跑飞了。

4.3 使用i2c-tools调试Codec寄存器

在Linux嵌入式系统里,i2c-tools是调试Codec的神器。你不需要写复杂的驱动代码,直接在终端敲命令就能读写寄存器。我个人觉得,这是每个音频工程师必须掌握的技能。

4.3.1 安装与基本命令

先确认你的系统有没有i2c-tools:

# 安装i2c-tools (Debian/Ubuntu)
sudo apt-get install i2c-tools

# 检测I2C总线
i2cdetect -l

# 扫描总线上的设备 (假设是I2C-1)
i2cdetect -y 1

扫描结果会显示所有检测到的I2C设备地址。比如你的Codec地址是0x1A,那在对应位置就会显示“1a”。如果全是“--”,说明硬件连接有问题——要么地址不对,要么上拉电阻没焊,要么Codec没上电。

注意:i2cdetect扫描时会向每个地址发送一个伪字节。有些Codec(比如某些TI的芯片)会对这个伪字节产生误动作。我建议在扫描前先看数据手册,确认Codec是否支持“General Call”或“Quick Command”。如果不确定,先不要扫,直接用手动读写验证。

4.3.2 读写寄存器实战

假设你的Codec I2C地址是0x1A,寄存器地址是8位,数据也是8位。读寄存器0x05的值:

# 读寄存器0x05
i2cget -y 1 0x1a 0x05

# 返回示例: 0x42

写寄存器0x05为0x42:

# 写寄存器0x05 值为0x42
i2cset -y 1 0x1a 0x05 0x42

如果寄存器地址是16位,需要加参数 -w 或指定字节数:

# 16位寄存器地址示例 (地址0x0100)
i2cget -y 1 0x1a 0x01 0x00  # 注意:地址分两个字节发送
# 或者用 -w 参数 (取决于i2c-tools版本)
i2cget -y 1 0x1a 0x0100 -w

这里有个坑:不同版本的i2c-tools对16位地址的处理方式不一样。我建议先用逻辑分析仪抓一下波形,确认地址发送顺序是MSB first还是LSB first。数据手册里通常会写“MSB first”,但有些芯片就是反的。

4.3.3 批量读写与脚本化

调试Codec时,经常需要一次性配置十几个寄存器。手动敲命令太慢了,我习惯写个shell脚本:

#!/bin/bash
# codec_init.sh - 初始化Codec寄存器

I2C_BUS=1
CODEC_ADDR=0x1a

# 复位Codec
i2cset -y $I2C_BUS $CODEC_ADDR 0x00 0x80

# 配置采样率 (假设寄存器0x02控制)
i2cset -y $I2C_BUS $CODEC_ADDR 0x02 0x10

# 配置音频接口格式 (I2S, 24bit)
i2cset -y $I2C_BUS $CODEC_ADDR 0x03 0x02

# 解除静音
i2cset -y $I2C_BUS $CODEC_ADDR 0x04 0x00

echo "Codec initialized."

运行脚本后,再用i2cget逐个读回来验证。如果读回来的值和写进去的不一致,别急着怀疑芯片——先检查I2C总线电压、上拉电阻、电平转换(如果Codec和主控电压不同)。

4.4 一张图看懂控制接口与寄存器读写

下面这张SVG图把I2C和SPI的寄存器读写流程画出来了。你可以把它当作调试时的“路线图”:

Codec控制接口与寄存器读写流程 Codec控制接口与寄存器读写流程 I2C 寄存器读写 写:START + 设备地址(W) + ACK 写:寄存器地址 + ACK 写:数据字节 + ACK + STOP 读:START + 设备地址(W) + 寄存器地址 读:REPEATED START + 设备地址(R) + 数据 读:主机NACK + STOP SPI 寄存器读写 写:拉低CS + 发送写指令(1字节) 写:发送寄存器地址(1~2字节) 写:发送数据字节 + 拉高CS 读:拉低CS + 发送读指令(1字节) 读:发送寄存器地址 + 接收数据 读:拉高CS (完成一次传输) 提示:I2C有ACK机制,SPI没有。SPI需要软件或硬件保证时序对齐。

4.5 调试中的常见陷阱

最后,我把自己这些年踩过的坑列出来,你调试时遇到类似问题可以优先排查:

  • I2C地址不对:很多Codec的I2C地址是可配置的(通过ADDR引脚)。数据手册里写的地址通常是7位,但i2c-tools用的是8位地址(左移1位)。比如手册写0x1A,实际i2cdetect显示0x34?嗯,你算一下:0x1A << 1 = 0x34。别搞混了。
  • 寄存器地址自增:有些Codec支持连续读写时寄存器地址自动递增。如果你读回来的数据是错位的,检查一下是不是忘了关自增功能。
  • 上电时序:Codec的I2C接口可能依赖某个电源域。如果主控先上电,Codec后上电,I2C总线可能被锁死。我遇到过好几次,解决方案是加I2C电平转换器或确保电源时序正确。
  • i2cset写不进去:先确认寄存器是不是只读的。有些状态寄存器(比如中断标志)只能读,写操作会被忽略。

调试利器:逻辑分析仪。我调试I2C时必挂一个24MHz采样率的逻辑分析仪。波形一看,地址对不对、ACK有没有、数据对不对,一目了然。比盯着示波器看模拟波形舒服多了。