在嵌入式系统开发中,非易失性存储器的使用几乎无处不在。25XXX系列EEPROM作为Microchip的经典产品线,以其可靠的性能和简单的接口深受开发者青睐。这类器件采用SPI(Serial Peripheral Interface)协议通信,最高时钟频率可达20MHz,容量从1Kbit到1Mbit不等。
SPI本质上是一种四线制同步串行总线,包含以下信号线:
相比I2C等其他串行协议,SPI的主要优势在于:
实际项目中我发现,当通信速率超过1MHz时,SPI相比I2C的稳定性优势会非常明显。特别是在工业环境等存在电气干扰的场景下。
以dsPIC33FJ256GP710与25LC256(256Kbit)为例,基本连接方式如下:
code复制VCC ----| |---- VCC
| |
GND ----| |---- GND
| 25XXX |
SCK1 ---| SCK |
| |
SDO1 ---| SI |
| |
SDI1 ---| SO |
| |
RF6 ----| CS |
| |
VCC ----| WP |
| |
VCC ----| HOLD |
|________|
调试时常见的一个坑:如果发现无法写入数据,首先检查WP引脚电位。我曾遇到过因PCB设计错误导致WP浮空的情况,导致写入操作间歇性失败。
在dsPIC33F/PIC24F系列MCU上,SPI模块的初始化主要涉及以下寄存器:
c复制// SPI1控制寄存器1配置示例
SPI1CON1 = 0;
SPI1CON1bits.MSTEN = 1; // 主机模式
SPI1CON1bits.CKP = 0; // 时钟极性:空闲时为低电平
SPI1CON1bits.CKE = 1; // 时钟边沿:从活跃到空闲时采样
SPI1CON1bits.SMP = 0; // 输入数据采样时间
SPI1CON1bits.PPRE = 3; // 主预分频 1:1
SPI1CON1bits.SPRE = 6; // 辅预分频 2:1
// SPI1状态寄存器
SPI1STATbits.SPIEN = 1; // 使能SPI模块
25XXX系列EEPROM支持SPI模式0(CPOL=0, CPHA=0)和模式3(CPOL=1, CPHA=1)。模式0的时序特性如下:
实际测试发现,某些批次EEPROM对模式3的兼容性更好。当通信不稳定时,可以尝试切换模式。
SPI时钟频率需满足:
时钟分频计算公式:
code复制实际SPI时钟 = Fosc / (PPRE * SPRE)
其中PPRE和SPRE为预分频系数。
在执行任何写入操作前,必须先发送WREN指令:
c复制void EEPROM_WriteEnable(void) {
CS_LOW(); // 拉低片选
SPI1_ExchangeByte(0x06); // WREN操作码
CS_HIGH(); // 释放片选
}
时序要点:
c复制uint8_t EEPROM_ReadStatus(void) {
uint8_t status;
CS_LOW();
SPI1_ExchangeByte(0x05); // RDSR操作码
status = SPI1_ExchangeByte(0x00); // dummy字节
CS_HIGH();
return status;
}
状态寄存器关键位:
| 位 | 名称 | 功能描述 |
|---|---|---|
| 0 | WIP | 写操作进行中(1=忙) |
| 1 | WEL | 写使能锁存(1=使能) |
| 2 | BP0 | 块保护位 |
| 3 | BP1 | 块保护位 |
25XXX系列支持页写入,页大小随容量变化:
页写入函数示例:
c复制void EEPROM_PageWrite(uint16_t addr, uint8_t *data, uint8_t len) {
// 等待上次写入完成
while(EEPROM_ReadStatus() & 0x01);
// 写使能
EEPROM_WriteEnable();
// 发送写入指令
CS_LOW();
SPI1_ExchangeByte(0x02); // WRITE操作码
SPI1_ExchangeByte(addr >> 8); // 高地址字节
SPI1_ExchangeByte(addr & 0xFF); // 低地址字节
// 写入数据
for(uint8_t i=0; i<len; i++) {
SPI1_ExchangeByte(data[i]);
}
CS_HIGH();
}
重要限制:如果写入跨越页边界,地址会自动回卷到页起始位置。例如向25LC256的地址0x003F写入2字节,第二个字节会写到0x0000。
标准读取操作每次都需要重新发送地址,效率较低。25XXX支持连续读取模式:
c复制void EEPROM_SequentialRead(uint16_t addr, uint8_t *buf, uint16_t len) {
CS_LOW();
SPI1_ExchangeByte(0x03); // READ操作码
SPI1_ExchangeByte(addr >> 8); // 高地址字节
SPI1_ExchangeByte(addr & 0xFF); // 低地址字节
// 连续读取
for(uint16_t i=0; i<len; i++) {
buf[i] = SPI1_ExchangeByte(0x00);
}
CS_HIGH();
}
性能对比测试:
| 读取方式 | 1KB数据耗时(10MHz SPI) |
|---|---|
| 单字节读取 | 12.8ms |
| 连续读取 | 1.2ms |
通过状态寄存器的BP位可以实现不同范围的写保护:
c复制void EEPROM_SetBlockProtect(uint8_t level) {
EEPROM_WriteEnable();
CS_LOW();
SPI1_ExchangeByte(0x01); // WRSR操作码
SPI1_ExchangeByte(level << 2); // 设置BP位
CS_HIGH();
}
保护级别对照表:
| BP1 | BP0 | 保护范围(25LC256) |
|---|---|---|
| 0 | 0 | 无保护 |
| 0 | 1 | 高1/4(18000-1FFFF) |
| 1 | 0 | 高1/2(10000-1FFFF) |
| 1 | 1 | 全部(00000-1FFFF) |
可靠的EEPROM操作需要完善的错误处理:
c复制#define EEPROM_TIMEOUT 100 // 100ms超时
int EEPROM_SafeWrite(uint16_t addr, uint8_t *data, uint8_t len) {
uint32_t timeout = 0;
// 检查写入长度
if(len > 64) return -1;
// 等待上次操作完成
while(EEPROM_ReadStatus() & 0x01) {
if(timeout++ > EEPROM_TIMEOUT) return -2;
DelayMs(1);
}
// 执行写入
EEPROM_PageWrite(addr, data, len);
// 验证写入完成
timeout = 0;
while(EEPROM_ReadStatus() & 0x01) {
if(timeout++ > EEPROM_TIMEOUT) return -3;
DelayMs(1);
}
return 0; // 成功
}
EEPROM的典型擦写寿命为100万次,可通过以下方式延长使用寿命:
| 故障现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取数据全为0xFF | 1. CS信号异常 2. 器件未供电 |
1. 检查CS线路 2. 测量VCC电压 |
| 写入后数据不持久 | 1. 写周期未完成 2. WP引脚状态 |
1. 增加WIP等待时间 2. 检查WP连接 |
| 通信间歇性失败 | 1. 时钟频率过高 2. 信号干扰 |
1. 降低SPI时钟 2. 缩短走线长度 |
| 特定地址写入失败 | 块保护启用 | 检查状态寄存器BP位 |
MPLAB X IDE提供完善的仿真功能:
在开发初期,通过仿真可以快速验证SPI配置的正确性,避免硬件调试的盲目性。我通常会在硬件测试前完成所有基本功能的仿真验证,这能节省约40%的调试时间。