前言
数据保存是所有项目的基本功能,但是对于STM32C8T6的原flash进行操作,一方面大小有可能不够,另一方面单片机的运行程序本来就放在这个里面,所以还是外接的好。这里选用w25Q128 FLASH存储器,参考实现简单读写。
作为一个初学者,技能都是东拼西凑的,基础可能不扎实,如有下列问题的解决方案,可以给我留言。
任然存在的问题:
- 读写时传入参数是uint8_t *的指针数组形式,可以传入u8类型的数组,但是存double类型的数据尚不知道怎么解决。
- 上面问题已解决,使用的是联合体,详情可见博文“stm32外设w25Q128存取浮点数问题解决”
w25Q128简介
挑重要的说:
- 16M字节,128Mbit,空间范围0x000000-0xFFFFFF。
-
分为256个块
每块16个扇区(256*16个扇区)
每个扇区16页 (2561616页)
每块的大小 16384KB/256 =64KB
每个扇区的大小 64KB/16 = 4KB
每个页的大小 4KB/16 = 256B - 每个块的地址:
块0地址:0x000000-0x00FFFF
块1地址:0x010000-0x01FFFF
…
块0地址:0xFF0000-0xFFFFFF
每个扇区的地址:
0块0扇区:0x000000-0x000FFF
0块1扇区:0x001000-0x001FFF
…
0块16扇区:0x00F000-0x00FFFF
由上可知:地址高8位(23-16)表示块的位置,第(15-12)表示扇区的位置。 - w25Q128属于SPI协议的FLASH。与EPROM相比,读写速度更快,但是读写次数只有10万次,EPROM读写次数100万次,w25Q128数据保存年限也更短。
硬件说明
硬件的主要引脚:文章来源:https://www.toymoban.com/news/detail-401815.html
- CS片选,每次操作都得拉低片选,就好像STM32叫“谁在叫我”,W25Q128拉低CS,表示“是我在叫”。
- CLK,时钟信号,通过时钟可以计算发送的字节,如果需要持续发送,也需要时钟的持续信号。
- DO,我的板子上写的是DO,实际上应该和SO,或者说MISO是一样的,send out发送,从机上send out,那就是从机发送,主机接收了。
- DI,SI,MISI,send in ,主机发送,从机接收。
状态寄存器SR
BIT7 6 5 4 3 2 1 0
SPR RV TB BP2 BP1 BP0 WEL BUSY
BUSY位指示当前的状态,0表示空闲,1表示忙碌
WEL:写使能锁定,为1时,可以操作页/扇区/块。为0时,写禁止。
SPR:默认0,状态寄存器保护位,配合WP使用
TB,BP2,BP1,BP0:FLASH区域写保护设置
编程逻辑
- 写使能(拉低CS,发送06H,拉高CS)
- 读状态寄存器(拉低CS,发送05H,返回SR1值,拉高CS)
- 读时序(拉低CS,发送03H,发送24位地址,读取数据1,读取数据2…,拉高CS)
- 页写时序(拉低CS,发送02H,发送24位地址,发送数据1,发送数据n(n<=256),拉高CS)
- 扇区擦除:这类型的FLASH只能把原来1的数据改成0,而原来0的数据不能直接改成1,所以数据写入前,需要检查空间是否满足,不满足需要擦除。(拉低CS,发送20H,发送24位地址,拉高CS)
w25Q128 FLASH驱动的基本步骤
- SPI配置
- 设置SPI通信速度
- SPI发送接收数据
- SPI初始化
- w25Q128配置
- W25Q128初始化
- 读取W25Q128状态寄存器
- W25Q128写使能
- 擦除一个扇区
- 读取函数
- 写一页256个字节
- 写数据 (扇区写入)
代码
基于库函数版本,绝对完整代码,毕竟拿来主义是最香的。
文章来源地址https://www.toymoban.com/news/detail-401815.html
user_gpio.c
#include "user_gpio.h"
void Gpio_Init(void)
{
/*GPIO结构体*/
GPIO_InitTypeDef GPIO_InitTypeDefstruct;
/*使能GPIOA时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*使能UART1时钟*/
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/*使能GPIOB时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
/*UART1发送引脚配置*/
GPIO_InitTypeDefstruct.GPIO_Mode = GPIO_Mode_AF_PP;//推挽复用输出
GPIO_InitTypeDefstruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitTypeDefstruct.GPIO_Speed = GPIO_Speed_10MHz;
/*写入结构体到GPIOA*/
GPIO_Init(GPIOA,&GPIO_InitTypeDefstruct);
/*UART1接收引脚配置*/
GPIO_InitTypeDefstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_InitTypeDefstruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitTypeDefstruct.GPIO_Speed = GPIO_Speed_10MHz;
/*写入结构体到GPIOA*/
GPIO_Init(GPIOA,&GPIO_InitTypeDefstruct);
/*配置SPI 13 时钟 14 MISO 15 MOSI*/
GPIO_InitTypeDefstruct.GPIO_Pin = GPIO_Pin_12| GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitTypeDefstruct.GPIO_Mode = GPIO_Mode_AF_PP; //PB12/13/14/15复用推挽输出 MISO推挽也正常
GPIO_InitTypeDefstruct.GPIO_Speed = GPIO_Speed_50MHz;
/*写入结构体到GPIOB*/
GPIO_Init(GPIOB,&GPIO_InitTypeDefstruct);
/*配置SPI 12 CS*/
GPIO_InitTypeDefstruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitTypeDefstruct.GPIO_Mode = GPIO_Mode_Out_PP; //PB12
GPIO_InitTypeDefstruct.GPIO_Speed = GPIO_Speed_50MHz;
/*写入结构体到GPIOB*/
GPIO_Init(GPIOB,&GPIO_InitTypeDefstruct);
/*高电平*/
GPIO_SetBits(GPIOB,GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
}
user_gpio.h
#ifndef __USER_GPIO_H__
#define __USER_GPIO_H__
#include "stm32f10x.h"
void Gpio_Init(void);
#endif
user_spi.c
#include "user_spi.h"
/*
设置SPI通信速度
*/
void SPI2_SetSpeed(uint8_t SPI_BaudRatePrescaler)
{
/*断言库函数里的*/
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
/*清除第bit3、4、5位可以进库函数去看分频具体数值,分频就是操作 3 4 5bit位*/
SPI2->CR1&=0XFFC7;
/*设置SPI2速度*/
SPI2->CR1|=SPI_BaudRatePrescaler;
/*重新使能SPI*/
SPI_Cmd(SPI2,ENABLE);
}
/*
描述:读写函数,SPI因为2个位移寄存器相连写一个字节的时候会返回上一次接受到的字节
输入参数:发送数据
返回值:读取的字节
*/
uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
/*等待超时时间*/
uint8_t retry=0;
/*检查指定的SPI标志位设置与否:发送缓存空标志位*/
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
retry++;
if(retry>200)
{
return 0;
}
}
/*通过外设SPI2发送一个数据*/
SPI_I2S_SendData(SPI2, TxData);
/*等待超时时间*/
retry=0;
/*检查指定的SPI标志位设置与否:接受缓存非空标志位*/
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)
{
retry++;
if(retry>200)
{
return 0;
}
}
/*返回通过SPIx最近接收的数据*/
return SPI_I2S_ReceiveData(SPI2);
}
/*
SPI初始化
*/
void Spi_Init(void)
{
/*SPI结构体*/
SPI_InitTypeDef SPI_InitStructure;
/*SPI2时钟使能*/
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2,ENABLE );
/*SPI配置*/
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //片选又软件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//APB1(36M)/256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB(高)位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //写入SPI2里面
/*使能SPI外设*/
SPI_Cmd(SPI2, ENABLE);
/*主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输*/
SPI2_ReadWriteByte(0xFF);
}
user_spi.h
#ifndef __USER_SPI_H__
#define __USER_SPI_H__
#include "stm32f10x.h"
void SPI2_SetSpeed(uint8_t SPI_BaudRatePrescaler);
uint8_t SPI2_ReadWriteByte(uint8_t TxData);
void Spi_Init(void);
#endif
user_w25q128.c
#include "user_w25q128.h"
/*中转变量发现有其他值的全部取出来,然后擦除一个扇区在写回去*/
uint8_t W25Q128_BUFFER[4096];
/*
W25Q128初始化
*/
void W25Q128_Init(void)
{
W25Q128_CS=1; //W25Q128不片选
Spi_Init(); //初始化SPI
SPI2_SetSpeed(SPI_BaudRatePrescaler_2); //设置为36/2=18M时钟,高速模式
}
/*
状态寄存器
BIT7 6 5 4 3 2 1 0
SPR RV TB BP2 BP1 BP0 WEL BUSY
SPR:默认0,状态寄存器保护位,配合WP使用
TB,BP2,BP1,BP0:FLASH区域写保护设置
WEL:写使能锁定
BUSY:忙标记位(1,忙;0,空闲)
默认:0x00
*/
/* 读取W25Q128状态寄存器 */
uint8_t W25Q128_ReadSR(void)
{
uint8_t byte=0; //声明接收数据变量
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态命令,同时会返回一个无用字节
byte=SPI2_ReadWriteByte(0XFF); //写一个无用字节,返回状态寄存器值
W25Q128_CS=1; //取消片选
return byte; //返回状态
}
/*
SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写
*/
/* 写W25Q128状态寄存器 */
void W25Q128_Write_SR(uint8_t sr)
{
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_WriteStatusReg); //发送写状态寄存器命令
SPI2_ReadWriteByte(sr); //写入一个字节
W25Q128_CS=1; //取消片选
}
/*
W25Q128写使能
*/
void W25Q128_Write_Enable(void)
{
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_WriteEnable); //发送写使能命令
W25Q128_CS=1; //取消片选
}
/*
W25Q128写禁止
*/
void W25Q128_Write_Disable(void)
{
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_WriteDisable); //发送写禁止指令
W25Q128_CS=1; //取消片选
}
/*
描述:读取ID
返回值:0XEF17,表示芯片型号为W25Q128
*/
uint16_t W25Q128_ReadID(void)
{
uint16_t Temp = 0; //声明ID返回变量
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_ManufactDeviceID); //发送读取ID命令
/*发送24bit位的地址*/
SPI2_ReadWriteByte(0x00);
SPI2_ReadWriteByte(0x00);
SPI2_ReadWriteByte(0x00);
/*发无用字节,接收ID*/
Temp|=SPI2_ReadWriteByte(0xFF)<<8;
Temp|=SPI2_ReadWriteByte(0xFF);
W25Q128_CS=1; //取消片选
return Temp; //返回ID
}
/*
擦除一个扇区
*/
void W25Q128_Erase_Sector(u32 Dst_Addr)
{
Dst_Addr*=4096; //一个扇区是4096个字节
W25Q128_Write_Enable(); //写使能
while((W25Q128_ReadSR()&0x01)==0x01); //等待BUSY位清空
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_SectorErase); //发送扇区擦除指令
/*发送24bit地址*/
SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));
SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));
SPI2_ReadWriteByte((u8)Dst_Addr);
W25Q128_CS=1; //取消片选
while((W25Q128_ReadSR()&0x01)==0x01); //等待擦除完成
}
/*
擦除整个芯片
*/
void W25Q128_Erase_Chip(void)
{
W25Q128_Write_Enable(); //写使能
while((W25Q128_ReadSR()&0x01)==0x01); //等待BUSY位清空
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_ChipErase); //发送片擦除命令
W25Q128_CS=1;
while((W25Q128_ReadSR()&0x01)==0x01); //等待擦除完成
}
/*
读取函数
参数1:读取BUFF 参数2:读取地址 参数3:读取数量(最大65535)
*/
void W25Q128_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
uint16_t i;
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_ReadData); //发送读取命令
SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //发送读取地址
SPI2_ReadWriteByte((u8)((ReadAddr)>>8));
SPI2_ReadWriteByte((u8)ReadAddr);
/*开始发空指令,读取数据,返回寄存器的值*/
for(i=0;i<NumByteToRead;i++)
{
/*循环读数*/
pBuffer[i]=SPI2_ReadWriteByte(0XFF);
}
W25Q128_CS=1; //取消片选
}
/*
描述:写一页256个字节
参数1:要写的BUFF 参数2:要写的地址(24bit) 参数3:要写的数量(256字节最大)
*/
void W25Q128_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
u16 i;
W25Q128_Write_Enable(); //写使能
W25Q128_CS=0; //选中片选
SPI2_ReadWriteByte(W25X_PageProgram); //发送写页命令
/*发送24bit地址*/
SPI2_ReadWriteByte((u8)((WriteAddr)>>16));
SPI2_ReadWriteByte((u8)((WriteAddr)>>8));
SPI2_ReadWriteByte((u8)WriteAddr);
/*开始循环写数*/
for(i=0;i<NumByteToWrite;i++){
SPI2_ReadWriteByte(pBuffer[i]);
}
W25Q128_CS=1; //取消片选
while((W25Q128_ReadSR()&0x01)==0x01); //等待擦除完成
}
/*
描述:检查是否一页当中有存其他值,可以换页 (页写入)
参数1:要写的数组 参数2:要写的地址(24bit) 参数3:要写的数量(256字节最大)
*/
void W25Q128_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
u16 pageremain;
pageremain=256-WriteAddr%256; //单页剩余的字节数
//如果要写入字节的大小<单页剩余字节数,说明不需要换页,则将要写入字节数赋值给pageremain
if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;
while(1)
{
W25Q128_Write_Page(pBuffer,WriteAddr,pageremain); //写一页256个字节
if(NumByteToWrite==pageremain) //如果要写入的字节和单页剩余字数正好相等,写入结束
{ /*写入结束*/
break;
}
else //如果要写入的字节>单页剩余字节,需要换页。
{
pBuffer+=pageremain; //写入数组=写入数组+剩余字节数
WriteAddr+=pageremain; //写入地址=写入地址+剩余字节数,相当于下一页的开始地址
NumByteToWrite-=pageremain; //要写入的数据=要写入的数据-上一页已经写入的字节数
/*一次可以写入256个字节*/
if(NumByteToWrite>256) //如果要写入的数据还>256
{
pageremain=256; //单页剩余字节数=256
}
else
{ /*不够256个字节了*/
pageremain=NumByteToWrite; //要写入数据字节给当页剩余字节数
}
}
}
}
/*
描述:写数据 (扇区写入)
参数1:写BUFF 参数2:要写入的地址 参数3:要写的数量(65535字节最大)
*/
void W25Q128_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
u32 secpos;
u16 secoff;
u16 secremain;
u16 i;
u8 * W25QXX_BUF;
W25QXX_BUF=W25Q128_BUFFER;
secpos=WriteAddr/4096; //扇区地址
secoff=WriteAddr%4096; //在扇区内的偏移
secremain=4096-secoff; //扇区剩余空间大小
/*不大于4096个字节*/
//如果(传入数量字节<扇区剩余空间大小) 将传入数量字节赋值给扇区剩余空间
if(NumByteToWrite<=secremain)secremain=NumByteToWrite;
while(1)
{ /*读出整个扇区的内容*/
W25Q128_Read(W25QXX_BUF,secpos*4096,4096);
/*校验数据*/
for(i=0;i<secremain;i++)
{
if(W25QXX_BUF[secoff+i]!=0XFF)
{
/*需要擦除 */
break;
}
}
/*//需要擦除 */
if(i<secremain)
{
/*擦除这个扇区*/
W25Q128_Erase_Sector(secpos);
/*复制*/
for(i=0;i<secremain;i++)
{
W25QXX_BUF[i+secoff]=pBuffer[i];
}
/*写入整个扇区*/
W25Q128_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);
}
else
{ /*写已经擦除了的,直接写入扇区剩余区间*/
W25Q128_Write_NoCheck(pBuffer,WriteAddr,secremain);
}
if(NumByteToWrite==secremain)
{ /*写入结束了*/
break;
}
else/*写入未结束*/
{ /*扇区地址增1*/
secpos++;
/*偏移位置为0*/
secoff=0;
/*指针偏移*/
pBuffer+=secremain;
/*写地址偏移*/
WriteAddr+=secremain;
/*字节数递减*/
NumByteToWrite-=secremain;
/*下一个扇区还是写不完*/
if(NumByteToWrite>4096)
{
secremain=4096;
}
else
{ /*下一个扇区可以写完了*/
secremain=NumByteToWrite;
}
}
}
}
/*
描述:获取地址块和扇区的位置,打印地址块、扇区的位置信息
传入参数:存储地址
*/
void Get_Address_Analysis(uint32_t address)
{
u32 addr =address; //存储地址
u8 block = addr>>16; //23-16位是块的位置
u8 sector = (addr<<16)>>28; //15-12位是扇区的位置
Serial_Printf("addr:%x,block:%d,sector:%d\r\n",addr,block,sector);
}
user_w25q128.h
#ifndef _USER_W25Q128_H__
#define _USER_W25Q128_H__
#include "stm32f10x.h"
#include "user_spi.h"
#include "Serial.h"
//指令表数据手册上的
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_ReadData 0x03
#define W25X_FastReadData 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_PageProgram 0x02
#define W25X_BlockErase 0xD8
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID 0xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JedecDeviceID 0x9F
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//片选引脚
#define W25Q128_CS BIT_ADDR(GPIOB_ODR_Addr,12) //W25QXX的片选信号 1取消片选 0 选中
uint16_t W25Q128_ReadID(void); //读取FLASH ID
void W25Q128_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead); //读取flash
void W25Q128_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash
void W25Q128_Init(void); //初始化
uint8_t W25Q128_ReadSR(void);
void W25Q128_Write_SR(uint8_t sr) ;
void W25Q128_Write_Enable(void);
void W25Q128_Write_Disable(void) ;
void W25Q128_Erase_Sector(u32 Dst_Addr);
void W25Q128_Erase_Chip(void);
void W25Q128_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25Q128_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void Get_Address_Analysis(uint32_t address); //获取地址块和扇区的位置,打印地址块、扇区的位置信息
#endif
delay.c
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
Serial.c
#include "Serial.h"
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure; //定义USART结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 USART1 TX使用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式 USART1 RX使用
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600; //设置波特率9600,init函数内部会自动算好9600对应的分频系数
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不启用
USART_InitStructure.USART_Mode = USART_Mode_Tx|USART_Mode_Rx; //USART模式:发送、接收数据
USART_InitStructure.USART_Parity = USART_Parity_No; //校验位:无校验
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位1
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,这里不需要校验,字长选择8位
USART_Init(USART1,&USART_InitStructure); //初始化USART
USART_Cmd(USART1,ENABLE);
}
/*
函数功能:发送数据
*/
void Serial_SendByte(u8 Byte)
{
/*内部将Byte传递给Data变量,之后Data&01FF,把无关的高位清零,
然后直接赋值给DR寄存器,通向TDR发送数据寄存器,再传递到移位寄存器最后一位一位传递给TX引脚*/
USART_SendData(USART1,Byte); //调用串口的SendDate()函数
/*写完数据,需要等待,等TDR的数据到移位寄存器,不然数据还在TDR进行等待,再写入数据会产生覆盖
所以发送之后,需要等待标志位
USART_FLAG_TXE 发送数据寄存器为空 标志位,要等待TXE为1,这里要嵌套循环
如果TXE为RESET就一直循环,直到TXE为SET
*/
while (USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
/*关于标志位是否需要手动清楚的问题:标志位置1之后,不需要手动清0。因为对USART_DR进行写操作,将该位清0*/
}
/*
发送数组,通过串口发送到电脑
参数1:指针指向传递数组的首地址
参数2:长度
*/
void Serial_SendArray(u8 *Array,u16 Length)
{
u16 i;
for ( i = 0; i < Length; i++)
{
Serial_SendByte(Array[i]);
}
}
/*
发送字符串函数
字符串自带一个结束标志位,不需要再传递长度参数,对应空字符,是字符串的标志位
*/
void Serial_SendString(char *String)
{
u8 i;
for(i=0; String[i]!='\0'; i++)
{
Serial_SendByte(String[i]); //发送字符串
}
}
/*
次方函数,提取千百十个
*/
u32 Serial_Pow(u32 X,u32 Y) //2 3
{
u32 Result = 1;
while(Y--) //3--
{
Result *= X; //1*2 *2 *2 = 2^3
}
return Result;
}
/*
发送一个数字
*/
void Serial_SendNumber(u32 Number,u8 Length)
{
u8 i;
for ( i = 0; i < Length; i++)
{
/*
(Number / Serial_Pow(10,i) % 10) 1234/1%10=4 1234/10%10=3 1234/100%10=2
第一个数据不是个位,需要反过来 (Number / Serial_Pow(10, Length - i -1) % 10)
目前循环,参数会以十进制从高位到低位依次发送,因为最终要以字符的形式显示,所以这里要加一个偏移
*/
Serial_SendByte((Number / Serial_Pow(10,Length - i - 1) % 10) + '0');
}
}
/*
printf函数默认是输出到屏幕,单片机没有屏幕,需要重定向printf函数
fputc是printf函数的底层,printf打印时,不断调用fputc函数一个一个打印
*/
int fputc(int ch,FILE *f)
{
Serial_SendByte(ch);
return ch;
}
/*
封装sprintf,参数1接收格式化字符串,...用来接收后面的可变参数列表
*/
void Serial_Printf(char *format,...)
{
char String[100];
va_list arg; //参数列表变量
va_start(arg,format); //从format位置开始接收参数表,放在arg里面
vsprintf(String,format,arg); //打印位置String,格式化字符串是format,参数表是arg
va_end(arg); //释放参数表
Serial_SendString(String);
}
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"
#include <stdio.h>
#include <stdarg.h>
void Serial_Init(void);
void Serial_SendByte(u8 Byte);
void Serial_SendArray(u8 *Array,u16 Length);
void Serial_SendString(char *String);
void Serial_SendNumber(u32 Number,u8 Length);
void Serial_Printf(char *format,...);
#endif
main.c
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "delay.h"
#include "user_w25q128.h"
#include "Serial.h"
#define SIZE sizeof(TEXT_Buffer)
const uint8_t TEXT_Buffer[]={"SPI TEST AAAAAAAAA"}; //19个字节
int main(void)
{
uint16_t id; //声明id变量
uint32_t FLASH_SIZE = 128*1024*1024; //声明flash大小16M字节
uint8_t datatemp[SIZE]; //声明读出数组
uint16_t i; //声明打印变量
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置系统中断分组为2位抢占2位响应
Gpio_Init(); //GPIO初始化
Serial_Init(); //串口1初始化
W25Q128_Init(); //W25Q128初始化
while(1){
id = W25Q128_ReadID(); //读取W25Q128的ID
if(id == 0xef17)
{
Serial_Printf("W25Q128\r\n");
}
/*从倒数第200个地址处开始,写入SIZE长度的数据*/
W25Q128_Write((uint8_t*)TEXT_Buffer,FLASH_SIZE-200,SIZE);
/*从倒数第200个地址处开始,读出SIZE个字节*/
W25Q128_Read(datatemp,FLASH_SIZE-200,SIZE);
for(i = 0; i < SIZE; i++)
{
Serial_Printf("%c",datatemp[i]);
}
Serial_Printf("\r\n");
Delay_ms(1000);
}
}
参考
- 39_SPI通讯W25Q128实验 原文链接:https://blog.csdn.net/Yuanghxb/article/details/128295231
- 一些视频教程,b站江科大自化协视频代码占比较多
到了这里,关于stm32使用外部flash w25Q128实现读写操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!