1.I2C硬件原理图
SDA:数据线(双向)
SCL:时钟线(主机控制)
I2C总线的SDA和SCL必须接上拉电阻!(一般选用4.7K~10K的电阻)
I2C总线上可以挂载多个主设备,以及多个从设备,在从机没有收到主机的地址访问信息前从机不会主动向主机发送数据。
2.电气特性
3.I2C总线传输数据的方式
3.1 起始和停止条件
起始条件:在SCL为高电平期间,SDA产生一个下降沿信号
停止条件:在SCL为高电平期间,SDA产生一个上升沿信号
模拟I2C起始和停止信号的程序(基于STM32)
//程序中的宏定义
#define HIGH 1
#define LOW 0
/* IO方向设置 */
#define SDA_IN() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=(uint32_t)8<<12;}
#define SDA_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=(uint32_t)3<<12;}
/* IO操作 */
#define IIC_SCL(n) (n?HAL_GPIO_WritePin(GPIOA,GPIO_PIN_12,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOA,GPIO_PIN_12,GPIO_PIN_RESET)) //SCL
#define IIC_SDA(n) (n?HAL_GPIO_WritePin(GPIOA,GPIO_PIN_11,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOA,GPIO_PIN_11,GPIO_PIN_RESET)) //SDA
#define READ_SDA HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_11) //输入SDA
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT();//sda线输出
IIC_SDA(HIGH);
IIC_SCL(HIGH);
delay_us(4);
IIC_SDA(LOW);//START:when CLK is high,DATA change form High to low
delay_us(4);
IIC_SCL(LOW);//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL(LOW);
IIC_SDA(LOW);//STOP:when CLK is high DATA change form low to High
delay_us(4);
IIC_SCL(HIGH);
IIC_SDA(HIGH);//发送I2C总线结束信号
delay_us(4);
}
3.2 传输数据格式
1. 在SCL的每个时钟脉冲期间传输 1 个数据位;
2. 地址由 7 bit 构成,最低位为读写命令,0:写,1:读
3. SDA数据线上的 1 个字节由 8 个数据位组成,字节可以是设备地址、寄存器地址,也可以是写入或从从机读取的数据;
4. 首先传输数据的是最高有效位(MSB);
5. 在启动和停止条件之间,可以将任意数量字节的数据从主设备传输到从设备;
6. 在时钟周期的高相位期间,SDA线上的数据必须保持稳定,因为SCL高时数据线上的变化被解释为控制命令(启动或停止);
应答信号:Acknowledge (ACK) and Not Acknowledge (NACK)
数据的每个字节(包括地址字节)后面跟着来自接收器的一个ACK位,ACK位允许接收机与发射机进行通信,告知该字节已成功接收,并可发送另一个字节,这其实是I2C总线的一种数据校验方式。
在接收机发送ACK之前,发射机必须释放SDA线路。为了发送ACK位,接收器应在ACK/NACK相关时钟周期(第9个周期)的低相位期间拉低SDA线,以便在ACK/NACK相关时钟周期的高相位期间SDA线稳定在低电平,同时必须考虑设置和保持的时间。
当然,主机也有可能会接收到费应答信号NACK:
接收到非应答信号NACK可能有以下原因:
1.接收器无法接收或发送,因为它正在执行某些实时功能,并且尚未准备好开始与主机通信;
2.在传输过程中,接收器获取其不理解的数据或命令;
3.在传输过程中,接收器无法再接收任何数据字节;
4.主接收器数据已经读取完毕,默认NACK信号的从机发过来的;
//主机等待从机应答信号
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
SDA_IN();//主机SDA设置为读取模式
IIC_SDA(HIGH);//主机释放SDA信号线
delay_us(1);
IIC_SCL(HIGH);
delay_us(1);
while(READ_SDA)//等待并读取SDA状态
{
ucErrTime++;
if(ucErrTime>250)//等待超时后结束本次数据传输
{
IIC_Stop();
return 1;
}
}
IIC_SCL(LOW);//时钟输出0
return 0;
}
//产生ACK应答信号
void IIC_Ack(void)
{
IIC_SCL(LOW);
SDA_OUT();
IIC_SDA(LOW);
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
}
//产生NACK非应答信号
void IIC_NAck(void)
{
IIC_SCL(LOW);
SDA_OUT();
IIC_SDA(HIGH);
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
}
3.3 主机通过I2C总线向从机设备写数据
I2C总线按照如下示意图向指定设备指定寄存器发送数据:
I2C总线发送数据的过程:
0.主机发送一个起始信号
1.主机发送从机设备地址,最低位为0,表示写命令,R/W=0
2.主机等待接收从机的应答信号
3.主机发送设备寄存器地址
4.主机等待接收从机的应答信号
5.主机发送一个字节数据
6.主机等待接收从机的应答信号
8.主机接收从机上传的一个字节数据(一般情况下只发送一个字节数据)
9.主机等待接收从机的应答信号
10.主机继续发送数据,等待从机应答信号,重复步骤8和9
11.主机发送一个停止信号
主机模拟I2C发送数据代码实现(基于STM32)
//发送一个字节数据
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL(LOW);
for(t=0;t<8;t++)
{
IIC_SDA((txd&0x80)>>7);
txd<<=1;
delay_us(2);
IIC_SCL(HIGH);
delay_us(2);
IIC_SCL(LOW);
delay_us(2);
}
}
//I2C总线向设指定设备指定寄存器写一个字节数据
//devaddr:设备地址
//addr:寄存器地址
//data:待发送数据
void iicDevWriteByte(uint8_t devaddr,uint8_t addr,uint8_t data)
{
IIC_Start();//起始信号
IIC_Send_Byte(devaddr);//发送从机设备地址
IIC_Wait_Ack();
IIC_Send_Byte(addr);//发送寄存器地址
IIC_Wait_Ack();
IIC_Send_Byte(data);//发送数据
IIC_Wait_Ack();
IIC_Stop();//停止信号
}
//I2C总线向指定设备指定地址连续写多个字节数据
//devaddr:设备地址
//addr:寄存器地址
//len:发送数据的长度
//wbuf:待发送数据缓存
void iicDevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf)
{
int i=0;
IIC_Start();//起始信号
IIC_Send_Byte(devaddr);//发送从机设备地址,发送写命令,R/W=0
IIC_Wait_Ack();
IIC_Send_Byte(addr);//寄存器地址
IIC_Wait_Ack();
for(i=0; i<len; i++)
{
IIC_Send_Byte(wbuf[i]);
IIC_Wait_Ack();//等待ACK信号
}
IIC_Stop( );//停止信号
}
3.4 主机通过I2C总线读取从机发送的数据
I2C总线按照如下示意图从指定从机的指定寄存器读取数据:
I2C总线读取数据的过程:
0.主机发送一个起始信号
1.主机发送从机设备地址,最低位为0,表示写命令,R/W=0
2.主机等待接收从机的应答信号
3.主机发送设备寄存器地址
4.主机等待接收从机的应答信号
5.主机重复发送一个起始信号
6.主机发送从机设备地址,最低位为1,表示读命令,R/W=1
7.主机等待接收从机的应答信号
8.主机接收从机上传的一个字节数据
9.若主机发送ACK应答信号,继续接收从机数据;若主机发送NACK非应答信号,停止接收数据
10.主机发送一个停止信号
主机模拟I2C接收数据代码实现(基于STM32)
//读取一个字节数据
//ack=1时,发送ACK,表示还有数据待继续读取
//ack=0时,发送NACK,表示停止读取数据
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL(LOW);
delay_us(2);
IIC_SCL(HIGH);
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack();//发送ACK
return receive;
}
//从指定设备指定寄存器地址读取一个字节数据
//ReadAddr:开始读数的地址
//temp:读到的数据
uint8_t iicDevReadByte(uint8_t devaddr,uint8_t addr)
{
uint8_t temp=0;
IIC_Start();//起始信号
IIC_Send_Byte(devaddr);//发送从机设备地址,发送写命令,R/W=0
IIC_Wait_Ack();
IIC_Send_Byte(addr);//发送寄存器地址
IIC_Wait_Ack();
IIC_Start();//Repeated START
IIC_Send_Byte(devaddr|1);//发送从机设备地址,发送读命令,R/W=1
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop();//停止信号
return temp;
}
//从指定设备指定寄存器地址连续读取多个字节数据
//devaddr:从机设备地址
//addr:寄存器地址
//len:字节总长度
//rbuf:读取数据缓存区
void iicDevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf)
{
int i=0;
IIC_Start();//起始信号
IIC_Send_Byte(devaddr);//发送从机设备地址,发送写命令,R/W=0
IIC_Wait_Ack();
IIC_Send_Byte(addr);//发送寄存器地址
IIC_Wait_Ack();
IIC_Start();//Repeated START
IIC_Send_Byte(devaddr|1);发送从机设备地址,发送读命令,R/W=1
IIC_Wait_Ack();
for(i=0; i<len; i++)
{
if(i==len-1)
{
rbuf[i]=IIC_Read_Byte(0);//全部数据接收完毕,主机发送NACK信号,产生停止信号
}
else
rbuf[i]=IIC_Read_Byte(1);//数据未全部接收完,主机发送ACK信号,继续接收数据
}
IIC_Stop( );停止信号
}