SPI-读写FLASH
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相关推荐
- STM32笔记(十二)---SPI读写FLASH
SPI读写FLASH 文章目录 SPI读写FLASH 一.SPI协议简介 1.1 SPI 物理层 1.2 协议层 1.2.1 SPI 基本通讯过程 1.2.2 通讯的起始和停止信号 1.2.3 数据有 ...
- STM32F103学习笔记——SPI读写Flash(二)
此系列文章是小白学习STM32的一些学习笔记.小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流! 目录 1.软件设计流程 2.SPI初始化 3.SPI发送接收一字节函数编写 4.FLASH ...
- 《STM32从零开始学习历程》——SPI读写FLASH
<STM32从零开始学习历程>@EnzoReventon SPI读写FLASH 相关链接: SPI物理层及FLASH芯片介绍 SPI协议层 SPI特性及架构 参考资料: [野火EmbedF ...
- STM32CUBEIDE之SPI读写FLASH进阶串行FLASH文件系统FatFs
预备知识 >>W25Q128是16M spi flash,一共有256个block ,每个Block 64KB. >>一个Block可以分割为16个扇区(small secto ...
- STM32F103配合STM32CubeMX实现SPI读写flash
本人采用的是正点原子的精英STM32F103开发板,其包含一块W25Q128型号的flash芯片.该flash与STM32F103的SPI2相连. 下面根据正点原子提供的开发指南文档,实现FreeRT ...
- STM32F429入门(二十一):SPI协议及SPI读写FLASH
IIC主要用于通讯速率一般的场合,而SPI一般用于较高速的场合. 一.SPI协议简介 SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设 ...
- STM32 SPI读写FLASH
文章目录 一.SPI协议 1.物理层 2.协议层 总体讲解 具体讲解 二.STM32 SPI外设 1.通讯引脚 2.时钟控制逻辑 3.数据控制 4.整体控制逻辑 三.通信过程 四.固件库编程 1.结构 ...
- AXI Quad SPI读写Flash做远程升级
未经允许,本文禁止转载 目录 简介 AXI Quad SPI IP设置 寄存器说明 AXI Quad SPI支持的通用命令 读flash id 读flash 数据 擦除扇区 写flash 数据 注意事 ...
- SPI读写FLASH 原理+完整代码
引言 实现SPI通讯,对FLASH进行读写.读取FLASH的ID信息,写入数据,并读取出来进行校验,通过串口打印写入与读取出来的数据,输出测试结果. 一.SPI总线 SPI通信的基础知识 SPI是串行 ...
- stm32中spi可以随便接吗_STM32的SPI模式读写FLASH芯片全面讲解
例程完整代码: SPI协议简介 SPI协议,即串行外围设备接口,是一种告诉全双工的通信总线,它被广泛地使用在ADC,LCD等设备与MCU间通信的场合. SPI信号线 SPI包含4条总线,分别为SS,S ...
最新文章
- 使用Navicat创建数据库,外键出现错误ERROR 1005: Can't create table (errno: 121)
- Linq怎么支持Monad
- BZOJ 3144 [HNOI2013]切糕 (最大流+巧妙的建图)
- Cassandra 3.x官方文档(1)---关于Cassandra
- Educational Codeforces Round 80 (Rated for Div. 2) E. Messenger Simulator 思维 + 树状数组
- U3D性能分析 Profiling
- android自定义布局中的平滑移动
- 设计HTML标签title属性值换行
- 关于HP M125-M126的无线链接方案
- 软件测试你的简历是这样的吗?
- Typora任意更改样式
- c语言用后缀字母表示不同数制,C语言基础知识总结
- GBase XDM 模型概要
- DRM驱动(六)之atomic_check
- iis和tomcat5整合
- 关于AD17 原理图设置差分出现Number of nets in differential pair ** is 1 instead of 2的问题
- linux系统用户和普通用户,适合普通Linux用户的五大Linux发行版
- MSELoss() 函数
- ibdp课程体系中要选择经济课吗?
- 计算机人离开后保护,设置屏幕保护密码防止他人在自己离开时偷窥
热门文章
- 主流IBM-HP-EMC存储产品比较
- 学计算机vr什么笔记本电脑好,最好的笔记本电脑推荐2017 哪些机型的游戏本配置高...
- An NVIDIA kernel module ‘nvidia-uvm‘ appears to already be loaded in your kernel...解决方案
- 爱情心理学2(致敬张晓文老师)
- ensp华为路由器ARP与ICMP的抓包
- 003-计算机应用基础 统考,计算机应用基础 试题003
- 使用U盘重装Windows系统
- @RequestBody接收Json参数 | 用自定义注解对Vo对象中Date类型日期格式校验
- Qz学算法-数据结构篇(表达式、递归)
- 掘金者:中国创业者十大素质之欲