斗胆分享一下早先使用ZYNQ裸机编程过程中的一些笔记,希望能帮到有需要的人。
测试使用 ZYNQ7020 + Vitis2022.2。
QSPI 控制器的底层寄存器接口是 32bit 的,因此驱动库会根据收发数据的长度进行拼接或移位处理,使得发送的数据序列形成如 sizeof(uint32_t) * N + RemainBytes 的形式。这里简要说明下在裸机环境使用 XQSPIPS 库对 Flash 编程时需要注意的一些事项。
QSPI 的驱动库和普通 SPI 驱动库的不同之处在于,它提供了对 Flash 操作命令的一些封装,即它的设计面向场景即对 Flash 的操作。驱动库内部有一个 XQspiPsInstFormat FlashInst[] 结构体数组,位于 xqspips.c 文件中,其中预先定义了一些命令的长度信息。

但是这些长度信息的使用上,似乎没有考虑到部分指令存在 DUMMY 字节的情况。这意味着,驱动库试图对指令和地址的“头部”信息进行判断,但是其后的数据格式并不关心。而如果需要使用包含 DUMMY 的指令,上层函数必须额外管理一个指令表,驱动库不能实现对这些指令荷载字段的剥离和提取操作。
DUMMY 字节在通信中的位置是:指令-地址-DUMMY-数据,是插入的无效通信周期。例如对于 FAST READ命令,需要8个 dummy cycle,即 1 字节。
在测试中,使用 s32 XQspiPs_PolledTransfer(XQspiPs *InstancePtr, u8 *SendBufPtr, u8 *RecvBufPtr, u32 ByteCount) 函数接口执行 RDSR 操作读取状态寄存器进行测试。
RDSR 的单线模式读写时序如下:

在 1 字节命令后如果执行连续读取,则始终传递所访问 SR 寄存器的 8bit 值。
但是实际测试中,当试图直接使用 XQspiPs_PolledTransfer API 连续访问时,由于驱动库中的限制,会导致实际访问周期加长,从而后续再调用 QSPI 接收会收到无效数据。
例程中对 0xB00000 位置执行 4K 扇区擦除,向 0xB00000 位置写入256 字节,而后向 0xB00000 位置读取 256 字节。程序打印和解读如下图所示。

其它的命令执行都是正确的,但是对于 RDSR 命令,实际的读取波形是:

驱动库在 RDSR 指令之后发起了多达 7 次的读取,而每一次的后 4 字节的周期似乎被延迟到了后续通过 API 执行读操作的过程中取出(虽然取出后的数据并不正确)。而当应用程序中将读写限制为一次,长度为2,即 05 XX 而非 05 XX XX 时,则 RDSR 命令波形和后续读取都是正常的。
这个例子说明,使用官方库对 QSPI Flash 操作时,对于已经存在的指令,应当按照实际长度进行多轮单次的读取,而不宜在一次操作中连续读取多次。
RecvBufPtr 参数必须为 NULL在函数的后续处理中,又将指令和后续数据分开进行发送。收发缓冲区具有 FIFO,在进行收发处理时,分为两个阶段进行,即:
先尝试将发送数据全部送入发送 FIFO 中;
若存在剩余没有处理的(未发送/未接收)数据,则:
在接收 FIFO 阈值范围内:
若传入的接收缓冲区指针有效,则发送 DUMMY 数据(0xFFFFFFFF)直到全部数据长度处理完成;
若传入的接收缓冲区指针无效(NULL),则发送在发送缓冲区中的剩余数据(从指令头取偏移)。
执行其它必要的收发过程。
因此在调用 API 的时候必须注意,在数据发送场景下(典型如页编程),函数的 RecvBufPtr 参数必须为 NULL 。
当我们修复了上述问题,仍旧进行一次页编程,但是同时传递收发缓冲区指针看看。

用 LA 抓一下波形可以看到,页编程的时候,数据就是错误的。

驱动库中问题根源的代码片段如下。

但是这个特性在函数注释中是没有提及的。所以如果不想改驱动库,那就在上层发起读请求的时候,令函数的 RecvBufPtr 参数为 NULL,即可适配大多数情况。
离线