4. I2C与GPIO控制:音频编解码器(如DA7212)的初始化与寄存器读写
好,我们进入第四讲。前面几章我们把R-Car的音频接口、DMA和时钟捋了一遍,现在终于要跟真正的硬件——音频编解码器打交道了。
编解码器是音频链路的终点(或者起点)。CPU送过来的数字音频流,得靠它转成模拟信号才能驱动喇叭;麦克风进来的模拟信号,也得靠它转成数字信号才能被CPU处理。而控制这个芯片的,通常就是I2C和GPIO。
这一章,我们就拿DA7212这颗常见的编解码器当例子,把初始化流程和寄存器读写讲透。你在项目里换别的Codec,套路是一样的。
4.1 硬件连接:I2C与GPIO的分工
先看一眼硬件上怎么连的。DA7212跟主控(R-Car)之间,主要就两类信号线:
| 信号类型 | 引脚 | 作用 |
|---|---|---|
| I2C控制总线 | SCL, SDA | 读写内部寄存器,配置采样率、增益、电源管理等 |
| GPIO控制 | RESET# | 硬件复位,让Codec回到初始状态 |
| GPIO控制 | IRQ | 中断通知,比如插拔检测、按键事件 |
| GPIO控制 | LDO_EN | 使能内部LDO,控制电源上电时序 |
我个人习惯,先把GPIO管脚的功能确认清楚,再动I2C。因为I2C通信的前提是Codec已经退出复位、电源稳定。顺序搞反了,I2C怎么都扫不到设备地址,这种坑我踩过不止一次。
4.2 初始化流程:先复位,再配置
初始化Codec,我一般分三步走。你想想看,这跟给一个新员工办入职差不多:先让他站起来(复位),再告诉他工位在哪(电源),最后分配任务(寄存器配置)。
- 硬件复位:拉低RESET#引脚至少5ms,再释放拉高。
- 电源稳定等待:释放复位后,等待内部LDO和参考电压建立,通常需要10-20ms。
- I2C寄存器配置:通过I2C写入初始化序列,包括系统时钟、音频接口格式、采样率、通路开关等。
下面是我在QNX下写的一个初始化函数片段,你可以直接参考:
#include <hw/i2c.h>
#include <sys/gpio.h>
/* DA7212寄存器地址定义 */
#define DA7212_SYSTEM_CLK 0xC0
#define DA7212_PLL_CTRL 0xC1
#define DA7212_PC_COUNT 0xC2
#define DA7212_SR 0xC3
#define DA7212_DAI_CTRL1 0xC4
#define DA7212_MIXIN_L_SEL 0xC5
#define DA7212_MIXIN_R_SEL 0xC6
int da7212_init(i2c_handle_t i2c, gpio_handle_t gpio, int reset_pin) {
/* 第一步:硬件复位 */
gpio_set_value(gpio, reset_pin, GPIO_LOW);
delay_ms(10); /* 至少5ms,我习惯给10ms */
gpio_set_value(gpio, reset_pin, GPIO_HIGH);
delay_ms(20); /* 等待内部电源稳定 */
/* 第二步:配置系统时钟 */
uint8_t buf[2];
buf[0] = DA7212_SYSTEM_CLK;
buf[1] = 0x01; /* 选择MCLK作为时钟源 */
i2c_write(i2c, 0x1A, buf, 2);
/* 第三步:配置PLL */
buf[0] = DA7212_PLL_CTRL;
buf[1] = 0x80; /* 使能PLL,设置分频系数 */
i2c_write(i2c, 0x1A, buf, 2);
/* 第四步:设置采样率 */
buf[0] = DA7212_SR;
buf[1] = 0x03; /* 48kHz */
i2c_write(i2c, 0x1A, buf, 2);
/* 第五步:配置DAI接口格式 */
buf[0] = DA7212_DAI_CTRL1;
buf[1] = 0x11; /* I2S格式,24位数据 */
i2c_write(i2c, 0x1A, buf, 2);
return EOK;
}
4.3 I2C寄存器读写:封装与调试
QNX下操作I2C,通常通过i2c-io库或者直接操作/dev/i2cN设备节点。我建议你封装两个基础函数:单字节读写和多字节读写。这样上层代码就干净多了。
/* 单字节写 */
int da7212_reg_write(i2c_handle_t i2c, uint8_t reg, uint8_t val) {
uint8_t buf[2] = {reg, val};
return i2c_write(i2c, 0x1A, buf, 2);
}
/* 单字节读 */
int da7212_reg_read(i2c_handle_t i2c, uint8_t reg, uint8_t *val) {
/* 先写寄存器地址 */
int ret = i2c_write(i2c, 0x1A, ®, 1);
if (ret != EOK) return ret;
/* 再读数据 */
return i2c_read(i2c, 0x1A, val, 1);
}
调试的时候,我习惯在驱动里加一个debugfs接口,可以直接从命令行读写任意寄存器。比如:
# 读寄存器0xC0
cat /sys/kernel/debug/da7212/regs/0xC0
# 写寄存器0xC1为0x80
echo 0xC1 0x80 > /sys/kernel/debug/da7212/regs
这个习惯帮我省了大量反复编译驱动的时间。你想想看,调音频驱动的时候,采样率不对、音量太小、有杂音,你怀疑是某个寄存器没配对。如果每次都要改代码、编译、烧写、重启,半天就过去了。有了debugfs接口,一条命令就能验证猜想。
4.4 核心知识结构
我把这一章的核心逻辑画成了一张图,方便你理解整个初始化链条:
4.5 避坑指南:我踩过的几个雷
做音频驱动,Codec初始化这块坑不少。我挑几个典型的说说:
- 复位时序不够长:有些Codec的复位引脚有内部滤波,脉冲太短根本触发不了。我曾经只给了1ms,结果I2C死活不通。后来用示波器一看,复位引脚根本没拉到底。改成10ms就解决了。
- 寄存器写后不回读:I2C总线可能受干扰,写操作看起来成功了,实际寄存器值没变。我建议关键寄存器(比如PLL配置)写完后马上回读一次,确认一致。
- 忽略电源域顺序:DA7212有多个电源域(数字、模拟、PLL),手册里规定了上电顺序。如果先给模拟部分供电再给数字部分,芯片可能进入异常状态。严格按照手册来。
- 中断引脚没配置:IRQ引脚默认可能是高阻态,需要外部上拉。如果没接上拉电阻,中断信号根本送不到R-Car。硬件设计阶段就要确认。
嗯,这一章的内容就这些。你先把DA7212的初始化跑通,后面讲音频通路和音量控制的时候,我们就在这个基础上继续搭。