IIC协议(根据时序图写C程序)

一、IIC通信概念

SPI的单主设备不同,IIC 多主设备的总线,IIC没有物理的芯片选择信号线,没有仲裁逻辑电路,IIC总线只有2根线:SDA数据线,SCL时钟线。不同于SPI是单主设备,IIC的主设备与各个从设备都是挂载在这两根线上的。每个设备都有自己的一个地址,当主机要与多个外围设备中的一个设备通信时,首先要发送要通信的器件地址以确定通信目标。如图:
在这里插入图片描述

二、IIC通信协议特点

第一,每一支IIC设备都有一个唯一的七位设备地址

第二,数据帧大小为8位的字节;

第三,数据(帧)中的某些数据位用于控制通信的开始停止方向(读写)和应答机制。

三、IIC通信

IIC协议层信号包括:空闲信号起始信号终止信号应答信号非应答信号
在讲各个信号前我们先了解什么叫空闲状态
空闲状态:两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高,如上图。
在空闲状态时,IIC设备不进行通信。主机要开始一次I2C通信,就发送一个START(起始)信号告诉所有的从机,结束空闲状态,准备开始通信。

void IIC_init()       //IIC初始化,设为空闲状态
{
       SCL=1; //首先把时钟线拉高
       delay_us(4);//延时函数
       SDA=1; //在SCL为高的情况下把SDA拉高
       delay_us(4); //延时函数
}

IIC写(发送)数据格式:
在这里插入图片描述
由图得:
主机向从机写(发送)数据流程:
  1. 主机首先产生START信号;
  2. 然后紧跟着发送一个从设备地址,这个地址共有7位,紧接着的第8位是数据方向位(R/W),
   0表示主机发送数据(写),1表示主机接收数据(读);
  3. 主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,主机则认为自己正在主机寻址,根据R/W位将自己确定为发送器接收器;
   (注:不要把发送器/接收器同主设备/从设备混淆,当主设备向从设备写数据的时候,设备做发送器,设备做接收器;当主设备向从设备读数据的时候,设备做接收器,设备做发送器)
  4. 这时候主机等待从机的应答信号(ACK);
  5. 当主机收到应答信号时,发送要访问(上述)从机里(相当于从机的寄存器)的哪个地址, 继续等待从机的应答信号;
  6. 当主机收到应答信号时,发送N个字节的数据,继续等待从机的N次应答信号;
  7. 主机产生终止信号,结束传送过程。

3.1 起始信号

起始信号时序图:
起始信号时序图

从时序图可知,起始信号:SCL为电平时,SDA产生降沿。

void IIC_Start()         //起始信号
{
       SDA=1; //确保SDA线为高电平
       delay_us(5);//SDA线拉高4.7us以上是为了确保是空闲状态
       SCL=1;  //确保SCL高电平
       delay_us(5);//如上
       SDA=0; //在SCL为高时拉低SDA线(下降沿),即为起始信号
       delay_us(5);  //缓冲,防止干扰 
       SCL=0;//为后面数据传输做准备
}

3.2 终止信号

终止信号时序图:
在这里插入图片描述
从时序图可知,终止信号:SCL为电平时,SDA产生升沿。

void IIC_Stop(void)    //终止信号
{
      SCL=0;    
      SDA=0;   
      delay_us(5);
      SCL=1;     //SCL线为高时
      SDA=1;    //SDA线由低到高(上升沿),即为终止信号
      delay_us(5);  //缓冲,防止干扰                  
}

3.3 应答信号

应答信号(ACK/NACK):主机将SCL拉高,读取从机SDA的电平,SDA为低电平则表示从机产生应答。也是主机等待 从机的应答 信号

所有地址和数据都以8bit为单位传输,如果接受端正确接收了8bit数据,则回复一个bit的“0”信号——ACK信号,如果未正确接收8bit数据,或者接收端不再接受数据,则回复一个bit的“1”信号——NACK信号。即每8bit数据,就会跟1bit ACK/NACK信号。

  • 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
  • 应答信号为高电平时,规定为非应答位(NACK非应答位),一般表示接收器接收该字节没有成功。
    可以这么理解:平时SDA线空闲状态下都是高电平,那么当读取时发现从机还是高电平就证明它没有改变就是非应答。相反,当它拉低了自己的姿态(置0,低电平)就是应答了。

: 比较难理解的就是应答信号 ≠ \not= =应答 ≠ \not= =非应答。还有所谓主机从机只是我们站的角度去定义的,比如说我们是在给单片机写代码,那么以下写的主机, 是指单片机从机是指外设模块等。

时序图如下:
在这里插入图片描述在这里插入图片描述
不管是主机还是从机设备都需要接收应答信号或者产生应答信号才能进行下一步的数据传输。

主机等待(从机的)应答信号:

//用于发送模式下,发送8位后,等待器件应答第9位
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{
    u8 ucErrTime=0;
  //  SDA_IN();
    SDA=1;    //SDA先拉高,准备接收应答信号
    delay_us(1);    
    SCL=1;    //SCL拉高,产生第9位的脉冲
    delay_us(1);    
    while(SDA)        //接收到非应答信号则循环接收
    {
        ucErrTime++;
        if(ucErrTime>250) //检测250次后仍然没有检测到应答信号
        {
            IIC_Stop();
            return 1; //接收应答失败
        }
    }
    IIC_SCL=0;//时钟输出0     //SCL拉低,结束第9位的脉冲 
    return 0;  //检测到应答信号,接收应答成功
}

主机(产生) 应答信号:

void IIC_Ack(void)
{
    SCL=0;      //确保时钟线为低时,数据线才能变化为0,
                 //否则这就可能成起始信号了!
 //   SDA_OUT(); 
    SDA=0;    //拉低SDA,表示应答
    delay_us(2);
    SCL=1;    //SCL先上升
    delay_us(5);//脉冲宽度大于4.7us
    SCL=0;    //SCL再下降,形成一个脉冲,应答才生效
}

该函数用在连续读取多个字节时,每读完一个字节(8位),产生回应,表示还要进行读,时器件就可以继续发数据了。
当单片机不需要继续读,如连续读的最后一个字节,或只读一个字节,单片机发送非应答信号,这时器件以为单片机没有收到数据,接下来就不会再发数据了。如下,

主机产生 非(不)应答信号:

//用于读取模式(SDA为in)读了8位器件数据后,在第9位给出一个应答
//==================================
void IIC_NAck(void)
{
    SCL=0;    //确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号了!
//    SDA_OUT();    //SDA由读取改为发送
    SDA=1;    //拉高SDA,表示不应答
    delay_us(2);
    SCL=1;    //SCL先上升
    delay_us(5);
    SCL=0;    //SCL再下降,形成一个脉冲,不应答才生效
}

3.4 数据传输

在这里插入图片描述

单片机发送一个字节

发送一个字节(8bit),就是分8次循环,产生8个时钟信号(脉冲),并将SDA赋值为0或1。
代码如下:

void IIC_Send_Byte(u8 txd)//需要发送的信号(字节):txd
{                        
    u8 t;   
//    SDA_OUT();                 //SDA发送模式
    SCL=0;                 //拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        SDA=(txd&0x80)>>7; //从txd的高位到低位传输
        txd<<=1;      
        delay_us(2);           
        SCL=1;             //SCL先上升
        delay_us(2); 
        SCL=0;             //SCL再下降,形成一个脉冲,发送一位数据生效
        delay_us(2);
    }    
}

单片机读取一个字节

同样读取一个字节(8bit)也分八次循环,产生8个时钟信号(脉冲)。
代码如下:

u8 IIC_Read_Byte(unsigned char ack)//ack表示在读取这个字节后还需不需要继续读取
{
    unsigned char i,receive=0;
//    SDA_IN();                  //SDA输入模式
    for(i=0;i<8;i++ )
    {
        SCL=0;             //SCL先下降,通过循环,形成时钟脉冲
        delay_us(2);
        SCL=1;             //SCL上升
        receive<<=1;        //从高位开始接收
        if(SDA)  //当SDA线被拉高
          receive++;         //读取并组合记录数据,++表示读到1了,最低位置1
        delay_us(1); 
    }   
    //读取8位后,主机需要变为发送模式,在第9位进行应答或不应答
    //此时CLK还是高电平状态,不过下面的应答会先将CLK拉低的 
    if (!ack)
    {
        //读1个字节,或读多个字节读到最后一个字节时,使用nACK
        //然后配合使用IIC停止信号
        IIC_NAck();//发送nACK
    }
    else
    {
        //读多个字节还没读完时,使用ACK,表示现在读的ok,还要继续读
        IIC_Ack(); //发送ACK 
    }       
    return receive;  //返回读取字节
}

扫盲:数据存储是以“字节”(Byte)为单位,数据传输是以大多是以“位”(bit,又名“比特”)为单位,一个位就代表一个0或1(即二进制),每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小一级的信息单位。
IIC的IO口通常用设置为开漏输出且外接上拉电阻。
至于为什么:这里推荐一篇

  • 8
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
24C04是一种IIC接口的串行EEPROM芯片,其容量为4Kbit,即512字节。在C语言中,可以使用以下代码示例来实现对24C04芯片的读操作。 读取数据: ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> int main() { int device; char *filename = "/dev/i2c-1"; // IIC设备文件路径,根据实际情况修改 int address = 0x50; // 24C04芯片的IIC地址,根据硬件连接情况修改 unsigned char buffer[2]; // 打开IIC设备 if ((device = open(filename, O_RDWR)) < 0) { perror("Failed to open the i2c bus"); return 1; } // 设置IIC设备的从设备地址 if (ioctl(device, I2C_SLAVE, address) < 0) { perror("Failed to acquire bus access and/or talk to slave"); return 1; } // 读取数据 if (read(device, buffer, sizeof(buffer)) != sizeof(buffer)) { perror("Failed to read from the i2c bus"); return 1; } // 打印读取的数据 printf("Read data: 0x%X, 0x%X\n", buffer[0], buffer[1]); close(device); return 0; } ``` 入数据: ```c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> int main() { int device; char *filename = "/dev/i2c-1"; // IIC设备文件路径,根据实际情况修改 int address = 0x50; // 24C04芯片的IIC地址,根据硬件连接情况修改 unsigned char buffer[2] = {0x01, 0x02}; // 要入的数据,根据实际情况修改 // 打开IIC设备 if ((device = open(filename, O_RDWR)) < 0) { perror("Failed to open the i2c bus"); return 1; } // 设置IIC设备的从设备地址 if (ioctl(device, I2C_SLAVE, address) < 0) { perror("Failed to acquire bus access and/or talk to slave"); return 1; } // 入数据 if (write(device, buffer, sizeof(buffer)) != sizeof(buffer)) { perror("Failed to write to the i2c bus"); return 1; } printf("Data written successfully.\n"); close(device); return 0; } ``` 这两个示例代码可以通过打开IIC设备文件,设置从设备地址,使用`read`和`write`函数来进行读操作。注意,程序中的`filename`和`address`需要根据实际的硬件连接情况进行修改。另外,`buffer`数组是需要读取或入的数据,也需要根据实际需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值