RT-Thread论坛
直播中

孔朱磊

8年用户 1043经验值
私信 关注
[问答]

qboot跳转app报bus fault的错,怎么解决?

ymodem_ota下载固件正常,qboot去更新app也没有报错。正常启动时,等待5s后,就报bus fault。

定位到是qboot_STM32.c:106 的 void qbt_jump_to_app(void)中的 rt_qspi_send_then_recv(device, send, sizeof(send), recv, sizeof(recv));报错的。

求助,不太会改了。
PS: SFUD 中使用的qspi一直都很稳定。


回帖(1)

地方

2025-10-11 16:14:47

问题分析:
根据描述,问题发生在QBoot跳转到应用程序(APP)时,具体是在执行`void qbt_jump_to_app(void)`函数中的`rt_qspi_send_then_recv`时报告了总线错误(Bus Fault)。同时,Ymodem_OTA下载固件正常,QBoot更新APP也没有报错,说明固件下载和写入过程没有问题。但是启动时等待5秒后(应该是QBoot等待按键进入升级模式的时间,然后尝试跳转到APP)出现错误。

错误位置:`qboot_stm32.c:106`的`rt_qspi_send_then_recv`调用。

可能的原因:
1. 跳转到APP之前,QSPI外设没有正确关闭或去初始化,导致在跳转后APP中重新初始化QSPI时发生冲突。
2. 中断未正确处理:在跳转到APP之前,没有正确禁用中断或者没有重新设置中断向量表。
3. 堆栈指针(SP)和程序计数器(PC)在跳转时设置不正确。
4. QSPI设备在跳转前没有进行必要的清理操作(比如关闭DMA传输、清除中断标志等),导致在APP中操作QSPI时出现异常。
5. 内存访问错误:可能是访问了无效的内存地址。

解决思路:
1. 确保在跳转到APP之前,QBoot正确关闭了所有使用的外设,特别是QSPI设备。如果QSPI设备在QBoot中用于读取外部Flash,那么在跳转前应该关闭该设备。
2. 确保在跳转前禁用所有中断,并将中断向量表设置为APP的中断向量表(通常在APP的启动代码中设置,但跳转前需要关闭中断,跳转后由APP重新使能)。
3. 检查跳转代码:确保正确设置了APP的堆栈指针和程序计数器。
4. 在跳转前,对QSPI设备进行反初始化,或者确保在APP中初始化QSPI之前,QSPI处于一个确定的状态。

具体步骤:

首先,检查跳转函数`qbt_jump_to_app`的实现。典型的跳转到APP的代码如下:

```c
typedef void (*app_func_t)(void);

void qbt_jump_to_app(void)
{
     uint32_t app_addr = 0x08010000; // 假设APP起始地址
     app_func_t app = (app_func_t)(*(volatile uint32_t*)(app_addr + 4)); // 第二个32位数据是复位向量

     // 关闭所有中断
     __disable_irq();

     // 重置外设,特别是QSPI
     // 1. 反初始化QSPI设备
     rt_device_t device = rt_device_find("qspi1");
     if (device)
     {
         rt_device_close(device);
         // 可能需要反初始化底层硬件,具体看RT-Thread的设备驱动框架
     }

     // 2. 设置中断向量表偏移(如果APP使用了不同的中断向量表偏移)
     // 注意:通常在APP中设置中断向量表,但跳转前需要确保中断向量表寄存器(VTOR)指向APP的向量表。
     // 但是,在跳转前设置VTOR可能会被APP覆盖,所以一般在APP的启动代码中设置。这里我们只需要关闭中断,跳转后由APP重新设置。

     // 3. 设置堆栈指针(SP)
     uint32_t stack_pointer = *(volatile uint32_t*)app_addr;
     __set_MSP(stack_pointer); // 设置主堆栈指针

     // 4. 跳转到APP
     app();
}
```

但是,问题是在跳转前(还在QBoot中)执行`rt_qspi_send_then_recv`时就报错了?注意问题描述中定位到的是在跳转函数中的`rt_qspi_send_then_recv`报错。所以,实际上在跳转之前就已经发生了错误。

因此,我们需要重新审视:这个`rt_qspi_send_then_recv`调用在跳转函数中做什么?为什么在跳转之前还要进行QSPI操作?

根据问题中提到的代码位置:`qboot_stm32.c:106`,我们需要查看那行代码的具体上下文。

如果确实在跳转前需要发送一些命令(比如退出QSPI模式等),那么可能是这个操作本身导致了错误。可能的原因有:
  - QSPI设备在之前已经被关闭或进入错误状态。
  - 发送的命令或参数不正确。

但是,根据问题描述,SFUD中使用的QSPI一直很稳定,说明QSPI驱动本身没有问题。那么问题可能出现在跳转前这个特定的操作上。

建议:
1. 检查跳转前执行`rt_qspi_send_then_recv`的目的。是否必要?如果是为了退出QSPI模式,那么可以尝试用更简单的方式,比如发送一个简单的命令。
2. 在调用这个函数之前,确保QSPI设备是打开并且可用的。
3. 检查`send`和`recv`缓冲区是否有效(比如不是空指针,没有越界)。

另外,考虑到跳转前可能已经关闭了QSPI设备,那么在这个函数中再次使用QSPI设备之前,需要确保设备是打开的。但是,根据RT-Thread的设备模型,如果之前关闭了,那么再次使用前需要打开。

但是,在跳转前,我们可能希望释放所有资源,包括关闭设备。所以,如果确实需要发送命令,那么应该先打开设备,然后发送命令,再关闭设备,最后跳转。

修改建议:

步骤:
  1. 打开QSPI设备(如果已经关闭)
  2. 发送必要的命令(退出QSPI模式等)
  3. 关闭QSPI设备(可选,因为马上要跳转)
  4. 然后继续跳转前的其他操作(关闭中断、设置堆栈指针、跳转)

但是,注意:如果QSPI设备是用于存储APP的外部Flash,那么在跳转前需要确保外部Flash已经处于一种安全状态(比如退出4字节地址模式,或者退出连续读模式等),否则APP初始化QSPI时可能会出错。

因此,我们需要在跳转前执行一些QSPI的清理命令。但是,如果这个命令执行出错,那么可能是驱动在之前已经处于不可用状态。

另一种思路:在跳转前不要执行复杂的QSPI操作。因为跳转后APP会重新初始化整个硬件,所以只需要确保QSPI硬件状态不会影响复位后的初始化即可。有些Flash芯片在复位后会回到默认状态,所以可能不需要额外的清理命令。

所以,如果可能的话,尝试注释掉`rt_qspi_send_then_recv`这行代码,然后测试跳转是否正常。如果正常,说明这个命令发送是不必要的,或者可以在APP初始化时处理。

如果注释掉后跳转正常,那么问题就解决了。如果不行,我们再考虑其他方法。

另外,注意总线错误(Bus Fault)通常是由于访问了无效的内存地址或者对齐问题等。所以,检查`rt_qspi_send_then_recv`的参数:
   - `device`:确保设备指针有效。
   - `send`和`recv`:确保这两个缓冲区地址有效,并且是4字节对齐的(如果QSPI要求对齐的话)。

总结修改方案:

1. 检查并确保`rt_qspi_send_then_recv`参数正确。
2. 如果参数没问题,尝试注释掉这行代码,看是否还会报错。如果不报错,说明这个操作可以移除(或者考虑在APP中处理)。
3. 如果注释掉后仍然报错(或者不能注释,因为必须执行),那么需要检查QSPI驱动在跳转前的状态。可以在执行这个命令之前重新初始化QSPI设备(如果之前关闭了,则重新打开,然后配置为基本模式)。

由于问题描述有限,以上为常见处理思路。如果问题依旧,需要进一步提供更多信息(如跳转函数的完整代码,错误发生的具体环境等)。

但是,根据问题描述,这里给出一个修改的示例:

```c
void qbt_jump_to_app(void)
{
     // ... 其他代码

     // 在跳转前,可能需要发送退出指令给外部Flash
     rt_device_t device = rt_device_find("qspi1");

     if (device)
     {
         // 确保设备已经打开,如果没有打开则打开
         if (rt_device_open(device, RT_DEVICE_OFLAG_RDWR) != RT_EOK)
         {
             // 打开失败,处理错误
         }
         else
         {
             // 准备发送退出命令(根据外部Flash手册)
             uint8_t send[1] = {0xFF}; // 示例:发送0xFF退出连续读模式
             uint8_t recv[1];

             // 注意:这里使用rt_qspi_send_then_recv,但根据驱动,可能只需要发送,不需要接收
             // 可以尝试使用 rt_device_write 只发送命令
             rt_device_write(device, 0, send, 1);
             // 或者使用 rt_qspi_send_then_recv 但确保参数正确
             // 如果这个函数调用报错,可以尝试简化,或者检查驱动状态

             // 然后关闭设备
             rt_device_close(device);
         }
     }

     // ... 关闭中断,设置堆栈指针,跳转
}
```

但是,如果问题是由于QSPI驱动在之前已经被反初始化,那么在跳转前重新初始化QSPI设备可能很复杂。因此,建议在QBoot中不要关闭QSPI设备,或者只在跳转前发送必要的命令而不关闭设备(因为跳转后内存会被重新初始化,不会产生资源泄漏问题)。

最后,如果以上方法都不能解决,建议在跳转前完全反初始化QSPI外设(包括复位外设寄存器),这样APP初始化时就能从一个确定的状态开始。

由于具体硬件和驱动实现不同,以上建议可能需要调整。希望这些思路能帮助你解决问题。

要解决qboot跳转App时在rt_qspi_send_then_recv()处报bus fault错误的问题,可按照以下步骤进行排查和修复:


1. 检查QSPI设备在跳转前的状态



  • 问题根源:跳转前未正确释放或复位QSPI外设,导致App初始化时硬件状态冲突。

  • 解决方案
     void qbt_jump_to_app(void) {
         // 在跳转前显式关闭并反初始化QSPI设备
         rt_device_t device = rt_device_find("qspi1");
         if (device) {
             rt_device_close(device); // 关闭设备
             // 可选:复位QSPI外设寄存器到默认状态(根据MCU手册)
             __HAL_RCC_QSPI_FORCE_RESET();  // 强制复位QSPI外设
             __HAL_RCC_QSPI_RELEASE_RESET(); // 释放复位
         }
         // ... 其余跳转代码
    }


2. 确保中断和向量表正确重置



  • 关键点:跳转前必须关闭中断并重置向量表,避免App启动时中断冲突。


  • 修改跳转代码


     void qbt_jump_to_app(void) {
         // 1. 关闭所有中断
         __disable_irq();

         // 2. 复位外设(包括QSPI)
         // ... 此处添加上述QSPI复位代码

         // 3. 设置App的堆栈指针和复位向量
         uint32_t app_addr = APP_START_ADDRESS; // e.g., 0x08010000
         uint32_t sp = *((volatile uint32_t*)app_addr);
         uint32_t pc = *((volatile uint32_t*)(app_addr + 4));
         __set_MSP(sp); // 设置主堆栈指针

         // 4. 重置向量表偏移(可选,App中需重新设置)
         SCB->VTOR = app_addr; // 设置VTOR寄存器

         // 5. 跳转到App
         ((void(*)(void))pc)();
    }



3. 检查QSPI硬件配置冲突



  • 可能问题:QSPI的时钟、GPIO等被过早修改。

  • 排查步骤

    • 在App初始化代码中,确认QSPI配置前是否有延时重新初始化序列。

    • qboot跳转前,避免操作QSPI相关GPIO或时钟



4. 简化QSPI退出逻辑



  • 优化建议:如果rt_qspi_send_then_recv()仅用于发送退出指令(如0xFF),替换为更稳定的方法:
     // 代替 rt_qspi_send_then_recv,直接发送单字节命令
    uint8_t exit_cmd = 0xFF; // 退出连续读模式
    rt_device_write(device, 0, &exit_cmd, sizeof(exit_cmd));


5. 验证App的启动流程



  • 检查点

    • 确保App的启动文件(如startup_stm32xxx.s)正确初始化堆栈。

    • 确认App的链接脚本中代码起始地址与qboot的跳转地址一致(无地址重叠)。



6. 调试手段



  • 捕获异常:在HardFault_Handler()中打印错误寄存器,定位具体原因:
     void HardFault_Handler(void) {
         volatile uint32_t *cfsr = (volatile uint32_t*)0xE000ED28;
         rt_kprintf("HardFault: CFSR=0x%08xn", *cfsr);
         while(1);
    }

  • 逻辑分析仪:检查QSPI的CLK、CS引脚在跳转瞬间是否有异常信号。


最终修复方案示例


void qbt_jump_to_app(void) {
    // 1. 关闭中断
    __disable_irq();

    // 2. 关闭并复位QSPI
    rt_device_t device = rt_device_find("qspi1");
    if (device) {
        rt_device_close(device);
        // STM32硬件复位QSPI外设
        __HAL_RCC_QSPI_FORCE_RESET();
        __HAL_RCC_QSPI_RELEASE_RESET();
    }

    // 3. 设置App堆栈指针和PC
    uint32_t app_addr = 0x08010000;
    uint32_t sp = *((volatile uint32_t*)app_addr);
    uint32_t pc = *((volatile uint32_t*)(app_addr + 4));
    __set_MSP(sp);
    SCB->VTOR = app_addr; // 更新向量表

    // 4. 跳转
    ((void(*)(void))pc)();
}

注意事项



  • 复位必要性:某些MCU的QSPI外设在跳转后需完整复位才能重新初始化。

  • SFUD兼容性:若App使用SFUD,确保其初始化时能处理"外设已初始化"的状态(或主动复位QSPI)。

  • 时序问题:在qboot中移除所有跳转前的QSPI操作,除非确认为必要且安全的退出命令。


通过以上步骤,应能解决跳转时的总线错误问题。重点在于硬件状态的清理中断环境的隔离。如果问题仍存在,建议结合硬件调试工具进一步分析QSPI信号波形。

举报

更多回帖

发帖
×
20
完善资料,
赚取积分