学习完CH582M的SPI0外设的输出操作,接下来自然是SPI0的读操作。以我所知,可以进行双向通讯的无非是两个由SPI外设的开发板之间、开发板与SPI接口显示屏之间、开发板与SPI接口的存储器之间的进行SPI双向通讯。这其中优先选择存储器,因为接口线路简单,电路连接也方便。因此本次学习,使用W25Q64。
处理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分频方式工作。
运行结果:
我要赚赏金
