你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32Cube HAL SPI

[复制链接]
STMCU小助手 发布时间:2022-4-7 14:43
一、原理图

~`B8KDN6X~)RJC(VYCHIXTF.png

二、 CubeMX配置

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

~VC35QGY$LT7]BKM36_EXDI.png

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

WAHULLSI5C[``04N~VQW4.png

0DCI2X2%V)[8A]8YSI1PJYT.png

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

{2FB@N$$U]][`3E$R83TUIV.png

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

4TZAP%EOLDQGG0X4]@RFJ)B.png

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

(U{3@]I@BK}G(VQ](VH[ARL.png

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

VBRF{_UYCO3DPPZ[MW6M@2S.png

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

三、添加功能代码

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

  1. //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
  2. int fputc(int ch, FILE *f)
  3. {
  4.         /* 发送一个字节数据到串口DEBUG_USART */
  5.         HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);        
  6.         
  7.         return (ch);
  8. }

  9. //重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
  10. int fgetc(FILE *f)
  11. {               
  12.         int ch;
  13.         HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);        
  14.         return (ch);
  15. }
复制代码


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

  1. #define NSS_HIGH() HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET)
  2. #define NSS_LOW()  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET)
复制代码

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

  1. #define FLASH_PAGESIZE         256                          //W25Q64的页面大小
  2. #define _Flash_ID        0xEF4017
复制代码
  1. extern SPI_HandleTypeDef hspi1;
  2. void MX_SPI1_Init(void);
  3. uint32_t SPI_FLASH_ReadID(void);
  4. void SPI_FLASH_WriteEnable(void);
  5. void SPI_FLASH_WaitForWriteEnd(void);
  6. void SPI_FLASH_SectorErase(uint32_t SectorAddr);
  7. void SPI_FLASH_PageWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite);
  8. void SPI_FLASH_BufferWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite);
  9. void SPI_FLASH_BufferRead(uint8_t * pBuffer, uint32_t  ReadAddr,uint16_t NumByteToWrite);
复制代码

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

  1. /*读取制造商和设备ID*/
  2. uint32_t SPI_FLASH_ReadID(void)
  3. {
  4.         uint8_t W25X_JEDEC_ID=0X9F;
  5.         uint8_t temp0[3];
  6.         uint32_t temp;
  7.         
  8.         NSS_LOW();//选择FLASH:NSS低电平
  9.         
  10.         HAL_SPI_Transmit(&hspi1,&W25X_JEDEC_ID,1,10);//发送指令
  11.         
  12.         HAL_SPI_Receive(&hspi1,temp0,3, 10);//读取制造商和设备ID
  13.         
  14.         NSS_HIGH();//停止信号 FLASH:NSS高电平
  15.         
  16.         temp=(temp0[0])<<16|(temp0[1]<<8)|temp0[2];//把数据组合起来,作为函数的返回值

  17.         return temp;
  18. }

  19. /*写使能*/
  20. void SPI_FLASH_WriteEnable(void)
  21. {
  22.         uint8_t W25X_WriteEnable=0x06;
  23.         
  24.         NSS_LOW();//选择FLASH:NSS低电平
  25.         
  26.         HAL_SPI_Transmit(&hspi1,&W25X_WriteEnable,1,10);//发送指令
  27.         
  28.         NSS_HIGH();//停止信号 FLASH:NSS高电平
  29. }

  30. /*扇区擦除*/
  31. void SPI_FLASH_SectorErase(uint32_t SectorAddr)
  32. {
  33.         uint8_t W25X_SectorErase=0x20;
  34.         uint8_t temp1,temp2,temp3;
  35.         
  36.         SPI_FLASH_WriteEnable();//写使能

  37.         NSS_LOW();//选择FLASH:NSS低电平
  38.         
  39.         HAL_SPI_Transmit(&hspi1,&W25X_SectorErase,1,10);//发送指令
  40.         
  41.         temp1=(SectorAddr&(0xFF0000))>>16;//发送地址
  42.         HAL_SPI_Transmit(&hspi1,&temp1,1,10);
  43.         temp2=(SectorAddr&(0x00FF00))>>8;
  44.         HAL_SPI_Transmit(&hspi1,&temp2,1,10);
  45.         temp3=(SectorAddr&(0x0000FF));
  46.         HAL_SPI_Transmit(&hspi1,&temp3,1,10);
  47.         NSS_HIGH();//停止信号 FLASH:NSS高电平
  48.         
  49.         SPI_FLASH_WaitForWriteEnd();//等待擦除完毕
  50. }

  51. /*等待 WIP(BUSY) 标志被置 0,即等待到 FLASH 内部数据写入完毕*/
  52. void SPI_FLASH_WaitForWriteEnd(void)
  53. {
  54.         uint8_t W25X_ReadStatusReg=0x05;
  55.         uint8_t temp;
  56.         
  57.         NSS_LOW();//选择FLASH:NSS低电平
  58.         
  59.         HAL_SPI_Transmit(&hspi1,&W25X_ReadStatusReg,1,10);//发送指令
  60.         do
  61.         {
  62.                 HAL_SPI_Receive(&hspi1,&temp,1,10);// 读取 FLASH 芯片的状态寄存器
  63.         }
  64.         while((temp&0x01)==1);
  65.         
  66.         NSS_HIGH();//停止信号 FLASH:NSS高电平
  67. }

  68. /*在FLASH的一个写循环中可以写多个字节,但一次写入
  69. 的字节数不能超过FLASH页的大小,W25Q64每页有256个字节*/
  70. void SPI_FLASH_PageWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite)
  71. {
  72.         uint8_t W25X_PageProgram=0x02;
  73.         uint8_t temp1,temp2,temp3;
  74.         
  75.         SPI_FLASH_WriteEnable();//写使能
  76.         
  77.         NSS_LOW();//选择FLASH:NSS低电平
  78.         
  79.         HAL_SPI_Transmit(&hspi1,&W25X_PageProgram,1,10);//发送指令
  80.         
  81.         temp1=(WriteAddr&(0xFF0000))>>16;//发送地址
  82.         HAL_SPI_Transmit(&hspi1,&temp1,1,10);
  83.         
  84.         temp2=(WriteAddr&(0x00FF00))>>8;
  85.         HAL_SPI_Transmit(&hspi1,&temp2,1,10);
  86.         
  87.         temp3=(WriteAddr&(0x0000FF));
  88.         HAL_SPI_Transmit(&hspi1,&temp3,1,10);
  89.         
  90.         HAL_SPI_Transmit(&hspi1,pBuffer,NumByteToWrite,10);//发送数据
  91.         
  92.         NSS_HIGH();//停止信号 FLASH:NSS高电平
  93.         
  94.         SPI_FLASH_WaitForWriteEnd();//等待写入完毕
  95. }

  96. /*不定量数据写入*/
  97. void SPI_FLASH_BufferWrite(uint8_t * pBuffer, uint32_t WriteAddr,uint16_t NumByteToWrite)
  98. {
  99.   uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

  100.   Addr = WriteAddr % FLASH_PAGESIZE;//判断写入的首地址是否与EEPROM页的首地址对齐,0为对齐
  101.   count = FLASH_PAGESIZE - Addr;//计算从写入的首地址需要写多少数据才能填满当前页
  102.   NumOfPage =  NumByteToWrite / FLASH_PAGESIZE;//计算写入数据需要写几个完整页(地址对齐的情况)
  103.   NumOfSingle = NumByteToWrite % FLASH_PAGESIZE;//计算写完完整页剩下的数据个数(地址对齐的情况)

  104.   if(Addr == 0) //判断写入的首地址是否与页地址对齐
  105.   {
  106.     if(NumOfPage == 0) //如果页对齐,判断数据是否不满一页
  107.     {
  108.       SPI_FLASH_PageWrite(pBuffer,WriteAddr,NumOfSingle);//如果不满一页,直接写入数据
  109.     }
  110.     else  //在数据满一页的情况下,通过地址自增方式,循环写入数据(页写入的形式)
  111.     {
  112.       while(NumOfPage--)//循环写入数据:先写入完整页
  113.       {
  114.                                 SPI_FLASH_PageWrite(pBuffer, WriteAddr, FLASH_PAGESIZE);
  115.         WriteAddr +=  FLASH_PAGESIZE;
  116.         pBuffer += FLASH_PAGESIZE;
  117.       }

  118.       if(NumOfSingle!=0)//循环写入数据:再写入不满一页的数据
  119.       {
  120.                                 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  121.       }
  122.     }
  123.   }
  124.   else
  125.   {
  126.     if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
  127.     {
  128.                         if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页
  129.                         {         
  130.                                 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
  131.                         }
  132.                         else
  133.                         {
  134.                                 SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
  135.                                 SPI_FLASH_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
  136.                         }
  137.     }

  138.     else
  139.     {
  140.                         /*如果数据满一页,对数据进行分离*/
  141.       NumByteToWrite -= count;//扣除第一页数据个数
  142.       NumOfPage =  NumByteToWrite / FLASH_PAGESIZE;//计算写入数据需要写几个完整页
  143.       NumOfSingle = NumByteToWrite % FLASH_PAGESIZE;        //计算写完完整页剩下的数据个数

  144.       if(count != 0)
  145.       {  
  146.         SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);//写入首页数据
  147.         WriteAddr += count;//写地址自增
  148.         pBuffer += count;//缓冲区指针自增
  149.       }

  150.       while(NumOfPage--)//依次写入完整页的数据
  151.       {
  152.                                 SPI_FLASH_PageWrite(pBuffer, WriteAddr, FLASH_PAGESIZE);
  153.         WriteAddr +=  FLASH_PAGESIZE;//写地址自增
  154.         pBuffer += FLASH_PAGESIZE; //缓冲区指针自增
  155.       }
  156.       if(NumOfSingle != 0)//判断最后一页的数据是否是填满完整一页的
  157.       {
  158.                                 SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);//写入最后一页的数据
  159.       }
  160.     }
  161.   }  
  162. }

  163. /*读取FLASH数据,读取的数据量没有限制*/
  164. void SPI_FLASH_BufferRead(uint8_t * pBuffer, uint32_t  ReadAddr,uint16_t NumByteToWrite)
  165. {
  166.         uint8_t W25X_ReadData=0x03;
  167.         uint8_t temp1,temp2,temp3;
  168.         
  169.         SPI_FLASH_WriteEnable();//写使能
  170.         
  171.         NSS_LOW();//选择FLASH:NSS低电平
  172.         
  173.         HAL_SPI_Transmit(&hspi1,&W25X_ReadData,1,10);//发送指令
  174.         
  175.         temp1=(ReadAddr&(0xFF0000))>>16;//发送地址
  176.         HAL_SPI_Transmit(&hspi1,&temp1,1,10);
  177.         
  178.         temp2=(ReadAddr&(0x00FF00))>>8;
  179.         HAL_SPI_Transmit(&hspi1,&temp2,1,10);
  180.         
  181.         temp3=(ReadAddr&(0x0000FF));
  182.         HAL_SPI_Transmit(&hspi1,&temp3,1,10);
  183.         
  184.         HAL_SPI_Receive(&hspi1,pBuffer,NumByteToWrite, 10);//这里超时时间不能设置为1,否则会读取错误
  185.         
  186.         NSS_HIGH();//停止信号 FLASH:NSS高电平
  187. }
复制代码

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

  1. /*测试代码涉及到变量,宏定义*/
  2. /* 获取缓冲区的长度 */

  3. /*sizeof():数组占用字节除以数组类型所占字节,结果为数组元素个数*/
  4. /*使用方法:sizeof(数组名)/ sizeof(数组类型名) */
  5. #define countof(a)      (sizeof(a) / sizeof(*(a)))
  6. #define  BufferSize (countof(Tx_Buffer)-1)

  7. #define  FLASH_WriteAddress     0x000006
  8. #define  FLASH_ReadAddress      FLASH_WriteAddress
  9. #define  FLASH_SectorToErase    FLASH_WriteAddress

  10. uint8_t Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength);

  11. uint32_t Flash_ID;
  12. uint8_t CMP_RES;
  13. uint8_t Tx_Buffer[] =
  14. "采薇采薇,薇亦作止。曰归曰归,岁亦莫止。靡室靡家,猃狁之故。不遑启居,猃狁之故。\
  15. 采薇采薇,薇亦柔止。曰归曰归,心亦忧止。忧心烈烈,载饥载渴。我戍未定,靡使归聘。\
  16. 采薇采薇,薇亦刚止。曰归曰归,岁亦阳止。王事靡盬,不遑启处。忧心孔疚,我行不来!\
  17. 彼尔维何?维常之华。彼路斯何?君子之车。戎车既驾,四牡业业。岂敢定居?一月三捷。\
  18. 驾彼四牡,四牡骙骙。君子所依,小人所腓。四牡翼翼,象弭鱼服。岂不日戒?猃狁孔棘!\
  19. 昔我往矣,杨柳依依。今我来思,雨雪霏霏。行道迟迟,载渴载饥。我心伤悲,莫知我哀!";
  20. uint8_t Rx_Buffer[BufferSize];
复制代码
  1. /*比较函数,用于比较写入和读取的数据是否一致*/
  2. uint8_t Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
  3. {
  4.   while(BufferLength--)
  5.   {
  6.     if(*pBuffer1 != *pBuffer2)
  7.     {
  8.       return 0;
  9.     }

  10.     pBuffer1++;
  11.     pBuffer2++;
  12.   }
  13.   return 1;
  14. }
  15. /*在主函数编写测试代码:ID验证,读写比较*/
  16. Flash_ID=SPI_FLASH_ReadID();//读取Flash ID
  17.         if (Flash_ID == _Flash_ID) //判断读取的ID和数据手册的ID是否一致
  18.         {        
  19.                 printf("\r\n系统检测到SPI FLASH W25Q64 ! FlashID:0x%X\r\n",Flash_ID);
  20.                
  21.                 /* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */
  22.                 SPI_FLASH_SectorErase(FLASH_SectorToErase);                  
  23.                
  24.                 /* 将发送缓冲区的数据写到flash中 *///
  25.                 SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);
  26.                 printf("\r\n写入的数据为:\r\n%s", Tx_Buffer);
  27.                
  28.                 /* 将刚刚写入的数据读出来放到接收缓冲区中 */
  29.                 SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
  30.                 printf("\r\n读出的数据为:\r\n%s", Rx_Buffer);
  31.                
  32.                 /* 检查写入的数据与读出的数据是否相等 */
  33.                 CMP_RES= Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
  34.                
  35.                 if(CMP_RES)
  36.                 {   
  37.                         printf("\r\n16M串行flash(W25Q64)测试成功!\n\r");
  38.                 }
  39.                 else
  40.                 {        
  41.                         printf("\r\n16M串行flash(W25Q64)测试失败!\n\r");
  42.                 }
  43.         }// if (FlashID == sFLASH_ID)
  44.         else
  45.         {   
  46.                 printf("\r\n获取不到 W25Q64 ID!\n\r");
  47.         }                        
复制代码


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

  1. HAL_SPI_Transmit(&hspi1,&W25X_PageProgram,1,10);//发送指令
复制代码

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

  1. HAL_SPI_Receive(&hspi1,pBuffer,NumByteToWrite, 10);
复制代码



[)2DJ%}K07NF8IKA]B7442L.png
JG7BVI6$2D$Y%P43UAGZY_V.png
P@_1HFM(@P86BV2P@7P{)RU.png
收藏 评论0 发布时间:2022-4-7 14:43

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版