STM32SPIFLASH读写
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读写的更多相关文章
- Hadoop 中利用 mapreduce 读写 mysql 数据
Hadoop 中利用 mapreduce 读写 mysql 数据 有时候我们在项目中会遇到输入结果集很大,但是输出结果很小,比如一些 pv.uv 数据,然后为了实时查询的需求,或者一些 OLAP ...
- 【造轮子】打造一个简单的万能Excel读写工具
大家工作或者平时是不是经常遇到要读写一些简单格式的Excel? shit!~很蛋疼,因为之前吹牛,就搞了个这东西,还算是挺实用,和大家分享下. 厌烦了每次搞简单类型的Excel读写?不怕~来,喜欢流式 ...
- ArcGIS 10.0紧凑型切片读写方法
首先介绍一下ArcGIS10.0的缓存机制: 切片方案 切片方案包括缓存的比例级别.切片尺寸和切片原点.这些属性定义缓存边界的存在位置,在某些客户端中叠加缓存时匹配这些属性十分重要.图像格式和抗锯齿等 ...
- socket读写返回值的处理
在调用socket读写函数read(),write()时,都会有返回值.如果没有正确处理返回值,就可能引入一些问题 总结了以下几点 1当read()或者write()函数返回值大于0时,表示实际从缓冲 ...
- Hyper-V无法文件拖拽解决方案~~~这次用一个取巧的方法架设一个FTP来访问某个磁盘,并方便的读写文件
异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 服务器相关的知识点:http://www.cnblogs.com/dunitia ...
- mybatis plugins实现项目【全局】读写分离
在之前的文章中讲述过数据库主从同步和通过注解来为部分方法切换数据源实现读写分离 注解实现读写分离: http://www.cnblogs.com/xiaochangwei/p/4961807.html ...
- 计算机程序的思维逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库
57节介绍了字节流, 58节介绍了字符流,它们都是以流的方式读写文件,流的方式有几个限制: 要么读,要么写,不能同时读和写 不能随机读写,只能从头读到尾,且不能重复读,虽然通过缓冲可以实现部分重读,但 ...
- Spark读写Hbase的二种方式对比
作者:Syn良子 出处:http://www.cnblogs.com/cssdongl 转载请注明出处 一.传统方式 这种方式就是常用的TableInputFormat和TableOutputForm ...
- C++标准库实现WAV文件读写
在上一篇文章RIFF和WAVE音频文件格式中对WAV的文件格式做了介绍,本文将使用标准C++库实现对数据为PCM格式的WAV文件的读写操作,只使用标准C++库函数,不依赖于其他的库. WAV文件结构 ...
- CSharpGL(33)使用uniform块来优化对uniform变量的读写
CSharpGL(33)使用uniform块来优化对uniform变量的读写 +BIT祝威+悄悄在此留下版了个权的信息说: Uniform块 如果shader程序变得比较复杂,那么其中用到的unifo ...
随机推荐
- 高性能MySQL实战(三):性能优化 | 京东物流技术团队
这篇主要介绍对慢 SQL 优化的一些手段,而在讲解具体的优化措施之前,我想先对 EXPLAIN 进行介绍,它是我们在分析查询时必要的操作,理解了它输出结果的内容更有利于我们优化 SQL.为了方便大家的 ...
- 责任链和策略设计模式-基于Java编程语言
作者:京东物流 钟磊 1 前言 最近在梳理接口逻辑的时候发现,代码中使用的策略和责任链设计模式给我留下了非常深刻的印象.一个业务逻辑流程通常非常适合使用责任链和策略设计模式来实现,因为一个业务需求通常 ...
- JS中every的简单使用
every 方法 every()方法用于检测数组中的所有元素是否都满足指定条件. every()方法会遍历数组的每一项,如果有一项不满足条件,则返回false,剩余的项将不会再执行检测. 如果遍历完数 ...
- 防止xxs攻击,input表单中不能输入script标签
在web网页中,所有的项目中.input表单中不能让用户输入script这些敏感性的. 一旦出现提示用户非正常输入.然后立刻将值清空 <el-input style="width:35 ...
- elementui表格内容超出显示省略号
有些时候表格的内容太长了: 但是elementui中的表格,会进行换行处理: 此时表格的高度就会发生变化 这样就不好看,此时就要进行省略号来出来这个问题: el-table是有这个控制属性的::sho ...
- Redis如何批量删除指定前缀的key
批量删除指定前缀的Key有两中方法,一种是借助 redis-cli,另一种是通过 SCAN 命令来遍历所有匹配前缀的 key,并使用 DEL 命令逐个删除它们. redis-cli 使用 Redis ...
- AsNoTracking()非跟踪数据 查询
刚开始学习使用EF ,做项目时需要查询数据将数据显示在datagrid中,使用如下方法: query是IQueryable的 在一次看别人写的代码的时候,发现了AsNoTracking()这个方法,并 ...
- AiTrust下预训练和小样本学习在中文医疗信息处理挑战榜CBLUE表现
项目链接: https://aistudio.baidu.com/aistudio/projectdetail/4592515?contributionType=1 如果有图片缺失参考项目链接 0.项 ...
- 驱动开发:内核封装TDI网络通信接口
在上一篇文章<驱动开发:内核封装WSK网络通信接口>中,LyShark已经带大家看过了如何通过WSK接口实现套接字通信,但WSK实现的通信是内核与内核模块之间的,而如果需要内核与应用层之间 ...
- C/C++ 操作注册表与服务
枚举注册表启动项: 通过添加注册表启动项,可以很方便地完成自启动,常用的启动位置有CurrentVersion,BootExecute,Active Setup. #include <stdio ...