在嵌入式系统设计中,非易失性存储是一个基础但至关重要的需求。传统并行EEPROM虽然速度快,但占用大量I/O引脚,这在资源受限的微控制器系统中往往成为瓶颈。SPI串行EEPROM凭借其精简的四线制接口(SCK、MOSI、MISO、CS)、字节级读写灵活性和低至1.8V的工作电压,成为中小容量存储场景的理想选择。
Microchip的25系列SPI EEPROM(如25LC160B)提供从1Kbit到1Mbit的多种容量选择,支持最高10MHz时钟频率。与PIC18系列微控制器的搭配使用,可以构建出高性价比的嵌入式存储解决方案。本文将以PIC18F1220为例,详细解析如何通过GPIO模拟SPI时序实现完整的EEPROM读写控制。
图1所示的硬件连接采用了最简配置:
code复制PIC18F1220 25LC160B
RB3(SCK) -----> SCK (6)
RB4(MOSI) -----> SI (5)
RB5(MISO) <----- SO (2)
RA1(CS) -----> CS (1)
特别注意三个关键设计细节:
当使用10MHz晶振驱动PIC18F1220时,GPIO模拟的SPI时序需满足25LC160B的规格要求:
在代码实现时,通过插入NOP指令确保时序裕量。若提高主频,必须重新计算延时周期。
通过GPIO模拟SPI模式0(CPOL=0, CPHA=0)的典型代码结构:
c复制#define CS_LOW() LATAbits.LATA1 = 0
#define CS_HIGH() LATAbits.LATA1 = 1
uint8_t SPI_Transfer(uint8_t data) {
uint8_t recv = 0;
for(uint8_t i=0; i<8; i++) {
LATBbits.LATB4 = (data & 0x80) ? 1 : 0; // MSB first
data <<= 1;
__delay_us(0.1); // 满足建立时间
LATBbits.LATB3 = 1; // SCK上升沿
recv = (recv << 1) | PORTBbits.RB5;
__delay_us(0.1);
LATBbits.LATB3 = 0; // SCK下降沿
}
return recv;
}
25LC160B支持的标准指令:
c复制#define CMD_READ 0x03
#define CMD_WRITE 0x02
#define CCMD_WREN 0x06
#define CMD_WRDI 0x04
#define CMD_RDSR 0x05
#define CMD_WRSR 0x01
写操作前必须发送WREN指令:
c复制void EEPROM_WriteEnable(void) {
CS_LOW();
SPI_Transfer(CMD_WREN);
CS_HIGH();
__delay_us(10); // 确保指令完成
}
带地址的写入时序实现:
c复制void EEPROM_WriteByte(uint16_t addr, uint8_t data) {
// 先使能写操作
EEPROM_WriteEnable();
CS_LOW();
SPI_Transfer(CMD_WRITE);
SPI_Transfer((uint8_t)(addr >> 8)); // 高地址
SPI_Transfer((uint8_t)addr); // 低地址
SPI_Transfer(data);
CS_HIGH();
// 等待写入完成
while(EEPROM_IsBusy());
}
通过读取状态寄存器(RDSR)检测WIP位(bit 0):
c复制uint8_t EEPROM_ReadStatus(void) {
CS_LOW();
SPI_Transfer(CMD_RDSR);
uint8_t status = SPI_Transfer(0xFF);
CS_HIGH();
return status;
}
bool EEPROM_IsBusy(void) {
return (EEPROM_ReadStatus() & 0x01);
}
25LC160B支持32字节页写入,显著提升批量数据效率:
c复制void EEPROM_WritePage(uint16_t startAddr, uint8_t *data) {
EEPROM_WriteEnable();
CS_LOW();
SPI_Transfer(CMD_WRITE);
SPI_Transfer((uint8_t)(startAddr >> 8));
SPI_Transfer((uint8_t)startAddr);
for(uint8_t i=0; i<32; i++) {
SPI_Transfer(data[i]);
}
CS_HIGH();
while(EEPROM_IsBusy());
}
利用地址自动递增特性实现高效连续读取:
c复制void EEPROM_ReadBuffer(uint16_t startAddr, uint8_t *buf, uint16_t len) {
CS_LOW();
SPI_Transfer(CMD_READ);
SPI_Transfer((uint8_t)(startAddr >> 8));
SPI_Transfer((uint8_t)startAddr);
for(uint16_t i=0; i<len; i++) {
buf[i] = SPI_Transfer(0xFF);
}
CS_HIGH();
}
在实际项目中需特别注意:
健壮的实现应包含:
c复制#define EEPROM_TIMEOUT 100 // 10ms超时
bool EEPROM_WaitReady(void) {
uint16_t timeout = 0;
while(EEPROM_IsBusy() && (timeout++ < EEPROM_TIMEOUT)) {
__delay_us(100);
}
return (timeout < EEPROM_TIMEOUT);
}
通过实测发现以下优化手段可提升吞吐量:
一个优化后的页写入示例:
c复制void EEPROM_FastWritePage(uint16_t addr, uint8_t *data) {
CS_LOW();
SPI_Transfer(CMD_WREN);
CS_HIGH();
CS_LOW();
SPI_Transfer(CMD_WRITE);
SPI_Transfer(addr >> 8);
SPI_Transfer(addr & 0xFF);
for(uint8_t i=0; i<32; ) {
SPI_Transfer(data[i++]);
if((i&0x07)==0) __delay_us(2); // 每8字节插入小延时
}
CS_HIGH();
}
通过本文介绍的软硬件设计方法,开发者可以快速构建可靠的SPI EEPROM存储子系统。这套方案已成功应用于工业传感器数据记录、设备参数存储等多种场景,经受住了长期运行考验。