代码简单改下引脚定义便可以使用!
使用的单片机具体型号:GD32F470ZGT6
简单介绍下W25Q64:
/* W25Q64 性能参数 */
/* 容量:8MByte = 64Mbit */
/* 有128个块,每个块有64KByte */
/* 每个块有16个扇区,每个扇区有4KByte */
/* 每个扇区有16页,每个页有256Byte */
/* 最小擦除单位:扇区:4KByte */
/* 最大写入单位:页:256Byte */
w25q64.c:
#include "bsp_w25q64.h"
#include "bsp_usart.h"
#include "systick.h"
#define uchar uint8_t
#define ushort uint16_t
#define ulong uint32_t
/**********************************************************
* 函 数 名 称:w25q64_init_config
* 函 数 功 能:w25q64初始化
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
void w25q64_init_config(void)
{
//SPI参数定义结构体
spi_parameter_struct spi_init_struct;
rcu_periph_clock_enable(W25Q64_SPI_CLK_RCU); // 使用F端口
rcu_periph_clock_enable(W25Q64_SPI_MISO_RCU); // 使用F端口
rcu_periph_clock_enable(W25Q64_SPI_MOSI_RCU); // 使用F端口
rcu_periph_clock_enable(W25Q64_SPI_RCU); // 使能SPI4
//引脚复用
gpio_af_set(W25Q64_SPI_CLK_PORT, W25Q64_SPI_AF, W25Q64_SPI_CLK_PIN);
gpio_af_set(W25Q64_SPI_MISO_PORT, W25Q64_SPI_AF, W25Q64_SPI_MISO_PIN);
gpio_af_set(W25Q64_SPI_MOSI_PORT, W25Q64_SPI_AF, W25Q64_SPI_MOSI_PIN);
//引脚模式
gpio_mode_set(W25Q64_SPI_CLK_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, W25Q64_SPI_CLK_PIN);
gpio_mode_set(W25Q64_SPI_MISO_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, W25Q64_SPI_MISO_PIN);
gpio_mode_set(W25Q64_SPI_MOSI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, W25Q64_SPI_MOSI_PIN);
//输出模式
gpio_output_options_set(W25Q64_SPI_CLK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, W25Q64_SPI_CLK_PIN);
gpio_output_options_set(W25Q64_SPI_MISO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, W25Q64_SPI_MISO_PIN);
gpio_output_options_set(W25Q64_SPI_MOSI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, W25Q64_SPI_MOSI_PIN);
//开启CS引脚时钟
rcu_periph_clock_enable(W25Q64_SPI_CS_RCU);
//配置CS引脚模式
gpio_mode_set(W25Q64_SPI_CS_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, W25Q64_SPI_CS_PIN);
//配置CS输出模式
gpio_output_options_set(W25Q64_SPI_CS_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, W25Q64_SPI_CS_PIN);
//W25Q64不选中
gpio_bit_write(W25Q64_SPI_CS_PORT, W25Q64_SPI_CS_PIN, SET);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; //极性相位,模式3(1,1)
spi_init_struct.nss = SPI_NSS_SOFT; //软件cs
spi_init_struct.prescale = SPI_PSC_32; //SPI时钟预分频为32(SPI通信速度有讲究,如果太快,会导致波形和预期现象都出现偏差)
spi_init_struct.endian = SPI_ENDIAN_MSB; //高位在前
//将参数填入SPI4
spi_init(W25Q64_SPIx, &spi_init_struct);
//使能SPI
spi_enable(W25Q64_SPIx);
}
static uchar spi_read_write_byte(uchar dat)
{
//等待发送缓冲区为空
while(RESET == spi_i2s_flag_get(W25Q64_SPIx, SPI_FLAG_TBE) );
spi_i2s_data_transmit(W25Q64_SPIx, dat);
//等待接收缓冲区为空
while(RESET == spi_i2s_flag_get(W25Q64_SPIx, SPI_FLAG_RBNE) );
return spi_i2s_data_receive(W25Q64_SPIx);
}
ushort W25Q64_readID(void)
{
ushort temp = 0;
W25Q64_CS(0); // 片选W25Q64
spi_read_write_byte(W25Q64_Manufacturer_Device_ID);//发送读取ID命令
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
//接收数据
temp |= spi_read_write_byte(0xFF) << 8; // 读取 manufacturer ID,固定为EF
temp |= spi_read_write_byte(0xFF); // 读取 device ID
W25Q64_CS(1); // 取消片选
return temp;
}
static void W25Q64_write_enable(void)
{
W25Q64_CS(0);
spi_read_write_byte(W25Q64_Write_Enable); // 发送写使能指令
W25Q64_CS(1);
}
/**********************************************************
* 函 数 名 称:W25Q64_wait_busy
* 函 数 功 能:判断W25Q64是否忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
**********************************************************/
void W25Q64_wait_busy(void)
{
unsigned char byte = 0;
do
{
W25Q64_CS(0);
spi_read_write_byte(W25Q64_Read_Status_register_1);
byte = spi_read_write_byte(0Xff);
W25Q64_CS(1);
}
while( ( byte & 0x01 ) == 1 ); // 如果得到的数据最低位是1,则等待
}
/**********************************************************
* 函 数 名 称:W25Q64_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:addr=擦除的扇区号,范围=0~15
**********************************************************/
void W25Q64_erase_sector(ulong addr)
{
addr *= 4096;
W25Q64_write_enable(); //写使能
W25Q64_wait_busy(); //判断忙
W25Q64_CS(0);
spi_read_write_byte(W25Q64_Sector_Erase_4KB);
spi_read_write_byte((uchar)((addr) >> 16));
spi_read_write_byte((uchar)((addr) >> 8));
spi_read_write_byte((uchar)addr);
W25Q64_CS(1);
}
// /**********************************************************
// * 函 数 名 称:W25Q64_write
// * 函 数 功 能:写数据到W25Q64进行保存
// * 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
// * 函 数 返 回:无
// * 作 者:LCKFB
// * 备 注:无
// **********************************************************/
// void W25Q64_write(uchar *buffer, ulong addr, ushort numbyte)
// {
// unsigned int i = 0;
// W25Q64_erase_sector(addr / 4096); //擦除扇区数据,2^12=4096
// //等待擦除完成
// W25Q64_wait_busy();
// delay_ms(50);
// W25Q64_write_enable();//写使能
// W25Q64_wait_busy(); //忙检测
// //写入数据
// W25Q64_CS(0);
// spi_read_write_byte(W25Q64_Page_Program);
// spi_read_write_byte((uchar)((addr) >> 16));
// spi_read_write_byte((uchar)((addr) >> 8));
// spi_read_write_byte((uchar)addr);
// for(i = 0; i < numbyte; i++)
// {
// spi_read_write_byte(buffer[i]);
// }
// W25Q64_CS(1);
// }
// /**********************************************************
// * 函 数 名 称:W25Q64_read
// * 函 数 功 能:读取W25Q64的数据
// * 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
// * 函 数 返 回:无
// * 作 者:LCKFB
// * 备 注:无
// **********************************************************/
// void W25Q64_read(uchar *buffer, ulong read_addr, ushort read_length)
// {
// ushort i;
// //等待擦除完成
// W25Q64_wait_busy();
// W25Q64_CS(0);
// spi_read_write_byte(W25Q64_Read_Data);
// spi_read_write_byte((uchar)((read_addr) >> 16));
// spi_read_write_byte((uchar)((read_addr) >> 8));
// spi_read_write_byte((uchar)read_addr);
// for(i = 0; i < read_length; i++)
// {
// buffer[i] = spi_read_write_byte(0XFF);
// }
// W25Q64_CS(1);
// }
/* 写入数据 */
/* buffer : 字符数组 */
/* section : 扇区,0-2047 */
/* page : 页数,0-15 */
/* addr : 具体地址,0-255 */
/* numbyte : 写入多少个数据,0-255 */
uchar W25Q64_write(uchar *buffer, ushort section, uchar page, ushort addr, ushort numbyte)
{
// 输入地址越界
if((section < 0) || (section > 2047) || (page > 15) || (page < 0) || (addr >255) || (addr < 0))
{
return 1;
}
// 输入字节数超过每页的数量,会造成数据覆盖
if(addr + numbyte > 255)
{
return 2;
}
ulong write_addr = (section * 4096) + (page * 256) + addr;
W25Q64_erase_sector(section); //擦除扇区数据,2^12=4096
//等待擦除完成
W25Q64_wait_busy();
delay_ms(50);
W25Q64_write_enable(); //写使能
W25Q64_wait_busy(); //忙检测
//写入数据
W25Q64_CS(0);
spi_read_write_byte(W25Q64_Page_Program);
spi_read_write_byte((uchar)((write_addr) >> 16));
spi_read_write_byte((uchar)((write_addr) >> 8));
spi_read_write_byte((uchar)write_addr);
for(ushort i = 0; i < numbyte; i++)
{
spi_read_write_byte(buffer[i]);
}
W25Q64_CS(1);
return 0;
}
/* 写入数据 */
/* buffer : 字符数组 */
/* section : 扇区,0-2047 */
/* page : 页数,0-15 */
/* addr : 具体地址,0-255 */
/* numbyte : 写入多少个数据,0-255 */
uchar W25Q64_read(uchar *buffer, ushort section, uchar page, ushort addr, ushort numbyte)
{
// 输入地址越界
if((section < 0) || (section > 2047) || (page > 15) || (page < 0) || (addr >255) || (addr < 0))
{
return 1;
}
// 输入字节数超过每页的数量,会造成数据错误
if(addr + numbyte > 255)
{
return 2;
}
ulong read_addr = (section * 4096) + (page * 256) + addr;
//等待擦除完成
W25Q64_wait_busy();
W25Q64_CS(0);
spi_read_write_byte(W25Q64_Read_Data);
spi_read_write_byte((uchar)((read_addr) >> 16));
spi_read_write_byte((uchar)((read_addr) >> 8));
spi_read_write_byte((uchar)read_addr);
for(ushort i = 0; i < numbyte; i++)
{
buffer[i] = spi_read_write_byte(0XFF);
}
W25Q64_CS(1);
return 0;
}
w25q64.h:
#ifndef _BSP_W25Q64_H__
#define _BSP_W25Q64_H__
#include "gd32f4xx.h"
#include "gd32f470_config.h"
/* W25Q64 性能参数 */
/* 容量:8MByte = 64Mbit */
/* 有128个块,每个块有64KByte */
/* 每个块有16个扇区,每个扇区有4KByte */
/* 每个扇区有16页,每个页有256Byte */
/* 最小擦除单位:扇区:4KByte */
/* 最大写入单位:页:256Byte */
//W25Q64指令表1
#define W25Q64_Write_Enable 0x06
#define W25Q64_Write_Disable 0x04
#define W25Q64_Read_Status_register_1 0x05
#define W25Q64_Read_Status_register_2 0x35
#define W25Q64_Write_Status_register 0x01
#define W25Q64_Page_Program 0x02
#define W25Q64_Quad_Page_Program 0x32
#define W25Q64_Block_Erase_64KB 0xD8
#define W25Q64_Block_Erase_32KB 0x52
#define W25Q64_Sector_Erase_4KB 0x20
#define W25Q64_Chip_Erase 0xC7
#define W25Q64_Erase_Suspend 0x75
#define W25Q64_Erase_Resume 0x7A
#define W25Q64_Power_down 0xB9
#define W25Q64_High_Performance_Mode 0xA3
#define W25Q64_Continuous_Read_Mode_Reset 0xFF
#define W25Q64_Release_Power_Down_HPM_Device_ID 0xAB
#define W25Q64_Manufacturer_Device_ID 0x90
#define W25Q64_Read_Uuique_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
//W25Q64指令集表2(读指令)
#define W25Q64_Read_Data 0x03
#define W25Q64_Fast_Read 0x0B
#define W25Q64_Fast_Read_Dual_Output 0x3B
#define W25Q64_Fast_Read_Dual_IO 0xBB
#define W25Q64_Fast_Read_Quad_Output 0x6B
#define W25Q64_Fast_Read_Quad_IO 0xEB
#define W25Q64_Octal_Word_Read_Quad_IO 0xE3
#define W25Q64_SPIx SPI4
#define W25Q64_SPI_AF GPIO_AF_5
#define W25Q64_SPI_RCU RCU_SPI4
#define W25Q64_SPI_CLK_RCU RCU_GPIOF
#define W25Q64_SPI_MISO_RCU RCU_GPIOF
#define W25Q64_SPI_MOSI_RCU RCU_GPIOF
#define W25Q64_SPI_CS_RCU RCU_GPIOF
#define W25Q64_SPI_CLK_PORT GPIOF // LED GPIOB的端口
#define W25Q64_SPI_CLK_PIN GPIO_PIN_7 // LED GPIOB的引脚
#define W25Q64_SPI_MISO_PORT GPIOF // LED GPIOB的端口 单片机的输入,接Flash的DO
#define W25Q64_SPI_MISO_PIN GPIO_PIN_8 // LED GPIOB的引脚
#define W25Q64_SPI_MOSI_PORT GPIOF // LED GPIOB的端口 单片机的输出,接Flash的DI
#define W25Q64_SPI_MOSI_PIN GPIO_PIN_9 // LED GPIOB的引脚
#define W25Q64_SPI_CS_PORT GPIOF // LED GPIOB的端口
#define W25Q64_SPI_CS_PIN GPIO_PIN_6 // LED GPIOB的引脚
#define W25Q64_CS(state) ((state) ? gpio_bit_set(W25Q64_SPI_CS_PORT, W25Q64_SPI_CS_PIN) : gpio_bit_reset(W25Q64_SPI_CS_PORT, W25Q64_SPI_CS_PIN))
void w25q64_init_config(void);
ushort W25Q64_readID(void);
uchar W25Q64_write(uchar *buffer, ushort section, uchar page, ushort addr, ushort numbyte);
uchar W25Q64_read(uchar *buffer, ushort section, uchar page, ushort addr, ushort numbyte);
#endif
mian.c测试:
int main(void)
{
systick_config(); // 滴答定时器 初始化
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组(0-4, 0-4)
Test_UART_Init(115200); // 调试的 UART 初始化
// w25q64_init_config();
// uchar buff[4] = {0x00, 0x11, 0xFF, 0x80};
// if(W25Q64_write(buff, 0, 0, 0, 4))
// {
// printf("write error\n");
// }
// uchar rec_buff[4];
// if(W25Q64_read(rec_buff, 0, 0, 0, 4))
// {
// printf("read error\n");
// }
// printf("rec_buff: %x %x %x %x\n",rec_buff[0],rec_buff[1],rec_buff[2],rec_buff[3]);
while(1)
{
}
}
有一个地方需要注意一下,就是判断W25Q64忙不忙,这里采用死等的办法,没有做超时溢出,可能会卡死,并且Flash扇区擦除是非常耗时间的操作,几十个ms去死等,显然不靠谱,最好搞个定时器搞个标志位去查看,这里只是测试就懒得做那么多了。
另外这种flash,只能从1变0,反之是不行的,所以在写数据前,必须擦除所写的扇区的所有数据。
图片的现象,跟我给的代码实际现象不一样,大家复制改定义就行。