sfud组件适配qspi flash W25Q64JV 踩坑记录

异常

  • 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的情况

  1. 测试发现,切换到qpi后,qpsi flash的指令模式是4线的,sfud 中使用的reset 接口是
    spi模式下1线的指令模式,无法通过reset函数实现芯片复位;
  2. 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 模式下,时钟频率的支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值