(1)I2C总线是一种串行数据总线,只有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,两根线可以挂多个设备。I2C设备(绝大多数)里有个固定的地址,只有在两条线上传输的值等于I2C设备的固化地址时,其才会作出响应。通常我们为了方便把I2C设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。
(2)I2C总线通过上拉电阻接正电源。当总线空闲时。两根线均为高电平。连到总线上的任意器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系。上拉电阻一般在4.7~10k之间。
当IIC总线的数据线SDA和时钟线SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
(1)起始信号:SCL为高电平期间,SDA出现下降沿
(2)终止信号:SCL为高电平期间,SDA出现上升沿
注意:起始信号和终止信号都是由主机发送的。在起始信号产生之后,总线就处于被占用的状态,在终止信号产生之后,总线就处于空闲状态。
(3)应答信号:发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
即每当发送器传输完8bit的数据之后就不再驱动总线了,发送端会等待一段时间,在第八个数据位,如果外接I2C设备数据已经收到信号的话,接着在第9个周期把SDA拉低,处理器检测到SDA拉低就能知道I2C设备数据已经收到。
I2C总线进行数据传送时,SDA的数据在SCL高电平期间被写入从机。所以时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或者低电平状态才允许变化。
即:数据在时钟线SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。
IIC总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。
注意:从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目,如一个从机的7位寻址位有4位是固定的,3位是可编程位,这时只能寻址8个同样的器件,即可以有8个同样的器件接入到该I2C总线系统中。
数据传输格式如下:(淡蓝色部分表示数据由主机向从机传送,粉红色部分则表示数据由从机向主机传送)
数据格式如下:
在从机产生响应时,主机从发送变成接收,从机从接收变成发送。之后,数据由从机发送,主机接收,每个应答由主机产生,时钟信号仍由主机产生。若主机要终止本次传输,则发送一个非应答信号,接着主机产生停止条件。
数据传输格式如下:
注意:
(1)每次数据传送总是由主机产生的终止信号来结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
(2)主机做的都是编程控制,从机做的都是自主控制,也可以说是硬件控制,如主机给应答信号是编程控制,但是从机给应答信号是硬件控制,我们只需要检查在SDA为高期间,SCL保持低电平一些时间,即可判定从机给了主机应答信号。
//IO方向设置
#define SDA_IN() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=8<<12;}//PB11设置为上拉下拉输入模式
#define SDA_OUT() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=3<<12;}//PB11设置为通用推挽输出
//IO操作函数
#define IIC_SCL PCout(12) //SCL时钟线
#define IIC_SDA PCout(11) //SDA数据线输出
#define READ_SDA PCin(11) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);//写数据
u8 IIC_Read_One_Byte(u8 daddr,u8 addr); //读数据
//初始化IIC
void IIC_Init(void)
{
RCC->APB2ENR|=1<<4; //先使能外设IO PORTC时钟
GPIOC->CRH&=0XFFF00FFF; //使能PC11/12
GPIOC->CRH|=0X00033000; //设置为推挽输出
GPIOC->ODR|=3<<11; //PC11,12 输出高
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线为输出
IIC_SDA=1; //初始化SCL与SDA为高
IIC_SCL=1;
delay_us(4);
IIC_SDA=0; //START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0; //钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT(); //sda线为输出
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SCL=1; //STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SDA=1; //发送I2C总线结束信号
}
//等待应答信号到来——主机
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA) //从机SDA一直为1表示没有应答,为0则接收到应答
{
ucErrTime++;
if(ucErrTime>250) //软件延时,如果时间超时,没有应答就停止
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0; //时钟输出0
return 0;
}
//产生ACK应答,SDA为0则应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答,SDA为1则无应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0; //拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
//让传输的数据与1000 0000与,则其他位置零,最高位保留,表示传输数据从最高位开始
txd<<=1; //相当于txd=txd<<1
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}