在嵌入式系统开发中,EEPROM作为非易失性存储器被广泛用于存储配置参数、运行日志等关键数据。本文将详细介绍如何使用MC9S12G128微控制器的I2C接口驱动M24M02 EEPROM芯片。M24M02是STMicroelectronics推出的2Mbit串行EEPROM,采用I2C接口通信,具有高可靠性、低功耗等特点,非常适合汽车电子、工业控制等应用场景。
我曾在多个车载诊断设备项目中采用这套方案,实测证明其稳定性可满足-40℃~85℃的严苛工作环境要求。相比SPI接口的EEPROM,I2C方案虽然速度稍慢,但引脚占用少,布线简单,特别适合引脚资源紧张的应用。
选择M24M02主要基于以下几点考虑:
正确的硬件连接是通信基础,必须特别注意以下几点:
plaintext复制MC9S12G128 M24M02
------------ --------
SCL → SCL (PB2) # 时钟线需加上拉电阻(4.7kΩ)
SDA → SDA (PB3) # 数据线需加上拉电阻(4.7kΩ)
GND → GND # 必须共地
VCC → 3.3V # 电源电压需一致
WP → VCC # 接高电平关闭写保护
A0/A1/A2 → GND # 地址引脚配置(根据实际需求)
关键提示:I2C总线必须加上拉电阻,阻值根据总线电容和速度选择,通常4.7kΩ适用于大多数情况。布线时应尽量缩短走线长度,避免信号完整性问题。
MC9S12G128的I2C模块涉及几个关键寄存器:
IBFD (I2C Frequency Divider)
SCL频率 = BusClock / (mul × (SCL divider + 2))c复制IBFD = 0x17; // mul=1, SCL divider=23 → 100kHz
IBCR (I2C Control Register)
IBSR (I2C Status Register)
完整初始化函数应包含以下步骤:
c复制void I2C_Init(void) {
/* 1. 使能I2C模块时钟 */
ICSR |= ICSR_IBE_MASK;
/* 2. 配置时钟分频 */
IBFD = 0x17; // 100kHz @24MHz
/* 3. 配置控制寄存器 */
IBCR = IBCR_IBEN_MASK | // 使能I2C
IBCR_IBSWAI_MASK; // 等待模式关闭
/* 4. 清除所有状态标志 */
IBSR = 0x00;
}
经验之谈:初始化后建议延时10ms再开始通信,确保EEPROM完全上电就绪。我曾遇到因上电延时不足导致的首次通信失败问题。
M24M02的写操作有几点特殊要求:
优化后的写函数实现:
c复制void EEPROM_Write(uint32_t addr, uint8_t *data, uint16_t size) {
uint8_t devAddr = 0xA0 | ((addr >> 16) & 0x07); // 处理高地址位
while(size > 0) {
uint16_t chunk = 256 - (addr % 256); // 当前页剩余空间
if(chunk > size) chunk = size;
/* 发送起始条件 */
I2C_Start();
/* 发送设备地址 + 写标志 */
if(I2C_Write(devAddr | 0x00) != I2C_ACK) {
// 错误处理
break;
}
/* 发送内存地址(16位) */
I2C_Write((addr >> 8) & 0xFF); // 高字节
I2C_Write(addr & 0xFF); // 低字节
/* 写入数据 */
for(uint16_t i=0; i<chunk; i++) {
if(I2C_Write(data[i]) != I2C_ACK) {
// 错误处理
break;
}
}
/* 发送停止条件 */
I2C_Stop();
/* 更新参数 */
addr += chunk;
data += chunk;
size -= chunk;
/* 等待写入完成 */
Delay_ms(5);
}
}
读操作需要注意:
高效读函数实现:
c复制void EEPROM_Read(uint32_t addr, uint8_t *buf, uint16_t size) {
uint8_t devAddr = 0xA0 | ((addr >> 16) & 0x07);
/* 设置起始地址(伪写操作) */
I2C_Start();
I2C_Write(devAddr | 0x00); // 写模式
I2C_Write((addr >> 8) & 0xFF);
I2C_Write(addr & 0xFF);
/* 重新启动读操作 */
I2C_Start();
I2C_Write(devAddr | 0x01); // 读模式
/* 读取数据 */
for(uint16_t i=0; i<size; i++) {
buf[i] = I2C_Read(i == size-1); // 最后一字节发NACK
}
I2C_Stop();
}
避坑指南:读操作前必须确保上次写操作已完成。可以通过发送起始条件+设备地址检测ACK来判断EEPROM是否就绪,避免读取出错。
M24M02支持软件写保护功能,可保护特定页不被误写:
c复制#define EEPROM_WP_ENABLE 0x00
#define EEPROM_WP_DISABLE 0xFF
void EEPROM_SetWriteProtect(uint8_t mode) {
uint8_t cmd[2] = {0x00, mode}; // 写保护控制命令
I2C_Start();
I2C_Write(0xA0); // 设备地址
I2C_Write(cmd[0]); // 命令码
I2C_Write(cmd[1]); // 模式
I2C_Stop();
Delay_ms(5);
}
为确保数据完整性,建议实现CRC校验:
c复制uint16_t CRC16_Calculate(uint8_t *data, uint16_t length) {
uint16_t crc = 0xFFFF;
while(length--) {
crc ^= *data++ << 8;
for(uint8_t i=0; i<8; i++) {
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : crc << 1;
}
}
return crc;
}
bool EEPROM_Verify(uint32_t addr, uint8_t *data, uint16_t size) {
uint8_t *readBuf = malloc(size);
EEPROM_Read(addr, readBuf, size);
uint16_t crc1 = CRC16_Calculate(data, size);
uint16_t crc2 = CRC16_Calculate(readBuf, size);
free(readBuf);
return (crc1 == crc2);
}
推荐的项目结构:
code复制M24M02_Driver/
├── Src/
│ ├── main.c # 应用层代码
│ ├── i2c_driver.c # I2C底层驱动
│ └── eeprom.c # EEPROM功能封装
├── Inc/
│ ├── i2c_driver.h
│ ├── eeprom.h
│ └── config.h # 参数配置
└── Libs/
└── MC9S12G128_Driver/ # 芯片外设库
写操作优化:
错误处理增强:
c复制typedef enum {
EEPROM_OK = 0,
EEPROM_TIMEOUT,
EEPROM_NACK,
EEPROM_ARB_LOST,
EEPROM_BUS_ERROR
} EEPROM_Status;
EEPROM_Status EEPROM_WriteWithRetry(uint32_t addr, uint8_t *data, uint16_t size, uint8_t retry) {
while(retry--) {
if(EEPROM_Write(addr, data, size) == EEPROM_OK) {
return EEPROM_OK;
}
Delay_ms(10);
}
return EEPROM_ERROR;
}
中断驱动设计:
c复制#pragma interrupt_handler I2C_ISR
void I2C_ISR(void) {
if(IBSR & IBSR_IBIF_MASK) {
// 处理传输完成中断
IBSR_IBIF = 1; // 清除标志
}
}
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 通信无响应 | 1. 电源未接通 2. 上拉电阻缺失 3. 地址错误 |
1. 检查VCC和GND连接 2. 确认SCL/SDA有4.7kΩ上拉 3. 核对设备地址 |
| 只能读写部分数据 | 1. 页边界处理错误 2. 时序不满足 |
1. 检查地址计算逻辑 2. 确保写周期延时≥5ms |
| 随机数据错误 | 1. 电源噪声 2. 总线冲突 |
1. 增加电源去耦电容 2. 检查多主设备仲裁 |
逻辑分析仪抓包:
软件调试手段:
c复制void I2C_DebugPrint(void) {
printf("IBCR: 0x%02X\n", IBCR);
printf("IBSR: 0x%02X\n", IBSR);
printf("IBFD: 0x%02X\n", IBFD);
}
信号质量检查:
在实际项目中,我曾遇到因电源噪声导致EEPROM随机写入失败的问题。最终通过在VCC引脚添加10μF钽电容+0.1μF陶瓷电容组合解决。这也提醒我们,硬件设计同样重要。