You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌入式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字节),连续写入不能跨页超过页容量,否则会循环覆盖当前页数据。写入流程需包含:

  1. 地址合法性校验:确保起始地址+写入长度不超过EEPROM总容量
  2. 跨页请求拆分:将超长写入拆分为多页单次写入
  3. 写使能触发:每次写入前必须发送WREN指令解锁写操作
  4. 写周期等待:写入完成后需等待内部擦写完成(可通过延时或状态寄存器判断)

读取算法

读取逻辑相对简单:

  1. 地址合法性校验
  2. 发送读指令+起始地址
  3. 连续读取指定长度字节

三、结构体参数传递与写入函数实现

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 03:28:27