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条件没满足建立时间,导致从机根本不响应。
- 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表示读。
写操作流程:
- 主机发送START条件
- 主机发送从机地址 + 写位(0)
- 从机应答ACK(拉低SDA)
- 主机发送寄存器地址(1字节)
- 从机应答ACK
- 主机发送数据(1字节或多字节)
- 从机每接收一字节都应答ACK
- 主机发送STOP条件
读操作流程:
- 主机发送START条件
- 主机发送从机地址 + 写位(0)——先写寄存器地址
- 从机应答ACK
- 主机发送要读取的寄存器地址
- 从机应答ACK
- 主机发送重复START条件(Sr)
- 主机发送从机地址 + 读位(1)
- 从机应答ACK
- 从机发送数据,主机每接收一字节后发送ACK
- 主机在最后一个字节后发送NACK,然后STOP
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();
4.4 知识体系结构图
下面这张图,是我自己总结的I2C通信在VCM驱动中的核心逻辑。你看一遍,基本就清楚了。
这张图把I2C通信在VCM驱动中的三个核心要素串起来了。你调试时,按这个顺序排查问题,基本不会跑偏。
4.5 实战中的几个坑
最后,我分享几个实战中遇到的坑,希望能帮你少走弯路。
- 上拉电阻不能省:I2C总线必须接上拉电阻,典型值4.7kΩ。我曾经见过有人用内部上拉,结果总线电平拉不上去,通信时好时坏。
- 地址左移一位:很多新手写代码时,忘了把7位地址左移1位。比如从机地址0x18,发送时应该是0x30(左移1位)加上读写位。这个错误我犯过不止一次。
- 应答检查:每次发送完地址或数据后,一定要检查从机是否应答。如果没应答,赶紧停止重试,别傻等。
- 寄存器地址顺序:VCM驱动芯片的寄存器地址,有些是从0x00开始,有些是从0x01开始。务必看数据手册确认。
好了,I2C通信协议在VCM驱动中的应用,就讲到这里。记住:时序是基础,地址是关键,寄存器是目标。把这三点吃透了,VCM驱动就成功了一半。
公众号:蓝海资料掘金营,微信deep3321