由51仿制的IIC可以很方便的帮助我们学习IIC。
IIC有3种传输模式:
- 标准模式:100K bit/s
- 快速模式:400K bit/s
- 高速模式:3.4M bit/s
主要的传输,通讯线SDA,时钟线SDL,由于时钟线的存在所以IIC协议为半双工协议。
全双工:允许A向B传输数据,同时允许B向A传输数据。
半双工:允许A向B传输数据,允许B向A传输数据,但两者不能同时进行。
单工:允许A向B传输数据,不允许B向A传输数据。
下面是他的起始和终止信号
首先时钟线和数据线默认情况下都是高电平,此时为空闲状态。
起始信号为在时钟信号高电平时,数据信号从高到低。
在起始位发送完毕后,下面就开始发送数位了。
在数据为发送时我们首先要保证时钟线处于低电平。
思考为何时钟线一定是低电平呢,在上面的起始位和中止位中我们可以看到,都是在时钟线处于高电平时完成的,所以我们需要将发送的数据与起始位和终止位区分开,那让时钟线处于低电平时才允许SDA改变当前的状态就很好的解决了这个问题。
以下为一帧标准的写数据帧:
可以看到我们在传输完起始位后传输的是地址位,通常为7位,还有1位读写信号位,它是如何传输的呢,首先我们需要在SCLK(时钟线)为低电平的时候传输数据,在时钟线为高电平时让外设读取数据,传输8位数据后会得到一个应答信号。
如上是在传输一个7位的地址位,都是在SCL为低电平时信号线SDA发生的跳变。
下面还需要一个读写数据位,通常情况下,我们需要读数据时需要给下一位置1,写数据时置0。
接下来就是应答信号的接受,当从机收到数据后会返回一个0,未收到或主机读取接受完成返回1。
之后就是寄存器地址位,一共8位的话可以访问256个地址,每个地址对应一个字节的存储空间,那我们就可以向256个字节的空间中写入数据。
在写入地址或写入数据后从机都会返回一个应答信号,通常0代表写入成功,最后我们需要发送一个停止位,代表数据写入操作完成。
标准的读数据帧如上,首先到发送地址和写数据很相似,后面我们需要再次发送一次起始位之后再传输一次地址和读写位,之后从机便会主动将地址中的内容传输到单片机。关于这个部分的理解为:读取数据之前,要先由主机指派操作地址到从机。
需要注意,SDA在接受应答信号和向外发送地址或数据信号时都需要改变SDA引脚的输入输出设置。
以下为在使用单片机进行通信时的常用代码。
/*
设置SDA总线为输出模式
参数值:NULL
返回值:NULL
*/
void IIC_setSDAMode_Out()
{
GPIO_InitTypeDef GPIO_IIC;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_IIC.GPIO_Mode = GPIO_Mode_OUT; //输出
GPIO_IIC.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_IIC.GPIO_Pin = GPIO_Pin_15; //引脚
GPIO_IIC.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_IIC.GPIO_Speed = GPIO_Speed_25MHz; //输出
GPIO_Init(GPIOE, &GPIO_IIC);
}
/*
设置SDA总线为输入模式
参数值:NULL
返回值:NULL
*/
void IIC_setSDAMode_In()
{
GPIO_InitTypeDef GPIO_IIC;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_IIC.GPIO_Mode = GPIO_Mode_IN; //输出
GPIO_IIC.GPIO_Pin = GPIO_Pin_15; //引脚
GPIO_IIC.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOE, &GPIO_IIC);
}
/*
IIC开始信号
参数值:NULL
返回值:NULL
*/
void IIC_Start()
{
IIC_setSDAMode_Out();
IIC_SDA_OUT(1); //总线释放状态
IIC_SCL_OUT(1);
delay_us(5);
IIC_SDA_OUT(0); //SDA跳变为低电平
delay_us(5);
IIC_SCL_OUT(0);
delay_us(5);
}
/*
IIC停止信号
参数值:NULL
返回值:NULL
*/
void IIC_Stop()
{
IIC_setSDAMode_Out();
IIC_SDA_OUT(0);
IIC_SCL_OUT(0);
delay_us(5);
IIC_SCL_OUT(1); //SDA跳变为高电平
delay_us(5);
IIC_SDA_OUT(1);
delay_us(5);
}
/*
主机写入数据到外设中
参数值:
data 要写入的一个字节
返回值:NULL
*/
void IIC_writeByte(u8 data)
{
IIC_setSDAMode_Out();
IIC_SCL_OUT(0); //只有时钟线拉低,SDA上的数据才允许写入
delay_us(5);
//将数据一位一位的发出去
for(int i =0;i<8;i++)
{
if(data&(0x1<<(7-i))) //高位先入
{
IIC_SDA_OUT(1);
}
else
{
IIC_SDA_OUT(0);
}
IIC_SCL_OUT(1); //让外设读取数据
delay_us(5);
IIC_SCL_OUT(0); //重新拉低,准备写入下一位数据
delay_us(5);
}
}
/*
主机从外设中读取一个字节的数据
参数值:NULL
返回值:NULL
*/
u8 IIC_readByte()
{
u8 data = 0;
IIC_setSDAMode_In();
IIC_SCL_OUT(0); //先拉低,为读取数据做准备
delay_us(5);
for(int i=0;i<8;i++)
{
IIC_SCL_OUT(1); // SCL为高期间才可以读取数据
delay_us(5);
if(IIC_SDA_IN)
{
data|=(0x01<<(7-i));
}else{
data &= ~(0x1<<(7-i));
}
IIC_SCL_OUT(0);
delay_us(5);
}
return data;
}
/*
主机等待应答
参数值:NULL
返回值:ack 0 应答 1 不应答
*/
u8 IIC_waitAck()
{
u8 ack =0;
IIC_setSDAMode_In();
IIC_SCL_OUT(0); //准备时序
delay_us(5);
IIC_SCL_OUT(1);
delay_us(5);
if(IIC_SDA_IN)
{
ack =1;
}
else
{
ack =0;
}
IIC_SCL_OUT(0); //拉低,表示应答完成
delay_us(5);
return ack;
}
/*
主机主动应答
参数值:
ack 0 应答 1 不应答
返回值:NULL
*/
void IIC_Ack(u8 ack)
{
IIC_setSDAMode_Out();
IIC_SCL_OUT(0);
delay_us(5);
if(ack)
{
IIC_SDA_OUT(1);
}
else
{
IIC_SCL_OUT(0);
}
IIC_SCL_OUT(1);
delay_us(5);
IIC_SCL_OUT(0);
delay_us(5);
}
以下为本内容的参考连接。