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

【经验分享】STM32H7的QSPI总线应用之驱动W25QXX(支持查询和MDMA)

[复制链接]
STMCU小助手 发布时间:2021-11-4 21:24
79.1 初学者重要提示
1、 学习本章节前,务必优先学习第79章。

2、 W25Q256JV属于NOR型Flash存储芯片。

3、 W25Q256JV手册下载地址。

d158c855c3630d64e0d72336ffa1a440.png


4、 本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。

5、 对QSPI Flash W25Q256JV的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。

6、 W25Q256JV最高支持133MHz。

7、 STM32H7驱动QSPI Flash的4线DMA模式,读速度48MB/S左右。

8、 内存映射模式下,最后一个字节无法正常读取的解决办法。

9、 本章配套例子的DMA是采用性能最强的MDMA。

79.2 W25QXX硬件设计
STM32H7驱动W25Q256JV的硬件设计如下:

43d24f69b56108e41724030a699d04ea.png


关于这个原理图,要了解到以下几个知识:

  V7开发板实际外接的芯片是W25Q256JV。
  CS片选最好接上拉电阻,防止意外操作。
  W25Q256的WP引脚用于写保护,低电平有效性,当前是将其作为4方式的IO2。
  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。当前是将其作为4线方式的IO3。
79.3 W25QXX关键知识点整理(重要)
驱动W25QXX前要先了解下这个芯片的相关信息。

d211eea6c41b7da28846a28c61bfebf4.png


79.3.1 W25QXX基础信息
  W25Q256JV是32MB(256Mbit)。
  W25Q256JV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。

支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。

(注:这里几线的意思是几个数据线)。

  W25Q256JV支持的最高时钟是133MHz。
  每个扇区最少支持10万次擦写,可以保存20年数据。
  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
整体框图如下:

2da620aeaf02ba8b3035e0b864e140b2.png


W25Q256JV:

  有512个Block,每个Block大小64KB。
  每个Block有16个Sector,每个Sector大小4KB。
  每个Sector有16个Page,每个Page大小是256字节。
79.3.2 W25QXX命令
使用W25Q256的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的QSPI,即4线SPI,并且用的4字节地址模式,使用的命令如下:

77c20d25330bfc517d68b73c7751ddf3.png


ee48eed7fa217e118947120ca34f5263.png


当前主要用到如下几个命令:

  1. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  
  2. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  
  3. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */
  4. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
  5. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
  6. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */
  7. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码

79.3.3 W25QXX页编程和页回卷
SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):

  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               

  5.     for (i = 0; i < _usSize; i++)
  6.     {
  7.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
  8.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
  9.     }
复制代码

页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。

比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):

  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。

56240b94a12eb9a067e63db9803b13b7.png


79.3.4 W25QXX扇区擦除
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。

如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。

79.3.5 W25QXX规格参数
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:

e21b7448e777b6e8e376fb71ea3e75ee.png



  页编程时间:典型值0.4ms,最大值3ms。
  扇区擦除时间(4KB):典型值50ms,最大值400ms。
  块擦除时间(32KB):典型值120ms,最大值1600ms。
  块擦除时间(64KB):典型值150ms,最大值2000ms。
  整个芯片擦除时间:典型值80s,最大值400s。

支持的速度参数如下:

9ab6f9e23e8cfb4e497e67913dda1d7e.png


可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。

79.4 W25QXX驱动设计
W25QXX的程序驱动框架设计如下:

0edab8ae6a09033608ede0892279b72a.png


有了这个框图,程序设计就比较好理解了。

79.4.1 第1步:QSPI总线配置
QSPI总线配置如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_InitQSPI_W25Q256
  4. *    功能说明: QSPI Flash硬件初始化,配置基本参数
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_InitQSPI_W25Q256(void)
  10. {
  11.     /* 复位QSPI */
  12.     QSPIHandle.Instance = QUADSPI;
  13.     if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)
  14.     {
  15.         Error_Handler(__FILE__, __LINE__);
  16.     }

  17.     /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */
  18.     QSPIHandle.Init.ClockPrescaler  = 1;  

  19.     /* 设置FIFO阀值,范围1 - 32 */
  20.     QSPIHandle.Init.FifoThreshold   = 32;

  21.     /*
  22.         QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。
  23.         在外部信号延迟时,这有利于推迟数据采样。
  24.     */
  25.     QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_HALFCYCLE;

  26.     /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */
  27.     //QSPI_FLASH_SIZE - 1; 需要扩大一倍,否则内存映射方位最后1个地址时,会异常。
  28.     QSPIHandle.Init.FlashSize       = QSPI_FLASH_SIZE;

  29.     /* 命令之间的CS片选至少保持2个时钟周期的高电平 */
  30.     QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;

  31.     /*
  32.        MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平
  33.        MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平
  34.     */
  35.     QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;

  36.     /* QSPI有两个BANK,这里使用的BANK1 */
  37.     QSPIHandle.Init.FlashID   = QSPI_FLASH_ID_1;

  38.     /* V7开发板仅使用了BANK1,这里是禁止双BANK */
  39.     QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;

  40.     /* 初始化配置QSPI */
  41.     if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)
  42.     {
  43.         Error_Handler(__FILE__, __LINE__);
  44.     }   
  45. }
复制代码

QSPI这部分配置,要特别注意Flash大小的设置,这里做了特别处理,本来是应该填入QSPI_FLASH_SIZE-1,而我们实际上填入的是QSPI_FLASH_SIZE,主要是因为内存映射模式下,最后一个字节访问有问题。

79.4.2 第2步:QSPI总线的查询和MDMA方式设置
本章提供了QSPI Flash的查询和MDMA两种方式的例子,驱动的区别是调用的API不同,查询方式调用的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。

79.4.3 第3步:W25QXX的读取实现
注:这里以查询方式的API进行说明,DMA方式是一样的。

W25QXX的读取功能是发送的4线快速读取指令0xEC,设置任意地址都可以读取数据,只要不超过芯片容量即可(如果采用的DMA方式,限制每次最大读取65536字节)。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: QSPI_ReadBuffer
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
  5. *    形    参: _pBuf : 数据源缓冲区。
  6. *              _uiReadAddr :起始地址。
  7. *              _usSize :数据个数, 可以大于PAGE_SIZE, 但是不能超出芯片总容量。
  8. *    返 回 值: 无
  9. *********************************************************************************************************
  10. */
  11. void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
  12. {

  13.     QSPI_CommandTypeDef sCommand = {0};


  14.     /* 基本配置 */
  15.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;        /* 1线方式发送指令 */
  16.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;          /* 32位地址 */
  17.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;      /* 无交替字节 */
  18.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;          /* W25Q256JV不支持DDR */
  19.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;      /* DDR模式,数据输出延迟 */
  20.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;        /* 每次传输要发指令 */   

  21.     /* 读取数据 */
  22.     sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速读取命令 */
  23.     sCommand.DummyCycles = 6;                    /* 空周期 */
  24.     sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4线地址 */
  25.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据 */
  26.     sCommand.NbData      = _uiSize;              /* 读取的数据大小 */
  27.     sCommand.Address     = _uiReadAddr;          /* 读取数据的起始地址 */

  28.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
  29.     {
  30.         Error_Handler(__FILE__, __LINE__);
  31.     }

  32.     /* 读取 */
  33.     if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
  34.     {
  35.         Error_Handler(__FILE__, __LINE__);
  36.     }   
  37. }
复制代码

此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:

6c1787c10d01bbaf5602747a947053b6.png


左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_4_LINES;

79.4.4 第4步:W25QXX的编程实现
注:这里以查询方式的API进行说明,DMA方式是一样的。

下面实现了一个页编程函数:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: QSPI_WriteBuffer
  4. *    功能说明: 页编程,页大小256字节,任意页都可以写入
  5. *    形    参: _pBuf : 数据源缓冲区;
  6. *              _uiWriteAddr :目标区域首地址,即页首地址,比如0, 256, 512等。
  7. *              _usWriteSize :数据个数,不能超过页面大小,范围1 - 256。
  8. *    返 回 值: 1:成功, 0:失败
  9. *********************************************************************************************************
  10. */
  11. uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)
  12. {
  13.     QSPI_CommandTypeDef sCommand={0};

  14.     /* 写使能 */
  15.     QSPI_WriteEnable(&QSPIHandle);   

  16.     /* 基本配置 */
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_ONLY_FIRST_CMD;     /* 仅发送一次命令 */   

  23.     /* 写序列配置 */
  24.     sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速写入命令 */
  25.     sCommand.DummyCycles = 0;                    /* 不需要空周期 */
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 4线地址方式 */
  27.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据方式 */
  28.     sCommand.NbData      = _usWriteSize;         /* 写数据大小 */   
  29.     sCommand.Address     = _uiWriteAddr;         /* 写入地址 */

  30.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
  31.     {
  32.         //return 0;
  33.         Error_Handler(__FILE__, __LINE__);
  34.     }

  35.     /* 启动传输 */
  36.     if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
  37.     {
  38.         //return 0;
  39.         Error_Handler(__FILE__, __LINE__);

  40.     }

  41.     QSPI_AutoPollingMemReady(&QSPIHandle);   

  42.     return 1;
  43. }
复制代码

此时函数使用的指令0x34对应的W25Q256JV手册说明,注意红色方框位置:



左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_4_LINES;

79.4.5 第5步:W25QXX的扇区擦除实现
注:这里以查询方式的API进行说明,DMA方式是一样的。

通过发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除,擦除的扇区大小是4KB。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: QSPI_EraseSector
  4. *    功能说明: 擦除指定的扇区,扇区大小4KB
  5. *    形    参: _uiSectorAddr : 扇区地址,以4KB为单位的地址,比如0,4096, 8192等,
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void QSPI_EraseSector(uint32_t _uiSectorAddr)
  10. {
  11.     QSPI_CommandTypeDef sCommand={0};

  12.     /* 写使能 */
  13.     QSPI_WriteEnable(&QSPIHandle);   

  14.     /* 基本配置 */
  15.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
  16.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
  17.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
  18.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
  19.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
  20.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */   

  21.     /* 擦除配置 */
  22.     sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;   /* 32bit地址方式的扇区擦除命令,扇区大小4KB*/      
  23.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */      
  24.     sCommand.Address     = _uiSectorAddr;        /* 扇区首地址,保证是4KB整数倍 */   
  25.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  
  26.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  

  27.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
  28.     {
  29.         Error_Handler(__FILE__, __LINE__);
  30.     }

  31.     QSPI_AutoPollingMemReady(&QSPIHandle);   
  32. }
复制代码

此时函数使用的指令0x21对应的W25Q256JV手册说明,注意红色方框位置:

a09f0f9fb051c5179f3e48824b99e875.png


左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反映到程序里面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;

sCommand.AddressMode = QSPI_ADDRESS_1_LINES;

sCommand.AddressSize = QSPI_ADDRESS_32_BITS;

sCommand.DataMode = QSPI_DATA_1_LINES;

79.4.6 第6步:W25QXX的整个芯片擦除实现
注:这里以查询方式的API进行说明,DMA方式是一样的。

整个芯片的擦除可以通过擦除各个扇区来实现,也可以调用专门的整个芯片擦除指令实现。下面实现方法是发送整个芯片擦除命令实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: QSPI_EraseChip
  4. *    功能说明: 整个芯片擦除
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void QSPI_EraseChip(void)
  10. {
  11.     QSPI_CommandTypeDef sCommand={0};

  12.     /* 写使能 */
  13.     QSPI_WriteEnable(&QSPIHandle);   

  14.     /* 基本配置 */
  15.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
  16.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
  17.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
  18.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
  19.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
  20.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */   

  21.     /* 擦除配置 */
  22.     sCommand.Instruction = BULK_ERASE_CMD;       /* 整个芯片擦除命令*/      
  23.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */      
  24.     sCommand.Address     = 0;                    /* 地址 */   
  25.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  
  26.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  

  27.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
  28.     {
  29.         Error_Handler(__FILE__, __LINE__);
  30.     }

  31.     QSPI_AutoPollingMemReady(&QSPIHandle);   
  32. }
复制代码

此时函数使用的指令0xC7对应的W25Q256JV手册说明,注意红色方框位置:



左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反应到程序里面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_1_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_1_LINES;

擦除用不到数据阶段,sCommand.DataMode  = QSPI_DATA_NONE即可。

79.4.7 第7步:W25QXX内存映射实现
注:这里以查询方式的API进行说明,DMA方式是一样的。

通过内存映射模式,就可以像使用内部Flash一样使用W25QXX,代码实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: QSPI_MemoryMapped
  4. *    功能说明: QSPI内存映射,地址 0x90000000
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void QSPI_MemoryMapped(void)
  10. {
  11.     QSPI_CommandTypeDef s_command = {0};
  12.     QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};

  13.     /* 基本配置 */
  14.     s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;      /* 1线方式发送指令 */
  15.     s_command.AddressSize = QSPI_ADDRESS_32_BITS;             /* 32位地址 */
  16.     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
  17.     s_command.DdrMode = QSPI_DDR_MODE_DISABLE;                /* W25Q256JV不支持DDR */
  18.     s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;   /* DDR模式,数据输出延迟 */
  19.     s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;            /* 每次传输都发指令 */

  20.     /* 全部采用4线 */
  21.     s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速读取命令 */
  22.     s_command.AddressMode = QSPI_ADDRESS_4_LINES;                 /* 4个地址线 */
  23.     s_command.DataMode = QSPI_DATA_4_LINES;                       /* 4个数据线 */
  24.     s_command.DummyCycles = 6;                                    /* 空周期 */

  25.     /* 关闭溢出计数 */
  26.     s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
  27.     s_mem_mapped_cfg.TimeOutPeriod = 0;

  28.     if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)
  29.     {
  30.        Error_Handler(__FILE__, __LINE__);
  31.     }
  32. }
复制代码

此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:

bb245c5c5434f7ec1a828434a3915d8c.png


左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,采用的4字节地址方式,反应到程序里面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_4_LINES;

79.4.8 第8步:使用MDMA方式要注意Cache问题
如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM,这样可以方便大家做测试,测试通过后,再根据需要开启Cache。

  1. /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  3. MPU_InitStruct.BaseAddress      = 0x24000000;
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  11. MPU_InitStruct.SubRegionDisable = 0x00;
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  13. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

79.5 W25QXX板级支持包(bsp_qspi_w25q256.c)
W25QXX驱动文件bsp_qspi_w25q256.c主要实现了如下几个API供用户调用:

  QSPI_ReadBuffer
  QSPI_WriteBuffer
  QSPI_EraseSector
  QSPI_EraseChip
  QSPI_MemoryMapped
79.5.1 函数QSPI_ReadBuffer
函数原型:

void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);

函数描述:

此函数主要用于从QSPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可(如果使用DMA方式,每次最大65536字节)。

函数参数:

  第1个参数用于存储从QSPI Flash读取的数据。
  第2个参数是读取地址,不可以超过芯片容量。
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
79.5.2 函数QSPI_WriteBuffer
函数原型:

uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);

函数描述:

页编程,页大小256字节,任意页都可以写入。注意使用前,务必保证相应页已经做了擦除操作。

函数参数:

  第1个参数是源数据缓冲区。
  第2个参数是目标区域首地址,即页首地址,比如0, 256, 512等。
  第3个参数是数据个数,不能超过页面大小,范围1 – 256,单位字节个数。
  返回值,返回1表示成功,返回0表示失败。
79.5.3 函数QSPI_EraseSector
函数原型:

void QSPI_EraseSector(uint32_t _uiSectorAddr)

函数描述:

此函数主要用于扇区擦除,一个扇区大小是4KB。

函数参数:

  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
79.5.4 函数QSPI_EraseChip
函数原型:

void QSPI_EraseChip(void)

函数描述:

此函数主要用于整个芯片擦除。

79.5.5 函数QSPI_MemoryMapped
函数原型:

void QSPI_MemoryMapped(void)

函数描述:

调用了此函数就可以像使用内部Flash一样使用外部Flash。

79.6 W25QXX驱动移植和使用
W25QXX移植步骤如下:

  第1步:复制bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目录,并添加到工程里面。
  第2步:根据使用的QSPI引脚,时钟等,修改bsp_qspi_w25q256.c文件开头的宏定义。
  1. /*
  2.     STM32-V7开发板接线

  3.     PG6/QUADSPI_BK1_NCS AF10
  4.     PF10/QUADSPI_CLK    AF9
  5.     PF8/QUADSPI_BK1_IO0 AF10
  6.     PF9/QUADSPI_BK1_IO1 AF10
  7.     PF7/QUADSPI_BK1_IO2 AF9
  8.     PF6/QUADSPI_BK1_IO3 AF9

  9.     W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB
  10. */

  11. /* QSPI引脚和时钟相关配置宏定义 */
  12. #define QSPI_CLK_ENABLE()              __HAL_RCC_QSPI_CLK_ENABLE()
  13. #define QSPI_CLK_DISABLE()             __HAL_RCC_QSPI_CLK_DISABLE()
  14. #define QSPI_CS_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOG_CLK_ENABLE()
  15. #define QSPI_CLK_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOF_CLK_ENABLE()
  16. #define QSPI_BK1_D0_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
  17. #define QSPI_BK1_D1_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
  18. #define QSPI_BK1_D2_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
  19. #define QSPI_BK1_D3_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()

  20. #define QSPI_MDMA_CLK_ENABLE()         __HAL_RCC_MDMA_CLK_ENABLE()
  21. #define QSPI_FORCE_RESET()             __HAL_RCC_QSPI_FORCE_RESET()
  22. #define QSPI_RELEASE_RESET()           __HAL_RCC_QSPI_RELEASE_RESET()

  23. #define QSPI_CS_PIN                    GPIO_PIN_6
  24. #define QSPI_CS_GPIO_PORT              GPIOG

  25. #define QSPI_CLK_PIN                   GPIO_PIN_10
  26. #define QSPI_CLK_GPIO_PORT             GPIOF

  27. #define QSPI_BK1_D0_PIN                GPIO_PIN_8
  28. #define QSPI_BK1_D0_GPIO_PORT          GPIOF

  29. #define QSPI_BK1_D1_PIN                GPIO_PIN_9
  30. #define QSPI_BK1_D1_GPIO_PORT          GPIOF

  31. #define QSPI_BK1_D2_PIN                GPIO_PIN_7
  32. #define QSPI_BK1_D2_GPIO_PORT          GPIOF

  33. #define QSPI_BK1_D3_PIN                GPIO_PIN_6
  34. #define QSPI_BK1_D3_GPIO_PORT          GPIOF
  35.   根据使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h头文件
  36. /* W25Q256JV基本信息 */
  37. #define QSPI_FLASH_SIZE     25                      /* Flash大小,2^25 = 32MB*/
  38. #define QSPI_SECTOR_SIZE    (4 * 1024)              /* 扇区大小,4KB */
  39. #define QSPI_PAGE_SIZE      256                        /* 页大小,256字节 */
  40. #define QSPI_END_ADDR        (1 << QSPI_FLASH_SIZE)  /* 末尾地址 */
  41. #define QSPI_FLASH_SIZES    32*1024*1024            /* Flash大小,2^25 = 32MB*/

  42. /* W25Q256JV相关命令 */
  43. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  
  44. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  
  45. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */
  46. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
  47. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
  48. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */
  49. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码

  第4步:如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM:
/* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress      = 0x24000000;
MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);
  第5步:初始化QSPI。
/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */
  第6步:QSPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
  第7步:应用方法看本章节配套例子即可。
79.7 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

32c08174cec9edf62fb6e6103da5cea5.png


  第1阶段,上电启动阶段:

这部分在第14章进行了详细说明。
  第2阶段,进入main函数:

第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
第2部分,应用程序设计部分,实现QSPI Flash的查询和MDMA方式操作。
79.8 实验例程说明(MDK)
配套例子:

V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)

V7-059_QSPI读写例程,查询方式

实验目的:

学习QSPI Flash的读写测试例程
实验操作:

支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
printf("请选择操作命令:\r\n");
printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
printf("【Z - 读取前1K,地址自动减少】\r\n");
printf("【X - 读取后1K,地址自动增加】\r\n");
printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
printf("其他任意键 - 显示命令提示\r\n");
上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1。

71e953366d6cc128ca9c0ddeef6d23ca.png


程序设计:

  系统栈大小分配:

7011bff1036a0ffa03f21e1e5894ea0e.png


  RAM空间用的DTCM:

6c91b4eae6d726676a3e5990d73f448d.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
  38.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  39.     bsp_InitTimer();       /* 初始化滴答定时器 */
  40.     bsp_InitLPUart();     /* 初始化串口 */
  41.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  42.     bsp_InitLed();         /* 初始化LED */   
  43. bsp_InitExtSDRAM(); /* 初始化SDRAM */

  44.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
  45.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14. #if 0
  15.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  16.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  17.     MPU_InitStruct.BaseAddress      = 0x24000000;
  18.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  19.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  20.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  21.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  22.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  23.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  24.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  25.     MPU_InitStruct.SubRegionDisable = 0x00;
  26.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  27.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  28. #else
  29.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
  30.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  31.     MPU_InitStruct.BaseAddress      = 0x24000000;
  32.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  33.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  34.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  35.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  36.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  37.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  38.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  39.     MPU_InitStruct.SubRegionDisable = 0x00;
  40.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  41.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
  42. #endif   

  43.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  44.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  45.     MPU_InitStruct.BaseAddress      = 0x60000000;
  46.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  47.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  48.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  49.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  50.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  51.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  52.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  53.     MPU_InitStruct.SubRegionDisable = 0x00;
  54.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  55.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  56.     /*使能 MPU */
  57.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  58. }

  59. /*
  60. *********************************************************************************************************
  61. *    函 数 名: CPU_CACHE_Enable
  62. *    功能说明: 使能L1 Cache
  63. *    形    参: 无
  64. *    返 回 值: 无
  65. *********************************************************************************************************
  66. */
  67. static void CPU_CACHE_Enable(void)
  68. {
  69.     /* 使能 I-Cache */
  70.     SCB_EnableICache();

  71.     /* 使能 D-Cache */
  72.     SCB_EnableDCache();
  73. }
复制代码

  每10ms调用一次按键处理:

按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_RunPer10ms
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
  6. *    形    参: 无
  7. *    返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. void bsp_RunPer10ms(void)
  11. {
  12.     bsp_KeyScan10ms();
  13. }
复制代码

  主功能:

主程序实现如下操作:

  启动一个自动重装软件定时器,每100ms翻转一次LED2。
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
  printf("【Z - 读取前1K,地址自动减少】\r\n");
  printf("【X - 读取后1K,地址自动增加】\r\n");
  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
  printf("其他任意键 - 显示命令提示\r\n");
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: DemoSpiFlash
  4. *    功能说明: QSPI读写例程
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void DemoSpiFlash(void)
  10. {
  11.     uint8_t cmd;
  12.     uint32_t uiReadPageNo = 0, id;

  13.     /* 检测串行Flash OK */
  14.     id = QSPI_ReadID();
  15.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);
  16.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");

  17.     sfDispMenu();        /* 打印命令提示 */

  18.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */
  19.     while(1)
  20.     {
  21.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  22.         /* 判断软件定时器0是否超时 */
  23.         if(bsp_CheckTimer(0))
  24.         {
  25.             /* 每隔200ms 进来一次 */  
  26.             bsp_LedToggle(2);
  27.         }

  28.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
  29.         {
  30.             switch (cmd)
  31.             {
  32.                 case '1':
  33.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  34.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
  35.                     break;

  36.                 case '2':
  37.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  38.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
  39.                     break;

  40.                 case '3':
  41.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
  42.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
  43.                     break;

  44.                 case '4':
  45.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
  46.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
  47.                     break;

  48.                 case 'y':
  49.                 case 'Y':
  50.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
  51.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
  52.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
  53.                     break;

  54.                 case 'z':
  55.                 case 'Z': /* 读取前1K */
  56.                     if (uiReadPageNo > 0)
  57.                     {
  58.                         uiReadPageNo--;
  59.                     }
  60.                     else
  61.                     {
  62.                         printf("已经是最前\r\n");
  63.                     }
  64.                     sfViewData(uiReadPageNo * 1024);
  65.                     break;

  66.                 case 'x':
  67.                 case 'X': /* 读取后1K */
  68.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)
  69.                     {
  70.                         uiReadPageNo++;
  71.                     }
  72.                     else
  73.                     {
  74.                         printf("已经是最后\r\n");
  75.                     }
  76.                     sfViewData(uiReadPageNo * 1024);
  77.                     break;

  78.                 default:
  79.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
  80.                     break;
  81.             }
  82.         }
  83.     }
  84. }
复制代码

79.9 实验例程说明(IAR)
配套例子:

V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)

V7-059_QSPI读写例程,查询方式

实验目的:

学习QSPI Flash的读写测试例程
实验操作:

支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
printf("请选择操作命令:\r\n");
printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
printf("【Z - 读取前1K,地址自动减少】\r\n");
printf("【X - 读取后1K,地址自动增加】\r\n");
printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
printf("其他任意键 - 显示命令提示\r\n");
上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1。

3ff8536496d184105fc92ca093930e2b.png


程序设计:

  系统栈大小分配:

29c63afe41336006cacb94f46fe77b35.png


  RAM空间用的DTCM:

4dda996917dfda9ff3ebfb7ab53d3531.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
  38.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  39.     bsp_InitTimer();       /* 初始化滴答定时器 */
  40.     bsp_InitLPUart();     /* 初始化串口 */
  41.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  42.     bsp_InitLed();         /* 初始化LED */   
  43. bsp_InitExtSDRAM(); /* 初始化SDRAM */

  44.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
  45.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14. #if 0
  15.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  16.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  17.     MPU_InitStruct.BaseAddress      = 0x24000000;
  18.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  19.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  20.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  21.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  22.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  23.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  24.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  25.     MPU_InitStruct.SubRegionDisable = 0x00;
  26.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  27.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  28. #else
  29.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
  30.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  31.     MPU_InitStruct.BaseAddress      = 0x24000000;
  32.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  33.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  34.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  35.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  36.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  37.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  38.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  39.     MPU_InitStruct.SubRegionDisable = 0x00;
  40.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  41.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
  42. #endif   

  43.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  44.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  45.     MPU_InitStruct.BaseAddress      = 0x60000000;
  46.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  47.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  48.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  49.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  50.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  51.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  52.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  53.     MPU_InitStruct.SubRegionDisable = 0x00;
  54.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  55.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  56.     /*使能 MPU */
  57.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  58. }

  59. /*
  60. *********************************************************************************************************
  61. *    函 数 名: CPU_CACHE_Enable
  62. *    功能说明: 使能L1 Cache
  63. *    形    参: 无
  64. *    返 回 值: 无
  65. *********************************************************************************************************
  66. */
  67. static void CPU_CACHE_Enable(void)
  68. {
  69.     /* 使能 I-Cache */
  70.     SCB_EnableICache();

  71.     /* 使能 D-Cache */
  72.     SCB_EnableDCache();
  73. }
复制代码

  每10ms调用一次按键处理:

按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_RunPer10ms
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
  6. *    形    参: 无
  7. *    返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. void bsp_RunPer10ms(void)
  11. {
  12.     bsp_KeyScan10ms();
  13. }
复制代码

  主功能:

主程序实现如下操作:

  启动一个自动重装软件定时器,每100ms翻转一次LED2。
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
  printf("【Z - 读取前1K,地址自动减少】\r\n");
  printf("【X - 读取后1K,地址自动增加】\r\n");
  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
  printf("其他任意键 - 显示命令提示\r\n");
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: DemoSpiFlash
  4. *    功能说明: QSPI读写例程
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void DemoSpiFlash(void)
  10. {
  11.     uint8_t cmd;
  12.     uint32_t uiReadPageNo = 0, id;

  13.     /* 检测串行Flash OK */
  14.     id = QSPI_ReadID();
  15.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);
  16.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");

  17.     sfDispMenu();        /* 打印命令提示 */

  18.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */
  19.     while(1)
  20.     {
  21.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  22.         /* 判断软件定时器0是否超时 */
  23.         if(bsp_CheckTimer(0))
  24.         {
  25.             /* 每隔200ms 进来一次 */  
  26.             bsp_LedToggle(2);
  27.         }

  28.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
  29.         {
  30.             switch (cmd)
  31.             {
  32.                 case '1':
  33.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  34.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
  35.                     break;

  36.                 case '2':
  37.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  38.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
  39.                     break;

  40.                 case '3':
  41.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
  42.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
  43.                     break;

  44.                 case '4':
  45.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
  46.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
  47.                     break;

  48.                 case 'y':
  49.                 case 'Y':
  50.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
  51.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
  52.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
  53.                     break;

  54.                 case 'z':
  55.                 case 'Z': /* 读取前1K */
  56.                     if (uiReadPageNo > 0)
  57.                     {
  58.                         uiReadPageNo--;
  59.                     }
  60.                     else
  61.                     {
  62.                         printf("已经是最前\r\n");
  63.                     }
  64.                     sfViewData(uiReadPageNo * 1024);
  65.                     break;

  66.                 case 'x':
  67.                 case 'X': /* 读取后1K */
  68.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)
  69.                     {
  70.                         uiReadPageNo++;
  71.                     }
  72.                     else
  73.                     {
  74.                         printf("已经是最后\r\n");
  75.                     }
  76.                     sfViewData(uiReadPageNo * 1024);
  77.                     break;

  78.                 default:
  79.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
  80.                     break;
  81.             }
  82.         }
  83.     }
  84. }
复制代码

79.10   总结
本章节就为大家讲解这么多,实际应用中根据需要选择DMA和查询方式。


f9d118d4b7d97794c046ced058924277.png
940488dfa9f0e39fce107fbd8fd9ea16.png
收藏 评论0 发布时间:2021-11-4 21:24

举报

0个回答

所属标签

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