本章节记录
异常
- weact stm32h750 开发板移植sfud 组件,开启QSPI相关的组件宏定义,并切换到QPI模式下,在使用qspi 方式读取数据出现全0x88以及读取数据存在0x00 的异常现象
- 前一次调试QSPI Flash已经处于映射模式,第二次重新烧录调试无法读取device id的情况
环境说明
- sfud地址
https://github.com/armink/SFUD.git - 移植参考
https://blog.youkuaiyun.com/ybhuangfugui/article/details/141578526
本次主要是异常分析,软件设计分为内部flash 128KB 当作bootloader,外部的qspi flash 当作app 区域,跳转前需要检查qspiflash 中的数据是否正常,切换qspiflash 到qpi 模式,然后切换到映射模式,后续跳转到qspi flash的地址段执行。
分析过程
问题一 调试QSPI Flash已经处于映射模式,第二次重新烧录调试无法读取device id的情况
- 测试发现,切换到qpi后,qpsi flash的指令模式是4线的,sfud 中使用的reset 接口是
spi模式下1线的指令模式,无法通过reset函数实现芯片复位; - sfud 通过标准spi 方式无法使用 static sfud_err read_jedec_id(sfud_flash *flash) 读取id信息;
查看数据手册可知:
static sfud_err reset(const sfud_flash *flash)
{
sfud_err result = SFUD_SUCCESS;
const sfud_spi *spi = &flash->spi;
uint8_t cmd_data[2];
SFUD_ASSERT(flash);
cmd_data[0] = SFUD_CMD_ENABLE_RESET;
result = spi->wr(spi, cmd_data, 1, NULL, 0);
if (result == SFUD_SUCCESS)
{
result = wait_busy(flash);
}
else
{
SFUD_INFO("Error: Flash device reset failed.");
return result;
}
cmd_data[1] = SFUD_CMD_RESET;
result = spi->wr(spi, &cmd_data[1], 1, NULL, 0);
if (result == SFUD_SUCCESS)
{
result = wait_busy(flash);
}
if (result == SFUD_SUCCESS)
{
SFUD_DEBUG("Flash device reset success.");
}
else
{
SFUD_INFO("Error: Flash device reset failed.");
}
return result;
}
所以在初始化过程,需要在标准spi下执行重启操作,并还需要在qpi 模式下执行重启操作;
如下(修改底层初始化操作):
#define W25X_EnableReset 0x66
#define W25X_ResetDevice 0x99
/**
* @brief debug
* @param hqspi
*/
static void _bsp_qspi_reset_device(QSPI_HandleTypeDef *hqspi)
{
QSPI_CommandTypeDef s_command;
/* Initialize the reset enable command */
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.Instruction = W25X_EnableReset;
s_command.AddressMode = QSPI_ADDRESS_NONE;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
s_command.DataMode = QSPI_DATA_NONE;
s_command.DummyCycles = 0;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Send the command */
if (HAL_QSPI_Command(hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
;
/* Send the reset device command */
s_command.Instruction = W25X_ResetDevice;
if (HAL_QSPI_Command(hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
;
s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;
s_command.Instruction = W25X_EnableReset;
/* Send the command */
if (HAL_QSPI_Command(hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
;
/* Send the reset memory command */
s_command.Instruction = W25X_ResetDevice;
if (HAL_QSPI_Command(hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
;
}
建议在初始化sfud 阶段也可以增加reset 接口调用,复位芯片。
static sfud_err hardware_init(sfud_flash *flash) 函数中
/* reset flash */
result = reset(flash);
if (result != SFUD_SUCCESS)
{
SFUD_INFO("flash reset error %d", result);
return result;
}
/* read JEDEC ID include manufacturer ID, memory type ID and flash capacity ID */
result = read_jedec_id(flash);
if (result != SFUD_SUCCESS)
问题二 切换到QPI模式下,在使用qspi 方式读取数据出现全0x88以及读取数据存在0x00 的异常现象
代码设计上,初始化过程,会执行 切换到QPI模式下,进行后续访问操作,这里有一个关键的组件接口
qspi_set_read_cmd_format,该函数实现了对数据模式的设置,但是并没有对指令模式设置
切换到QPI模式下,相关的指令线应该是4线的,具体参考数据手册
static void qspi_set_read_cmd_format(sfud_flash *flash, uint8_t ins, uint8_t ins_lines, uint8_t addr_lines,
uint8_t dummy_cycles, uint8_t data_lines) {
/* if medium size greater than 16Mb, use 4-Byte address, instruction should be added one */
if (flash->chip.capacity <= 0x1000000) {
flash->read_cmd_format.instruction = ins;
flash->read_cmd_format.address_size = 24;
} else {
if(ins == SFUD_CMD_READ_DATA){
flash->read_cmd_format.instruction = ins + 0x10;
}
else{
flash->read_cmd_format.instruction = ins + 1;
}
flash->read_cmd_format.address_size = 32;
}
flash->read_cmd_format.instruction_lines = ins_lines;
flash->read_cmd_format.address_lines = addr_lines;
flash->read_cmd_format.alternate_bytes_lines = 0;
flash->read_cmd_format.dummy_cycles = dummy_cycles;
flash->read_cmd_format.data_lines = data_lines;
}
组件中的该接口并没有对指令的线的设置
手动更改,程序可判断是否进入qpi ,然后选择调整参数 ins_line_width,选择 1 或者 2 或者 4
/**
* Enbale the fast read mode in QSPI flash mode. Default read mode is normal SPI mode.
*
* it will find the appropriate fast-read instruction to replace the read instruction(0x03)
* fast-read instruction @see SFUD_FLASH_EXT_INFO_TABLE
*
* @note When Flash is in QSPI mode, the method must be called after sfud_device_init().
*
* @param flash flash device
* @param data_line_width the data lines max width which QSPI bus supported, such as 1, 2, 4
* @param ins_line_width the instruction_lines lines max width which QSPI bus supported[QPI mode only used 4], such as 1, 2, 4
*
* @return result
*/
sfud_err sfud_qspi_fast_read_enable(sfud_flash *flash, uint8_t data_line_width, uint8_t ins_line_width)
{
size_t i = 0;
uint8_t read_mode = NORMAL_SPI_READ;
sfud_err result = SFUD_SUCCESS;
SFUD_ASSERT(flash);
SFUD_ASSERT(data_line_width == 1 || data_line_width == 2 || data_line_width == 4);
SFUD_ASSERT(ins_line_width == 1 || ins_line_width == 2 || ins_line_width == 4);
/* get read_mode, If don't found, the default is SFUD_QSPI_NORMAL_SPI_READ */
for (i = 0; i < sizeof(qspi_flash_ext_info_table) / sizeof(sfud_qspi_flash_ext_info); i++)
{
if ((qspi_flash_ext_info_table[i].mf_id == flash->chip.mf_id) \
&& (qspi_flash_ext_info_table[i].type_id == flash->chip.type_id) \
&& (qspi_flash_ext_info_table[i].capacity_id == flash->chip.capacity_id))
{
read_mode = qspi_flash_ext_info_table[i].read_mode;
}
}
/* determine qspi supports which read mode and set read_cmd_format struct */
switch (data_line_width)
{
case 1:
qspi_set_read_cmd_format(flash, SFUD_CMD_READ_DATA, 1, 1, 0, 1);
break;
case 2:
if (read_mode & DUAL_IO)
{
qspi_set_read_cmd_format(flash, SFUD_CMD_DUAL_IO_READ_DATA, 1, 2, 4, 2);
}
else if (read_mode & DUAL_OUTPUT)
{
qspi_set_read_cmd_format(flash, SFUD_CMD_DUAL_OUTPUT_READ_DATA, 1, 1, 8, 2);
}
else
{
qspi_set_read_cmd_format(flash, SFUD_CMD_READ_DATA, 1, 1, 0, 1);
}
break;
case 4:
if (read_mode & QUAD_IO)
{
qspi_set_read_cmd_format(flash, SFUD_CMD_QUAD_IO_READ_DATA, ins_line_width, 4, 6, 4);
qspi_set_read_cmd_alter_format(flash, 0xFF, 8, 4); // 配置交替字节等 (为了测试添加 - 可整理成一个表 直接通过ID 进行自动配置)
}
else if (read_mode & QUAD_OUTPUT)
{
qspi_set_read_cmd_format(flash, SFUD_CMD_QUAD_OUTPUT_READ_DATA, 1, 1, 8, 4);
}
else
{
qspi_set_read_cmd_format(flash, SFUD_CMD_READ_DATA, 1, 1, 0, 1);
}
break;
}
return result;
}
以上操作完成后,发现读取的数据,某一个字节 是0x00 ,并不是我想要的数据
这里需要回到数据手册上
交替字节段缺失,需要增加交替字节段的使用,目前我的处理方式是,增加一个函数,并修改sfud 中的sfud_qspi_read_cmd_format
/**
* QSPI flash read cmd format
*/
typedef struct {
uint8_t instruction;
uint8_t instruction_lines;
uint8_t address_size;
uint8_t address_lines;
uint8_t alternate; // XXX: fast read must add. id default 0xFF
uint8_t alternate_bytes; // XXX: fast read must add. id len default 8bits
uint8_t alternate_bytes_lines; // BUG: when used QPI mode not work W25Q64jv,must send 1bytes[0xFF] when fast read[0xEB] default 4lines
uint8_t dummy_cycles;
uint8_t data_lines;
} sfud_qspi_read_cmd_format;
static void qspi_set_read_cmd_alter_format(sfud_flash *flash, uint8_t alter, uint8_t alter_bytes, uint8_t alter_lines)
{
flash->read_cmd_format.alternate = alter;
flash->read_cmd_format.alternate_bytes = alter_bytes;
flash->read_cmd_format.alternate_bytes_lines = alter_lines;
}
接下来使用适配的接口转换到底层操作结构即可;
这是底层测试读取接口,可以直接参考作本地适配
uint8_t W25qxx_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
uint8_t result;
QSPI_CommandTypeDef s_command;
/* Configure the command for the read instruction */
if(w25qxx_Mode == w25qxx_QPIMode) // qpi 模式下,读取配置不一样,留意之
{
s_command.Instruction = W25X_QUAD_INOUT_FAST_READ_CMD;
s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;
s_command.DummyCycles = 6;
}
else
{
s_command.Instruction = W25X_QUAD_INOUT_FAST_READ_CMD;
s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s_command.DummyCycles = 6-2;
}
s_command.Address = ReadAddr;
s_command.AddressMode = QSPI_ADDRESS_4_LINES;
s_command.AddressSize = QSPI_ADDRESS_24_BITS;
s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_4_LINES;
s_command.AlternateBytes = 0xFF;
s_command.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;
s_command.DataMode = QSPI_DATA_4_LINES;
s_command.NbData = Size;
s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
result = HAL_QSPI_Command(&_stm32_qspi_bus.QSPI_Handler, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);
if(result == w25qxx_OK)
result = HAL_QSPI_Receive(&_stm32_qspi_bus.QSPI_Handler,pData,HAL_QPSI_TIMEOUT_DEFAULT_VALUE);
return result;
}
测试结果
注意相关芯片在QSPI 模式下,时钟频率的支持