STM32之QSPI调试记录

STM32之QSPI调试记录

先声明一下,STM32的QSPI外设同样支持单线模式(兼容普通spi),只是相比普通的spi少了一些特性(比如只支持模式0和3、不能LSB发送等),但是用来操作flash已经足矣。如果有朋友和我之前一样,以为要先用普通spi外设去初始化flash并让它进入四线模式,再使用STM32的QSPI外设去操作flash,那么现在可以打消这个顾虑了。

引脚定义

一般情况下,QSPI要使用6个GPIO,其中片选引脚、时钟引脚和spi是一样的,但数据引脚变成了4根。IO0~IO4这四根数据引脚既用来输入也用来输出,所以四线模式下的QSPI实际上是半双工的。

NCS -->片选
CLK -->时钟
IO0 -->数据0
IO1 -->数据1
IO2 -->数据2
IO3 -->数据3

四线模式下的工作机制和SDIO非常像。

单线模式下与SPI对应关系

NCS <--> CS
CLK <--> CLK
IO0 <--> MOSI
IO1 <--> MISO
IO2 <--> 强制低电平(以禁止“写保护”功能)
IO3 <--> 强制高电平(以禁止“保持”功能)

解释一下后两句:在flash进入四线模式之前,有两个引脚被用作写保护、保持功能。一般情况下这两个功能用不上,所以这两个引脚本身也是接GND和VCC的。(下图摘自正点原子阿波罗F4核心板原理图)

在这里插入图片描述

CubeMX初始化配置

1、开启外设

在这里插入图片描述

BANK1 和 BANK2 对应两块区域,STM32的QSPI外设支持同时操作两块FLASH。选择 BANK1还是2取决于开发板上连接FLASH的引脚,因为引脚的功能是不能随便复用的。当然如果开发版支持,可以选择双 BANK。同时,这里选择 Quad (四线),因为我的开发板连接了四根数据线。

2、配置时钟树

主时钟树就按正常配置,QSPI时钟我这里选择 HCLK3 这一路。

在这里插入图片描述

3、参数配置

在这里插入图片描述

  • Clock Prescaler :时钟分频因子。前面配置的QSPI时钟为240M,这个240M就会除以分频因子最终输出到CLK时钟引脚。W25Q系列的FLASH最高支持104M的频率。这里设置成5,输出到FLASH的时钟为48M。

  • Fifo Threshold :FIFO阈值。这个应该是配合DAM用的。我配成1或者4好像没什么区别,都可以用。

  • Sample Shifting :选择这个会在时钟沿的后半周期才采集数据,一般建议选上。

  • Flash Size :FLASH大小。FLASH容量的字节数 = 2 的(FlashSize + 1)次方。比如我的FLASH是8M大小的,这里应该配置成22。

  • Chip Select High Time :从片选开始到正式传输数据的时间,这个根据FLASH芯片的数据手册来选。应该是下图的 tSLCH在这里插入图片描述

  • Clock Mode :选 LOW 对应SPI的模式0(空闲时时钟为低电平,第一个沿采集数据),选 HIGH 对应模式3。W25Q系列支持模式0和3。

  • 最后两个不用管。

4、GPIO设置

按照开发板原理图来选。

但注意,默认的IO口速率是最低,这里一定要调成高速!!!!!

5、生成代码

其他的配置保持默认即可,这里也没用用到中断和DMA。然后生成代码。

QSPI工作机制与HAL库使用说明

1、初始化结构体QSPI_InitTypeDef与句柄结构体QSPI_HandleTypeDef

初始化结构体CubeMX已经帮我们配置好了,如果之前的配置没问题就不需要手动更改。

句柄结构体一般也不需要去看,在后续使用库函数的时候当做输入参数即可。

2、QSPI的命令序列

(下图摘自官方参考手册)

在这里插入图片描述

回想一下之前用SPI操作FLASH的场景:

  • 发送写使能命令:拉低片选,发送 0X06,拉高片选。这里的 0X06 就是指令,而后面的地址、交替字节等都略过了。
  • 写操作:拉低片选,发送 0X02,发送地址,发送数据,拉高片选。
  • 读ID:拉低片选,发送 0X90,等待三个无效字节,接收两字节,拉高片选。这里的三个无效字节,就可以用上面的地址或者交替字节来填补,当然也可以用空周期来填补。

我们对FLASH的操作,就和上面命令序列对应上了。注意,序列的先后顺序是不可以改变的。

3、QSPI_CommandTypeDef结构体

这个结构体是我们使用次数最多的,用来配合库函数发送命令序列。

结构体的成员很多,但大都是用来设置命令序列的。

针对指令阶段的成员:
  • Instruction:指令码。填写像 0X06 这样的指令码,范围是0到255(一字节长度)。

  • InstructionMode:指令模式。有无指令、单线、双线、四线这四种选项。其中无指令就是略过指令阶段。单线就是只使用 IO0 发送指令码,八个时钟周期发完。四线就是通过 IO0~IO3 这四根数据线发送指令码,两个时钟周期发完。

    QSPI_INSTRUCTION_NONE    
    QSPI_INSTRUCTION_1_LINE       
    QSPI_INSTRUCTION_2_LINES     
    QSPI_INSTRUCTION_4_LINES
    
针对地址阶段的成员
  • Address:地址。填写像 0X1234 这样的地址参数,最大长度是四字节。

  • AddressSize:可选8位、16位、24位和32位长度。根据FLASH芯片来定。

    QSPI_ADDRESS_8_BITS        
    QSPI_ADDRESS_16_BITS         
    QSPI_ADDRESS_24_BITS      
    QSPI_ADDRESS_32_BITS            
    

    在这里插入图片描述

  • AddressMode:地址模式。和前面的指令模式一样,有四种选择。如果选择 NONE,前面的地址、地址长度成员填写的参数都会被忽略。

针对交替字节的成员
  • AlternateBytes
  • AlternateBytesSize
  • AlternateByteMode

都和前面一样,就不多说了,具体参数参考 stm32h7xx_hal_qspi.h 头文件。交替字节可以用来占位,填补 Dummy 字节的位置。

针对空周期的成员
  • DummyCycles

顾名思义,就是空周期,范围从0到31。但要注意,无论是单线模式还是四线模式,一个空周期都只是对应一个时钟周期(一个上升沿)。

针对数据阶段的成员
  • DataMode:数据的模式,同前。
  • NbData:数据的长度,从 0 到 0xFFFFFFFF 个字节长度。

这里可能有人会疑惑,那么数据的内容去哪了?无论是发送数据,还是接收数据,数据的内容都是通过库函数来给定的,而非这个结构体。

其他成员
  • DdrMode:是否使能双边沿采集,也就是时钟的上升下降沿都采集数据。一般都不使能 QSPI_DDR_MODE_DISABLE
  • DdrHoldHalfCycle:和上一个相关的。
  • SIOOMode:是否每次传输都发送命令。一般选择是 QSPI_SIOO_INST_EVERY_CMD

4、使用HAL_QSPI_Command函数发送写使能命令

void W25Q_WriteEnable(void)
{
    QSPI_CommandTypeDef qspi_cmd;
    
    qspi_cmd.Instruction = 0x06;  
    qspi_cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    
    qspi_cmd.AddressMode = QSPI_ADDRESS_NONE;
    qspi_cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    
    qspi_cmd.DataMode = QSPI_DATA_NONE;
    
    qspi_cmd.DummyCycles = 0;    
    
    qspi_cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    qspi_cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
  
    HAL_QSPI_Command(&hqspi, &qspi_cmd, 1000);
}

在这里插入图片描述

我的逻辑分析仪不支持分析四线QSPI,所以只好用单线模式来演示。

注意到:

  • 片选信号是硬件自动帮我们完成的。
  • 如果地址、交替字节、数据模式选择了 NONE ,这三个阶段就会被略过。
  • 如果空周期选择了0,这个阶段同样会被略过。

5、使用HAL_QSPI_Receive函数读取ID

void W25Q_ReadID()
{
    QSPI_CommandTypeDef qspi_cmd;
    uint8_t id[8];
    
    qspi_cmd.Instruction = 0x90;  
    qspi_cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    
    qspi_cmd.AddressMode = QSPI_ADDRESS_1_LINE;
    qspi_cmd.Address = 0x0;
    qspi_cmd.AddressSize = QSPI_ADDRESS_24_BITS;  
    
    qspi_cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;    
    
    qspi_cmd.DataMode = QSPI_DATA_1_LINE;
    qspi_cmd.NbData = 2;
    
    qspi_cmd.DummyCycles = 0;    
    
    qspi_cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    qspi_cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    
       
    HAL_QSPI_Command(&hqspi, &qspi_cmd, 1000);    
    HAL_QSPI_Receive(&hqspi, id, 1000);
    
    HAL_UART_Transmit(&huart1, id, 2, 1000);
}

在这里插入图片描述

在这里插入图片描述

注意到:

  • Receive 函数要和 Cmd 函数配套使用。
  • 接收数组接收到的数据是从数据阶段开始接收的。而不是像SPI那样,从发送的第一个字节开始就开始接收数据了。

6、使用HAL_QSPI_Transmit 发送数据

和接收数据一样,不再赘述。

QSPI的三种工作模式

1、间接模式

上面的操作全都属于间接模式:手动配置QSPI_CommandTypeDef结构体,再调用HAL_QSPI_CommandHAL_QSPI_ReceiveHAL_QSPI_Transmit函数。

2、状态轮询模式

回想一下,在FLASH经过擦、写操作后,会经历一个忙状态,只有忙状态的标志清除后,才能继续擦、写FLASH。以往我们都是自己写一个 dowhile 循环,不停地读状态寄存器1来等待FLASH的忙状态清除。而状态轮询模式就是用来做这件事的,但我也没研究怎么弄()。

3、内存映射模式

就是将外部的FLASH当成STM32自己内部的FLASH,可以用来跑程序。

注意:该模式下,FLASH只可读不可写,同样也不能进行其他操作,要操作FLASH,首先退出内存映射模式(话说用哪个函数退出?我还没找到)。映射的地址为 0X90000000

void Enter_MemoryMappedMode(void)
{
    QSPI_CommandTypeDef qspi_cmd;
    QSPI_MemoryMappedTypeDef qspi_mm;
    
    qspi_cmd.Instruction = 0x0B;								//读操作的指令码
    qspi_cmd.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    
    qspi_cmd.AddressMode = QSPI_ADDRESS_4_LINES;				//根据手册的时序图来定
    qspi_cmd.AddressSize = QSPI_ADDRESS_24_BITS;				//具体的地址不需要填写,硬件会自动寻址
    
    qspi_cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_4_LINES;	//根据手册的时序图,发完地址后会有一字节的
    qspi_cmd.AlternateBytes = 0x0;								//Dummy字节,这里用交替字节来填补
    qspi_cmd.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;
    
    qspi_cmd.DataMode = QSPI_DATA_4_LINES;						//同样,不需要指定数据长度,硬件会负责
    
    qspi_cmd.DummyCycles = 0;
    
    qspi_cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
    qspi_cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    
       
    qspi_mm.TimeOutActivation = QSPI_TIMEOUT_COUNTER_ENABLE;	//这两个干啥的我也不清楚,但这样写没出错
    qspi_mm.TimeOutPeriod = 1000;
   
    HAL_QSPI_MemoryMapped(&hqspi, &qspi_cmd, &qspi_mm);
}

注意:

  • 这里的QSPI_CommandTypeDef结构体必须配置成读数据时的参数,否则进入这个模式后单片机会死机,甚至无法下载程序。不过不用担心,按住复位键在点击下载,然后松开复位键,程序还是能下进去的。
  • 亲测,在单线模式下一样可以内存映射,不过没必要。

总结

最后强调几点注意事项:

  1. 相关的 GPIO 别忘了设成高速模式
  2. W25Q系列的FLASH时钟频率最高支持104M,超频到120M也许问题不大。
  3. W25Q系列的FLASH在进入四线模式后,指令、地址、Dummy、数据都会变成四线模式(两个上升沿传输一字节)。
  4. W25Q系列的FLASH在重新上电后会恢复为单线模式。
  5. 进入四线模式后,依然不能跨页写入。继续发挥你的聪明才智吧,或者直接移植别人的代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值