IIC协议

        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) 如果不需要继续通信,主机发送一个停止条件,释放总线。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值