I2C

前提

Mini_F0140原理图上I2C连接着24C02:

1.png

一.IIC介绍

I2C(Inter-Integrated Circuit)是一种通用的总线协议,实现I2C需要两根信号线完成信息交换,SCL时钟作为信号线,SDA作为数据输入/输出线。I2C属于同步通信,由于输入输出数据均使用一根线,因此通信方向为半双工。

主要特征有:

1. I2C 总线协议转换器/并行总线

2. 半双工同步操作

3. 支持主从模式

4. 支持 7 位和 10 位地址格式

5. 支持起始(START)、停止(STOP)、重新起始(RESTART)以及应答(ACK)信号的生成和检测

6. 支持标准模式(最大 100Kbps)、快速模式(最大 400Kbps)

7. 分别有 2 字节的发送和接收缓冲

8. 支持过滤毛刺功能

9. 支持 DMA 操作

10. 支持中断和查询操作

11. 支持多个从地址(详细见用户手册)

下图为信号变化及其特定含义:

2.png

当主发送器如下图所示传输数据时,从接收器在接收到的每个字节后产生一个 ACK 来响应主发送器。

3.png

二.24C02

24C02是低工作电压的2K位串行电可擦除只读存储器,内部组织为256个字节,每个字节8位,该芯片被广泛应用于低电压及低功耗的工商业领域。

主器件通过发送一个起始信号启动发送过程,然后发送它所要寻址的从器件的地址。8位从器件地址的高4位固定为1010。接下来的3位(A2、A1、A0)为器件的地址位。当接收数据时,与I2C连接的从器件名为0xA0。

三.例程

向从机写数据后,再读取数据。

1. I2C配置

RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);
I2C_StructInit(&I2C_InitStruct);    
//Configure I2C as master mode    
I2C_InitStruct.Mode = I2C_CR_MASTER;    
I2C_InitStruct.OwnAddress = 0;    
I2C_InitStruct.Speed = I2C_CR_STD;    
I2C_InitStruct.ClockSpeed = 100000;    
I2C_Init(I2Cx, &I2C_InitStruct);    
I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter);    
I2C_Cmd(I2Cx, ENABLE);

2. 定义24C02结构体

typedef struct {
    u8 busy;
    u8 ack;
    u8 fault;
    u8 opt;
    u8 sub;
    u8 cnt;
    u8* ptr;
    u8 sadd;
} gEepromTypeDef;

3. 写数据

EEPROM_WriteByte(sub);
while (cnt --) {
        EEPROM_WriteByte(*ptr);
        ptr++;
}
I2C_GenerateSTOP(I2C1, ENABLE);
while((I2C_GetITStatus(I2C1, I2C_IT_STOP_DET)) == 0);    
gEeprom.ack = true;    
gEeprom.busy = false;    
EEPROM_WaitEEready();    
return true;

4. 读数据

u8 i, flag = 0, _cnt = 0;
for (i = 0; i < gEeprom.cnt; i++) {
    while(1) {
        //Write command is sent when RX FIFO is not full
        if ((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (flag == 0)) {
            I2C_ReadCmd(I2C1);
            _cnt++;
            if (_cnt == gEeprom.cnt)
                flag = 1;
        }
        //Check receive FIFO not empty
        if (I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE)) {
            gEeprom.ptr[i] = I2C_ReceiveData(I2C1);
            break;
        }
    }
}

5. 结果

4.png

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 27

I2C接口简介

I2C接口是由数据线SDA和时钟线SCL构成,在标准模式下通信速度可达到100kHz,快速模式下则可以达到400kHz,增强快速模式可达到1MHz。一帧数据传输从开始信号开始,在结束信号后停止,在收到开始信号后总线被认为是繁忙的,当收到结束信号后,总线被认为再次空闲。I2C接口具有主机和从机模式、多主机功能、可编程建立和保持时间、时钟延展功能、DMA存取数据、支持SMBus 2.0协议等特点。

1.png
图1. I2C框图

I2C接口通信

主机通信流程

主机通信初始化

1. 主机时钟初始化

在启动外设(I2CEN)之前,必须先设置I2Cx_CLKCTRL寄存器的各个位用以配置I2C主时钟。

  • DIV[7:0]:I2C时钟分频;

  • SDAD[3:0]:数据保持时间(tHD;DAT)

  • SCLD[3:0]:数据建立时间(tSU;DAT)

  • SCLH[7:0]:SCL高电平时间

  • SCLL[7:0]:SCL低电平时间

该寄存器的配置可以使用Artery_I2C_Timing_Configuration时钟配置工具计算,见第三章节。低电平控制:当检测到SCL总线为低电平时,内部SCLL计数器开始计数,当计数值达到SCLL值时,释放SCL线,SCL线变为高电平。

高电平控制:当检测到SCL总线为高电平时,内部SCLH计数器开始计数,当计数值达到SCLH值时,拉低SCL线,SCL线变为低电平,当在高电平期间,如果被外部总线拉低,那么内部SCLH计数器停止计数,并开始低电平计数,这为时钟同步提供了条件。

2.png
图2. 主机时钟的产生

2. 主机通信初始化

在启动通讯前须先设定I2C_CTRL2寄存器中的几项参数:

1) 设置传输字节数

  • ≤255字节

    配置I2C_CTRL2的RLDEN=0,关闭重载模式

    配置I2C_CTRL2的CNT[7:0]=N

  • >255字节

    配置I2C_CTRL2的RLDEN=1,使能重载模式

    配置I2C_CTRL2的CNT[7:0]=255

    剩余传输字节数N=N-255

2) 设置传输结束模式
  • ASTOPEN=0:软件结束模式,当数据传输完成后,I2C_STS的TDC标志置1,软件设置GENSTOP=1或者GENSTART=1,发送STOP条件或者START条件。

  • ASTOPEN=1:自动结束模式,当数据传输完成后,自动发送STOP条件。

3) 设置从机地址
  • 设置寻址的从机地址值(I2C_CTRL2的SADDR)

  • 设置从机地址模式(I2C_CTRL2的ADDR10)

    ADDR10=0:7位地址模式

    ADDR10=1:10位地址模式

4) 设置传输方向(I2C_CTRL2的DIR)
  • DIR=0:主机接收数据

  • DIR=1:主机发送数据

5) 开始传输

设置I2C_CTRL2的GENSTART=1,主机开始在总线上发送START条件和从机地址。

3. 主机10 bits寻址的特殊时序初始化

在10位地址传输模式下,I2C_CTRL2的READH10用于产生特殊时序,当READH10=1时,支持如下传输序:主机先发送数据给从机,然后再从从机读取数据,传输时序图如下图所示:

3.png
图3. 10位地址的读访问READH10=1

主机在软件结束模式(ASTOPEN=0)下,发送数据到从机,当数据发送完成后设置READH10=1,然后再从从机接收数据。

4.png
图4. 10位地址的读访问READH10=0

主机通信初始化软件接口

主机通信初始化所用到的软件接口通过独立的函数接口实现,如下:

5.png

i2c_init函数三个参数分别为:所使用的I2C、数字滤波值和主机时钟配置值。

i2c_transmit_set 函数用于初始化通信参数,包括:所使用的I2C、从机地址、传输字节数、停止条件产生模式和起始条件产生模式。

i2c_addr10_mode_enable函数用于使能10位地址模式。

i2c_addr10_header_enable函数用于使能10位地址头读取时序,即主机发送完整的10位从机地址读序列或主机只发送10位地址的前7位。

主机发送流程

1) I2C_TXDT数据寄存器为空,I2C_STS的TDIS=1;

2) 向TXDT数据寄存器写入数据,数据开始发送;

3) 重复1、2步骤直到发送CNT[7:0]个数据;

4) 如果此时I2C_STS的TCRLD=1(重载模式),分为以下两种情况:
  • 剩余字节数N>255:向CNT写入255,N=N-255,TCRLD被自动清0,传输继续;
  • 剩余字节数N≤255:关闭重载模式(RLDEN=0),向CNT写入N,TCRLD被自动清0,传输继续。
5) 结束时序
  • 停止条件产生:
    软件结束模式(ASTOPEN=0):此时I2C_STS的TDC置1,设置GENSTOP=1产生STOP条件;
    自动结束模式(ASTOPEN=1):自动产生STOP条件。
  • 等待产生STOP条件,当STOP条件产生时,I2C_STS的STOPF置1,将I2C_CLR的STOPC写1,清除STOPF标志,传输结束。
6.png
图5. I2C主机发送流程图
7.png
图6. I2C主机发送时序图

主机发送流程软件接口

主机发送通过独立的函数接口实现,如下:

8.png

i2c_master_transmit函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、从机地址、发送数据指针、发送数据字节数和函数超时时间。

注:此函数为Artery所提供的标准主机发送函数。用户也可根据前述主机发送流程,自行编写主机发送函数。

主机接收流程

1) 当收到数据后,RDBF=1,读取RXDT数据寄存器,RDBF被自动清零;

2) 重复步骤2直到接收CNT[7:0]个数据;

3) 如果此时I2C_STS的TCRLD=1(重载模式),分为以下两种情况:
  • 剩余字节数N>255:向CNT写入255,N=N-255,TCRLD被自动清0,传输继续;
  • 剩余字节数N≤255:关闭重载模式(RLDEN=0),向CNT写入N,TCRLD被自动清0,传输继续。

4) 当在接收到最后一个字节时,主机会自动发送一个NACK。

5) 结束时序

  • 停止条件产生:
    软件结束模式(ASTOPEN=0):此时I2C_STS的TDC置1,设置GENSTOP=1产生STOP条件;
    自动结束模式(ASTOPEN=1):自动产生STOP条件。
  • 等待产生STOP条件,当STOP条件产生时,I2C_STS的STOPF置1,将I2C_CLR的STOPC写1,清除STOPF标志,传输结束。
9.png
图7. I2C主机接收流程图
10.png
图8. I2C主机接收时序图

主机接收流程软件接口

主机接收通过独立的函数接口实现,如下:

11.png

i2c_master_receive函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、从机地址、接收数据指针、接收数据字节数和函数超时时间。

注:此函数为Artery所提供的标准主机接收函数。用户也可根据前述主机接收流程,自行编写主机接收函数。

从机通信流程

从机通信初始化

1. 从机地址配置

每个I2C从设备可同时支持2个从设备地址,由OADDR1和OADDR2指定

I2C_OADDR1

  • 通过ADDR1EN使能

  • 通过ADDR1MODE配置为7位(默认)或10位地址

I2C_OADDR2
  • 通过ADDR2EN使能

  • 固定7位地址模式

  • 可通过ADDR2MASK[2:0]来在进行地址匹配比较时屏蔽掉0~7个LSB地址位

    ADDR2MASK=0表示7位地址中的每一位都要参与匹配比较

    ADDR2MASK=7表示任何非保留地址的7位地址都会被该从设备应答

2. 从机地址匹配

当I2C启用的地址选中匹配时,ADDRF中断状态标志会被置1,如果ADDRIEN位为1,就会产生一个中断。如果两个从地址都使能,在地址匹配产生ADDR中断时,可以查看状态寄存器中的ADDR[6:0]来得知是OADDR1还是OADDR2被寻址了。

3. 从机字节控制模式(通常SMBus模式下才使用)

从设备可以对每个收到的字节进行应答控制。

所需配置:SCTRL=1 & RLDEN=1 & STRETCH=0 & CNT≥1

从机字节控制流程:

1) 每收到一个字节TCRLD置位,时钟延展于第8和第9个脉冲之间

2) 软件读取RXDT中的值,并决定是否置位ACK

3) 软件重装载CNT=1来停止时钟延展

4) 应答或非应答信号在第9个脉冲时刻出现在总线上

注意:

置位SCTRL时,必须开启时钟延展,即STRETCH=0

CNT可以是大于1的值,来实现多个字节以自动ACK接收完毕后再启动应答控制,从设备发送时推荐关闭SCTRL,此时无需字节应答控制。

从机通信初始化软件接口

从机通信初始化所用到的软件接口通过独立的函数接口实现,如下:

12.png

i2c_own_address1_set函数用于配置OADDR1地址模式以及ADDR1地址值。

i2c_own_address2_set函数用于配置ADDR2地址值以及ADDR2屏蔽位。

i2c_own_address2_enable函数用于使能ADDR2地址。

i2c_slave_data_ctrl_enable函数用于使能从机字节控制模式。

i2c_clock_stretch_enable函数用于使能从机时钟延展功能。

i2c_reload_enable函数用于使能发送数据重载模式。

从机发送流程

1) 响应主机地址,匹配时回复ACK;

2) TXDT为空时,置位TDIS,从设备写入发送数据;

3) 每发送一个字节会收到ACK,且置位TDIS;

4) 如果收到NACK位:
  • 置位NACKF,产生中断;
  • 从设备自动释放SCL和SDA(以便主设备发送STOP或RESTART);
5) 如果收到STOP位:
  • 置位STOPF,产生中断;

当从机发送开启时钟延展(STRETCH=0)时,在等待ADDRF标志时和发送前一个数据的第9个时钟脉冲后,会把TXDT中的数据拷贝到移位寄存器中,如果此时TDIS还是置位,表示TXDT没有写进待发送数据,将发生时钟延展,如下流程图:

13.png

图9. I2C从机发送流程图

需要注意的是,在时钟延展关闭(STRETCH=1)的情况下,如果在将要传输数据的第一个Bit位开始发送之前,也就是SDA边沿产生之前,如果数据还未写入TXDT数据寄存器,那么会发生欠载错误,此时I2C_STS的OUF将会置1,并将0xFF发送到总线。

为了能及时的写入数据,可以在通信开始前,先将数据写入到DT寄存器:软件先将TDBE置1,目的是为了清空TXDT寄存器的数据,然后将第一个数据写入TXDT寄存器,此时TDBE清零。

14.png
图10. I2C从机发送时序图

从机发送流程软件接口

从机发送通过独立的函数接口实现,如下:

15.png

i2c_slave_transmit函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、发送数据指针、发送数据字节数和函数超时时间。

注:此函数为Artery所提供的标准从机发送函数。用户也可根据前述从机发送流程,自行编写从机发送函数。

从机接收流程

1) 当收到数据后,RDBF=1,读取RXDT数据寄存器,RDBF被自动清零;

2) 重复步骤2直到所有数据接收完成;

3) 等待收到STOP条件,当收到STOP条件时,I2C_STS的STOPF置1,将I2C_CLR的STOPC写1,清除STOPF标志,传输结束。

16.png
图11. I2C从机接收流程图
17.png

图12. I2C从机接收时序图

从机接收流程软件接口

从机接收通过独立的函数接口实现,如下:

18.png

i2c_slave_receive函数为i2c_application.c文件所提供的应用层接口函数,参数包括:I2C结构体指针、接收数据指针、接收数据字节数和函数超时时间。

注:此函数为Artery所提供的标准从机接收函数。用户也可根据前述从机接收流程,自行编写从机接收函数。

I2C配置工具

功能简介

I2C配置工具Artery_I2C_Timing_Configuration.exe可以实现对主机和从机的时钟、数字滤波、模拟滤波配置。

资源准备

1) 软件环境Artery_I2C_Timing_Configuration.exe
19.png
图13. Artery I2C Timing Configuration
使用步骤

1) 选择芯片型号

选择当前使用的芯片型号,例如可以选择AT32F425。

2) 选择设备模式
  • Master:主模式,I2C作为主机;

  • Slave:从模式,I2C作为从机。

3) 选择I2C速度模式
  • Standard-mode:标准模式,范围0~100kHz;

  • Fast-mode:快速模式,范围0~400kHz;

  • Fast-mode Plus:增强快速模式,范围0~1000kHz。

4) 设置I2C速度(单位kHz)

根据实际需求设置I2C通信速度,例如需要通信速度为10kHz,那么这里设置为10。

5) 设置I2C时钟源频率(单位kHz)

根据实际使用的I2C时钟源频率来配置,例如AT32425 I2C时钟源为PCLK1,当AT32425主频为144MHz,APB1为144MHz时,这里设置为144000。

6) 模拟滤波使能
  • On:打开;

  • Off:关闭。

模拟滤波使能后,将过滤50ns以下的脉冲。

7) 数字滤波(范围0~15)

数字滤波时间=数字滤波值xTI2C_CLK;

其中TI2C_CLK=1/I2C时钟源频率。

当值为0时,数字滤波关闭,当值>0时将过滤小于数字滤波时间的脉冲。

8) 上升时间(tr单位ns)

SCL和SDA总线的上升沿,如图18所示。I2C协议中规定了在标准模式(Standard-mode)、

快速模式(Fast-mode)、增强快速模式(Fast-mode Plus)下的范围,详情请参照表1。上升时间和上拉电阻的阻值关系很大,上拉电阻越小,上升时间越短,可以支持的通信速度就越快,但是功耗也越高。

表2中给出了一些常用上拉电阻阻值所对应的上升沿时间,实际可能会因为总线挂的设备数量、布线等差异而有所不同,仅供参考。

9) 下降时间(tf单位ns)

SCL和SDA总线的下降沿,如图18所示。I2C协议中规定了在标准模式(Standard-mode)、快速模式(Fast-mode)、增强快速模式(Fast-mode Plus)下的范围,详情请参照表1。

20.png
图14. 上升沿(tr)下降沿(tf)规范
21.png
表1. I2C时间规范
22.png

表2. 常用上拉电阻阻值的tr、tf参考值(VDD=3.3V)

注:该值是总线上连接两片AT32MCU,一个作为主机,一个作为从机测试出来的值,实际可能会因为总线挂的设备数量、布线等差异而有所不同。

10) 产生代码

点击产生代码,上诉配置的值,将会以代码的形式产生出来,如下图红框所示,只需要将右侧输出的代码替换到自己的程序即可。

23.png
图15. 代码产生

来源:AT32 MCU 雅特力科技

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 219

1、EEPROM 简介

EEPROM全称为EEPROM(Electrically Erasable Programmable Read Only Memory)是电可擦除可编程只读存储器。虽然名称为只读存储器,但是擦除和写入都是直接使用电路控制,不需要再使用外部设备来擦写,即设备在运行过程中即可随时擦除和写入。可以按字节为单位修改数据,无需整个芯片擦除,且掉电后数据不丢失,一般用来存储一些配置信息,以便系统重新上电的时候加载。

2、EPROM 操作说明

2.1  通讯方式

主要通讯方式有 I2C 和 SPI 协议,本文将介绍如何通过 I2C 来驱动EEPROM。

2.2  设备地址

如果仅接入一个AT24C02,可以将设备的A0、A1、A2引脚全部接入低电平,那么此时该设备的地址为0xA0。

1.png

2.3  I2C配置流程

开时钟,外设清零,参数配置,初始化,引脚配置,使能

2.png

2.4  写入数据

  • 写入一个字节:

MCU先向AT24C02写入一个地址,然后再写入数据。

  • 写入一页:

MCU先向AT24C02写入一个地址,然后再依次写入数据,注意AT24C02一页有8个字节,每页开始地址均是8的整数倍,一次页写入操作地址不能超过当前页的尾地址。

  • 连续写入:

AT24C02本身没有提供连续写入的操作,因此必须先将数据按页地址分为若干页,然后再依次调用页写入操作进行写入。

3.png

2.5  读出数据

  • 读取当前字节:

MCU直接发起读操作,设备返回当前字节,当前字节自动加1,该操作较少使用。

  • 读取指定地址一个字节:

MCU先向AT24C02写入一个地址,然后再发起一个读操作,AT24C02返回该地址存储的字节。

  • 连续读取:

MCU发起读当前字节,或者读指定地址字节,设备返回数据,MCU发送ACK,设备继续返回后续地址数据,直到MCU发送NACK,设备不再返回数据。

4.png

2.6  样例测试

初始化两个数组,配置I2C,调用写入和读取的函数操作EEPROM,通过串口打印观察结果。

5.png

运行实例

6.png

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 27

一、概述

CW32L083 内部集成 2 个 I2C 控制器,能按照设定的传输速率(标准,快速,高速)将需要发送的数据按照 I2C 规范串行发送到 I2C 总线上,或从总线上接收数据,并对通信过程中的状态进行检测,另外还支持多主机通信中的总线冲突和仲裁处理。

二、主要功能

• 支持主机发送 / 接收,从机发送 / 接收四种工作模式 

• 支持时钟延展 ( 时钟同步 ) 和多主机通信冲突仲裁

• 支持标准 (100Kbps)/ 快速 (400Kbps)/ 高速 (1Mbps) 三种工作速率 

• 支持 7bit 寻址功能 

• 支持 3个从机地址 

• 支持广播地址 

• 支持输入信号噪声过滤功能 

• 支持中断状态查询功能

1.协议介绍

I2C 总线使用两根信号线(数据线 SDA 和时钟线 SCL)在设备间传输数据。SCL 为单向时钟线,固定由主机驱动。SDA 为双向数据线,在数据传输过程中由收发两端分时驱动。I2C 总线上可以连接多个设备,所有设备在没有进行数据传输时都处于空闲状态(未寻址从机接收模式),任一设备都可以作为主机发送 START 起始信号来开始数据传输,在 STOP 停止信号出现在总线上之前,总线一直处于 被占用状态。I2C 通信采用主从结构,并由主机发起和结束通信。主机通过发送 START 起始信号来发起通信,之后发送 SLA+W/R 共 8bit 数据(其中,SLA 为 7bit 从机地址,W/R 为读写位),并在第 9个SCL 时钟释放 SDA 总线, 对应的从机在第 9个SCL 时钟占用 SDA 总线并输出 ACK 应答信号,完成从机寻址。此后根据主机发送的第 1 字 节的 W/R 位来决定数据通信的发端和收端,发端每发送 1个字节数据,收端必须回应 1个ACK 应答信号。数据传输完成后,主机发送 STOP 信号结束本次通信。

2.功能框图

I2C 模块主要包括时钟发生器、输入滤波器、地址比较器、协议控制逻辑、仲裁和同步逻辑、以及相关寄存器等。

1.png

CW32L083 支持用户灵活选择 GPIO 作为 I2C 通信引脚,如下表所示:

2.png

3.I2C中断

I2C 控制寄存器 I2Cx_CR 的 SI 位域为中断标志位。当 I2C 状态寄存器 I2Cx_STAT 的 STAT 位域值发生改变(变成 0xF8 除外)时,I2Cx_CR.SI 标志位就会被置位,同时产生中断请求。在用户 I2C 中断服务程序中,应查询 I2C 状态寄存器 I2Cx_STAT 的 STAT 位域值获取 I2C 总线的状态,以确定中断产生原因。设置 I2Cx_CR.SI 为 0 清除该标志位。

4.工作模式

I2C 控制器支持 4 种工作模式:主机发送模式、主机接收模式、从机发送模式、从机接收模式。另外还支持广播 接收模式,其工作方式和从机接收模式类似。

三、EEPROM(CW24C02AD)

1.功能简介

CW24C02是一个2Kbit的串行EEPROM存储芯片,可存储256个字节数据。芯片内部分为32页,每页8字节。工作电压范围为1.7V到5.5V,I2C接口时钟频率为 1MHz (5V,3V),400 KHz (1.7V)。器件地址为1010 A2 A1 A0,对于我们单板A2 A1 A0引脚全部接GND,故器件地址为1010000,即0x50。器件内部存储空间地址长度8 bit。

2.读写时序

字节写操作时序:起始信号+器件地址(7bit)+读写指示(1bit)+存储空间地址(8bit)+写入数据(8bit)+停止信号,即可完成指定字节写入操作。

3.png

页写操作时序:起始信号+器件地址(7bit)+读写指示(1bit)+存储空间地址(8bit)+写入数据(8bit*8)+停止信号,即可完成指定地址(必须是页起始地址)的页写入操作。

4.png

随机读操作时序:起始信号+器件地址(7bit)+读写指示(1bit)+存储空间地址(8bit)+重复起始信号+器件地址(7bit)+读写指示(1bit),之后器件会返回1字节数据,主机收到后发送停止信号,即可完成指定字节读取操作。

5.png

顺序读操作时序:和随机读时序类似,只是在主机接收到1字节数据后,不发送停止信号,而是继续发送时钟进行下一个字节数据的接收,直到所有所需读取的数据全部读取,之后再发送停止信号。

四、硬件连接

如下图所示,MCU和EEPROM通过I2C总线互连。

6.png

五、实例演示:MCU采用页写和顺序读操作时序完成EERPOM的访问。

1.I2C读写EEPROM芯片中断函数(I2C分为I2C1和I2C2)

void I2c1EepromReadWriteInterruptFunction(void)
{
    u8State = I2C_GetState(CW_I2C1);// I2C:获取状态寄存器函数
    switch(u8State)
    {
        case 0x08:     //发送完START信号
        I2C_GenerateSTART(CW_I2C1, DISABLE);// 发送START信号
        I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS,0X00);// 做主时发送从机地址字节
        break;
        case 0x10:     //发送完重复起始信号
        I2C_GenerateSTART(CW_I2C1, DISABLE);
        if(0 == SendFlg)
        {
            I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS,0X00);    //写命令
        }
        else
        {
            I2C_Send7bitAddress(CW_I2C1, I2C_SLAVEADDRESS,0X01); //读命令,eeprom 随机读
        }
        break;
        case 0x18:    //发送完SLA+W/R字节
        I2C_GenerateSTART(CW_I2C1, DISABLE);
        I2C_SendData(CW_I2C1, u8Addr);   //发送访问EEPROM的目标地址字节
        break;
        case 0x20:    //发送完SLA+W后从机返回NACK
        case 0x38:    //主机在发送 SLA+W 阶段或者发送数据阶段丢失仲裁  或者  主机在发送 SLA+R 阶段或者回应 NACK 阶段丢失仲裁
        case 0x30:    //发送完一个数据字节后从机返回NACK
        case 0x48:    //发送完SLA+R后从机返回NACK
        I2C_GenerateSTOP(CW_I2C1, ENABLE);
        I2C_GenerateSTART(CW_I2C1, ENABLE);
        break;
        case 0x58:    //接收到一个数据字节,且NACK已回复
        u8Recdata[u8RecvLen++] = I2C_ReceiveData(CW_I2C1);//所有数据读取完成,NACK已发送
        receivedflag =1;
        I2C_GenerateSTOP(CW_I2C1, ENABLE);//发送停止条件
        break;
        case 0x28:     //发送完1字节数据:发送EEPROM中memory地址也会产生,发送后面的数据也会产生
        if(0 == SendFlg)
        {
            if(u8SendLen <WRITELEN)
            {
                 I2C_SendData(CW_I2C1,u8Senddata[u8SendLen++]);
            }
            else
            {
                u8SendLen = 0;
                Comm_flg = 1;
                SendFlg = 1;
                I2C_GenerateSTOP(CW_I2C1, ENABLE);//发送完数据,发送停止信号
            }
        }
        else//SendFlg=1为读,SendFlg=0为写。读数据发送完地址字节后,重复起始条件
        {
            CW_I2C1->CR_f.STA = 1;  //set start       //发送重复START信号,START生成函数改写后,会导致0X10状态被略过,故此处不调用函数
            I2C_GenerateSTOP(CW_I2C1, DISABLE);
        }
        break; 
        case 0x40:     //发送完SLA+R信号,开始接收数据
        u8RecvLen = 0;
        if(READLEN>1)
        {
            I2C_AcknowledgeConfig(CW_I2C1,ENABLE);//读取数据超过1个字节才发送ACK
        }
        break;
        case 0x50:     //接收完一字节数据,在接收最后1字节数据之前设置AA=0;
        u8Recdata[u8RecvLen++] = I2C_ReceiveData(CW_I2C1);
        if(u8RecvLen==READLEN-1)
        {
            I2C_AcknowledgeConfig(CW_I2C1,DISABLE);;
        }
        break;
    }
    I2C_ClearIrq(CW_I2C1);
}

2.设置系统时钟

void RCC_Configuration(void)
{
    CW_SYSCTRL->APBEN1_f.I2C1 = 1U;   
}

3.设置GPIO口

void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    CW_SYSCTRL->AHBEN_f.GPIOA  = 1;
    CW_SYSCTRL->AHBEN_f.GPIOB  = 1;
    CW_SYSCTRL->AHBEN_f.GPIOC  = 1;
    CW_SYSCTRL->AHBEN_f.GPIOD  = 1;
    CW_SYSCTRL->AHBEN_f.GPIOE  = 1;
    CW_SYSCTRL->AHBEN_f.GPIOF  = 1;
    
    PB10_AFx_I2C1SCL();
    PB11_AFx_I2C1SDA();
    GPIO_InitStructure.Pins = I2C1_SCL_GPIO_PIN | I2C1_SDA_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_Init(I2C1_SCL_GPIO_PORT, &GPIO_InitStructure);
}

4.配置嵌套矢量中断控制器

void NVIC_Configuration(void)
{
     __disable_irq();
    NVIC_EnableIRQ(I2C1_IRQn);
    __enable_irq();
}
void I2C1_IRQHandler(void)
{
    I2c1EepromReadWriteInterruptFunction();
}

5.定义常量

#define  I2C1_SCL_GPIO_PORT       CW_GPIOB
#define  I2C1_SCL_GPIO_PIN        GPIO_PIN_10   
#define  I2C1_SDA_GPIO_PORT       CW_GPIOB
#define  I2C1_SDA_GPIO_PIN        GPIO_PIN_11    
//EEPROM内部地址
uint8_t u8Addr = 0x00;        //地址字节
#define WRITELEN   8          //写数据长度
#define READLEN   8           //读数据长度
#define WriteReadCycle  35    //写读次数,每次写入数据为n+i(n为次数,i=0~7)
uint8_t u8Senddata[8] = {0x66,0x02,0x03,0x04,0x05,0x60,0x70,0x20};
uint8_t u8Senddata2[8] = {0x55,0xAA,0xAA,0x55,0x55,0xAA,0x55,0xAA};
uint8_t u8Recdata[16]= {0x00};
uint8_t u8SendLen=0;
uint8_t u8RecvLen=0;
uint8_t SendFlg = 0,Comm_flg = 0;
uint8_t u8recvflg=0;
uint8_t u8State = 0;
uint8_t receivedflag = 0;    //读取完成标志

6.主程序:利用I2C接口,采用中断方式读写EEPROM芯片(CW24C02)。

int32_t main(void)
{
    I2C_InitTypeDef I2C_InitStruct = {0};
    uint16_t tempcnt = 0 ;
    RCC_Configuration();//时钟初始化
    GPIO_Configuration();//IO口初始化
    //I2C初始化
    I2C_InitStruct.I2C_Baud = 0x01;//500K=(8000000/(8*(1+1)) ,波特率计数器配置
    I2C_InitStruct.I2C_BaudEn = ENABLE;// 波特率计数器使能
    I2C_InitStruct.I2C_FLT = DISABLE; //<FLT配置
    I2C_InitStruct.I2C_AA =  DISABLE; //<ACK配置
    I2C1_DeInit();
    I2C_Master_Init(CW_I2C1,&I2C_InitStruct);//初始化模块
    NVIC_Configuration();//中断设置
    //I2C模块开始工作
    I2C_Cmd(CW_I2C1,ENABLE);  //模块使能
    tempcnt =0;
    for(uint8_t i=0; i<8; i++)
    {
        u8Senddata[i] = i;
    }
    while(1)
    {
        I2C_GenerateSTART(CW_I2C1, ENABLE); //开始信号
        while(1)
        {
            while(!Comm_flg) ; //等待数据发送完成
            FirmwareDelay(3000);
            
            Comm_flg = 0; //启动读数据过程
            receivedflag=0;
            I2C_GenerateSTART(CW_I2C1, ENABLE); //开始信号
            while(!receivedflag) ; //等待数据读取完成
            receivedflag = 0; //初始化下一次写数据
            SendFlg = 0;
            u8RecvLen = 0;
            tempcnt++;
            for(uint8_t i=0; i<8; i++)
            {
                u8Senddata[i] =tempcnt+i;
            }
            break;
        }

        if(tempcnt >=WriteReadCycle) //测试次数完成,退出
        {
            break;
        }
    }
    while(1);
}

7.程序流程

程序完成I2C主设备配置后,先将u8Senddata数组中的内容写入到EEPROM的第1页(CW24C02每页8字节):发送START信号后,I2C模块会产生状态改变中断,在中断服务程序中根据不同状态值进行不同处理,直到完成CW24C02的页写模式所有数据字节以及STOP信号发送,发送完成后置写操作流程完成标志。主循环中判断到写操作流程完成后,启动从EERROM的第1页数据读取流程:发送启动信号后,I2C模块会产生状态改变中断,在中断服务程序中根据不同状态值进行不同处理,直到完成CW24C02的顺序读模式所有数据字节发送及读取,在发送完STOP信号后置读操作流程完成标志。主循环中判断读操作流程完成后,初始化u8Senddata数组内容,重复下一次测试过程。完成WriteReadCycle变量设置的测试次数后退出。

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 34

一般情况下, i2c 设备焊接没什么问题,按照设备手册一步步来,基本上就顺风顺水能够用起来。如果这么一个简单的东西,有时候想要的结果死活不出来,反复的检查问题的原因,查询解决办法,核查设备的数据手册,甚至发送和接收的每一条命令与数据都知道是什么意思,仍然无法解决问题,那该怎么办呢?

本文主要针对 i2c 设备,讲解如何解决 i2c 设备主机与从机直接无法正常数据交互的问题,侧重点是针对硬件设计不太合理、i2c 设备设计不标准导致总线故障的情况,并且通过分析现象,提出解决方案。对于在设备初始化中,没有设置相应的寄存器或者发送命令,而导致的无法获取想要的数据情况,不作详细介绍。

1、i2c 基本用法

i2c 总线是一种简单、双向二线制同步串行总线。所有主机在 SCL 线上产生它们自己的时钟来传输总线上的报文,SDA 线传输每个字节必须为 8 位,每次传输可以发送的字节数量不受限制,每个字节后必须跟一个响应位。在空闲状态时,SCL 与 SDA 均为高电平。

通常一些低功耗 i2c 设备,芯片引脚使用上拉输出即可满足与其正常数据交互,还有一些 i2c 设备,则需要在总线上外加一个上拉电阻,此时相应的 I/O 配置成开漏输出,其他的按照芯片手册进行标准配置。

2、硬件问题汇总

2.1 无法正常拉高拉低引脚

首先确定 SDA 与SCL 引脚能够被拉高、拉低,检测方式直接软件控制 I/O 口输出引脚低电平/高电平,测量引脚电压是否能够随着芯片引脚的设置输出相应的状态。

如果不能被拉低,检测虚焊、上拉电阻断开、i2c 设备是否正常、芯片引脚是否损坏等问题,确保能够正常被拉高或者拉低。

2.2、电气特性无法满足

如果正常拉高、拉低的情况下,依然无法正常读取数据。通常建议,根据负载电流更换小阻值的电阻。

如果需要详细知道原因,就具体查询 i2c 设备电气特性。大多数 i2c 设备电气特性,大致下图所示

“再谈I2C,硬件问题汇总及死锁解决办法"

通常这块内容在 i2c 设备电气特性这一块,主要讲解电平拉高拉低的最长时间、最短时间,以及处于高电平与电平的阈值与持续时间等等内容。

硬件设计,为了降低单片机的功耗与保护芯片引脚,在满足负载电流和负载电容相关要求的前提下,阻值设置通常比较大。如果同一个总线上挂载多个 i2c 设备, 即使在 I/O 口配置正确的前提下,也会导致驱动能力不足。

现象是拉高电压不足,在拉高、拉低过程中消耗时间过长。这两个问题通常还引起数据线与时钟线:拉高时,高电压持续时间过短;拉低时,低电压持续时间过短。用示波器抓取图形:从波形上看,显示是尖波、斜波、杂波等不符合 i2c 设备电气特性的波形;从数据上看,数据线高电平持续时间过小 ,上升沿时间过长 ,下降沿时间过长等等数据超出设备电气特性的有效值。典型杂波图,如下所示

“再谈I2C,硬件问题汇总及死锁解决办法"

如果出现此类异常,建议更换小一点的电阻,用来增强总线驱动能力,提高电平转换速度。应当注意的是每个 MCU 的耐受电流不一样,减小电阻应避免超过相应引脚承受电流的最大值。

3、SDA 死锁

如果i2c 设备的数据偶尔能够正确获取,但是仍然会在总线发送数据或者命令的时候,爆出总线读写错误,那么有可能遇到下面的死锁问题,死锁时候,就是数据线被拉低,主机无法拉高。死锁一般发生在从机上,且为数据线死锁。因为i2c总线是共享的,如果需要确定,是否是从机死锁,可以参照下面两幅图,串联电阻进行测试

“再谈I2C,硬件问题汇总及死锁解决办法"

如上图所示,如果从机死锁,即从机拉低电平,此时检测到的电压为1/3 Vcc。

“再谈I2C,硬件问题汇总及死锁解决办法"

如上图所示,如果主机死锁,即主机拉低电平,此时检测到的电压为 1/11 Vcc。依据这个原理,可以准确判定死锁的具体位置,多个传感器依据类似方式进行定位。

3.1 反复重启导致死锁

3.1.1 现象

如果设备需要反复重启,很有可能在从机设备返回数据的时候,SDA被锁住。具体原因是从机设备在回数据,还没有发送完成,主机时钟消失,从机等待时钟信号, MCU重启,如果从机设备的电源没有复位,从机继续等待 MCU 时钟信号,数据一直被钳住,总线无法完成数据交互。

3.1.2 解决方式

解决重启导致总线死锁,一种方式可以如同 rt-thread 驱动解决方式一样,在系统复位的时候,提供9个时钟信号,解初总线死锁;另一种是在按下复位键初始化的时候,给从机设备电源断电重启,这个需要引脚控制。

3.1.3 9 个时钟信号

i2c 设备进行读写操作的过程中,在从机钳住总线的期间,MCU 异常复位,会导致 SDA 死锁,异常产生出现在俩个阶段:从机响应阶段、从机发送数据阶段。下面将针对这两种异常,对时钟信号进行解释,并且总结其他原因,得出结论。

(a) 从机响应阶段

MCU 在开始信号后发送地址,得到从机设备响应,准备开始返回数据,在这个时候,从机将 SDA 信号拉为低电平,如果 MCU 异常复位,会导致总线上 SCL 停止发送时钟信号,从机等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 9 个时钟信号,使得从机完成响应,释放 SDA 。

(b) 从机发送数据阶段

如果从机响应完成了,开始给 MCU 返回数据。这个数据有八位,每一位都有可能为低,如果在数据低位,MCU 异常复位,停止发送时钟信号,从机就会等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 1-8 个时钟信号,使得从机完成数据响应,释放 SDA 。

(c)其他情况

在从机一个 8 位数据发送完成后,等待 MCU 响应, 即使属于 MCU 的,从机不再钳住 SDA,没有时钟,数据交互停止。

在主机发送数据阶段,总线所有权在主机,主机异常,数据交互停止,总线释放。所以,这些情况下,不存在 SDA 死锁的情况。

(d)结论

综上所述,解锁 SDA 从机最多需要 9 个时钟信号,也就是异常复位后,MCU 至少发送需要 9 个时钟信号,完成 i2c 总线的 SDA 解锁。所以,RT_Thread 为了避免此类问题的产生,在 i2c 驱动初始化,对总线进行判断,判断是否需要解锁,如果需要,就进行解锁,确保 i2c 设备不会因为这个问题导致数据交互失败。

3.2 多个 i2c 设备导致死锁

多 i2c 设备除了异常复位导致死锁,还会形成相互干扰的问题,一般情况下,不会把同种从机地址挂在同一条总线上,但除此之外,有些 i2c 设备设计不是按照标准的 i2c 总线协议设计,在 i2c 总线共享的前提条件下,有的设备只要总线上从机地址就会有响应。这样由于从机的错误响应,使得各个 i2c 总线异常,甚至钳住总线,导致 I2C 总线进人一种死锁状态。

解决方式,这样的不标准i2c设备,单独使用一个总线,避免干扰,或者单独一个独立引脚,控制电源。

来源:RT-Thread
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 160

在I2C中,通信是借助设备地址寻址实现的,大致可以分为两类:一对多、多对多通信。在多主机通信时,从机如果想接收多个主机的数据,就需要使用到从机多地址的功能。

本文是针对在MM32F013x上实现I2C多地址的功能应用。

配置方式

MM32F013x的多地址功能是通过配置I2C_SLAVMASK寄存器来实现的。通过I2C_SAR寄存器配置从机地址后,再配置I2C_SLAVMASK寄存器。MM32F013x是支持7位地址和10位地址格式的,所以需要按照自己的实际情况配置I2C_SLAVMASK寄存器的低九位,I2C_SLVRCVADDR寄存器会给出真实地址。

注意:在I2C中有些特殊地址是不会产生响应的。


寄存器描述


相关软件的实现

01、功能验证

制作一个主机设备发送16个字节的字符串,连接两块开发板的SDA/SCL,通过主机向从机发送数据,分别将目标地址设置为:0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE进行通信测试。

1.1 主机程序

void I2C_WRTest(void)
{
    Write(0x00, gTxData, 0x10);
    DELAY_Ms(100);
    Read(0x00, gRxData, 0x10);
    DELAY_Ms(100);
}

s32 main(void)
{
    DELAY_Init();
    I2C_WRInit();

    for(int i=0;i<8;i++)
    {
        I2C_Cmd(I2C1, DISABLE);
        I2C_SetDeviceAddr(I2C1, EEPROM_ADDR+2*i);
        I2C_Cmd(I2C1, ENABLE);
        I2C_WRTest();
    }
    While(1)
    {

    }
}

1.2 从机初始化部分

void I2C_NVIC_SlaveInit(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef  GPIO_InitStructure;

    NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_1);
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //Need extra plus pull
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;  //I2C1 remap IO port
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  // clock input
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    I2C_SlaveMode();
}

void I2C_NVIC_SlaveTest()
{
    u32 i;
    while( gTxFlag | gRxFlag);
    for(i = 0; i < 16; i++) 
    {
        printf("TX data%d is  : %x \r\n", i, gTxBuff[i]);
    }
    for(i = 0; i < 16; i++)
    {
        printf("RX data%d is  : %x \r\n", i, gRxBuff[i]);
    }
    gTxFlag = 1;
    gRxFlag = 1;
}

void I2C_SlaveMode()
{
    I2C_InitTypeDef I2C_InitStructure;
    I2C_StructInit(&I2C_InitStructure);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    I2C_DeInit(I2C1);
    I2C_InitStructure. Mode = (I2C_CR_MASTER >> 1); 
    I2C_InitStructure. OwnAddress = 0;
    I2C_InitStructure. Speed = I2C_CR_STD;
    I2C_InitStructure. ClockSpeed = 100000;
    I2C_Init(I2C1, &I2C_InitStructure);

    I2C_ITConfig( I2C1, I2C_IT_RD_REQ, ENABLE );//Read request
    I2C_ITConfig( I2C1, I2C_IT_RX_FULL, ENABLE );//Receive interrupt

    I2C_SendSlaveAddress(I2C1, 0xA8);

    I2C1->SLVMASK = 0x0F;
    I2C_Cmd(I2C1, ENABLE);
}

s32 main(void)
{
    CONSOLE_Init(115200);
    I2C_NVIC_SlaveInit();
    I2C_NVIC_SlaveTest();
    while(1) 
    {
    }
}

以上程序将设备配置为从机模式,使能读请求/接收缓冲非空中断,从机地址配置为0xA8,SLVMASK配置为0x0F,表示地址的低四位不进行比较,则从机设备可以从总线上接受地址为0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE的数据包。

1.3 从机多地址中断处理程序

void I2C1_IRQHandler(void)
{
    u16 stop_flag, start_flag;
    if(I2C_GetITStatus(I2C1, I2C_IT_RD_REQ)) 
    { 
        I2C_ClearITPendingBit(I2C1,I2C_IT_RD_REQ);   
        //The master has sent a read request from the slave
        I2C1->DR = (u8)gTxBuff[gTxCnt];
        I2C_TX_EmptyCheck(I2C1);
        while(!(I2C1->SR & 0x4));
        I2C_GenerateSTOP( I2C1, ENABLE );

        gTxCnt ++;
        if(gTxCnt == 16) 
        {
            gTxCnt = 0;
        }
        gTxFlag = 0;
    }
    // interrupt receive
    if(I2C_GetITStatus(I2C1, I2C_IT_RX_FULL)) 
    { 
        //Master sends slave receive
        gRxBuff[gRxCnt++] = I2C_ReceiveData(I2C1);
        while(!(I2C1->SR & 0x4));
        I2C_GenerateSTOP( I2C1, ENABLE );
        if(gRxCnt == 16)
        {
            gRxCnt = 0;
        }
        gRxFlag = 0;
    }
    stop_flag = I2C1->STOP;
    start_flag = I2C1->START;
    if((stop_flag  & start_flag) != ((u32)RESET)) //slave receive
    {
        I2C_ClearITPendingBit(I2C1,I2C_IT_STOP_DET);
        I2C_ClearITPendingBit(I2C1,I2C_IT_STOP_DET);   
    }
}

1.4 测试结果

通过UART1打印接收及发送的结果发现每次结果都如下图所示:


波形如下:









结合上述结果与调试过程,可知从机可以接收地址为:0xA0、0xA2、0xA4、0xA6、0xA8、0xAA、0xAC、0xAE的数据包。

转自:灵动微电子

围观 245

IICvs SPI

现今,在低端数字通信应用领域,我们随处可见IIC (Inter-Integrated Circuit) 和 SPI (Serial Peripheral Interface)的身影。原因是这两种通信协议非常适合近距离低速芯片间通信。Philips(for IIC)和Motorola(for SPI) 出于不同背景和市场需求制定了这两种标准通信协议。

IIC 开发于1982年,当时是为了给电视机内的CPU和外围芯片提供更简易的互联方式。电视机是最早的嵌入式系统之一,而最初的嵌入系统是使用内存映射(memory-mapped I/O)的方式来互联微控制器和外围设备的。要实现内存映射,设备必须并联入微控制器的数据线和地址线,这种方式在连接多个外设时需大量线路和额外地址解码芯片,很不方便并且成本高。

为了节省微控制器的引脚和和额外的逻辑芯片,使印刷电路板更简单,成本更低,位于荷兰的Philips实验室开发了 ‘Inter-Integrated Circuit’,IIC 或 IIC ,一种只使用二根线接连所有外围芯片的总线协议。最初的标准定义总线速度为100kbps。经历几次修订,主要是1995年的400kbps,1998的3.4Mbps。

有迹象表明,SPI总线首次推出是在1979年,Motorola公司将SPI总线集成在他们第一支改自68000微处理器的微控制器芯片上。SPI总线是微控制器四线的外部总线(相对于内部总线)。与IIC不同,SPI没有明文标准,只是一种事实标准,对通信操作的实现只作一般的抽象描述,芯片厂商与驱动开发者通过data sheets和application notes沟通实现上的细节。

SPI

对于有经验的数字电子工程师来说,用SPI互联两支数字设备是相当直观的。SPI是种四根信号线协议(如图):

关于I2C和SPI总线协议

§ SCLK: Serial Clock (output from master);

§ MOSI; SIMO: Master Output, Slave Input(output from master);

§ MISO; SOMI: Master Input, Slave Output(output from slave);

§ SS: Slave Select (active low, outputfrom master).

SPI是[单主设备( single-master )]通信协议,这意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写[从设备]时,它首先拉低[从设备]对应的SS线(SS是低电平有效),接着开始发送工作脉冲到时钟线上,在相应的脉冲时间上,[主设备]把信号发到MOSI实现“写”,同时可对MISO采样而实现“读”,如下图:

关于I2C和SPI总线协议

SPI有四种操作模式——模式0、模式1、模式2和模式3,它们的区别是定义了在时钟脉冲的哪条边沿转换(toggles)输出信号,哪条边沿采样输入信号,还有时钟脉冲的稳定电平值(就是时钟信号无效时是高还是低)。每种模式由一对参数刻画,它们称为时钟极(clock polarity)CPOL与时钟期(clock phase)CPHA。

关于I2C和SPI总线协议

[主从设备]必须使用相同的工作参数——SCLK、CPOL 和 CPHA,才能正常工作。如果有多个[从设备],并且它们使用了不同的工作参数,那么[主设备]必须在读写不同[从设备]间重新配置这些参数。以上SPI总线协议的主要内容。SPI不规定最大传输速率,没有地址方案;SPI也没规定通信应答机制,没有规定流控制规则。事实上,SPI[主设备]甚至并不知道指定的[从设备]是否存在。这些通信控制都得通过SPI协议以外自行实现。例如,要用SPI连接一支[命令-响应控制型]解码芯片,则必须在SPI的基础上实现更高级的通信协议。SPI并不关心物理接口的电气特性,例如信号的标准电压。在最初,大多数SPI应用都是使用间断性时钟脉冲和以字节为单位传输数据的,但现在有很多变种实现了连续性时间脉冲和任意长度的数据帧。

IIC

与SPI的单主设备不同,IIC 是多主设备的总线,IIC没有物理的芯片选择信号线,没有仲裁逻辑电路,只使用两条信号线—— ‘serial data’ (SDA) 和 ‘serial clock’ (SCL)。IIC协议规定:

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

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

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

IIC 数据传输速率有标准模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些变种实现了低速模式(10 kbps)和快速+模式(1 Mbps)。

物理实现上,IIC 总线由两根信号线和一根地线组成。两根信号线都是双向传输的,参考下图。IIC协议标准规定发起通信的设备称为主设备,主设备发起一次通信后,其它设备均为从设备。

关于I2C和SPI总线协议

IIC 通信过程大概如下。首先,主设备发一个START信号,这个信号就像对所有其它设备喊:请大家注意!然后其它设备开始监听总线以准备接收数据。接着,主设备发送一个7位设备地址加一位的读写操作的数据帧。当所设备接收数据后,比对地址自己是否目标设备。如果比对不符,设备进入等待状态,等待STOP信号的来临;如果比对相符,设备会发送一个应答信号——ACKNOWLEDGE作回应。

当主设备收到应答后便开始传送或接收数据。数据帧大小为8位,尾随一位的应答信号。主设备发送数据,从设备应答;相反主设备接数据,主设备应答。当数据传送完毕,主设备发送一个STOP信号,向其它设备宣告释放总线,其它设备回到初始状态。

关于I2C和SPI总线协议

基于IIC总线的物理结构,总线上的START和STOP信号必定是唯一的。另外,IIC总线标准规定SDA线的数据转换必须在SCL线的低电平期,在SCL线的高电平期,SDA线的上数据是稳定的。

关于I2C和SPI总线协议

在物理实现上,SCL线和SDA线都是漏极开路(open-drain),通过上拉电阻外加一个电压源。当把线路接地时,线路为逻辑0,当释放线路,线路空闲时,线路为逻辑1。基于这些特性,IIC设备对总线的操作仅有“把线路接地”——输出逻辑0。

IIC总线设计只使用了两条线,但相当优雅地实现任意数目设备间无缝通信,堪称完美。我们设想一下,如果有两支设备同时向SCL线和SDA线发送信息会出现什么情况。

基于IIC总线的设计,线路上不可能出现电平冲突现象。如果一支设备发送逻辑0,其它发送逻辑1,那么线路看到的只有逻辑0。也就是说,如果出现电平冲突,发送逻辑0的始终是“赢家”。

总线的物理结构亦允许主设备在往总线写数据的同时读取数据。这样,任何设备都可以检测冲突的发生。当两支主设备竞争总线的时候,“赢家”并不知道竞争的发生,只有“输家”发现了冲突——当它写一个逻辑1,却读到0时——而退出竞争。

10位设备地址

任何IIC设备都有一个7位地址,理论上,现实中只能有127种不同的IIC设备。实际上,已有IIC的设备种类远远多于这个限制,在一条总线上出现相同的地址的IIC设备的概率相当高。为了突破这个限制,很多设备使用了双重地址——7位地址加引脚地址(external configuration pins)。IIC 标准也预知了这种限制,提出10位的地址方案。

10位的地址方案对 IIC协议的影响有两点:

§ 第一,地址帧为两个字节长,原来的是一个字节;

§ 第二,第一个字节前五位最高有效位用作10位地址标识,约定是“11110”。

关于I2C和SPI总线协议

除了10位地址标识,标准还预留了一些地址码用作其它用途,如下表:

关于I2C和SPI总线协议

时钟拉伸

在 IIC 通信中,主设备决定了时钟速度。因为时钟脉冲信号是由主设备显式发出的。但是,当从设备没办法跟上主设备的速度时,从设备需要一种机制来请求主设备慢一点。这种机制称为时钟拉伸,而基于I²C结构的特殊性,这种机制得到实现。当从设备需要降低传输的速度的时候,它可以按下时钟线,逼迫主设备进入等待状态,直到从设备释放时钟线,通信才继续。

高速模式

原理上讲,使用上拉电阻来设置逻辑1会限制总线的最大传输速度。而速度是限制总线应用的因素之一。这也说明为什么要引入高速模式(3.4 Mbps)。在发起一次高速模式传输前,主设备必须先在低速的模式下(例如快速模式)发出特定的“High Speed Master”信号。为缩短信号的周期和提高总线速度,高速模式必须使用额外的I/O缓冲区。另外,总线仲裁在高速模式下可屏蔽掉。更多的信息请参与总线标准文档。

IIC vs SPI: 哪位是赢家?

我们来对比一下IIC 和 SPI的一些关键点:

第一,总线拓扑结构/信号路由/硬件资源耗费

IIC 只需两根信号线,而标准SPI至少四根信号,如果有多个从设备,信号需要更多。一些SPI变种虽然只使用三根线——SCLK, SS和双向的MISO/MOSI,但SS线还是要和从设备一对一根。另外,如果SPI要实现多主设备结构,总线系统需额外的逻辑和线路。用IIC 构建系统总线唯一的问题是有限的7位地址空间,但这个问题新标准已经解决——使用10位地址。从第一点上看,IIC是明显的大赢家。

第二,数据吞吐/传输速度

如果应用中必须使用高速数据传输,那么SPI是必然的选择。因为SPI是全双工,IIC 的不是。SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps。IIC 最高的速度也就快速+模式(1 Mbps)和高速模式(3.4 Mbps),后面的模式还需要额外的I/O缓冲区,还并不是总是容易实现的。

第三,优雅性

IIC 常被称更优雅于SPI。公正的说,我们更倾向于认为两者同等优雅和健壮。IIC的优雅在于它的特色——用很轻盈的架构实现了多主设备仲裁和设备路由。但是对使用的工程师来讲,理解总线结构更费劲,而且总线的性能不高。

SPI的优点在于它的结构相当的直观简单,容易实现,并且有很好扩展性。SPI的简单性不足称其优雅,因为要用SPI搭建一个有用的通信平台,还需要在SPI之上构建特定的通信协议软件。也就是说要想获得SPI特有而IIC没有的特性——高速性能,工程师们需要付出更多的劳动。另外,这种自定的工作是完全自由的,这也说明为什么SPI没有官方标准。IIC和SPI都对低速设备通信提供了很好的支持,不过,SPI适合数据流应用,而IIC更适合“字节设备”的多主设备应用。

小结

在数字通信协议簇中,IIC和SPI常称为“小”协议,相对Ethernet, USB, SATA, PCI-Express等传输速度达数百上千兆字节每秒的总线。但是,我们不能忘记的是各种总线的用途是什么。“大”协议是用于系统外的整个系统之间通信的,“小”协议是用于系统内各芯片间的通信,没有迹象表明“大”协议有必要取代“小”协议。IIC和SPI的存在和流行体现了“够用就好”的哲学。回应文首,IIC和SPI如此的流行,它是任何一位嵌入式工程师必备的工具。

版权声明:本文为CSDN博主「求佛_ce123」的原创文章,转载此文目的在于传递更多信息,版权归原作者所有。遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/ce123_zhouwei/article/details/6878547

围观 100

一般情况下, i2c 设备焊接没什么问题,按照设备手册一步步来,基本上就顺风顺水能够用起来。如果这么一个简单的东西,有时候想要的结果死活不出来,反复的检查问题的原因,查询解决办法,核查设备的数据手册,甚至发送和接收的每一条命令与数据都知道是什么意思,仍然无法解决问题,那该怎么办呢?

本文主要针对 i2c 设备,讲解如何解决 i2c 设备主机与从机直接无法正常数据交互的问题,侧重点是针对硬件设计不太合理、i2c 设备设计不标准导致总线故障的情况,并且通过分析现象,提出解决方案。对于在设备初始化中,没有设置相应的寄存器或者发送命令,而导致的无法获取想要的数据情况,不作详细介绍。

1 i2c 基本用法

i2c 总线是一种简单、双向二线制同步串行总线。所有主机在 SCL 线上产生它们自己的时钟来传输总线上的报文,SDA 线传输每个字节必须为 8 位,每次传输可以发送的字节数量不受限制,每个字节后必须跟一个响应位。在空闲状态时,SCL 与 SDA 均为高电平。

通常一些低功耗 i2c 设备,芯片引脚使用上拉输出即可满足与其正常数据交互,还有一些 i2c 设备,则需要在总线上外加一个上拉电阻,此时相应的 I/O 配置成开漏输出,其他的按照芯片手册进行标准配置。

2 硬件问题汇总

2.1 无法正常拉高拉低引脚

首先确定 SDA 与SCL 引脚能够被拉高、拉低,检测方式直接软件控制 I/O 口输出引脚低电平/高电平,测量引脚电压是否能够随着芯片引脚的设置输出相应的状态。

如果不能被拉低,检测虚焊、上拉电阻断开、i2c 设备是否正常、芯片引脚是否损坏等问题,确保能够正常被拉高或者拉低。

2.2 电气特性无法满足

如果正常拉高、拉低的情况下,依然无法正常读取数据。通常建议,根据负载电流更换小阻值的电阻。

如果需要详细知道原因,就具体查询 i2c 设备电气特性。大多数 i2c 设备电气特性,大致下图所示

通常这块内容在 i2c 设备电气特性这一块,主要讲解电平拉高拉低的最长时间、最短时间,以及处于高电平与电平的阈值与持续时间等等内容。

硬件设计,为了降低单片机的功耗与保护芯片引脚,在满足负载电流和负载电容相关要求的前提下,阻值设置通常比较大。如果同一个总线上挂载多个 i2c 设备, 即使在 I/O 口配置正确的前提下,也会导致驱动能力不足。

现象是拉高电压不足,在拉高、拉低过程中消耗时间过长。这两个问题通常还引起数据线与时钟线:拉高时,高电压持续时间过短;拉低时,低电压持续时间过短。用示波器抓取图形:从波形上看,显示是尖波、斜波、杂波等不符合 i2c 设备电气特性的波形;从数据上看,数据线高电平持续时间过小 ,上升沿时间过长 ,下降沿时间过长等等数据超出设备电气特性的有效值。典型杂波图,如下所示

如果出现此类异常,建议更换小一点的电阻,用来增强总线驱动能力,提高电平转换速度。应当注意的是每个 MCU 的耐受电流不一样,减小电阻应避免超过相应引脚承受电流的最大值。

3 SDA 死锁

如果i2c 设备的数据偶尔能够正确获取,但是仍然会在总线发送数据或者命令的时候,爆出总线读写错误,那么有可能遇到下面的死锁问题,死锁时候,就是数据线被拉低,主机无法拉高。死锁一般发生在从机上,且为数据线死锁。因为i2c总线是共享的,如果需要确定,是否是从机死锁,可以参照下面两幅图,串联电阻进行测试

如上图所示,如果从机死锁,即从机拉低电平,此时检测到的电压为1/3 Vcc。

如上图所示,如果主机死锁,即主机拉低电平,此时检测到的电压为 1/11 Vcc。依据这个原理,可以准确判定死锁的具体位置,多个传感器依据类似方式进行定位。

3.1 反复重启导致死锁

3.1.1 现象

如果设备需要反复重启,很有可能在从机设备返回数据的时候,SDA被锁住。具体原因是从机设备在回数据,还没有发送完成,主机时钟消失,从机等待时钟信号, MCU重启,如果从机设备的电源没有复位,从机继续等待 MCU 时钟信号,数据一直被钳住,总线无法完成数据交互。

3.1.2 解决方式

解决重启导致总线死锁,一种方式可以如同 rt-thread 驱动解决方式一样,在系统复位的时候,提供9个时钟信号,解初总线死锁;另一种是在按下复位键初始化的时候,给从机设备电源断电重启,这个需要引脚控制。

3.1.3 9 个时钟信号

i2c 设备进行读写操作的过程中,在从机钳住总线的期间,MCU 异常复位,会导致 SDA 死锁,异常产生出现在俩个阶段:从机响应阶段、从机发送数据阶段。下面将针对这两种异常,对时钟信号进行解释,并且总结其他原因,得出结论。

(a) 从机响应阶段

MCU 在开始信号后发送地址,得到从机设备响应,准备开始返回数据,在这个时候,从机将 SDA 信号拉为低电平,如果 MCU 异常复位,会导致总线上 SCL 停止发送时钟信号,从机等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 9 个时钟信号,使得从机完成响应,释放 SDA 。

(b) 从机发送数据阶段

如果从机响应完成了,开始给 MCU 返回数据。这个数据有八位,每一位都有可能为低,如果在数据低位,MCU 异常复位,停止发送时钟信号,从机就会等待 MCU 的时钟信号,产生钳住并且拉低 SDA 的现象。如果想要解锁 SDA,从机需要 1-8 个时钟信号,使得从机完成数据响应,释放 SDA 。

(c)其他情况

在从机一个 8 位数据发送完成后,等待 MCU 响应, 即使属于 MCU 的,从机不再钳住 SDA,没有时钟,数据交互停止。

在主机发送数据阶段,总线所有权在主机,主机异常,数据交互停止,总线释放。所以,这些情况下,不存在 SDA 死锁的情况。

(d)结论

综上所述,解锁 SDA 从机最多需要 9 个时钟信号,也就是异常复位后,MCU 至少发送需要 9 个时钟信号,完成 i2c 总线的 SDA 解锁。所以,RT_Thread 为了避免此类问题的产生,在 i2c 驱动初始化,对总线进行判断,判断是否需要解锁,如果需要,就进行解锁,确保 i2c 设备不会因为这个问题导致数据交互失败。

3.2 多个 i2c 设备导致死锁

多 i2c 设备除了异常复位导致死锁,还会形成相互干扰的问题,一般情况下,不会把同种从机地址挂在同一条总线上,但除此之外,有些 i2c 设备设计不是按照标准的 i2c 总线协议设计,在 i2c 总线共享的前提条件下,有的设备只要总线上从机地址就会有响应。这样由于从机的错误响应,使得各个 i2c 总线异常,甚至钳住总线,导致 I2C 总线进人一种死锁状态。

解决方式,这样的不标准i2c设备,单独使用一个总线,避免干扰,或者单独一个独立引脚,控制电源。

来源:RT-Thread

围观 289

页面

订阅 RSS - I2C