直接控制 RT1052 的两个 GPIO 引脚,分别用作 SCL 及 SDA,按照上述信号的时序要求,就可以实现 I 2 C 通讯。
由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。
硬件协议方式:
RT1052 的 LPI2C(Low power I2C,低功耗 I2C)片上外设专门负责实现 I 2 C 通讯协议
RT1052 的 I 2 C 外设可用作通讯的主机及从机
I 2 C 的所有硬件架构都是根据图中右侧 SCL 线和 SDA 线展开的。
在标准 I2C 协议之外,LPI2C 还增加了 HREQ、SCLS 及 SDAS 这三个引脚
I2C 外设由功能时钟(Functional Clock)、外部时钟(Extern Clock)和总线时钟(BusClock)这三部分时钟驱动
功能时钟
I2C 主设备逻辑电路(Master Logic)由功能时钟驱动。
可以使用选择的时钟来源:
LPI2C_CLK_ROOT 输入到 LPI2C 外设内部后,经过LPI2C 内部的分频器(Prescaler)后用于驱动生成 SCL 时钟信号
外部时钟
LPI2C 模块工作于从设备模式时,它的从设备逻辑电路(Slave Logic)直接以外部的 SCL 和 SDA 总线作为时钟进行驱动。
总线时钟
指 RT1052 内部外设总线(Internal Soc Peripheral Bus)的时钟,
SCL 和 SDA 信号线输入后都经过毛刺过滤器(Glitch Filter),用于消除输入信号的噪声
LPI2C 的主从模式下
当信号线接收到的信号周期小于对应的 FILTSDA 或 FILSCL位配置的周期时,该信号会被认为是毛刺而被过滤掉
主从设备逻辑电路(Master Logic 和 Slave Logic)分别用于管理主设备模式和从设备模式下 LPI2C外设的工作
实际应用中我们可以直接通过调用 NXP 提供的库函数设置波特率
主状态寄存器 MSR
告知我们当前外设运行的情况
在进行通讯前可以检查 bit25 的 BBF 位
如通过检测 bit10 的 NDF 位即可知道从设备的响应状态。
LPI2C 外设工作于 I2C 中的主机模式
起始、结束、应答、地址以及数据发送信号的产生,都由“命令/发送 FIFO”控制
内核可以通过向“主发送数据寄存器 MTDR”的写操作往“命令/发送 FIFO”写入内容
MTDR 寄存器
该寄存器分为 CMD 和 DATA 两个域
每次按 16 或 32 位的方式往 MTDR 寄存器写入内容后“命令/发送 FIFO”的计数指针会加 1,进入待发送状态
当 I2C 总线上接收到数据后,主设备逻辑电路会把数据存储到接收 FIFO 中
主 FIFO 状态寄存器 MFSR
通过查看“主 FIFO 状态寄存器 MFSR”的 RXCOUNT 及 TXCOUNT 域可以知道当前“接收 FIFO”和“命令/发送 FIFO”的使用状态
往“主发送数据寄存器 MTDR”写入数据前尤其要注意查看 TXCOUNT 域
当 LPI2C 外设工作于 I2C 中的从机模式时,收发数据时没有 FIFO 进行缓冲,直接通过“从发送数据寄存器 STDR”和“从接收数据寄存器 SRDR”进行发送和接收数据即可
NXP 标准库提供了 LPI2C 初始化配置结构体及初始化配置函数来配置 LPI2C外设。
1 /*!
2 * @brief LPI2C 主机模式的初始化配置结构体
3 * 可以通过调用 LPI2C_MasterGetDefaultConfig() 函数
4 * 把本结构体变量赋值为一个适当的默认配置
5 */
6 typedef struct _lpi2c_master_config {
7 bool enableMaster; /*!< 是否使能主机模式 */
8 bool enableDoze; /*!< 是否使用主机的 doze 模式 */
9 bool debugEnable; /*!< 在调试模式暂停时,是否持续传输 */
10 bool ignoreAck; /*!< 是否忽略 ACK/NACK 响应 */
11 lpi2c_master_pin_config_t pinConfig; /*!< 引脚配置模式结构体 */
12 uint32_t baudRate_Hz; /*!< 期望通讯的波特率 */
13 /*!< 总线空闲检测确认时间( ns ),设置为 0 时禁止该功能 */
14 uint32_t busIdleTimeout_ns;
15 /*!< 总线低电平检测超时时间( ns ),设置为 0 时禁止该功能 */
16 uint32_t pinLowTimeout_ns;
17 /*!< SDA 信号线的毛刺滤波器宽度配置( ns ),设置为 0 时禁止该功能 */
18 uint8_t sdaGlitchFilterWidth_ns;
19 /*!< SCL 信号线的毛刺滤波器宽度配置( ns ),设置为 0 时禁止该功能 */
20 uint8_t sclGlitchFilterWidth_ns;
21 struct {
22 /*!< 使能主机请求功能 */
23 bool enable;
24 /*!< 主机请求源 */
25 lpi2c_host_request_source_t source;
26 /*!< 主机请求的有效信号极性 */
27 lpi2c_host_request_polarity_t polarity;
28 } hostRequest; /*!< 主机请求配置 */
29 } lpi2c_master_config_t;
本成员设置 LPI2C 是否工作于 I2C 通讯的主机模式,在 I2C 中主机。
本成员设置是否使能 doze 模式,
通过配置 de-bugEnable 可以决定在调试暂停时 LPI2C 是否依然进行数据传输。
使能本成员配置后,LPI2C 的通讯会直接忽略其它设备的响应
这个成员是一个 lpi2c_master_pin_config_t 类型的枚举变量
1 /*! @brief LPI2C 引脚配置 */
2 typedef enum _lpi2c_master_pin_config {
3 /*!< LPI2C 配置为 2-pin 开漏模式 */
4 kLPI2C_2PinOpenDrain = 0x0U,
5 /*!< LPI2C 配置为 2-pin 只输出的模式 ( 超快速模式 ) */
6 kLPI2C_2PinOutputOnly = 0x1U,
7 /*!< LPI2C 配置为 2-pin 推挽模式 */
8 kLPI2C_2PinPushPull = 0x2U,
9 /*!< LPI2C 配置为 4-pin 推挽模式 */
10 kLPI2C_4PinPushPull = 0x3U,
11 /*!< LPI2C 配置为 2-pin 开漏并且主从机使用独立的总线 */
12 kLPI2C_2PinOpenDrainWithSeparateSlave = 0x4U,
13 /*!< LPI2C 配置为 2-pin 只输出的模式 ( 超快速模式 ) 并且主从机使用独立的总线 */
14 kLPI2C_2PinOutputOnlyWithSeparateSlave = 0x5U,
15 /*!< LPI2C 配置为 2-pin 推挽模式并且主从机使用独立的总线 */
16 kLPI2C_2PinPushPullWithSeparateSlave = 0x6U,
17 /*!< LPI2C 配置为 4-pin 反相输出推挽模式 */
18 kLPI2C_4PinPushPullWithInvertedOutput = 0x7U
19 } lpi2c_master_pin_config_t;
按标准的 I2C 协议,直接使用上面定义的 2 线开漏模式 kLPI2C_2PinOpenDrain 即可,其余的主从机独立总线模式、4 线模式可根据自己的应用进行扩展。
本成员用于配置 I2C 通讯的波特率
用于设置总线空闲检测的确认时间,单位为纳秒。
这用于设置 SCL 或 SDA 总线低电平的检测超时时间,单位为纳秒。
分别用于配置 SDA 和 SCL 信号线的毛刺过滤器
hostRequest 是一个包含三个成员的结构体变量,它用于配置主机请求相关的功
能。
1 /*! @brief LPI2C 主机请求来源选择 */
2 typedef enum _lpi2c_host_request_source {
3 /*!< 以 LPI2C_HREQ 引脚作为请求来源 */
4 kLPI2C_HostRequestExternalPin = 0x0U,
5 /*!< 以输入触发器作为请求源 */
6 kLPI2C_HostRequestInputTrigger = 0x1U,
7 } lpi2c_host_request_source_t;
1 /*! @brief LPI2C 主机请求引脚极性配置 */
2 typedef enum _lpi2c_host_request_polarity {
3 /*!< 配置 LPI2C_HREQ 引脚为低电平有效 */
4 kLPI2C_HostRequestPinActiveLow = 0x0U,
5 /*!< 配置 LPI2C_HREQ 引脚为高电平有效 */
6 kLPI2C_HostRequestPinActiveHigh = 0x1U
7 } lpi2c_host_request_polarity_t;
初始化配置结构体时,通常先直接调用 LPI2C_MasterGetDefaultConfig 函数赋予常用默认配置,然后再针对性地把初始化配置结构体修改成自己需要的内容
6 void LPI2C_MasterGetDefaultConfig(lpi2c_master_config_t *masterConfig)
7 {
8 masterConfig->enableMaster = true;
9 masterConfig->debugEnable = false;
10 masterConfig->enableDoze = true;
11 masterConfig->ignoreAck = false;
12 masterConfig->pinConfig = kLPI2C_2PinOpenDrain;
13 masterConfig->baudRate_Hz = 100000U;
14 masterConfig->busIdleTimeout_ns = 0;
15 masterConfig->pinLowTimeout_ns = 0;
16 masterConfig->sdaGlitchFilterWidth_ns = 0;
17 masterConfig->sclGlitchFilterWidth_ns = 0;
18 masterConfig->hostRequest.enable = false;
19 masterConfig->hostRequest.source = kLPI2C_HostRequestExternalPin;
20 masterConfig->hostRequest.polarity = kLPI2C_HostRequestPinActiveHigh;
21 }
还需要调用LPI2C_MasterInit 函数根据结构体的配置值向寄存器写入配置
2 * @brief 初始化 LPI2C 主机外设
3 *
4 * 本函数使能 LPI2C 时钟并根据结构体初始化 LPI2C 主机外设
5 * @param base LPI2C 设备号
6 * @param masterConfig 初始化结构体
7 * @param sourceClock_Hz LPI2C 的功能时钟,函数根据它计算波特率分频因子、
8 * 毛刺滤波器宽度及超时周期
9 */
10 void LPI2C_MasterInit(LPI2C_Type *base,
11 const lpi2c_master_config_t *masterConfig,
12 uint32_t sourceClock_Hz);
NXP 提供了传输结构体 lpi2c_master_transfer_t 和发送函数以简化 LPI2C 的数据通讯
1 /*!
2 * @brief 传输结构体
3 * 本结构体用于给 LPI2C_MasterTransferNonBlocking()
4 * 或 LPI2C_MasterTransferBlocking() 提供传输参数
5 *
6 */
7 struct _lpi2c_master_transfer {
8 uint32_t
9 flags; /*!< 传输的选项标志, 可选值为枚举类型 #_lpi2c_master_transfer_flags
10 设置为 0 或 #kLPI2C_TransferDefaultFlag 值时表示正常传输 */
11 uint16_t slaveAddress; /*!< 要访问的 I2C 设备地址( 7bit 、 10bit ) */
12 lpi2c_direction_t direction; /*!< 读 #kLPI2C_Read 或写 #kLPI2C_Write 方向 */
13 uint32_t subaddress; /*!< 子地址(设备内部的寄存器地址), MSB 先行 */
14 size_t subaddressSize; /*!< 字地址长度,最长为 4 字节 */
15 void *data; /*!< 指向要传输的数据指针 */
16 size_t dataSize; /*!< 要传输数据的字节数 */
17 };
18
19 /* 传输结构体 typedef 定义 */
20 typedef struct _lpi2c_master_transfer lpi2c_master_transfer_t;
用于控制传输选项的标志,它的取值如下枚举:
1 /*!
2 * @brief 传输选项标志枚举变量
3 * @note 这些枚举标志支持使用 或 操作
4 * 来对 #_lpi2c_master_transfer::flags 域进行配置
5 */
6 enum _lpi2c_master_transfer_flags {
7 /*!< 传输以起始信号开始,以停止信号结束 */
8 kLPI2C_TransferDefaultFlag = 0x00U,
9 /*!< 不发送起始信号、 I2C 设备地址及子地址(寄存器地址) */
10 kLPI2C_TransferNoStartFlag = 0x01U,
11 /*!< 发送一个重复起始信号 */
12 kLPI2C_TransferRepeatedStartFlag = 0x02U,
13 /*!< 不发送停止信号 */
14 kLPI2C_TransferNoStopFlag = 0x04U,
15 };
且可通过“|”或运算同时设置几个选项
设置这次通讯要访问的 I2C 设备地址
设置本次通讯的传输方向,它可选的枚举变量值为 kLPI2C_Read(读方向)或kLPI2C_Write(写方向)。
设置要访问的子地址,即 I2C 复合传输过程中的寄存器地址
本成员用于表示上面 subaddress 的大小
data 结构体成员是一个指针,使用时它指向一个缓冲区,在程序上的表现通常为一个数组的地址。
用于指定传输时要发送或接收数据的字节数。
调用库函数 LPI2C_MasterTransferBlocking 或LPI2C_MasterTransferNonBlocking 开始数据传输
EEPOM 芯片最常用的通讯方式就是 I 2 C 协议。
(1) 定义要使用的 LPI2C 号,控制相关的引脚号;
(2) 配置引脚的 MUX 复用模式及 PAD 属性配置;
(3) 配置 LPI2C 外设的时钟来源、分频得到 LPI2C 根时钟(LPI2C_CLK_ROOT);
(4) 配置 LPI2C 外设的模式、速率等参数;
(5) 编写 LPI2C 按字节收发的函数;
(6) 编写读写 EEPROM 存储内容的函数;
(7) 编写测试程序,对读写数据进行校验。
1 /* lpi2c 外设编号 */
2 #define EEPROM_I2C_MASTER_BASE (LPI2C1_BASE)
3 #define EEPROM_I2C_MASTER ((LPI2C_Type *)EEPROM_I2C_MASTER_BASE)
4 /* lpi2c 波特率 */
5 #define EEPROM_I2C_BAUDRATE 400000U
6
7 /*! @brief I2C 引脚定义 */
8 #define EEPROM_SCL_IOMUXC IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL
9 #define EEPROM_SDA_IOMUXC IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA
1 /********************* 第 2 部分中使用的宏 **********************/
2 /* I2C 的 SCL 和 SDA 引脚使用同样的 PAD 配置 */
3 #define I2C_PAD_CONFIG_DATA (SRE_0_SLOW_SLEW_RATE| \
4 DSE_6_R0_6| \
5 SPEED_1_MEDIUM_100MHz| \
6 ODE_1_OPEN_DRAIN_ENABLED| \
7 PKE_1_PULL_KEEPER_ENABLED| \
8 PUE_0_KEEPER_SELECTED| \
9 PUS_3_22K_OHM_PULL_UP| \
10 HYS_0_HYSTERESIS_DISABLED)
11 /* 配置说明 : */
12 /* 转换速率 : 转换速率慢
13 驱动强度 : R0/6
14 带宽配置 : medium(100MHz)
15 开漏配置 : 使能
16 拉 / 保持器配置 : 使能
17 拉 / 保持器选择 : 保持器
18 上拉 / 下拉选择 : 22K 欧姆上拉 ( 选择了保持器此配置无效 )
19 滞回器配置 : 禁止 */
20
21 /************************ 第 1 部分 ****************************/
22 /**
23 * @brief 初始化 EEPROM 相关 IOMUXC 的 MUX 复用配置
24 * @param 无
25 * @retval 无
26 */
27 static void I2C_EEPROM_IOMUXC_MUX_Config(void)
28 {
29 /* SCL 和 SDA 引脚,需要使能 SION 以接收数据 */
30 IOMUXC_SetPinMux(EEPROM_SCL_IOMUXC, 1U);
31 IOMUXC_SetPinMux(EEPROM_SDA_IOMUXC, 1U);
32 }
33
34 /************************ 第 2 部分 ****************************/
35 /**
36 * @brief 初始化 EEPROM 相关 IOMUXC 的 PAD 属性配置
37 * @param 无
38 * @retval 无
39 */
40 static void I2C_EEPROM_IOMUXC_PAD_Config(void)
41 {
42 /* SCL 和 SDA 引脚 */
43 IOMUXC_SetPinConfig(EEPROM_SCL_IOMUXC, I2C_PAD_CONFIG_DATA);
44 IOMUXC_SetPinConfig(EEPROM_SDA_IOMUXC, I2C_PAD_CONFIG_DATA);
45 }
I2C_EEPROM_IOMUXC_MUX_Config 函数
定义一个 I2C_EEPROM_IOMUXC_PAD_Config 函数
1 /**** 第 1 部分使用的宏,本内容位于 bsp_i2c_eeprom.h 文件 **********/
2 /*
3 选择 LPI2C 的时钟源
4 0 derive clock from pll3_60m
5 1 derive clock from osc_clk
6 */
7 /* 选择 USB1 PLL/8 (480/8 = 60MHz) 作为 lpi2c 主机时钟源 , */
8 #define LPI2C_CLOCK_SOURCE_SELECT (0U)
9 /* lpi2c 主机 时钟源的时钟分频因子 */
10 #define LPI2C_CLOCK_SOURCE_DIVIDER (5U)
11 /* 获取 lpi2c 时钟频率 LPI2C_CLK_ROOT = 60/(5+1) = 10MHz */
12 #define LPI2C_CLOCK_FREQUENCY ((CLOCK_GetFreq(kCLOCK_Usb1PllClk) / 8) /
13 (LPI2C_CLOCK_SOURCE_DIVIDER + 1U))
14
15 #define LPI2C_MASTER_CLOCK_FREQUENCY LPI2C_CLOCK_FREQUENCY
16 /************** 以下内容位于 bsp_i2c_eeprom.c 文件 *************/
17 /**
18 * @brief 初始化 EEPROM 使用的 I2C 外设
19 * @param 无
20 * @retval 无
21 */
22 static void EEPROM_I2C_ModeInit(void)
23 {
24 lpi2c_master_config_t masterConfig;
6 /************************ 第 1 部分 ****************************/
27 /* 配置时钟 LPI2C */
28 CLOCK_SetMux(kCLOCK_Lpi2cMux, LPI2C_CLOCK_SOURCE_SELECT);
29 CLOCK_SetDiv(kCLOCK_Lpi2cDiv, LPI2C_CLOCK_SOURCE_DIVIDER);
30
31 /************************ 第 2 部分 ****************************/
32 /* 给 masterConfig 赋值为以下默认配置 */
33 /*
34 * masterConfig.debugEnable = false;
35 * masterConfig.ignoreAck = false;
36 * masterConfig.pinConfig = kLPI2C_2PinOpenDrain;
37 * masterConfig.baudRate_Hz = 100000U;
38 * masterConfig.busIdleTimeout_ns = 0;
39 * masterConfig.pinLowTimeout_ns = 0;
40 * masterConfig.sdaGlitchFilterWidth_ns = 0;
41 * masterConfig.sclGlitchFilterWidth_ns = 0;
42 */
43 LPI2C_MasterGetDefaultConfig(&masterConfig);
44
45 /************************ 第 3 部分 ****************************/
46 /* 把默认波特率改为 I2C_BAUDRATE */
47 masterConfig.baudRate_Hz = EEPROM_I2C_BAUDRATE;
48
49 /************************ 第 4 部分 ****************************/
50 /* 使用以上配置初始化 LPI2C 外设 */
51 LPI2C_MasterInit(EEPROM_I2C_MASTER,
52 &masterConfig,
53 LPI2C_MASTER_CLOCK_FREQUENCY);
54 }
55
56 /************************ 第 5 部分 ****************************/
57 /**
58 * @brief I2C 初始化
59 * @param 无
60 * @retval 无
61 */
62 void I2C_EEPROM_Init(void)
63 {
64 /* 初始化各引脚 IOMUXC 相关 */
65 I2C_EEPROM_IOMUXC_MUX_Config();
66 I2C_EEPROM_IOMUXC_PAD_Config();
67
68 /* 初始化 I2C 外设工作模式 */
69 EEPROM_I2C_ModeInit();
70 }
调用库函数 CLOCK_SetMux
调用库函数 CLOCK_SetDiv 设置时钟分频因子
调用 LPI2C_MasterGetDefaultConfig 函数
对 baudRate_Hz 结构体成员重新赋值
调用库函数 LPI2C_MasterInit 初始化 LPI2C 外设
执行完本函数后,LPI2C 外设就完成了初始化。
EEPROM 根据标准 I2C 通讯协议写操作定义的一种页写入时序
上图为I2C 通讯中由主机发送给 EEPROM 的 SDA 信号
各个环节说明如下:
发送起始信号 START。
发送第 1 个字节:7 位的 I2C 设备地址(Device Address)加 1 位的写方向标志。
发送第 2 个字节:对于 I2C 通讯标准来说,这就是主机向从机发送的一个字节数据内容
发送第 3~10 个字节:在 Word Address 之后,可连续发送 n 个数据
发送停止信号 STOP。
1 /********* 第 1 部分,本内容位于 bsp_i2c_eeprom.h 文件 **********/
2 /* EEPROM 的总空间大小 */
3 #define EEPROM_SIZE 256
4 /* AT24C01/02 每页有 8 个字节 */
5 #define EEPROM_PAGE_SIZE 8
6 /* EEPROM 设备地址 */
7 #define EEPROM_ADDRESS_7_BIT (0xA0>>1)
8 #define EEPROM_WRITE_ADDRESS_8_BIT (0xA0)
9 #define EEPROM_READ_ADDRESS_8_BIT (0xA1)
10 /* EEPROM 内部存储单元地址的大小,单位:字节 */
11 #define EEPROM_INER_ADDRESS_SIZE 0x01
12
13 /************* 以下内容位于 bsp_i2c_eeprom.c 文件 ***************/
14 /************************ 第 2 部分 ****************************/
15 /**
16 * @brief 向 EEPROM 写入最多一页数据
17 * @note 调用本函数后必须调用 I2C_EEPROM_WaitStandbyState 进行等待
18 * @param ClientAddr:EEPROM 的 I2C 设备地址 (8 位地址 )
19 * @param WriteAddr: 写入的存储单元首地址
20 * @param pBuffer: 缓冲区指针
21 * @param NumByteToWrite: 要写的字节数,不可超过 EEPROM_PAGE_SIZE 定义的值
22 * @retval 1 不正常, 0 正常
23 */
24 uint32_t I2C_EEPROM_Page_Write( uint8_t ClientAddr,
25 uint8_t WriteAddr,
26 uint8_t* pBuffer,
27 uint8_t NumByteToWrite)
28 {
29 /************************ 第 3 部分 ****************************/
30 lpi2c_master_transfer_t masterXfer = {0};
31 status_t reVal = kStatus_Fail;
32
33 /************************ 第 4 部分 ****************************/
34 if (NumByteToWrite>EEPROM_PAGE_SIZE) {
35 EEPROM_ERROR("NumByteToWrite>EEPROM_PageSize\r\n");
36 return 1;
37 }
38
39 /************************ 第 5 部分 ****************************/
40 /* subAddress = WriteAddr, data = pBuffer 发送至从机
41 起始信号 start + 设备地址 slaveaddress(w 写方向 ) +
42 发送缓冲数据 tx data buffer + 停止信号 stop */
43
44 masterXfer.slaveAddress = (ClientAddr>>1);
45 masterXfer.direction = kLPI2C_Write;
46 masterXfer.subaddress = WriteAddr;
47 masterXfer.subaddressSize = EEPROM_INER_ADDRESS_SIZE;
48 masterXfer.data = pBuffer;
49 masterXfer.dataSize = NumByteToWrite;
50 masterXfer.flags = kLPI2C_TransferDefaultFlag;
51
52 /************************ 第 6 部分 ****************************/
53 reVal = LPI2C_MasterTransferBlocking(EEPROM_I2C_MASTER, &masterXfer);
54
55 if (reVal != kStatus_Success) {
56 return 1;
57 }
58 return 0;
59 }
第 1 部分。定义 EEPROM 相关的信息
第 2 部分。定义函数 I2C_EEPROM_Page_Write 封装页写入操作,这个函数包含四个输入参
数:
第 3 部分。使用 LPI2C 传输结构体类型定义一个 masterXfer 变量,
第 4 部分。确认要传输的字节数 NumByteToWrite 小于等于页大小 EEPROM_PAGE_SIZE,
否则函数直接返回退出。
第 5 部分。根据函数输入参数及 EEPROM 的特性对 masterXfer 变量赋值:
第 6 部分。调用库函数 LPI2C_MasterTransferBlocking
在写入通讯结束后,要先等待 EEPROM 内部擦写完毕再进行后续访问
1 /********* 第 1 部分,本内容位于 bsp_i2c_eeprom.h 文件 **********/
2 /* 等待超时时间 */
3 #define I2CT_FLAG_TIMEOUT ((uint32_t)0x100)
4 #define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
5
6 /************ 以下内容位于 bsp_i2c_eeprom.c 文件 ************/
7
8 /************************ 第 2 部分 ****************************/
9 /**
10 * @brief 用于等待 EEPROM 的内部写入时序,
11 * 在 I2C_EEPROM_Page_Write 函数后必须被调用
12 * @param ClientAddr: 设备地址( 8 位地址)
13 * @retval 等待正常为 0 ,等待超时为 1
14 */
15 uint8_t I2C_EEPROM_WaitStandbyState(uint8_t ClientAddr)
16 {
17 status_t lpi2c_status;
18 /* 等待超过 40*I2CT_LONG_TIMEOUT us 后认为超时 */
19 uint32_t delay_count = I2CT_LONG_TIMEOUT;
20
21 do {
22 /************************ 第 3 部分 ****************************/
23 /* 清除非应答标志,以便下一次检测 */
24 LPI2C_MasterClearStatusFlags(EEPROM_I2C_MASTER, kLPI2C_ MasterNackDetectFlag);
25
26 /* 对 EEPROM 发出写方向的寻址信号,以检测是否响应 */
27 lpi2c_status = LPI2C_MasterStart(EEPROM_I2C_MASTER, (ClientAddr>>1), kLPI2C_Write);
28
29 /* 必须等待至少 30us ,才会产生非应答标志 */
30 EEPROM_DELAY_US(40);
31
32 /************************ 第 4 部分 ****************************/
33 /* 检测 LPI2C MSR 寄存器的 NDF 标志,并且确认 delay_count 没减到 0 (减到 0 认
为超时,退出) */
34 } while (EEPROM_I2C_MASTER->MSR & kLPI2C_MasterNackDetectFlag
35 && delay_count-- );
36
37 /************************ 第 5 部分 ****************************/
38 /* 清除非应答标志,防止下一次通讯错误 */
39 LPI2C_MasterClearStatusFlags(EEPROM_I2C_MASTER, kLPI2C_MasterNackDetectFlag);
40
41 /* 产生停止信号,防止下次通讯出错 */
42 lpi2c_status = LPI2C_MasterStop(EEPROM_I2C_MASTER);
43 /* 必须等待至少 10us ,确保停止信号发送完成 */
44 EEPROM_DELAY_US(10);
45 /************************ 第 6 部分 ****************************/
46 /* 产生失败或前面的等待超时 */
47 if (delay_count == 0 || lpi2c_status != kStatus_Success) {
48 I2C_Timeout_Callback(3);
49 return 1;
50 }
51
52 return 0;
53 }
54
55 /************************ 第 7 部分 ****************************/
56 /**
57 * @brief IIC 等待超时调用本函数输出调试信息
58 * @param None.
59 * @retval 返回 0xff ,表示 IIC 读取数据失败
60 */
61 static uint32_t I2C_Timeout_Callback(uint8_t errorCode)
62 {
63 /* 在此处进行错误处理 */
64 EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode);
65 return 0xFF;
66 }·
向 I2C 总线发送 EEPROM 的设备地址并检测是否有响应
检 测 响 应 是 通 过 读 取 RT1052 的 LPI2C->MSR 寄 存 器 的 NDF 位即kLPI2C_MasterNackDetectFlag 标志实现的
每次调用完 I2C_EEPROM_Page_Write 函数传输完数据后,都必须调用这个 I2C_EEPROM_WaitStandbyState 函数等待 EEPROM 内部写入完成,再进行其它访问操作。
利用 EEPROM 的页写入方式,每次最多只可写入 8 个字节
要往第 4、5、6、7、8、9、10、11 这 8 个地址写入内容
不能只进行一次 8 字节的页写入,即发送内部地址 4 后面直接跟着 8 个数据
正确的操作是用两次页写入过程实现
只有当首地址是 8 字节对齐的时候,才能通过一次的页写入最多 8 个字节的数据。
I2C_EEPROM_Buffer_Write 函数对页写入函数 I2C_EEPROM_Page_Write 再进行一次封装
1 /************************ 第 1 部分 ****************************/
2 /**
3 * @brief 向 EEPROM 写入不限量的数据
4 * @param ClientAddr:EEPROM 的 I2C 设备地址 (8 位地址 )
5 * @param WriteAddr: 写入的存储单元首地址
6 * @param pBuffer: 缓冲区指针
7 * @param NumByteToWrite: 要写的字节数
8 * @retval 无
9 */
10 void I2C_EEPROM_Buffer_Write( uint8_t ClientAddr,
11 uint8_t WriteAddr,
12 uint8_t* pBuffer,
13 uint16_t NumByteToWrite)
14 {
15 /************************ 第 2 部分 ****************************/
16 uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
17 /* 后续要处理的字节数,初始值为 NumByteToWrite*/
18 uint8_t NumByteToWriteRest = NumByteToWrite;
19
20 /************************ 第 3 部分 ****************************/
21 /* 根据以下情况进行处理:
22 1. 写入的首地址是否对齐
23 2. 最后一次写入是否刚好写满一页 */
24 Addr = WriteAddr % EEPROM_PAGE_SIZE;
25 count = EEPROM_PAGE_SIZE - Addr;
26
27 /* 若 NumByteToWrite > count :
28 第一页写入 count 个字节,对其余字节再进行后续处理,
29 所以用 (NumByteToWriteRest = NumByteToWrite - count)
30 求出后续的 NumOfPage 和 NumOfSingle 进行处理。
31 若 NumByteToWrite < count :
32 即不足一页数据,直接用 NumByteToWriteRest = NumByteToWrite
33 求出 NumOfPage 和 NumOfSingle 即可 */
34 NumByteToWriteRest = (NumByteToWrite > count) ? (NumByteToWrite - count):NumByteToWrite;
37 /* 要完整写入的页数(不包括前 count 字节) */
38 NumOfPage = NumByteToWriteRest / EEPROM_PAGE_SIZE;
39 /* 最后一页要写入的字节数(不包括前 count 字节) */
40 NumOfSingle = NumByteToWriteRest % EEPROM_PAGE_SIZE;
41
42 /************************ 第 4 部分 ****************************/
43 /* NumByteToWrite > count 时,需要先往第一页写入 count 个字节
44 NumByteToWrite < count 时无需进行此操作 */
45 if (count != 0 && NumByteToWrite > count) {
46 I2C_EEPROM_Page_Write(ClientAddr, WriteAddr, pBuffer, count);
47 I2C_EEPROM_WaitStandbyState(ClientAddr);
48 WriteAddr += count;
49 pBuffer += count;
50 }
51
52 /************************ 第 5 部分 ****************************/
53 /* 处理后续数据 */
54 if (NumOfPage== 0 ) {
55 I2C_EEPROM_Page_Write(ClientAddr, WriteAddr, pBuffer, NumOfSingle);
56 I2C_EEPROM_WaitStandbyState(ClientAddr);
57 } else {
58 /************************ 第 6 部分 ****************************/
59 /* 后续数据大于一页 */
60 while (NumOfPage--) {
61 I2C_EEPROM_Page_Write(ClientAddr, WriteAddr, pBuffer, EEPROM_PAGE_
,→ SIZE);
62 I2C_EEPROM_WaitStandbyState(ClientAddr);
63 WriteAddr += EEPROM_PAGE_SIZE;
64 pBuffer += EEPROM_PAGE_SIZE;
65 }
66 /************************ 第 7 部分 ****************************/
67 /* 最后一页 */
68 if (NumOfSingle != 0) {
69 I2C_EEPROM_Page_Write(ClientAddr, WriteAddr, pBuffer,␣
,→ NumOfSingle);
70 I2C_EEPROM_WaitStandbyState(ClientAddr);
71 }
72 }
73 }
第 1 部分。函数包含四个输入参数
第 2 部分。定义一些变量
第 3 部分。根据输入参数求解以上各个变量的值
第 4 部分。按照前面的,先进行第一次页写入操作,写入 count 个字节
第 5 部分。
第 6 部分。
第 7 部分。
EEPROM 是支持随机访问的 (直接读写任意一个地址),也就是说使用页写入操作对任意一个地址写入一个字节数据是可以的,数据写入前不需要擦除整页。
EEPROM 读取数据是一个复合的 I2C 时序,它实际上包含一个写过程和一个读过程
EEPROM 会向主机返回从“存储器内部地址”开始的数据
1 /**
2 * @brief 从 EEPROM 里面读取一块数据
3 * @param ClientAddr:EEPROM 的 I2C 设备地址 (8 位地址 )
4 * @param pBuffer[out]: 存放从 EEPROM 读取的数据的缓冲区指针
5 * @param ReadAddr: 接收数据的 EEPROM 的地址
6 * @param NumByteToWrite: 要从 EEPROM 读取的字节数
7 * @retval 无
8 */
9 uint32_t I2C_EEPROM_BufferRead( uint8_t ClientAddr,
10 uint8_t ReadAddr,
11 uint8_t* pBuffer,
12 uint16_t NumByteToRead)
13 {
14 lpi2c_master_transfer_t masterXfer = {0};
15 status_t reVal = kStatus_Fail;
16
17 /* subAddress = ReadAddr, data = pBuffer 自从机处接收
18 起始信号 start + 设备地址 slaveaddress(w 写方向 ) + 子地址 subAddress +
19 重复起始信号 repeated start + 设备地址 slaveaddress(r 读方向 ) +
20 接收缓冲数据 rx data buffer + 停止信号 stop */
21 masterXfer.slaveAddress = (ClientAddr>>1);
22 masterXfer.direction = kLPI2C_Read;
23 masterXfer.subaddress = (uint32_t)ReadAddr;
24 masterXfer.subaddressSize = EEPROM_INER_ADDRESS_SIZE;
25 masterXfer.data = pBuffer;
26 masterXfer.dataSize = NumByteToRead;
27 masterXfer.flags = kLPI2C_TransferDefaultFlag;
28
29 reVal = LPI2C_MasterTransferBlocking(EEPROM_I2C_MASTER, &masterXfer);
30
31 if (reVal != kStatus_Success) {
32 return 1;
33 }
34 return 0;
35 }
传输结构体 masterXfer 的 direction 成员值为 kLPI2C_Read 表示读方向。
/* 测试读写的数据个数 */
2 #define EEPROM_TEST_NUM 256
3 /* 测试的起始地址 */
4 #define EEPORM_TEST_START_ADDR 0
5
6 /* 读写缓冲区 */
7 uint8_t EEPROM_Buffer_Write[256];
8 uint8_t EEPROM_Buffer_Read[256];
9
10 /**
11 * @brief I2C(AT24C02) 读写测试
12 * @param 无
13 * @retval 正常返回 0 ,不正常返回 1
14 */
15 uint8_t EEPROM_Test(void)
16 {
17 uint16_t i;
19 EEPROM_INFO("写入的数据");
20
21 for ( i=0; i<EEPROM_TEST_NUM; i++ ) { // 填充缓冲
22 EEPROM_Buffer_Write[i] = i;
23
24 PRINTF("0x%02X ", EEPROM_Buffer_Write[i]);
25 if ((i+1)%10 == 0 || i == (EEPROM_TEST_NUM-1))
26 PRINTF("\r\n");
27 }
28
29 // 将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中
30 I2C_EEPROM_Buffer_Write(EEPROM_WRITE_ADDRESS_8_BIT,
31 EEPORM_TEST_START_ADDR,
32 EEPROM_Buffer_Write,
33 EEPROM_TEST_NUM);
34
35 EEPROM_INFO("写成功");
36
37 EEPROM_INFO("读出的数据");
38 // 将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中
39 I2C_EEPROM_BufferRead(EEPROM_READ_ADDRESS_8_BIT,
40 EEPORM_TEST_START_ADDR,
41 EEPROM_Buffer_Read,
42 EEPROM_TEST_NUM);
43
44 // 将 I2c_Buf_Read 中的数据通过串口打印
45 for (i=0; i<EEPROM_TEST_NUM; i++) {
46 if (EEPROM_Buffer_Read[i] != EEPROM_Buffer_Write[i]) {
47 PRINTF("0x%02X ", EEPROM_Buffer_Read[i]);
48 RGB_LED_COLOR_RED;
49 EEPROM_ERROR("错误:I2C EEPROM 写入与读出的数据不一致");
50 return 1;
51 }
52 PRINTF("0x%02X ", EEPROM_Buffer_Read[i]);
53 if ((i+1)%10 == 0 || i == (EEPROM_TEST_NUM-1))
54 PRINTF("\r\n");
55 }
56
57 RGB_LED_COLOR_GREEN;
58 EEPROM_INFO("I2C(AT24C02) 读写测试成功");
59 return 0;
60 }
1 /**
2 * @brief 主函数
3 * @param 无
4 * @retval 无
5 */
6 int main(void)
7 {
8 /* 初始化内存保护单元 */
9 BOARD_ConfigMPU();
10 /* 初始化开发板引脚 */
11 BOARD_InitPins();
12 /* 初始化开发板时钟 */
13 BOARD_BootClockRUN();
14 /* 初始化调试串口 */
15 BOARD_InitDebugConsole();
16 /* 打印系统时钟 */
17 PRINTF("\r\n");
18 PRINTF("***** 欢迎使用 野火 i.MX RT1052 开发板 *****\r\n");
19 PRINTF("CPU: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_CpuClk));
20 PRINTF("AHB: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_AhbClk));
21 PRINTF("SEMC: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_SemcClk));
22 PRINTF("SYSPLL: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_SysPllClk));
23 PRINTF("SYSPLLPFD0: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_
,→ SysPllPfd0Clk));
24 PRINTF("SYSPLLPFD1: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_
,→ SysPllPfd1Clk));
25 PRINTF("SYSPLLPFD2: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_
,→ SysPllPfd2Clk));
26 PRINTF("SYSPLLPFD3: %d Hz\r\n", CLOCK_GetFreq(kCLOCK_
,→ SysPllPfd3Clk));
27
28 PRINTF("读写 EEPROM\r\n");
29
30 /* 初始化 LED 引脚 */
31 LED_GPIO_Config();
32
33 /* 初始化 EEPROM */
34 I2C_EEPROM_Init();
35
36 /* 进行写入测试 */
37 EEPROM_Test();
38
39 while (1) {
40 }
41 }