在嵌入式面试中,需要能够熟悉IIC的工作原理,使用场景,并且需要能够手撕IIC的时序图和写一个IIC读写程序。
IIC的一些工作原理、时序图在《沧小海详解面试的必答题——I2C协议》这篇博客介绍已经很全面了,我这里主要就是补充IIC的代码部分。顺便补充一下IIC和SPI以及UART的一些区别。
IIC读写特点
IIC最大的特点就是只使用两根线进行通讯,支持多个主服务器和多个从服务器。
IIC只有两根线,而且又不像SPI一样有片选线,那怎么区分地址呢?其实IIC总线上每个设备都有自己的地址,有的是芯片里面定死的,如果自己用GPIO模拟则可以自己定一个地址。
IIC、SPI、UART区别
SPI:4根线,全双工同步,速率最大可10Mbps,
I2C:2根线,半双工同步,速率一般是1Mbps,
UART:2根线,全双工异步,速率一般不超过20Kbps
UART和SPI、I2C两者最大的区别在于后面两者是同步的,要有统一的时钟线,而UART不需要,接线方便,但收发双方都需要配置相同的波特率,即设备要有自己的时钟源;
I2C协议中是有地址规则的,因此在一对多通信的时候更有优势。常用来连接传感器;
SPI编程简单,在设备不多,端口充足,对通信速度要求不高的场合可以应用,较常用来连接EEPROM、FLASH或液晶显示器。
SPI优点:支持全双工操作;操作简单;数据传输速率较高。SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps。
SPI缺点:需要占用主机较多的口线(每个从机都需要一根片选线);只支持单个主机;没有指定的流控制,没有应答机制确认是否接收到数据。
I2C优点:只使用两根电线,支持多个主服务器和多个从服务器,ACK / NACK位确认每个帧都已成功传输,硬件没有UART那么复杂。
I2C缺点:数据传输速率比SPI慢(IIC 最高的速度也就快速+模式(1 Mbps)和高速模式(3.4 Mbps)),数据帧的大小限制为8位,实现比SPI更复杂的硬件。
UART优点:只使用两根电线,不需要时钟信号,有一个奇偶校验位,只要双方设置后,就可以改变数据包的结构。
UART缺点:数据帧的大小限制为最多9位,不支持多个从属或多个主系统,速度最慢。
IIC八种状态和对应代码
其实IIC的通信就是通过两根线的八种状态完成的。**两根线怎么会有八种状态?**其实一根线有高电平、低电平、上升沿、下降沿四种状态。
数据线和时钟线组成的8种状态。
下面的代码就是用IO口去模拟IIC。实验是在STM32F103芯片做的,是能够跑通的。
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda输出
IIC_SDA=1;
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发送一个字节
//返回从机有无应答
//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; //往SDA上放数据,放txd最高位
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1); //SDA设置为1,释放SDA总线
IIC_SCL=1;delay_us(1);
while(READ_SDA) //读SDA,如果SDA一直为1,则无应答;如果SDA为0,则应答成功
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//读数据
//返回值就是接收到的一个字节
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; //SCL为高的时候读取数据
receive<<=1; //每读一次,receive左移一位
if(READ_SDA)receive++; //读取SDA放在receive最低位
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
参考资料
[1] https://blog.csdn.net/z123canghai/article/details/83244185