# 13.01_I2C-读写EEPROM

## 测试步骤

先后向eeprom里写入两次数据， 写入完成后从 eeprom 里都回来 判断写入的数据和读回来的是否一致

## 实测数据

测试通过，回读数据一致

## 参考代码

13.01_I2C_MasterSend.c

    /**
     * @brief I2C-读写EEPROM
     * @details
     * 先后向eeprom里写入两次数据，
     * 写入完成后从 eeprom 里都回来
     * 判断写入的数据和读回来的是否一致
     */
    #include "lks32mc09x_gpio.h"
    #include "lks32mc09x_i2c.h"
    void WriteData(uint8_t eepromaddr, u16 taddr, u8 *data, uint32_t len);
    void ReadData(uint8_t eepromaddr, u16 taddr, u8 *data, uint32_t len);
    void SoftDelaymS(uint32_t n);
    volatile uint32_t testflg = 0; // 测试状态，0空闲 1进行中 2写入数据&读回数据一致 3写入数据&读回数据不一致
    volatile uint32_t errline = 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, GPIO_PinSource_3, GPIO_Mode_IO, GPIO_AF_I2C);
        GPIO_Config(GPIO0, GPIO_PinSource_4, GPIO_Mode_IO, GPIO_AF_I2C);
        {
            I2C_InitTypeDef I2C_InitStruct;
            I2C_StructInit(&I2C_InitStruct);

            I2C_InitStruct.ADRCMP = DISABLE;         //  I2C 硬件地址比较使能开关，只有在 DMA 模式下开启才有效。
            I2C_InitStruct.MST_MODE = ENABLE;        //  I2C 主模式使能
            I2C_InitStruct.SLV_MODE = DISABLE;       //  I2C 从模式使能
            I2C_InitStruct.BaudRate = 10000;         //  I2C 波特率
            I2C_InitStruct.IE = ENABLE;              //  I2C 中断使能
            I2C_InitStruct.TC_IE = ENABLE;           //  I2C 数据传输完成中断使能
            I2C_InitStruct.BUS_ERR_IE = ENABLE;      //  I2C 总线错误事件中断使能
            I2C_InitStruct.STOP_IE = ENABLE;         //  I2C STOP 事件中断使能
            I2C_InitStruct.BURST_NACK = ENABLE;      //  I2C 传输，NACK 事件中断使能
            I2C_InitStruct.BURST_ADDR_CMP = DISABLE; //  I2C 传输，硬件地址匹配中断使能
            I2C_Init(I2C0, &I2C_InitStruct);
        }
        NVIC_EnableIRQ(I2C0_IRQn);      /* 使能I2C中断*/
        NVIC_SetPriority(I2C0_IRQn, 1); /* I2C中断优先级配置*/
        __enable_irq();                 /* 开启总中断 */

        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; // 确保每次写入的数据都不一样
                    }
                    WriteData(EEPROMADDR, 0, write_data, len);
                    SoftDelaymS(10);
                    ReadData(EEPROMADDR, 0, read_data, len);
                    for (int i = 0; i < len; i++)
                    {
                        if (read_data[i] != write_data[i])
                        {
                            if (errline == 0)
                            {
                                errline = i;
                            }
                            testflg = 3;
                        }
                    }
                }
                if (testflg != 3)
                {
                    testflg = 2; // 测试完成，并且没有出现错误
                }
            }
        }
    }

    void WriteData(uint8_t eepromaddr, u16 taddr, u8 *data, uint32_t len)
    {
        int i;
        static uint8_t txdata[34];
        if (len > 32) // 一次最多写32个
        {
            len = 32;
        }
        txdata[0] = taddr >> 8;
        txdata[1] = taddr;
        for (i = 0; i < len; i++)
        {
            txdata[i + 2] = data[i];
        }
        for (i = 0; i < 10; i++) // 如果收到nack 则最多尝试发送10次
        {
            I2C_TableSend(I2C0, eepromaddr, txdata, len + 2);
            while (I2C_TableState(I2C0))
                ; // 等待发送完成
            if (I2C_TableAck(I2C0) == 0)
            {
                return;
            }
        }
        return;
    }
    void ReadData(uint8_t eepromaddr, u16 taddr, u8 *data, uint32_t len)
    {
        uint8_t txdata[2];
        int i;
        uint16_t val;
        for (i = 0; i < 10; i++) // 如果收到nack 则最多尝试发送10次
        {
            txdata[0] = taddr >> 8;
            txdata[1] = taddr;
            I2C_TableSend(I2C0, eepromaddr, txdata, 2); // 写地址
            I2C_TableRead(I2C0, eepromaddr, data, len); // 读数据
            while (I2C_TableState(I2C0))
                ; // 等待发送完成
            if (I2C_TableAck(I2C0) == 0)
            {
                return;
            }
        }
        return;
    }
    void I2C0_IRQHandler(void)
    {
        I2C_TableIrq(I2C0);
    }

## 使用到的库函数

库函数部分代码

    #define GPIO_AF_I2C 6
    #define I2C I2C0
    #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_CLK_CFG_CLK_DIV_MASK (uint32_t)(0xFF << SYS_CLK_CFG_CLK_DIV_POS)
    #define SYS_CLK_CFG_CLK_DIV_POS 0
    #define SYS_CLK_CFG_CLK_SEL_MASK (uint32_t)(0x3 << SYS_CLK_CFG_CLK_SEL_POS)
    #define SYS_CLK_CFG_CLK_SEL_POS 8
    #define SYS_MODULE_GPIO BIT11
    #define SYS_MODULE_I2C0 BIT1
    /**
     * @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};
    typedef struct I2C_InitTypeDef {
        uint32_t ADRCMP;
        uint32_t ADDR;
        uint32_t MST_MODE;
        uint32_t SLV_MODE;
        uint32_t BUSRT_EN;
        uint32_t BaudRate;
        uint32_t IE;
        uint32_t TC_IE;
        uint32_t BUS_ERR_IE;
        uint32_t STOP_IE;
        uint32_t BURST_NACK;
        uint32_t BURST_ADDR_CMP;
    };
    /**
     * @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 I2C_SendTable和I2C_ReadTable对应的中断处理函数
     */
    void I2C_TableIrq(I2C_TypeDef *I2Cx)
    {

        if (I2Cx->SCR & BIT1)
        {
            I2C_TableVariable[0].nackflg = 1;
        }
        else
        {
            __NOP();
        }
        if (I2Cx->SCR & BIT5)
        {
            I2C_TableVariable[0].state = 0;
            I2Cx->SCR                  = 0;
        }
        else if (I2C_TableVariable[0].state == 1)
        {
            if (I2Cx->SCR & BIT3)
            {
                I2Cx->SCR = 0;
            }
            else
            {
                I2Cx->DATA                   = 0xff;
                *I2C_TableVariable[0].rxdata = I2Cx->DATA;
                I2C_TableVariable[0].rxdatap--;
                if (I2C_TableVariable[0].rxdatap != 0)
                {
                    I2C_TableVariable[0].rxdata++;
                    I2Cx->SCR = BIT4;
                }
                else
                {
                    I2Cx->SCR = 0;
                }
            }
        }
        else if (I2C_TableVariable[0].state == 2)
        {
            if (I2C_TableVariable[0].txdatap != 0)
            {
                I2Cx->DATA = *I2C_TableVariable[0].txdata;
                I2C_TableVariable[0].txdata++;
                I2C_TableVariable[0].txdatap--;
                I2Cx->SCR = BIT2;
            }
            else
            {
                I2Cx->SCR = 0;
            }
        }
        else
        {
            I2Cx->SCR = 0;
        }
    }
    /**
     * @brief I2C初始化
     * @param I2Cx: I2C外设指针
     * @param I2C_InitStruct: I2C初始化结构体指针
     */
    void I2C_Init(I2C_TypeDef *I2Cx, I2C_InitTypeDef *I2C_InitStruct)
    {
        SYS_ModuleClockCmd(SYS_MODULE_I2C0, ENABLE);
        SYS_SoftResetModule(SYS_MODULE_I2C0);
        I2C_SetBaudRate(I2Cx, I2C_InitStruct->BaudRate);
        I2Cx->ADDR = (I2C_InitStruct->ADRCMP << 7);
        I2Cx->CFG  = (I2C_InitStruct->IE << 7) | (I2C_InitStruct->TC_IE << 6) |
                    (I2C_InitStruct->BUS_ERR_IE << 5) | (I2C_InitStruct->STOP_IE << 4) |
                    (I2C_InitStruct->MST_MODE << 1) | (I2C_InitStruct->SLV_MODE);
        I2Cx->SCR  = 0;
        I2Cx->DATA = 0;
        I2Cx->MSCR = 0;
        I2Cx->BCR  = (I2C_InitStruct->BURST_NACK << 7) | (I2C_InitStruct->BURST_ADDR_CMP << 6) |
                    (I2C_InitStruct->BUSRT_EN << 5);
    }
    /**
     * @brief I2C时钟分频
     * @param div 分频系数
     */
    void SYS_I2CClkDiv(uint16_t div)
    {
        REG_WRITE(SYS0->PROTECT, 0x7a83);
        REG_WRITE(SYS0->CLK_DIV0, div);
        REG_WRITE(SYS0->PROTECT, 0);
    }
    /**
     * @brief I2C配置结构体初始化
     * @param I2C_InitStruct: I2C初始化结构体指针
     */
    void I2C_StructInit(I2C_InitTypeDef *I2C_InitStruct)
    {
        for (int i = 0; i < sizeof(I2C_InitTypeDef) / sizeof(uint32_t); i++)
        {
            ((uint32_t *)I2C_InitStruct)[i] = 0;
        }
    }
    /**
     * @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 向从机发送数据 （需要在中断里执行I2C_TableIrq）
     * @param I2Cx I2C外设指针
     * @param taddr 从机地址
     * @param data 数据指针
     * @param len 数据长度
     * @return 状态，0成功 1失败（i2c总线非空闲，或上一次数据尚未发送完毕）
     */
    int I2C_TableSend(I2C_TypeDef *I2Cx, u8 taddr, u8 *data, u8 len)
    {
        I2C_TableVariable[0].nackflg = 0;
        while (I2C_TableVariable[0].state != 0);
        I2C->SCR                     = 0;
        I2C_TableVariable[0].txdata  = data;
        I2C_TableVariable[0].txdatap = len;
        I2C_TableVariable[0].state   = 2;
        I2C->DATA                    = taddr;
        I2C->MSCR                    = 1; // 触发I2C发送数据
        return 0;
    }
    /**
     * @brief 向从机读取数据 （需要在中断里执行I2C_TableIrq）
     * @param I2Cx I2C外设指针
     * @param taddr 从机地址
     * @param data 数据指针
     * @param len 数据长度
     * @return 状态，0成功 1失败（i2c总线非空闲，或上一次数据尚未发送完毕）
     */
    int I2C_TableRead(I2C_TypeDef *I2Cx, u8 taddr, u8 *data, u8 len)
    {
        I2C_TableVariable[0].nackflg = 0;
        while (I2C_TableVariable[0].state != 0);
        I2C->SCR                     = 0;
        I2C_TableVariable[0].rxdata  = data;
        I2C_TableVariable[0].rxdatap = len;
        I2C_TableVariable[0].state   = 1;
        I2C->DATA                    = taddr | 1;
        I2C->MSCR                    = 1; // 触发I2C发送数据
        return 0;
    }
    /**
     * @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 获取I2C_Table发送后的ack电平
     * @return 收到的ACK电平
     */
    int I2C_TableAck(I2C_TypeDef *I2Cx)
    {
        return I2C_TableVariable[0].nackflg;
    }
    /**
     * @brief 获取I2C_Table发送状态
     * @return 状态，0空闲 1正在发送或接收数据
     */
    int I2C_TableState(I2C_TypeDef *I2Cx)
    {
        return I2C_TableVariable[0].state;
    }
    /**
     * @brief 获取当前MCU时钟
     * @return 当前MCU时钟频率
     */
    uint32_t SYS_ReadMcuClk(void)
    {
        uint32_t clk    = 96000000;
        uint32_t clkdiv = (SYS_CLK_CFG & SYS_CLK_CFG_CLK_DIV_MASK) >> SYS_CLK_CFG_CLK_DIV_POS;
        uint32_t clksel = (SYS_CLK_CFG & SYS_CLK_CFG_CLK_SEL_MASK) >> SYS_CLK_CFG_CLK_SEL_POS;
        switch (clksel) //  0: HRC 1: PLL 2: LRC
        {
            case 0: // HRC
                clk = 8000000;
                break;
            case 1: // PLL
                switch (clkdiv)
                {
                    case 0xff: // 96M
                        clk = 96000000;
                        break;
                    case 0x55: // 48M
                        clk = 48000000;
                        break;
                    case 0x11: // 24M
                        clk = 24000000;
                        break;
                    case 0x01: // 12M
                        clk = 12000000;
                        break;
                    case 0x00: // 12M
                        clk = 12000000;
                        break;
                    default: // 逐位计算
                        clk = 0;
                        for (int i = 0; i < 8; i++)
                        {
                            if (clkdiv & (1 << i))
                            {
                                clk += 12000000;
                            }
                            else
                            {
                                __NOP();
                            }
                        }
                        break;
                }
                break;
            case 2: // LRC
                clk = 32000;
                break;
            default:
                break;
        }
        return clk;
    }
    /**
     * @brief 设置I2C波特率
     * @param BaudRate: 波特率值
     */
    void I2C_SetBaudRate(I2C_TypeDef *I2Cx, uint32_t BaudRate)
    {
        // 根据系统时钟和波特率计算分频值
        u32 div;
        div = ((u32)SYS_ReadMcuClk() + ((BaudRate * 17) >> 1)) / (BaudRate * 17) - 1;
        SYS_I2CClkDiv(div);
    }
    /**
     * @brief 软复位模块
     * @param nModule 模块编号
     */
    void SYS_SoftResetModule(uint32_t nModule)
    {
        REG_WRITE(SYS0->PROTECT, 0x7a83);
        REG_SET(SYS0->SFT_RST, nModule);
        REG_RESET(SYS0->SFT_RST, nModule); // 触发复位后需清除复位位
        REG_WRITE(SYS0->PROTECT, 0);
    }
    /**
     * @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;
        }
    }