
《STM32从零开始学习历程》——SPI读写FLASH
由上图可以看出:SPI的SCK,MISO,MOSI分别与芯片的PB3,PB4,PB5连接,片选信号F_CS与PB14相连接,因此在后面程序配置的时候需要注意不能配置错引脚。
发布日期:2021-05-10 01:33:10
浏览次数:23
分类:精选文章
本文共 13654 字,大约阅读时间需要 45 分钟。
《STM32从零开始学习历程》
SPI读写FLASH
相关链接:
参考资料:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》 [正点原子]STM32F4开发指南-库函数版本_V1.2 [ST]《STM32F4xx中文参考手册》1 实现功能
- 实现对FLASH一扇区的擦除
- 对该FLASH扇区写入数据,并读取该数据,通过串口调试助手进行调试观察
- 写入256个数据,并读取写入的数据以及其余未写入的数据。
- 对该扇区所有数据都写入。
2 硬件设计
本文使用的外设为SPI1(正点原子F4探索者开发板)、FLASH以及USART1。
USART用来调试程序,我们还是使用USART1,因此将PB9,PB10与TX,RX相连接即可。查阅正点原子F4探索者开发板硬件手册,了解SPI引脚与GPIO的对应情况。


3 软件设计流程
① 使能SPIx和IO口时钟
RCC_AHBxPeriphClockCmd() / RCC_APBxPeriphClockCmd(); ② 初始化IO口为复用功能 void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); ③ 设置引脚复用映射: GPIO_PinAFConfig(); ② 初始化SPIx,设置SPIx工作模式 void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct); ③ 使能SPIx void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState); ④ 编写字节发送函数:uint8_t SPI_FLASH_ByteWrite(uint8_t data)发送数据(指令):void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
接收返回的数据:uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
⑤ 编写擦除扇区函数:void SPI_FLASH_Erase_Sector(uint32_t addr);
⑥ 编写写入数据函数:void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size);
⑦ 编写读取数据函数:void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size); ⑧ 查看SPI传输状态 SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);⑨ 编写SPI发送函数
控制片选引脚:GPIO_ResetBits()、GPIO_SetBits(); ⑩ 主函数调用,优化程序(超时函数、宏定义、等待空闲函数、写使能函数)4 代码分析
- 宏定义
这里大家可以按照自己的需求以及编程习惯进行定义,宏定义的好处在于便于程序的移植,方便改动程序。
/*命令定义-开头*/#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 /*SPI GPIO 接口*/#define FLASH_SPI SPI1#define FLASH_SPI_CLK RCC_APB2Periph_SPI1#define FLASH_SPI_CLK_INIT RCC_APB2PeriphClockCmd#define FLASH_SPI_SCK_PIN GPIO_Pin_3 #define FLASH_SPI_SCK_GPIO_PORT GPIOB #define FLASH_SPI_SCK_GPIO_CLK RCC_AHB1Periph_GPIOB#define FLASH_SPI_SCK_SOURCE GPIO_PinSource3#define FLASH_SPI_SCK_AF GPIO_AF_SPI1#define FLASH_SPI_MOSI_PIN GPIO_Pin_5 #define FLASH_SPI_MOSI_GPIO_PORT GPIOB #define FLASH_SPI_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOB#define FLASH_SPI_MOSI_SOURCE GPIO_PinSource5#define FLASH_SPI_MOSI_AF GPIO_AF_SPI1#define FLASH_SPI_MISO_PIN GPIO_Pin_4 #define FLASH_SPI_MISO_GPIO_PORT GPIOB #define FLASH_SPI_MISO_GPIO_CLK RCC_AHB1Periph_GPIOB#define FLASH_SPI_MISO_SOURCE GPIO_PinSource4#define FLASH_SPI_MISO_AF GPIO_AF_SPI1#define FLASH_SPI_CS_PIN GPIO_Pin_14 #define FLASH_SPI_CS_GPIO_PORT GPIOB #define FLASH_SPI_CS_GPIO_CLK RCC_AHB1Periph_GPIOB#define FLASH_SPI_CS_LOW() GPIO_ResetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN)#define FLASH_SPI_CS_HIGH() GPIO_SetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN)/*等待超时时间*/#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
- 函数申明
/*函数申明*/static void SPI_GPIO_Config(void); //引脚初始化函数void SPI_Mode_Config(void); //SPI模式初始化函数void SPI_FLASH_Init(void); //SPI FLASH外设初始化调用函数uint8_t SPI_FLASH_ByteWrite(uint8_t data); //字节写入函数uint8_t SPI_FLASH_Read_ID(void); //SPI读取FLASH ID函数void SPI_FLASH_Erase_Sector(uint32_t addr); //FLASH擦除函数void SPI_FLASH_Write_Enable(void); //SPI写使能函数void SPI_FLASH_Wait_For_Standby(void); //SPI等待直到空闲函数void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size); //SPI连续读函数void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size); //SPI连续写函数void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size); //SPI页写入函数
SPI子函数:
- 静态函数申明:
//申明超时变量static uint32_t SPITimeOut = ((uint32_t)(10 * SPIT_FLAG_TIMEOUT));//申明错误代码返回函数static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
- SPI GPIO初始化 复用函数
/*================================================SPI GPIO初始化 复用函数================================================*/static void SPI_GPIO_Config(void){ GPIO_InitTypeDef GPIO_InitStructure; FLASH_SPI_CLK_INIT(FLASH_SPI_CLK, ENABLE); //使能SPI时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1) /* 使能 FLASH_SPI 及GPIO 时钟 */ /*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO, SPI_FLASH_SPI_MISO_GPIO,SPI_FLASH_SPI_SCK_GPIO 时钟使能 */ RCC_AHB1PeriphClockCmd(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MOSI_GPIO_CLK| FLASH_SPI_MISO_GPIO_CLK|FLASH_SPI_CS_GPIO_CLK, ENABLE); //初始化GPIO时钟 //查看引脚,初始化SCK,MOSI,MISO,CS引脚GPIO //设置引脚复用 GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT, FLASH_SPI_SCK_SOURCE, FLASH_SPI_SCK_AF); GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT, FLASH_SPI_MOSI_SOURCE, FLASH_SPI_MOSI_AF); GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT, FLASH_SPI_MISO_SOURCE, FLASH_SPI_MISO_AF); /*!< 配置 SPI_FLASH_SPI 引脚: SCK */ GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure); /*!< 配置 SPI_FLASH_SPI 引脚: MISO */ GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN; GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure); /*!< 配置 SPI_FLASH_SPI 引脚: MOSI */ GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN; GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure); /*!< 配置 SPI_FLASH_SPI 引脚: CS */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(FLASH_SPI_CS_GPIO_PORT, &GPIO_InitStructure);}/*================================================SPI 初始化结构体初始化================================================*/void SPI_Mode_Config(void){ /*!< 初始化SPI结构体函数 */ SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge; SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low; SPI_InitStructure.SPI_CRCPolynomial=7; 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(FLASH_SPI,&SPI_InitStructure); SPI_Cmd(FLASH_SPI,ENABLE);}
- SPI FLASH 外设初始化
================================================SPI FLASH 外设初始化================================================*/void SPI_FLASH_Init(void){ SPI_GPIO_Config(); SPI_Mode_Config();}
- SPI字节写入
/*================================================通过SPI发送一个字节参数:要写入的数据返回值:错误代码================================================*/uint8_t SPI_FLASH_ByteWrite(uint8_t data){ uint8_t re_data; //等待TXE标志 SPITimeOut = SPIT_FLAG_TIMEOUT; while(SPI_I2S_GetFlagStatus (FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET ) { if((SPITimeOut--) == 0) return SPI_TIMEOUT_UserCallback(1); } SPI_I2S_SendData(FLASH_SPI, data); //等待RXNE标志 来确认发送完成,及准备读取数据 SPITimeOut = SPIT_FLAG_TIMEOUT; while(SPI_I2S_GetFlagStatus (FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET ) { if((SPITimeOut--) == 0) return SPI_TIMEOUT_UserCallback(2); } re_data = SPI_I2S_ReceiveData(FLASH_SPI); return re_data;}
- 读取FLASH ID
/*================================================读ID0-ID7================================================*/uint8_t SPI_FLASH_Read_ID(void){ uint8_t id; //控制片选引脚 FLASH_SPI_CS_LOW(); //指令代码 SPI_FLASH_ByteWrite(W25X_ReleasePowerDown); SPI_FLASH_ByteWrite(0xFF); SPI_FLASH_ByteWrite(0xFF); SPI_FLASH_ByteWrite(0xFF); //接收读取到的内容 id = SPI_FLASH_ByteWrite(0xFF); FLASH_SPI_CS_HIGH(); return id;}
- 擦除FLASH扇区函数
/*================================================擦除扇区addr:必须对齐到要擦除的扇区的首地址================================================*/void SPI_FLASH_Erase_Sector(uint32_t addr){ //写使能 SPI_FLASH_Write_Enable(); //控制片选引脚 FLASH_SPI_CS_LOW(); //指令代码 SPI_FLASH_ByteWrite(W25X_SectorErase); //发送要擦除的地址 SPI_FLASH_ByteWrite((addr>>16) & 0xFF); SPI_FLASH_ByteWrite((addr>>8) & 0xFF); SPI_FLASH_ByteWrite(addr & 0xFF); FLASH_SPI_CS_HIGH(); //等待内部时序完成 SPI_FLASH_Wait_For_Standby();}
- 页写入函数
/*================================================写入数据addr:要写入数据的首地址,buf:存储写入的数据的指针size:要写入多少个数据 不超过256================================================*/void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size){ //写使能 SPI_FLASH_Write_Enable(); //控制片选引脚 FLASH_SPI_CS_LOW(); //指令代码 SPI_FLASH_ByteWrite(W25X_PageProgram); //发送要写入的地址 SPI_FLASH_ByteWrite((addr>>16) & 0xFF); SPI_FLASH_ByteWrite((addr>>8) & 0xFF); SPI_FLASH_ByteWrite(addr & 0xFF); while(size--) { SPI_FLASH_ByteWrite(*buf); buf++; } FLASH_SPI_CS_HIGH(); //等待内部时序完成 SPI_FLASH_Wait_For_Standby();}
- 读取数据函数
/*================================================读取数据addr:要读取数据的首地址,buf:存储读取到的数据的指针size:要读取多少个数据================================================*/void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size){ //控制片选引脚 FLASH_SPI_CS_LOW(); //指令代码 SPI_FLASH_ByteWrite(W25X_ReadData); //发送要读取的地址 SPI_FLASH_ByteWrite((addr>>16) & 0xFF); SPI_FLASH_ByteWrite((addr>>8) & 0xFF); SPI_FLASH_ByteWrite(addr & 0xFF); while(size--) { *buf = SPI_FLASH_ByteWrite(0xFF); buf++; } FLASH_SPI_CS_HIGH(); }
- 写入数据,不受256数据限制
/*================================================写入数据addr:要写入数据的首地址,buf:要写入的数据的指针size:要写入多少个数据 不超过256================================================*/void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size){ uint32_t count=0;//计算循环次数 while(size--) { count++; //第一次执行,第257次,256*2+1,256*3+1,addr对齐到4096时 if(count == 1 || (count%256) ==1 || (addr%4096)==0) { //结束上一次的页写入指令 FLASH_SPI_CS_HIGH(); //等待上一次页写入的完成 SPI_FLASH_Wait_For_Standby(); //写使能,每次写入前都必须调用 SPI_FLASH_Write_Enable(); //控制片选引脚 FLASH_SPI_CS_LOW(); //指令代码 SPI_FLASH_ByteWrite(W25X_PageProgram); //发送要写入的地址 SPI_FLASH_ByteWrite((addr>>16) & 0xFF); SPI_FLASH_ByteWrite((addr>>8) & 0xFF); SPI_FLASH_ByteWrite(addr & 0xFF); } SPI_FLASH_ByteWrite(*buf); buf++; addr++; } FLASH_SPI_CS_HIGH(); //等待内部时序完成 SPI_FLASH_Wait_For_Standby();}
- 写使能函数
/*================================================写使能================================================*/void SPI_FLASH_Write_Enable(void){ //控制片选引脚 FLASH_SPI_CS_LOW(); //指令代码 SPI_FLASH_ByteWrite(W25X_WriteEnable); FLASH_SPI_CS_HIGH();}
- 等待直到空闲函数
/*================================================等待直到空闲状态================================================*/void SPI_FLASH_Wait_For_Standby(void){ uint8_t status ; //控制片选引脚 FLASH_SPI_CS_LOW(); //指令代码 0x05 检测读取标志位 SPI_FLASH_ByteWrite(W25X_ReadStatusReg); SPITimeOut = SPIT_LONG_TIMEOUT; while(1) { status = SPI_FLASH_ByteWrite(0xFF); //如果条件成立,说明为空闲状态 if((status & 0x01) == 0) break; //若SPITimeout为0,表示已检测SPITimeout次都仍为忙碌,跳出循环 if((SPITimeOut--)==0) { SPI_TIMEOUT_UserCallback(3); break; } } FLASH_SPI_CS_HIGH();}
- 错误代码 返回函数
/*================================================错误代码返回函数参数:写如的错误代码返回值:错误代码================================================*/static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode){ /* Block communication and all processes */ printf("\r\nSPI 等待超时!errorCode = %d\r\n",errorCode); return errorCode;}
主函数
uint8_t read_buff[4096] = { 0};uint8_t write_buff[4096] = { 0};int main(void){ int i = 0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2 delay_init(168); //延时初始化 uart_init(115200); //串口初始化波特率为115200 LED_Init(); //初始化与LED连接的硬件接口 SPI_FLASH_Init(); printf("\r\n=======================================\r\n"); printf("this is id : 0x%x",SPI_FLASH_Read_ID()); printf("\r\n擦除开始"); //擦除测试 SPI_FLASH_Erase_Sector(4096*0); //擦除测试 SPI_FLASH_Erase_Sector(4096*1); printf("\r\n擦除完成"); SPI_FLASH_Read_Buff(0,read_buff,4096); for(i=0;i<4096;i++) { //若不等于0xFF,说明擦除不成功 if(read_buff[i] != 0xFF) { printf("\r\n擦除失败"); } } printf("\r\n擦除完成"); //初始化要写入的数据 for(i=0;i<256;i++) { write_buff[i] = i; } printf("\r\n开始写入"); SPI_FLASH_Write_Buff(0,write_buff,256); //写入数据 0为扇区首地址 write_buff为数据地址,256为写入数据数量 printf("\r\n写入完成"); SPI_FLASH_Read_Buff(0,write_buff,4096); printf("\r\n读取到的数据:\r\n"); for(i=0;i<4096;i++) //循环打印输出 { printf("0x%02x ",write_buff[i]); } while (1) { } }
5 效果展示
- 写入256个数据。
- 对整个扇区写入数据。 修改主函数部分参数。
//初始化要写入的数据 for(i=0;i<4096;i++) { write_buff[i] = i; } printf("\r\n开始写入"); SPI_FLASH_Write_Buff(0,write_buff,4096); printf("\r\n写入完成"); SPI_FLASH_Read_Buff(0,write_buff,4096); printf("\r\n读取到的数据:\r\n"); for(i=0;i<4096;i++) { printf("0x%02x ",write_buff[i]); }
发表评论
最新留言
初次前来,多多关照!
[***.217.46.12]2025年04月27日 13时32分07秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
学习spring security 5~入门
2021-05-10
ArcEngine代码 GP区域分析之面积制表(统计各行政区内的各土地利用类型面积)
2021-05-10
ArcEngine代码 GP提取分析之筛选
2021-05-10
Flutter-Dart version solving failed
2021-05-10
常见状态码
2021-05-10
重定向
2021-05-10
MYISAM存储引擎
2021-05-10
什么情况必须使用 statement
2021-05-10
账号转账演示事务
2021-05-10
HDML BS结构和CS结构介绍
2021-05-10
java中的集合回顾-collections工具类进行一个集合排序
2021-05-10
idea创建工程时错误提醒的是architectCatalog=internal
2021-05-10
E - Another Postman Problem FZU - 2038
2021-05-10
图解redis(二)
2021-05-10
input type="checkbox" 样式美化
2021-05-10
【Java】 # 对于日期Date类的相关操作
2021-05-10
【JavaLearn】 # 培训(一)—— JavaSE查漏补缺
2021-05-10
SpringBoot找不到@EnableRety注解
2021-05-10
JQuery 基础 || 目前 jQuery 有三个大版本||JQuery快速入门
2021-05-10
简易计算器案例
2021-05-10