# 07.01_EEPROM-读写

## 测试步骤

使用芯片内置EEPROM模块对EEPROM进行操作。 写入完成后从 eeprom 里读回来。 判断写入的数据和读回来的是否一致。

## 实测数据

测试成功

## 参考代码

07.01_EEPROM_ReadWriteTest.c

    /**
     * @brief EEPROM-读写
     * @details 使用芯片内置EEPROM模块对EEPROM进行操作。
     * 写入完成后从 eeprom 里读回来。
     * 判断写入的数据和读回来的是否一致。
     */
    #include "lks32mc09x_eeprom.h"
    #include "lks32mc09x_gpio.h"
    void SoftDelaymS(uint32_t n);
    volatile uint32_t testflg = 0; // 测试状态，0空闲 1进行中 2写入数据&读回数据一致 3写入数据&读回数据不一致
    uint8_t read_data[32];
    uint8_t read_data1[32];
    uint8_t write_data[32] = {0};
    #define EEPROMADDR 0xa0
    /**
     * @brief 主函数，用于测试HSI时钟输出及GPIO翻转
     */
    int main(void)
    {
        GPIO_Config(GPIO0, 0, GPIO_Mode_OUT, GPIO_AF_GPIO);
        GPIO_Config(GPIO2, 1, GPIO_Mode_IO, GPIO_AF_I2C);
        GPIO_Config(GPIO2, 2, GPIO_Mode_IO, GPIO_AF_I2C);
        EEPROM_InitDefault(); // 初始化时会自动使能
        for (;;)
        {
            if (testflg == 1)
            {
                for (int len = 1; len <= 32; len++) // 从1个数据到32个数据进行测试，以确保不同数据长度下i2c通信是否正常
                {
                    for (int i = 0; i < len; i++)
                    {
                        write_data[i] = i + len; // 确保每次写入的数据都不一样
                    }
                    EEPROM_WriteData(EEPROMADDR, 0, write_data, len);
                    SoftDelaymS(10);
                    EEPROM_ReadData(EEPROMADDR, 0, read_data, len);
                    for (int i = 0; i < len; i++)
                    {
                        if (read_data[i] != write_data[i])
                        {
                            testflg = 3;
                        }
                    }
                }
                if (testflg != 3)
                {
                    testflg = 2; // 测试完成，并且没有出现错误
                }
            }
        }
    }

## 使用到的库函数

库函数部分代码

    #define EEPROM_CFG REG32(0x40048000)
    #define EEPROM_RDATA REG32(0x40048004)
    #define GPIO_AF_I2C 6
    #define REG_RESET(reg,mask) reg &= ~(mask)
    #define REG_SET(reg,mask) reg |= (mask)
    #define REG_WRITE(reg,mask) reg = (mask)
    #define SYS0 ((SYS_TypeDef *)(SYS_BASE))
    #define SYS_MODULE_EEPROM BIT26
    #define SYS_MODULE_GPIO BIT11
    /**
     * @brief GPIO功能配置结构体句柄
     */
    typedef struct GPIO_InitTypeDef {
        uint32_t GPIO_Pin;
        GPIO_Mode_TypeDef GPIO_Mode;
        GPIO_PuPd_TypeDef GPIO_PuPd;
        uint32_t GPIO_PODEna;
        uint32_t GPIO_PFLT;
    };
    /**
     * @brief GPIO模式选择枚举
     */
    typedef enum GPIO_Mode_TypeDef {
        GPIO_Mode_IN = 0,
        GPIO_Mode_OUT = 1,
        GPIO_Mode_ANA = 2,
        GPIO_Mode_IO = 3,
    }} {enum_name};
    /**
     * @brief  GPIO配置函数
     * @param  GPIOx: GPIO端口
     * @param  GPIO_PinSource: GPIO引脚
     * @param  mode: GPIO模式
     * @param  GPIO_AF_x: GPIO复用功能选择
     */
    void GPIO_Config(GPIO_TypeDef *GPIOx, uint32_t GPIO_PinSource, GPIO_Mode_TypeDef mode, uint32_t GPIO_AF_x)
    {
        GPIO_PinAFConfig(GPIOx, GPIO_PinSource, GPIO_AF_x);
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_StructInit(&GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin  = 1 << GPIO_PinSource;
        GPIO_InitStructure.GPIO_Mode = mode;
        if (GPIO_AF_x == GPIO_AF_I2C)
        {
            GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
        }
        else
        {
            __NOP();
        }
        GPIO_Init(GPIOx, &GPIO_InitStructure);
    }
    /**
     * @brief EEPROM使能
     */
    void EEPROM_Enable(void)
    {
        SYS_ModuleClockCmd(SYS_MODULE_EEPROM, ENABLE);
    }
    /**
     * @brief 初始化GPIO
     * @param GPIOx GPIO模块指针
     * @param GPIO_InitStruct 指向包含初始化参数的GPIO_InitTypeDef结构体
     */
    void GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_InitStruct)
    {
        // 使能GPIO时钟
        SYS_ModuleClockCmd(SYS_MODULE_GPIO, ENABLE);

        // 配置引脚模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IN)
        {
            GPIOx->PIE |= GPIO_InitStruct->GPIO_Pin;  // 打开输入使能
            GPIOx->POE &= ~GPIO_InitStruct->GPIO_Pin; // 关闭输出使能
            GPIOx->PUE &= ~GPIO_InitStruct->GPIO_Pin; // 关闭上拉使能
        }
        else if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT)
        {
            GPIOx->PIE &= ~GPIO_InitStruct->GPIO_Pin; // 关闭输入使能
            GPIOx->POE |= GPIO_InitStruct->GPIO_Pin;  // 使能输出
        }
        else if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IO)
        {
            GPIOx->PIE |= GPIO_InitStruct->GPIO_Pin; // 使能输入
            GPIOx->POE |= GPIO_InitStruct->GPIO_Pin; // 使能输出
        }
        else // GPIO_Mode_ANA
        {
            GPIOx->PIE &= ~GPIO_InitStruct->GPIO_Pin; // 关闭输入使能
            GPIOx->POE &= ~GPIO_InitStruct->GPIO_Pin; // 关闭输出使能
            GPIOx->PUE &= ~GPIO_InitStruct->GPIO_Pin; // 关闭上拉使能
        }

        if (GPIO_InitStruct->GPIO_PuPd == GPIO_PuPd_UP)
        {
            GPIOx->PUE |= GPIO_InitStruct->GPIO_Pin; // 使能上拉
        }
        else
        {
            GPIOx->PUE &= ~GPIO_InitStruct->GPIO_Pin; // 关闭上拉
        }
        // 配置开漏使能
        if (GPIO_InitStruct->GPIO_PODEna)
        {
            GPIOx->PODE |= GPIO_InitStruct->GPIO_Pin;
        }
        else
        {
            GPIOx->PODE &= ~GPIO_InitStruct->GPIO_Pin;
        }

        // 配置滤波使能
        if (GPIO_InitStruct->GPIO_PFLT)
        {
            GPIOx->PFLT |= GPIO_InitStruct->GPIO_Pin;
        }
        else
        {
            GPIOx->PFLT &= ~GPIO_InitStruct->GPIO_Pin;
        }
    }
    /**
     * @brief 初始化GPIO结构体为默认值
     * @param GPIO_InitStruct 指向要初始化的GPIO_InitTypeDef结构体
     */
    void GPIO_StructInit(GPIO_InitTypeDef *GPIO_InitStruct)
    {
        for (int i = 0; i < sizeof(GPIO_InitTypeDef); i++)
        {
            ((uint8_t *)GPIO_InitStruct)[i] = 0;
        }
    }
    /**
     * @brief 初始化为默认配置，IO不交换，波特率100k，地址数据长度16位，读写时响应中断
     */
    void EEPROM_InitDefault(void)
    {
        EEPROM_Enable();
        EEPROM_CFG = BIT16 | BIT9 | (0xa0 << 1) | 0;
    }
    /**
     * @brief 向eeprom写入数据
     * @param eeprom_addr eeprom数据地址
     * @param data 要写入的数据
     */
    void EEPROM_Write(uint16_t eeprom_addr, uint8_t data)
    {
        if (eeprom_addr > 32767)
        {
            eeprom_addr = 32767;
        }
        else
        {
            __NOP();
        }
        if (EEPROM_CFG & BIT16)
        {
            do
            {
                REG8(eeprom_addr + 0x40040000) = data;
                while (EEPROM_CFG & BIT17);
            } while (EEPROM_CFG & BIT11); // 收到NACK则自动重发
        }
        else
        {
            REG8(eeprom_addr + 0x40040000) = data; // 先触发一次写操作
            do
            {
                REG8(eeprom_addr + 0x40040000) = data; // 这里的写操作时，mcu会暂停运行，等待上一个写操作完成
            } while (EEPROM_CFG & BIT11); // 这里判断的是上一次的写操作是否收到nack
        }
    }
    /**
     * @brief 向eeprom写入数据(多字节)，09上eeprom本身不支持多字节模式，这里用n个单次读写实现，执行时间为len*5mS（典型值）
     * @param eepromaddr eeprom模块设备地址，例如0xa0
     * @param taddr eeprom数据的起始地址
     * @param data 要写入的数据地址
     * @param len 要写入的长度
     */
    void EEPROM_WriteData(uint8_t eepromaddr, u16 taddr, u8 *data, uint32_t len)
    {
        uint16_t i;
        eepromaddr &= 0xfe;
        EEPROM_CFG = (EEPROM_CFG & (~0x1fc)) | (eepromaddr << 1);
        for (i = 0; i < len; i++)
        {
            EEPROM_Write(taddr + i, data[i]);
        }
    }
    /**
     * @brief 数字模块时钟使能
     * @param nModule 模块编号
     * @param state 使能或禁用状态
     */
    void SYS_ModuleClockCmd(uint32_t nModule, uint8_t state)
    {
        REG_WRITE(SYS0->PROTECT, 0x7a83);
        if (state)
        {
            REG_SET(SYS0->CLK_FEN, nModule);
        }
        else
        {
            REG_RESET(SYS0->CLK_FEN, nModule);
        }
        REG_WRITE(SYS0->PROTECT, 0);
    }
    /**
     * @brief 读取eeprom内数据
     * @param eeprom_addr eeprom数据地址
     * @return 数据
     */
    uint8_t EEPROM_Read(uint16_t eeprom_addr)
    {
        uint8_t val;
        if (eeprom_addr > 32767)
        {
            eeprom_addr = 32767;
        }
        else
        {
            __NOP();
        }
        while (EEPROM_CFG & BIT17); // 等待之前的操作结束（如果有的话）
        do
        {
            val = REG8(eeprom_addr | 0x40040000);
            while (EEPROM_CFG & BIT17);
        } while (EEPROM_CFG & BIT11);
        val = EEPROM_RDATA;
        return val;
    }
    /**
     * @brief 读取eeprom内数据，09上eeprom本身不支持多字节模式，这里用n个单次读写实现
     * @param eepromaddr eeprom模块设备地址，例如0xa0
     * @param taddr eeprom数据的起始地址
     * @param data 存放数据的地址
     * @param len 数据长度
     */
    void EEPROM_ReadData(uint8_t eepromaddr, u16 taddr, u8 *data, uint32_t len)
    {
        uint16_t i;
        eepromaddr &= 0xfe;
        EEPROM_CFG = (EEPROM_CFG & (~0x1fc)) | (eepromaddr << 1);
        for (i = 0; i < len; i++)
        {
            data[i] = EEPROM_Read(taddr + i);
        }
    }
    /**
     * @brief 配置GPIO引脚复用功能
     * @param GPIOx GPIO模块指针
     * @param GPIO_PinSource 引脚源
     * @param GPIO_AF 复用功能选择
     */
    void GPIO_PinAFConfig(GPIO_TypeDef *GPIOx, uint32_t GPIO_PinSource, uint32_t GPIO_AF)
    {
        uint8_t offset;
        uint8_t pins = GPIO_PinSource >> 2;
        offset       = ((GPIO_PinSource & 0x3) * 4);
        switch (pins)
        {
            case 0:
                GPIOx->F3210 = (GPIOx->F3210 & ~(0xf << offset)) | (GPIO_AF << offset);
                break;
            case 1:
                GPIOx->F7654 = (GPIOx->F7654 & ~(0xf << offset)) | (GPIO_AF << offset);
                break;
            case 2:
                GPIOx->FBA98 = (GPIOx->FBA98 & ~(0xf << offset)) | (GPIO_AF << offset);
                break;
            case 3:
                GPIOx->FFEDC = (GPIOx->FFEDC & ~(0xf << offset)) | (GPIO_AF << offset);
                break;
            default:
                break;
        }
    }