【STM32Cube HAL】SPI(十)
实验内容:使用硬件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(十)相关推荐
- stm32cube,hal库来实现PS2手柄数据发送
stm32cube,hal库来实现PS2手柄数据发送 很久前买了个PS2的手柄,如下,之前 以前不会使用cube来配置工程,导致写程序很麻烦,对我这样的新手很不友好,看卖家提供的程序也很麻烦,拉高拉低 ...
- 【STM32Cube HAL】IIC(九)
实验内容:分别采用软件/硬件IIC读写EEPROM(AT24C02). 一.原理图 -- -- -- -- -- -- -- -- 软件IIC-- -- -- -- -- -- -- -- 二. C ...
- STM32cube HAL库 UART串口中断方式收发任意长度 调试笔记
STM32Cube对于新项目的开发能节省不少时间,从繁琐芯片初始化中解脱出来 1.STM32 UART初始化部分,配置好工程,采用STM32cube生成代码,初始化即已经完成. 2.串口中断方式的发动 ...
- STM32cube HAL库两条命令实现i2c通信---Nucleo L476RG用I2C实现tmp117模块温度读取并串口打印
用stm32 cubemx默认配置i2c1和urart1,本例子是用硬件i2c非模拟i2c /* I2C1 GPIO Configuration PB6 ------> I2C1_SCLPB7 ...
- 1、STM32CubeMX和STM32Cube库(HAL)详细介绍
目录 前言 STM32Cube生态 STM32Cube 是什么? STM32Cube 软件工具套件 STM32Cube Embedded 软件 STM32CubeMX 编辑 前言 也许大家在学习正点 ...
- STM32 HAL库学习笔记1-HAL库简介
STM32 HAL库学习笔记1-HAL库简介 HAL库 SPL 库 和 HAL 库两者相互独立,互不兼容.几种库的比较如下 目前几种库对不同芯片的支持情况如下 ST 中文官网上有一篇<关于ST库 ...
- hal库选择滴答时钟函数_STM32入门 : HAL库、标准外设库、LL库
国内使用STM32 单片机的人很多,ST 为开发者提供了非常方便的开发库:有标准外设库(SPL 库).HAL 库.LL 库 三种.前者是ST的老库,后两者是ST现在主推的开发库,其中 LL 库是 ...
- 正点原子STM32(基于HAL库)0
目录 开发环境搭建与使用 常用开发工具简介 MDK 安装 仿真器驱动安装 CH340 USB 虚拟串口驱动安装 使用MDK5 编译例程 使用串口下载程序 使用DAP 下载与调试程序 使用DAP 下载程 ...
- Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
最新文章
- 运行时异常 检查时异常
- Failed to load module script: The server responded with a non-JavaScript MIME type of “text/plain“.
- 在python程序中的进程操作
- bs和cs架构的区别和优缺点_C/S和B/S两种架构区别与优缺点分析
- vue父组件传值给字组件
- java指针操作符_rxjava 操作符大全
- OpenCASCADE:拓扑 API之特征
- 判空前后顺序的思考(代码规范)
- h5配合css和js如何自定义单选框
- c++ 共享内存_Python3.8多进程之共享内存
- 基于 Serverless 打造如 Windows 体验的个人专属家庭网盘
- 串行口通信c语言代码,问一下单片机串行口通信用c语言实现的问题
- JSF MVC 流程
- [读书笔记] - 《深度探索C++对象模型》第1章 关于对象
- 普通用户添加systemctl 自定义服务的开机启动项
- centos 7 安装donet core2.0环境
- idea验证失败_解决iPad登陆不了Apple ID验证失败的问题
- 华为项目管理金种子培训教材(资料下载)
- 领域驱动架构(DDD)建模中的模型到底是什么? 1
- 5G网络与5G WiFi有什么区别
热门文章
- 税务总局发文进一步简便优化部分纳税人个人所得税预扣预缴方法!2021年1月1日起施行
- JAVA中重写toString
- Thrift框架介绍
- 澳门大学计算机语言博士生导师王珊,博士招生 | 澳门大学王珊招收语言学等方向学生...
- 计算两个日期间有多少个工作日
- Follow your heart (174)----礼物,逛街,吃饭,像小女人一样生活
- 密位测距离口诀_战争雷霆历史模式炮术教学 历史模式测距方法详解
- Linux PWM驱动框架 (二)
- 学习Mac开发第一弹 认识 NSButton
- 微信公众号授权登录获取code获取openid注意事项(采坑解决方案)