细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV

打印 上一主题 下一主题

主题 778|帖子 778|积分 2334

目次
一、工程设置
1、时钟、DEBUG
2、GPIO
3、SPI2
4、USART6
5、NVIC 
二、软件设计
1、FALSH
(1)w25flash.h
(2) w25flash.c
        1)W25Q16基本操纵指令
        2)盘算所在的辅助功能函数
        3)器件、块、扇区擦除函数
        4)存储区读写函数
2、KEY_LED
3、spi.h
4、spi.c
5、main.c
三、下载与运行


        本文旨在说明STM32F407单片机通过SPI2扩展FLASH W25Q16BV,对FLASH进行读ID、写操纵、读操纵、擦除操纵。利用的是旺宝红龙开发板STM32F407ZGT6 KIT V1.0。利用开发板上的按键S2、S3、S4、S5、S6依此实验擦除芯片、擦除BLOCK0、写、读操纵、MCU复位。利用开发板上的D1、D2、D3、D4作为动作指示灯。通过串口USART6把操纵状态表现到串口助手上。资源利用详见工程设置。
      工程里创建FLASH目次,包罗w25flash.c、w25flash.h。
      工程依旧创建KEY_LED目次,包罗keyled.c、keyled.h。关于如何在工程中创建目次并添加文件以及keyled.c、keyled.h详见作者的其他文章。
        其它资料可以参考本文作者的其他文章:细说STM32F407单片机SPI底子知识_stm32f407 spi2-CSDN博客  https://wenchm.blog.csdn.net/article/details/144376346
        细说Flash存储芯片W25Q128FW和W25Q16BV_flash拓展所在寄存器0xc5-CSDN博客  https://wenchm.blog.csdn.net/article/details/144398492
一、工程设置

1、时钟、DEBUG

        外部时钟,25MHz,设置到HCLK=168MHz,PCLK1=42MHz,PCLK2=84MHz,其它,都设置成168MHz。
        DEBUG,选择serial wire。
2、GPIO

        项目工程利用了开发板的按键S2、S3、S4、S5、S6,和开发板上的LED灯D1、D2、D3、D4。具体设置如下表:
用户标签

开发板

引脚名称

引脚功能
GPIO模式

默认电平

上拉

或下拉

LED1

D1

PA6

GPIO_Output

推挽输出

High,MCU输出低电平时灯亮

上拉

LED2

D2

PA4

GPIO_Output

推挽输出

High,MCU输出低电平时灯亮

上拉

LED3

D3

PB6

GPIO_Output

推挽输出

High,MCU输出低电平时灯亮

上拉

LED4

D4

PC6

GPIO_Output

推挽输出

High,MCU输出低电平时灯亮

上拉

KeyUp

S2

PA0

GPIO_Input

输入

按键输入低电平

上拉

KeyDown

S3

PD3

GPIO_Input

输入

按键输入低电平

上拉

KeyLeft

S4

PF7

GPIO_Input

输入

按键输入低电平

上拉

KeyRight

S5

PF6

GPIO_Input

输入

按键输入低电平

上拉


S6

NRST

GPIO_Input

输入

按键复位

上拉

3、SPI2

        MCU的SPI2与FLASH的连接原理图,详见参考文章。管脚设置见下表:
用户标签

开发板

标识

引脚名称

引脚功能
GPIO模式

默认电平

上拉

或下拉

FLASH_CS

FLASH_CS

PC13

GPIO_Output片选

推挽输出

High

上拉

SPI2_SCK

SPI_SCK

PB10

时钟

Alternate Function

High

No
SPI2_MOSI

SPI_MOSI

PB15

MOSI数据线

Alternate Function

High

No
SPI2_MISO

SPI_MISO

PB14

MISO数据线
Alternate Function

High

No

4、USART6

        115200、8、None、1,其它参数默认。占用管脚PG14、PG9。
5、NVIC 

         不设置停止,但把Time Base的优先级修改为0。
二、软件设计

1、FALSH

        创建一个名为FLASH的子目次,创建文件w25flash.h和w25flash.c,并将其存放在这个子目次里。 将W25Q16常用的一些功能编写为函数,也就是实现W25Q16常用操纵指令,例如擦除芯片、擦除扇区、读取数据、写入数据等。
        注意W25Q16驱动步调与SPI接口的HAL驱动步调的区别。SPI的HAL驱动步调实现了SPI接口数据传输的基本功能,是SPI硬件层的驱动;而W25Q16驱动步调则是根据W25Q16的指令定义,实现器件具体功能操纵的一系列函数。W25Q16驱动步调要用到SPI硬件层的HAL驱动步调,要通过SPI的HAL驱动步调实现数据帧的收发。
        W25Q16驱动步调涉及的硬件接口包括SPI接口和CS信号,驱动步调应该能很容易地移植到其他电路板上,所以在文件开头定义了硬件接口相关的宏。


  • 为CS信号连接的GPIO引脚定义了表现GPIO端口和引脚号的宏CS_PORT和CS_PIN,定义了两个宏函数__Select_Flash()和__Deselect_Flash()用于对CS置位和复位。
  • W25Q16器件连接的SPI接口定义为宏SPI_HANDLE,这里指向文件spi.h中的变量hspi2,也就是表现SPI2接口的外设对象变量。在驱动步调的全部函数内部都利用宏SPI_HANDLE表现SPI2接口。
        这样定义硬件接口后,假如要将这个驱动步调移植到其他开发板上操纵W25Q16,只需修改这3个宏的定义即可。
        文件w25flash.h中的函数分为几组,这些函数就是W25Q16常用指令的实现。文件w25flash.c的全部代码有400多行,这里就不全部表现出来了,只选择其中一些范例的函数进行解释说明。
        文件w25flash.h是W25Q16驱动步调的头文件,定义了一些宏和函数。这个文件的完备代码如下:
(1)w25flash.h

  1. /* 文件: w25flash.h
  2. * 功能描述: Flash 存储器W25Q16的驱动程序
  3. * 作者:
  4. * 移植:
  5. * 修改日期:2019-06-05
  6. * 移植日期:2024-12-09
  7. * W25Q16BV 芯片参数: 2M字节,24位地址线
  8. * 分为32个Block,每个Block 64K字节
  9. * 一个Block又分为16个Sector,共512个Sector,每个Sector 4K字节
  10. * 一个Sector又分为16个Page,共8192个Page,每个Page 256字节
  11. * 写数据操作的基本单元是Page,一次连续写入操作不能超过一个Page的范围。写的Page必须是擦除过的。
  12. */
  13. #ifndef _W25FLASH_H
  14. #define _W25FLASH_H
  15. #include "stm32f4xx_hal.h"
  16. #include "spi.h"                        //使用其中的变量 hspi1,表示SPI1接口
  17. /* W25Q16硬件接口相关的部分:CS引脚和SPI接口 ,若电路不同,更改这部分配置即可*/
  18. // Flash_CS -->PC13, 片选信号CS操作的宏定义函数
  19. #define CS_PORT GPIOC
  20. #define        CS_PIN GPIO_PIN_13
  21. #define        SPI_HANDLE hspi1        //SPI接口对象,使用spi.h中的变量 hspi1
  22. #define        __Select_Flash() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET)        //CS=0
  23. #define        __Deselect_Flash() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET)        //CS=1
  24. //===========Flash存储芯片W25Q16的存储容量参数================
  25. #define FLASH_PAGE_SIZE 256                //一个Page是256字节
  26. #define        FLASH_SECTOR_SIZE 4096        //一个Sector是4096字节
  27. #define        FLASH_SECTOR_COUNT 512        //总共512个Sector
  28. //=======1. SPI 基本发送和接收函数,阻塞式传输============
  29. HAL_StatusTypeDef SPI_TransmitOneByte(uint8_t byteData);                                        //SPI接口发送一个字节
  30. HAL_StatusTypeDef SPI_TransmitBytes(uint8_t* pBuffer, uint16_t byteCount);        //SPI接口发送多个字节
  31. uint8_t        SPI_ReceiveOneByte();                                                                                                //SPI接口接收一个字节
  32. HAL_StatusTypeDef SPI_ReceiveBytes(uint8_t* pBuffer, uint16_t byteCount);        //SPI接口接收多个字节
  33. //=========2. W25Qxx 基本控制指令==========
  34. // 0xEF14,表示芯片型号为W25Q16BV, Winbond,0x90指令
  35. // 0xEF16,表示芯片型号为W25Q64JV, Winbond,0x90指令
  36. // 0xEF17,表示芯片型号为W25Q128JV, Winbond,0x90指令
  37. // 根据FLASH数据修改此处,比如:
  38. // 0xC817,表示芯片型号为GD25Q128,ELM,用过
  39. // 0x1C17,表示芯片型号为EN25Q128,台湾EON
  40. // 0xA117,表示芯片型号为FM25Q128,复旦微电子
  41. // 0x2018,表示芯片型号为N25Q128,美光
  42. // 0x2017,表示芯片型号为XM25QH128,武汉新芯,用过
  43. uint16_t Flash_ReadID(void);         // Command=0x90, Manufacturer/Device ID
  44. uint64_t Flash_ReadSerialNum(uint32_t* High32,  uint32_t* Low32);        //Command=0x4B, Read Unique ID, 64-bit
  45. // W25Q16无此指令
  46. // HAL_StatusTypeDef Flash_WriteVolatile_Enable(void);                          //Command=0x50: Write Volatile Enable
  47. HAL_StatusTypeDef Flash_Write_Enable(void);          //Command=0x06: Write Enable, 使WEL=1
  48. HAL_StatusTypeDef Flash_Write_Disable(void);        //Command=0x04, Write Disable,使WEL=0
  49. uint8_t        Flash_ReadSR1(void);                  //Command=0x05:  Read Status Register-1,返回寄存器SR1的值
  50. uint8_t        Flash_ReadSR2(void);                  //Command=0x35:  Read Status Register-2,返回寄存器SR2的值
  51. void Flash_WriteSR1(uint8_t SR1);        //Command=0x01:  Write Status Register,        只写SR1的值,禁止写状态寄存器
  52. uint32_t Flash_Wait_Busy(void);          //读状态寄存器SR1,等待BUSY变为0,返回值是等待时间
  53. void Flash_PowerDown(void);                   //Command=0xB9: Power Down
  54. void Flash_WakeUp(void);                          //Command=0xAB: Release Power Down
  55. //========3. 计算地址的辅助功能函数========
  56. //根据Block  绝对编号获取地址,共32个Block
  57. uint32_t Flash_Addr_byBlock(uint8_t BlockNo);
  58. //根据Sector 绝对编号获取地址,共512个Sector
  59. uint32_t Flash_Addr_bySector(uint16_t  SectorNo);
  60. //根据Page  绝对编号获取地址,共8192个Page
  61. uint32_t Flash_Addr_byPage(uint16_t  PageNo);
  62. //根据Block编号,和内部Sector编号计算地址,一个Block有16个Sector,
  63. uint32_t Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo);
  64. //根据Block编号,内部Sector编号,内部Page编号计算地址
  65. uint32_t Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo, uint8_t  SubPageNo);
  66. //将24位地址分解为3个字节
  67. void Flash_SpliteAddr(uint32_t globalAddr, uint8_t* addrHigh, uint8_t* addrMid,uint8_t* addrLow);
  68. //=======4. chip、Block,Sector擦除函数============
  69. //Command=0xC7: Chip Erase, 擦除整个器件,大约4秒
  70. void Flash_EraseChip(void);
  71. //Command=0xD8: Block Erase(64KB) 擦除整个Block, globalAddr是全局地址,耗时大约150ms
  72. void Flash_EraseBlock64K(uint32_t globalAddr);
  73. //Command=0x20: Sector Erase(4KB) 扇区擦除, globalAddr是扇区的全局地址,耗时大约30ms
  74. void Flash_EraseSector(uint32_t globalAddr);
  75. //=========5. 数据读写函数=============
  76. //Command=0x03,  读取一个字节,任意全局地址
  77. uint8_t Flash_ReadOneByte(uint32_t globalAddr);
  78. //Command=0x03,  连续读取多个字节,任意全局地址
  79. void Flash_ReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount);
  80. //Command=0x0B,  高速连续读取多个字节,任意全局地址, 速度大约是常规读取的2倍
  81. void Flash_FastReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount);
  82. //Command=0x02: Page program 对一个Page写入数据(最多256字节), globalAddr是初始位置的全局地址,耗时大约3ms
  83. void Flash_WriteInPage(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount);
  84. //从某个Sector的起始地址开始写数据,数据可能跨越多个Page,甚至跨越Sector,总字节数byteCount不能超过64K,也就是一个Block的大小
  85. void Flash_WriteSector(uint32_t globalAddr,  const uint8_t* pBuffer, uint16_t byteCount);
  86. #endif
复制代码
        定义了4个SPI基本发送和接收函数,用于传输1字节或多字节,接收1字节或多字节。这几个函数实际上就是调用了SPI的HAL驱动步调中阻塞式数据传输函数HAL_SPI_Transmit()和HAL SPI_Receive()。在封装为W25Q16驱动步调的函数时,内部直接利用宏SPI_HANDLE替换了具体的hspil,利用宏定义常量MAX_TIMEOUT作为超时等候时间,这样可以简化函数的调用,因为这几个基本的传输函数在其他函数里被大量调用。
(2) w25flash.c

        1)W25Q16基本操纵指令

        W25Q16的每个函数对应于W25Q16的一个指令。例如:

        读器件ID的函数Flash ReadID()就是实现了指令码为0x90的指令;FlashWrite_Enable()函数实现了“写使能”(指令码0x06)指令;Flash_Wait_Busy()函数读取状态寄存器SR1,判定BUSY位是否为0,直到BUSY为0时才退出。

        Flash_Write_Enable()和Flash_Wait_Busy()是在其他指令操纵函数里经常用到的,例如擦除芯片、擦除扇区、写数据等操纵之前必须实验“写使能”指令。一些比较耗时间的操纵实验后,必须等候状态寄存器SR1的BUSY位变为0,也就是必要调用函数Flash_Wait_Busy()。
        这些函数的代码解释了通过一个函数实现一个指令操纵的方法,它们就是根据指令定义以及相应的指令时序图,通过片选信号CS的控制以及SPI接口的字节数据发送和接收来实现一个指令的操纵。例如,函数Flash_ReadID()实验查询芯片的制造商和器件ID的指令,指令码0x90。步调先实验宏函数__Select_Flash()使片选信号CS为低电平,从而开始一次SPI传输。然后,按照指令0x90的定义依次发送4个字节数据0x90、0x00、0x00、0x00,其中0x90是指令码,中间两个0x00是dummy字节,末了一个0x00是特定的。末了,W25Q16会返回2字节数据,依次接收这2字节数据,就能得到制造商和器件ID信息。
        函数Flash_Wait_Busy()是用于判定状态寄存器SR1的BUSY位是否为0的,所以必要调用函数Flash_ReadSR1()读取状态寄存器SR1的内容,直到BUSY位变为0时,函数才退出。
        2)盘算所在的辅助功能函数

        W25Q16的一些指令必要利用24位的绝对所在,例如块擦除、读数据等指令都必要提供24位绝对所在。直接记住或推算所在是比较贫苦的,在利用Flash的存储空间时,一般以块、扇区、页为单位进行管理,直接根据块、扇区、页的编号盘算所在是比较实用的。所以,我们在驱动步调中定义了几个辅助函数,用于根据块、扇区、页的编号盘算24位绝对所在,还可以将24位绝对所在分解为3字节数据,便于在指令中利用。
        3)器件、块、扇区擦除函数

        定义了器件擦除、块擦除和扇区擦除指令的操纵函数,其中块擦除和扇区擦除指令必要起始所在。例如,扇区擦除的函数是Flash_EraseSector()。在擦除操纵之前,必须实验“写使能”指令,使状态寄存器SR1的WEL位变为1,并且等候BUSY位变为0的时间,才能开始发送擦除操纵指令。擦除指令发送结束后,器件实验擦除操纵,在此期间BUSY位为1,需等候BUSY位变为0之后,才能退出函数。

        实验擦除操纵后的Flash存储区域数据为0xFF。向存储区域写数据时,必须是擦除后的区域才能写入数据,否则写入无效。所以,一个存储区域只能有效写入一次,下次再写入之前必须先擦除。从存储区读出数据的次数是无限定的。

        4)存储区读写函数

        “读数据”指令(指令码0x03)可以从任何一个24位所在开始读取1字节或一连多字节的数据,由此定义了两个函数Flash_ReadOneByte()和Flash_ReadBytes()。
        写数据利用“页编程”指令(指令码0x02),由此定义函数Flash_WriteInPage()用于向一个页内写入数据。

        利用函数Flash_ReadBytes(读取数据时,起始所在可以是任何所在,读取的数据长度也可以凌驾页的容量,也就是可以凌驾256字节,最多可一连读取65536字节。利用函数Flash_WriteInPage()写入数据时必要注意以下几点。


  • 一次的写数据操纵是限定在一个页范围内的,所以一次写入数据长度最多256字节。
  • 起始所在可以是任何所在,但写数据的偏移所在凌驾页的边界后,会从该页的开始所在继续写。所以,起始所在为页的开始所在时,最多可写入256字节。
  • 写入数据的存储区域必须是擦除过的,也就是存储内容是0xFF,否则写入数据无效。所以一个页只能写入一次,下次再写之前,必要先擦除页。W25Q16擦除的最小单位是扇区。
        驱动步调中还有一个函数Flash_WriteSector(),它可以从一个扇区的起始所在开始写入不凌驾64KB的数据。这个函数内部会先擦除必要用到的扇区,然后将数据按页的大小分解,调用Flash_WriteInPage()逐个页写入数据。

  1. /* 文件: w25flash.c
  2. * 功能描述: Flash 存储器W25Q16的驱动程序
  3. * 作者:
  4. * 移植:
  5. * 修改日期:2019-06-05
  6. * 移植日期:2024-12-10
  7. */
  8. #include "w25flash.h"
  9. #define MAX_TIMEOUT 200 //SPI轮询操作时的最大等待时间,ms
  10. //SPI接口发送一个字节,byteData是需要发送的数据
  11. HAL_StatusTypeDef SPI_TransmitOneByte(uint8_t byteData)
  12. {
  13.         return HAL_SPI_Transmit(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);
  14. }
  15. //SPI接口发送多个字节, pBuffer是发送数据缓存区指针,byteCount是发送数据字节数,byteCount最大256
  16. HAL_StatusTypeDef SPI_TransmitBytes(uint8_t* pBuffer, uint16_t byteCount)
  17. {
  18.         return HAL_SPI_Transmit(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
  19. }
  20. //SPI接口接收一个字节,返回接收的一个字节数据
  21. uint8_t        SPI_ReceiveOneByte()
  22. {
  23.         uint8_t        byteData=0;
  24.         HAL_SPI_Receive(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);
  25.         return byteData;
  26. }
  27. //SPI接口接收多个字节,pBuffer是接收数据缓存区指针,byteCount是需要接收数据的字节数
  28. HAL_StatusTypeDef SPI_ReceiveBytes(uint8_t* pBuffer, uint16_t byteCount)
  29. {
  30.         return HAL_SPI_Receive(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
  31. }
  32. //Command=0x05:  Read Status Register-1,返回寄存器SR1的值
  33. uint8_t Flash_ReadSR1(void)
  34. {  
  35.         uint8_t byte=0;
  36.         __Select_Flash();        //CS=0
  37.         SPI_TransmitOneByte(0x05); //Command=0x05: Read Status Register-1
  38.         byte=SPI_ReceiveOneByte();
  39.         __Deselect_Flash();        //CS=1
  40.         return byte;   
  41. }
  42. //Command=0x35: Read Status Register-2,返回寄存器SR2的值
  43. uint8_t Flash_ReadSR2(void)
  44. {
  45.         uint8_t byte=0;
  46.         __Select_Flash();        //CS=0
  47.         SPI_TransmitOneByte(0x35);        //Command=0x35: Read Status Register-2
  48.         byte=SPI_ReceiveOneByte();        //读取一个字节
  49.         __Deselect_Flash();        //CS=1
  50.         return byte;
  51. }
  52. //Command=0x01: Write Status Register,只写SR1的值
  53. //耗时大约10-15ms
  54. void Flash_WriteSR1(uint8_t SR1)
  55. {   
  56.         Flash_Write_Enable();                //必须使 WEL=1
  57.         __Select_Flash();                        //CS=0
  58.         SPI_TransmitOneByte(0x01);        //Command=0x01:Write Status Register,只写SR1的值
  59.         SPI_TransmitOneByte(0x00);        //SR1的值
  60. //        SPI_WriteOneByte(0x00);                //SR2的值, 只发送SR1的值,而不发送SR2的值,QE和CMP将自动被清零
  61.         __Deselect_Flash();                        //CS=1
  62.         Flash_Wait_Busy();                        //耗时大约10-15ms
  63. }  
  64. /*
  65. HAL_StatusTypeDef Flash_WriteVolatile_Enable(void)          //Command=0x50: Write Volatile Enable
  66. {
  67.         __Select_Flash();        //CS=0
  68.         HAL_StatusTypeDef result=SPI_TransmitOneByte(0x50);
  69.         __Deselect_Flash();        //CS=1
  70.         return result;
  71. }
  72. */
  73. //Command=0x06: Write Enable,使WEL=1
  74. HAL_StatusTypeDef Flash_Write_Enable(void)
  75. {
  76.         __Select_Flash();        //CS=0
  77.         HAL_StatusTypeDef result=SPI_TransmitOneByte(0x06);  //Command=0x06: Write Enable,使WEL=1
  78.         __Deselect_Flash();        //CS=1
  79.         Flash_Wait_Busy();         //等待操作完成
  80.         return result;
  81. }
  82. //Command=0x04, Write Disable,使WEL=0
  83. HAL_StatusTypeDef Flash_Write_Disable(void)
  84. {  
  85.         __Select_Flash();        //CS=0
  86.         HAL_StatusTypeDef result=SPI_TransmitOneByte(0x04); //Command=0x04, Write Disable,使WEL=0
  87.         __Deselect_Flash();        //CS=1
  88.         Flash_Wait_Busy();
  89.         return result;
  90. }
  91. //根据Block绝对编号获取地址, 共32个Block,BlockNo 取值范围0-31
  92. //每个块64K字节,16位地址,块内地址范围0x0000-0xFFFF。
  93. uint32_t Flash_Addr_byBlock(uint8_t        BlockNo)
  94. {
  95. //        uint32_t addr=BlockNo*0x10000;
  96.         uint32_t addr = BlockNo;
  97.         addr = addr<<16; //左移16位,等于乘以0x10000
  98.         return addr;
  99. }
  100. //根据Sector绝对编号获取地址, 共512个Sector, SectorNo取值范围0-511
  101. //每个扇区4K字节,12位地址,扇区内地址范围0x000-0xFFF
  102. uint32_t Flash_Addr_bySector(uint16_t SectorNo)
  103. {
  104.         if (SectorNo>511)        //不能超过511
  105.                 SectorNo=0;
  106. //        uint32_t addr=SectorNo*0x1000;
  107.         uint32_t addr = SectorNo;
  108.         addr = addr<<12;        //左移12位,等于乘以0x1000
  109.         return addr;
  110. }
  111. //根据Page绝对编号获取地址,共8192个Page,  PageNo取值范围0-8191
  112. //每个页256字节,8位地址,页内地址范围0x00—0xFF
  113. uint32_t Flash_Addr_byPage(uint16_t PageNo)
  114. {
  115. //        uint32_t addr=PageNo*0x100;
  116.         uint32_t addr = PageNo;
  117.         addr = addr<<8;                //左移8位,等于乘以0x100
  118.         return addr;
  119. }
  120. //根据Block编号和内部Sector编号计算地址,一个Block有16个Sector
  121. //BlockNo取值范围0-31,内部SubSectorNo取值范围0-15
  122. uint32_t Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo)
  123. {
  124.         if (SubSectorNo>15)         //不能超过15
  125.                 SubSectorNo = 0;
  126. //        uint32_t addr=BlockNo*0x10000;                //先计算Block的起始地址
  127.         uint32_t addr = BlockNo;
  128.         addr = addr<<16;        //先计算Block的起始地址
  129. //        uint32_t offset=SubSectorNo*0x1000;        //计算Sector的偏移地址
  130.         uint32_t offset = SubSectorNo;                //计算Sector的偏移地址
  131.         offset = offset<<12;//计算Sector的偏移地址
  132.         addr += offset;
  133.         return addr;
  134. }
  135. // 根据Block编号,内部Sector编号,内部Page编号获取地址
  136. // BlockNo取值范围0-31
  137. // 一个Block有16个Sector, 内部SubSectorNo取值范围0-15
  138. // 一个Sector有16个Page , 内部SubPageNo取值范围0-15
  139. uint32_t Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo, uint8_t SubPageNo)
  140. {
  141.         if (SubSectorNo>15)        //不能超过15
  142.                 SubSectorNo = 0;
  143.         if (SubPageNo>15)        //不能超过15
  144.                 SubPageNo = 0;
  145. //        uint32_t addr=BlockNo*0x10000;                //先计算Block的起始地址
  146.         uint32_t addr = BlockNo;
  147.         addr = addr<<16;        //先计算Block的起始地址
  148. //        uint32_t offset=SubSectorNo*0x1000;        //计算Sector的偏移地址
  149.         uint32_t offset = SubSectorNo;                //计算Sector的偏移地址
  150.         offset = offset<<12;//计算Sector的偏移地址
  151.         addr += offset;
  152. //        offset=SubPageNo*0x100;        //计算Page的偏移地址
  153.         offset = SubPageNo;
  154.         offset = offset<<8;//计算Page的偏移地址
  155.         addr += offset;           //Page的起始地址
  156.         return addr;
  157. }
  158. // 将24位地址分解为3个字节
  159. // globalAddr是全局24位地址, 返回 addrHigh高字节,addrMid中间字节,addrLow低字节
  160. void Flash_SpliteAddr(uint32_t globalAddr, uint8_t* addrHigh, uint8_t* addrMid,uint8_t* addrLow)
  161. {
  162.         *addrHigh = (globalAddr>>16);                //addrHigh=高字节
  163.         globalAddr = globalAddr & 0x0000FFFF;//屏蔽高字节
  164.         *addrMid = (globalAddr>>8);                        //addrMid=中间字节
  165.         *addrLow = globalAddr & 0x000000FF;        //屏蔽中间字节,只剩低字节,addrLow=低字节
  166. }
  167. //读取芯片ID
  168. //返回值如下:                                  
  169. // 0xEF17,表示芯片型号为W25Q16, Winbond,用过
  170. // 0xC817,表示芯片型号为GD25Q16,ELM,用过
  171. // 0x1C17,表示芯片型号为EN25Q16,台湾EON
  172. // 0xA117,表示芯片型号为FM25Q16,复旦微电子
  173. // 0x2018,表示芯片型号为N25Q16,美光
  174. // 0x2017,表示芯片型号为XM25QH16,武汉新芯,用过
  175. //读取芯片的制造商和器件ID,高字节是Manufacturer ID,低字节是Device ID
  176. uint16_t Flash_ReadID(void)
  177. {
  178.         uint16_t Temp = 0;
  179.         __Select_Flash();        //CS=0
  180.         SPI_TransmitOneByte(0x90);                //指令码,0x90=Manufacturer/Device ID
  181.         SPI_TransmitOneByte(0x00);                //dummy
  182.         SPI_TransmitOneByte(0x00);                //dummy
  183.         SPI_TransmitOneByte(0x00);                //0x00
  184.         Temp = SPI_ReceiveOneByte()<<8;        //Manufacturer ID
  185.         Temp|= SPI_ReceiveOneByte();        //Device ID, 与具体器件相关
  186.         __Deselect_Flash();        //CS=1
  187.         return Temp;
  188. }
  189. // 参数High32和Low32分别返回64位序列号的高32位和低32位的值
  190. // 函数返回值为64位序列号的值
  191. uint64_t Flash_ReadSerialNum(uint32_t* High32,  uint32_t* Low32)//读取64位序列号,
  192. {
  193.         uint8_t Temp = 0;
  194.         uint64_t SerialNum = 0;
  195.         uint32_t High=0,Low = 0;
  196.         __Select_Flash();                        //CS=0
  197.         SPI_TransmitOneByte(0x4B);        //发送指令码, 4B=read Unique ID
  198.         SPI_TransmitOneByte(0x00);        //发送4个Dummy字节数据
  199.         SPI_TransmitOneByte(0x00);
  200.         SPI_TransmitOneByte(0x00);
  201.         SPI_TransmitOneByte(0x00);
  202.         for(uint8_t i=0; i<4; i++)//高32位
  203.         {
  204.                 Temp = SPI_ReceiveOneByte();
  205.                 High = (High<<8);
  206.                 High = High | Temp;   //按位或
  207.         }
  208.         for(uint8_t i=0; i<4; i++)//低32位
  209.         {
  210.                 Temp = SPI_ReceiveOneByte();
  211.                 Low = (Low<<8);
  212.                 Low = Low | Temp;           //按位或
  213.         }
  214.         __Deselect_Flash();                 //CS=1
  215.         *High32 = High;
  216.         *Low32 = Low;
  217.         SerialNum = High;
  218.         SerialNum = SerialNum<<32;//高32位
  219.         SerialNum = SerialNum | Low;
  220.         return SerialNum;
  221. }
  222. // 在任意地址读取一个字节的数据,返回读取的字节数据
  223. // globalAddr是24位全局地址
  224. uint8_t Flash_ReadOneByte(uint32_t globalAddr)
  225. {
  226.         uint8_t byte2, byte3, byte4;
  227.         Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节
  228.         __Select_Flash();        //CS=0
  229.         SPI_TransmitOneByte(0x03);         //Command=0x03, read data
  230.         SPI_TransmitOneByte(byte2);         //发送24位地址
  231.         SPI_TransmitOneByte(byte3);
  232.         SPI_TransmitOneByte(byte4);
  233.         byte2 = SPI_ReceiveOneByte();//接收1个字节 //why byte2?
  234.         __Deselect_Flash();        //CS=1
  235.         return byte2;
  236. }
  237. //从任何地址开始读取指定长度的数据
  238. //globalAddr:开始读取的地址(24bit), pBuffer:数据存储区指针,byteCount:要读取的字节数
  239. void Flash_ReadBytes(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount)
  240. {
  241.         uint8_t byte2, byte3, byte4;
  242.         Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节
  243.         __Select_Flash();        //CS=0
  244.         SPI_TransmitOneByte(0x03);  //Command=0x03, read data
  245.         SPI_TransmitOneByte(byte2);        //发送24位地址
  246.         SPI_TransmitOneByte(byte3);
  247.         SPI_TransmitOneByte(byte4);
  248.         SPI_ReceiveBytes(pBuffer, byteCount);//接收byteCount个字节数据
  249.         __Deselect_Flash();        //CS=1
  250. }  
  251. //Command=0x0B,  高速连续读取flash多个字节,任意全局地址, 速度大约是常规读取的2倍
  252. void Flash_FastReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount)
  253. {
  254. //         uint16_t i;
  255.         uint8_t byte2, byte3, byte4;
  256.         Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节
  257.         __Select_Flash();        //CS=0
  258.         SPI_TransmitOneByte(0x0B);  //Command=0x0B, fast read data
  259.         SPI_TransmitOneByte(byte2);        //发送24位地址
  260.         SPI_TransmitOneByte(byte3);
  261.         SPI_TransmitOneByte(byte4);
  262.         SPI_TransmitOneByte(0x00);        //Dummy字节
  263.         SPI_ReceiveBytes(pBuffer, byteCount);//接收byteCount个字节数据
  264.         __Deselect_Flash();        //CS=1
  265. }
  266. //Command=0xC7: Chip Erase, 擦除整个器件
  267. // 擦除后,所有存储区内容为0xFF,耗时大约25秒
  268. void Flash_EraseChip(void)
  269. {                                   
  270.         Flash_Write_Enable();   //使 WEL=1
  271.         Flash_Wait_Busy();           //等待空闲
  272.         __Select_Flash();                //CS=0
  273.         SPI_TransmitOneByte(0xC7);//Command=0xC7: Chip Erase, 擦除整个器件
  274.         __Deselect_Flash();                //CS=1
  275.         Flash_Wait_Busy();                //等待芯片擦除结束,大约25秒
  276. }   
  277. // Command=0x02: Page program, 对一个页(256字节)编程, 耗时大约3ms,
  278. // globalAddr是写入初始地址,全局地址
  279. // pBuffer是要写入数据缓冲区指针,byteCount是需要写入的数据字节数
  280. // 写入的Page必须是前面已经擦除过的,如果写入地址超出了page的边界,就从Page的开头重新写
  281. void Flash_WriteInPage(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount)
  282. {
  283.         uint8_t byte2, byte3, byte4;
  284.         Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节
  285.         Flash_Write_Enable();//SET WEL
  286.         Flash_Wait_Busy();
  287.         __Select_Flash();        //CS=0
  288.         SPI_TransmitOneByte(0x02);  //Command=0x02: Page program 对一个扇区编程
  289.         SPI_TransmitOneByte(byte2);        //发送24位地址
  290.         SPI_TransmitOneByte(byte3);
  291.         SPI_TransmitOneByte(byte4);
  292.         SPI_TransmitBytes(pBuffer, byteCount);//发送byteCount个字节的数据
  293. //        for(uint16_t i=0; i<byteCount; i++)
  294. //        {
  295. //                byte2=pBuffer[i];
  296. //                SPI_WriteOneByte(byte2);//要写入的数据
  297. //        }
  298.         __Deselect_Flash();        //CS=1
  299.         Flash_Wait_Busy();         //耗时大约3ms
  300. }
  301. // 从某个Sector的起始位置开始写数据,数据可能跨越多个Page,甚至跨越Sector,不必提前擦除
  302. // globalAddr是写入初始地址,全局地址,是扇区的起始地址,
  303. // pBuffer是要写入数据缓冲区指针
  304. // byteCount是需要写入的数据字节数,byteCount不能超过64K,也就是一个Block(16个扇区)的大小,但是可以超过一个Sector(4K字节)
  305. // 如果数据超过一个Page,自动分成多个Page,调用EN25Q_WriteInPage分别写入
  306. void Flash_WriteSector(uint32_t globalAddr,  const uint8_t* pBuffer, uint16_t byteCount)
  307. {
  308. //需要先擦除扇区,可能是重复写文件
  309.         uint8_t secCount = (byteCount / FLASH_SECTOR_SIZE);//数据覆盖的扇区个数
  310.         if ((byteCount % FLASH_SECTOR_SIZE) >0)
  311.                 secCount++;
  312.         uint32_t startAddr = globalAddr;
  313.         for (uint8_t k=0; k<secCount; k++)
  314.         {
  315.                 Flash_EraseSector(startAddr);        //擦除扇区
  316.                 startAddr += FLASH_SECTOR_SIZE;        //移到下一个扇区
  317.         }
  318. //分成Page写入数据,写入数据的最小单位是Page
  319.         uint16_t leftBytes = byteCount % FLASH_PAGE_SIZE; //非整数个Page剩余的字节数,即最后一个Page写入的数据
  320.         uint16_t pgCount = byteCount/FLASH_PAGE_SIZE;            //前面整数个Page
  321.         uint8_t* buff = (uint8_t*)pBuffer;
  322.         for(uint16_t i=0; i<pgCount; i++)        //写入前面pgCount个Page的数据,
  323.         {
  324.                 Flash_WriteInPage(globalAddr, buff, FLASH_PAGE_SIZE);//写一整个Page的数据
  325.                 globalAddr += FLASH_PAGE_SIZE;        //地址移动一个Page
  326.                 buff += FLASH_PAGE_SIZE;                //数据指针移动一个Page大小
  327.         }
  328.         if (leftBytes>0)
  329.                 Flash_WriteInPage(globalAddr, buff, leftBytes);                //最后一个Page,不是一整个Page的数据
  330. }
  331. //Command=0xD8: Block Erase(64KB) 擦除整个Block, globalAddr是全局地址
  332. //清除后存储区内容全部为0xFF,  耗时大概150ms
  333. void Flash_EraseBlock64K(uint32_t globalAddr)
  334. {
  335.         Flash_Write_Enable();//SET WEL
  336.         Flash_Wait_Busy();
  337.         uint8_t byte2, byte3, byte4;
  338.         Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节
  339.         __Select_Flash();        //CS=0
  340.         SPI_TransmitOneByte(0xD8);  //Command=0xD8, Block Erase(64KB)
  341.         SPI_TransmitOneByte(byte2);        //发送24位地址
  342.         SPI_TransmitOneByte(byte3);
  343.         SPI_TransmitOneByte(byte4);
  344.         __Deselect_Flash();        //CS=1
  345.         Flash_Wait_Busy();  //耗时大概150ms
  346. }
  347. // 擦除一个扇区(4KB字节),Command=0x20, Sector Erase(4KB)
  348. // globalAddr: 扇区的绝对地址,24位地址0x00XXXXXX
  349. // 擦除后,扇区内全部内容为0xFF, 耗时大约30ms,
  350. void Flash_EraseSector(uint32_t globalAddr)
  351. {  
  352.         Flash_Write_Enable();//SET WEL
  353.         Flash_Wait_Busy();
  354.         uint8_t byte2, byte3, byte4;
  355.         Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节
  356.         __Select_Flash();        //CS=0
  357.         SPI_TransmitOneByte(0x20); //Command=0x20, Sector Erase(4KB)
  358.         SPI_TransmitOneByte(byte2);//发送24位地址
  359.         SPI_TransmitOneByte(byte3);
  360.         SPI_TransmitOneByte(byte4);
  361.         __Deselect_Flash();        //CS=1
  362.         Flash_Wait_Busy();        //大约30ms
  363. }
  364. // 检查寄存器SR1的BUSY位,直到BUSY位为0
  365. uint32_t Flash_Wait_Busy(void)
  366. {   
  367.         uint8_t        SR1 = 0;
  368.         uint32_t  delay = 0;
  369.         SR1=Flash_ReadSR1();            //读取状态寄存器SR1
  370.         while((SR1 & 0x01)==0x01)
  371.         {
  372.                 HAL_Delay(1);
  373.                 delay++;
  374.                 SR1 = Flash_ReadSR1();  //读取状态寄存器SR1
  375.         }
  376.         return delay;
  377. }
  378. // 进入掉电模式
  379. // Command=0xB9: Power Down
  380. void Flash_PowerDown(void)
  381. {
  382.         __Select_Flash();        //CS=0
  383.         SPI_TransmitOneByte(0xB9);        //Command=0xB9: Power Down
  384.         __Deselect_Flash();        //CS=1
  385.     HAL_Delay(1);                //等待TPD
  386. }   
  387. // 唤醒
  388. // Command=0xAB: Release Power Down
  389. void Flash_WakeUp(void)
  390. {  
  391.         __Select_Flash();        //CS=0
  392.         SPI_TransmitOneByte(0xAB);        //Command=0xAB: Release Power Down
  393.         __Deselect_Flash();        //CS=1
  394.         HAL_Delay(1);             //等待TRES1
  395. }
复制代码
2、KEY_LED

        修改参考文章里keyled.h关于指示灯的定义,其它不变:
  1. #ifdef LED1_Pin                 //LED1的控制
  2.         #define LED1_Toggle()        HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin)                                        //输出翻转
  3.         #define LED1_ON()                HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET)         //输出0,亮
  4.         #define LED1_OFF()                 HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET)                //输出1,灭
  5. #endif
  6. #ifdef LED2_Pin         //LED2的控制
  7.         #define LED2_Toggle()        HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin)                                        //输出翻转
  8.         #define LED2_ON()                HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET)        //输出0,亮
  9.         #define LED2_OFF()                HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET)                //输出1,灭
  10. #endif
  11. //新增LED3、LED4
  12. #ifdef LED3_Pin         //LED3的控制
  13.         #define LED3_Toggle()        HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin)                                        //输出翻转
  14.         #define LED3_ON()                HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET)        //输出0,亮
  15.         #define LED3_OFF()                HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET)                //输出1,灭
  16. #endif
  17. #ifdef LED4_Pin         //LED4的控制
  18.         #define LED4_Toggle()        HAL_GPIO_TogglePin(LED4_GPIO_Port,LED4_Pin)                                        //输出翻转
  19.         #define LED4_ON()                HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,GPIO_PIN_RESET)        //输出0,亮
  20.         #define LED4_OFF()                HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,GPIO_PIN_SET)                //输出1,灭
  21. #endif
复制代码
3、spi.h

  1. /* USER CODE BEGIN Private defines */
  2. void Flash_TestReadStatus(void);
  3. void Flash_TestWrite(void);
  4. void Flash_TestRead(void);
  5. /* USER CODE END Private defines */
复制代码
4、spi.c

  1. /* USER CODE BEGIN 0 */
  2. #include "w25flash.h"
  3. #include <string.h>        //用到函数strlen()
  4. #include <stdio.h>
  5. /* USER CODE END 0 */
复制代码
  1. /* USER CODE BEGIN 1 */
  2. //读取DeviceID,状态寄存器 SR1和SR2
  3. void Flash_TestReadStatus(void)
  4. {
  5. //读DeviceID,SR1,SR2
  6.         uint16_t devID = Flash_ReadID();        //读取器件ID
  7.         printf("Device ID = %X\r\n",devID);        //大写的十六进制
  8.         printf("The chip is: ");
  9.         switch (devID)
  10.         {
  11.         case 0xEF14:
  12.                 printf("W25Q16BV \r\n");
  13.                 break;
  14.         case 0xEF16:
  15.                 printf("W25Q64JV \r\n");
  16.                 break;
  17.         case 0xEF17:
  18.                 printf("W25Q128JV \r\n");
  19.                 break;
  20.         case 0xC817:
  21.                 printf("GD25Q128 \r\n");
  22.                 break;
  23.         case 0x1C17:
  24.                 printf("EN25Q128 \r\n");
  25.                 break;
  26.         case 0x2018:
  27.                 printf("N25Q128 \r\n");
  28.                 break;
  29.         case 0x2017:
  30.                 printf("XM25QH128 \r\n");
  31.                 break;
  32.         case 0xA117:
  33.                 printf("FM25Q128 \r\n");
  34.                 break;
  35.         default:
  36.                 printf("Unknown type \r\n");
  37.         }
  38.         uint8_t SR1 = Flash_ReadSR1();                //Read SR1=0x00
  39.         printf("Status Reg1 = %X\r\n",SR1);        //Hex显示
  40.         uint8_t SR2 = Flash_ReadSR2();                //Read SR2=0x00
  41.         printf("Status Reg2 = %X\r\n",SR2);        //Hex显示
  42. }
  43. //测试写入Page0 和Page1
  44. //注意:一个Page写入之前必须是被擦除过的,写入之后就不能再重复写��??
  45. void Flash_TestWrite(void)
  46. {
  47.         uint8_t blobkNo = 0;
  48.         uint16_t sectorNo = 0;
  49.         uint16_t pageNo = 0;
  50.         uint32_t memAddress = 0;
  51.         //写入Page0 两个string
  52.         memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo,pageNo);        //Page0 address
  53.         uint8_t        bufStr1[] = "Hello from beginning";
  54.         uint16_t len = 1+strlen((char*)bufStr1);                 //Include'\0'
  55.         Flash_WriteInPage(memAddress,bufStr1,len);           //Write data at the beginning of Page0.
  56.         printf("Write in Page0:0 %s\r\n",bufStr1);
  57.         uint8_t        bufStr2[] = "Hello in page";
  58.         len = 1+strlen((char*)bufStr2);                                 //include '\0'
  59.         Flash_WriteInPage(memAddress+100,bufStr2,len);        //Offset address 100 within Page0
  60.         printf("Write in Page0:100 %s\r\n",bufStr2);                //display string
  61. //写入Page 1
  62.         uint8_t        bufPage[FLASH_PAGE_SIZE];        //EN25Q_PAGE_SIZE=256
  63.         for (uint16_t i=0;i<FLASH_PAGE_SIZE; i++)
  64.                 bufPage[i] = i;                                        //准备数据
  65.         pageNo = 1;                                                 //Page 1
  66.         memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo, pageNo);//page1 address
  67.         Flash_WriteInPage(memAddress, bufPage, FLASH_PAGE_SIZE);//写一个Page
  68.         printf("Write 0-255 in Page1 \r\n");
  69. }
  70. //test for reading Page0 and Page1
  71. void Flash_TestRead(void)
  72. {
  73.         uint8_t        blobkNo = 0;
  74.         uint16_t sectorNo = 0;
  75.         uint16_t pageNo = 0;
  76. //读取Page 0
  77.         uint8_t bufStr[50];                                                        //Data read from Page0
  78.         uint32_t memAddress = Flash_Addr_byBlockSectorPage(blobkNo,sectorNo,pageNo);
  79.         Flash_ReadBytes(memAddress,bufStr,50);                //read 50 Bytes
  80.         printf("Read from Page0:0 %s\r\n",(char*)bufStr);        //display string
  81.         Flash_ReadBytes(memAddress+10,bufStr,50);        //地址偏移100后的50个字节
  82.         printf("Read from Page0:100 %s\r\n",bufStr);//display string
  83. //读取Page 1
  84.         uint8_t        randData = 0;
  85.         pageNo = 1;
  86.         memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo,pageNo);
  87.         randData = Flash_ReadOneByte(memAddress+12);//读取1个字节数据,页内地址偏移12
  88.         printf("Page1[12] = %d\r\n",randData);
  89.         randData =Flash_ReadOneByte(memAddress+136);//页内地址偏移136
  90.         printf("Page1[136] = %d\r\n",randData);
  91.         randData =Flash_ReadOneByte(memAddress+210);//页内地址偏移210
  92.         printf("Page1[210] = %d\r\n",randData);
  93. }
  94. /* USER CODE END 1 */
复制代码
5、main.c

  1. /* USER CODE BEGIN Includes */
  2. #include "w25flash.h"        //W25Q16驱动程序.h
  3. #include "keyled.h"
  4. #include <stdio.h>
  5. /* USER CODE END Includes */
复制代码
  1.   /* USER CODE BEGIN 2 */
  2.   //Read and display the values ​​of DeviceID, status register SR1 and SR2.
  3.   Flash_TestReadStatus();
  4.   // MCU output low level LED light is on
  5.   LED1_OFF();
  6.   LED2_OFF();
  7.   LED3_OFF();
  8.   LED4_OFF();
  9.   /* USER CODE END 2 */
复制代码
  1. /* USER CODE BEGIN 3 */
  2.         KEYS curKey=ScanPressedKey(KEY_WAIT_ALWAYS);
  3.         switch(curKey)
  4.         {
  5.           case KEY_UP:                //S2
  6.                   printf("Erasing chip, about 30sec...\r\n");
  7.                   Flash_EraseChip();
  8.                   printf("Chip is erased.\r\n");
  9.         LED1_ON();
  10.                   LED2_OFF();
  11.                   LED3_OFF();
  12.                   LED4_OFF();
  13.                   break;
  14.           case KEY_DOWN:            //S3
  15.                   printf("Erasing Block 0(256 pages)...\r\n");
  16.                   uint32_t globalAddr=0x000000;
  17.                   Flash_EraseBlock64K(globalAddr);
  18.                   printf("Block 0 is erased.\r\n");
  19.         LED1_OFF();
  20.                   LED2_ON();
  21.                   LED3_OFF();
  22.                   LED4_OFF();
  23.                   break;
  24.           case KEY_LEFT:                //S4
  25.                   Flash_TestWrite();        //测试写入Page0 and Page1
  26.         LED1_OFF();
  27.                   LED2_OFF();
  28.                   LED3_ON();
  29.                   LED4_OFF();
  30.                   break;
  31.           case KEY_RIGHT:                //S5
  32.                   Flash_TestRead();        //测试读取Page0 and Page1
  33.         LED1_OFF();
  34.                   LED2_OFF();
  35.                   LED3_OFF();
  36.                   LED4_ON();
  37.                   break;
  38.           default:
  39.         LED1_OFF();
  40.                 LED2_OFF();
  41.                 LED3_OFF();
  42.                 LED4_OFF();
  43.                   break;
  44.         }
  45.         printf("** Reselect menu or reset **\r\n");
  46.         HAL_Delay(500);                    //Delay to eliminate key jitter.
  47.   }
  48.   /* USER CODE END 3 */
复制代码
  1. /* USER CODE BEGIN 4 */
  2. //串口打印
  3. int __io_putchar(int ch)
  4. {
  5.         HAL_UART_Transmit(&huart6, (uint8_t*)&ch, 1, 0xFFFF);
  6.         return ch;
  7. }
  8. /* USER CODE END 4 */
复制代码
三、下载与运行

       下载后,自动打印芯片ID为EF14,并打印状态寄存器为0;
        按下S2键,对芯片整体格式化,并打印;
        按下S3键,对BLOCK0格式化,并打印;
        按下S4键, 对芯片进行写操纵,并打印;
        按下S5键,对芯片进行读操纵,并打印;
        上述操纵结果,如下图:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

耶耶耶耶耶

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表