一、基本概念
IIC(Inter-Integrated Circuit)又称I2C,是是IICBus简称,所以中文应该叫集成电路总线。是飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展而来的一种同步串行半双工通信总线方式。该总线允许同时连接多个设备(芯片)。每块芯片在总线上拥有特定的地址。又名内部集成电路,属于同步串行半双工。
99%处于单主机状态,地址值具有唯一性,从机最多有127个。
二、I2C的组网模式
上图所示是IIC的总线的使用场景,所有挂载在IIC总线上的设备都有两根信号线,一根是数据线SDA,另一根是时钟线SCL。这两个信号线都是双向的。
作为一种通信方式,IIC总线在某一时刻,总线只允许有一个设备处于发送状态,所发出的数据被总线上所有的设备所接收。IIC通信协议包含有设备地址,只有发送方携带的地址与某个接收方的地址相同时,接收方才真正执行相关的指令。
IIC总线规定,设备在空闲时,两根总线都处于高电平状态。为保证这种状态以及保证数据的可靠性和可靠性,数据线SDA和时钟线SCL都要外接上拉电阻。上拉电阻的阻值一般位4.7~10K。I2C 总线标准模式下速度可以达到 100Kb/S,快速模式下可以达到 400Kb/S。
若A想要拉为高电平,B想要置低,则A为释放总线,B为占据总线。空闲时保持高电平。R越小,速率越快。
三、I2C的协议时序
起始位
由于空闲时SDA和SCL都是高电平状态,每次通信前,发送方首先发送一个“起始”信号,其实信号就是在SCL为高电平时,SDA发送一个低电平。当其它设备接收到这个其实信号后,将进行一次“总线仲裁”。意思就是设备(除发送其实信号的那个设备以外的)都将处于聆听状态。
数据位
IIC总线进行数据传送时,时钟线(SCL)上的信号为高电平期间,数据线(SDA)上的数据必须保持稳定。只有在时钟线(SCL)上的信号为低电平期间,数据线(SCL)上的高电平或低电平状态才允许变化。同时,SCL信号由数据启动发送的设备提供。输出到数据线(SDA)上的每个字节必须是8位。数据传送时,先传送最高位(MSB),后传送最低位(LSB)。
应答位
发送器每发送一个字节(8个bit),就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。 主机SCL拉高,读取从机SDA的电平。对于反馈有效应答位ACK的要求是:接收器在第9个时钟脉冲之前的低电平期间将数据线SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平。
应答分两种情况:数据线(SDA)为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;数据线(SDA)为高电平时,规定为非应答位(NACK),表示接收器没有成功接收该字节。
停止位
当发送方发送完最后一个bit后,需要发送一个结束标志来终止整个通信过程。当时钟线SCL 为高电平时,数据线SDA 由低电平向高电平跳变:
综合起来一个完整地字节发送如图:
四、I2C的设备地址
由于IIC总线的多设备设计,为了能够在通信时能够将数据发送给正确的设备,每个设备都具有一个独一无二的地址。并且IIC在每次启动通信后的第一个字节就是需要接收数据的设备的地址。这一个字节的地址中其实真正的地址只有7位,最低位事实上代表的是方向位用来表示接下来数据传输的方向。0表示主设备向从设备(write)写数据,1表示主设备向从设备(read)读数据。例如某个IIC芯片的设备地址为0xA0,当主机发送0xA0时表示要向该设备写入数据,当主机发送0xA1时,表示要从该设备读取数据。
五、I2C的传输速率
按照IIC的时序描述,很容易理解IIC的波特率这个概念。很明显,IIC的传输速率取决于SCL的变换频率。。I2C 总线标准模式下速度可以达到 100Kb/S,快速模式下可以达到 400Kb/S。
主机收发时序
主机通过 I2C 总线与从机之间进行通信不外乎两个操作:写和读,I2C 总线单字节写时序如图:
先看写时序:
- 开始信号;
- 发送 I2C 设备地址,每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为1 的话表示这是一个读操作,为 0 的话表示这是一个写操作;
- I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作;
- 从机发送的 ACK 应答信号;
- 重新发送开始信号;
- 发送要写写入数据的寄存器地址;
- 从机发送的 ACK 应答信号;
- 发送要写入寄存器的数据;
- 从机发送的 ACK 应答信号;
- 停止信号。
再来看读时序,I2C 总线单字节读时序如图:
单字节读时序比写时序要复杂一点,读时序分为 4 大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是 I2C 从器件输出要读取的寄存器值,我们具体来看一下这几步
- 主机发送起始信号;
- 主机发送要读取的 I2C 从设备地址;
- 读写控制位,因为是向 I2C 从设备发送数据,因此是写信号;
- 从机发送的 ACK 应答信号;
- 重新发送 START 信号;
- 主机发送要读取的寄存器地址;
- 从机发送的 ACK 应答信号;
- 重新发送 START 信号;
- 重新发送要读取的 I2C 从设备地址;
- 读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据;
- 从机发送的 ACK 应答信号;
- 从 I2C 器件里面读取到的数据;
- 主机发出 NO ACK 信号,表示读取完成,不需要从机再发送 ACK 信号了;
- 主机发出 STOP 信号,停止 I2C 通信。
代码如下:
int i2c_finished;
void i2c_handler(void)
{
i2c_finished = 1;
}
void init_i2c(void)
{
unsigned int t;
t = GPECON;
t &= ~((3 << 28) | ((unsigned int)3 << 30));
t |= ((2 << 28) | ((unsigned int)2 << 30));
GPECON = t;
IICCON |= (1 << 7) | (1 << 6) | (1 << 5);
IICCON &= ~(0x0F << 0);
register_irq(IRQ_I2C, i2c_handler);
enable_irq(IRQ_I2C);
}
void do_and_wait_ack(void)
{
i2c_finished = 0;
IICCON &= ~(1 << 4);
while(i2c_finished == 0)
{
udelay(100);
}
}
void write_at24c02(unsigned char device_address, unsigned char reg_address, unsigned char *pData, unsigned int len)
{
unsigned int i;
IICSTAT = 0xF0;
IICDS = device_address;
do_and_wait_ack();
IICDS = reg_address;
do_and_wait_ack();
for(i = 0;i < len;++i)
{
IICDS = *pData++;
do_and_wait_ack();
}
IICSTAT = 0xD0;
IICCON &= ~(1 << 4);
udelay(400);
}
void read_at24c02(unsigned char device_address, unsigned char reg_address, unsigned char *pData, unsigned int len)
{
unsigned int i;
IICSTAT = 0xF0;
IICDS = device_address;
do_and_wait_ack();
IICDS = reg_address;
do_and_wait_ack();
IICSTAT = 0xB0;
IICDS = device_address;
do_and_wait_ack();
*pData = IICDS;
for(i = 0;i < len;++i)
{
if(len - 1 == i)
{
IICCON &= ~(1 << 7);
}
do_and_wait_ack();
*pData++ = IICDS;
}
IICCON |= (1 << 7);
IICSTAT = 0x90;
IICCON &= ~(1 << 4);
udelay(400);
}
int main(void)
{
char buffer[24] = {0};
init_wdt();
init_Clock();
init_led();
// init_pwm();
// init_key();
init_timer4();
init_i2c();
init_uart0();
led_on(0);
// write_at24c02(0xA0, 0x00, (unsigned char *)s, 5);
// mdelay(1000);
// read_at24c02(0xA0, 0x00, (unsigned char *)buffer, 5);
//
// send_buffer(buffer, 5);
while(1)
{
if(pos != 0)
{
mdelay(100);
write_at24c02(0xA0, 0x00, (unsigned char *)rcv_buffer, pos);
mdelay(500);
read_at24c02(0xA0, 0x00, (unsigned char *)buffer, pos);
send_buffer(buffer, pos);
pos = 0;
}
}
}