当前位置:   article > 正文

STM32Cube SPI协议 读写flash(W25Q32)_stm32 w25q32

stm32 w25q32

一、开发板实例:STM32L431RCT6

(1)开发板采用意法半导体(ST)公司的低功耗 Arm® Cortex®-M4 处理器STM32L431RCT6,其工作主频高达80MHz,并提供256KB Flash, 64KB SRAM存储空间。

(2)其高性能、超低功耗的特性,更加适用于各种物联网产品电池供电的低功耗要求。

二、SPI Flash介绍

1、SPI协议
(1)SPI总线特点

SPI协议是一种高速全双工同步串行通信协议

由一个主设备(Master)和一个或多个从设备(Slave)组成

四线协议:MISO(Master Input Slave Output)/SDI(Serial Data Input)

MOSI(Master Output Slave Input)/SDO(Serial Data Output)SCLK(Synchronous Clock)CS(Chip Select)

1、MISO,主设备数据输入,从设备数据输出;

2、MOSI,主设备数据输出,从设备数据输入;

3、SCLK,同步时钟信号,由主设备产生;

4、CS,从设备使能(片选)信号,由主设备控制,低电平为选中。当总线上有多个从设备的时候,主设备如果需要和某个从设备通信,就将该设备对应的片选引脚拉低使能;

SPI 采用两根数据线实现全双工的数据通信数据线 MOSI MISO

(2)SPI总线传输模式:

CPOL(Clock Polarity,时钟的极性): 规约 SPI 总线在空闲时, SCK时钟信号是高电平1还是低电平0;

CPHA(Clock Phase,时钟的相位): 规约 SPI 设备是在SCK时钟信号上升沿还是下降沿触发数据采样;

SPI 总线传输一共有 4 模式

模式 0CPOL= 0CPHA=0SCK 串行时钟线空闲是为低电平,数据在 SCK 时钟的上升沿被采样, 数据在 SCK 时钟的下降沿切换;

模式 1CPOL= 0CPHA=1SCK 串行时钟线空闲是为低电平,数据在 SCK 时钟的下降沿被采样, 数据在 SCK 时钟的上升沿切换;

模式 2CPOL= 1CPHA=0SCK 串行时钟线空闲是为高电平,数据在 SCK 时钟的下降沿被采样, 数据在 SCK 时钟的上升沿切换;

模式 3CPOL= 1CPHA=1SCK 串行时钟线空闲是为高电平,数据在 SCK 时钟的上升沿被采样, 数据在 SCK 时钟的下降沿切换;

 (3)SPI总线数据交换:

一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访 问 (Access). 所以, Master 设备必须首先通过拉低 SS/CS 管脚对 Slave 设备进行片选, 把想要访问的Slave 设备选上. SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信 过程中仅仅只充当一个 "发送者(Transmitter)" 或者 "接收者(Receiver)"

2、Flash存储特性

1.在写入数据时必须先擦除、擦除时会把数据位全重置为1、写入数据时只能把为1的数据改成0、擦除时必须按最小单位来擦除(一般为扇区)

norflash 可以一个字节写入、nandflash 必须以块或扇区为单位进行读写

 三、开始

使⽤STM32CubeMX配置STM32L431RCT6的SPI1外设与SPIFlash通信(W25Q32)。

1、配置串⼝1

使能USART1,使PC的串⼝与USART1之间连接。如图:

 

2、配置SPI1外设引脚

PA4:Output、PA5:SPI1_SCK、PA6:SPI1_MISO、PA7:SPI1_MOSI

其引脚连接情况如下:

 

然后使能SPI1,如图:

3、封装 SPI Flash(W25QXX)的底层函数

MCU 通过向 SPI Flash 发送各种命令 来读写 SPI Flash内部的寄存器,首先要定义需要使⽤的命令,然后利⽤HAL 库提供的库函数,封装出三个底层函数。(如果后续要做littlefs的移植,这一步一定要写好,本人就踩过坑......)
定义向SPI Flash发送的命令、向SPI Flash发送数据的函数、从SPI Flash接收数据的函数
接下来开始编写代码

(1)在工程Inc目录下新建一个w25qxx.h文件:
  1. #ifndef __W25QXX_H
  2. #define __W25QXX_H
  3. #include <main.h>
  4. //片选信号,拉低选中,拉高释放
  5. #define SPI_FLASH_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
  6. #define SPI_FLASH_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)
  7. //指令表
  8. #define W25X_WriteEnable 0x06
  9. #define W25X_WriteDisable 0x04
  10. #define W25X_ReadStatusReg 0x05
  11. #define W25X_WriteStatusReg 0x01
  12. #define W25X_ReadData 0x03
  13. #define W25X_FastReadData 0x0B
  14. #define W25X_FastReadDual 0x3B
  15. #define W25X_PageProgram 0x02
  16. #define W25X_BlockErase 0xD8
  17. #define W25X_SectorErase 0x20
  18. #define W25X_ChipErase 0xC7
  19. #define W25X_PowerDown 0xB9
  20. #define W25X_ReleasePowerDown 0xAB
  21. #define W25X_DeviceID 0xAB
  22. #define W25X_ManufactDeviceID 0x90
  23. #define W25X_JedecDeviceID 0x9F
  24. #endif
(2)在工程Src目录下新建一个w25qxx.c文件:
  1. #include "w25qxx.h"
  2. #include "spi.h"
  3. /*SPI读写一个字节
  4. TxData:要写入的字节
  5. RxData返回值:读取到的字节
  6. &hspi1使用的SPI外设,此处我们用的是spi1*/
  7. static uint8_t W25QXX_SPI_ReadWriteByte(uint8_t TxData)
  8. {
  9. uint8_t RxData = 0X00;
  10. if(HAL_SPI_TransmitReceive(&hspi1, &TxData, &RxData, 1, 0xFFFFFF) != HAL_OK)
  11. {
  12. RxData = 0XFF;
  13. }
  14. return RxData;
  15. }
  16. //读取W25QXX的状态寄存器
  17. uint8_t W25QXX_ReadSR(void)
  18. {
  19. uint8_t byte = 0;
  20. SPI_FLASH_CS_LOW(); //使能器件
  21. W25QXX_SPI_ReadWriteByte(W25X_ReadStatusReg);
  22. byte = W25QXX_SPI_ReadWriteByte(0Xff);
  23. SPI_FLASH_CS_HIGH(); //取消片选
  24. return byte;
  25. }
  26. //写W25QXX状态寄存器
  27. void W25QXX_Write_SR(uint8_t sr)
  28. {
  29. SPI_FLASH_CS_LOW(); //使能器件
  30. W25QXX_SPI_ReadWriteByte(W25X_WriteStatusReg); //发送写取状态寄存器命令
  31. W25QXX_SPI_ReadWriteByte(sr); //写入一个字节
  32. SPI_FLASH_CS_HIGH(); //取消片选
  33. }
  34. //W25QXX写使能
  35. void W25QXX_Write_Enable(void)
  36. {
  37. SPI_FLASH_CS_LOW(); //使能器件
  38. W25QXX_SPI_ReadWriteByte(W25X_WriteEnable); //发送写使能
  39. SPI_FLASH_CS_HIGH(); //取消片选
  40. }
  41. //W25QXX写禁止
  42. void W25QXX_Write_Disable(void)
  43. {
  44. SPI_FLASH_CS_LOW(); //使能器件
  45. W25QXX_SPI_ReadWriteByte(W25X_WriteDisable); //发送写禁止指令
  46. SPI_FLASH_CS_HIGH(); //取消片选
  47. }
  48. 读取FLASH ID
  49. uint16_t W25QXX_ReadID(void)
  50. {
  51. uint32_t temp, temp0, temp1, temp2;
  52. SPI_FLASH_CS_LOW(); //使能器件
  53. W25QXX_SPI_ReadWriteByte(W25X_JedecDeviceID); //发送读取ID命令
  54. temp0=W25QXX_SPI_ReadWriteByte(0x00);
  55. temp1=W25QXX_SPI_ReadWriteByte(0x00);
  56. temp2=W25QXX_SPI_ReadWriteByte(0x00);
  57. SPI_FLASH_CS_HIGH(); //取消片选
  58. temp = (temp0 << 16) | (temp1 << 8) | temp2;
  59. return temp;
  60. }
  61. //读取 W25QXX SPI Flash的容量
  62. //每个字节存储在 arr 数组中
  63. uint32_t W25QXX_ReadCapacity(void)
  64. {
  65. int i = 0;
  66. uint8_t arr[4] = {0,0,0,0};
  67. SPI_FLASH_CS_LOW(); //使能器件
  68. W25QXX_SPI_ReadWriteByte(0x5A);
  69. W25QXX_SPI_ReadWriteByte(0x00);
  70. W25QXX_SPI_ReadWriteByte(0x00);
  71. W25QXX_SPI_ReadWriteByte(0x84);
  72. W25QXX_SPI_ReadWriteByte(0x00);
  73. for(i = 0; i < sizeof(arr); i++)
  74. {
  75. arr[i] = W25QXX_SPI_ReadWriteByte(0xFF);
  76. }
  77. SPI_FLASH_CS_HIGH(); //取消片选
  78. return ((((*(uint32_t *)arr)) + 1) >> 3);
  79. }
  80. //读取 W25QXX SPI Flash 的唯一标识符
  81. //每个字节存储在 UID 数组中
  82. void W25QXX_ReadUniqueID(uint8_t UID[8])
  83. {
  84. int i = 0;
  85. SPI_FLASH_CS_LOW(); //使能器件
  86. W25QXX_SPI_ReadWriteByte(0x4B);
  87. W25QXX_SPI_ReadWriteByte(0x00);
  88. W25QXX_SPI_ReadWriteByte(0x00);
  89. W25QXX_SPI_ReadWriteByte(0x00);
  90. W25QXX_SPI_ReadWriteByte(0x00);
  91. for(i = 0; i < 8; i++)
  92. {
  93. UID[i] = W25QXX_SPI_ReadWriteByte(0xFF);
  94. }
  95. SPI_FLASH_CS_HIGH(); //取消片选
  96. }
  97. //读取SPI FLASH
  98. //在指定地址开始读取指定长度的数据
  99. //pBuffer:数据存储区
  100. //ReadAddr:开始读取的地址(24bit)
  101. //NumByteToRead:要读取的字节数(最大65535)
  102. void W25QXX_Read(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
  103. {
  104. uint16_t i;
  105. SPI_FLASH_CS_LOW(); //使能器件
  106. W25QXX_SPI_ReadWriteByte(W25X_ReadData);
  107. W25QXX_SPI_ReadWriteByte((uint8_t)((ReadAddr) >> 16));
  108. W25QXX_SPI_ReadWriteByte((uint8_t)((ReadAddr) >> 8));
  109. W25QXX_SPI_ReadWriteByte((uint8_t)ReadAddr);
  110. for (i = 0; i < NumByteToRead; i++)
  111. {
  112. pBuffer[i] = W25QXX_SPI_ReadWriteByte(0XFF);
  113. }
  114. SPI_FLASH_CS_HIGH(); //取消片选
  115. }
  116. /*SPI在一页(0~65535)内写入少于256个字节的数据
  117. 在指定地址开始写入最大256字节的数据
  118. pBuffer:数据存储区
  119. WriteAddr:开始写入的地址(24bit)
  120. NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数*/
  121. void W25QXX_Write_Page(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
  122. {
  123. uint16_t i;
  124. W25QXX_Write_Enable();
  125. SPI_FLASH_CS_LOW(); //使能器件
  126. W25QXX_SPI_ReadWriteByte(W25X_PageProgram); //发送写页命令
  127. W25QXX_SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 16)); //发送24bit地址
  128. W25QXX_SPI_ReadWriteByte((uint8_t)((WriteAddr) >> 8));
  129. W25QXX_SPI_ReadWriteByte((uint8_t)WriteAddr);
  130. for (i = 0; i < NumByteToWrite; i++)
  131. W25QXX_SPI_ReadWriteByte(pBuffer[i]); //循环写数
  132. SPI_FLASH_CS_HIGH(); //取消片选
  133. W25QXX_Wait_Busy(); //等待写入结束
  134. }
  135. /*必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
  136. 具有自动换页功能
  137. 在指定地址开始写入指定长度的数据,但是要确保地址不越界!
  138. pBuffer数据存储区
  139. WriteAddr:开始写入的地址(24bit)
  140. NumByteToWrite:要写入的字节数(最大65535)*/
  141. void W25QXX_Write_NoCheck(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
  142. {
  143. uint16_t pageremain;
  144. pageremain = 256 - WriteAddr % 256; //单页剩余的字节数
  145. if (NumByteToWrite <= pageremain)
  146. pageremain = NumByteToWrite; //不大于256个字节
  147. while (1)
  148. {
  149. W25QXX_Write_Page(pBuffer, WriteAddr, pageremain);
  150. if (NumByteToWrite == pageremain)
  151. break; //写入结束了
  152. else //NumByteToWrite>pageremain
  153. {
  154. pBuffer += pageremain;
  155. WriteAddr += pageremain;
  156. NumByteToWrite -= pageremain; //减去已经写入了的字节数
  157. if (NumByteToWrite > 256)
  158. pageremain = 256; //一次可以写入256个字节
  159. else
  160. pageremain = NumByteToWrite; //不够256个字节了
  161. }
  162. };
  163. }
  164. /*写SPI FLASH
  165. 在指定地址开始写入指定长度的数据
  166. 该函数带擦除操作
  167. pBuffer:数据存储区
  168. WriteAddr:开始写入的地址(24bit)
  169. NumByteToWrite:要写入的字节数(最大65535)*/
  170. void W25QXX_Write(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
  171. {
  172. uint32_t secpos;
  173. uint16_t secoff;
  174. uint16_t secremain;
  175. uint16_t i;
  176. uint8_t *W25QXX_BUF;
  177. W25QXX_BUF = W25QXX_BUFFER;
  178. secpos = WriteAddr / 4096; //扇区地址
  179. secoff = WriteAddr % 4096; //在扇区内的偏移
  180. secremain = 4096 - secoff; //扇区剩余空间大小
  181. if (NumByteToWrite <= secremain)
  182. secremain = NumByteToWrite; //不大于4096个字节
  183. while (1)
  184. {
  185. W25QXX_Read(W25QXX_BUF, secpos * 4096, 4096); //读出整个扇区的内容
  186. for (i = 0; i < secremain; i++) //校验数据
  187. {
  188. if (W25QXX_BUF[secoff + i] != 0XFF)
  189. break;//需要擦除
  190. }
  191. if (i < secremain) //需要擦除
  192. {
  193. W25QXX_Erase_Sector(secpos); //擦除这个扇区
  194. for (i = 0; i < secremain; i++) //复制
  195. {
  196. W25QXX_BUF[i + secoff] = pBuffer[i];
  197. }
  198. W25QXX_Write_NoCheck(W25QXX_BUF, secpos * 4096, 4096); //写入整个扇区
  199. }
  200. else
  201. W25QXX_Write_NoCheck(pBuffer, WriteAddr, secremain); //写已经擦除了的,直接写入扇区剩余区间
  202. if (NumByteToWrite == secremain)
  203. break; //写入结束了
  204. else //写入未结束
  205. {
  206. secpos++; //扇区地址增1
  207. secoff = 0; //偏移地址为0
  208. pBuffer += secremain; //指针偏移
  209. WriteAddr += secremain; //写地址偏移
  210. NumByteToWrite -= secremain; //字节数递减
  211. if (NumByteToWrite > 4096)
  212. secremain = 4096; //下一个扇区还是写不完
  213. else
  214. secremain = NumByteToWrite; //下一个扇区可以写完了
  215. }
  216. };
  217. }
  218. //擦除整个芯片
  219. //等待时间超长...
  220. void W25QXX_Erase_Chip(void)
  221. {
  222. W25QXX_Write_Enable();
  223. W25QXX_Wait_Busy();
  224. SPI_FLASH_CS_LOW(); //使能器件
  225. W25QXX_SPI_ReadWriteByte(W25X_ChipErase); //发送片擦除命令
  226. SPI_FLASH_CS_HIGH(); //取消片选
  227. W25QXX_Wait_Busy(); //等待芯片擦除结束
  228. }
  229. //擦除一个扇区
  230. //Dst_Addr:扇区地址,根据实际容量设置
  231. //擦除一个扇区最少时间:150ms
  232. void W25QXX_Erase_Sector(uint32_t Dst_Addr)
  233. {
  234. Dst_Addr *= 4096;
  235. W25QXX_Write_Enable();
  236. W25QXX_Wait_Busy();
  237. SPI_FLASH_CS_LOW(); //使能器件
  238. W25QXX_SPI_ReadWriteByte(W25X_SectorErase); //发送扇区擦除命令
  239. W25QXX_SPI_ReadWriteByte((uint8_t)((Dst_Addr) >> 16)); //发送24bit地址
  240. W25QXX_SPI_ReadWriteByte((uint8_t)((Dst_Addr) >> 8));
  241. W25QXX_SPI_ReadWriteByte((uint8_t)Dst_Addr);
  242. SPI_FLASH_CS_HIGH(); //取消片选
  243. W25QXX_Wait_Busy(); //等待擦除完成
  244. }
  245. //等待空闲
  246. void W25QXX_Wait_Busy(void)
  247. {
  248. while ((W25QXX_ReadSR() & 0x01) == 0x01); //等待BUSY位清空
  249. }
(3)在main.c函数中测试:

在该位置添加头文件:

在int main(void):

(此处我写入的是字符串,但是读出来的是其写入的对应的地址)

 ​​​​​​​

(4)串口调试助手查看测试结果:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/704066
推荐阅读
相关标签
  

闽ICP备14008679号