STM32SPIFLASH读写

1.1 SPI注意事项

SPI是同步通信,即通信双方每次信息交互必会带有一问一答,这代表在正常的单核MCU(例如STM32)中很难实现软件模拟的双向SPI通信(TFT屏幕一类的外设不算,那些顶多属于单向SPI),因为无法同时发送和接收数据。而在STM32中,硬件实现同步通信的办法是利用硬件缓冲区,以字节为单位,每次发送一个字节的数据,接收缓冲区就会缓存一个字节的接收数据,如此实现同时接收和发送。

1.2 SPI代码编写

SPI的代码需要引用如下的标准库头文件:

#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_spi.h"

其中rcc头文件用于外设时钟的初始化,gpio头文件用于GPIO口的初始化,spi头文件用于SPI外设的初始化。

1.1.1 初始化结构体与定义变量

//定义SPI设备
#define FLASH_SPI SPI1
//定义CS引脚
#define FLASH_SPI_CS_PORT GPIOC
#define FLASH_SPI_CS_Pin GPIO_Pin_0
//定义SCK引脚
#define FLASH_SPI_SCK_PORT GPIOA
#define FLASH_SPI_SCK_Pin GPIO_Pin_5
//定义MISO引脚
#define FLASH_SPI_MISO_PORT GPIOA
#define FLASH_SPI_MISO_Pin GPIO_Pin_6
//定义MOSI引脚
#define FLASH_SPI_MOSI_PORT GPIOA
#define FLASH_SPI_MOSI_Pin GPIO_Pin_7
//定义CS引脚控制函数
#define SPI_FLASH_CS_High() GPIO_SetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_Pin)
#define SPI_FLASH_CS_Low() GPIO_ResetBits(FLASH_SPI_CS_PORT,FLASH_SPI_CS_Pin)
//定义无意义字节,用于挤占同步通信以读取数据
#define Dummy_Byte 0xFF void SPI_FLASH_Init() //SPI初始化函数
{
SPI_InitTypeDef FLASH_InitStructure; //定义SPI初始化结构体 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); //使能SPI1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能GPIOC时钟 GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO初始化结构体
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_Pin; //定义CS引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //定义CS引脚为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //定义CS引脚速度为50MHz
GPIO_Init(FLASH_SPI_CS_PORT,&GPIO_InitStructure); //初始化CS引脚 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_Pin; //定义SCK引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //定义SCK引脚为复用推挽输出
GPIO_Init(FLASH_SPI_SCK_PORT,&GPIO_InitStructure); //初始化SCK引脚 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_Pin; //定义MOSI引脚
GPIO_Init(FLASH_SPI_MOSI_PORT,&GPIO_InitStructure); //初始化MOSI引脚 GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_Pin; //定义MISO引脚
GPIO_Init(FLASH_SPI_MISO_PORT,&GPIO_InitStructure); //初始化MISO引脚 SPI_FLASH_CS_High(); //CS引脚打开后最好将其置高以释放CS片选线 FLASH_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //定义SPI为双线全双工模式
FLASH_InitStructure.SPI_Mode = SPI_Mode_Master; //定义SPI为主模式
FLASH_InitStructure.SPI_DataSize = SPI_DataSize_8b; //定义SPI数据大小为8位
FLASH_InitStructure.SPI_CPOL = SPI_CPOL_High; //定义时钟极性为高电平
FLASH_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //定义时钟相位为第二个时钟边沿
FLASH_InitStructure.SPI_NSS = SPI_NSS_Soft; //定义NSS信号由软件控制
FLASH_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; //定义波特率预分频为4
FLASH_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //定义数据传输从MSB位开始
FLASH_InitStructure.SPI_CRCPolynomial = 7; //定义CRC多项式为7
SPI_Init(FLASH_SPI,&FLASH_InitStructure); //初始化SPI SPI_Cmd(FLASH_SPI,ENABLE);
}

其中SPI外设的主要参数为SPI_Direction、SPI_Mode、SPI_DataSize、SPI_NSS、SPI_BaudRatePrescaler、SPI_FirstBit。SPI_Direction决定了SPI外设的工作模式,可设置为双线全双工、双线只接收、单线只接收、单线只发送四个模式。SPI_Mode决定了SPI外设的工作模式,可设置为主模式、从模式。SPI_DataSize决定了每一帧数据帧的长度。SPI_NSS决定了NSS信号的来源,NSS信号即为CS信号,可设置为软件控制、硬件控制。SPI_BaudRatePrescaler决定了波特率预分频,可设置为主频的2、4、6、8、16、128、256分频。SPI_FirstBit决定了数据传输的起始位,可设置为MSB位或LSB位,即数据从左向右读取与从右向左读取。

其余参数也很重要,但若只是使用的话并没有上面那些起决定性作用。其中SPI_CPOL与SPI_CPHA决定了数据的读取模式,SPI_CPOL设置时钟信号为高电平有效或低电平有效,SPI_CPHA设置时钟相位为第一个时钟边沿或第二个时钟边沿有效。SPI_CRCPolynomial决定了CRC校验的中的多项式。

1.1.2 SPI发送与接收函数

uint8_t SPI_FLASH_SendRecive(uint8_t byte)      //SPI发送接收函数
{
while(SPI_I2S_GetFlagStatus(FLASH_SPI,SPI_I2S_FLAG_TXE) == RESET); //等待发送缓冲区为空
SPI_I2S_SendData(FLASH_SPI,byte); //发送一个字节
while(SPI_I2S_GetFlagStatus(FLASH_SPI,SPI_I2S_FLAG_RXNE) == RESET); //等待接收缓冲区非空
return SPI_I2S_ReceiveData(FLASH_SPI); //从接收缓冲区读取一个字节
}

发送与接收函数没什么好说的,就单纯的等待SPI外设寄存器状态,然后发送或接收数据。不过这点与IIC不同的是,发送与接收无法分开,必须要同步进行。

2.1 FLASH注意事项

我所使用的开发板为野火STM32F103VET6开发板,其板载SPI FLASH芯片为W25Q64,若板载SPI FLASH芯片不同,则指令与扇区大小也不同,需查看手册确定。

在W25Q64中,有128个块(BLOCK),每个块有16个扇区(SECTOR),每个扇区有64个页(PAGE)。块 = 64KB,扇区 = 4KB,页 = 256B。每次擦除FLASH最小为整个扇区擦除,每次写入FLASH最大不超过一页,即256个字节。

2.2 FLASH代码编写

FLASH的头文件引用需要如下头文件:

#include "Spi.h"

Spi头文件内为文章上述SPI相关的代码。

2.2.1 定义变量与初始化

W25Q64为自带微型处理器的设备,STM32作为主设备若利用W25Q64读取与写入数据,需要对应其指令表进行操作,下列代码为W25Q64的指令定义:

#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 //设备ID指令
#define W25X_ManufactDeviceID 0x90 //生产ID指令
#define W25X_JedecDeviceID 0x9F //JEDEC设备ID指令
#define sFlash_ID 0xEF4017 //JEDEC设备ID #define WIP_Flag 0x01 //设备忙标志位
#define FLASH_Page_Size 0x1000//定义FLASH每页的大小,4K = 4096 = 0x1000

2.2.2 W25Q64通信验证

当STM32通过SPI向W25Q64发送W25X_JedecDeviceID命令时,W25Q64会返回一个24位的JEDECID,可通过此ID确定通信是否成功。

uint8_t FLASH_Device_Init(void)                         //初始化FLASH设备
{
uint32_t temp1 = 0,temp2 = 0,temp3 = 0; //定义三个临时变量
uint8_t status = 0; //定义状态变量
SPI_FLASH_CS_Low(); //拉低CS片选线,选中FLASH
SPI_FLASH_SendRecive(W25X_JedecDeviceID); //发送W25X_JedecDeviceID命令
temp1 = SPI_FLASH_SendRecive(Dummy_Byte); //接收一个字节
temp2 = SPI_FLASH_SendRecive(Dummy_Byte); //接收一个字节
temp3 = SPI_FLASH_SendRecive(Dummy_Byte); //接收一个字节
SPI_FLASH_CS_High(); //拉高CS片选线,取消选中FLASH
if( ((temp1<<16)|(temp2<<8)|temp3) == sFlash_ID) //比较接收到的JEDECID与预设的JEDECID
{
status = 0x01; //通信成功
return status; //返回通信状态
}
else
{
return 0; //通信失败
}
}

2.2.2 写入使能及写保护检测

W25Q64具有严格的写保护机制,若想要向其写入数据,必须保证写使能,且写入的页面必须为擦除后的页面。而且每次对FLASH内容进行修改后,W25Q64硬件会自动写失能,会触发写失能的操作有“写状态寄存器”、“页编程”、“扇区擦除”、“块区擦除”、“芯片擦除”。要想打开写使能,只需向W25Q64发送W25X_WriteEnable命令后释放CS片选线即可。

void FLASH_WriteEnable(void)				//写入使能
{
SPI_FLASH_CS_Low(); //拉低CS片选线,选中FLASH
SPI_FLASH_SendRecive(W25X_WriteEnable); //发送W25X_WriteEnable命令
SPI_FLASH_CS_High(); //拉高CS片选线,取消选中FLASH
}

W25Q64对于自身的状态有一个8位的寄存器专门存放,当主设备向W25Q64发送状态寄存器查询时,不论此时W25Q64处于什么状态,都会将8位的状态寄存器返回给主设备。而这8位的状态寄存器中,第0位为写保护位,若该位为1,则表示W25Q64处于写保护状态,此时主设备无法向其写入数据。可以在此时写一个死循环重复读取其状态寄存器,直到该位为0,在进行后续操作。

void FLASH_WaitForWriteEnd(void)				//等待写入结束
{
uint8_t FLASH_Status = 0xff; //初始化状态变量
SPI_FLASH_CS_Low(); //拉低CS片选线,选中FLASH
SPI_FLASH_SendRecive(W25X_ReadStatusReg); //发送W25X_ReadStatusReg命令
while((FLASH_Status & WIP_Flag) == SET) //若写入标志位为1,则表示写入未完成
{
FLASH_Status = SPI_FLASH_SendRecive(Dummy_Byte); //发送占位字节,接收状态寄存器
}
SPI_FLASH_CS_High(); //拉高CS片选线,取消选中FLASH
}

2.2.3 FLASH扇区擦除

在前文中提到过,W25Q64的最小擦除单位位扇区,每个扇区的大小为4KB。若想要擦除W25Q64中的数据,只需向其发送W25X_SectorErase命令,再将其24位的地址发送给W25Q64即可。

void FLASH_SecortErase(uint32_t addr)				//扇区擦除
{
FLASH_WaitForWriteEnd(); //等待写入结束
FLASH_WriteEnable(); //写入使能
FLASH_WaitForWriteEnd(); //等待写入结束
SPI_FLASH_CS_Low(); //拉低CS片选线,选中FLASH
SPI_FLASH_SendRecive(W25X_SectorErase); //发送W25X_SectorErase命令
SPI_FLASH_SendRecive((addr & 0xFF0000) >>16); //发送24位地址中的高8位
SPI_FLASH_SendRecive((addr & 0xFF00)>>8); //发送24位地址中的中间8位
SPI_FLASH_SendRecive(addr & 0xFF); //发送24位地址中的低8位
SPI_FLASH_CS_High(); //拉高CS片选线,取消选中FLASH
FLASH_WaitForWriteEnd(); //等待写入结束
}

2.2.4 FLASH页写入

W25Q64的最小写入单位为页,每个页的大小为256字节。若想要向W25Q64中写入数据,只需向其发送W25X_PageProgram命令,再将其24位的地址发送给W25Q64,最后发送要写入的数据即可。但是有个限制,每次写入的数据大小不能超过256字节,即不能超过一页,超过一页就需要重新等待写入结束,写使能,发送写指令与地址。

void FLASH_Write(uint8_t* databuff,uint32_t addr,uint32_t data_length)	//页写入
{
FLASH_WaitForWriteEnd(); //等待写入结束
FLASH_WriteEnable(); //写入使能
FLASH_WaitForWriteEnd(); //等待写入结束
SPI_FLASH_CS_Low(); //拉低CS片选线,选中FLASH
SPI_FLASH_SendRecive(0x02); //发送W25X_PageProgram命令
SPI_FLASH_SendRecive((addr & 0xFF0000) >>16); //发送24位地址中的高8位
SPI_FLASH_SendRecive((addr & 0xFF00)>>8); //发送24位地址中的中间8位
SPI_FLASH_SendRecive(addr & 0xFF); //发送24位地址中的低8位
while(data_length--) //循环发送数据
{
SPI_FLASH_SendRecive(*databuff); //发送一个字节的数据
databuff++; //指针后移
}
SPI_FLASH_CS_High(); //拉高CS片选线,取消选中FLASH
FLASH_WaitForWriteEnd(); //等待写入结束
}

2.2.5 FLASH不定页写入

不定页写入与页写入类似,区别在于不定页写入每次写入的数据大小通过处理将其划分为每一页依次写入,即可以调用该函数写入任意长度的数据。

void FLASH_PageWrite(uint8_t* databuff,uint32_t addr,uint32_t data_length)	//不定页写入
{
uint32_t page_count,page_other,i,addr_start,addr_other; //定义变量
addr_start = addr % 256; //计算地址的起始位置
addr_other = 256 - addr_start; //计算地址的剩余位置
if(addr_start == 0) //如果地址的起始位置为0
{
if(data_length >= 256) //如果数据长度大于等于256
{
page_count = data_length/256; //计算页数
page_other = data_length%256; //计算剩余数据长度
for(i=0;i<page_count;i++) //循环写入页数据
{
FLASH_Write(databuff,(addr += (i * 256)),256); //写入256个字节的数据
*databuff += page_count * 256; //指针后移
}
FLASH_Write(databuff,(addr += (page_count * 256)),page_other); //写入剩余数据
}
else //如果数据长度小于256
{
FLASH_Write(databuff,addr,data_length); //写入数据
}
}
else //如果地址的起始位置不为0
{
FLASH_Write(databuff,addr,addr_other); //先写入地址的剩余位置数据
*databuff += addr_other; //指针后移
data_length -= addr_other; //数据长度减去不满一页地址的长度
if(data_length >= 256) //如果数据长度大于等于256
{
page_count = data_length/256; //计算页数
page_other = data_length%256; //计算剩余数据长度
for(i=0;i<page_count;i++) //循环写入页数据
{
FLASH_Write(databuff,(addr += (i * 256)),256); //写入256个字节的数据
*databuff += page_count * 256; //指针后移
}
FLASH_Write(databuff,(addr += (page_count * 256)),page_other); //写入剩余数据
}
else
{
FLASH_Write(databuff,addr,data_length); //写入数据
}
}
}

2.2.5 FLASH读取

虽然FLASH写入有256字节的限制,但是读取时没有限制,只要发送W25X_ReadData与读取起始即可一直接收数据。

void FLASH_Read(uint8_t* databuff,uint32_t addr,uint32_t data_length)	//读取数据
{
FLASH_WaitForWriteEnd(); //等待写入完成
SPI_FLASH_CS_Low(); //拉低CS片选线,选中FLASH
SPI_FLASH_SendRecive(W25X_ReadData); //发送读取命令
SPI_FLASH_SendRecive((addr & 0xFF0000) >>16); //发送地址的高8位
SPI_FLASH_SendRecive((addr & 0xFF00)>>8); //发送地址的中8位
SPI_FLASH_SendRecive(addr & 0xFF); //发送地址的低8位
while(data_length--) //循环读取数据
{
*databuff = SPI_FLASH_SendRecive(Dummy_Byte); //读取数据
databuff++; //指针后移
}
SPI_FLASH_CS_High(); //拉高CS片选线,取消选中FLASH
FLASH_WaitForWriteEnd(); //等待写入完成
}

STM32SPIFLASH读写的更多相关文章

  1. Hadoop 中利用 mapreduce 读写 mysql 数据

    Hadoop 中利用 mapreduce 读写 mysql 数据   有时候我们在项目中会遇到输入结果集很大,但是输出结果很小,比如一些 pv.uv 数据,然后为了实时查询的需求,或者一些 OLAP ...

  2. 【造轮子】打造一个简单的万能Excel读写工具

    大家工作或者平时是不是经常遇到要读写一些简单格式的Excel? shit!~很蛋疼,因为之前吹牛,就搞了个这东西,还算是挺实用,和大家分享下. 厌烦了每次搞简单类型的Excel读写?不怕~来,喜欢流式 ...

  3. ArcGIS 10.0紧凑型切片读写方法

    首先介绍一下ArcGIS10.0的缓存机制: 切片方案 切片方案包括缓存的比例级别.切片尺寸和切片原点.这些属性定义缓存边界的存在位置,在某些客户端中叠加缓存时匹配这些属性十分重要.图像格式和抗锯齿等 ...

  4. socket读写返回值的处理

    在调用socket读写函数read(),write()时,都会有返回值.如果没有正确处理返回值,就可能引入一些问题 总结了以下几点 1当read()或者write()函数返回值大于0时,表示实际从缓冲 ...

  5. Hyper-V无法文件拖拽解决方案~~~这次用一个取巧的方法架设一个FTP来访问某个磁盘,并方便的读写文件

    异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 服务器相关的知识点:http://www.cnblogs.com/dunitia ...

  6. mybatis plugins实现项目【全局】读写分离

    在之前的文章中讲述过数据库主从同步和通过注解来为部分方法切换数据源实现读写分离 注解实现读写分离: http://www.cnblogs.com/xiaochangwei/p/4961807.html ...

  7. 计算机程序的思维逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库

    57节介绍了字节流, 58节介绍了字符流,它们都是以流的方式读写文件,流的方式有几个限制: 要么读,要么写,不能同时读和写 不能随机读写,只能从头读到尾,且不能重复读,虽然通过缓冲可以实现部分重读,但 ...

  8. Spark读写Hbase的二种方式对比

    作者:Syn良子 出处:http://www.cnblogs.com/cssdongl 转载请注明出处 一.传统方式 这种方式就是常用的TableInputFormat和TableOutputForm ...

  9. C++标准库实现WAV文件读写

    在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库. WAV文件结构 ...

  10. CSharpGL(33)使用uniform块来优化对uniform变量的读写

    CSharpGL(33)使用uniform块来优化对uniform变量的读写 +BIT祝威+悄悄在此留下版了个权的信息说: Uniform块 如果shader程序变得比较复杂,那么其中用到的unifo ...

随机推荐

  1. vue报错:'XX' is defined but never used no-unused-vars

    参考地址:http://www.gold404.cn/info/87 导致这个报错的原因就是eslint校验, 就是你当初在vue创建脚手架的时候选择了eslint校验: 后面你绝对会碰到这样的报错, ...

  2. 使用C#做为游戏开发的服务器语言方案

    Scut开源服务器 开源C#/Python/Lua 手游服务器 主页:http://www.scutgame.com/index.html 开源:https://github.com/ScutGame ...

  3. Unity2020或Unity2019安装后无法启动

    无法启动Unity 下载国际版的Unity2020,双击Unity.exe无法启动,通过Unity Hub也无法启动 ​ 原因 通过查看unity hub的日志发现Unity 启动的时候会检查 lie ...

  4. 【云原生】为什么要虚拟化,为什么要容器,为什么要Docker,为什么要K8S?

    前言 如标题中的问题所提到的虚拟化,容器,Docker和K8s那样,我们不妨这样问:这些技术到底适用于哪些场景,有没有别的技术可以替代?这些技术的优劣在哪里? 下面我将针对性地从以上几个问题的出发点, ...

  5. 6.2 Windows驱动开发:内核枚举SSSDT表基址

    在Windows内核中,SSSDT(System Service Shadow Descriptor Table)是SSDT(System Service Descriptor Table)的一种变种 ...

  6. C++ LibCurl实现Web隐藏目录扫描

    LibCurl是一个开源的免费的多协议数据传输开源库,该框架具备跨平台性,开源免费,并提供了包括HTTP.FTP.SMTP.POP3等协议的功能,使用libcurl可以方便地进行网络数据传输操作,如发 ...

  7. 20.6 OpenSSL 套接字分发RSA公钥

    通过上一节的学习读者应该能够更好的理解RSA加密算法在套接字传输中的使用技巧,但上述代码其实并不算完美的,因为我们的公钥和私钥都必须存储在本地文本中且公钥与私钥是固定的无法做到更好的保护效果,而一旦公 ...

  8. 21.14 Python 实现Web指纹识别

    在当今的Web安全行业中,识别目标网站的指纹是渗透测试的常见第一步.指纹识别的目的是了解目标网站所使用的技术栈和框架,从而进一步根据目标框架进行针对性的安全测试,指纹识别的原理其实很简单,目前主流的识 ...

  9. C#9中使用静态匿名函数

    匿名函数是很早以前在C#编程语言中引入的.尽管匿名功能有很多好处,但它们并不便宜.避免不必要的分配很重要,这就是为什么在C#9中引入静态匿名函数的原因.在C#9中,lambda或匿名方法可以具有静态修 ...

  10. P3509 [POI2010] ZAB-Frog 题解

    题目链接:ZAB-Frog 基于一个根据距离第 \(k\) 大的事实: 容易知道,对于红色的点而言,与它相近最近的 \(k\) 个点是连续的.而第 \(k\) 远的要么是最左侧要么是最右侧.而我们注意 ...