SPI-读写FLASH

SPI协议层

​ SPI协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通讯总线。它广泛地使用在ADC、LCD等设备与MCU间,要求通讯速率较高的场合。


SPI物理层

​ SPI通讯设备之间的常用连接方式如下图所示:

​ SPI通讯使用3条总线及片选线,3条总线分别为:SCL、MOSI、MISO,片选线为:SS#,他们的作用介绍如下:

1、SS#(Slave Select):从设备选择信号线,常称为片选信号线,也成为NSS、CS,以下用NSS表示。当有多个SPI从设备与SPI主机相连时,设备的其他信号线SCK、MOSI和MISO同样并联到相同的SPI总线上,即无论有多少个从设备,都共同只使用这3条总线;而每个从设备都有独立的这一条NSS信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少片选信号线。I2C协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而SPI协议中没有设备地址,它使用NSS信号线来寻址,当主机要选择从设备时,把该从设备的NSS信号线设置成低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以NSS线置低电平为开始信号,以NSS线被拉高作为结束信号。
2.SCK(Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如STM32的SPI时钟频率最大为Fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
3.MOSI(Master Output,Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读取主机发送的数据,即这条线上数据的方向为主机到从机。
4.MISO(Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据从这条信号线传输到主机,即这条线上数据的方向为从机到主机。

协议层

​ 与I2C的类似,SPI协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。

SPI基本通讯协议

​ 先看看SPI通讯的通讯时序:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwoTqVlB-1625201360986)(C:\Users\asus\Desktop\STM32-库函数-授课内容\SPI-读写FLASH\SPI-读写FLASH(图片)]\SPI通讯时序.png)

​ 这是一个主机的通讯时序。NSS、SCK、MOSI信号都是由主机控制产生,而MISO的信号由从机产生,主机通过该信号线读取从机的数据。MOSI与MISO的信号只在NSS为低电平的时候才有效,在SCK的每个时钟周期MOSI和MISO传输一位数据。

通讯的起始和停止信号

NSS信号线由高变低,是SPI通讯的起始信号。NSS是每个从机各自独占的信号线,当从机在自己的NSS线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。

NSS信号由低变高,是SPI通讯的停止信号。表示本次通讯结束,从机的选中状态被取消。

数据有效性

​ SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同步进行的。数据传输时,MSB先行或LSB先行并没有作硬性规定,但要保证两个SPI通讯设备之间使用同样的协定,一般都会采用MSB先行模式。

​ MOSI及MISO的数据在SCK的上升沿器件变化输出,在SCK的下降沿被采样。即在SCK的下降沿时刻,MOSI及MISO的数据有效,高电平表示数据"1",为低电平时表示数据"0"。在其他时刻,数据无效,MOSI及MISO为下一次表示数据做准备。

​ SPI每次数据传输可以8位或16位为单位,每次传输的单位数不受限制。

CPOL/CPHA及通讯模式

​ SPI一共有4种通讯模式,他们主要区别是总线空闲时SCK的时钟状态及数据采样时刻。

​ 时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、NSS线为高电平时SCK的状态)。

CPOL=0时,SCK在空闲状态时为低电平。
CPOL=1时,SCK在空闲状态时为高电平。

​ 时钟相位CPHA是指数据的采样的时刻,

当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。
当CPHA=1时,MOSI或MISO数据线上的信号将会在SCK时钟线的“偶数边沿”被采样。

我们来分析这个CPHA=0的时序图。首先,根据SCK在空闲状态时的电平,分为两种情况。SCK信号线在空闲状态为低电平时,CPOL=0;空闲状态为高电平时,CPOL=1。
无论CPOL=0还是=1,因为我们配置的时钟相位CPHA=0,在图中可以看到,采样时刻都是在SCK的奇数边沿。
注意当CPOL=0 的时候,时钟的奇数边沿是上升沿,而CPOL=1的时候,时钟的奇数边沿是下降沿。所以SPI 的采样时刻不是由上升/下降沿决定
的。MOSI和MISO数据线的有效信号在SCK的奇数边沿保持不变,数据信号将在SCK奇数边沿时被采样,在非采样时刻,MOSI和MISO的有效信号才发生切换。

​ 类似地,当CPHA=1时,不受CPOL的影响,数据信号在SCK的偶数边沿被采样,
​ 见图25-4。

​ 由CPOL即CPHA的不同状态,SPI分为了4种状态模式

SP模式 CPOL CPHA 空闲时SCK时钟 采样时刻
0 0 0 低电平 奇数边沿
1 0 1 低电平 偶数边沿
2 1 0 高电平 奇数边沿
3 1 1 高电平 偶数边沿

SPI特性即架构

​ STM32的SPI外设可用作通讯的主机及从机,支持最高的SCK时钟频率为Fpclk/2(STM32F103型号的芯片默认Fpclk1为72MHz,Fpclk2为36MHz),完全支持SPI协议的4种模式,数据帧长度可设置为8位或16位,可设置数据MSB先行或LSB先行。它还支持双线全双工、双线单向以及单线模式。其中双线单向模式可以同时使用MOSI及MISO数据线向一个方向传输数据,可以加快一倍的传输速率。而单线模式则可以减少硬件接线,当然这样速率会受到影响。

​ 我们使用双线全双工模式。

通讯引脚

​ SPI1是APB2上的设备,最高通信速率达36Mbit/s,SPI2、SPI3是APB1上的设备,最高的通讯速率为18Mbit/s。

​ 除了通讯速率,在其他功能上没有差异,其中SPI3用到了下载接口的引脚,这几个引脚默认功能是下载,第二功能才是IO口,如果想使用SPI3接口,则程序上必须先禁用这几个IO口的下载功能。一般在资源不是十分紧张的情况下,这几个IO口是专门用于下载和调试程序,不会复用为SPI3。

时钟控制逻辑

​ SCK线的时钟信号,由波特率发生器根据"控制寄存器CR1"种的BR[0:2]位控制,该位是对Fpclk时钟的分频因子,对Fpclk的分频结果就是SCK引脚的输出时钟频率,计算方法如下表所示:

BR[0:2] 分频结果(SCK频率) BR[0:2] 分频结果(SCK频率)
000 Fpclk/2 100 Fpclk/32
001 Fpclk/4 101 Fpclk/64
010 Fpclk/8 110 Fpclk/128
011 Fpclk/16 111 Fpclk/256

​ 其中,Fpclk频率是指SPI所在的APB总线频率,APB1位Fpclk1,APB2为Fpclk2。

​ 通过配置“控制寄存器CR”的“CPOL位”及“CPHA”位可以把SPI设置成前面分析的4种SPI模式。

数据控制逻辑

​ SPI的MOSI及MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源于目标接收、发送缓冲区以及MISO、MOSI线。

当向外发送数据的时候,数据移位寄存器以“发送缓冲区”为数据源,把数据一位一位地通过数据线发送出去;

当从外部接收数据的时候,数据移位寄存器把数据线采样到的数据一位一位地存储到“接收缓冲区”中。

​ 通过写SPI的“数据寄存器DR”把数据填充到发送F缓冲区中,通讯读“数据寄存器DR",可以获取接收缓冲区中的内容。其中数据帧长度可以通过“控制寄存器CR1”的“DFF位”配置成8位及16位模式;配置“LSBFIRST位”可选择MSB先行还是LSB先行。

整体控制逻辑

​ 整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR 1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式等等。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,我们只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。
实际应用中,我们一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用,普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号


通讯过程

​ 下图中是”主模式“流程,即STM32作为SPI通讯的主机端时的数据收发过程。

主模式收发流程及事件说明如下:
(1) 控制NSS信号线,产生起始信号(图中没有画出);
(2)把要发送的数据写入到“数据寄存器DR”中,该数据会被存储到发送缓冲区;
(3)通讯开始,SCK时钟开始运行。MOSI把发送缓冲区中的数据一位一位地传输出去;MISO则把数据一位一位地存储进接收缓冲区中;
(4)当发送完一帧数据的时候, “状态寄存器 SR"中的“TXE标志位”会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置1,表示传输完一帧,接收缓冲区非空;
(5) 等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE标志位”为1时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。

​ 假如我们使能了TXE 或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入同一个中断服务函数,到SPI中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用DMA方式来收发“数据寄存器DR”中的数据。


SPI-读写串行FLASH实验

​ FLSAH存储器又称闪存,它与EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于EEPROM,现在基本取代了它的地位。我们生活中常用的U盘、SD卡、SSD固态硬盘以及我们STM32芯片内部用于存储程序的设备,都是FLASH类型的存储器。

​ 在存储控制上,最主要的区别是FLASH芯片只能一大片一大片地擦写,而在“I2C 章节”中我们了解到EEPROM可以单个字节擦写。

​ 本小节以一种使用SPI 通讯的串行FLASH存储芯片的读写实验为大家讲解STM32的SPI使用方法。实验中STM32的SPI外设采用主模式,通过查询事件的方式来确保正常通讯。

​ FLASH芯片中还有WP和HOLD引脚。WP引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。

​ HOLD引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能。


第一步,为了确保通讯正常,先验证FLASH的ID号,如果正常,再进行下一步,不正常的话,说明连接失败。

读取ID号

SPI_FLASH.C文件:
#include "./spi_flash/bsp_spi_flash.h"uint32_t SPI_FLASH_Delay = SPI_FLASH_DelayLongTime;//SPI_FLASH  GPIO 配置
static void SPI_FLASH_GPIO_Init(void)
{GPIO_InitTypeDef   GPIO_InitStructure = {0};//开启各个GPIO的时钟SPI_FLASH_SCK_RCC_CLK_CMD(SPI_FLASH_SCK_RCC_CLK_Periph,ENABLE);SPI_FLASH_MISO_RCC_CLK_CMD(SPI_FLASH_MISO_RCC_CLK_Periph,ENABLE);SPI_FLASH_MOSI_RCC_CLK_CMD(SPI_FLASH_MOSI_RCC_CLK_Periph,ENABLE);SPI_FLASH_NSS_RCC_CLK_CMD(SPI_FLASH_NSS_RCC_CLK_Periph,ENABLE);//时钟线 GPIO 配置 推挽复用输出GPIO_InitStructure.GPIO_Pin = SPI_FLASH_SCK_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(SPI_FLASH_SCK_GPIO_PORT,&GPIO_InitStructure);//MISO GPIO 配置 推挽复用输出GPIO_InitStructure.GPIO_Pin = SPI_FLASH_MISO_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(SPI_FLASH_MISO_GPIO_PORT,&GPIO_InitStructure);//MOSI GPIO 配置 推挽复用输出GPIO_InitStructure.GPIO_Pin = SPI_FLASH_MOSI_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(SPI_FLASH_MOSI_GPIO_PORT,&GPIO_InitStructure);//片选 GPIO 配置 普通推挽输出GPIO_InitStructure.GPIO_Pin = SPI_FLASH_NSS_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(SPI_FLASH_NSS_GPIO_PORT,&GPIO_InitStructure);//不选中片选信号SPI_FLASH_NSS_HIGH;
}
//SPI_FLASH 模式配置
static void SPI_FLASH_Mode_Init(void)
{SPI_InitTypeDef    SPI_InitStructure = {0};//开启SPI的时钟SPI_FLASH_X_RCC_CLK_CMD(SPI_FLASH_X_RCC_CLK_Periph,ENABLE);//模式配置SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CRCPolynomial = 0;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_Init(SPI_FLASH_X,&SPI_InitStructure);//开启SPI时钟SPI_Cmd(SPI_FLASH_X,ENABLE);
}
void SPI_FLASH_Init(void)
{SPI_FLASH_GPIO_Init();SPI_FLASH_Mode_Init();
}
//写一个错误回调函数
static uint32_t SPI_FLASH_ErrorBackFunction(uint8_t ErrorCode)
{SPI_FLASH_ERROR("SPI 连接超时!错误代码为:%d\n",ErrorCode);return 0;
}//写一个发送数据的函数
uint8_t SPI_FLASH_Send_Byte(uint8_t Data)
{//给判断信号成功与否,一个判断次数变量赋值SPI_FLASH_Delay = SPI_FLASH_DelayTime;//检查并等待TXN是否为1,如果是1,就可以发送数据了,否则等待while(SPI_I2S_GetFlagStatus(SPI_FLASH_X,SPI_I2S_FLAG_TXE) == RESET){while((SPI_FLASH_Delay--) == 0){return SPI_FLASH_ErrorBackFunction(0);}}//给判断信号成功与否,一个判断次数变量赋值SPI_FLASH_Delay = SPI_FLASH_DelayTime;//现在寄存器是空的,我们可以发送数据了SPI_I2S_SendData(SPI_FLASH_X,Data);//判断发送是否成功while(SPI_I2S_GetFlagStatus(SPI_FLASH_X,SPI_I2S_FLAG_RXNE) == RESET){while((SPI_FLASH_Delay--) == 0){return SPI_FLASH_ErrorBackFunction(1);}     }//由于是同步的,可以把发送的数据返回读出return (SPI_I2S_ReceiveData(SPI_FLASH_X));
}
//读取发送的内容
uint8_t SPI_FLASH_Read_Byte(void)
{return SPI_FLASH_Send_Byte(DUMMY);
}
//写了发送和接收函数,那我们首先判断一下FLASH的ID号是否正确,正确了在进行下一步,否则判断错误
uint32_t SPI_FLASH_Read_ID(void)
{//定义一个读取ID号的存储变量uint32_t SPI_FLASH_ID = 0;//拉低片选电平,通讯开始SPI_FLASH_NSS_LOW;//发送指令数据SPI_FLASH_Send_Byte(Read_JEDEC_ID);//读取ID号SPI_FLASH_ID = SPI_FLASH_Send_Byte(DUMMY);//这里读到的高位数据,8个SPI_FLASH_ID <<= 8;SPI_FLASH_ID |= SPI_FLASH_Send_Byte(DUMMY);//这里读到的中位数据,8个SPI_FLASH_ID <<= 8;  SPI_FLASH_ID |= SPI_FLASH_Send_Byte(DUMMY);//这里读到的低位数据,8个   //拉高片选电平,通讯结束SPI_FLASH_NSS_HIGH;return SPI_FLASH_ID;
}

SPI_FLASH.H文件:
#ifndef  __BSP_SPI_FLASH_H
#define  __BSP_SPI_FLASH_H#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#define     SPI_FLASH_DelayTime             ((uint32_t)0x1000)
#define     SPI_FLASH_DelayLongTime         ((uint32_t)(10*SPI_FLASH_DelayTime))#define     SPI_FLASH_DEBUG_ON          0
#define     SPI_FLASH_INFO(fmt,arg...)              printf("<<-SPI_FLASH_INFO->>"fmt"\n",##arg)
#define     SPI_FLASH_ERROR(fmt,arg...)             printf("<<-SPI_FLASH_ERROR->>"fmt"\n",##arg)
#define     SPI_FLASH_DEBUG(fmt,arg...)             DO{\if(SPI_FLASH_DEBUG_ON)\printf("<<-SPI_FLASH_DEBUG->>[%s][%d]"fmt"'n",__FILE__,__LINE__,##arg);\}while(0)#define     SPI_FLASH_X                                SPI1
#define     SPI_FLASH_X_RCC_CLK_CMD                 RCC_APB2PeriphClockCmd
#define     SPI_FLASH_X_RCC_CLK_Periph              RCC_APB2Periph_SPI1
//SPI_SCK宏定义   时钟线
#define     SPI_FLASH_SCK_GPIO_PORT                 GPIOA
#define     SPI_FLASH_SCK_GPIO_PIN                  GPIO_Pin_5
#define     SPI_FLASH_SCK_RCC_CLK_CMD               RCC_APB2PeriphClockCmd
#define     SPI_FLASH_SCK_RCC_CLK_Periph            RCC_APB2Periph_GPIOA
//SPI_MISO宏定义       主机输入,从机输出
#define     SPI_FLASH_MISO_GPIO_PORT                GPIOA
#define     SPI_FLASH_MISO_GPIO_PIN                 GPIO_Pin_6
#define     SPI_FLASH_MISO_RCC_CLK_CMD              RCC_APB2PeriphClockCmd
#define     SPI_FLASH_MISO_RCC_CLK_Periph           RCC_APB2Periph_GPIOA
//SPI_MOSI宏定义   主机输出,从机输入
#define     SPI_FLASH_MOSI_GPIO_PORT                GPIOA
#define     SPI_FLASH_MOSI_GPIO_PIN                 GPIO_Pin_7
#define     SPI_FLASH_MOSI_RCC_CLK_CMD              RCC_APB2PeriphClockCmd
#define     SPI_FLASH_MOSI_RCC_CLK_Periph           RCC_APB2Periph_GPIOA
//CS 片选引脚宏定义   软件引脚,非SPI内部的NSS引脚
#define     SPI_FLASH_NSS_GPIO_PORT                 GPIOC
#define     SPI_FLASH_NSS_GPIO_PIN                  GPIO_Pin_0
#define     SPI_FLASH_NSS_RCC_CLK_CMD               RCC_APB2PeriphClockCmd
#define     SPI_FLASH_NSS_RCC_CLK_Periph            RCC_APB2Periph_GPIOC//选中从设备,讲片选引脚拉低,即可
#define     SPI_FLASH_NSS_LOW                       GPIO_ResetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN)
//不选从设备,讲片选引脚拉高,即可
#define     SPI_FLASH_NSS_HIGH                      GPIO_SetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN)#define      DUMMY                  0x00
#define      Read_JEDEC_ID          0x9F
extern void SPI_FLASH_Init(void);
extern uint8_t SPI_FLASH_Send_Byte(uint8_t Data);
extern uint8_t SPI_FLASH_Read_Byte(void);
extern uint32_t SPI_FLASH_Read_ID(void);
#endif

#include "stm32f10x.h"
#include "bsp_led.h"
#include "./usart/bsp_usart.h"
#include "./spi_flash/bsp_spi_flash.h"int main(void)
{       USART_Config();SPI_FLASH_Init();printf("ID号为:0x%x",SPI_FLASH_Read_ID());while (1){}
}

扇区擦除并读取擦除的内容

​ 我们知道写入Flash要先擦除,所以,下面就是擦除函数,然后擦除后,扇区的内容均为1,串口打印数据完全正确。

//有了上面的确保了ID号正常了,我们就该向FLASH中写入数据并检查数据写入与否的读写函数了
//由于FLASH要先擦除 才能写入,所以我们不妨先擦除,再读一下数据,看是否擦除成功。
//因为FLASH中擦出了之后全部都会变成1.所以我们可以通过串口打印出来看看
//为了能写入数据,再写一个写使能函数的代码,加入写函数中,确保写入正常。
void SPI_FLASH_Write_Enable(void)
{//拉低片选信号,表示信号开始SPI_FLASH_NSS_LOW;//发送指令代码:0x06SPI_FLASH_Send_Byte(Write_Enable);//拉高片选信号,表示通讯停止SPI_FLASH_NSS_HIGH;
}
//跟I2C一样,写一个等待FLASH内部时序完成的函数
void SPI_FLASH_WaitForWriteEnd(void)
{//定义一个状态变量,判断最低为是否为1,为1表示忙,否则就是写完了uint8_t States_Reg = 0;//拉低片选信号,表示信号开始SPI_FLASH_NSS_LOW;//发送指令代码:0x05SPI_FLASH_Send_Byte(READ_STATUS);do{States_Reg = SPI_FLASH_Send_Byte(DUMMY);}while((States_Reg&0x01) == 1);//拉高片选信号,表示通讯停止SPI_FLASH_NSS_HIGH;
}
//扇区擦除函数,形参:擦除哪里的地址
void SPI_FLASH_Erase_Sector(uint32_t Address)
{//写使能SPI_FLASH_Write_Enable();//拉低片选信号,表示信号开始SPI_FLASH_NSS_LOW;//发送指令代码:0x20SPI_FLASH_Send_Byte(Sector_Erase);SPI_FLASH_Send_Byte((Address>>16)&0xff);//发送高8位SPI_FLASH_Send_Byte((Address>>8)&0xff);//发送中8位SPI_FLASH_Send_Byte(Address&0xff);//发送低8位//拉高片选信号,表示通讯停止SPI_FLASH_NSS_HIGH;   SPI_FLASH_WaitForWriteEnd();
}
//扇区擦除了,检查是否擦除正确,写一个读取函数,判断是否全是1
//形参1:读哪个地方的地址
//形参2:读取的数据放在那里,数据缓冲区
//形参3:读多少个数据
void SPI_FLASH_Read_Data(uint32_t Addr,uint8_t *readBuff,uint32_t NumOfRead)
{//写使能SPI_FLASH_Write_Enable();//拉低片选信号,表示信号开始SPI_FLASH_NSS_LOW;//发送指令代码:0x03SPI_FLASH_Send_Byte(READ_Data);SPI_FLASH_Send_Byte((Addr>>16)&0xff);//发送高8位SPI_FLASH_Send_Byte((Addr>>8)&0xff);//发送中8位SPI_FLASH_Send_Byte(Addr&0xff);//读发送低8位while(NumOfRead--){*readBuff = SPI_FLASH_Send_Byte(DUMMY);readBuff++;}//拉高片选信号,表示通讯停止SPI_FLASH_NSS_HIGH; SPI_FLASH_WaitForWriteEnd();
}
#ifndef  __BSP_SPI_FLASH_H
#define  __BSP_SPI_FLASH_H#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#define     SPI_FLASH_DelayTime             ((uint32_t)0x1000)
#define     SPI_FLASH_DelayLongTime         ((uint32_t)(10*SPI_FLASH_DelayTime))#define     SPI_FLASH_DEBUG_ON          0
#define     SPI_FLASH_INFO(fmt,arg...)              printf("<<-SPI_FLASH_INFO->>"fmt"\n",##arg)
#define     SPI_FLASH_ERROR(fmt,arg...)             printf("<<-SPI_FLASH_ERROR->>"fmt"\n",##arg)
#define     SPI_FLASH_DEBUG(fmt,arg...)             DO{\if(SPI_FLASH_DEBUG_ON)\printf("<<-SPI_FLASH_DEBUG->>[%s][%d]"fmt"'n",__FILE__,__LINE__,##arg);\}while(0)#define     SPI_FLASH_X                                SPI1
#define     SPI_FLASH_X_RCC_CLK_CMD                 RCC_APB2PeriphClockCmd
#define     SPI_FLASH_X_RCC_CLK_Periph              RCC_APB2Periph_SPI1
//SPI_SCK宏定义   时钟线
#define     SPI_FLASH_SCK_GPIO_PORT                 GPIOA
#define     SPI_FLASH_SCK_GPIO_PIN                  GPIO_Pin_5
#define     SPI_FLASH_SCK_RCC_CLK_CMD               RCC_APB2PeriphClockCmd
#define     SPI_FLASH_SCK_RCC_CLK_Periph            RCC_APB2Periph_GPIOA
//SPI_MISO宏定义       主机输入,从机输出
#define     SPI_FLASH_MISO_GPIO_PORT                GPIOA
#define     SPI_FLASH_MISO_GPIO_PIN                 GPIO_Pin_6
#define     SPI_FLASH_MISO_RCC_CLK_CMD              RCC_APB2PeriphClockCmd
#define     SPI_FLASH_MISO_RCC_CLK_Periph           RCC_APB2Periph_GPIOA
//SPI_MOSI宏定义   主机输出,从机输入
#define     SPI_FLASH_MOSI_GPIO_PORT                GPIOA
#define     SPI_FLASH_MOSI_GPIO_PIN                 GPIO_Pin_7
#define     SPI_FLASH_MOSI_RCC_CLK_CMD              RCC_APB2PeriphClockCmd
#define     SPI_FLASH_MOSI_RCC_CLK_Periph           RCC_APB2Periph_GPIOA
//CS 片选引脚宏定义   软件引脚,非SPI内部的NSS引脚
#define     SPI_FLASH_NSS_GPIO_PORT                 GPIOC
#define     SPI_FLASH_NSS_GPIO_PIN                  GPIO_Pin_0
#define     SPI_FLASH_NSS_RCC_CLK_CMD               RCC_APB2PeriphClockCmd
#define     SPI_FLASH_NSS_RCC_CLK_Periph            RCC_APB2Periph_GPIOC//选中从设备,讲片选引脚拉低,即可
#define     SPI_FLASH_NSS_LOW                       GPIO_ResetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN)
//不选从设备,讲片选引脚拉高,即可
#define     SPI_FLASH_NSS_HIGH                      GPIO_SetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN)#define      DUMMY                  0x00
#define      Read_JEDEC_ID          0x9F
#define      Write_Enable           0x06
#define      Sector_Erase           0x20
#define      READ_STATUS            0x05
#define      READ_Data              0x03extern void SPI_FLASH_Init(void);
extern uint8_t SPI_FLASH_Send_Byte(uint8_t Data);
extern uint8_t SPI_FLASH_Read_Byte(void);
extern uint32_t SPI_FLASH_Read_ID(void);
extern void SPI_FLASH_Write_Enable(void);
extern void SPI_FLASH_WaitForWriteEnd(void);
extern void SPI_FLASH_Erase_Sector(uint32_t Address);
extern void SPI_FLASH_Read_Data(uint32_t Addr,uint8_t *readBuff,uint32_t NumOfRead);#endif
#include "stm32f10x.h"
#include "bsp_led.h"
#include "./usart/bsp_usart.h"
#include "./spi_flash/bsp_spi_flash.h"uint8_t ReadBuff[4096];int main(void)
{uint16_t i = 0;USART_Config();SPI_FLASH_Init();printf("\r\n ID号为:0x%x \r\n",SPI_FLASH_Read_ID());SPI_FLASH_Erase_Sector(0);SPI_FLASH_Read_Data(0,ReadBuff,4096);for(i=0;i<4096;i++){printf("%x ",ReadBuff[i]);}while (1){}
}

​ 有了扇区擦除了,接下来就可以写入我们想写入的数据了,下面就是写入函数。

扇区擦除后,写入数据

//写入数据函数,一页只允许写256字节
void SPI_FLASH_WriteData(uint8_t Addr,uint8_t *WriteData,uint32_t NumOfWrite)
{//写使能SPI_FLASH_Write_Enable();//拉低片选信号,表示信号开始SPI_FLASH_NSS_LOW;//发送指令代码:0x02SPI_FLASH_Send_Byte(Page_Write);SPI_FLASH_Send_Byte((Addr>>16)&0xff);//发送高8位SPI_FLASH_Send_Byte((Addr>>8)&0xff);//发送中8位SPI_FLASH_Send_Byte(Addr&0xff);//读发送低8位while(NumOfWrite--){SPI_FLASH_Send_Byte(*WriteData);WriteData++;}//拉高片选信号,表示通讯停止SPI_FLASH_NSS_HIGH;  SPI_FLASH_WaitForWriteEnd();
}
#ifndef  __BSP_SPI_FLASH_H
#define  __BSP_SPI_FLASH_H#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#define     SPI_FLASH_DelayTime             ((uint32_t)0x1000)
#define     SPI_FLASH_DelayLongTime         ((uint32_t)(10*SPI_FLASH_DelayTime))#define     SPI_FLASH_DEBUG_ON          0
#define     SPI_FLASH_INFO(fmt,arg...)              printf("<<-SPI_FLASH_INFO->>"fmt"\n",##arg)
#define     SPI_FLASH_ERROR(fmt,arg...)             printf("<<-SPI_FLASH_ERROR->>"fmt"\n",##arg)
#define     SPI_FLASH_DEBUG(fmt,arg...)             DO{\if(SPI_FLASH_DEBUG_ON)\printf("<<-SPI_FLASH_DEBUG->>[%s][%d]"fmt"'n",__FILE__,__LINE__,##arg);\}while(0)#define     SPI_FLASH_X                                SPI1
#define     SPI_FLASH_X_RCC_CLK_CMD                 RCC_APB2PeriphClockCmd
#define     SPI_FLASH_X_RCC_CLK_Periph              RCC_APB2Periph_SPI1
//SPI_SCK宏定义   时钟线
#define     SPI_FLASH_SCK_GPIO_PORT                 GPIOA
#define     SPI_FLASH_SCK_GPIO_PIN                  GPIO_Pin_5
#define     SPI_FLASH_SCK_RCC_CLK_CMD               RCC_APB2PeriphClockCmd
#define     SPI_FLASH_SCK_RCC_CLK_Periph            RCC_APB2Periph_GPIOA
//SPI_MISO宏定义       主机输入,从机输出
#define     SPI_FLASH_MISO_GPIO_PORT                GPIOA
#define     SPI_FLASH_MISO_GPIO_PIN                 GPIO_Pin_6
#define     SPI_FLASH_MISO_RCC_CLK_CMD              RCC_APB2PeriphClockCmd
#define     SPI_FLASH_MISO_RCC_CLK_Periph           RCC_APB2Periph_GPIOA
//SPI_MOSI宏定义   主机输出,从机输入
#define     SPI_FLASH_MOSI_GPIO_PORT                GPIOA
#define     SPI_FLASH_MOSI_GPIO_PIN                 GPIO_Pin_7
#define     SPI_FLASH_MOSI_RCC_CLK_CMD              RCC_APB2PeriphClockCmd
#define     SPI_FLASH_MOSI_RCC_CLK_Periph           RCC_APB2Periph_GPIOA
//CS 片选引脚宏定义   软件引脚,非SPI内部的NSS引脚
#define     SPI_FLASH_NSS_GPIO_PORT                 GPIOC
#define     SPI_FLASH_NSS_GPIO_PIN                  GPIO_Pin_0
#define     SPI_FLASH_NSS_RCC_CLK_CMD               RCC_APB2PeriphClockCmd
#define     SPI_FLASH_NSS_RCC_CLK_Periph            RCC_APB2Periph_GPIOC//选中从设备,讲片选引脚拉低,即可
#define     SPI_FLASH_NSS_LOW                       GPIO_ResetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN)
//不选从设备,讲片选引脚拉高,即可
#define     SPI_FLASH_NSS_HIGH                      GPIO_SetBits(SPI_FLASH_NSS_GPIO_PORT,SPI_FLASH_NSS_GPIO_PIN)#define      DUMMY                  0x00
#define      Read_JEDEC_ID          0x9F
#define      Write_Enable           0x06
#define      Sector_Erase           0x20
#define      READ_STATUS            0x05
#define      READ_Data              0x03
#define      Page_Write             0x02extern void SPI_FLASH_Init(void);
extern uint8_t SPI_FLASH_Send_Byte(uint8_t Data);
extern uint8_t SPI_FLASH_Read_Byte(void);
extern uint32_t SPI_FLASH_Read_ID(void);
extern void SPI_FLASH_Write_Enable(void);
extern void SPI_FLASH_WaitForWriteEnd(void);
extern void SPI_FLASH_Erase_Sector(uint32_t Address);
extern void SPI_FLASH_Read_Data(uint32_t Addr,uint8_t *readBuff,uint32_t NumOfRead);
extern void SPI_FLASH_WriteData(uint8_t Addr,uint8_t *WriteData,uint32_t NumOfWrite);#endif
#include "stm32f10x.h"
#include "bsp_led.h"
#include "./usart/bsp_usart.h"
#include "./spi_flash/bsp_spi_flash.h"uint8_t ReadBuff[4096];
uint8_t WriteBuff[256];int main(void)
{uint16_t i = 0;USART_Config();SPI_FLASH_Init();printf("\r\n ID号为:0x%x \r\n",SPI_FLASH_Read_ID());SPI_FLASH_Erase_Sector(0);SPI_FLASH_Read_Data(0,ReadBuff,4096);for(i=0;i<4096;i++){printf("%x ",ReadBuff[i]);}printf("\n");//扇区擦除了,这里开始写入数据for(i=0;i<256;i++){WriteBuff[i] = i+1;}SPI_FLASH_WriteData(0,WriteBuff,256);//一页只能写入256字节,根据数据手册的Page_Write得知的SPI_FLASH_Read_Data(0,ReadBuff,256);for(i=0;i<256;i++){printf("%x ",ReadBuff[i]);}while (1){}
}

学有所成,以图强国!

_usart.h"
#include “./spi_flash/bsp_spi_flash.h”

uint8_t ReadBuff[4096];
uint8_t WriteBuff[256];

int main(void)
{
uint16_t i = 0;
USART_Config();
SPI_FLASH_Init();
printf("\r\n ID号为:0x%x \r\n",SPI_FLASH_Read_ID());

SPI_FLASH_Erase_Sector(0);
SPI_FLASH_Read_Data(0,ReadBuff,4096);for(i=0;i<4096;i++)
{printf("%x ",ReadBuff[i]);
}printf("\n");//扇区擦除了,这里开始写入数据
for(i=0;i<256;i++)
{WriteBuff[i] = i+1;
}
SPI_FLASH_WriteData(0,WriteBuff,256);//一页只能写入256字节,根据数据手册的Page_Write得知的
SPI_FLASH_Read_Data(0,ReadBuff,256);
for(i=0;i<256;i++)
{printf("%x ",ReadBuff[i]);
}while (1)
{}

}


---**学有所成,以图强国!**

SPI-读写FLASH相关推荐

  1. STM32笔记(十二)---SPI读写FLASH

    SPI读写FLASH 文章目录 SPI读写FLASH 一.SPI协议简介 1.1 SPI 物理层 1.2 协议层 1.2.1 SPI 基本通讯过程 1.2.2 通讯的起始和停止信号 1.2.3 数据有 ...

  2. STM32F103学习笔记——SPI读写Flash(二)

      此系列文章是小白学习STM32的一些学习笔记.小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流! 目录 1.软件设计流程 2.SPI初始化 3.SPI发送接收一字节函数编写 4.FLASH ...

  3. 《STM32从零开始学习历程》——SPI读写FLASH

    <STM32从零开始学习历程>@EnzoReventon SPI读写FLASH 相关链接: SPI物理层及FLASH芯片介绍 SPI协议层 SPI特性及架构 参考资料: [野火EmbedF ...

  4. STM32CUBEIDE之SPI读写FLASH进阶串行FLASH文件系统FatFs

    预备知识 >>W25Q128是16M spi flash,一共有256个block ,每个Block 64KB. >>一个Block可以分割为16个扇区(small secto ...

  5. STM32F103配合STM32CubeMX实现SPI读写flash

    本人采用的是正点原子的精英STM32F103开发板,其包含一块W25Q128型号的flash芯片.该flash与STM32F103的SPI2相连. 下面根据正点原子提供的开发指南文档,实现FreeRT ...

  6. STM32F429入门(二十一):SPI协议及SPI读写FLASH

    IIC主要用于通讯速率一般的场合,而SPI一般用于较高速的场合. 一.SPI协议简介 SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设 ...

  7. STM32 SPI读写FLASH

    文章目录 一.SPI协议 1.物理层 2.协议层 总体讲解 具体讲解 二.STM32 SPI外设 1.通讯引脚 2.时钟控制逻辑 3.数据控制 4.整体控制逻辑 三.通信过程 四.固件库编程 1.结构 ...

  8. AXI Quad SPI读写Flash做远程升级

    未经允许,本文禁止转载 目录 简介 AXI Quad SPI IP设置 寄存器说明 AXI Quad SPI支持的通用命令 读flash id 读flash 数据 擦除扇区 写flash 数据 注意事 ...

  9. SPI读写FLASH 原理+完整代码

    引言 实现SPI通讯,对FLASH进行读写.读取FLASH的ID信息,写入数据,并读取出来进行校验,通过串口打印写入与读取出来的数据,输出测试结果. 一.SPI总线 SPI通信的基础知识 SPI是串行 ...

  10. stm32中spi可以随便接吗_STM32的SPI模式读写FLASH芯片全面讲解

    例程完整代码: SPI协议简介 SPI协议,即串行外围设备接口,是一种告诉全双工的通信总线,它被广泛地使用在ADC,LCD等设备与MCU间通信的场合. SPI信号线 SPI包含4条总线,分别为SS,S ...

最新文章

  1. 使用Navicat创建数据库,外键出现错误ERROR 1005: Can't create table (errno: 121)
  2. Linq怎么支持Monad
  3. BZOJ 3144 [HNOI2013]切糕 (最大流+巧妙的建图)
  4. Cassandra 3.x官方文档(1)---关于Cassandra
  5. Educational Codeforces Round 80 (Rated for Div. 2) E. Messenger Simulator 思维 + 树状数组
  6. U3D性能分析 Profiling
  7. android自定义布局中的平滑移动
  8. 设计HTML标签title属性值换行
  9. 关于HP M125-M126的无线链接方案
  10. 软件测试你的简历是这样的吗?
  11. Typora任意更改样式
  12. c语言用后缀字母表示不同数制,C语言基础知识总结
  13. GBase XDM 模型概要
  14. DRM驱动(六)之atomic_check
  15. iis和tomcat5整合
  16. 关于AD17 原理图设置差分出现Number of nets in differential pair ** is 1 instead of 2的问题
  17. linux系统用户和普通用户,适合普通Linux用户的五大Linux发行版
  18. MSELoss() 函数
  19. ibdp课程体系中要选择经济课吗?
  20. 计算机人离开后保护,设置屏幕保护密码防止他人在自己离开时偷窥

热门文章

  1. 主流IBM-HP-EMC存储产品比较
  2. 学计算机vr什么笔记本电脑好,最好的笔记本电脑推荐2017 哪些机型的游戏本配置高...
  3. An NVIDIA kernel module ‘nvidia-uvm‘ appears to already be loaded in your kernel...解决方案
  4. 爱情心理学2(致敬张晓文老师)
  5. ensp华为路由器ARP与ICMP的抓包
  6. 003-计算机应用基础 统考,计算机应用基础 试题003
  7. 使用U盘重装Windows系统
  8. @RequestBody接收Json参数 | 用自定义注解对Vo对象中Date类型日期格式校验
  9. Qz学算法-数据结构篇(表达式、递归)
  10. 掘金者:中国创业者十大素质之欲