瑞萨单片机william hill官网
直播中

ouxiaolong

11年用户 470经验值
擅长:嵌入式技术 光电显示
私信 关注
[资料]

【RA4M2设计挑战赛】智能家居助手之I2C

1 I2C工作原理

1.1 I2C串行总线概述

I2C总线是PHLIPS公司推出的一种双线式半双工串行总线,是具备多主机系统所需的总线裁决和高低速器件同步功能的高性能串行总线。用于连接微控器及外围设备。I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。

  • 物理层

1)它只使用两条总线线路 :一条双向串行数据线(SDA),一条串行时钟线(SCL)。见图 1。

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

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

4)具有三种传输模式 :标准模式的传输速率为 100 Kbit/s ,快速模式为 400 Kbit/s ,高速模式下可达 3.4 Mbit/s,但目前大多12C设备尚不支持高速模式。

5)片上的滤波器可以滤去总线数据线上的毛刺波以保证数据完整。

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

167725006538396jit455sl

I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系。

每个接到I2C总线上的器件都有唯一的地址。主机与其它器件间的数据传送可以是由主机发送数据到其它器件,这时主机即为发送器。由总线上接收数据的器件则为接收器。

在多主机系统中,可能同时有几个主机企图启动总线传送数据。为了避免混乱, I2C总线要通过总线仲裁,以决定由哪一台主机控制总线。

  • 协议层

I2C的协议包括起始和停止条件、数据有效性、响应、仲裁、时钟同步和地址广播等环节,由于我们使用的是 RA4M2集成的硬件I2C接口,并不需要用软件去模拟 SDA 和SCL 线的时序。

1677250065702tv6e26jjse

1677250066041q27zzqfll5

这两幅图表示的是主机和从机通信时 SDA 线的数据包序列。

其中 S 表示由主机的I2C接口产生的传输起始信号(S),这时连接到I2C总线上的所有从机都会接收到这个信号。

起始信号产生后,所有从机就开始等待主机紧接下来广播的从机地址信号(SLAVE_ADDRESS),在I2C总线上,每个设备的地址都是唯一的。当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据I2C协议,这个从机地址可以是 7 位或 10 位。

在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主机传输至从机。该位为 1 时,则相反。

从机接收到匹配的地址后,主机或从机会返回一个应答(A)或非应答信号,只有接收到应答信号后,主机才能继续发送或接收数据。

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

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

2 RA4M2 的I2C架构及特性

瑞萨 RA4M2有两个I2C通道,I2C模块符合变更提供NXPI2C总线接口功能。

2.1 I2C接口特性

1)RA4M2的芯片均有多达两个的I2C总线接口。

2)能够工作于多主模式或从模式,分别为主接收器、主发送器、从接收器及从发送器。

3)支持标准模式 100 Kbit/s 和快速模式 400 Kbit/s,不支持高速模式。

4)支持 7 位或 10 位寻址。

5)内置了硬件 CRC 发生器 / 校验器。

6)I2 C 的接收和发送都可以使用 DMA 操作。

7)支持系统管理总线(SMBus)2.0 版。

2.2 I2C 架构

I2C的所有硬件架构就是根据 SCL 线和 SDA 线展开的(其中 SMBALERT 线用于 SMBus)。

1677250067424dj9ofi07u2

SCL 线的时序即为I2C 协议中的时钟信号,它由I2C 接口根据时钟控制寄存器(CCR)控制,控制的参数主要为时钟频率。而 SDA 线的信号则通过一系列数据控制架构,在将要发送的数据的基础上,根据协议添加各种起始信号、应答信号、地址信号,实现以I2C 协议的方式发送出去。读取数据时则从 SDA 线上的信号中取出接收到的数据值。发送和接收的数据都被保存在数据寄存器上。

3 I2C总线的数据传送

3.1 数据位的有效性规定

I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

1677250067921ce0ldxqqer

3.2 起始和终止信号

SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号

1677250068117230hxfolgb

起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态。连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。每当发送器件传输完一个字节的数据后,后面必须紧跟一个校验位,这个校验位是接收端通过控制SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成,数据传送可以继续进行。

3.3 数据传送格式

  • 字节传送与应答

每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

1677250068349cc12s788ks

由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据),它必须将数据线置于高电平,而由主机产生一个终止信号以结束总线的数据传送。

如果从机对主机进行了应答,但在数据传送一段时间后无法继续接收更多的数据时,从机可以通过对无法接收的第一个数据字节的“非应答”通知主机,主机则应发出终止信号以结束数据的继续传送。

当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。

  • 总线的寻址

I2C总线协议有明确的规定:采用7位的寻址字节(寻址字节是起始信号后的第一个字节)。

1677250068624cicw3xbxff

3.4 寻址字节的位定义

D7~D1位组成从机的地址。D0位是数据传送方向位,为“0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。

主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己正被主机寻址,根据R/T位将自己确定为发送器或接收器。

从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的7位寻址位有4位是固定位,3位是可编程位,这时仅能寻址8个同样的器件,即可以有8个同样的器件接入到该I2C总线系统中。

  • 数据帧格式

I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。

在总线的一次数据传送过程中,可以有以下几种组合方式:

A)主机向从机发送数据,数据传送方向在整个过程中不变;

16772500688625d6vybune6

注:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。A表示应答, A表示非应答(高电平)。S表示起始信号,P表示终止信号。

B)主机在第一个字节后,立即从从机读数据。

1677250069121txdv4lngto

C)在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相。

1677250069374r6wrtfmn3g

4 EEPROM存储器原理

EEPROM 是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载。 EEPROM 芯片最常用的通讯方式就是 I2C 协议,本文将要讲解EEPROM的读写操作。

4.1 AT24Cxx概述

AT24C01/02/04/08/16是一个1K/2K/4K/8K/16K位串行CMOS,EEPROM内部含有128/256/512/1024/2048个8位字节CATALYST公司的先进CMOS技术实质上减少了器件的功耗,AT24C01/02有一个8字节页写缓冲器AT24C04/08/16有一个16字节页写缓冲器,该器件通过I2C总线接口进行操作有一个专门的写保护功能。AT24C01/02每页有8个字节,分别为16/32页;AT24C04/08/16每页有16个字节,分别为32/64/128页

工作特点

  • 与400KHz I2C总线兼容
  • 1.8到6.0伏工作电压范围
  • 低功耗CMOS技术
  • 写保护功能当WP为高电平时进入写保护状态
  • 页写缓冲器
  • 自定时擦写周期
  • 100万次编程/擦除周期
  • 可保存数据100年
  • 8脚DIP SOIC或TSSOP封装
  • 温度范围商业级和工业级

AT24Cxx的引脚定义如下:

1677250069610ajdsrnpveu

Note: For use of 5-lead SOT23, the software A2, A1, and A0 bits in the device address word must be set to zero toproperly communicate.

1677250069955pg37jk71l7

4.2 AT24Cxx总线时序

AT24Cxx的I2C总线时序如下:

16772500718424sxuyc3url

其读写周期的的电压范围如下:

1677250072238fz4juyw2ax

写周期时间是指从一个写时序的有效停止信号到内部编程/擦除周期结束的这一段时间。在写周期期间,总线接口威廉希尔官方网站 禁能,SDA保持为高电平,器件不响应外部操作。

4.3 AT24Cxx器件寻址

主器件通过发送一个起始信号启动发送过程,然后发送它所要寻址的从器件的地址。8位从器件地址的高4位固定为(1010)。接下来的3位(A2、A1、A0)为器件的地址位,用来定义哪个器件以及器件的哪个部分被主器件访问,上述8个AT24C01/02,4个AT24C04,2个AT24C08,1个AT24C16可单独被系统寻址。从器件8位地址的最低位,作为读写控制位。“1”表示对从器件进行读操作,“0”表示对从器件进行写操作。在主器件发送起始信号和从器件地址字节后,AT24C01/02/04/08/16监视总线并当其地址与发送的从地址相符时响应一个应答信号(通过SDA线)。AT24C01/02/04/08/16再根据读写控制位(R/W)的状态进行读或写操作。

1677250074218w6fgui5gav

  • 字节写

在字节写模式下,主器件发送起始命令和从器件地址信息(R/W)位置发给从器件,在从器件产生应答信号后,主器件发送AT24Cxx的字节地址,主器件在收到从器件的另一个应答信号后,再发送数据到被寻址的存储单元。AT24Cxx再次应答,并在主器件产生停止信号后开始内部数据的擦写,在内部擦写过程中,AT24Cxx不再应答主器件的任何请求。

1677250074521pzl85tkh42

  • 页写

用页写,AT24C01/02可一次写入8 个字节数据,AT24C04/08/16可以一次写入16个字节的数据。__页写操作的启动和字节写一样,不同在于传送了一字节数据后并不产生停止信号。__主器件被允许发送P(AT24C01:P=7;AT24C02/04/08/16:P=15)个额外的字节。每发送一个字节数据后AT24Cxx产生一个应答位并将字节地址低位加1,高位保持不变。

如果在发送停止信号之前主器件发送超过P+1个字节,地址计数器将自动翻转,先前写入的数据被覆盖。

接收到P+1字节数据和主器件发送的停止信号后,AT24Cxx启动内部写周期将数据写到数据区。所有接收的数据在一个写周期内写入AT24Cxx。

16772500750560n68cynqwp

  • 读字节

读操作允许主器件对寄存器的任意字节进行读操作,主器件首先通过发送起始信号、从器件地址和它想读取的字节数据的地址执行一个写操作。在AT24Cxx应答之后,主器件重新发送起始信号和从器件地址,此时R/W位置1,AT24Cxx响应并发送应答信号,然后输出所要求的一个8位字节数据,主器件不发送应答信号但产生一个停止信号。

16772500753563n6k7asstv

  • 顺序读

在AT24Cxx发送完一个8位字节数据后,主器件产生一个应答信号来响应,告知AT24Cxx主器件要求更多的数据,对应每个主机产生的应答信号AT24Cxx将发送一个8位数据字节。当主器件不发送应答信号而发送停止位时结束此操作。

从AT24Cxx输出的数据按顺序由N到N+1输出。读操作时地址计数器在AT24Cxx整个地址内增加,这样整个寄存器区域可在一个读操作内全部读出,当读取的字节超过E(对于24WC01,E=127;对24C02,E=255;对24C04,E=511;对24C08,E=1023;对24C16,E=2047)计数器将翻转到零并继续输出数据字节。

1677250075885efyqhbf741

  • 典型应用

ATC02的典型威廉希尔官方网站 如下:

1677250076169nlfn4s52ws

根据AT24C02的芯片资料,我们会发现AT24C02有三个地址A0,A1,A2。同时,我们会在资料的Device Address介绍发现I2C器件一共有七位地址码,还有一位是读/写(R/W)操作位,而在AT24C02的前四位已经固定为1010。R/W为1则为 读操作,为0则为写操作。R/W位我们要设置为0(写操作)。

规则为:1010(A0)(A1)(A2)(R/W)

例子1:

那么对应的A0,A1,A2都是接的VCC,所以为A0=1,A1=1,A2=1;可以知道AT24C02的从设备写地址为10101110(0xae),读设备地址为10101111(0xaf)。

例子2:

那么对应的A0,A1,A2都是接的GND,所以为A0=0,A1=0,A2=0;可以知道AT24C02的从设备写地址为10100000(0xa0),读设备地址为10100001(0xa1)。

5 硬件设计

本文是使用I2C协议对EEPROM进行读写操作,具体的硬件连接如下。

1677250077016co4l1uyjid

按照我们此处的连接, A0/A1/A2 均为 0,所以 EEPROM 的 7 位设备地址是: 1010 000b ,即 0x50

RA4M2开发板上预留一个I2C接口,如下图所示。

1677250077359l0rw4geg1s

这里就使用I2C3读写AT24C02设备。

6 I2C读写EEPROM

6.1 RA Smart Configurator配置I2C

打开RA Smart Configurator,根据硬件连接,I2C使用的是I2C3,因此在配置界面里面依次打开“Pins->Peripherals->Connectivity:SCI>SCI3”配置SCI模块,选择开发板所用的I2C引脚,这里SCL和SDA分别接的是P408和P409引脚。

16772500778302pzaw09rxf

接下来就是添加I2C的stack。

1677250078190e60tv715yx

接下来需要配置I2C的参数。

1677250079152s9x682nu88

这里可以设置I2C的参数,我这里设置I2C的变量名、通道以及从机地址,I2C的编号和Channel编号是一一对应的,因此需要设置为3,回调函数依据C语言命名规范任意编译一个就行。

值得注意的是,这里的从机地址是7位,代码中自动左移了。

然后让软件自动生成配置代码即可。

6.2 基于I2C的EEPROM读写实现

R_SCI_I2C_Open()函数为执行IIC初始化,开启配置如下所示。

/* Initialize the I2C module */
err = R_SCI_I2C_Open(&g_i2c3_ctrl, &g_i2c3_cfg);
/* Handle any errors. This function should be defined by the user. */
assert(FSP_SUCCESS == err);

R_SCI_I2C_Write()函数是向IIC设备中写入数据,写入格式如下所示。

err = R_SCI_I2C_Write(&g_i2c_device_ctrl_1, &g_i2c_tx_buffer[0], I2C_BUFFER_SIZE_BYTES, false);
assert(FSP_SUCCESS == err);

R_SCI_I2C_Read ()函数是向IIC设备中写入数据,写入格式如下所示。

err = R_SCI_I2C_Read(&g_i2c_device_ctrl_1, &g_i2c_rx_buffer[0], I2C_BUFFER_SIZE_BYTES, false);
assert(FSP_SUCCESS == err);

sci_i2c_master_callback()回调函数用于数据是否发送完毕,可以查看是否获取到I2C_MASTER_EVENT_TX_COMPLETE字段。

i2c_master_event_t i2c_event = I2C_MASTER_EVENT_ABORTED;
void sci_i2c_master_callback(i2c_master_callback_args_t *p_args)
{
    i2c_event = I2C_MASTER_EVENT_ABORTED;
    if (NULL != p_args)
    {
        /* capture callback event for validating the i2c transfer event*/
        i2c_event = p_args->event;
    }
}

主要配置I2C模式、低电平占空比、I2C寻址模式以及通信速率,最后使能I2C设备。

初始化完成后就是对AT24C02的读写操作,严格按照相应的时序操作就行。

6.2.1 字节写

在字节写模式下,向AT24C02中写数据时序如下:

1677250079514x8xffj6j2s

操作时序如下:

1.MCU先发送一个开始信号(START)启动总线

2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)

3.等待应答信号(ACK)

4.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储在哪个位置,此刻写的就是哪个地址。

5.发送要存储的数据,在写数据的过程中,AT24C02会回应一个“应答位0”,则表明写AT24C02数据成功,如果没有回应答位,说明写入不成功。

6.发送结束信号(STOP)停止总线。

代码很简单,跟着时序来就行。

/**
  * @brief   以单字节的方式到 I2C EEPROM 中
  * @param   address: 写地址
  * @param   byte: 写的数据
  * @retval  None
  */
void BSP_I2C_EE_ByteWrite(unsigned char address, unsigned char byte)
{
    unsigned char send_buffer[2] = {};
    send_buffer[0] = address;
    send_buffer[1] = byte;
    R_SCI_I2C_Write(&g_i2c3_ctrl, &send_buffer[0], 2, false); //每当写完数据 false 总线拉高

    while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms)
    {
        R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MILLISECONDS);
        timeout_ms--;
    }
    timeout_ms = 500;
}

6.2.2 页写

用页写,AT24C01可一次写入8 个字节数据,AT24C02/04/08/16可以一次写入16个字节的数据。__页写操作的启动和字节写一样,不同在于传送了一字节数据后并不产生停止信号。__每发送一个字节数据后AT24Cxx产生一个应答位并将字节地址低位加1,高位保持不变。

如果在发送停止信号之前主器件发送超过P+1个字节,地址计数器将自动翻转,先前写入的数据被覆盖。

接收到P+1字节数据和主器件发送的停止信号后,AT24Cxx启动内部写周期将数据写到数据区。所有接收的数据在一个写周期内写入AT24Cxx。

1677250079853soohyfjvof

代码很简单,和字节写不同的是,数据会一直发,直到主机发送停止信号。

/**
  * [url=home.php?mod=space&uid=2666770]@Brief[/url]  将缓冲区中的数据以页写入的方式写到 I2C EEPROM 中
  * [url=home.php?mod=space&uid=3142012]@param[/url]  ptr_write: 缓冲区指针
  * @param  WriteAddr: 写地址
  * @param  len: 写的长度
  * @retval None
  */
void BSP_I2C_EE_Writepage(unsigned char* ptr_write , unsigned char WriteAddr, unsigned char len)
{
    unsigned char send_buffer[9] = {};
    send_buffer[0] = WriteAddr;

    for(unsigned char i = 0;i<len;i++)
    {
        send_buffer[1+i] = *(ptr_write+i);
    }
    R_SCI_I2C_Write(&g_i2c3_ctrl, &send_buffer[0], len+1 , false);
    while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms)
    {
        R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MILLISECONDS);
        timeout_ms--;
    }
    timeout_ms = 500;
    BSP_I2C_EE_WaitState(); //一定要有延时
}

任意写

在实际过程中,我们经常需要任意写数据,这里就调用页写的操作,来实现任意字节的写操作。

/**
  * @brief  将缓冲区中的数据写到 I2C EEPROM 中
  * @param  pBuffer: 缓冲区指针
  * @param  WriteAddr: 写地址
  * @param  NumByteToWrite: 写的字节数
  * @retval None
  */
void BSP_I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
{
    uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
    Addr = WriteAddr % EEPROM_PAGESIZE;
    count = EEPROM_PAGESIZE - Addr;
    NumOfPage = (uint8_t)(NumByteToWrite / EEPROM_PAGESIZE);
    NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;

    /* If WriteAddr is I2C_PageSize aligned */
    if (Addr == 0)
    {
        /* If NumByteToWrite < I2C_PageSize */
        if (NumOfPage == 0)
        {
            BSP_I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
        }
        /* If NumByteToWrite > I2C_PageSize */
        else
        {
            while (NumOfPage--)
            {
                BSP_I2C_EE_Writepage(pBuffer, WriteAddr, EEPROM_PAGESIZE);
                WriteAddr += EEPROM_PAGESIZE;
                pBuffer += EEPROM_PAGESIZE;
            }
            if (NumOfSingle!=0)
            {
                BSP_I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
            }
        }
    }
    /* If WriteAddr is not I2C_PageSize aligned */
    else
    {
        /* If NumByteToWrite < I2C_PageSize */
        if (NumOfPage== 0)
        {
            BSP_I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
        }
        /* If NumByteToWrite > I2C_PageSize */
        else
        {
            NumByteToWrite -= count;
            NumOfPage = (uint8_t)(NumByteToWrite / EEPROM_PAGESIZE);
            NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
            if (count != 0)
            {
                BSP_I2C_EE_Writepage(pBuffer, WriteAddr, count);
                WriteAddr += count;
                pBuffer += count;
            }
            while (NumOfPage--)
            {
                BSP_I2C_EE_Writepage(pBuffer, WriteAddr, EEPROM_PAGESIZE);
                WriteAddr += EEPROM_PAGESIZE;
                pBuffer += EEPROM_PAGESIZE;
            }
            if (NumOfSingle != 0)
            {
                BSP_I2C_EE_Writepage(pBuffer, WriteAddr, NumOfSingle);
            }
        }
    }
}

主要分为两种情况,写的地址正好是一页的开始,另外一种是在一页的中间。不管如何,始终遵循的原则就是最大智能写一页,可以从一页的中间开始。

6.2.4 读字节

读操作允许主器件对寄存器的任意字节进行读操作,主器件首先通过发送起始信号、从器件地址和它想读取的字节数据的地址执行一个写操作。在AT24Cxx应答之后,主器件重新发送起始信号和从器件地址,此时R/W位置1,AT24Cxx响应并发送应答信号,然后输出所要求的一个8位字节数据,主器件不发送应答信号但产生一个停止信号。

1677250080293b7gdbd2nnh

读取字节的时序如下:

1.MCU先发送一个开始信号(START)启动总线

2.接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)

注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉AT24Cxx要读取哪个地址的数据。

3.发送要读取内存的地址(WORD ADDRESS),通知AT24Cxx读取要哪个地址的信息。

4.重新发送开始信号(START)。

5.发送设备读操作地址(DEVICE ADDRESS)对AT24Cxx进行读操作 (0xA1)。

6.AT24Cxx会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)。

7.发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线。

6.2.5 顺序读

在AT24Cxx发送完一个8位字节数据后,主器件产生一个应答信号来响应,告知AT24Cxx主器件要求更多的数据,对应每个主机产生的应答信号AT24Cxx将发送一个8位数据字节。当主器件不发送应答信号而发送停止位时结束此操作。

从AT24Cxx输出的数据按顺序由N到N+1输出。读操作时地址计数器在AT24Cxx整个地址内增加,这样整个寄存器区域可在一个读操作内全部读出,当读取的字节超过E(对于24WC01,E=127;对24C02,E=255;对24C04,E=511;对24C08,E=1023;对24C16,E=2047)计数器将翻转到零并继续输出数据字节。

1677250080706t9oephkexo

我们常用的方式就是连续读取,代码很简单。

/**
  * @brief  读取 I2C EEPROM 数据
  * @param  ptr_read: 读取缓冲区指针
  * @param  address: 地址
  * @param  byte: 读取的字节数
  * @retval None
  */
void BSP_I2C_EE_BufferRead(unsigned char* ptr_read,unsigned char address,unsigned char byte)
{
    unsigned char send_buffer[2] = {};
    send_buffer[0] = address;
    R_SCI_I2C_Write(&g_i2c3_ctrl, &send_buffer[0], 1, true);
    while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms)
    {
        R_BSP_SoftwareDelay(400U, BSP_DELAY_UNITS_MICROSECONDS);
        timeout_ms--;
    }
    timeout_ms = 500;
    R_BSP_SoftwareDelay(250U, BSP_DELAY_UNITS_MICROSECONDS);
    R_SCI_I2C_Read(&g_i2c3_ctrl, ptr_read, byte, false);
}

最后看下hal_entry()函数吧。

void hal_entry(void)
{
    R_SCI_UART_Open (g_uart9.p_ctrl, g_uart9.p_cfg);

    /* Initialize the I2C module */
    R_SCI_I2C_Open(&g_i2c3_ctrl, &g_i2c3_cfg);

    BSP_I2C_Test();
    
    /* TODO: add your own code here */
    while(1)
    {
        R_BSP_SoftwareDelay(500, BSP_DELAY_UNITS_MILLISECONDS);
    }
#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

首先对I2C初始化,然后就进行EEPROM的读写测试。

BSP_I2C_Test()函数如下:

/**
  * @brief  I2C(AT24C02) 读写测试
  * @param  None
  * @retval 正常返回 1 ,不正常返回 0
  */
uint8_t BSP_I2C_Test(void)
{
    uint8_t i;
    uint8_t DATA_Size = 16;
    uint8_t I2c_Buf_Write[32] = {};
    uint8_t I2c_Buf_Read[32] = {};
    //将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中
    printf("Write data:\r\n");
    for ( i=0; i<DATA_Size; i++ )
    {
        I2c_Buf_Write[i] = i;
        printf("0x%02X ", I2c_Buf_Write[i]);
        if (i%16 == 15)
        {
            printf("\r\n");
        }
    }
    BSP_I2C_EE_BufferWrite( I2c_Buf_Write, 0x00 , DATA_Size);
    //将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中
    printf("\r\nRead data:\r\n");
    R_BSP_SoftwareDelay(10U, BSP_DELAY_UNITS_MILLISECONDS);
    BSP_I2C_EE_BufferRead(I2c_Buf_Read, 0x00 , DATA_Size);
    //将 I2c_Buf_Read 中的数据通过串口打印
    R_BSP_SoftwareDelay(10U, BSP_DELAY_UNITS_MILLISECONDS);
    for (i=0; i<DATA_Size; i++)
    {
        if (I2c_Buf_Read[i] != I2c_Buf_Write[i])
        {
            printf("0x%02X \r\n", I2c_Buf_Read[i]);
            printf("error : the data written and read are inconsistent\r\n");
            printf("%d\n",i);
            return 0;
        }
        printf("0x%02X ", I2c_Buf_Read[i]);
        if (i%16 == 15)
        {
            printf("\r\n");
        }
    }
    printf("\r\nI2C(AT24C02)Read and write test succeeded \r\n");
    return 1;
}

6.3 实现现象

接上AT24C02模块,打开串口助手,打印信息如下:

1677250081039ghnzk3t5ac

从打印信息可以看出,I2C读写AT24C02成功。

更多回帖

发帖
×
20
完善资料,
赚取积分