IIC也称I2C,是一个多主从的串行总线,由飞利浦公司发明的通讯总线,属于半双工同步传输类总线,仅由两条线就能完成多机通讯,一条SCL时钟线,另外一条双向数据线SDA,IIC总线要求每个设备SCL/SDA线都是漏极开路模式,因此必须带上拉电阻才能正常工作。I2C协议占用引脚少,硬件实现简单,可扩展性强,I2C数据传输速率有标准模式(100kbps)、快速模式(400kbps)和高速模式(3.4Mbps)。
注:本文目前只对常用方式(一主多从)进行讲解,多主机模式后续更新。
IIC总线的SDA和SCL两根总线需要上拉,使总线处于空闲状态。IIC总线一共有两种状态、四种信号。除此之外还需要了解IIC总线的数据有效性。
SDA 和SCL 都是双向线路,都通过一个电流源或上拉电阻连接到正的电源电压。当总线空闲时,这两条线路都是高电平。连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能。
总线器件数目:由于每一个IIC器件在IIC总线上都有一个确切的7位地址码,这也意味着一条IIC总线上最多可链接127(0X00位地址不使用)个地址互不相同的IIC器件。但在单条IIC总线上链接不多与127个器件的同时,必须要满足总线电容不能超过400pF(协议规定),总线之所以规定电容大小是因为,IIC的OD要求外部有电阻上拉,电阻和总线电容产生了一个RC延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC乘积必须更小。实际设计中经验值大概是8个器件左右。
IIC总线进行数据传送时,在SCL的每个时钟脉冲期间传输一个数据位,时钟信号SCL为高电平期间,数据线SDA上的数据必须保持稳定,只有在时钟线SCL上的信号为低电平期间,数据线SDA上的高电平或低电平状态才允许变化,因为当SCL是高电平时,数据线SDA的变化被规定为控制命令(START或STOP,也就是起始信号和停止信号)。
IDLE表示总线空闲状态。此状态下时钟信号SCL和数据信号SDL均为高电 平,此时无I2C设备工作。时钟线(SCL)和数据线(SDA)接上拉电阻,默认高电平,就是为了表示总线是空闲状态。
表示起始状态。在空闲状态下,时钟信号SCL继续保持高电平,数据信号SDL出现由高电平转换为低电平的下降沿,此时产生一个起始信号,与总线相连的I2C设备检测到起始信号之后,进入起始状态等待控制字节的输入。
I2C通信的停止信号由主设备发出,SCL保持高电平,SDA由低电平跳变到高电平。
应答信号接收端收到有效数据后需要向对方响应的信号,发送端每发送一个字节(8位)数据,在第9个时钟周期释放数据线去接收对方的应答。
在第9个时钟周期:
当SDA是低电平为有效应答(ACK),表示对方接收成功;
当SDA是高电平为无效应答(NACK),表示对方没有接收成功。
注意:数据发射端需要在第9个时钟周期等待接收端的应答信号。
IIC协议的读写操作都是一字节大小,从高到低收发数据。
该例程是基于STM32芯片编写的,是软件模拟IIC协议,在使用时可按照所使用的相关芯片将SDA与SCL线进行替换和相对应的设置。
在基于STM32编程时本例程是将SCL时钟线与SDA数据线所对应的GPIO口设置成开漏输出模式并且外接上拉电阻以使IIC总线处于空闲状态,如果使用的是推挽输出模式,这需要自己配置相对应的输入输出函数。
#ifndef _IIC_H_
#define _IIC_H_
#include "main.h"
#include "gpio.h"
#define SCL GPIOB
#define SDA GPIOB
#define SCL_PIN GPIO_PIN_6
#define SDA_PIN GPIO_PIN_7
#define SCL_Read HAL_GPIO_ReadPin(SCL,SCL_PIN)
#define SDA_Read HAL_GPIO_ReadPin(SDA,SDA_PIN)
#define SCL_High HAL_GPIO_WritePin(SCL ,SCL_PIN ,GPIO_PIN_SET)
#define SCL_Low HAL_GPIO_WritePin(SCL ,SCL_PIN ,GPIO_PIN_RESET)
#define SDA_High HAL_GPIO_WritePin(SDA ,SDA_PIN ,GPIO_PIN_SET)
#define SDA_Low HAL_GPIO_WritePin(SDA ,SDA_PIN ,GPIO_PIN_RESET)
void iic_delay_us(void );
void iic_start( void );
void iic_stop( void );
uint8_t iic_wait_ack(void );
void iic_ack(void );
void iic_no_ack( void );
void iic_write_byte(uint8_t data);
uint8_t iic_read_byte(void);
#endif
#include "iic.h"
void iic_delay_us(void )
{
uint32_t time;
for(time =0;time <100;time ++);
}
/****起始条件结束后 SDA低,SCL低*****/
void iic_start( void )
{
SDA_High ;
SCL_High ;
iic_delay_us ();
SDA_Low ;
iic_delay_us ();
SCL_Low ;
iic_delay_us ();
}
/****停止条件结束后 SDA高,SCL高*****/
void iic_stop( void )
{
SDA_Low ;
SCL_High ;
iic_delay_us ();
SDA_High ;
iic_delay_us ();
}
uint8_t iic_wait_ack(void )
{
uint32_t i=3000;
SDA_High ;
iic_delay_us ();
SCL_High ;
iic_delay_us ();
while(SDA_Read)
{
i--;
if(i==0)
return 0 ; //等待超时 器件未应答
}
SCL_Low ;
iic_delay_us ();
return 1;
}
void iic_ack(void )
{
SDA_Low ;
iic_delay_us();
SCL_High ;
iic_delay_us ();
SCL_Low ;
iic_delay_us ();
SDA_High ;
iic_delay_us ();
}
void iic_no_ack( void )
{
SDA_High ;
iic_delay_us ();
SCL_High ;
iic_delay_us ();
SCL_Low ;
iic_delay_us ();
}
void iic_write_byte(uint8_t data)
{
uint8_t i=0;
for(i=0;i<8;i++)
{
if(data & 0x80) //读取最高位数据
{
SDA_High ;
}
else SDA_Low ;
iic_delay_us ();
SCL_High ;
iic_delay_us ();
SCL_Low ;
data <<=1; //数据左移1位
if(i==7)
{
SDA_High ; //第8位结束后释放SDA线
}
}
}
uint8_t iic_read_byte(void)
{
uint8_t i=0,data=0;
for(i=0;i<8;i++)
{
data <<=1;
SCL_High ;
iic_delay_us ();
if(SDA_Read )
{
data ++;
}
SCL_Low ;
iic_delay_us ();
}
return data;
}