嵌入式SPI EEPROM无现成函数C驱动设计及函数指针实现咨询
SPI EEPROM 嵌入式C驱动设计方案
一、驱动整体结构设计
采用设备结构体+函数指针的封装方式,把EEPROM的硬件属性、底层SPI操作、上层读写接口整合在一起,实现驱动与硬件的解耦——更换MCU或SPI外设时,仅需替换底层操作函数指针即可,无需修改核心读写逻辑。
结构体定义示例:
#include <stdint.h> // EEPROM设备结构体 typedef struct EEPROM_Device { uint32_t address; // EEPROM设备地址(部分型号用硬件引脚配置,无地址的可设为0) uint32_t size; // EEPROM总容量(字节数) // 底层SPI硬件操作函数指针(需手动操作寄存器实现,禁用现成库函数) void (*spi_send_byte)(uint8_t byte); uint8_t (*spi_receive_byte)(void); void (*spi_cs_select)(void); void (*spi_cs_deselect)(void); // 上层驱动接口函数指针 int (*write)(struct EEPROM_Device *dev, uint32_t start_addr, const uint8_t *data, uint32_t len); int (*read)(struct EEPROM_Device *dev, uint32_t start_addr, uint8_t *data, uint32_t len); } EEPROM_Device;
二、核心读写算法说明
写入算法(必须处理页边界与写周期)
SPI EEPROM普遍有页写入限制(比如AT25系列页大小为256字节),连续写入不能跨页超过页容量,否则会循环覆盖当前页数据。写入流程需包含:
- 地址合法性校验:确保起始地址+写入长度不超过EEPROM总容量
- 跨页请求拆分:将超长写入拆分为多页单次写入
- 写使能触发:每次写入前必须发送WREN指令解锁写操作
- 写周期等待:写入完成后需等待内部擦写完成(可通过延时或状态寄存器判断)
读取算法
读取逻辑相对简单:
- 地址合法性校验
- 发送读指令+起始地址
- 连续读取指定长度字节
三、结构体参数传递与写入函数实现
1. 底层SPI操作函数示例(手动操作寄存器)
以下是基于32位MCU的底层实现模板(需根据你的硬件寄存器调整):
// SPI发送单个字节 void spi_send_byte(uint8_t byte) { // 等待发送缓冲区为空 while (!(SPI->SR & SPI_SR_TXE)); // 写入数据到SPI数据寄存器 SPI->DR = byte; // 等待发送完成,读取接收寄存器清空标志 while (!(SPI->SR & SPI_SR_RXNE)); (void)SPI->DR; } // SPI接收单个字节 uint8_t spi_receive_byte(void) { // 发送空字节触发接收 while (!(SPI->SR & SPI_SR_TXE)); SPI->DR = 0xFF; // 等待接收完成并返回数据 while (!(SPI->SR & SPI_SR_RXNE)); return (uint8_t)SPI->DR; } // 拉低片选引脚 void spi_cs_select(void) { GPIO->ODR &= ~GPIO_PIN_4; } // 拉高片选引脚 void spi_cs_deselect(void) { GPIO->ODR |= GPIO_PIN_4; }
2. 写入函数实现(结构体作为参数传入)
通过结构体指针dev,可以直接访问其内部的硬件属性、SPI操作函数:
// EEPROM写入函数 int eeprom_write(EEPROM_Device *dev, uint32_t start_addr, const uint8_t *data, uint32_t len) { // 参数合法性检查 if (!dev || !data || start_addr + len > dev->size) { return -1; } const uint32_t PAGE_SIZE = 256; // 根据你的EEPROM datasheet修改 uint32_t remaining_len = len; uint32_t current_addr = start_addr; const uint8_t *current_data = data; // 拆分跨页写入 while (remaining_len > 0) { // 计算当前页剩余可写入长度 uint32_t page_remaining = PAGE_SIZE - (current_addr % PAGE_SIZE); uint32_t write_len = (remaining_len < page_remaining) ? remaining_len : page_remaining; // 发送写使能指令 dev->spi_cs_select(); dev->spi_send_byte(0x06); // WREN指令 dev->spi_cs_deselect(); // 执行页写入 dev->spi_cs_select(); dev->spi_send_byte(0x02); // WRITE指令 // 发送24位地址(根据EEPROM容量调整,16位地址只需发前两个字节) dev->spi_send_byte((current_addr >> 16) & 0xFF); dev->spi_send_byte((current_addr >> 8) & 0xFF); dev->spi_send_byte(current_addr & 0xFF); // 发送数据 for (uint32_t i = 0; i < write_len; i++) { dev->spi_send_byte(current_data[i]); } dev->spi_cs_deselect(); // 等待写完成(这里用延时,也可以读状态寄存器优化) for (uint32_t i = 0; i < 5000; i++) { __NOP(); } // 更新剩余写入信息 remaining_len -= write_len; current_addr += write_len; current_data += write_len; } return 0; // 写入成功 }
3. 设备初始化与调用示例
// 初始化EEPROM设备 void eeprom_device_init(EEPROM_Device *dev) { dev->address = 0x00; dev->size = 32768; // 示例为32KB容量的EEPROM // 绑定底层SPI操作函数 dev->spi_send_byte = spi_send_byte; dev->spi_receive_byte = spi_receive_byte; dev->spi_cs_select = spi_cs_select; dev->spi_cs_deselect = spi_cs_deselect; // 绑定上层读写接口 dev->write = eeprom_write; // 此处可绑定read函数(实现逻辑类似,省略) } // 调用示例 int main(void) { EEPROM_Device my_eeprom; eeprom_device_init(&my_eeprom); uint8_t write_buf[] = {0x12, 0x34, 0x56}; // 从地址0x100开始写入3字节数据 int ret = my_eeprom.write(&my_eeprom, 0x100, write_buf, sizeof(write_buf)); if (ret == 0) { // 写入成功处理 } return 0; }
四、关键注意事项
- 地址长度适配:不同容量的EEPROM地址长度不同(如16KB为14位,64KB为16位),需根据 datasheet 调整地址发送的字节数。
- 状态寄存器优化:用读取状态寄存器的WIP位判断写完成,比固定延时更高效,可自行实现状态寄存器读取函数替换延时逻辑。
- 时序严格性:必须严格遵循SPI时序,操作前后正确控制CS引脚,避免总线冲突。
内容的提问来源于stack exchange,提问作者user22935108
相关产品推荐
相关产品推荐

