IIC的使用

借鉴于:蓝桥杯嵌入式快速通关篇,IIC通讯及EEPROM_穿上我的格子衫的博客-CSDN博客

                IIC原理超详细讲解---值得一看_Z小旋的博客-CSDN博客_iic

总线空闲:SDA  = 1,SCL = 1;
启动信号:SCL = 1,SDA 1 -> 0;
停止信号:SCL = 1,SDA 0 -> 1;

数据传输:SCL为1时,SDA必须保持稳定,即为0或1(停止时相反,所以在停止前要改变SDA都要先把SCL置为0);
         SCL为0时,才允许改变SDA状态;
         SCL在上升沿时写入数据,在下降沿时读出数据;
         
应答信号:为0时,是有效应答,为1时,是无效应答;
         传输到最后一个字节后,要发送一个无效应答,再发送停止信号;

不管什么信号,其线的变化都要严格与对应时序吻合(包括起始,变化过程,结果)

总线封锁:将SCL锁定为0即可;

多次数据通信,采用复启动信号,即写一个停止信号,再接上一个启动信号;

单片机为主机,24C02为从机;
SCL时钟由主机控制;

初始化时仅需要设置端口为最基本的推挽模式(因为仅需要gpio输出最基本的高低电平)
写时应答信号从机给出;
读时由主机给出;

24C02器件地址:前四位默认为1010,后四位的前三位为A0,A1,A2,均已接地,即为0,
              最后一位为读写保护位,写时为0(0xA0),读时为1(0xA1).

在这里插入图片描述

Start: IIC开始信号,表示开始传输。
DEVICE_ADDRESS:: 从设备地址,就是7位从机地址
R/W: W(write)为写,R(read)为读
ACK: 应答信号
WORD_ADDRESS : 从机中对应的寄存器地址 比方说访问 OLED中的 某个寄存器
DATA发送的数据
STOP: 停止信号。结束IIC

首先要知道的是读写分为总体部分与发送接收的单字节部分

IIC数据传送

数据传送格式

SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位ACK, 此时才认为一个字节真正的被传输完成 ,如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据。

在功能函数结束后不要将线的电平恢复(不要越界处理别的函数要做的事)

img

//void SDA_Output( uint16_t val )  val为1或0
//{
//    if ( val ) {
//        GPIO_SetBits(I2C_PORT,SDA_Pin);
//    } else {
//        GPIO_ResetBits(I2C_PORT,SDA_Pin);
//    }

每次设置线上的电平高低后都要进行延时

IIC写数据(要注意的是读写模式不同,注意切换SDA线的GPIO模式):

//uint8_t SDA_Input()
//{
//    return GPIO_ReadInputDataBit( I2C_PORT, SDA_Pin);
//}

img

从这两个图中我们可以看出在结束时SDA与SCL都为低电平恢复为高电平,这就是结束条件!

多数从设备的地址为7位或者10位,一般都用七位。
八位设备地址=7位从机地址+读/写地址,另加一个ack应答地址

再给地址添加一个方向位位用来表示接下来数据传输的方向,

  • 0表示主设备向从设备(write)写数据,

  • 1表示主设备向从设备(read)读数据

IIC的每一帧数据由9bit组成,其中数据有两种

如果是发送数据,则包含 8bit数据+1bit ACK,

如果是设备地址数据,则8bit包含7bit设备地址 1bit方向
在这里插入图片描述

在起始信号后必须传送一个从机的地址(7位) 1~7位为7位接收器件地址,第8位为读写位,用“0”表示主机发送数据(W),“1”表示主机接收数据 (R), 第9位为ACK应答位,紧接着的为第一个数据字节,然后是一位应答位,后面继续第2个数据字节。

IIC发送一个字节数据(8位)

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答            

//IIC_SCL=0;
//在SCL上升沿时准备好数据,进行传送数据时,拉高拉低SDA,因为传输一个字节,一个SCL脉冲里传输一个位。
//数据传输过程中,数据传输保持稳定(在SCL高电平期间,SDA一直保持稳定,没有跳变)
//只有当SCL被拉低后,SDA才能被改变
//总结:在SCL为高电平期间,发送数据,发送8次数据,数据为1,SDA被拉高,数据为0,SDA被拉低。
//传输期间保持传输稳定,所以数据线仅可以在时钟SCL为低电平时改变。
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
    SDA_OUT();         //设置为输出模式(PP模式)
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        //IIC_SDA=txd&0x80;   //获取最高位
        //获取数据的最高位,然后数据左移一位
        //如果某位为1,则SDA为1,否则相反
        if(txd&0x80)  //如果最高位(第八位)为一,则置SDA为高电平
            IIC_SDA=1;
        else
            IIC_SDA=0;
        txd<<=1;       
        delay_us(2);   
        IIC_SCL=1;
        delay_us(2); 
        IIC_SCL=0;    
        delay_us(2);
    }     
}       
或者:
        //IIC_SDA=txd&0x80;   //获取最高位
        //获取数据的最高位,然后右移7位,假设为 1000 0000 右移7位为 0000 0001 
        // 假设为 0000 0000 右移7位为 0000 0000 
        //如果某位为1,则SDA为1,否则相反
        IIC_SDA=((txd&0x80)>>7);
        txd<<=1;

或者这么写:

//void I2CSendByte(unsigned char cSendByte)
//{
//    unsigned char  i = 8;
//    while (i--)
//    {
//        SCL_Output(0);delay1(500); 
//        SDA_Output(cSendByte & 0x80); delay1(500);
//        cSendByte += cSendByte;        //二进制自加相当于左移一位
//        delay1(500); 
//        SCL_Output(1);delay1(500); 
//    }
//    SCL_Output(0);delay1(500); 
//}


IIC读取一个字节数据:


//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack)
{
    unsigned char i,receive=0;
    SDA_IN();        //SDA设置为输入   (其实就是IPD模式
    for(i=0;i<8;i++ )
    {
        IIC_SCL=0; 
        delay_us(2);
        IIC_SCL=1;
        receive<<=1;                        //左移一位
       if(READ_SDA)receive++;   //使最低位为一
        delay_us(1); 
    }                     
    if (!ack)
        IIC_NAck();        //发送nACK
    else
        IIC_Ack();         //发送ACK   
    return receive;
}

  1. 主机首先产生START信号
  2. 然后紧跟着发送一个从机地址,这个地址共有7位,紧接着的第8位是数据方 向位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)
  3. 主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器
  4. 这时候主机等待从机的应答信号(A)
  5. 当主机收到应答信号时,发送要访问从机的那个地址, 继续等待从机的应答信号
  6. 当主机收到应答信号时,发送N个字节的数据,继续等待从机的N次应答信号,
  7. 主机产生停止信号,结束传送过程。

具体表现为:

void i2c_write(u8 add ,u8 reg ,u8 data)

{ I2CStart();

I2CSendByte(add);

I2CWaitAck();

I2CSendByte(reg);

I2CWaitAck();

I2CSendByte(data);

I2CWaitAck();

I2CStop();

}

函数void i2c_write(u8 add ,u8 reg ,u8 data);
是iic通讯的写函数,第一个 add是设备的地址,如0xa0是EEPROM的地址,0x38 是扩展板上的三轴加速度计的通讯地址。
第二个形参是寄存器的地址,需要注意的是AT24C02的地址是从0x00~0xff,如果超出该地址值会出现错误。地址和写入或读取的数据的类型都是无符号字符型,其他类型的数据有可能出现错误。就是说,但个地址存入读出的值不会超过255,即存取的的数据范围是0 ~255

主机要从从机读数据时

  1. 主机首先产生START信号
  2. 然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令,
  3. 这时候主机等待从机的应答信号(ACK)
  4. 当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号,
  5. 当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据,
  6. 这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不在接收数据
  7. 主机进而产生停止信号,结束传送过程。

u8 i2c_read(u8 add ,u8 reg){
    u8 data ;
    I2CStart();
    I2CSendByte(add);
    I2CWaitAck();
    
    I2CSendByte(reg);
    I2CWaitAck();
    I2CStart();
    I2CSendByte(add+1);
    I2CWaitAck();
    data =I2CReceiveByte();
    I2CWaitAck();

    I2CStop();
    return data;
    
    
}
    

以AT24C02为例子(与连接芯片的差别为寄存器地址改为了具体储存地址

24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。
在这里插入图片描述
A0,A1,A2:硬件地址引脚
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线

以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:
在这里插入图片描述
芯片的寻址:
AT24C设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。A2~A0为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。

  • 一个EEPROM空间(地址)占一个字节,只能存储0-255,就是一个byte值的范围。
  • EEPROM可操作的地址为0~4095。

常见的EEPROM如24C02的大小为2Kbit(有的也称2KB)

2KB中,B表示单位bit,K表示1024。

单片机编程中常用的数据类型为unsigned char(u8)的变量的大小为1字节。

1字节=8bit

因此

2KB = 2*1024/8 = 256字节

也就是说,单片机编程中,一片2KB的EEPROM最多存储256(2的8次方,也就是八位)个u8(unsigned char)类型的数据

AT24C设备地址,前四位固定为1010,而储存不受此限制,可以从0x00开始储存

也就是说如果是
写24C02的时候从器件地址为10100000(0xA0);
读24C02的时候,从器件地址为10100001(0xA1)。

片内地址寻址:

芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。

对应的修改 A2A1A0 三位数据即可
在这里插入图片描述

向AT24C02中写数据
在这里插入图片描述
操作时序:

  1. MCU先发送一个开始信号(START)启动总线
  2. 接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)
  3. 等待应答信号(ACK)
  4. 发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。
  5. 发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。
  6. 发送结束信号(STOP)停止总线

注意:
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。

写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!
所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以



 

从AT24C02中读数据

读当前地址的数据
在这里插入图片描述
2、读随机地址的数据
在这里插入图片描述

  1. MCU先发送一个开始信号(START)启动总线
  2. 接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)
    注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。
  3. 发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。
  4. 重新发送开始信号(START)
  5. 发送设备读操作地址(DEVICE ADDRESS)对E2PROM进行读操作 (0xA1)这里发送读操作地址是为了让从设备知道主设备要读了,为什么不在第二部就发的原因是要先联系上从设备。
  6. E2PROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
  7. 如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线

应答信号(ack

每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,

应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答

  • 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
  • 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

在这里插入图片描述

**每发送一个字节(8个bit)**在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。

å¨è¿éæå¥å¾çæè¿°

应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答,

//主机产生(发送ack)应答信号ACK
//1.先拉低SCL,再拉低SDA
//2.拉高SCL
//3.拉低SCL
## 标题
void I2C_Ack(void)
{
   IIC_SCL=0;   //先拉低SCL,使得SDA数据可以发生改变
   IIC_SDA=0;   
   delay_us(2);
   IIC_SCL=1;
   delay_us(5);
   IIC_SCL=0;
}

标准写法:

unsigned char I2CWaitAck(void)
{
    unsigned short cErrTime = 5;
    SDA_Input_Mode();                 //设为IPU输入模式
    delay1(500);
    SCL_Output(1);delay1(500);
    while(SDA_Input())            //如果一直读端口为1(GPIO_ReadInputDataBit( I2C_PORT, SDA_Pin)),表示没有接受到应答,返回失败
    {
        cErrTime--;
        delay1(500);
        if (0 == cErrTime)
        {
            SDA_Output_Mode();        //实际上是不必要的
            I2CStop();
            return FAILURE;
        }
    }
    SDA_Output_Mode();                //实际上是不必要的
    SCL_Output(0);delay1(500);         
    return SUCCESS;        
}


//主机不产生应答信号,也就是NACK
//1.先拉低SCL,再拉高SDA
//2.拉高SCL
//3.拉低SCL
void I2C_NAck(void)
{
   IIC_SCL=0;   //先拉低SCL,使得SDA数据可以发生改变
   IIC_SDA=1;   //拉高SDA,不产生应答信号
   delay_us(2);
   IIC_SCL=1;
   delay_us(5);
   IIC_SCL=0;
}

3、连续读数据
在这里插入图片描述
E2PROM支持连续写操作,操作和单个字节类似,先发送设备写操作地址(DEVICE ADDRESS),然后发送内存起始地址(WORD ADDRESS),MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据。E2PROM的地址指针会自动递增,数据会依次保存在内存中。不应答发送结束信号后终止传输。

软件IIC和硬件IIC

IIC分为软件IIC和硬件IIC

软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。

硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。

硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。

代码需要注意的点(出现问题也可依次检查)

读取的操作结束后,都建议延迟几毫秒,实测如果不延迟数据极有可能会出错,延迟操作可以加到函数末尾,也可在函数调用后。

调用函数之前,要先使用i2c.c文件中已有的函数i2c_init()来初始化iic要使用到的时钟,引脚等。

读写函数并不能直接存取超过范围的数,如果存入的数不在0到255之间,肯定会出错,地址也一样,要注意范围。

在读取iic的值时,需要再进行一次I2CStart(),不然读取的值也可能会出错!
在这里插入图片描述


版权声明:本文为qq_54215896原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。