I2C总线
SCL:时钟,通常时钟都是由master提供的
SDA:数据
1. 接线
主设备——从设备(两根线都要接上拉电阻)
SDA脚——SDA脚
SCL脚——SCL脚
2. 协议
- I2C设备都有设备地址,可以多对多(多个主设备,多个从设备)
一对多:这种比较常见(我还没遇到过多对多,手动滑稽),也不会有太多问题
多对多:需要确保时钟(SCL)同步 - SCL时钟速率不要高于从设备
2.1 数据传输步骤如下
- 开始条件:SDA为高电平(1),SCL为高电平时(1),SDA由高向低跳变(下降沿)
- 发送数据:8个数据位,先传最高位(MSB),然后释放SDA线(1)(拉高电平)
SCL为低电平时(0),SDA可进行电平转换;
SCL为高电平时(1),SDA保持不变,接收设备(主设备或从设备)读取数据。 - 接收响应:ACK,在第九个周期(可以说是第九个数据位)
接收设备拉低SDA(0);
答复发送设备,接收完成; - 结束条件:SDA为低电平(0),SCL为高电平时(1),SDA由低向高跳变(上升沿)
- 重复开始条件:master需要在一次通信中进行多次消息交换(例如与不同的slave传输消息,或切换读写操作)
开始和结束不一一对应,可以发送多次开始条件,然后只有一个结束条件
a. 主设备发送地址帧(地址+写),发送要读取的寄存器地址(如0xA0)
b. 主设备发送地址帧(地址+读),接收(0xA0)寄存器数据
- 时钟拉伸(clock stretching):
有时候,低速slave可能由于上一个请求还没处理完,尚无法继续接收master的后续请求,即master的数据传输速率超过了slave的处理能力。这种情况下,slave可以进行时钟拉伸来要求master暂停传输数据 —— 通常时钟都是由master提供的,slave只是在SDA上放数据或读数据。
而时钟拉伸则是slave在master释放SCL后,将SCL主动拉低并保持,此时要求master停止在SCL上产生脉冲以及在SDA上发送数据,直到slave释放SCL(SCL为高电平)。之后,master便可以继续正常的数据传输了。可见时钟拉伸实际上是利用了时钟同步的机制,只是时钟由slave产生。
如果系统中存在这种低速slave并且slave实现了clock stretching,则master必须实现为能够处理这种情况,实际上大部分slave设备中不包含SCL驱动器的,因此无法拉伸时钟。
所以更完整的I2C数据传输时序图为:
2.2 传输详细内容
- 地址帧(address frame):(用于master指明消息发往哪个slave)
地址帧:7个地址位+1个读写位(0写1读),也支持10bit地址
地址位:确保芯片Datasheet给的是几位地址位,有的给的7位,有的给的8位(需要左移);直接给地址帧,带读/写
10bit地址帧:b1111 0XX(W/R)+bXXXX XXXX;
如下图所示
- 数据帧(data frames): 由master发往slave的数据(或由slave发往master),每一帧是8-bit的数据
- 根据 (1地址帧) 的读写位,确定发送/接收设备
写:和发送地址帧一样,主设备(发送),从设备(接收);
读:反过来,主设备(接收),从设备(发送);
后续的数据传输中,发送设备发送数据,接收设备需要发送ACK
调试方法
硬件
如果是无法配置pad的芯片,需要接(SCL、SDA)引脚要上拉
软件
/* Enable I2C clock */
RCC_PeriphClockCmd(OLED_I2C_APBPeriph, OLED_I2C_APBPeriph_CLOCK, ENABLE);
/* pinmux */
Pinmux_Config(OLED_I2C_SCL, OLED_I2C_CLK);
Pinmux_Config(OLED_I2C_SDA, OLED_I2C_DAT);
/* pad */
Pad_Config(OLED_I2C_SCL, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_HIGH);
Pad_Config(OLED_I2C_SDA, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_HIGH);
/* I2C IO config*/
I2C_InitTypeDef I2C_InitStructure;
I2C_StructInit(&I2C_InitStructure);
I2C_InitStructure.I2C_ClockSpeed = OLED_I2C_SPEED;
I2C_InitStructure.I2C_DeviveMode = I2C_DeviveMode_Master;
I2C_InitStructure.I2C_AddressMode = I2C_AddressMode_7BIT;
I2C_InitStructure.I2C_SlaveAddress = OLED_I2C_ADDR;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_Init(OLED_I2C_NUM, &I2C_InitStructure);
I2C_Cmd(OLED_I2C_NUM, ENABLE);
- 通过看datesheet确定IC的I2C地址,注意给的是8位还是7位,会影响到寻址
- 在软件上写条发送命令,通过逻辑分析仪查看发送数据,
- 首先确定配置的没有问题,是走的I2C协议
- 然后看发送的地址是多少,是否是我们要的地址;不同的硬件平台,有的会将地址左移(7位地址),有的不会移动(8位地址)
- 在确定发送的地址正确的情况下,看是否有应答
- 以上都没有问题的话,硬件上基本调通,然后就是发送数据了;在发送数据前确保有地址,如果是一个硬件的话,可以不用(拓展性会不好,后续要增加设备就会不方便了);
- 发送多个数据时,不同的平台可能会不一样,我遇到过发两个字节的数据,连发的话,无法初始化硬件(QAQ,可能速度跟不上~),分开一个个发送,就他喵的好了
- 综上OK都的话就可以IC进行初始化,读写数据,愉快的玩耍了^^
(每次换个平台,就调的我吐血,总结一波,这个坑我希望不要出现了,手动狗头)