概念
IIC,或者I2C,年"I方C",集成电路总线,是同步串行半双工通信总线方式。该总线允许同时连接多个设备(芯片),每个芯片在总线上有特定的地址

SDA:数据线
SCL:时钟线
两个信号线都是双向的,在硬件电路设计上,是必须要接上拉电阻,因为这是一种双开漏的通信模式,引脚只能主动拉低电平,无法主动输出高电平,需要上拉电阻来提供稳定的高电平,同时实现多设备 "线与" 通信逻辑
开漏和推挽
GPIO通过两个MOS管来控制引脚的高低电平

开漏模式:当GPIO处于该模式下,只允许出现2,3情景,高阻态下,相当于断路
推挽模式:当GPIO处于该模式下,只允许出现1,2情景
时序
总线处于空闲时,不管是SCL,还是SDA线,均处于高电平状态,所以总线上的设备都处于开漏模式中的高阻态。当某个设备想要通信时,就先在总线上发送start起始信号,把SDA拉低,此时会在SCL上产生时钟信号,是高低电平交错的脉冲信号
1,start信号:在总线空闲时(高电平),主机在时钟线SCL为高电平时,在数据线SDA上产生一个下降沿,称之为起始信号
2,发送数据时
1)遵循高位优先原则MSB
2)在SCL为低电平时,只允许发送方改变SDA数据线(高1低0),接收方不得采样SDA
3)在SCL为高电平时,只允许接收方采样SDA数据线,此时发送方需保证数据线稳定
3,应答位(9clock第九个bit)
1)ACK:应答,类似真人回复
2)NACK:非应答,类似自动回复
4,stop型号:在主机不需要再通信时,主机在SCL为高电平时,在数据线SDA上产生一个上升沿,称之为停止信号


从机地址
为了确定接收的从机,规定发送的第一个字节必须具有特定含义,前七个bit位为从机地址,第八个为数据流向位,指的是通信的过程的收发方,0为主机发送从机接收,1则相反

线与特性
|-------|-------|------|
| chipA | chipB | 实际电平 |
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 1 | 1 |
| 0 | 0 | 0 |
当一个芯片输出高电平,真实的引脚电平由外接的芯片、上下拉电路决定
PSR引脚状态寄存器

所需寄存器


写函数
cs
void i2c_write(I2C_Type *base, unsigned char dev_addr, unsigned short reg_addr, int reg_len, unsigned char *data, unsigned int len)
{
int stat = 0;
//0. 清除IAL、IIF,
base->I2SR &= ~(I2SR_IAL | I2SR_IIF);
//1. 设置为发送模式
base->I2CR |= I2CR_MTX;
//2.start信号
base->I2CR |= I2CR_MSTA;
//3.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 0);
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
//4.发送从机寄存器地址
int i = reg_len - 1;
for (; i >=0; i--)
{
base->I2DR = (reg_addr >> (i * 8)) & 0xFF;
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
}
for (i = 0; i < len; i++)
{
base->I2DR = data[i];
stat = wait_i2c_iif(base);
#if AT24C02
if (((reg_addr + (i + 1)) % 8 == 0) && ((i + 1) < len))
{
//发送stop信号
base->I2CR &= ~I2CR_MSTA;
delay_ms(5);
//2.start信号
base->I2CR |= I2CR_MSTA;
//3.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 0);
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
//4.发送从机寄存器地址
int j = reg_len - 1;
for (; j >= 0; j--)
{
base->I2DR = ((reg_addr + (i + 1)) >> (j * 8)) & 0xFF;
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
}
}
#endif
if (stat != 0) goto stop;
}
stop:
//发送stop信号
base->I2CR &= ~I2CR_MSTA;
while ((base->I2SR & I2SR_IBB) != 0)
{
//设计超时机制
}
#if AT24C02
delay_ms(5);
#endif
}
读函数
cs
void i2c_read(I2C_Type *base, unsigned char dev_addr,unsigned short reg_addr, int reg_len, unsigned char *data, unsigned int len)
{
int stat = 0;
//0. 清除IAL、IIF,
base->I2SR &= ~(I2SR_IAL | I2SR_IIF);
//1. 设置为发送模式
base->I2CR |= I2CR_MTX;
//2.start信号
base->I2CR |= I2CR_MSTA;
//3.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 0);
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
//4.发送从机寄存器地址
int i = reg_len - 1;
for (; i >= 0; i--)
{
base->I2DR = (reg_addr >> (i * 8)) & 0xFF;
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
}
//5.重发start
base->I2CR |= I2CR_RSTA;
//6.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 1);
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
//7. 设置为接收模式
base->I2CR &= ~I2CR_MTX;
//8. 设置应答类型
if (len > 1)
{
base->I2CR &= ~I2CR_TXAK; //ACK
} else {
base->I2CR |= I2CR_TXAK; //NACK
}
// 9.触发一次伪读;
data[0] = base->I2DR;
for (i = 0; i < len; i++)
{
stat = wait_i2c_iif(base);
if (i == len - 2){
base->I2CR |= I2CR_TXAK; //倒数第二个字节读数据前,设置应答类型为NACK,开始触发接收 倒数第一个字节。
}
if (i == len - 1){
base->I2CR |= I2CR_MTX; //倒数第一个字节读数据前 修改为发送模式(避免触发新的读取操作)
}
data[i] = base->I2DR;
if (stat != 0) goto stop;
}
stop:
//发送stop信号
base->I2CR &= ~I2CR_MSTA;
while ((base->I2SR & I2SR_IBB) != 0)
{
//设计超时机制
}
}