I2C/IIC(集成电路总线)是philips推出的一种串行总线。
如图,
可以这么理解,
具体解释如下:
如图,
如图,
1. 当SCL为低电平时,SDA的状态是允许切换的,即发送的数据要在时钟是低电平时输出
2. 当SCL是高电平时,SDA的状态是不能变化的,即需要读取的数据要在时钟是高电平时读走
如图,当SCL是高电平的时候,SDA由高电平变化到低电平
如图,
如图,
如图,当SCL是高电平时,SDA由低电平变化到高电平
如图,我们通过I2C总线完成对EEPROM AT24C02的读写操作,A0,A1,A2都接地,即该芯片物理地址是0
要实现对AT24C02C读写操作,我们需要了解AT24C02C的基本功能和要求,这些可以从Datasheet中找到,例如
如下图,芯片复位时长在130到270ms之间,稳妥起见,上电后至少应该在270ms后在对该芯片进行操作
如下图,SCL和SDA的每个电平的时间都做了详细的说明,编码时需要严格按照该时间要求编码。
写操作支持两种,手册里也有详细介绍
为了更深入的理解I2C总线协议,此例中没用硬件I2C控制器,而是用GPIO口模拟的,原理明白了,硬件方式就更加简单了
从前面的介绍可以看出,I2C总线是通用的,不仅AT24C02C可以使用,其它的芯片也可以使用,且协议规范是一样的,因此分成三部分,这样三部分的代码在后续的编码中可以做到通用
下面三部分的编码与上面原理和手册中的时序是完全匹配的,可以对照阅读
#define I2C_Set1(i2c) GPIO_SetBits(i2c);I2C_Delay(5);
#define I2C_Set0(i2c) GPIO_ResetBits(i2c);I2C_Delay(5);
#define I2C_Get(i2c) GPIO_ReadInputBit(i2c);
VOID DRV_I2C_Start(VOID)
{
I2C_SetOutput(I2C_SDA);
I2C_Set1(I2C_SDA);
I2C_Set1(I2C_SCL);
I2C_Set0(I2C_SDA);
I2C_Set0(I2C_SCL);
}
VOID DRV_I2C_Stop(VOID)
{
I2C_SetOutput(I2C_SDA);
I2C_Set0(I2C_SDA);
I2C_Set1(I2C_SCL);
I2C_Set1(I2C_SDA);
}
U32 DRV_I2C_WriteByte(IN U8 data)
{
U8 i = 0;
U8 byte = data;
U8 sda = 0;
I2C_SetOutput(I2C_SDA);
for (i = 0; i < 8; i++)
{
I2C_Set0(I2C_SCL);
if (byte & 0x80)
{
I2C_Set1(I2C_SDA);
}
else
{
I2C_Set0(I2C_SDA);
}
I2C_Set1(I2C_SCL);
byte <<= 1;
}
I2C_Set0(I2C_SCL);
I2C_SetInput(I2C_SDA);
I2C_Set1(I2C_SCL);
sda = I2C_Get(I2C_SDA);
if (sda)
{
I2C_Set0(I2C_SCL);
I2C_SetOutput(I2C_SDA);
return OS_ERROR;
}
I2C_Set0(I2C_SCL);
I2C_SetOutput(I2C_SDA);
I2C_Set1(I2C_SDA);
return OS_OK;
}
U32 DRV_I2C_ReadByte(OUT U8 *byte)
{
U8 i = 0;
U8 bit = 0;
U8 sda = 0;
I2C_SetInput(I2C_SDA);
for (i = 0; i < 8; i++)
{
I2C_Set1(I2C_SCL);
sda = I2C_Get(I2C_SDA);
if (sda)
{
bit |= 0x1;
}
I2C_Set0(I2C_SCL);
if (i != 7)
{
bit <<= 1;
}
}
*byte = bit;
return OS_OK;
}
VOID DRV_I2C_NoAck(VOID)
{
I2C_Set0(I2C_SCL);
I2C_SetOutput(I2C_SDA);
I2C_Set1(I2C_SDA);
I2C_Set1(I2C_SCL);
I2C_Set0(I2C_SCL);
}
VOID DRV_I2C_Ack(VOID)
{
I2C_Set0(I2C_SCL);
I2C_SetOutput(I2C_SDA);
I2C_Set0(I2C_SDA);
I2C_Set1(I2C_SCL);
I2C_Set0(I2C_SCL);
}
VOID DRV_I2C_Init(VOID)
{
GPIO_InitPara GPIO_InitStructure;
RCC_APB2PeriphClock_Enable(RCC_APB2PERIPH_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_SPEED_50MHZ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_PIN_6);
GPIO_SetBits(GPIOB, GPIO_PIN_7);
}
下面的函数接口与手册中的读写接口一一对应,其中立即地址读没有做,因为不常用。
/* 字节写 */
VOID DRV_AT24C02C_WriteByte(IN U8 slaveAddr, IN U8 byteAddr, IN U8 data)
{
DRV_I2C_Start();
DRV_I2C_WriteByte(slaveAddr);
DRV_I2C_WriteByte(byteAddr);
DRV_I2C_WriteByte(data);
DRV_I2C_Stop();
}
/* 页写 */
VOID DRV_AT24C02C_WritePage(IN U8 slaveAddr, IN U8 byteAddr, IN U8 data[], IN U8 len)
{
U8 i = 0;
DRV_I2C_Start();
DRV_I2C_WriteByte(slaveAddr);
DRV_I2C_WriteByte(byteAddr);
for (i = 0; i < len; i++)
{
DRV_I2C_WriteByte(data[i]);
}
DRV_I2C_Stop();
}
/* 选择地址读 */
VOID DRV_AT24C02C_ReadByte(IN U8 slaveAddr, IN U8 byteAddr, OUT U8 *data)
{
U8 tmp = 0;
DRV_I2C_Start();
DRV_I2C_WriteByte(slaveAddr);
DRV_I2C_WriteByte(byteAddr);
DRV_I2C_Start();
DRV_I2C_WriteByte(slaveAddr+1);
DRV_I2C_ReadByte(&tmp);
DRV_I2C_NoAck();
DRV_I2C_Stop();
*data = tmp;
}
/* 连续读 */
VOID DRV_AT24C02C_ReadPage(IN U8 slaveAddr, IN U8 byteAddr, OUT U8 data[], IN U8 len)
{
U8 tmp = 0;
U8 i = 0;
DRV_I2C_Start();
DRV_I2C_WriteByte(slaveAddr);
DRV_I2C_WriteByte(byteAddr);
DRV_I2C_Start();
DRV_I2C_WriteByte(slaveAddr+1);
for (i = 0; i < len-1; i++)
{
DRV_I2C_ReadByte(&tmp);
DRV_I2C_Ack();
data[i] = tmp;
}
DRV_I2C_ReadByte(&tmp);
DRV_I2C_NoAck();
data[i] = tmp;
DRV_I2C_Stop();
}
VOID DRV_AT24C02C_Init(VOID)
{
DRV_I2C_Init();
}
下面的代码,只是为了举例说明如何使用上述接口而已,其中0xA0的含义如下图,其中R/W位接口内部有处理,此处统一填了0。
#define I2C_AT24C02C_ADDR 0xA0
VOID APP_I2C_Test(VOID)
{
U8 len = 255;
U8 databufIn[5] = {0};
U8 databufOut[255] = {0};
U8 dataOut = 0;
U8 byteAddr = 0x00;
DRV_AT24C02C_Init();
dataOut = 0;
DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
APP_DEBUG("read=0x%x", dataOut);
DRV_AT24C02C_WriteByte(I2C_AT24C02C_ADDR, byteAddr, 0x11);
APP_Delay(10);
DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
APP_DEBUG("read=0x%x", dataOut);
DRV_AT24C02C_WriteByte(I2C_AT24C02C_ADDR, byteAddr, 0x2);
APP_Delay(10);
DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
APP_DEBUG("read=0x%x", dataOut);
DRV_AT24C02C_WriteByte(I2C_AT24C02C_ADDR, byteAddr, 0xff);
APP_Delay(10);
DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
APP_DEBUG("read=0x%x", dataOut);
DRV_AT24C02C_ReadPage(I2C_AT24C02C_ADDR, byteAddr, databufOut, len);
I2C_Dump(databufOut, len);
DRV_AT24C02C_WritePage(I2C_AT24C02C_ADDR, byteAddr, databufIn, 5);
APP_Delay(10);
DRV_AT24C02C_ReadPage(I2C_AT24C02C_ADDR, byteAddr, databufOut, len);
I2C_Dump(databufOut, len);
while(1);
}
https://github.com/YaFood/GD32F103/tree/master/TestI2C