实验内容:使用硬件SPI读写串行FLASH(W25Q64) 。

一、原理图

二、 CubeMX配置

Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。

Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。

Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)

Step4.串口配置(主要为了在串口调试助手显示读写数据),因为没有用到中断和DMA所以我们就不过多讲解。

step5.SPI外设配置,这里选用的SPI1。因为没有使用到中断和DMA所以只需要把硬件SPI基本参数配置好就行。

step6.因为是采用软件NSS,所以还需要配置相应的IO口。

到这里关于硬件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。

三、添加功能代码

1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。

//重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{/* 发送一个字节数据到串口DEBUG_USART */HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);   return (ch);
}//重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{       int ch;HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);  return (ch);
}

2、在gpio.h文件中添加相关的宏定义。因为是使用软件控制NSS,下面宏定义通过IO口模拟NSS信号。(NSS低电平控制,NSS高电平释放)

#define NSS_HIGH() HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET)
#define NSS_LOW()  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET)

3、在spi.h添加宏定义,和函数的声明。方便移植和提高可读性。

#define FLASH_PAGESIZE    256              //W25Q64的页面大小
#define _Flash_ID   0xEF4017
extern SPI_HandleTypeDef hspi1;
void MX_SPI1_Init(void);
uint32_t SPI_FLASH_ReadID(void);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
void SPI_FLASH_PageWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite);
void SPI_FLASH_BufferWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite);
void SPI_FLASH_BufferRead(uint8_t * pBuffer, uint32_t  ReadAddr,uint16_t NumByteToWrite);

然后在spi.c中实现声明函数的具体功能。

/*读取制造商和设备ID*/
uint32_t SPI_FLASH_ReadID(void)
{uint8_t W25X_JEDEC_ID=0X9F;uint8_t temp0[3];uint32_t temp;NSS_LOW();//选择FLASH:NSS低电平HAL_SPI_Transmit(&hspi1,&W25X_JEDEC_ID,1,10);//发送指令HAL_SPI_Receive(&hspi1,temp0,3, 10);//读取制造商和设备IDNSS_HIGH();//停止信号 FLASH:NSS高电平temp=(temp0[0])<<16|(temp0[1]<<8)|temp0[2];//把数据组合起来,作为函数的返回值return temp;
}/*写使能*/
void SPI_FLASH_WriteEnable(void)
{uint8_t W25X_WriteEnable=0x06;NSS_LOW();//选择FLASH:NSS低电平HAL_SPI_Transmit(&hspi1,&W25X_WriteEnable,1,10);//发送指令NSS_HIGH();//停止信号 FLASH:NSS高电平
}/*扇区擦除*/
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{uint8_t W25X_SectorErase=0x20;uint8_t temp1,temp2,temp3;SPI_FLASH_WriteEnable();//写使能NSS_LOW();//选择FLASH:NSS低电平HAL_SPI_Transmit(&hspi1,&W25X_SectorErase,1,10);//发送指令temp1=(SectorAddr&(0xFF0000))>>16;//发送地址HAL_SPI_Transmit(&hspi1,&temp1,1,10);temp2=(SectorAddr&(0x00FF00))>>8;HAL_SPI_Transmit(&hspi1,&temp2,1,10);temp3=(SectorAddr&(0x0000FF));HAL_SPI_Transmit(&hspi1,&temp3,1,10);NSS_HIGH();//停止信号 FLASH:NSS高电平SPI_FLASH_WaitForWriteEnd();//等待擦除完毕
}/*等待 WIP(BUSY) 标志被置 0,即等待到 FLASH 内部数据写入完毕*/
void SPI_FLASH_WaitForWriteEnd(void)
{uint8_t W25X_ReadStatusReg=0x05;uint8_t temp;NSS_LOW();//选择FLASH:NSS低电平HAL_SPI_Transmit(&hspi1,&W25X_ReadStatusReg,1,10);//发送指令do{HAL_SPI_Receive(&hspi1,&temp,1,10);// 读取 FLASH 芯片的状态寄存器}while((temp&0x01)==1);NSS_HIGH();//停止信号 FLASH:NSS高电平
}/*在FLASH的一个写循环中可以写多个字节,但一次写入
的字节数不能超过FLASH页的大小,W25Q64每页有256个字节*/
void SPI_FLASH_PageWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite)
{uint8_t W25X_PageProgram=0x02;uint8_t temp1,temp2,temp3;SPI_FLASH_WriteEnable();//写使能NSS_LOW();//选择FLASH:NSS低电平HAL_SPI_Transmit(&hspi1,&W25X_PageProgram,1,10);//发送指令temp1=(WriteAddr&(0xFF0000))>>16;//发送地址HAL_SPI_Transmit(&hspi1,&temp1,1,10);temp2=(WriteAddr&(0x00FF00))>>8;HAL_SPI_Transmit(&hspi1,&temp2,1,10);temp3=(WriteAddr&(0x0000FF));HAL_SPI_Transmit(&hspi1,&temp3,1,10);HAL_SPI_Transmit(&hspi1,pBuffer,NumByteToWrite,10);//发送数据NSS_HIGH();//停止信号 FLASH:NSS高电平SPI_FLASH_WaitForWriteEnd();//等待写入完毕
}/*不定量数据写入*/
void SPI_FLASH_BufferWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite)
{uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;Addr = WriteAddr % FLASH_PAGESIZE;//判断写入的首地址是否与EEPROM页的首地址对齐,0为对齐count = FLASH_PAGESIZE - Addr;//计算从写入的首地址需要写多少数据才能填满当前页NumOfPage =  NumByteToWrite / FLASH_PAGESIZE;//计算写入数据需要写几个完整页(地址对齐的情况)NumOfSingle = NumByteToWrite % FLASH_PAGESIZE;//计算写完完整页剩下的数据个数(地址对齐的情况)if(Addr == 0) //判断写入的首地址是否与页地址对齐{if(NumOfPage == 0) //如果页对齐,判断数据是否不满一页{SPI_FLASH_PageWrite(pBuffer,WriteAddr,NumOfSingle);//如果不满一页,直接写入数据}else  //在数据满一页的情况下,通过地址自增方式,循环写入数据(页写入的形式){while(NumOfPage--)//循环写入数据:先写入完整页{SPI_FLASH_PageWrite(pBuffer, WriteAddr, FLASH_PAGESIZE); WriteAddr +=  FLASH_PAGESIZE;pBuffer += FLASH_PAGESIZE;}if(NumOfSingle!=0)//循环写入数据:再写入不满一页的数据{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle); }}}else {if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页{if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页{    SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据}else{SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据SPI_FLASH_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);}}else{/*如果数据满一页,对数据进行分离*/NumByteToWrite -= count;//扣除第一页数据个数NumOfPage =  NumByteToWrite / FLASH_PAGESIZE;//计算写入数据需要写几个完整页NumOfSingle = NumByteToWrite % FLASH_PAGESIZE;   //计算写完完整页剩下的数据个数if(count != 0){  SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);//写入首页数据WriteAddr += count;//写地址自增pBuffer += count;//缓冲区指针自增} while(NumOfPage--)//依次写入完整页的数据{SPI_FLASH_PageWrite(pBuffer, WriteAddr, FLASH_PAGESIZE);WriteAddr +=  FLASH_PAGESIZE;//写地址自增pBuffer += FLASH_PAGESIZE; //缓冲区指针自增 }if(NumOfSingle != 0)//判断最后一页的数据是否是填满完整一页的{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);//写入最后一页的数据}}}
}/*读取FLASH数据,读取的数据量没有限制*/
void SPI_FLASH_BufferRead(uint8_t * pBuffer, uint32_t  ReadAddr,uint16_t NumByteToWrite)
{uint8_t W25X_ReadData=0x03;uint8_t temp1,temp2,temp3;SPI_FLASH_WriteEnable();//写使能NSS_LOW();//选择FLASH:NSS低电平HAL_SPI_Transmit(&hspi1,&W25X_ReadData,1,10);//发送指令temp1=(ReadAddr&(0xFF0000))>>16;//发送地址HAL_SPI_Transmit(&hspi1,&temp1,1,10);temp2=(ReadAddr&(0x00FF00))>>8;HAL_SPI_Transmit(&hspi1,&temp2,1,10);temp3=(ReadAddr&(0x0000FF));HAL_SPI_Transmit(&hspi1,&temp3,1,10);HAL_SPI_Receive(&hspi1,pBuffer,NumByteToWrite, 10);//这里超时时间不能设置为1,否则会读取错误NSS_HIGH();//停止信号 FLASH:NSS高电平
}

3、最后在main.c文件中,编写测试代码进行验证。

/*测试代码涉及到变量,宏定义*/
/* 获取缓冲区的长度 *//*sizeof():数组占用字节除以数组类型所占字节,结果为数组元素个数*/
/*使用方法:sizeof(数组名)/ sizeof(数组类型名) */
#define countof(a)      (sizeof(a) / sizeof(*(a)))
#define  BufferSize (countof(Tx_Buffer)-1)#define  FLASH_WriteAddress     0x000006
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddressuint8_t Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength);uint32_t Flash_ID;
uint8_t CMP_RES;
uint8_t Tx_Buffer[] =
"采薇采薇,薇亦作止。曰归曰归,岁亦莫止。靡室靡家,猃狁之故。不遑启居,猃狁之故。\
采薇采薇,薇亦柔止。曰归曰归,心亦忧止。忧心烈烈,载饥载渴。我戍未定,靡使归聘。\
采薇采薇,薇亦刚止。曰归曰归,岁亦阳止。王事靡盬,不遑启处。忧心孔疚,我行不来!\
彼尔维何?维常之华。彼路斯何?君子之车。戎车既驾,四牡业业。岂敢定居?一月三捷。\
驾彼四牡,四牡骙骙。君子所依,小人所腓。四牡翼翼,象弭鱼服。岂不日戒?猃狁孔棘!\
昔我往矣,杨柳依依。今我来思,雨雪霏霏。行道迟迟,载渴载饥。我心伤悲,莫知我哀!";
uint8_t Rx_Buffer[BufferSize];
/*比较函数,用于比较写入和读取的数据是否一致*/
uint8_t Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{while(BufferLength--){if(*pBuffer1 != *pBuffer2){return 0;}pBuffer1++;pBuffer2++;}return 1;
}
/*在主函数编写测试代码:ID验证,读写比较*/
Flash_ID=SPI_FLASH_ReadID();//读取Flash IDif (Flash_ID == _Flash_ID) //判断读取的ID和数据手册的ID是否一致{    printf("\r\n系统检测到SPI FLASH W25Q64 ! FlashID:0x%X\r\n",Flash_ID);/* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */SPI_FLASH_SectorErase(FLASH_SectorToErase);       /* 将发送缓冲区的数据写到flash中 *///SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);printf("\r\n写入的数据为:\r\n%s", Tx_Buffer);/* 将刚刚写入的数据读出来放到接收缓冲区中 */SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);printf("\r\n读出的数据为:\r\n%s", Rx_Buffer);/* 检查写入的数据与读出的数据是否相等 */CMP_RES= Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);if(CMP_RES){    printf("\r\n16M串行flash(W25Q64)测试成功!\n\r");}else{        printf("\r\n16M串行flash(W25Q64)测试失败!\n\r");}}// if (FlashID == sFLASH_ID)else{    printf("\r\n获取不到 W25Q64 ID!\n\r");}            

调试过程碰到几个问题,也是比较无脑的。
1、编写页写入的时候,常规流程:写使能->NSS拉低->发送页编程指令->发送地址->写入数据。
我忘记写发送页编程指令,导致了每次读取的时候,数据前面总是多出一些空白。(数据内容:
空格(发送内容越多,空格越多)+数据)。刚开始在论坛看到了,一个网友也出现类似情况,评论区,都是说等待函数加个延时就可以了,试了下还是不行,后来才发现是指令忘记写了。

HAL_SPI_Transmit(&hspi1,&W25X_PageProgram,1,10);//发送指令

2、还出现一个问题,读取大量数据的时候(超过一页),后面的内容总是读不全。刚开始以为是自己本身写入就有问题,后来用了例程读取我写入的数据,可以正常读取。基本可以锁定是读取函数的问题,看了一下整个读取的流程没有问题,唯一需要更改就是库函数自带的接收函数的参数:超时时间的设置。果然把原先设置的1改成10就可以正常读写了。(因为这个原因,我索性把发送的超时时间也都设置为10,直接设置为1也会正常的就是了。)

HAL_SPI_Receive(&hspi1,pBuffer,NumByteToWrite, 10);

【STM32Cube HAL】SPI(十)相关推荐

  1. stm32cube,hal库来实现PS2手柄数据发送

    stm32cube,hal库来实现PS2手柄数据发送 很久前买了个PS2的手柄,如下,之前 以前不会使用cube来配置工程,导致写程序很麻烦,对我这样的新手很不友好,看卖家提供的程序也很麻烦,拉高拉低 ...

  2. 【STM32Cube HAL】IIC(九)

     实验内容:分别采用软件/硬件IIC读写EEPROM(AT24C02). 一.原理图 -- -- -- -- -- -- -- -- 软件IIC-- -- -- -- -- -- -- -- 二. C ...

  3. STM32cube HAL库 UART串口中断方式收发任意长度 调试笔记

    STM32Cube对于新项目的开发能节省不少时间,从繁琐芯片初始化中解脱出来 1.STM32 UART初始化部分,配置好工程,采用STM32cube生成代码,初始化即已经完成. 2.串口中断方式的发动 ...

  4. STM32cube HAL库两条命令实现i2c通信---Nucleo L476RG用I2C实现tmp117模块温度读取并串口打印

    用stm32 cubemx默认配置i2c1和urart1,本例子是用硬件i2c非模拟i2c /* I2C1 GPIO Configuration PB6 ------> I2C1_SCLPB7 ...

  5. 1、STM32CubeMX和STM32Cube库(HAL)详细介绍

    目录 前言 STM32Cube生态 STM32Cube 是什么? STM32Cube 软件工具套件 STM32Cube Embedded 软件 STM32CubeMX ​编辑 前言 也许大家在学习正点 ...

  6. STM32 HAL库学习笔记1-HAL库简介

    STM32 HAL库学习笔记1-HAL库简介 HAL库 SPL 库 和 HAL 库两者相互独立,互不兼容.几种库的比较如下 目前几种库对不同芯片的支持情况如下 ST 中文官网上有一篇<关于ST库 ...

  7. hal库选择滴答时钟函数_STM32入门 : HAL库、标准外设库、LL库

      国内使用STM32 单片机的人很多,ST 为开发者提供了非常方便的开发库:有标准外设库(SPL 库).HAL 库.LL 库 三种.前者是ST的老库,后两者是ST现在主推的开发库,其中 LL 库是 ...

  8. 正点原子STM32(基于HAL库)0

    目录 开发环境搭建与使用 常用开发工具简介 MDK 安装 仿真器驱动安装 CH340 USB 虚拟串口驱动安装 使用MDK5 编译例程 使用串口下载程序 使用DAP 下载与调试程序 使用DAP 下载程 ...

  9. Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

最新文章

  1. 运行时异常 检查时异常
  2. Failed to load module script: The server responded with a non-JavaScript MIME type of “text/plain“.
  3. 在python程序中的进程操作
  4. bs和cs架构的区别和优缺点_C/S和B/S两种架构区别与优缺点分析
  5. vue父组件传值给字组件
  6. java指针操作符_rxjava 操作符大全
  7. OpenCASCADE:拓扑 API之特征
  8. 判空前后顺序的思考(代码规范)
  9. h5配合css和js如何自定义单选框
  10. c++ 共享内存_Python3.8多进程之共享内存
  11. 基于 Serverless 打造如 Windows 体验的个人专属家庭网盘
  12. 串行口通信c语言代码,问一下单片机串行口通信用c语言实现的问题
  13. JSF MVC 流程
  14. [读书笔记] - 《深度探索C++对象模型》第1章 关于对象
  15. 普通用户添加systemctl 自定义服务的开机启动项
  16. centos 7 安装donet core2.0环境
  17. idea验证失败_解决iPad登陆不了Apple ID验证失败的问题
  18. 华为项目管理金种子培训教材(资料下载)
  19. 领域驱动架构(DDD)建模中的模型到底是什么? 1
  20. 5G网络与5G WiFi有什么区别

热门文章

  1. 税务总局发文进一步简便优化部分纳税人个人所得税预扣预缴方法!2021年1月1日起施行
  2. JAVA中重写toString
  3. Thrift框架介绍
  4. 澳门大学计算机语言博士生导师王珊,博士招生 | 澳门大学王珊招收语言学等方向学生...
  5. 计算两个日期间有多少个工作日
  6. Follow your heart (174)----礼物,逛街,吃饭,像小女人一样生活
  7. 密位测距离口诀_战争雷霆历史模式炮术教学 历史模式测距方法详解
  8. Linux PWM驱动框架 (二)
  9. 学习Mac开发第一弹 认识 NSButton
  10. 微信公众号授权登录获取code获取openid注意事项(采坑解决方案)