概述
IIC总线包含一根串行时钟线SCL和一条串行数据线SDA。采用IIC总线标准的器件均可并联在总线上,每个器件具备唯一地址。器件间或器件与控制器间可实现双向通讯,当某个器件发送数据时,该器件即为发送器,当某个器件接收数据时,该器件即为接收器。
IIC总线在空闲时,即没有数据传输时,其数据线SDA及时钟线SCL均为高电平。IIC总线在进行数据传输时,时钟线SCL电平为高电平期间,数据线SDA上的数据必须保持稳定,只有时钟线SCL电平为低电平,才允许数据线SDA上的电平发生变化。
IIC设备与IIC总线的接口电路如图所示:
用GPIO模拟IIC总线时,且外设IIC总线上已经外置了上拉电阻 ,为了实现“线与”,这里显然要将IIC两条线对应的引脚配置为“开漏”
/*IIC初始化函数*/
void IIC_init(void)
{
//开GPIOB时钟使能
RCC->AHB1ENR |= (1 << 1);
//模式配置:
//PB8:通用输出模式
GPIOB->MODER &= ~(3 << 16);
GPIOB->MODER |= (1 <<16);
//PB9:通用输出模式
GPIOB->MODER &= ~(3 << 18);
GPIOB->MODER |= (1 << 18);
//输出类型:
GPIOB->OTYPER |= (1 << 8); //PB8输出类型为开漏.时钟线外接了上拉电阻。所以可以配成上拉
GPIOB->OTYPER |= (1 << 9); //PB9输出类型为开漏。因为要同时做输入输出。做输入时置1由外部引脚值决定
//输出速度:2Mhz
GPIOB->OSPEEDR &= ~(3 << 16);
GPIOB->OSPEEDR &= ~(3 << 18);
//上下拉:无上下拉
GPIOB->PUPDR &= ~(3 << 16);
GPIOB->PUPDR &= ~(3 << 18);
//IIC总线初始状态:均高电平
IIC_SCL = 1;
IIC_SDA_OUT = 1;
IIC_Delay_ms(1); //延时1毫秒是为了等待硬件正常启动工作
}
(1)起始信号
当主控制器要与具有IIC接口的器件进行通信时,主控制器必须先向此器件发送起始信号。
在时钟线SCL高电平期间,数据线SDA由高电平向低电平跳变。
/*起始条件配置函数*/
/*函数描述:在SCL高电平期间,SDA从高电平到低电平跳变*/
void IIC_Start(void)
{
//总线初始状态:都为高电平
IIC_SCL = 1;
IIC_SDA_OUT = 1;
IIC_Delay_us(5); //延时5微秒,让电平稳定一下
IIC_SDA_OUT = 0;
IIC_Delay_us(5);
IIC_SCL = 0; //每次操作完,都将SCL拉低,避免因为SCL为高电平时,SDA发生变化(为误启动或停止)
IIC_Delay_us(5);
}
(2)停止信号
当主控制器要结束同某具有IIC接口的器件的通信时,主控制器就要发出停止信号,以结束数据传输操作。停止信号的规定为:在时钟线SCL高电平期间,数据线SDA由低电平向高电平跳变。
/*停止条件配置函数*/
/*函数描述:在SCL高电平期间,SDA从低电平到高电平跳变*/
void IIC_Stop(void)
{
//初始状态电平
IIC_SDA_OUT = 0;
IIC_SCL = 1;
IIC_Delay_us(5); //延时5微秒,让电平稳定一下
IIC_SDA_OUT = 1;
IIC_Delay_us(5);
}
起始信号与停止信号都是由主控制器产生的。在主控制器发出停止信号后,SDA和SCL都将处于高电平(由停止函数可以看出这一点),即处于空闲状态。
(3)应答信号
IIC总线在每传送一个字节数据,都必须由接收器回发一个应答信号给发送器,以确定数据传送是否正确。与应答信号对应的时钟信号(第9个时钟)由主控制器发出,主控制器在该时钟位上必须使数据线SDA呈现高电平(即主控制器释放数据线SDA),从而使作为接收方的从器件在该时钟的低电平期间可以拉低SDA线的电平,以表示接收正常,若接收器件接收信号出现异常情况,则保持SDA为高电平,由主机发出停止信号使传送结束。
时钟的第九个时钟脉冲是应答信号对应的脉冲,在这个时钟脉冲的低电平期间,主控制器释放数据线SDA,对于具有IIC接口的器件,由于其内部具有IIC相应的硬件电路,当该器件一旦检测到第9个时钟脉冲,其内部电路就会主动将数据线SDA拉低,以便向主控制器作出应答(由于主机已经释放了SDA,显然此时SDA的电平状态唯一由接收器决定)。主控制器若同样具有IIC硬件电路,则会自动在该时钟脉冲的高电平期间读取应答信号,并根据读取的电平状态而自行确定接收器是否已经应答,若采用GPIO模拟,则需通过软件读取引脚电平,根据读取的电平状态,人为确定是否接收器正确应答。
/*主机接收成功响应函数*/
/*函数描述:从机为发送器,主机为接收
主机应答:第九个时钟低电平期间,主机拉低SDA,发‘0’*/
void IIC_AckToSlave(void)
{
IIC_SCL = 0; //构造第9个时钟脉冲,拉低SCL,让主机可以准备发应答信号(只有SCL为低电平时,SDA才能改变电平状态)
IIC_Delay_us(5);
IIC_SDA_OUT = 0; //主机发应答信号给从机,表示自己已经接收完毕。(从机自己有IIC协议接口,发完8位数据后自动接收应答信号,不需要软件置1)
IIC_Delay_us(5);
IIC_SCL = 1; //拉高SCL,让从机接收主机回复的应答信号
IIC_Delay_us(5);
IIC_SCL = 0; //每次操作完,都将SCL拉低,避免因为SCL为高电平时,SDA发生变化(为误启动或停止)
IIC_Delay_us(5);
}
/*主机接收失败响应函数*/
/*函数描述:从机为发送器,主机为接收
主机不应答:第九个时钟低电平期间,主机拉低SDA,发‘1’*/
void IIC_NoAckToSlave(void)
{
IIC_SCL = 0; //构造第9个时钟脉冲,拉低SCL,让主机可以准备发应答信号(只有SCL在低电平时,发送器SDA才能改变电平状态)
IIC_Delay_us(5);
IIC_SDA_OUT = 1; //主机发应答信号给从机,表示自己接收失败。(从机自己有IIC协议接口,发完8位数据后自动接收应答信号,不需要软件置1)
IIC_Delay_us(5);
IIC_SCL = 1; //拉高SCL,让从机接收主机回复的应答信号(只有SCL在从低电平变为高电平的这段周期时,接收器才能读取SDA的电平状态)
IIC_Delay_us(5);
IIC_SCL = 0; //每次操作完,都将SCL拉低,避免因为SCL为高电平时,SDA发生变化(为误启动或停止)
IIC_Delay_us(5);
}
当主机做为发送器,从设备做为接收器时,若从设备接收到一个字节的数据,其也会向主机回发应答信号。由于从设备内部有IIC接口电路,因此在第9个脉冲从设备会自动发送该信号,但由于主机无IIC硬件电路(因为是GPIO模拟),因此从机回发的应答信号并不能被主机自动捕获到,用户需用软件读取引脚电平,以判断从机是否真的接收到主机发送的数据
/*主机接收从机响应函数*/
/*函数描述:主机作为发送器,从机为接收
主机接收从机应答信号
返回值:
0:应答;1:没应答*/
u8 IIC_CheckACK(void)
{
u8 ack = 0;
IIC_SCL = 0; //构造第9个时钟脉冲,拉低SCL,让主机释放总线,从机发应答信号
IIC_SDA_OUT = 1; //主机释放总线,从机发应答信号
IIC_Delay_us(5);
IIC_SCL = 1; //拉高SCL,让主机接收从机返回来的应答信号
IIC_Delay_us(5);
if(IIC_SDA_IN == 1) //检测应答信号是否为1
ack = 1; //为1说明没有应答
else
ack = 0; //为0说明应答了
IIC_Delay_us(5);
IIC_SCL = 0; //每次操作完,都将SCL拉低,避免因为SCL为高电平时,SDA发生变化(为误启动或停止)
IIC_Delay_us(5);
return ack;
}
/*IIC发送字节函数*/
/*函数描述:发数据,以字节形式发送;
在SCL低电平期间发数据,而且先发高位*/
void IIC_SendByte(u8 dat)
{
u8 i = 0; //循环变量;
for(i = 0; i < 8; i++)
{
IIC_SCL = 0; //SCL拉低,准备发数据
IIC_Delay_us(5);
if(dat & 0X80) //先发高位,判断电平状态选择发送1或0
IIC_SDA_OUT = 1;
else
IIC_SDA_OUT = 0;
IIC_Delay_us(5);
IIC_SCL = 1; //SCL拉高,让从机接收数据;
IIC_Delay_us(5);
dat <<= 1; //左移到下一位继续发送;
}
IIC_SCL = 0; //每次操作完,都将SCL拉低,避免因为SCL为高电平时,SDA发生变化(为误启动或停止)
IIC_Delay_us(5);
}
/*IIC接收字节函数*/
/*函数描述:
接收数据,以字节形式发送
在高电平期间接收数据,先收高位*/
u8 IIC_RecByte(void)
{
u8 rec = 0; //存放接收回来的字节
u8 i = 0;
IIC_SCL = 0; //将SCL拉低,避免因为SCL为高电平时,SDA发生变化(为误启动或停止)
IIC_SDA_OUT = 1; //主机输出高电平,切断NMOS,从而切断输出通道,变化输入模式
for(i = 0; i < 8; i++)
{
IIC_SCL = 0; //时钟拉低,使从机有机会给主机发一个数据位
IIC_Delay_us(5);
IIC_SCL = 1; //时钟拉高,准备接收数据
IIC_Delay_us(5);
rec <<= 1; //空出最低位,准备接收
if(IIC_SDA_IN == 1) //如果接收回来的数据位为1,则给rec的最高位赋值1;
rec |= 1;
}
IIC_SCL = 0; //每次操作完,都将SCL拉低,避免因为SCL为高电平时,SDA发生变化(为误启动或停止)
IIC_Delay_us(5);
return rec;
}
总结 & 归纳
1) 首先由主机发出起始信号来唤醒所有从设备。
2) 主机发送寻址字节,所有的从设备都会收到这个地址,把这个地址跟自己的地址做对比。
如果一致,则发送一个应答信号,否则继续休眠。
3) 主机如果得到应答,主机继续发送器件内部地址(命令字节或者寄存器地址),当从机
接收到这个地址,然后给出一个应答信号。
4) 主机发送数据给从机,或者接收从机发送的数据。
5) 如果不需要继续通信,主机发送一个停止条件,释放总线。
最后
以上就是疯狂魔镜为你收集整理的IIC协议的全部内容,希望文章能够帮你解决IIC协议所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复