STM32 IIC通信详解及实验教程(野火指南者)

一、简介  

       ​   iic是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路 (IC) 间的通讯。

二、物理层

iic只由两条总线构成:     

        SDA(Serial Data)是数据线,D代表Data也就是数据,用来传输数据的串行总线

        SCL(Serial Clock)是时钟线,C代表Clock也就是时钟,控制数据收发时序的串行总线

物理层特点:

(1)
它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个
I2C
通讯总线中,可连

     接多个 I2C
通讯设备,支持多个通信主机及多个通讯从机

(2)
一个
I2C
总线只使用两条总线线路,一条双向串行数据线
(SDA)
,一条串行时钟线
(SCL)
。数

     据线即用来表示数据,时钟线用于数据收发同步。

(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访          问。

(4)
总线通过上拉电阻接到电源。当
I2C
设备空闲时,会输出高阻态,而当所有设备都空闲,都

     输出高阻态时,由上拉电阻把总线拉成高电平。

(5)
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线

(6)
具有三种传输模式:标准模式传输速率为
100kbit/s
,快速模式为
400kbit/s
,高速模式下可达

      3.4Mbit/s,但目前大多
II
C
设备尚不支持高速模式。

(7)
连接到相同总线的
IC
数量受到总线的最大电容
400pF
限制

三、协议层

        I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。

3.1基本读写过程

        先简单看看 IIC
通讯过程的基本结构,它的通信过程见图
主机写数据到从机
、图
主机由从机中读数
据及图 IIC 通信复合格式 。

3.1.1 写数据

        若配置的方向传输位为“写数据”方向,广播完地址,接收到应答信号后,主机开始正式向从机传输数据
(DATA)
,数据包的大小为
8
位,主机每发送完一个字节数据,都
要等待从机的应答信号
(ACK)
,重复这个过程,可以向从机传输
N
个数据,这个
N
没有大小限
制。当数据传输结束时,主机向从机发送一个停止传输信号
(P)
,表示不再传输数据。

3.1.2 读数据

        若配置的方向传输位为“读数据”方向,广播完地址,接收到应答信号后,
从机开始向主机返回数据
(DATA)
,数据包大小也为
8
位,从机每发送完一个数据,都会等待主
机的应答信号
(ACK)
,重复这个过程,可以返回
N
个数据,这个
N
也没有大小限制。当主机希
望停止接收数据时,就向从机返回一个非应答信号
(NACK)
,则从机自动停止数据传输。

3.1.3 复合读写格式

        除了基本的读写,I2C
通讯更常用的是复合格式,该传输过程有两次起始信

(S)
。一般在第一次传输中,主机通过
SLAVE_ADDRESS
寻找到从设备后,发送一段“数据”,
这段数据通常用于表示从设备内部的寄存器或存储器地址
(
注意区分它与
SLAVE_ADDRESS

区别
)
;在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读
写地址,第二次则是读写的实际内容。

3.2 起始信号和停止信号

        前文中提到的起始 (S)
和停止
(P)
信号是两种特殊的状态,见下图

。当
SCL
线是高
电平时
SDA
线从高电平向低电平切换,这个情况表示通讯的起始。当
SCL
是高电平时
SDA
线由
低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

3.3 数据有效性

        I2C 使用
SDA
信号线来传输数据,使用
SCL
信号线进行数据同步。见下图


SDA
数据
线在
SCL
的每个时钟周期传输一位数据。传输时,
SCL
为高电平的时候
SDA
表示的数据有效,
即此时的
SDA
为高电平时表示数据“
1
”,为低电平时表示数据“
0
”。当
SCL
为低电平时,
SDA
的数据无效,一般在这个时候
SDA
进行电平切换,为下一次表示数据做好准备。

3.4 地址及数据方向

        I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS) 来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位 (R/),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。

3.5 响应

        I2C 的数据和地址传输都带响应。响应包括“应答
(ACK)
”和“非应答
(NACK)
”两种信号。作为
数据接收端时,当设备
(
无论主从机
)
接收到
I2C
传输的一个字节数据或地址后,若希望对方继
续发送数据,则需要向对方发送“应答
(ACK)
”信号,发送方会继续发送下一个数据;若接收端
希望结束数据传输,则向对方发送“非应答
(NACK)
”信号,发送方接收到该信号后会产生一个
停止信号,结束信号传输。

        传输时主机产生时钟,在第 9
个时钟时,数据发送端会释放
SDA
的控制权,由数据接收端控制
SDA
,若
SDA
为高电平,表示非应答信号
(NACK)
,低电平表示应答信号
(ACK)

四、STM32 的 IIC 架构

        

        STM32 的
I2C
外设可用作通讯的主机及从机,支持
100Kbit/s

400Kbit/s
的速率,支持
7
位、
10位设备地址,支持
DMA
数据传输,并具有数据校验功能。它的
I2C
外设还支持
SMBus2.0
协议,
SMBus
协议与
I2C
类似,主要应用于笔记本电脑的电池管理中,本文不展开。

        下图为STM32 的 IIC 架构图,下文将分为四部分进行讲解。

4.1 通信引脚

        II
C
的所有硬件架构都是根据图中左侧
SCL
线和
SDA
线展开的
(
其中的
SMBA
线用于
SMBUS
的警告信号,
I2C
通讯没有使用
)

STM32
芯片有多个
I2C
外设,它们的
I2C
通讯信号引出到不同的
GPIO
引脚上,使用时必须配置到这些指定的引脚。

4.2 时钟控制逻辑

SCL
线的时钟信号,由
II
C
接口根据时钟控制寄存器
(CCR)
控制,控制的参数主要为时钟频率。

配置
I2C

CCR
寄存器可修改通讯速率相关的参数:

  • 可选择 I2C 通讯的“标准/快速”模式,这两个模式分别 I2C 对应 100/400Kbit/s 的通讯速率。
  • 在快速模式下可选择 SCL 时钟的占空比,可选 Tlow/Thigh=2 或 Tlow/Thigh=16/9 模式,我们知道 I2C 协议在 SCL 高电平时对 SDA 信号采样,SCL 低电平时 SDA 准备下一个数据,修改 SCL 的高低电平比会影响数据采样,但其实这两个模式的比例差别并不大,若不是要求非常严格,这里随便选就可以了。
  • CCR 寄存器中还有一个 12 位的配置因子 CCR,它与 I2C 外设的输入时钟源共同作用,产生SCL 时钟,STM32 的 I2C 外设都挂载在 APB1 总线上,使用 APB1 的时钟源 PCLK1,SCL
  • 信号线的输出时钟公式如下:

    例如,我们的
    PCLK1=36MHz
    ,想要配置
    400Kbit/s
    的速率,计算方式如下:

    PCLK
    时钟周期:
    TPCLK1 = 1/36000000

    目标
    SCL
    时钟周期:
    TSCL = 1/400000

    SCL
    时钟周期内的高电平时间:
    THIGH = TSCL/3

    SCL
    时钟周期内的低电平时间:
    TLOW = 2*TSCL/3

    计算
    CCR
    的值:
    CCR = THIGH/TPCLK1 = 30

    计算结果得出
    CCR

    30
    ,向该寄存器位写入此值则可以控制
    IIC
    的通讯速率为
    400KHz
    ,其实即使配置出来的
    SCL
    时钟不完全等于标准的
    400KHz

    IIC
    通讯的正确性也不会受到影响,因为所
    有数据通讯都是由
    SCL
    协调的,只要它的时钟频率不远高于标准即可。

    4.3 数据控制逻辑

    I2C

    SDA
    信号主要连接到数据移位寄存器上,数据移位寄存器的数据来源及目标是数据寄存

    (DR)
    、地址寄存器
    (OAR)

    PEC
    寄存器以及
    SDA
    数据线。当向外发送数据的时候,数据移位
    寄存器以“数据寄存器”为数据源,把数据一位一位地通过
    SDA
    信号线发送出去;当从外部接收
    数据的时候,数据移位寄存器把
    SDA
    信号线采样到的数据一位一位地存储到“数据寄存器”中。
    若使能了数据校验,接收到的数据会经过
    PCE
    计算器运算,运算结果存储在“
    PEC
    寄存器”中。

    STM32

    I2C
    工作在从机模式的时候,接收到设备地址信号时,数据移位寄存器会把接收到
    的地址与
    STM32
    的自身的“
    I2C
    地址寄存器”的值作比较,以便响应主机的寻址。
    STM32
    的自

    I2C
    地址可通过修改“自身地址寄存器”修改,支持同时使用两个
    I2C
    设备地址,两个地址分
    别存储在
    OAR1

    OAR2
    中。

    4.4 整体逻辑控制

            整体控制逻辑负责协调整个 I2C
    外设,控制逻辑的工作模式根据我们配置的“控制寄存器
    (CR1/CR2)
    ”的参数而改变。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存

    (SR1

    SR2)
    ”,我们只要读取这些寄存器相关的寄存器位,就可以了解
    I2C
    的工作状态。除
    此之外,控制逻辑还根据要求,负责控制产生
    I2C
    中断信号、
    DMA
    请求及各种
    I2C
    的通讯信号
    (
    起始、停止、响应信号等
    )

    4.5 具体通信过程

            使用 I2C
    外设通讯时,在通讯的不同阶段它会对“状态寄存器
    (SR1

    SR2)
    ”的不同数据位写入
    参数,我们通过读取这些寄存器标志来了解通讯状态。

    4.5.1 主发送器

    主发送器发送流程及事件说明如下:

    (1)
    控制产生起始信号
    (S)
    ,当发生起始信号后,它产生事件“
    EV5
    ”,并会对
    SR1
    寄存器的“
    SB
    ”位置
    1
    ,表示起始信号已经发送;

    (2)
    紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“
    EV6
    ”及“
    EV8
    ”,这时SR1
    寄存器的“
    ADDR
    ”位及“
    TXE
    ”位被置
    1

    ADDR

    1
    表示地址已经发送,
    TXE

    1 表示
    数据寄存器为空;

    (3)
    以上步骤正常执行并对
    ADDR
    位清零后,我们往
    I2C
    的“数据寄存器
    DR
    ”写入要发送的数据,这时
    TXE
    位会被重置
    0
    ,表示数据寄存器非空,
    I2C
    外设通过
    SDA
    信号线一位位把数据发送
    出去后,又会产生“
    EV8
    ”事件,即
    TXE
    位被置
    1
    ,重复这个过程,就可以发送多个字节数据了;

    (4)
    当我们发送数据完成后,控制
    I2C
    设备产生一个停止信号
    (P)
    ,这个时候会产生
    EV8_2
    事件,SR1

    TXE
    位及
    BTF
    位都被置
    1
    ,表示通讯结束。

            假如我们使能了 I2C
    中断,以上所有事件产生时,都会产生
    I2C
    中断信号,进入同一个中断服务
    函数,到
    I2C
    中断服务程序后,再通过检查寄存器位来判断是哪一个事件。

    4.5.2 主接收器

    主接收器接收流程及事件说明如下:

    (1)
    同主发送流程,起始信号
    (S)
    是由主机端产生的,控制发生起始信号后,它产生事件“
    EV5
    ”,并会对
    SR1
    寄存器的“
    SB
    ”位置
    1
    ,表示起始信号已经发送;

    (2)
    紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“
    EV6
    ”这时
    SR1
    寄存器

    的“
    ADDR
    ”位被置
    1
    ,表示地址已经发送。

    (3)
    从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生“
    EV7
    ”事件,
    SR1
    寄存器的
    RXNE
    被置
    1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据
    寄存器清空,以便接收下一次数据。此时我们可以控制
    I2C
    发送应答信号
    (ACK)
    或非应答信号
    (NACK)
    ,若应答,则重复以上步骤接收数据,若非应答,则停止传输;

    (4)
    发送非应答信号后,产生停止信号
    (P)
    ,结束传输。在发送和接收过程中,有的事件不只是标志了我们上面提到的状态位,还可能同时标志主机状态
    之类的状态位,而且读了之后还需要清除标志位,比较复杂。我们可使用
    STM32
    标准库函数来
    直接检测这些事件的复合标志,降低编程难度。

    五、IIC 通信实验

    5.1 IIC结构体

    typedef struct {
     uint32_t I2C_ClockSpeed; /*!< 设置 SCL 时钟频率,此值要低于 400000*/
     uint16_t I2C_Mode; /*!< 指定工作模式,可选 I2C 模式及 SMBUS 模式 */
     uint16_t I2C_DutyCycle; /* 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式*/
     uint16_t I2C_OwnAddress1; /*!< 指定自身的 I2C 设备地址 */
     uint16_t I2C_Ack; /*!< 使能或关闭响应 (一般都要使能) */
     uint16_t I2C_AcknowledgedAddress; /*!< 指定地址的长度,可为 7 位及 10 位 */
    } I2C_InitTypeDef;

    (1) I2C_ClockSpeed

            本成员设置的是 I2C 的传输速率,在调用初始化函数时,函数会根据我们输入的数值经过运算后把时钟因子写入到 I2C 的时钟控制寄存器 CCR。而我们写入的这个参数值不得高于 400KHz。实际上由于 CCR 寄存器不能写入小数类型的时钟因子,影响到 SCL 的实际频率可能会低于本成员设置的参数值,这时除了通讯稍慢一点以外,不会对 I2C 的标准通讯造成其它影响。

    (2) I2C_Mode

            本成员是选择 I
    2
    C
    的使用方式,有
    I
    2
    C
    模式
    (I2C_Mode_I2C)

    SMBus
    主、从模式

    (I2C_Mode_SMBusHost

    I2C_Mode_SMBusDevice )

    I2C
    不需要在此处区分主从模式,直接设

    I2C_Mode_I2C
    即可。

    (3) I2C_DutyCycle

            本成员设置的是 I
    2
    C

    SCL
    线时钟的占空比。该配置有两个选择,分别为低电平时间比高电平时间为
    2

    1 ( I2C_DutyCycle_2)

    16

    9 (I2C_DutyCycle_16_9)
    。其实这两个模式的比例差别并不
    大,一般要求都不会如此严格,这里随便选就可以。

    (4) I2C_OwnAddress1

            本成员配置的是 STM32

    I2C
    设备自己的地址,每个连接到
    I2C
    总线上的设备都要有一个自己
    的地址,作为主机也不例外。地址可设置为
    7
    位或
    10

    (
    受下面
    I2C_AcknowledgeAddress
    成员决

    )
    ,只要该地址是
    I2C
    总线上唯一的即可。
    STM32

    I2C
    外设可同时使用两个地址,即同时对两个地址作出响应,这个结构成员
    I2C_OwnAddress1
    配置的是默认的、
    OAR1
    寄存器存储的地址,若需要设置第二个地址寄存器
    OAR2
    ,可使用
    I2C_OwnAddress2Config
    函数来配置,
    OAR2
    不支持
    10
    位地址,只有
    7
    位。

    (5) I2C_Ack_Enable

            本成员是关于 I
    2
    C
    应答设置,设置为使能则可以发送响应信号。本实验配置为允许应答
    (I2C_Ack_Enable)
    ,这是绝大多数遵循
    I
    2
    C
    标准的设备的通讯要求,改为禁止应答
    (I2C_Ack_Disable)
    往往会导致通讯错误。

    (6) I2C_AcknowledgeAddress

            本成员选择 I2C
    的寻址模式是
    7
    位还是
    10
    位地址。这需要根据实际连接到
    I2C
    总线上设备的地址进行选择,这个成员的配置也影响到
    I2C_OwnAddress1
    成员,只有这里设置成
    10
    位模式时,
    I2C_OwnAddress1
    才支持
    10
    位地址。

    5.2 核心代码

    我使用的是野火的指南者开发板,代码仅供参考。

    5.2.1 硬件IIC

            以 STM32 I2C1外设为例,它连接了一个EEPROM,硬件 IIC 即直接使用片上IIC外设,但是硬件 IIC 不稳定,有时会莫名卡住,还是推荐使用软件IIC。

    /**
      * @brief  I2C I/O配置
      * @param  无
      * @retval 无
      */
    static void I2C_GPIO_Config(void)
    {
      GPIO_InitTypeDef  GPIO_InitStructure; 
    
    	/* 使能与 I2C 有关的时钟 */
    	EEPROM_I2C_APBxClock_FUN ( EEPROM_I2C_CLK, ENABLE );
    	EEPROM_I2C_GPIO_APBxClock_FUN ( EEPROM_I2C_GPIO_CLK, ENABLE );
    	
        
      /* I2C_SCL、I2C_SDA*/
      GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       // 开漏输出
      GPIO_Init(EEPROM_I2C_SCL_PORT, &GPIO_InitStructure);
    	
      GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SDA_PIN;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       // 开漏输出
      GPIO_Init(EEPROM_I2C_SDA_PORT, &GPIO_InitStructure);		
    }
    
    /**
      * @brief  I2C 工作模式配置
      * @param  无
      * @retval 无
      */
    static void I2C_Mode_Config(void)
    {
      I2C_InitTypeDef  I2C_InitStructure; 
    
      /* I2C 配置 */
      I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    	
    	/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
      I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    	
      I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7; 
      I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
    	 
    	/* I2C的寻址模式 */
      I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    	
    	/* 通信速率 */
      I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
      
    	/* I2C 初始化 */
      I2C_Init(EEPROM_I2Cx, &I2C_InitStructure);
      
    	/* 使能 I2C */
      I2C_Cmd(EEPROM_I2Cx, ENABLE);   
    }
    /**
      * @brief   从EEPROM里面读取一块数据 
      * @param   
      *		@arg pBuffer:存放从EEPROM读取的数据的缓冲区指针
      *		@arg WriteAddr:接收数据的EEPROM的地址
      *     @arg NumByteToWrite:要从EEPROM读取的字节数
      * @retval  无
      */
    uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
    {  
      
      I2CTimeout = I2CT_LONG_TIMEOUT;
      
      //*((u8 *)0x4001080c) |=0x80; 
      while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY))
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
       }
      
      /* Send START condition */
      I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
      //*((u8 *)0x4001080c) &=~0x80;
      
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV5 and clear it */
      while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
       }
      
      /* Send EEPROM address for write */
      I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);
    
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV6 and clear it */
      while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);
       }
        
      /* Clear EV6 by setting again the PE bit */
      I2C_Cmd(EEPROM_I2Cx, ENABLE);
    
      /* Send the EEPROM's internal address to write to */
      I2C_SendData(EEPROM_I2Cx, ReadAddr);  
    
       
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV8 and clear it */
      while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);
       }
        
      /* Send STRAT condition a second time */  
      I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
      
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV5 and clear it */
      while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);
       }
        
      /* Send EEPROM address for read */
      I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);
      
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV6 and clear it */
      while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);
       }
      
      /* While there is data to be read */
      while(NumByteToRead)  
      {
        if(NumByteToRead == 1)
        {
          /* Disable Acknowledgement */
          I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);
          
          /* Send STOP Condition */
          I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
        }
    
        /* Test on EV7 and clear it */    
        I2CTimeout = I2CT_LONG_TIMEOUT;
        
    		while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)  
    		{
    			if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
    		} 
        {      
          /* Read a byte from the EEPROM */
          *pBuffer = I2C_ReceiveData(EEPROM_I2Cx);
    
          /* Point to the next location where the byte read will be saved */
          pBuffer++; 
          
          /* Decrement the read bytes counter */
          NumByteToRead--;        
        }   
      }
    
      /* Enable Acknowledgement to be ready for another reception */
      I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);
      
        return 1;
    }
    
    /**
      * @brief   在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数
      *          不能超过EEPROM页的大小,AT24C02每页有8个字节
      * @param   
      *		@arg pBuffer:缓冲区指针
      *		@arg WriteAddr:写地址
      *     @arg NumByteToWrite:写的字节数
      * @retval  无
      */
    uint32_t I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
    {
      I2CTimeout = I2CT_LONG_TIMEOUT;
    
      while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY))   
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);
      } 
      
      /* Send START condition */
      I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
      
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV5 and clear it */
      while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))  
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);
      } 
      
      /* Send EEPROM address for write */
      I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);
      
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV6 and clear it */
      while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))  
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);
      } 
      
      /* Send the EEPROM's internal address to write to */    
      I2C_SendData(EEPROM_I2Cx, WriteAddr);  
    
      I2CTimeout = I2CT_FLAG_TIMEOUT;
      /* Test on EV8 and clear it */
      while(! I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
      {
        if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);
      } 
    
      /* While there is data to be written */
      while(NumByteToWrite--)  
      {
        /* Send the current byte */
        I2C_SendData(EEPROM_I2Cx, *pBuffer); 
    
        /* Point to the next byte to be written */
        pBuffer++; 
      
        I2CTimeout = I2CT_FLAG_TIMEOUT;
    
        /* Test on EV8 and clear it */
        while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        {
          if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);
        } 
      }
    
      /* Send STOP condition */
      I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
      
      return 1;
    }
    5.2.2软件 IIC

            就是用两个 GPIO 模拟 IIC 的 SDA 和 SCL 总线,用软件拉高拉低的它们的电平,模拟出 IIC 协议。

    /*
    *********************************************************************************************************
    *	函 数 名: i2c_Delay
    *	功能说明: I2C总线位延迟,最快400KHz
    *	形    参:无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    static void i2c_Delay(void)
    {
    	uint8_t i;
    
    	/* 
    	 	下面的时间是通过逻辑分析仪测试得到的。
        工作条件:CPU主频72MHz ,MDK编译环境,1级优化
      
    		循环次数为10时,SCL频率 = 205KHz 
    		循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us 
    	 	循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us 
    	*/
    	for (i = 0; i < 10; i++);
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: i2c_Start
    *	功能说明: CPU发起I2C总线启动信号
    *	形    参:无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void i2c_Start(void)
    {
    	/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
    	EEPROM_I2C_SDA_1();
    	EEPROM_I2C_SCL_1();
    	i2c_Delay();
    	EEPROM_I2C_SDA_0();
    	i2c_Delay();
    	EEPROM_I2C_SCL_0();
    	i2c_Delay();
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: i2c_Stop
    *	功能说明: CPU发起I2C总线停止信号
    *	形    参:无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void i2c_Stop(void)
    {
    	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
    	EEPROM_I2C_SDA_0();
    	EEPROM_I2C_SCL_1();
    	i2c_Delay();
    	EEPROM_I2C_SDA_1();
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: i2c_SendByte
    *	功能说明: CPU向I2C总线设备发送8bit数据
    *	形    参:_ucByte : 等待发送的字节
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void i2c_SendByte(uint8_t _ucByte)
    {
    	uint8_t i;
    
    	/* 先发送字节的高位bit7 */
    	for (i = 0; i < 8; i++)
    	{		
    		if (_ucByte & 0x80)
    		{
    			EEPROM_I2C_SDA_1();
    		}
    		else
    		{
    			EEPROM_I2C_SDA_0();
    		}
    		i2c_Delay();
    		EEPROM_I2C_SCL_1();
    		i2c_Delay();	
    		EEPROM_I2C_SCL_0();
    		if (i == 7)
    		{
    			 EEPROM_I2C_SDA_1(); // 释放总线
    		}
    		_ucByte <<= 1;	/* 左移一个bit */
    		i2c_Delay();
    	}
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: i2c_ReadByte
    *	功能说明: CPU从I2C总线设备读取8bit数据
    *	形    参:无
    *	返 回 值: 读到的数据
    *********************************************************************************************************
    */
    uint8_t i2c_ReadByte(void)
    {
    	uint8_t i;
    	uint8_t value;
    
    	/* 读到第1个bit为数据的bit7 */
    	value = 0;
    	for (i = 0; i < 8; i++)
    	{
    		value <<= 1;
    		EEPROM_I2C_SCL_1();
    		i2c_Delay();
    		if (EEPROM_I2C_SDA_READ())
    		{
    			value++;
    		}
    		EEPROM_I2C_SCL_0();
    		i2c_Delay();
    	}
    	return value;
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: i2c_WaitAck
    *	功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
    *	形    参:无
    *	返 回 值: 返回0表示正确应答,1表示无器件响应
    *********************************************************************************************************
    */
    uint8_t i2c_WaitAck(void)
    {
    	uint8_t re;
    
    	EEPROM_I2C_SDA_1();	/* CPU释放SDA总线 */
    	i2c_Delay();
    	EEPROM_I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
    	i2c_Delay();
    	if (EEPROM_I2C_SDA_READ())	/* CPU读取SDA口线状态 */
    	{
    		re = 1;
    	}
    	else
    	{
    		re = 0;
    	}
    	EEPROM_I2C_SCL_0();
    	i2c_Delay();
    	return re;
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: i2c_Ack
    *	功能说明: CPU产生一个ACK信号
    *	形    参:无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void i2c_Ack(void)
    {
    	EEPROM_I2C_SDA_0();	/* CPU驱动SDA = 0 */
    	i2c_Delay();
    	EEPROM_I2C_SCL_1();	/* CPU产生1个时钟 */
    	i2c_Delay();
    	EEPROM_I2C_SCL_0();
    	i2c_Delay();
    	EEPROM_I2C_SDA_1();	/* CPU释放SDA总线 */
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: i2c_NAck
    *	功能说明: CPU产生1个NACK信号
    *	形    参:无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void i2c_NAck(void)
    {
    	EEPROM_I2C_SDA_1();	/* CPU驱动SDA = 1 */
    	i2c_Delay();
    	EEPROM_I2C_SCL_1();	/* CPU产生1个时钟 */
    	i2c_Delay();
    	EEPROM_I2C_SCL_0();
    	i2c_Delay();	
    }

    作者:嵌功尽弃

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 IIC通信详解及实验教程(野火指南者)

    发表评论