目录
前言
在嵌入式系统开发中,对于存储器的选择和驱动的开发至关重要。W25Q128作为一种常见的串行闪存器件,通过SPI接口提供高速的数据读写和擦除功能,广泛应用于嵌入式设备、存储设备以及网络设备中。本文将介绍如何进行W25Q128的移植与驱动开发学习,帮助开发者更好地理解和应用这一关键的存储器组件。
学习目标
- 掌握w25q128的移植
- 熟悉驱动开发学习方式
学习内容
原理图
引脚说明
W25Q128介绍
W25Q128是一种常见的串行闪存器件,它采用SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。W25Q128芯片容量为128 M-bit(16 M-byte),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,比如W25Q16、W25Q32、W25Q128等。通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。
W25Q128闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为4KB,每个块包含16个扇区,即一个块的大小为64KB。
引脚选择
实现参考
封装改造
官方示例中,耦合太强,进行一些修改,修改后如下:
#ifndef _BSP_W25Q64_H__
#define _BSP_W25Q64_H__
#include "gd32f4xx.h"
#include "SPI.h"
#define W25Q_CS_PORT_RCU RCU_GPIOA
#define W25Q_CS_PORT GPIOA
#define W25Q_CS_PIN GPIO_PIN_4
#define W25Q_CS_SELECT() gpio_bit_write(W25Q_CS_PORT, W25Q_CS_PIN, RESET)
#define W25Q_CS_UNSELECT() gpio_bit_write(W25Q_CS_PORT, W25Q_CS_PIN, SET)
#define W25Q_SPI_RD_WR(data) SPI0_read_write(data)
void W25Q64_init(void);
uint16_t W25Q64_readID(void);
void W25Q64_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte);
void W25Q64_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length) ;
#endif
#include "bsp_w25q64.h"
void W25Q64_init(void)
{
//开启CS引脚时钟
rcu_periph_clock_enable(W25Q_CS_PORT_RCU);
//配置CS引脚模式
gpio_mode_set(W25Q_CS_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, W25Q_CS_PIN);
//配置CS输出模式
gpio_output_options_set(W25Q_CS_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, W25Q_CS_PIN);
//W25Q64不选中
W25Q_CS_UNSELECT();
}
/******************************************************************
* 函 数 名 称:spi_read_write_byte
* 函 数 说 明:硬件SPI的读写
* 函 数 形 参:dat=发送的数据
* 函 数 返 回:读取到的数据
* 作 者:LC
* 备 注:无
******************************************************************/
static uint8_t spi_read_write_byte(uint8_t dat)
{
return W25Q_SPI_RD_WR(dat);
}
/******************************************************************
* 函 数 名 称:W25Q64_readID
* 函 数 说 明:读取W25Q64的厂商ID和设备ID
* 函 数 形 参:无
* 函 数 返 回:设备正常返回EF16
* 作 者:LC
* 备 注:无
******************************************************************/
uint16_t W25Q64_readID(void)
{
uint16_t temp = 0;
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x90);//发送读取ID命令
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
//接收数据
temp |= spi_read_write_byte(0xFF)<<8;
temp |= spi_read_write_byte(0xFF);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
return temp;
}
/**********************************************************
* 函 数 名 称:W25Q64_wait_busy
* 函 数 功 能:判断W25Q64是否忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
static void W25Q64_wait_busy(void)
{
unsigned char byte = 0;
do
{
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x05);
byte = spi_read_write_byte(0Xff);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
}while( ( byte & 0x01 ) == 1 );
}
/**********************************************************
* 函 数 名 称:W25Q64_write_enable
* 函 数 功 能:发送写使能
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_write_enable(void)
{
W25Q_CS_SELECT();
spi_read_write_byte(0x06);
W25Q_CS_UNSELECT();
}
/**********************************************************
* 函 数 名 称:W25Q64_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~15
**********************************************************/
void W25Q64_erase_sector(uint32_t addr)
{
addr *= 4096;
W25Q64_write_enable(); //写使能
W25Q64_wait_busy(); //判断忙
gpio_bit_write(GPIOF, GPIO_PIN_6, RESET);
spi_read_write_byte(0x20);
spi_read_write_byte((uint8_t)((addr)>>16));
spi_read_write_byte((uint8_t)((addr)>>8));
spi_read_write_byte((uint8_t)addr);
gpio_bit_write(GPIOF, GPIO_PIN_6, SET);
//等待擦除完成
W25Q64_wait_busy();
}
/**********************************************************
* 函 数 名 称:W25Q64_write
* 函 数 功 能:写数据到W25Q64进行保存
* 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{ //0x02e21
unsigned int i = 0;
W25Q64_erase_sector(addr/4096);//擦除扇区数据
W25Q64_write_enable();//写使能
W25Q64_wait_busy(); //忙检测
//写入数据
W25Q_CS_SELECT();
spi_read_write_byte(0x02);
spi_read_write_byte((uint8_t)((addr)>>16));
spi_read_write_byte((uint8_t)((addr)>>8));
spi_read_write_byte((uint8_t)addr);
for(i=0;i<numbyte;i++)
{
spi_read_write_byte(buffer[i]);
}
W25Q_CS_UNSELECT();
W25Q64_wait_busy(); //忙检测
}
/**********************************************************
* 函 数 名 称:W25Q64_read
* 函 数 功 能:读取W25Q64的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void W25Q64_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
W25Q_CS_SELECT();
spi_read_write_byte(0x03);
spi_read_write_byte((uint8_t)((read_addr)>>16));
spi_read_write_byte((uint8_t)((read_addr)>>8));
spi_read_write_byte((uint8_t)read_addr);
for(i=0;i<read_length;i++)
{
buffer[i]= spi_read_write_byte(0XFF);
}
W25Q_CS_UNSELECT();
}
测试逻辑如下:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "Usart0.h"
#include "SPI.h"
#include "bsp_w25q64.h"
#include "string.h"
uint8_t cnt = 0;
void Usart0_on_recv(uint8_t* data, uint32_t len) {
printf("recv: %X\r\n", data[0]);
if(data[0] == 0x00) {
printf("ID = %X\r\n",W25Q64_readID());
} else if(data[0] == 0x01) {
char buff[128];
sprintf(buff, "hello: %d", cnt++);
printf("write: %s (%d)\r\n", buff, strlen(buff));
W25Q64_write((uint8_t*)buff, 0, strlen(buff));
} else if(data[0] == 0x02) {
uint8_t buff[128];
W25Q64_read(buff, 0, 16);
printf("read: %s (%d)\r\n", (char*)buff, strlen((char*)buff));
}
}
int main(void)
{
systick_config();
Usart0_init();
SPI_init();
W25Q64_init();
while(1)
{
}
}
练习题
总结
W25Q128闪存芯片具有高容量、高速度和稳定性的特点,适合需要大容量数据存储和快速访问的应用场景。通过学习其移植方法和驱动开发,开发者可以深入了解SPI接口的应用,掌握如何在嵌入式系统中利用这些功能强大的存储器实现数据的安全存储和高效读取。随着技术的进步和应用需求的不断演变,对W25Q128及其类似产品的熟悉与掌握,将为嵌入式设备的开发带来更多的创新可能性和应用前景。