这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 国产MCU » 【CH582M】CH582M的SPI外设(4)-读写W25Q64的操作

共2条 1/1 1 跳转至

【CH582M】CH582M的SPI外设(4)-读写W25Q64的操作

专家
2026-08-28 09:25:59     打赏

学习完CH582M的SPI0外设的输出操作,接下来自然是SPI0的读操作。以我所知,可以进行双向通讯的无非是两个由SPI外设的开发板之间、开发板与SPI接口显示屏之间、开发板与SPI接口的存储器之间的进行SPI双向通讯。这其中优先选择存储器,因为接口线路简单,电路连接也方便。因此本次学习,使用W25Q64。

image.png 

处理W25Q64模块的程序(w25q64.h)如下:

#ifndef __W25Q64_H__
#define __W25Q64_H__
 
#include "CH58x_common.h"
 
/* W25Q64 指令定义 */
#define W25X_WRITE_ENABLE       0x06
#define W25X_WRITE_DISABLE      0x04
#define W25X_READ_STATUS_REG1   0x05
#define W25X_READ_STATUS_REG2   0x35
#define W25X_READ_DATA          0x03
#define W25X_FAST_READ_DATA     0x0B
#define W25X_PAGE_PROGRAM       0x02
#define W25X_SECTOR_ERASE       0x20    /* 4KB 扇区擦除 */
#define W25X_BLOCK_ERASE_32K    0x52
#define W25X_BLOCK_ERASE_64K    0xD8
#define W25X_CHIP_ERASE         0xC7    /* 或 0x60 */
#define W25X_JEDEC_ID           0x9F
#define W25X_READ_DEVICE_ID     0x90
 
/* W25Q64 容量参数 */
#define W25Q64_PAGE_SIZE        256
#define W25Q64_SECTOR_SIZE      4096
#define W25Q64_BLOCK_SIZE       65536
#define W25Q64_CHIP_SIZE        0x800000    /* 8MB = 64Mbit */
 
/* 片选引脚:PA12 */
#define W25Q64_CS_PIN           GPIO_Pin_12
#define W25Q64_CS_LOW()         GPIOA_ResetBits(W25Q64_CS_PIN)
#define W25Q64_CS_HIGH()        GPIOA_SetBits(W25Q64_CS_PIN)
 
/* 函数声明 */
void W25Q64_Init(void);
uint32_t W25Q64_ReadJEDECID(void);
uint32_t W25Q64_ReadDeviceID(void);
void W25Q64_ReadData(uint32_t addr, uint8_t *buf, uint32_t len);
void W25Q64_WritePage(uint32_t addr, uint8_t *buf, uint16_t len);
void W25Q64_EraseSector(uint32_t addr);
void W25Q64_EraseBlock64K(uint32_t addr);
void W25Q64_EraseChip(void);
void W25Q64_WaitBusy(void);
 
/**
 * @brief  SPI0 与 GPIO 初始化
 * @note   使用 PA12(CS)、PA13(SCK)、PA14(MOSI)、PA15(MISO)
 *         SPI 模式: Mode3 (CPOL=1, CPHA=1), MSB 先行
 */
void W25Q64_Init(void)
{
   /* SPI 0 */
    GPIOA_SetBits(GPIO_Pin_12);
    /* PA12(CS)、PA13(SCK)、PA14(MOSI) 推挽输出 */
    GPIOA_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14, GPIO_ModeOut_PP_5mA);
 
    /* PA15(MISO) 上拉输入 */
    GPIOA_ModeCfg(GPIO_Pin_15, GPIO_ModeIN_PU);  
 
    SPI0_MasterDefInit();                           /* 初始化 SPI0 主机默认参数 */     
    //R8_SPI0_CLOCK_DIV = 15; // 主频时钟15分频,修改SPI时钟
}
 
/**
 * @brief  读状态寄存器1
 */
static uint8_t W25Q64_ReadSR1(void)
{
    uint8_t byte = 0;
    //PRINT("W25Q64_ReadSR1:\r\n");
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_READ_STATUS_REG1);
    byte = SPI0_MasterRecvByte();
    W25Q64_CS_HIGH();
    //PRINT("}\r\n");
    return byte;
}
 
/**
 * @brief  等待 Flash 空闲(BUSY位清零)
 */
void W25Q64_WaitBusy(void)
{
    //PRINT("W25Q64_WaitBusy:\r\n");
    while ((W25Q64_ReadSR1() & 0x01) == 0x01);      /* 等待 BUSY = 0 */
    //PRINT("}\r\n");
}
 
/**
 * @brief  写使能
 */
static void W25Q64_WriteEnable(void)
{
    //PRINT("W25Q64_WriteEnable {\r\n");
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_WRITE_ENABLE);
    W25Q64_CS_HIGH();
    //PRINT("}\r\n");
}
 
/**
 * @brief  读 JEDEC ID (Manufacturer + Memory Type + Capacity)
 * @retval 例如 W25Q64 返回 0xEF4017
 */
uint32_t W25Q64_ReadJEDECID(void)
{
    uint32_t id = 0;
    uint8_t id1 = 0, id2 = 0, id3 = 0;
    //PRINT("W25Q64_ReadJEDECID {\r\n");
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_JEDEC_ID);
    id1 = SPI0_MasterRecvByte();
    id2 = SPI0_MasterRecvByte();
    id3 = SPI0_MasterRecvByte();
    W25Q64_CS_HIGH();
    id = ((uint32_t)id1 << 16) + ((uint32_t)id2 << 8) + id3;
    //PRINT("}\r\n");
    return id;
}
 
/**
 * @brief  读 Device ID (Manufacturer ID + Device ID)
 * @retval W25Q64 返回 0xEF16
 */
uint32_t W25Q64_ReadDeviceID(void)
{
    uint32_t id = 0;
    
    //PRINT("W25Q64_ReadDeviceID {\r\n");
 
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_READ_DEVICE_ID);
    SPI0_MasterSendByte(0x00);    /* Dummy Address */
    SPI0_MasterSendByte(0x00);
    SPI0_MasterSendByte(0x00);
    id |= ((uint32_t)SPI0_MasterRecvByte() << 8); /* Manufacturer ID: 0xEF */
    id |= SPI0_MasterRecvByte();                  /* Device ID: 0x16 */
    W25Q64_CS_HIGH();
 
    //PRINT("}\r\n");
    return id;
}
 
/**
 * @brief  从指定地址读取数据
 * @param  addr: 24位地址 (0 ~ 0x7FFFFF)
 * @param  buf:  接收缓冲区
 * @param  len:  读取长度(字节)
 */
void W25Q64_ReadData(uint32_t addr, uint8_t *buf, uint32_t len)
{
    uint32_t i;
    uint8_t v1 = 0, v2 = 0, v3 = 0;
 
    //PRINT("W25Q64_ReadData:\r\n");
 
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_READ_DATA);              // 发送指令
    SPI0_MasterSendByte((uint8_t)(addr >> 16));       // 发送地址
    SPI0_MasterSendByte((uint8_t)(addr >> 8));        // 
    SPI0_MasterSendByte((uint8_t)addr);               // 
    for (i = 0; i < len; i++)
    {
        buf[i] = SPI0_MasterRecvByte();
    }
    W25Q64_CS_HIGH();
 
    //PRINT("}\r\n");
}
 
/**
 * @brief  页编程(写入一页,最多 256 字节)
 * @note   写入前需确保目标区域已擦除!
 *         地址需按页对齐(addr % 256 == 0),len <= 256
 */
void W25Q64_WritePage(uint32_t addr, uint8_t *buf, uint16_t len)
{
    uint16_t i;
    //PRINT("W25Q64_WritePage:\r\n");
 
    W25Q64_WriteEnable();
    W25Q64_WaitBusy();
 
    W25Q64_CS_LOW();
 
    SPI0_MasterSendByte(W25X_PAGE_PROGRAM);
    SPI0_MasterSendByte((uint8_t)(addr >> 16));
    SPI0_MasterSendByte((uint8_t)(addr >> 8));
    SPI0_MasterSendByte((uint8_t)addr);
 
    for (i = 0; i < len; i++)
    {
        SPI0_MasterSendByte(buf[i]);
    }
    W25Q64_CS_HIGH();
 
    W25Q64_WaitBusy();  /* 等待写入完成 */
 
    //PRINT("}\r\n");
}
 
/**
 * @brief  扇区擦除(4KB)
 * @param  addr: 扇区内的任意地址
 */
void W25Q64_EraseSector(uint32_t addr)
{
    //PRINT("W25Q64_EraseSector:\r\n");
    addr &= ~(W25Q64_SECTOR_SIZE - 1);  /* 4KB 对齐 */
 
    W25Q64_WriteEnable();
    W25Q64_WaitBusy();
 
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_SECTOR_ERASE);
    SPI0_MasterSendByte((uint8_t)(addr >> 16));
    SPI0_MasterSendByte((uint8_t)(addr >> 8));
    SPI0_MasterSendByte((uint8_t)addr);    
    W25Q64_CS_HIGH();
 
    W25Q64_WaitBusy();  /* 扇区擦除约 45ms */
    //PRINT("}\r\n");
}
 
/**
 * @brief  块擦除(64KB)
 * @param  addr: 块内的任意地址
 */
void W25Q64_EraseBlock64K(uint32_t addr)
{
    //PRINT("W25Q64_EraseBlock64K:\r\n");
    addr &= ~(W25Q64_BLOCK_SIZE - 1);  /* 64KB 对齐 */
 
    W25Q64_WriteEnable();
    W25Q64_WaitBusy();
 
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_BLOCK_ERASE_64K);
    SPI0_MasterSendByte((uint8_t)(addr >> 16));
    SPI0_MasterSendByte((uint8_t)(addr >> 8));
    SPI0_MasterSendByte((uint8_t)addr);
    W25Q64_CS_HIGH();
 
    W25Q64_WaitBusy();  /* 块擦除约 150ms */
    //PRINT("}\r\n");
}
 
/**
 * @brief  整片擦除(耗时较长,约 20s)
 */
void W25Q64_EraseChip(void)
{
    //PRINT("W25Q64_EraseChip:\r\n");
 
    W25Q64_WriteEnable();
    W25Q64_WaitBusy();
 
    W25Q64_CS_LOW();
    SPI0_MasterSendByte(W25X_CHIP_ERASE);
    W25Q64_CS_HIGH();
 
    W25Q64_WaitBusy();  /* 整片擦除约 20s */
    //PRINT("}\r\n");
}
 
#endif


 主程序如下:

 

int main(void) {
    uint8_t tx_buf[256];
    uint8_t rx_buf[256];
    uint32_t flash_id;
    uint16_t i;
    uint8_t err = 0;
 
    /* 系统时钟初始化 */
    SetSysClock(CLK_SOURCE_PLL_60MHz);
 
    /* 调试串口初始化 */
    DebugInit();
    PRINT("CH582M SPI0 + W25Q64 测试开始\r\n");
 
    DelayMs(100);
 
    W25Q64_Init();
    PRINT("SPI0 初始化完成\r\n");
 
    /* 读取 JEDEC ID */
    flash_id = W25Q64_ReadJEDECID();
    PRINT("JEDEC ID: 0x%06X (W25Q64应为 0xEF4017)\r\n", flash_id);
 
    if (flash_id != 0xEF4017)     {
        PRINT("Flash ID 错误,请检查硬件连接!\r\n");
        while (1);
    }
 
    /* ========== 擦除测试 ========== */
    PRINT("正在擦除扇区 0 ...\r\n");
    W25Q64_EraseSector(0x000000);
 
    /* 验证擦除结果(应全为 0xFF) */
    W25Q64_ReadData(0x000000, rx_buf, 16);
    PRINT("擦除后前16字节: ");
    for (i = 0; i < 16; i++) PRINT("%02X ", rx_buf[i]);
    PRINT("\r\n");
 
    // /* ========== 写入测试 ========== */
    for (i = 0; i < 256; i++) tx_buf[i] = i;  /* 填充 0x00~0xFF */
 
    PRINT("正在写入 256 字节到地址 0x000000 : 0x00 - 0xFF \r\n");
    W25Q64_WritePage(0x000000, tx_buf, 256);
 
    /* ========== 读取验证 ========== */
    W25Q64_ReadData(0x000000, rx_buf, 256);
    PRINT("读取 256 字节: ");
    for (i = 0; i < 256; i++)     {
        if (rx_buf[i] != tx_buf[i])  {
            err = 1;
            break;
        }
    }
 
    if (err)  {
        PRINT("数据校验失败!\r\n");
    } else {
        PRINT("数据校验成功!\r\n");
    }
 
    /* 打印前 16 字节 */
    PRINT("前256字节: ");
    for (i = 0; i < 256; i++) PRINT("%02X ", rx_buf[i]);
    PRINT("\r\n");
 
    while (1);
}


 程序使用SPI0的主机模式、模式0(RB_SPI_MST_SCK_MOD = 0 )、4分频方式工作。

运行结果:

image.png 

 





关键词: CH582M     SPI    

助工
2026-08-28 10:05:55     打赏
2楼

SPI操作Flash应该是基础实验了吧!

楼主居然能把基础实验做得这么好


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]