一、 I2C 串行总线的组成及其工作原理
采用串行总线技术可以使用系统的硬件设计大大简化、系统的体积减小、可靠性提高,同事,系统的更改和扩充极为容易。
常用的串行扩展总线有 i2c (Inter IC BUS)总线,单总线(1 - WIRE BUS) SPI总线
I2C 总线式PHLIPS 公司推出的一种串行总线,式具备多主机系统所需的包括总线裁决和高低速器件同步功能的高性能串行总线。
I2C 总线只有两根双向信号线,一根是数据线SDA,另一根是时钟线SCL。
I2C 总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连接总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDK 以及SCL 都是线“与”的关系
二、 I2C 总线的数据传送
1.数据位的有效性规定
I2C 总线进行数据传送时,时钟信号为高电平器件,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平的期间,数据线上的高电平或低电平状态才允许变化。
2. 起始和终止信号
SCL线为高电平期间,SDA线由高电平向低电平的变化为起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号发生后,总线就处于空闲状态。
连接到I2C总线的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。
接收器件收到一个完整的数据字节后,由可能需要完成一些其他的工作,如处理内部中断服务等,可能无法立刻接收下一个字节,这时接收器件可以将SCL线拉成低电平,从而使主机处于等待状态。直到接收器件准备好接收下一个字节时,再释放SCL线使之为高电平,从而使数据传送可以继续进行。
三、 数据传送格式
1. 字节传送与应答
每一个字节必须保证是8位长度。数据传送时,先传送最高位,每一个被传送的字节后面都必须跟随一位应达位。即一帧共有9位
从最高位起,传送8位数据,第9位是等待应答,如果无应答,则默认失败。
由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据),它必须将数据线置于高电平,而由主机产生一个终止信号以结束总线的数据传送。
如果从机对主机进行了应答,但再数据传送一段事件后无法继续接收更多的数据时,从机可以通过对无法接收的第一个数据字节的“非应答”通知主机,主机则应发出终止信号以结束数据的继续传送。
当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号时由对从机的“非应答”来实现的。 然后,从机释放SDA线,以允许主机产生终止信号。
2.数据帧格式
I2C 总线上传送的数据信号是广义的,既包括地址信息,又包括真正的数据信号。
再起始信号后必须发送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用 “0” 表示主机发送数据“ T ”,“ 1 ” 表示主机接收数据(R)。 每次数据传送总是由主机产生的终止信号结束。但是若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
a. 主机向从机发送数据,数据传送方向再整个传送过程不变;
注: 由阴影的不分表示数据由主机向从机传送,无阴影的不分则表示数据由从机向主机传送
A表示应答,A非表示非应答(高电平),S表示起始信号,P表示终止信号。
b. 主机在第一个字节后,立即从从机读数据
c. 在传送过程中,当需要改变方向时,起始信号禾从机地址都被重复产生一次,但两次读/写方向位正好相反。
四、 总线的寻址
I2C 总线协议由明确的规定:采用7位的寻址字节(寻址字节是起始信号后的第一个字节)
1.寻址字节的位定义
第7位 ~第1位组成从机的地址。 第0位是数据的传送方向 , ” 0 “ 表示主机向从机发送数据(写数据T),” 1“ 表示主机由从机读数据
五、中科蓝讯I2C使用方法
GPIO的配置(使用PE6 当SCL时钟线, PE7 当SDA数据线)
#if 1
#define I2C_DATA 7
#define I2C_CLK 6
#define I2C_SCL_IN() GPIOEDIR |= BIT(I2C_CLK); GPIOEPU |= BIT(I2C_CLK)
#define I2C_SCL_OUT() GPIOEDE |= BIT(I2C_CLK); GPIOEDIR &= ~BIT(I2C_CLK)
#define I2C_SCL_H() GPIOESET = BIT(I2C_CLK)
#define I2C_SCL_L() GPIOECLR = BIT(I2C_CLK)
#define I2C_SDA_IN() GPIOEDIR |= BIT(I2C_DATA); GPIOEPU |= BIT(I2C_DATA)
#define I2C_SDA_OUT() GPIOEDE |= BIT(I2C_DATA); GPIOEDIR &= ~BIT(I2C_DATA)
#define I2C_SDA_H() GPIOESET = BIT(I2C_DATA);
#define I2C_SDA_L() GPIOECLR = BIT(I2C_DATA);
#define I2C_SDA_IS_H() (GPIOE & BIT(I2C_DATA))
#define I2C_SDA_SCL_OUT() {I2C_SDA_OUT(); I2C_SCL_OUT();}
#define I2C_SDA_SCL_H() {I2C_SDA_H(); I2C_SCL_H();}
#endif
I2C 初始化
AT(.text.bsp.i2c) //通过IO来模拟I2C通信
void bsp_i2c_init(void) //I2C 初始化
{
I2C_SDA_SCL_OUT(); //设置SDA SCL为输出方向(可控制高低电平)
I2C_SDA_H(); //设置SDA 为高电平
delay_5ms(2); //延迟10ms
}
#endif
起始信号 : ( 时钟信号高电平时,数据信号出现由高转低电平,为起始信号)
//START: A HIGH to LOW transition on the SDA line while SCL is HIGH is one such unique case.
AT(.text.bsp.i2c)
void bsp_i2c_start(void) //起始信号 时钟信号高电平时,数据信号出现由高转低电平,为起始信号
{
I2C_SDA_SCL_OUT(); //设置SDA SCL为输出方向(可控制高低电平)
I2C_SDA_SCL_H(); //设置SDA SCL为高电平
bsp_i2c_delay(); //延迟5us
I2C_SDA_L(); //设置SDA 低电平
bsp_i2c_delay(); //延迟5us
I2C_SCL_L(); //设置SCL 低电平
}
终止信号: ( 时钟信号高电平时,数据信号出现由低转高电平,为终止信号)
//STOP: A LOW to HIGH transition on the SDA line while SCL is HIGH
AT(.text.bsp.i2c)
void bsp_i2c_stop(void) //终止信号 时钟信号高电平时,数据信号出现由低转高电平,为终止信号
{
I2C_SDA_OUT(); //设置SDA SCL为输出方向(可控制高低电平)
I2C_SDA_L(); //设置SDA 为低电平
bsp_i2c_delay(); //延迟5us
I2C_SCL_H(); //设置SCL 为高电平
bsp_i2c_delay(); //延迟5us
I2C_SDA_H(); //设置SDA为高电平
}
给从端发送数据(此处的表示外接的支持I2C设备)
//tx 1byte
AT(.text.bsp.i2c)
void bsp_i2c_tx_byte(uint8_t dat) //通过i2c发送数据 , dat 为次数要发送的数据
{
u8 i;
I2C_SDA_OUT(); //设置SDA 为输出方向
for (i=0; i<8; i++) { //8位数据
if (dat & BIT(7)) { // 高位为1,则SDA 为高电平
I2C_SDA_H();
} else {
I2C_SDA_L(); // 高位为0,则SDA 为高电平
}
bsp_i2c_delay(); //延迟5us
I2C_SCL_H(); //时钟为高电平
bsp_i2c_delay(); //延迟5us
I2C_SCL_L(); //时钟为低电平
dat <<= 1;
}
}
主机接收数据
//rx 1byte
AT(.text.bsp.i2c)
uint8_t bsp_i2c_rx_byte(void) //通过i2c接收数据(读数据)
{
u8 i, dat = 0;
I2C_SDA_IN(); //设置数据为输入方向
for (i=0; i<8; i++) { // 每次接收8位数据,即1个字节
bsp_i2c_delay(); //延迟5us
I2C_SCL_H(); // 设置时钟高电平
bsp_i2c_delay(); //延迟5us
dat <<= 1;
if (I2C_SDA_IS_H()) { //判断SDA 是否为高电平
dat |= BIT(0);
}
I2C_SCL_L();
}
return dat;
}
主机发送无效应答
//NACK: The transmitter holds the SDA line (keep HIGH) during the acknowledge clock pulse
//第8个字节后,第九个周期为应答
//当SCL高电平时,SDA也是高电平,属于无效应答
AT(.text.bsp.i2c)
void bsp_i2c_tx_nack(void)
{
I2C_SDA_OUT(); //设置方向为输出方向,控制SDA的高低
I2C_SDA_H(); //SDA 设置高电平
bsp_i2c_delay(); //延时5us
I2C_SCL_H(); //设置SCL高电平
bsp_i2c_delay(); //延时5us
I2C_SCL_L(); //设置SCL低电平
}
接收应答(无效应答 与有效应答)
AT(.text.bsp.i2c)
//第8个字节后,第九个周期为应答
//当SCL高电平时;SDA是低电平,属于有效应答,SDA为高电平,则为无效应答
bool bsp_i2c_rx_ack(void) //SCL为高点平的时候,SDA为低电平为有效应答
{
bool ret = false;
I2C_SDA_IN(); //设置方向为输入方向,检测SDA的变化
bsp_i2c_delay(); //延迟5us
I2C_SCL_H(); //设置SCL高电平
bsp_i2c_delay(); //延迟5us
if (!I2C_SDA_IS_H()) { //如果SDA是低电平,表示有效应答,返回true,
ret = true;
}
I2C_SCL_L(); //设置SCL低电平
return ret;
}
案列:(此处我通过单击来发送数据)
并通过逻辑分析仪,抓取到如下波形,并对波形进行分析。
如图: