糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > STM32之QSPI调试记录

STM32之QSPI调试记录

时间:2022-04-16 19:12:25

相关推荐

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 -->数据0IO1 -->数据1IO2 -->数据2IO3 -->数据3

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

单线模式下与SPI对应关系

NCS <--> CSCLK <--> CLKIO0 <--> MOSIIO1 <--> MISOIO2 <--> 强制低电平(以禁止“写保护”功能)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_LINESQSPI_INSTRUCTION_4_LINES

针对地址阶段的成员

Address:地址。填写像 0X1234 这样的地址参数,最大长度是四字节。

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

QSPI_ADDRESS_8_BITS QSPI_ADDRESS_16_BITS QSPI_ADDRESS_24_BITSQSPI_ADDRESS_32_BITS

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

针对交替字节的成员

AlternateBytesAlternateBytesSizeAlternateByteMode

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

针对空周期的成员

DummyCycles

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

针对数据阶段的成员

DataMode:数据的模式,同前。NbData:数据的长度,从 0 到 0xFFFFFFFF 个字节长度。

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

其他成员

DdrMode:是否使能双边沿采集,也就是时钟的上升下降沿都采集数据。一般都不使能QSPI_DDR_MODE_DISABLEDdrHoldHalfCycle:和上一个相关的。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结构体必须配置成读数据时的参数,否则进入这个模式后单片机会死机,甚至无法下载程序。不过不用担心,按住复位键在点击下载,然后松开复位键,程序还是能下进去的。亲测,在单线模式下一样可以内存映射,不过没必要。

总结

最后强调几点注意事项:

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

如果觉得《STM32之QSPI调试记录》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。