第三章 STM32 IIC驱动详解

1、IIC的速度:标准模式100Kbit/s、快速模式下400Kbit/s、高速模式下3.4Mbit/s
2、理论上IIC地址是8位,其中1位广播地址,7位地址,2^7=128,理论上IIC可以挂载128个器件。但IIC总线上可挂接的设备数量受总线的最大电容400pF限制,(管脚都是有输入电容、PCB也会有寄生电容)所有在实际设计中不超过8个器件。

1、 IIC的概念

IIC协议是一种用于电子设备之间进行通信和数据交互的串行通信协议。

IIC协议采用双线结构传输数据,一根数据线SDA,一根时钟线SCL。其中SDA线用于双向数据传输,SCL线则用于同步数据传输的时钟信号。通信始终由主设备控制,从设备被动接收和回应。

2、 IIC详解

  • 主机有权发起和结束一次通信,从机只能被动呼叫

  • 当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生

  • 每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(但同一时刻只能一个主机),总线上的器件增加和删除不影响其它器件正常工作

  • IIC总线在通信时总线上发出数据的器件为发送器,接收数据的器件为接收器。

  • IIC总线可以通过外部连线进行检测,便于系统故障诊断和调试,故障可以立即被寻址,软件也有利于标准化和模块化,缩短开发时间。

    理论上IIC地址是8位,其中1位广播地址,7位地址,2^7=128,理论上IIC可以挂载128个器件。但IIC总线上可挂接的设备数量受总线的最大电容400pF限制,(管脚都是有输入电容、PCB也会有寄生电容)所有在实际设计中不超过8个器件。

    串行的8位双向数据传输速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3Mbit/s

    3、 IIC协议

    IIC时序包括起始信号、停止信号、应答信号和数据传输。

    3.1 信号的时序

    3.1.1 起始信号

    起始信号就是在SCL时钟信号为高电平时,SDA数据信号出现下降沿就表示为起始信号。

    3.1.2 停止信号

    停止信号是SCL为高电平时,SDA出现上升沿表示停止信号

    3.1.3 数据传输

    SDA上数据的变化,只能在SCL为低电平的时候,SCL为高电平时候,SDA上的数据不变。

    3.1.4 应答信号

    应答信号是主机发送完8位数据后,等待从机应答,也就是等待从机告诉主机它接收到了数据,应答信号由从机发送,主机提供应答信号所需的时钟,主机只需要让SCL为高电平,检测SDA是高电平或是低电平以及持续时间,即刻知道应答信号是否有效。

    3.2 IIC设备地址格式

    3.2.1 写数据操作

  • 主机发送起始信号

  • 发送器件地址+读操作(0):8位

  • 从机发送应答信号

  • 主机发送写入寄存器地址

  • 从机发送应答

  • 主机发送写入寄存器数据

  • 从机发送应答

  • 主机发送停止

  • 3.2.2 读数据操作

    IIC读时许操作要比写时许操作要多一点步骤,读时许分为四个步骤,第一步发送设备地址,第二步发送要读取的寄存器地址,第三步从新发送设备地址,最后一步从器件输出要读取的寄存器值。

  • 主机发送起始信号
  • 主机发送器件地址+写操作(0):要先告诉设备我是要操作这个寄存器,要先写入寄存器地址
  • 从机发送应答信号
  • 主机从新发送起始信号
  • 主机发送要读取的寄存器地址+读操作(1):已经告诉过设备我需要操作的寄存器,这里就可以来读取寄存器中数据
  • 从机发送应答信号
  • 从IIC器件里面读取到数据
  • 主机发出NACK信号,表示读取完毕,不需要从机再发送ACK信号
  • 主机发送停止信号
  • 3.2.3 单个和多个写/读数据

    写入单个字节:
    开始信号+设备地址(7bit)写(1bit)+等待从机应答+寄存器地址(8bit)+等待从机应答+写入的数据(8bit)+等待从机应答+停止位
    写入多个字节:
    开始信号+设备地址(7bit)写(1bit)+等待从机应答+寄存器地址(8bit)+等待从机应答+写入数据1(8bit)+等待从机应答+写入数据2(8bit)+等待从机应答+……+停止位

    读取一个字节:
    开始信号+设备地址(7bit)写(1bit)+等待从机应答+寄存器地址(8bit)+等待从机应答开始信号+设备地址(7bit)读(1bit)+等待从机应答+读取寄存器数据(8bit)+主机应答+停止位
    读取多个字节:
    开始信号+设备地址(7bit)写(1bit)+等待从机应答+寄存器地址(8bit)+等待从机应答开始信号+设备地址(7bit)读(1bit)+等待从机应答+读取寄存器数据1(8bit)+主机应答+读取寄存器数据2(8bit)+主机应答+……+停止位

    4、 STM32的软件IIC

    根据IIC时许可知,IIC时许的模拟需要起始信号、停止信号、应答和非应答信号,以及读写字节操作。

    4.1 起始信号

    根据IIC时许可知,IIC时许的模拟需要起始信号、停止信号、应答和非应答信号,以及读写字节操作。

    void IIC_Start(void)
    {
        SDA_OUT();     //sda线输出
        IIC_SDA=1;            
        IIC_SCL=1;
        delay_us(4);
         IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
        delay_us(4);
        IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
    }  

    SDA配置为输出模式,SDA为高电平,SCL(推挽输出)为高电平,一个延时然后让SDA产生下降沿,表示起始信号。这里把SCL时钟线拉低的原因是SCL为低电平时,SDA上的数据变化才能被接收到,SCL为高电平,是不允许SDA上数据发生变化的,为后续的发送或接收数据做准备。

    4.2 停止信号

    void IIC_Stop(void)
    {
        SDA_OUT();//sda线输出
        IIC_SCL=0;
        IIC_SDA=0;
         delay_us(4);
        IIC_SCL=1; 
        IIC_SDA=1;//发送I2C总线结束信号
        delay_us(4);                                   
    }

    SCL从为高电平,SDA产生一个上升沿表示结束信号。代码中SCL、SDA均为低电平,短暂延时后拉高SCL,再拉高SDA让SDA再SCL在高电平时候产生一个上升沿信号表示结束。

    IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
    SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。

    4.3 应答、非应答信号及等待应答

    void IIC_Ack(void)
    {
        IIC_SCL=0;
        SDA_OUT();
        IIC_SDA=0;
        delay_us(2);
        IIC_SCL=1;
        delay_us(2);
        IIC_SCL=0;
    }

    应答信号,SCL从低电平变化为高电平时,持续4us时间SDA为低电平,然后SCL从高电平变化为低电平。

    void IIC_NAck(void)
    {
        IIC_SCL=0;
        SDA_OUT();
        IIC_SDA=1;
        delay_us(2);
        IIC_SCL=1;
        delay_us(2);
        IIC_SCL=0;
    }        

    非应答信号,SCL从低电平变为高电平时,持续4us时间SDA为高电平,然后SCL从高电平变化为低电平。

    u8 IIC_Wait_Ack(void)
    {
        u8 ucErrTime=0;
        SDA_IN();      //SDA设置为输入  
        IIC_SDA=1;delay_us(1);       
        IIC_SCL=1;delay_us(1);     
        while(READ_SDA)
        {
            ucErrTime++;
            if(ucErrTime>250)
            {
                IIC_Stop();
                return 1;
            }
        }
        IIC_SCL=0;//时钟输出0        
        return 0;  

    等待从机给出应答,在主机向从机读取数据时,读取成功需要从机返回一个标志,这个标志就是需要取读取SDA数据线,在SCL=1时,SDA是否由设备端拉低为0。

    4.4 发送一个字节

    void IIC_Send_Byte(u8 txd)
    {                        
        u8 t;   
        SDA_OUT();         
        IIC_SCL=0;//拉低时钟开始数据传输
        for(t=0;t<8;t++)
        {              
            IIC_SDA=(txd&0x80)>>7;
            txd<<=1;       
            delay_us(2);
            IIC_SCL=1;
            delay_us(2); 
            IIC_SCL=0;    
            delay_us(2);
        }     

    SCL为低电平的时候,将要发送的数据给到SDA数据线上(这个时候可以给),SCL为高电平时候,就会产生一个脉冲信号将SDA上面的数据发送给从机设备。SCL为高电平时,SDA上面的数据不能发生变化(即使变化也是无效的,会记录SDA产生上升沿前的数据)。

    IIC发送数据是从最高位开始,然后一步一步的将8bit数据发送到SDA上。

    4.5 读取一个字节

    u8 IIC_Read_Byte(unsigned char ack)
    {
        unsigned char i,receive=0;
        SDA_IN();//SDA设置为输入
        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;
    }

    读取数据也是在SCL从低电平变化为高电平后,获取SDA上面发送的数据。获取完8bit数据后,主机发送应答信号给从机。

    4.6 模拟IIC获取AT24C02

    4.6.1 读取数据

    发送起始信号,发送设备地址+写命令,等待从机应答,发送设备寄存器地址,等待应答,从新发送起始信号,发送设备寄存器地址,等待应答,读数据,发送停止信号。

    u8 AT24CXX_ReadOneByte(u16 ReadAddr)
    {                  
        u8 temp=0;                                                                                   
        IIC_Start();  
        IIC_Send_Byte(0XA0);       //发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(ReadAddr>>8);//发送高地址 
        IIC_Wait_Ack(); 
        IIC_Send_Byte(ReadAddr%256);   //发送低地址
        IIC_Wait_Ack();        
        IIC_Start();              
        IIC_Send_Byte(0XA1);           //进入接收模式               
        IIC_Wait_Ack();
        temp=IIC_Read_Byte(0);
        IIC_Stop();//产生一个停止条件        
        return temp;
    }

    //在AT24CXX里面的指定地址开始读出长度为Len的数据
    //该函数用于读出16bit或者32bit的数据.
    //ReadAddr   :开始读出的地址 
    //返回值     :数据
    //Len        :要读出数据的长度2,4
    u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
    {      
        u8 t;
        u32 temp=0;
        for(t=0;t<Len;t++)
        {
            temp<<=8;
            temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);                  
        }
        return temp;                                                    
    }

    指定开始地址,读多少个数据
    void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
    {
        while(NumToRead)
        {
            *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);    
            NumToRead–;
        }
    }  

    4.6.2 写数据

    void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
    {                                                                             
        IIC_Start();  
        IIC_Send_Byte(0XA0);        //发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(WriteAddr>>8);//发送高地址     
        IIC_Wait_Ack();       
        IIC_Send_Byte(WriteAddr%256);   //发送低地址
        IIC_Wait_Ack();                                                    
        IIC_Send_Byte(DataToWrite);     //发送字节                               
        IIC_Wait_Ack();                     
        IIC_Stop();//产生一个停止条件 
        delay_ms(10);     
    }
    void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
    {      
        u8 t;
        for(t=0;t<Len;t++)
        {
            AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
        }                                                    
    }

    指定地址写入多少个数据
    void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
    {
        while(NumToWrite–)
        {
            AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
            WriteAddr++;
            pBuffer++;
        }
    }

    4.6.3 检测AT24C02是否正常

    //检查AT24CXX是否正常
    //这里用了24XX的最后一个地址(255)来存储标志字.
    //如果用其他24C系列,这个地址要修改
    //返回1:检测失败
    //返回0:检测成功
    u8 AT24CXX_Check(void)
    {
        u8 temp;
        temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX               
        if(temp==0X55)return 0;           
        else//排除第一次初始化的情况
        {
            AT24CXX_WriteOneByte(255,0X55);
            temp=AT24CXX_ReadOneByte(255);      
            if(temp==0X55)return 0;
        }
        return 1;                                              
    }

    5、 STM32的硬件IIC

    STM32控制器内置硬件IIC模块,支持配置IIC主机和从机模式。

    硬件IIC发送数据,不会被中断所打断,因为硬件IIC发送数据是由硬件完成的,只受外部晶振时钟影响。

    主模式时, I2C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停止
    条件结束。起始条件和停止条件都是在主模式下由软件控制产生。

    从模式时, I2C接口能识别它自己的地址(7位或10位)和广播呼叫地址。软件能够控制开启或禁止广播呼叫地址的识别。

    数据和地址按8位/字节进行传输,高位在前。跟在起始条件后的1或2个字节是地址(7位模式为1
    个字节, 10位模式为2个字节)。地址只在主模式发送。

    在一个字节传输的8个时钟后的第9个时钟期间,接收器必须回送一个应答位(ACK)给发送器。

    硬件IIC:利用STM32芯片中的硬件IIC外设,和USART串口外设类似,只需要配置好对应的寄存器,外设就会产生标准串口协议的时许。

    5.1 硬件IIC

    5.1.1 时钟控制

    时钟控制寄存器(I2C_CCR)用来控制在主模式下,输出时钟频率(标准模式、快速模式)。

    Thigh:高电平的时间
    Tlow: 低电平的时间    
    Tpclk1:1/36MHz
    时钟周期SCL:高电平时间+低电平时间

    5.1.2 数据控制

    数据控制寄存器(I2C_DR)

  • 当向外发送数据的时候,数据移位寄存器以“数据寄存器”为数据源,把数据一位一位地通过SDA信号线发送出去。(从数据寄存器中发)

  • 当从外部接收数据的时候,数据移位寄存器把SDA信号线采样到的数据一位一位地存储到“数据寄存器”中。(从数据寄存器中拿)

  • CPU或DMA从DATA REGISTER(数据寄存器)中获取数据。这个里面可以是外设返回的数据,也可以是CPU向外设发送的数据。

    如果IIC配置在从机模式,就需要设置自身的地址,在寄存器I2C_OAR1和I2C_OAR2中

    5.1.3 逻辑控制

    前面看了IIC时钟信号的配置、数据的配置,逻辑控制就是描述:IIC起始信号、停止信号、应答信号等配置。

    控制寄存器(I2C_CR1)

    在I2C_SR1寄存器中,只要看bit10、bit7、bit6、bit3、bit2、bit1这几个位的信息。

    5.2 硬件IIC软件配置

    5.2.1 IIC初始化

    I2C_InitStuctrue.I2C_Ack=I2C_Ack_Enable;                              // 设置ACK应答
    I2C_InitStuctrue.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;// 设置设备地址位数
    I2C_InitStuctrue.I2C_ClockSpeed=400000;                                 // 设置IIC通信时钟速度
    I2C_InitStuctrue.I2C_DutyCycle=I2C_DutyCycle_2;                    // 设置时钟信号占空比时间
    I2C_InitStuctrue.I2C_Mode=I2C_Mode_I2C;                            // 设置为IIC通信模式
    I2C_InitStuctrue.I2C_OwnAddress1=STM32_I2C_OWN_ADDR;            // 设置IIC控制器地址
    I2C_Init(EEPROM_I2C,&I2C_InitStuctrue);
    I2C_Cmd(EEPROM_I2C,ENABLE);

    5.2.2 写入一个字节

    // 发送起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    // 检测EVS事件   模式选择
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
    // 发送设备写地址
    I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
    // 检测EV事件
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
    // 发送要操作设备内部的地址
    I2C_SendData(EEPROM_I2C,addr);
    // 检测字节发送
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR);
      I2C_SendData(EEPROM_I2C,data);
    // 检测EV事件
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED )==ERROR);
    // 发送停止信号
    I2C_GenerateSTOP(EEPROM_I2C,ENABLE);

    5.1.3 读取一个字节

    // 判断IIC总线是否忙碌
    while(I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))  {;}
    // 发送起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    // 等待事件
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
    // 发送设备地址写数据
    I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
    // 等待事件
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED )==ERROR);
    // 发送要操作设备内部寄存器地址
    I2C_SendData(EEPROM_I2C,addr);
    // 检测事件
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR);
    // 发送起始信号
    I2C_GenerateSTART(EEPROM_I2C,ENABLE);
    // 检测事件
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT )==ERROR);
    // 发送设备读地址操作
    I2C_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Receiver);
    // 检测
    while( I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR);
    // 读取数据
     *data=I2C_ReceiveData(EEPROM_I2C);
    // 发送非应答信号
    I2C_AcknowledgeConfig(EEPROM_I2C,DISABLE);
    // 发送停止信号
    I2C_GenerateSTOP(EEPROM_I2C,ENABLE);

    作者:嘿·嘘

    物联沃分享整理
    物联沃-IOTWORD物联网 » 第三章 STM32 IIC驱动详解

    发表回复