一、使用方式及接线:
硬件SPI,软件片选CS(PA4)
STM32F407(主机) | GD25Q322(从机) | 说明 |
---|---|---|
PA4(SPI1_NSS) | CS(NSS) | 片选线 |
PA5(SPI1_SCK) | CLK | 时钟线 |
PA6(SPI_MISO) | DO(IO1)(MISO) | 主机输入从机输出线 |
PA7(SPI_MOSI) | DI(IO0)(MOSI) | 主机输出从机输入线 |
二、操作:
写入一串数据(嘉立创)
GD25Q32E-Rev1.3 (szlcsc.com)https://atta.szlcsc.com/upload/public/pdf/source/20240402/EEA34CC25AEF519FAE78D6FF38362CF4.pdf
三、GD25Q32简介
1.简介
GD25Q32是一种常见的串行闪存器件,它采用SPI(Serial Peripheral Interface)接口协议,具有高速读写和擦除功能,可用于存储和读取数据。GD25Q32芯片容量为32 Mbit(4 MB),其中名称后的数字代表不同的容量选项。不同的型号和容量选项可以满足不同应用的需求,通常被用于嵌入式设备、存储设备、路由器等高性能电子设备中。
GD25Q32闪存芯片的内存分配是按照扇区(Sector)和块(Block)进行的,每个扇区的大小为4KB,每个块包含16个扇区,即一个块的大小为64KB。
2.技术手册
3.主要参数
芯片容量 | 每颗芯片块数量 | 每个块大小 | 每块扇区数量 | 每个扇区大小 |
32Mbit(4MB) | 64 | 64KB | 16 | 4KB |
3.芯片相关指令
3.1读ID
//读取芯片ID
//读取设备ID
uint16_t GD25Q32_readID(void)
{
uint16_t temp = 0;
//将CS端拉低为低电平
W25QXX_CS_ON(0);
//发送指令90h
spi_read_write_byte(0x90);//发送读取ID命令
//发送地址 000000H
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
//接收数据
//接收制造商ID
temp |= spi_read_write_byte(0xFF)<<8;
//接收设备ID
temp |= spi_read_write_byte(0xFF);
//恢复CS端为高电平
W25QXX_CS_ON(1);
//返回ID
return temp;
}
3.2写使能
//发送写使能
void GD25Q32_write_enable(void)
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令06h
spi_read_write_byte(0x06);
//拉高CS端为高电平
W25QXX_CS_ON(1);
}
3.3忙碌状态
在GD25Q322的数据手册中,有3个状态寄存器,可以判断当前GD25Q32是否正在传输、写入、读取数据等,我们每一次要对GD25Q32进行操作时,需要先判断GD25Q32是否在忙。如果在忙的状态,我们去操作GD25Q32,很可能会导致数据丢失,并且操作失败。而判断是否忙,是通过状态寄存器1的S0为进行判断,状态寄存器1的地址为0X05。
读取状态寄存器的时序图如下:
拉低CS端为低电平;
发送指令05h(0000_0101);
接收状态寄存器值;
恢复CS端为高电平;
/**********************************************************
* 函 数 名 称:GD25Q32_wait_busy
* 函 数 功 能:检测线路是否繁忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void GD25Q32_wait_busy(void)
{
unsigned char byte = 0;
do
{
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令05h
spi_read_write_byte(0x05);
//接收状态寄存器值
byte = spi_read_write_byte(0Xff);
//恢复CS端为高电平
W25QXX_CS_ON(1);
//判断BUSY位是否为1 如果为1说明在忙,重新读写BUSY位直到为0
}while( ( byte & 0x01 ) == 1 );
}
3.4扇区擦除
发送写使能->CS拉低->擦除扇区命令(20H)->三字节(24位)地址->CS拉高
/**********************************************************
* 函 数 名 称:GD25Q32_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~15
**********************************************************/
void GD25Q32_erase_sector(uint32_t addr)
{
//计算扇区号,一个扇区4KB=4096
addr *= 4096;
GD25Q32_write_enable(); //写使能
GD25Q32_wait_busy(); //判断忙,如果忙则一直等待
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令20h
spi_read_write_byte(0x20);
//发送24位扇区地址的高8位
spi_read_write_byte((uint8_t)((addr)>>16));
//发送24位扇区地址的中8位
spi_read_write_byte((uint8_t)((addr)>>8));
//发送24位扇区地址的低8位
spi_read_write_byte((uint8_t)addr);
//恢复CS端为高电平
W25QXX_CS_ON(1);
//等待擦除完成
GD25Q32_wait_busy();
}
3.5 写入数据
CS拉低--》写入数据命令(0X02)--》3个字节地址(24bit)--》写入至少一个字节--》CS拉高
/**********************************************************
* 函 数 名 称:GD25Q32_write
* 函 数 功 能:写数据到GD25Q32进行保存
* 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void GD25Q32_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{
unsigned int i = 0;
//擦除扇区数据
GD25Q32_erase_sector(addr/4096);
//写使能
GD25Q32_write_enable();
//忙检测
GD25Q32_wait_busy();
//写入数据
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令02h
spi_read_write_byte(0x02);
//发送写入的24位地址中的高8位
spi_read_write_byte((uint8_t)((addr)>>16));
//发送写入的24位地址中的中8位
spi_read_write_byte((uint8_t)((addr)>>8));
//发送写入的24位地址中的低8位
spi_read_write_byte((uint8_t)addr);
//根据写入的字节长度连续写入数据buffer
for(i=0;i<numbyte;i++)
{
spi_read_write_byte(buffer[i]);
}
//恢复CS端为高电平
W25QXX_CS_ON(1);
//忙检测
GD25Q32_wait_busy();
}
3.6读取数据
CS拉低-》发送读取命令0X03-》发送24位地址
/**********************************************************
* 函 数 名 称:GD25Q32_read
* 函 数 功 能:读取GD25Q32的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void GD25Q32_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
//拉低CS端为低电平
W25QXX_CS_ON(0);
//发送指令03h
spi_read_write_byte(0x03);
//发送24位读取数据地址的高8位
spi_read_write_byte((uint8_t)((read_addr)>>16));
//发送24位读取数据地址的中8位
spi_read_write_byte((uint8_t)((read_addr)>>8));
//发送24位读取数据地址的低8位
spi_read_write_byte((uint8_t)read_addr);
//根据读取长度读取出地址保存到buffer中
for(i=0;i<read_length;i++)
{
buffer[i]= spi_read_write_byte(0XFF);
}
//恢复CS端为高电平
W25QXX_CS_ON(1);
}
四、SPI介绍
1.上面所述的FLASH芯片的SPI模式
2.SPI概述
在SPI协议中,主设备是通信的发起方和控制方,而从设备则是被动接收和响应主设备的命令和数据。主设备通过时钟信号来同步数据传输,同时使用多个双向数据线来实现数据的传输和接收。
SPI协议是一种全双工通信方式,意味着主设备和从设备可以同时发送和接收数据。它还使用一种选择信号(通常称为片选或使能信号),用于选择与主设备进行通信的特定从设备。
3.SPI模式
SPI协议定义了多种传输模式,也称为SPI模式或时序模式,用于控制数据在时钟信号下的传输顺序和数据采样方式。SPI的传输模式主要由两个参数决定:时钟极性 (CKPL) 和相位 (CKPH)。 时钟极性 (CKPL):时钟极性定义了时钟信号在空闲状态时的电平。
CKPL = 0:时钟信号在空闲状态时为低电平。
CKPL = 1:时钟信号在空闲状态时为高电平。
时钟相位 (CKPH):相位定义了数据采样和更新发生在时钟信号的哪个边沿上。
CKPH = 0:数据采样发生在时钟的第一个边沿,数据更新发生在第二个边沿。
CKPH = 1:数据采样发生在时钟的第二个边沿,数据更新发生在第一个边沿。
以下是常见的SPI模式:
模式0(CKPL=0,CKPH=0):
- 时钟极性(Clock Polarity)为0,表示时钟空闲状态为低电平。
- 时钟相位(Clock Phase)为0,表示数据在时钟信号的第一个边沿(时钟上升沿)进行采样和稳定。
模式1(CKPL=0,CKPH=1):
- 时钟极性为0,时钟空闲状态为低电平。
- 时钟相位为1,数据在时钟信号的第二个边沿(时钟下降沿)进行采样和稳定。
模式2(CKPL=1,CKPH=0):
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为0,数据在时钟信号的第一个边沿(时钟下降沿)进行采样和稳定。
模式3(CKPL=1,CKPH=1):
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为1,数据在时钟信号的第二个边沿(时钟上升沿)进行采样和稳定。
GD25Q32手册中指出模式0和模式3都是支持的,如下配置即为模式三
五、编程
1.先读取ID,我贴的GD25Q32ESIG芯片ID为C815
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#include "board.h"
#include "bsp_uart.h"
#include "stdio.h"
#include "sys.h"
#define CS_HIGH GPIO_SetBits(GPIOA,GPIO_Pin_4)
#define CS_LOW GPIO_ResetBits(GPIOA,GPIO_Pin_4)
/*
*初始化SPI的GPIO
*PA4--SPI1_NSS
*PA5--SPI1_SCK
*PA6--SPI1_MISO
*PA7--SPI1_MOSI
*/
void SpiGpio_Init(void)
{
//1.1复用GPIO
GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_SPI1);
//1.2初始化GPIO PA4 PA5 PA6 PA7
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置MISO*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置MOSI*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置CS*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//1.3拉高CS
CS_HIGH;
}
/*
*初始化SPI1
*/
void Spi_Init(void)
{
//2.1初始化gpio
SpiGpio_Init();
//2.2配置Spi
SPI_InitTypeDef SPI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
SPI_InitStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2; //84MHZ/4=21MHZ
SPI_InitStruct.SPI_CPHA=SPI_CPHA_2Edge; //第二个边沿有效
SPI_InitStruct.SPI_CPOL=SPI_CPOL_High; //高电平有效
SPI_InitStruct.SPI_CRCPolynomial= 7;
SPI_InitStruct.SPI_DataSize=SPI_DataSize_8b; //单位数据大小1个字节
SPI_InitStruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex; //全双工
SPI_InitStruct.SPI_FirstBit=SPI_FirstBit_MSB; //高位先行
SPI_InitStruct.SPI_Mode=SPI_Mode_Master; //主机
SPI_InitStruct.SPI_NSS=SPI_NSS_Soft; //软件选择CS
SPI_Init(SPI1,&SPI_InitStruct);
SPI_Cmd(SPI1,ENABLE);
}
/*
*spi发送一个字节
*/
uint8_t spi_read_write_byte(uint8_t dat)
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //等待发送完毕
SPI_I2S_SendData(SPI1,dat);
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //等待接收到数据
return SPI_I2S_ReceiveData(SPI1);
}
/*
*获取芯片ID和设备ID
*/
uint16_t GD25Q32_readID(void)
{
uint16_t id=0; //要做移位操作必须初始化!!!
CS_LOW;
spi_read_write_byte(0x90); //读芯片ID指令
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
id |= spi_read_write_byte(0xff)<<8;
id |= spi_read_write_byte(0xff);
CS_HIGH;
return id;
}
int main(void)
{
board_init();
uart1_init(115200);
Spi_Init();
while(1)
{
printf("man_id is %X",GD25Q32_readID());
delay_ms(1000);
}
}
2.spi编写为便于移植
spi.c
#include "spi.h"
/*
*初始化SPI的GPIO
*PA4--SPI1_NSS
*PA5--SPI1_SCK
*PA6--SPI1_MISO
*PA7--SPI1_MOSI
*/
void SpiGpio_Init(void)
{
//1.1复用GPIO
GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_SPI1);
//1.2初始化GPIO PA4 PA5 PA6 PA7
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置MISO*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置MOSI*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*配置CS*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//1.3拉高CS
CS_HIGH;
}
/*
*初始化SPI1
*/
void Spi_Init(void)
{
//2.1初始化gpio
SpiGpio_Init();
//2.2配置Spi
SPI_InitTypeDef SPI_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
SPI_InitStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2; //84MHZ/4=21MHZ
SPI_InitStruct.SPI_CPHA=SPI_CPHA_2Edge; //第二个边沿有效
SPI_InitStruct.SPI_CPOL=SPI_CPOL_High; //空闲状态为高电平
SPI_InitStruct.SPI_CRCPolynomial= 7;
SPI_InitStruct.SPI_DataSize=SPI_DataSize_8b; //单位数据大小1个字节
SPI_InitStruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex; //全双工
SPI_InitStruct.SPI_FirstBit=SPI_FirstBit_MSB; //高位先行
SPI_InitStruct.SPI_Mode=SPI_Mode_Master; //主机
SPI_InitStruct.SPI_NSS=SPI_NSS_Soft; //软件选择CS
SPI_Init(SPI1,&SPI_InitStruct);
SPI_Cmd(SPI1,ENABLE);
}
/*
*spi发送一个字节
*/
uint8_t spi_read_write_byte(uint8_t dat)
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //等待发送完毕
SPI_I2S_SendData(SPI1,dat);
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //等待接收到数据
return SPI_I2S_ReceiveData(SPI1);
}
spi.h
#ifndef _SPI_H
#define _SPI_H
#include "board.h"
#define CS_HIGH GPIO_SetBits(GPIOA,GPIO_Pin_4)
#define CS_LOW GPIO_ResetBits(GPIOA,GPIO_Pin_4)
void SpiGpio_Init(void);
void Spi_Init(void);
uint8_t spi_read_write_byte(uint8_t dat);
#endif
3.gd25q32
gd25q32.c
#include "gd25q32.h"
#include "spi.h"
/*
*获取芯片ID和设备ID
*/
uint16_t GD25Q32_readID(void)
{
uint16_t id=0; //要做移位操作必须初始化!!!
CS_LOW;
spi_read_write_byte(0x90); //读芯片ID指令
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
spi_read_write_byte(0x00);
id |= spi_read_write_byte(0xff)<<8;
id |= spi_read_write_byte(0xff);
CS_HIGH;
return id;
}
//发送写使能
void GD25Q32_write_enable(void)
{
//拉低CS端为低电平
CS_LOW;
//发送指令06h
spi_read_write_byte(0x06);
//拉高CS端为高电平
CS_HIGH;
}
/**********************************************************
* 函 数 名 称:GD25Q32_wait_busy
* 函 数 功 能:检测线路是否繁忙
* 传 入 参 数:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void GD25Q32_wait_busy(void)
{
unsigned char byte = 0;
do
{
//拉低CS端为低电平
CS_LOW;
//发送指令05h
spi_read_write_byte(0x05);
//接收状态寄存器值
byte = spi_read_write_byte(0Xff);
//恢复CS端为高电平
CS_HIGH;
//判断BUSY位是否为1 如果为1说明在忙,重新读写BUSY位直到为0
}while( ( byte & 0x01 ) == 1 );
}
/**********************************************************
* 函 数 名 称:GD25Q32_erase_sector
* 函 数 功 能:擦除一个扇区
* 传 入 参 数:addr=擦除的扇区号
* 函 数 返 回:无
* 作 者:LC
* 备 注:addr=擦除的扇区号,范围=0~15
**********************************************************/
void GD25Q32_erase_sector(uint32_t addr)
{
//计算扇区号,一个扇区4KB=4096
addr *= 4096;
GD25Q32_write_enable(); //写使能
GD25Q32_wait_busy(); //判断忙,如果忙则一直等待
//拉低CS端为低电平
CS_LOW;
//发送指令20h
spi_read_write_byte(0x20);
//发送24位扇区地址的高8位
spi_read_write_byte((uint8_t)((addr)>>16));
//发送24位扇区地址的中8位
spi_read_write_byte((uint8_t)((addr)>>8));
//发送24位扇区地址的低8位
spi_read_write_byte((uint8_t)addr);
//恢复CS端为高电平
CS_HIGH;
//等待擦除完成
GD25Q32_wait_busy();
}
/**********************************************************
* 函 数 名 称:GD25Q32_write
* 函 数 功 能:写数据到GD25Q32进行保存
* 传 入 参 数:buffer=写入的数据内容 addr=写入地址 numbyte=写入数据的长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void GD25Q32_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte)
{
unsigned int i = 0;
//擦除扇区数据
GD25Q32_erase_sector(addr/4096);
//写使能
GD25Q32_write_enable();
//忙检测
GD25Q32_wait_busy();
//写入数据
//拉低CS端为低电平
CS_LOW;
//发送指令02h
spi_read_write_byte(0x02);
//发送写入的24位地址中的高8位
spi_read_write_byte((uint8_t)((addr)>>16));
//发送写入的24位地址中的中8位
spi_read_write_byte((uint8_t)((addr)>>8));
//发送写入的24位地址中的低8位
spi_read_write_byte((uint8_t)addr);
//根据写入的字节长度连续写入数据buffer
for(i=0;i<numbyte;i++)
{
spi_read_write_byte(buffer[i]);
}
//恢复CS端为高电平
CS_HIGH;
//忙检测
GD25Q32_wait_busy();
}
/**********************************************************
* 函 数 名 称:GD25Q32_read
* 函 数 功 能:读取GD25Q32的数据
* 传 入 参 数:buffer=读出数据的保存地址 read_addr=读取地址 read_length=读去长度
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
**********************************************************/
void GD25Q32_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length)
{
uint16_t i;
//拉低CS端为低电平
CS_LOW;
//发送指令03h
spi_read_write_byte(0x03);
//发送24位读取数据地址的高8位
spi_read_write_byte((uint8_t)((read_addr)>>16));
//发送24位读取数据地址的中8位
spi_read_write_byte((uint8_t)((read_addr)>>8));
//发送24位读取数据地址的低8位
spi_read_write_byte((uint8_t)read_addr);
//根据读取长度读取出地址保存到buffer中
for(i=0;i<read_length;i++)
{
buffer[i]= spi_read_write_byte(0XFF);
}
//恢复CS端为高电平
CS_HIGH;
}
gd25q32.h
#ifndef _GD25Q32_H
#define _GD25Q32_H
#include "gd25q32.h"
#include "board.h"
uint16_t GD25Q32_readID(void);
void GD25Q32_write_enable(void);
void GD25Q32_wait_busy(void);
void GD25Q32_erase_sector(uint32_t addr);
void GD25Q32_write(uint8_t* buffer, uint32_t addr, uint16_t numbyte);
void GD25Q32_read(uint8_t* buffer,uint32_t read_addr,uint16_t read_length);
#endif