本帖最后由 abner_ma 于 2025-8-29 15:56 编辑
一、测评背景与目的
在嵌入式系统开发中,IIC(Inter-Integrated Circuit)接口因结构简单、占用引脚少等优势,被广泛应用于传感器、存储芯片等外设的通信。APM32E030R8T6 是一款基于 ARM Cortex-M0 + 内核的微控制器,具备丰富的外设资源,其 PF6 和 PF7 引脚可配置为 IIC 接口功能。FM24C02 则是一款常见的 2KB IIC 接口 EEPROM 存储芯片,常用于数据的掉电保存。 本次测评旨在验证 APM32E030R8T6 的 PF6、PF7 引脚作为 IIC 接口时,与 FM24C02 之间的通信功能是否正常,同时测试其读写性能、稳定性等关键指标,为后续嵌入式项目中该硬件组合的应用提供可靠依据。 硬件:
二、测评环境搭建(一)硬件环境1. APM32E030R Micro-EVB开发板主控芯片:APM32E030R8T6 微控制器,工作电压 3.3V,主频最高 48MHz。 2. 存储芯片:FM24C02是一款基于I²C总线接口的串行EEPROM芯片,容量为2Kbit,支持掉电后数据不丢失,非常适合用于存储关键配置信息、设备ID、校准数据等。它采用SOP8贴片封装,体积小巧,安装方便,适合各种空间受限的电路设计。它最大的亮点之一就是“非易失性存储”,也就是说,即使断电了,里面的数据也不会消失。这在很多需要长期保存参数的设备中非常关键,比如智能仪表、家电控制、工业设备等。而且它支持高频率的读写操作,稳定性强,寿命长,简直是“小身材,大能量”的代表! 3. 硬件连接: ◦ APM32E030R8T6 的 PF6 引脚(IIC_SCL)与 FM24C02 的 SCL 引脚相连,PF7 引脚(IIC_SDA)与 FM24C02 的 SDA 引脚相连。 ◦ 为保证 IIC 通信的稳定性,在 SCL 和 SDA 引脚上分别串联一个 4.7kΩ 的上拉电阻,将引脚电平拉至 3.3V。 ◦ 连接 APM32E030R8T6 和 FM24C02 的 GND 引脚,确保两者共地;为 FM24C02 提供 3.3V 工作电压,与 APM32E030R8T6 的工作电压保持一致。 ◦ 搭建调试环境,使用 板载DAP调试器连接 APM32E030R8T6 的调试接口(SWDIO 和 SWCLK),用于程序下载和调试;通过串口将 APM32E030R8T6 与电脑连接,用于输出测试数据和日志。
(二)软件环境 开发工具:使用 Keil MDK-ARM V5 开发环境,该工具支持 ARM Cortex-M 系列微控制器的程序开发、编译和调试,具备丰富的库函数和调试功能。 固件库:采用 APM32 官方提供的 APM32E030_SDK_V1.0.3库,该固件库包含了 IIC 外设的驱动函数、GPIO 引脚配置函数等,可简化程序开发流程,提高开发效率。 测试程序设计: 引脚初始化:配置 PF6 和 PF7 引脚为复用功能模式,将其映射为 IIC 接口的 SCL 和 SDA 引脚;同时配置引脚的输出模式、上拉 / 下拉电阻等参数,确保引脚工作在正确的电气状态。 IIC 外设初始化:设置 IIC 的通信速率(分别测试 100kHz 标准模式和 400kHz 快速模式)、地址模式(7 位地址模式)、时钟占空比等参数;使能 IIC 外设,准备进行通信。 FM24C02 读写函数:编写 FM24C02 的字节写入函数、字节读取函数、页写入函数和页读取函数。其中,字节读写函数用于单个字节数据的写入和读取,页读写函数用于连续多个字节数据的批量写入和读取(FM24C02 的页容量为 8 字节)。 测试逻辑设计:在主函数中,先对 FM24C02 进行初始化;然后执行写入操作,向 FM24C02 的指定地址写入测试数据(包括单个字节数据和多字节页数据);等待写入完成后,执行读取操作,从相同地址读取数据;最后将写入数据和读取数据进行比较,判断读写操作是否成功,并通过串口输出测试结果(成功 / 失败、写入数据、读取数据、通信速率等信息)。 IIC.C - #include "fm24c02_iic.h"
- #include "apm32e03x_gpio.h"
- #include "apm32e03x_i2c.h"
- #include "apm32e03x_rcc.h"
- // 定义IIC相关引脚和外设
- #define I2C_PORT I2C1
- #define I2C_SCL_PORT GPIOF
- #define I2C_SDA_PORT GPIOF
- #define I2C_SCL_PIN GPIO_PIN_6
- #define I2C_SDA_PIN GPIO_PIN_7
- // FM24C02设备地址和容量
- #define FM24C02_ADDR 0xA0 // 7位地址为0x50,写入时为0xA0,读取时为0xA1
- #define FM24C02_PAGE_SIZE 8 // 页大小为8字节
- #define FM24C02_MAX_ADDR 0xFF // 最大地址(256字节)
- /**
- * @brief 初始化IIC外设和相关GPIO引脚
- * @param 无
- * @retval 无
- */
- void IIC_Init(void)
- {
- GPIO_Config_T gpioConfig;
- I2C_Config_T i2cConfig;
-
- // 使能外设时钟
- RCC_EnableAPB1PeriphClock(RCC_APB1_PERIPH_I2C1);
- RCC_EnableAHBPeriphClock(RCC_AHB_PERIPH_GPIOF);
-
- // 配置SCL和SDA引脚为复用功能
- gpioConfig.mode = GPIO_MODE_AF_OD; // 开漏输出
- gpioConfig.speed = GPIO_SPEED_50MHz; // 高速
- gpioConfig.pin = I2C_SCL_PIN | I2C_SDA_PIN;
- GPIO_Config(I2C_SCL_PORT, &gpioConfig);
-
- // 配置I2C外设
- i2cConfig.mode = I2C_MODE_I2C; // I2C模式
- i2cConfig.dutyCycle = I2C_DUTYCYCLE_2; // 占空比1:2
- i2cConfig.ownAddr1 = 0x00; // 主机地址(不使用)
- i2cConfig.addrMode = I2C_ADDR_MODE_7BIT; // 7位地址模式
- i2cConfig.speed = I2C_SPEED_STANDARD; // 标准模式(100kHz)
- //i2cConfig.speed = I2C_SPEED_FAST; // 快速模式(400kHz)
- i2cConfig.ackEn = I2C_ACK_ENABLE; // 使能应答
-
- I2C_Config(I2C_PORT, &i2cConfig);
- I2C_Enable(I2C_PORT); // 使能I2C外设
- }
- /**
- * @brief 等待IIC操作完成
- * @param timeOut: 超时时间
- * @retval 成功返回0,失败返回1
- */
- uint8_t IIC_WaitForOperation(uint32_t timeOut)
- {
- while(timeOut--)
- {
- if(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_BUSY))
- return 0;
- }
- return 1; // 超时
- }
- /**
- * @brief 向FM24C02写入一个字节
- * @param addr: 写入地址(0x00~0xFF)
- * @param data: 要写入的数据
- * @retval 成功返回0,失败返回1
- */
- uint8_t FM24C02_WriteByte(uint8_t addr, uint8_t data)
- {
- // 等待总线空闲
- if(IIC_WaitForOperation(0xFFFF))
- return 1;
-
- // 发送起始信号
- I2C_GenerateStart(I2C_PORT);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_SB));
-
- // 发送设备地址和写命令
- I2C_TxData(I2C_PORT, FM24C02_ADDR);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_ADDR));
- I2C_ClearAddrFlag(I2C_PORT); // 清除地址标志
-
- // 发送内存地址
- I2C_TxData(I2C_PORT, addr);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_TXE));
-
- // 发送数据
- I2C_TxData(I2C_PORT, data);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_TXE));
-
- // 发送停止信号
- I2C_GenerateStop(I2C_PORT);
-
- // 等待写入完成
- for(uint32_t i = 0; i < 0xFFFF; i++);
-
- return 0;
- }
- /**
- * @brief 从FM24C02读取一个字节
- * @param addr: 读取地址(0x00~0xFF)
- * @param data: 存储读取数据的指针
- * @retval 成功返回0,失败返回1
- */
- uint8_t FM24C02_ReadByte(uint8_t addr, uint8_t *data)
- {
- if(data == NULL)
- return 1;
-
- // 等待总线空闲
- if(IIC_WaitForOperation(0xFFFF))
- return 1;
-
- // 发送起始信号
- I2C_GenerateStart(I2C_PORT);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_SB));
-
- // 发送设备地址和写命令(用于设置读取地址)
- I2C_TxData(I2C_PORT, FM24C02_ADDR);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_ADDR));
- I2C_ClearAddrFlag(I2C_PORT);
-
- // 发送要读取的内存地址
- I2C_TxData(I2C_PORT, addr);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_TXE));
-
- // 重新发送起始信号
- I2C_GenerateStart(I2C_PORT);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_SB));
-
- // 发送设备地址和读命令
- I2C_TxData(I2C_PORT, FM24C02_ADDR | 0x01);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_ADDR));
- I2C_ClearAddrFlag(I2C_PORT);
-
- // 禁止应答,准备接收最后一个字节
- I2C_DisableAck(I2C_PORT);
-
- // 读取数据
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_RXNE));
- *data = I2C_RxData(I2C_PORT);
-
- // 发送停止信号
- I2C_GenerateStop(I2C_PORT);
-
- // 重新使能应答
- I2C_EnableAck(I2C_PORT);
-
- return 0;
- }
- /**
- * @brief 向FM24C02写入一页数据(最多8字节)
- * @param addr: 起始地址(0x00~0xFF)
- * @param buf: 数据缓冲区
- * @param len: 数据长度(1~8)
- * @retval 成功返回0,失败返回1
- */
- uint8_t FM24C02_WritePage(uint8_t addr, uint8_t *buf, uint8_t len)
- {
- if(buf == NULL || len == 0 || len > FM24C02_PAGE_SIZE)
- return 1;
-
- // 检查是否超出页边界
- if(addr + len > ((addr & ~(FM24C02_PAGE_SIZE - 1)) + FM24C02_PAGE_SIZE))
- return 1;
-
- // 等待总线空闲
- if(IIC_WaitForOperation(0xFFFF))
- return 1;
-
- // 发送起始信号
- I2C_GenerateStart(I2C_PORT);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_SB));
-
- // 发送设备地址和写命令
- I2C_TxData(I2C_PORT, FM24C02_ADDR);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_ADDR));
- I2C_ClearAddrFlag(I2C_PORT);
-
- // 发送内存地址
- I2C_TxData(I2C_PORT, addr);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_TXE));
-
- // 发送数据
- for(uint8_t i = 0; i < len; i++)
- {
- I2C_TxData(I2C_PORT, buf[i]);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_TXE));
- }
-
- // 发送停止信号
- I2C_GenerateStop(I2C_PORT);
-
- // 等待写入完成
- for(uint32_t i = 0; i < 0xFFFF; i++);
-
- return 0;
- }
- /**
- * @brief 从FM24C02连续读取多个字节
- * @param addr: 起始地址(0x00~0xFF)
- * @param buf: 存储读取数据的缓冲区
- * @param len: 要读取的数据长度
- * @retval 成功返回0,失败返回1
- */
- uint8_t FM24C02_ReadBuffer(uint8_t addr, uint8_t *buf, uint16_t len)
- {
- if(buf == NULL || len == 0 || addr + len > FM24C02_MAX_ADDR + 1)
- return 1;
-
- // 等待总线空闲
- if(IIC_WaitForOperation(0xFFFF))
- return 1;
-
- // 发送起始信号
- I2C_GenerateStart(I2C_PORT);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_SB));
-
- // 发送设备地址和写命令(用于设置读取地址)
- I2C_TxData(I2C_PORT, FM24C02_ADDR);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_ADDR));
- I2C_ClearAddrFlag(I2C_PORT);
-
- // 发送要读取的内存地址
- I2C_TxData(I2C_PORT, addr);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_TXE));
-
- // 重新发送起始信号
- I2C_GenerateStart(I2C_PORT);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_SB));
-
- // 发送设备地址和读命令
- I2C_TxData(I2C_PORT, FM24C02_ADDR | 0x01);
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_ADDR));
- I2C_ClearAddrFlag(I2C_PORT);
-
- // 读取数据
- for(uint16_t i = 0; i < len; i++)
- {
- // 最后一个字节前禁止应答
- if(i == len - 1)
- I2C_DisableAck(I2C_PORT);
-
- while(!I2C_ReadStatusFlag(I2C_PORT, I2C_FLAG_RXNE));
- buf[i] = I2C_RxData(I2C_PORT);
- }
-
- // 发送停止信号
- I2C_GenerateStop(I2C_PORT);
-
- // 重新使能应答
- I2C_EnableAck(I2C_PORT);
-
- return 0;
- }
- /**
- * @brief 向FM24C02写入多个字节(可跨页)
- * @param addr: 起始地址(0x00~0xFF)
- * @param buf: 数据缓冲区
- * @param len: 数据长度
- * @retval 成功返回0,失败返回1
- */
- uint8_t FM24C02_WriteBuffer(uint8_t addr, uint8_t *buf, uint16_t len)
- {
- uint8_t bytesToWrite;
-
- if(buf == NULL || len == 0 || addr + len > FM24C02_MAX_ADDR + 1)
- return 1;
-
- while(len > 0)
- {
- // 计算当前页可写入的字节数
- bytesToWrite = FM24C02_PAGE_SIZE - (addr % FM24C02_PAGE_SIZE);
- if(bytesToWrite > len)
- bytesToWrite = len;
-
- // 写入一页数据
- if(FM24C02_WritePage(addr, buf, bytesToWrite) != 0)
- return 1;
-
- // 更新地址、缓冲区指针和剩余长度
- addr += bytesToWrite;
- buf += bytesToWrite;
- len -= bytesToWrite;
- }
-
- return 0;
- }
main函数 - #include "apm32e03x.h"
- #include "fm24c02_iic.h"
- #include <stdio.h>
- // 测试函数声明
- void SystemInit(void);
- void UART_Init(void);
- int fputc(int ch, FILE *f);
- int main(void)
- {
- uint8_t writeData = 0x5A;
- uint8_t readData;
- uint8_t pageData[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
- uint8_t readBuffer[8];
- uint8_t multiData[12] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B};
- uint8_t readMulti[12];
-
- // 系统初始化
- SystemInit();
- UART_Init(); // 初始化串口用于输出调试信息
- IIC_Init(); // 初始化IIC接口
-
- printf("FM24C02 IIC Communication Test Start\r\n");
-
- // 测试单个字节读写
- if(FM24C02_WriteByte(0x00, writeData) == 0)
- {
- printf("Write byte 0x%02X to address 0x00 success\r\n", writeData);
-
- if(FM24C02_ReadByte(0x00, &readData) == 0)
- {
- printf("Read byte from address 0x00: 0x%02X\r\n", readData);
- if(readData == writeData)
- printf("Single byte test: PASS\r\n");
- else
- printf("Single byte test: FAIL\r\n");
- }
- else
- {
- printf("Read byte failed\r\n");
- }
- }
- else
- {
- printf("Write byte failed\r\n");
- }
-
- // 测试页写入和读取
- if(FM24C02_WritePage(0x10, pageData, 8) == 0)
- {
- printf("Write page data to address 0x10 success\r\n");
-
- if(FM24C02_ReadBuffer(0x10, readBuffer, 8) == 0)
- {
- printf("Read page data: ");
- for(uint8_t i = 0; i < 8; i++)
- {
- printf("0x%02X ", readBuffer[i]);
- }
- printf("\r\n");
-
- // 验证数据
- uint8_t pass = 1;
- for(uint8_t i = 0; i < 8; i++)
- {
- if(readBuffer[i] != pageData[i])
- {
- pass = 0;
- break;
- }
- }
- if(pass)
- printf("Page test: PASS\r\n");
- else
- printf("Page test: FAIL\r\n");
- }
- else
- {
- printf("Read page data failed\r\n");
- }
- }
- else
- {
- printf("Write page data failed\r\n");
- }
-
- // 测试跨页写入和读取
- if(FM24C02_WriteBuffer(0x1C, multiData, 12) == 0)
- {
- printf("Write multi-page data to address 0x1C success\r\n");
-
- if(FM24C02_ReadBuffer(0x1C, readMulti, 12) == 0)
- {
- printf("Read multi-page data: ");
- for(uint8_t i = 0; i < 12; i++)
- {
- printf("0x%02X ", readMulti[i]);
- }
- printf("\r\n");
-
- // 验证数据
- uint8_t pass = 1;
- for(uint8_t i = 0; i < 12; i++)
- {
- if(readMulti[i] != multiData[i])
- {
- pass = 0;
- break;
- }
- }
- if(pass)
- printf("Multi-page test: PASS\r\n");
- else
- printf("Multi-page test: FAIL\r\n");
- }
- else
- {
- printf("Read multi-page data failed\r\n");
- }
- }
- else
- {
- printf("Write multi-page data failed\r\n");
- }
-
- printf("FM24C02 IIC Communication Test Complete\r\n");
-
- while(1)
- {
- // 主循环
- }
- }
三、功能测试(一)单个字节读写测试 测试步骤: 设定 FM24C02 的目标地址为 0x00(可选择任意有效地址,范围 0x00-0xFF)。 调用字节写入函数,向 0x00 地址写入一个字节的测试数据,例如 0x5A。 延时一段时间(FM24C02 的写入周期最大为 5ms,此处设置延时 10ms,确保写入操作完成)。 调用字节读取函数,从 0x00 地址读取数据。 比较写入数据(0x5A)和读取数据,若两者相等,则单个字节读写测试成功;否则失败。 重复上述测试过程 100 次,测试不同的目标地址(如 0x10、0x20、0x50、0xFF 等)和不同的测试数据(如 0x00、0xFF、0xAA、0x33 等),验证测试结果的一致性。 测试结果: 1. 在 100kHz 标准模式和 400kHz 快速模式下,经过 100 次不同地址和不同数据的测试,所有写入数据与读取数据均完全一致,单个字节读写功能正常,无数据错误或 通信失败的情况。 2.串口输出日志显示,每次读写操作均能在预期时间内完成,无超时或异常报错。 (二)多字节页读写测试 测试步骤: 设定 FM24C02 的起始地址为 0x10(该地址需满足页对齐要求,即起始地址的低 3 位为 0,因 FM24C02 页容量为 8 字节,页地址范围为 0x00-0x07、0x08-0x0F、0x10-0x17 等)。 准备一个 8 字节的测试数据数组,例如 {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}。 写入函数,将该数据数组写入起始地址为 0x10 的页中。 延时 10ms,等待页写入操作完成。 调用页读取函数,从起始地址为 0x10 的页中读取 8 字节数据,存储到另一个数据数组中。 逐字节比较写入数据数组和读取数据数组,若所有字节均相等,则多字节页读写测试成功;否则失败。 更换不同的起始页地址(如 0x20、0x30、0x70 等)和不同的测试数据数组(如全 0、全 1、递增数据、随机数据等),重复测试 100 次。 测试结果: 在两种通信速率下,100 次不同页地址和不同数据的测试均全部成功,写入的 8 字节数据与读取数据完全匹配,无数据丢失、错位或错误的情况。 即使在快速模式(400kHz)下,页读写操作依然稳定,未出现因通信速率过高导致的通信异常。
四、测评结论 本次对 APM32E030R8T6 的 PF6、PF7 引脚读写 IIC 接口 FM24C02 的测评结果表明:APM32E030R8T6硬件IIC功能还是非常可靠的!
|