# 19.01_SPI-读写SPI_FLASH

## 测试步骤

初始化SPI，并和SPI_FLASH通信。

## 实测数据

ID = ef4018

testflg = 2

测试通过

## 参考代码

19.01_SPI_SendData.c

    /**
     * @brief SPI-读写SPI_FLASH
     * @details
     * 初始化SPI，并和SPI_FLASH通信。
     */
    #include "lks32mc09x_lib.h"
    uint8_t write_data[256] = {0};
    uint8_t read_data[256] = {0};
    volatile uint32_t testflg = 0; // 测试状态，0空闲 1进行中 2写入数据&读回数据一致 3写入数据&读回数据不一致
    volatile uint32_t id;

    void SoftDelaymS(uint32_t n);

    uint32_t SPI_FLASH_ReadID(void);
    void SPI_WaitForWriteEnd(void);
    void SPI_FLASH_WriteEnable(void);
    void SPI_FLASH_SectorErase(u32 SectorAddr);
    void SPI_FLASH_BlockErase64k(u32 SectorAddr);
    void SPI_Write_Data(uint32_t addr, uint8_t *writeBuff, uint32_t numByteToWrite);
    void SPI_Read_Data(uint32_t addr, uint8_t *readBuff, uint32_t numByteToRead);

    /**
     * @brief 主函数，用于测试HSI时钟输出及GPIO翻转
     */
    int main(void)
    {
        {
            SPI_InitTypeDef SPI_InitStruct;

            SPI_StructInit(&SPI_InitStruct);             // SPI结构体初始化
            SPI_InitStruct.Duplex = SPI_Full;            // 双工模式设置
            SPI_InitStruct.Mode = SPI_Master;            // master主机模式
            SPI_InitStruct.EN = ENABLE;                  // 使能SPI模块
            SPI_InitStruct.TRANS_MODE = SPI_DMA_DISABLE; // 选择SPI搬移方式，由MCU搬运数据到SPI
            SPI_InitStruct.Trig = 0;                     // 内部自动触发传输
            SPI_InitStruct.ENDIAN = SPI_FIRSTSEND_MSB;   // 该字节先发送 LSB
            SPI_InitStruct.CPHA = 0;                     // 第一个沿为发送数据时刻
            SPI_InitStruct.CPOL = 0;                     // CLK默认高电平
            SPI_InitStruct.BaudRate = 100;                // SPI 传输速度 = 系统时钟 / (2*(BAUD + 1))
            SPI_InitStruct.ByteLength = 8;               // 发送8个字节
            SPI_InitStruct.IRQEna = DISABLE;             // 关闭SPI各中断
            SPI_Init(SPI0, &SPI_InitStruct);             // SPI初始化程序
        }
        GPIO_Config(GPIO1, GPIO_PinSource_1, GPIO_Mode_OUT, GPIO_AF_GPIO); // CS
        GPIO_Config(GPIO0, GPIO_PinSource_14, GPIO_Mode_OUT, GPIO_AF_SPI); // CLK
        GPIO_Config(GPIO1, GPIO_PinSource_0, GPIO_Mode_IN, GPIO_AF_SPI);   // DI
        GPIO_Config(GPIO0, GPIO_PinSource_15, GPIO_Mode_OUT, GPIO_AF_SPI); // DO
        GPIO_SetBits(GPIO1, GPIO_Pin_1);
        for (int i = 0; i < 256; i++)
        {
            write_data[i] = i; // 确保每次写入的数据都不一样
        }
        SPI0->IE = BIT2;

        while (1)
        {
            if (testflg == 1)
            {
                id = SPI_FLASH_ReadID();
                id = SPI_FLASH_ReadID();
                SPI_FLASH_BlockErase64k(0);
                for (int len = 1; len < 256; len++) // 从1个数据到32个数据进行测试，以确保不同数据长度下i2c通信是否正常
                {
                    for (int i = 0; i < len; i++)
                    {
                        write_data[i] = i + len; // 确保每次写入的数据都不一样
                    }
                    SPI_Write_Data(len << 8, write_data, len);
                    SPI_Read_Data(len << 8, read_data, len);
                    for (int i = 0; i < len; i++)
                    {
                        if (read_data[i] != write_data[i])
                        {
                            testflg = 3;
                        }
                    }
                }
                if (testflg != 3)
                {
                    testflg = 2; // 测试完成，并且没有出现错误
                }
            }
        }
    }
    uint8_t SPI_FLASH_Send_Byte(uint8_t val)
    {
        SPI0->IE = BIT2;
        SPI0->TX_DATA = val;
        while ((SPI0->IE & BIT2) == 0)
            ;
        val = SPI0->RX_DATA;
        return val;
    }
    /**
     * @brief  读取设备ID
     * @return 设备ID
     */
    uint32_t SPI_FLASH_ReadID(void)
    {
        uint32_t temp = 0, temp0 = 0, temp1 = 0, temp2 = 0;

        GPIO_ResetBits(GPIO1, GPIO_Pin_1); // 低电平片选有效，SPI通讯开始

        SPI_FLASH_Send_Byte(0x9f); // 发送读取ID指令

        temp0 = SPI_FLASH_Send_Byte(0x00); // 读取制造商ID（M7-M0）
        temp1 = SPI_FLASH_Send_Byte(0x00); // 读取芯片ID（ID15~ID8）
        temp2 = SPI_FLASH_Send_Byte(0x00); // 读取芯片ID（ID7~ID0）

        GPIO_SetBits(GPIO1, GPIO_Pin_1); // 停止SPI通讯

        temp = (temp0 << 16) | (temp1 << 8) | temp2; // 组合数据

        return temp;
    }
    /**
     * @brief 等待flash内部时序操作完成
     */
    void SPI_WaitForWriteEnd(void)
    {
        uint8_t status_reg = 0;

        GPIO_ResetBits(GPIO1, GPIO_Pin_1);

        SPI_FLASH_Send_Byte(0x05); // 发送“Read Status Register”指令

        do
        {
            SoftDelaymS(1);
            status_reg = SPI_FLASH_Send_Byte(0x00);
        } while ((status_reg & 0x01) == 1);

        GPIO_SetBits(GPIO1, GPIO_Pin_1);
    }
    /**
     * @brief  写使能
     */
    void SPI_FLASH_WriteEnable(void)
    {
        GPIO_ResetBits(GPIO1, GPIO_Pin_1);
        SPI_FLASH_Send_Byte(0x06); // 发送“Write Enable”指令，06h
        GPIO_SetBits(GPIO1, GPIO_Pin_1);
    }

    /**
     * @brief  块擦除(64k)
     * @param  SectorAddr 擦除地址
     */
    void SPI_FLASH_BlockErase64k(u32 SectorAddr)
    {
        SPI_WaitForWriteEnd();
        SPI_FLASH_WriteEnable();

        GPIO_ResetBits(GPIO1, GPIO_Pin_1);

        SPI_FLASH_Send_Byte(0xd8);
        SPI_FLASH_Send_Byte((SectorAddr & 0xFF0000) >> 16); // 高位擦除地址A23~A16
        SPI_FLASH_Send_Byte((SectorAddr & 0xFF00) >> 8);    // 中位擦除地址A15~A8
        SPI_FLASH_Send_Byte(SectorAddr & 0xFF);             // 低位擦除地址A7~A0

        GPIO_SetBits(GPIO1, GPIO_Pin_1);

        SPI_WaitForWriteEnd();
    }
    /**
     * @brief  扇区擦除
     * @param  SectorAddr 擦除地址
     */
    void SPI_FLASH_SectorErase(u32 SectorAddr)
    {
        SPI_FLASH_WriteEnable();
        SPI_WaitForWriteEnd();

        GPIO_ResetBits(GPIO1, GPIO_Pin_1);

        SPI_FLASH_Send_Byte(0x20);
        SPI_FLASH_Send_Byte((SectorAddr & 0xFF0000) >> 16); // 高位擦除地址A23~A16
        SPI_FLASH_Send_Byte((SectorAddr & 0xFF00) >> 8);    // 中位擦除地址A15~A8
        SPI_FLASH_Send_Byte(SectorAddr & 0xFF);             // 低位擦除地址A7~A0

        GPIO_SetBits(GPIO1, GPIO_Pin_1);

        SPI_WaitForWriteEnd();
    }
    /**
     * @brief  数据写入
     * @param addr 写入地址
     * @param writeBuff 包含数据的指针
     * @param numByteToWrite 写入字节数
     */
    void SPI_Write_Data(uint32_t addr, uint8_t *writeBuff, uint32_t numByteToWrite)
    {
        SPI_FLASH_WriteEnable();
        GPIO_ResetBits(GPIO1, GPIO_Pin_1);

        SPI_FLASH_Send_Byte(0x02); // 发送“Page Program”指令，02h

        SPI_FLASH_Send_Byte((addr >> 16) & 0xff);
        SPI_FLASH_Send_Byte((addr >> 8) & 0xff);
        SPI_FLASH_Send_Byte(addr & 0xff);

        while (numByteToWrite--)
        {
            SPI_FLASH_Send_Byte(*writeBuff);
            writeBuff++;
        }

        GPIO_SetBits(GPIO1, GPIO_Pin_1);

        SPI_WaitForWriteEnd();
    }
    /**
     * @brief  读取flash数据
     * @param addr 读取地址
     * @param readBuff 存放读出的数据的指针
     * @param numByteToRead 读出的字节数
     */
    void SPI_Read_Data(uint32_t addr, uint8_t *readBuff, uint32_t numByteToRead)
    {
        GPIO_ResetBits(GPIO1, GPIO_Pin_1);

        SPI_FLASH_Send_Byte(0x03);
        SPI_FLASH_Send_Byte((addr >> 16) & 0xff);
        SPI_FLASH_Send_Byte((addr >> 8) & 0xff);
        SPI_FLASH_Send_Byte(addr & 0xff);

        while (numByteToRead--)
        {
            *readBuff = SPI_FLASH_Send_Byte(0x00);
            readBuff++;
        }

        GPIO_SetBits(GPIO1, GPIO_Pin_1);
    }

## 使用到的库函数

库函数部分代码

    #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_GPIO BIT11
    #define SYS_MODULE_SPI0 BIT0
    /**
     * @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 SPI模块初始化结构体
     */
    typedef struct SPI_InitTypeDef {
        SPI_Duplex Duplex;
        uint8_t CS;
        SPI_Mode Mode;
        uint8_t CPHA;
        uint8_t CPOL;
        uint8_t ENDIAN;
        uint8_t EN;
        uint8_t IRQEna;
        uint8_t Trig;
        uint8_t TRANS_MODE;
        uint16_t BaudRate;
        uint8_t ByteLength;
    };
    /**
     * @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 SPI初始化结构体默认配置函数
     * @param SPI_InitStruct 指向SPI初始化结构体的指针
     */
    void SPI_StructInit(SPI_InitTypeDef *SPI_InitStruct)
    {
        for (int i = 0; i < sizeof(SPI_InitTypeDef); i++)
        {
            ((uint8_t *)SPI_InitStruct)[i] = 0;
        }
    }
    /**
     * @brief SPI模块初始化函数
     * @param SPIx SPI模块指针
     * @param SPI_InitStruct 指向SPI初始化结构体的指针
     */
    void SPI_Init(SPI_TypeDef *SPIx, SPI_InitTypeDef *SPI_InitStruct)
    {
        // 使能SPI模块时钟
        SYS_ModuleClockCmd(SYS_MODULE_SPI0, ENABLE);

        // 配置SPI控制寄存器
        SPIx->CFG = (SPI_InitStruct->Duplex << 6) |
                    (SPI_InitStruct->CS << 5) |
                    (SPI_InitStruct->Mode << 4) |
                    (SPI_InitStruct->CPHA << 3) |
                    (SPI_InitStruct->CPOL << 2) |
                    (SPI_InitStruct->ENDIAN << 1) |
                    SPI_InitStruct->EN;

        // 配置SPI中断使能寄存器
        SPIx->IE = (SPI_InitStruct->IRQEna << 4) |
                   (SPI_InitStruct->Trig << 3);

        // 配置SPI波特率
        SPIx->DIV = (SPI_InitStruct->TRANS_MODE << 15) |
                    (SPI_InitStruct->BaudRate);

        // 配置SPI数据长度
        SPIx->SIZE = SPI_InitStruct->ByteLength;
    }
    /**
     * @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 复位指定的GPIO引脚位
     * @param GPIOx GPIO模块指针
     * @param GPIO_Pin 要复位的引脚
     */
    void GPIO_ResetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
    {
        GPIOx->PDO &= ~GPIO_Pin;
    }
    /**
     * @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 设置指定的GPIO引脚位
     * @param GPIOx GPIO模块指针
     * @param GPIO_Pin 要设置的引脚
     */
    void GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
    {
        GPIOx->PDO |= GPIO_Pin;
    }
    /**
     * @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;
        }
    }