1. I2C协议基础与Rockchip RK3288实现解析
I2C(Inter-Integrated Circuit)总线是嵌入式系统中最常用的串行通信接口之一,以其简单的两线制(SDA数据线和SCL时钟线)和多主多从架构著称。在Rockchip RK3288这类ARM处理器上,I2C控制器通常集成在SoC内部,通过操作特定寄存器实现通信功能。
I2C协议的核心在于其时序控制。一个完整的I2C传输包含起始条件(START)、从机地址传输、数据帧传输和停止条件(STOP)。RK3288的I2C控制器通过自动处理这些时序细节,减轻了软件开发负担。例如,当配置I2C_CON寄存器的START位时,硬件会自动生成符合规范的起始信号,包括在SCL高电平时SDA的下降沿。
关键提示:I2C总线采用开漏输出设计,必须外接上拉电阻。RK3288平台典型值为4.7kΩ,但具体值需根据总线电容和通信速率调整。
2. RK3288 I2C寄存器深度剖析
2.1 控制寄存器(I2C_CON)
I2C_CON(0x000)是I2C控制的核心寄存器,其关键位域包括:
- Bit 0 (I2C_EN): 控制器使能位,任何操作前必须置1
- Bits 2-1 (MODE):
- 00: 仅发送模式
- 01: 复合模式(发送地址→重启→接收)
- 10: 仅接收模式
- Bit 5 (NAK): 主设备应答控制,1表示不应答
- Bit 6 (STOP): NAK时停止传输控制位
2.2 时钟分频寄存器(I2C_CLKDIV)
I2C_CLKDIV(0x004)决定SCL时钟频率,计算公式为:
code复制SCL频率 = PCLK / (8 × (DIVH + DIVL + 2))
其中DIVH和DIVL分别对应寄存器的高16位和低16位。RK3288的典型PCLK为148.5MHz,若要实现标准模式(100kHz):
code复制DIV = (148500000 / (8 × 100000)) - 2 = 183.625
取整后DIVH=92, DIVL=92
2.3 数据传输寄存器组
- I2C_TXDATA_BASE(0x100): 发送数据缓冲区,每次写入32位数据
- I2C_RXDATA_BASE(0x200): 接收数据缓冲区,小端格式存储
- I2C_MTXCNT(0x010)/I2C_MRXCNT(0x014): 设置待发送/接收的字节数
3. I2C底层驱动函数实现
3.1 起始信号生成(rk3288_i2c_start)
c复制void rk3288_i2c_start(struct rk3288_i2c *i2c)
{
// 清除所有中断标志
writel(0x7F, i2c->regs + I2C_IPD);
// 使能I2C并发送START条件
writel((1 << 0) | (1 << 3), i2c->regs + I2C_CON);
// 等待START中断标志置位
unsigned long timeout = jiffies + usecs_to_jiffies(10);
while (!(readl(i2c->regs + I2C_IPD) & (1 << 4))) {
if (time_after(jiffies, timeout)) {
dev_err(i2c->dev, "START condition timeout\n");
return;
}
cpu_relax();
}
// 清除START中断标志
writel(1 << 4, i2c->regs + I2C_IPD);
}
3.2 数据读取(rk3288_i2c_read)
c复制int rk3288_i2c_read(struct rk3288_i2c *i2c, struct i2c_msg *msg)
{
// 设置从设备地址(读方向)
writel((1 << 24) | (msg->addr << 1) | 1, i2c->regs + I2C_MRXADDR);
// 不使用子地址时设为0
writel(0, i2c->regs + I2C_MRXRADDR);
int len = msg->len;
u8 *buf = msg->buf;
while (len > 0) {
int bytes = min(len, 32);
u32 con = (1 << 0) | (1 << 5) | (1 << 6);
// 第一次传输使用复合模式,后续使用接收模式
con |= (len == msg->len) ? (1 << 1) : (2 << 1);
// 清除中断,设置接收字节数
writel(0x7F, i2c->regs + I2C_IPD);
writel(con, i2c->regs + I2C_CON);
writel(bytes, i2c->regs + I2C_MRXCNT);
// 等待传输完成
timeout = jiffies + msecs_to_jiffies(100);
while (1) {
u32 ipd = readl(i2c->regs + I2C_IPD);
if (ipd & (1 << 3)) { // 接收完成
writel(1 << 3, i2c->regs + I2C_IPD);
break;
}
if (ipd & (1 << 6)) { // NAK
writel(1 << 6, i2c->regs + I2C_IPD);
return -EIO;
}
if (time_after(jiffies, timeout))
return -ETIMEDOUT;
}
// 读取接收数据
u32 data = readl(i2c->regs + I2C_RXDATA_BASE);
memcpy(buf, &data, bytes);
buf += bytes;
len -= bytes;
}
return 0;
}
4. SYR827电源管理IC实战应用
4.1 寄存器配置详解
SYR827是常见的I2C接口电源管理IC,其关键寄存器包括:
-
VSEL0/VSEL1(0x00/0x01): 电压选择寄存器
- Bit 7: 输出使能(1=ON)
- Bits 5-0: 电压值(0x00=0.7125V, 0x3F=1.5V)
-
CTRL(0x02): 控制寄存器
- Bits 6-4: 压摆率控制
- 000: 5mV/μs
- 100: 10mV/2.4μs(推荐值)
- Bits 6-4: 压摆率控制
4.2 电压设置函数实现
c复制static int syr82x_vol_to_reg(int vol, int step, int min, int max)
{
if (vol < min || vol > max)
return -EINVAL;
return (vol - min) / step;
}
int regulator_syr82x_set_voltage(struct i2c_client *client, int voltage)
{
u8 val;
int ret;
// 转换电压值为寄存器值
int reg_val = syr82x_vol_to_reg(voltage, 12500, 712500, 1500000);
if (reg_val < 0)
return reg_val;
// 更新VSEL0
ret = i2c_smbus_read_byte_data(client, SYR82X_VSEL0);
if (ret < 0)
return ret;
val = (u8)ret;
val &= ~0x3F;
val |= reg_val & 0x3F;
ret = i2c_smbus_write_byte_data(client, SYR82X_VSEL0, val);
if (ret < 0)
return ret;
// 同步更新VSEL1
ret = i2c_smbus_read_byte_data(client, SYR82X_VSEL1);
if (ret < 0)
return ret;
val = (u8)ret;
val &= ~0x3F;
val |= reg_val & 0x3F;
return i2c_smbus_write_byte_data(client, SYR82X_VSEL1, val);
}
5. 调试技巧与常见问题
5.1 信号完整性测量
使用示波器检查I2C信号时需关注:
- 起始/停止条件是否干净(无毛刺)
- SCL频率是否符合预期(标准模式100kHz,快速模式400kHz)
- 信号上升时间(标准模式≤1μs)
5.2 典型错误排查
-
无应答(NACK)问题:
- 检查从设备地址是否正确(7位地址需左移1位)
- 确认从设备电源和复位正常
- 测量总线电压(SCL/SDA高电平应≥0.7×VDD)
-
时钟不同步:
- 检查I2C_CLKDIV配置
- 确认PCLK时钟源稳定
- 适当增加SCL上升时间(调整上拉电阻)
-
数据损坏:
- 检查总线电容(总电容应≤400pF)
- 确保中断处理及时(响应时间<1/4 SCL周期)
- 验证DMA配置(如使用)是否正确
5.3 性能优化建议
- 批量传输时使用DMA模式减少CPU占用
- 对时间敏感操作关闭中断(如关键时序段)
- 合理设置超时时间(典型值:标准模式5ms,快速模式1.25ms)
- 使用硬件CRC校验(RK3288支持)提高可靠性
在实际项目中,我发现RK3288的I2C控制器对信号质量较为敏感。当总线长度超过30cm时,建议:
- 将上拉电阻减小到2.2kΩ
- 在总线两端添加100Ω串联电阻
- 考虑使用I2C缓冲器(如PCA9515)扩展总线
