4. I2C通信协议详解:I2C总线时序、从机地址与读写操作、VCM驱动芯片的I2C寄存器映射

各位做VCM驱动的朋友,大家好。今天我们来聊聊I2C通信协议。

说实话,I2C这玩意儿在嵌入式领域太常见了。但越是常见的东西,越容易出问题。我见过不少工程师,调VCM驱动时卡在I2C通信上,波形一看全是毛刺,地址死活应答不了。嗯,今天我就把I2C在VCM驱动中的应用,掰开了揉碎了讲清楚。

4.1 I2C总线时序:从START到STOP

I2C总线就两根线:SCL(时钟)和SDA(数据)。说白了,就是一根线负责打拍子,一根线负责传数据。

空闲状态:SCL和SDA都是高电平。这是总线释放的状态。

起始条件(START):SCL为高时,SDA从高变低。这个下降沿告诉所有从机:“注意了,主机要开始通信了!”

停止条件(STOP):SCL为高时,SDA从低变高。这个上升沿告诉从机:“本次通信结束。”

我个人习惯,在写驱动代码时,会特意用逻辑分析仪抓一下START和STOP的波形。为什么?因为很多MCU的I2C外设,在时序紧张时容易把START和STOP搞混。我曾经在一个项目里,就因为START条件没满足建立时间,导致从机根本不响应。

关键时序参数(标准模式100kHz):
  • SCL时钟频率:≤100kHz
  • START保持时间:≥4.7μs
  • 数据建立时间:≥250ns
  • 数据保持时间:≥0ns
  • STOP建立时间:≥4.7μs
  • 总线空闲时间:≥4.7μs

你想想看,如果SCL频率跑到400kHz(快速模式),这些时间都要按比例缩短。但VCM驱动芯片通常只支持标准模式,所以别贪快。

4.2 从机地址与读写操作

每个I2C从机都有一个7位或10位的地址。VCM驱动芯片几乎都是7位地址。比如常见的DW9714,它的7位地址是0x18(二进制:0011000)。

但注意了,I2C通信时,主机发送的地址是8位的——7位地址 + 1位读写位。读写位为0表示写,为1表示读。

写操作流程:

  1. 主机发送START条件
  2. 主机发送从机地址 + 写位(0)
  3. 从机应答ACK(拉低SDA)
  4. 主机发送寄存器地址(1字节)
  5. 从机应答ACK
  6. 主机发送数据(1字节或多字节)
  7. 从机每接收一字节都应答ACK
  8. 主机发送STOP条件

读操作流程:

  1. 主机发送START条件
  2. 主机发送从机地址 + 写位(0)——先写寄存器地址
  3. 从机应答ACK
  4. 主机发送要读取的寄存器地址
  5. 从机应答ACK
  6. 主机发送重复START条件(Sr)
  7. 主机发送从机地址 + 读位(1)
  8. 从机应答ACK
  9. 从机发送数据,主机每接收一字节后发送ACK
  10. 主机在最后一个字节后发送NACK,然后STOP
避坑指南: 我曾经在调试时,读操作一直失败。后来发现是重复START条件没处理好。有些MCU的I2C外设,在发送完寄存器地址后,需要先发STOP再发START,而不是直接发重复START。这取决于硬件设计,务必看数据手册。

4.3 VCM驱动芯片的I2C寄存器映射

VCM驱动芯片的寄存器,说白了就是一堆控制位和数据位。以某款常用芯片为例,它的寄存器映射如下:

寄存器地址 寄存器名称 位宽 描述
0x00 CONTROL 8位 控制寄存器:使能、模式选择等
0x01 DAC_DATA_H 8位 DAC数据高8位
0x02 DAC_DATA_L 8位 DAC数据低8位(实际只用到低4位)
0x03 STATUS 8位 状态寄存器:忙标志、错误标志等
0x04 CONFIG 8位 配置寄存器:时钟分频、驱动电流等

你看,DAC数据是12位的,但分成了两个寄存器:高8位和低4位。写数据时,必须先写高8位,再写低4位。顺序不能乱。

我举个例子,假设你要让VCM马达走到1000(十进制),对应的12位DAC值是0x3E8:

// 写DAC值0x3E8到VCM驱动芯片
uint8_t slave_addr = 0x18 << 1;  // 7位地址左移1位,加上读写位
uint8_t reg_high = 0x01;          // DAC_DATA_H寄存器地址
uint8_t data_high = 0x03;         // 高8位:0x3E8 >> 4 = 0x3E,取高8位实际是0x03
uint8_t data_low = 0xE8;          // 低8位:0x3E8 << 4 = 0x3E80,取高8位实际是0xE8

// 写高8位
I2C_Start();
I2C_SendByte(slave_addr | 0);  // 写操作
I2C_WaitAck();
I2C_SendByte(reg_high);
I2C_WaitAck();
I2C_SendByte(data_high);
I2C_WaitAck();
I2C_Stop();

// 写低4位(实际是低8位,但只有低4位有效)
reg_high = 0x02;  // DAC_DATA_L寄存器地址
I2C_Start();
I2C_SendByte(slave_addr | 0);
I2C_WaitAck();
I2C_SendByte(reg_high);
I2C_WaitAck();
I2C_SendByte(data_low);
I2C_WaitAck();
I2C_Stop();
注意: 有些VCM驱动芯片支持连续写模式,即一次START后连续写多个寄存器。但有些芯片不支持,必须每个寄存器单独写。我建议你一开始就按单个寄存器写的方式实现,稳定后再优化。

4.4 知识体系结构图

下面这张图,是我自己总结的I2C通信在VCM驱动中的核心逻辑。你看一遍,基本就清楚了。

I2C通信在VCM驱动中的核心逻辑 I2C总线(SCL + SDA) 总线时序 从机地址与读写操作 寄存器映射 START条件:SCL高时SDA下降沿 数据位:SCL低时改变SDA,高时采样 STOP条件:SCL高时SDA上升沿 7位从机地址 + 1位读写位 写操作:地址+写位 → 寄存器地址 → 数据 读操作:写寄存器地址 → 重复START → 读数据 CONTROL(0x00):使能、模式 DAC_DATA_H/L(0x01/02):12位DAC值 STATUS(0x03):忙/错误标志 核心:时序正确 → 地址匹配 → 寄存器读写 → VCM驱动

这张图把I2C通信在VCM驱动中的三个核心要素串起来了。你调试时,按这个顺序排查问题,基本不会跑偏。

4.5 实战中的几个坑

最后,我分享几个实战中遇到的坑,希望能帮你少走弯路。

  • 上拉电阻不能省:I2C总线必须接上拉电阻,典型值4.7kΩ。我曾经见过有人用内部上拉,结果总线电平拉不上去,通信时好时坏。
  • 地址左移一位:很多新手写代码时,忘了把7位地址左移1位。比如从机地址0x18,发送时应该是0x30(左移1位)加上读写位。这个错误我犯过不止一次。
  • 应答检查:每次发送完地址或数据后,一定要检查从机是否应答。如果没应答,赶紧停止重试,别傻等。
  • 寄存器地址顺序:VCM驱动芯片的寄存器地址,有些是从0x00开始,有些是从0x01开始。务必看数据手册确认。
我的习惯: 每次拿到新的VCM驱动芯片,我会先写一个I2C扫描程序,把总线上所有从机地址扫一遍。确认地址正确后,再开始读写寄存器。这一步能省下大量调试时间。

好了,I2C通信协议在VCM驱动中的应用,就讲到这里。记住:时序是基础,地址是关键,寄存器是目标。把这三点吃透了,VCM驱动就成功了一半。


公众号:蓝海资料掘金营,微信deep3321