RT-Thread论坛
直播中

艾玛

13年用户 893经验值
擅长:模拟技术 EDA/IC设计 RF/无线
私信 关注
[问答]

jlink连接mcu,查看日志 rtthread运行不正常怎么解决?

使用jlink连接mcu查看日志,偶发重新初始化,部分线程未退出,变量错位。
串口1的线程是在main最后面。
未见初始化打印,就已经发送数据了。并且发送的数据不对。感觉是重新初始化时,串口1线程未退出

回帖(1)

吴湛

2025-10-13 18:12:53

我们面对的问题是在使用J-Link连接MCU时,RT-Thread运行不正常,具体表现为:
1. 偶发重新初始化:可能是指系统偶尔会重启或者重新初始化。
2. 部分线程未退出:在重新初始化后,部分线程可能还在运行,导致状态混乱。
3. 变量错位:可能是指变量被意外修改,或者内存错乱。
4. 串口1的线程在main函数最后启动,但是未见初始化打印就已经发送数据,且数据不对。
5. 感觉是重新初始化时,串口1线程未退出,导致在重新初始化后旧线程还在运行,而新的初始化又开始了。

可能的原因分析:
1. 堆栈溢出:某个线程堆栈溢出,导致内存被破坏,可能引发复位或者变量错乱。
2. 中断处理问题:某个中断处理函数有bug,导致中断触发后系统行为异常。
3. 电源不稳定:导致MCU复位。
4. 看门狗复位:如果启用了看门狗,且没有及时喂狗,会导致复位。
5. 多线程竞争或资源未保护:共享资源访问冲突导致异常。
6. 内存管理问题:动态内存分配失败或内存泄漏导致系统崩溃。
7. 在系统初始化完成前启动了线程:比如在硬件初始化完成前,线程就开始运行并使用了未初始化的硬件。

解决步骤建议:

1. 检查复位原因:
    - MCU的复位寄存器可以记录复位原因(比如上电复位、看门狗复位、软件复位等)。在RT-Thread中,可以通过读取相关寄存器来获取复位原因。例如,在启动代码中或者main函数开始处,读取复位标志并打印出来,这样可以帮助我们定位问题。

2. 检查堆栈使用情况:
    - 使用RT-Thread提供的线程堆栈检查函数,例如在shell中使用`ps`命令查看线程堆栈使用情况,或者使用`list_thread`命令(取决于RT-Thread版本)。重点检查是否有线程堆栈使用率接近100%的情况。
    - 可以考虑增加容易溢出线程的堆栈大小。

3. 检查看门狗:
    - 确认是否启用了看门狗。如果启用了,检查喂狗操作是否正常,特别是高优先级任务长时间占用CPU导致低优先级任务无法及时喂狗。

4. 检查中断处理:
    - 确保中断处理函数中没有进行过于耗时的操作,或者可能引起阻塞的操作。
    - 检查中断优先级设置是否正确,尤其是系统相关的中断(如SysTick、PendSV)优先级是否设置为最低,以避免中断嵌套导致问题。

5. 检查共享资源访问:
    - 对于全局变量等共享资源,访问时应使用互斥锁(mutex)或者关中断等方式进行保护,避免多线程同时访问导致的数据错乱。

6. 检查硬件初始化和线程启动顺序:
    - 确保在启动线程之前,相关硬件(如串口)已经初始化完成。在main函数中,通常的流程是:
        - 硬件初始化(包括串口)
        - 打印初始化信息(比如版本信息)
        - 创建并启动其他线程
    - 这样,在串口线程启动前,串口已经初始化好了。

7. 使用调试器设置断点:
    - 在J-Link调试环境下,可以在复位处理函数、看门狗中断处理函数以及一些关键位置设置断点,观察程序执行流程,特别是系统复位时的现场。

8. 内存检测:
    - 使用内存检测工具(如RTT的内存泄漏检测组件,或者硬件内存检测)来检查是否有内存越界或泄漏。

9. 日志系统问题:
    - 如果使用了串口日志,确保在系统启动早期(在调度器启动之前)不要使用可能导致阻塞的打印函数(因为此时没有线程调度)。RT-Thread的早期打印可以使用`rt_kprintf`,但注意它不会阻塞,但也要确保串口初始化完成。

10. 电源稳定性:
     - 使用示波器检查电源电压是否稳定,特别是在复位发生时。

针对“串口1线程在main最后面,未见初始化打印就已经发送数据,并且数据不对”的问题,我们可以采取以下措施:

  - 确认串口初始化是否在创建线程之前完成。如果串口线程在创建后立即运行,但此时串口硬件还没有初始化,那么发送数据就会出错。
    ```c
    int main(void)
    {
        /* 硬件初始化 */
        rt_hw_usart_init();  // 初始化串口硬件

        /* 打印初始化信息 */
        rt_kprintf("System start...n");

        /* 创建线程 */
        rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 512, 10, 10);
        rt_thread_startup(thread);

        /* 启动调度器 */
        rt_system_scheduler_start();

        return 0;
    }
    ```

  - 另外,检查串口线程入口函数,确保在发送数据前,等待必要的信号量或事件,确保硬件已经就绪(不过一般硬件初始化在创建线程前,所以应该就绪了)。

11. 关于重新初始化时线程未退出:
     - 当系统复位(除了上电复位外)时,RAM中的数据可能没有清除,而程序重新运行,此时旧的线程控制块可能还保留在内存中,而系统重新初始化线程,导致出现两个相同的线程(一个未退出的旧线程和一个新线程)。这种情况通常称为“热复位”问题。
     - 解决方法:在复位处理中,尽量使用软件复位(调用NVIC_SystemReset)并确保复位后RAM被初始化(可以通过设置复位后不初始化RAM,但这样会有残留,所以一般建议在软件复位前标记需要重新初始化,然后复位后根据标记重新初始化全局变量和静态变量)。

但是,对于RT-Thread系统,如果发生复位,整个系统都会重新启动,包括重新初始化所有数据结构。如果复位后没有进行完整的内存初始化,可能会有残留。因此,建议:
     - 在启动代码中,对于非冷启动(比如复位),进行必要的数据区清零(特别是.bss和.data段)或者重新初始化全局变量。

然而,标准的启动代码(如ARM Cortex-M)只会在上电复位时初始化.bss(清零)和.data(从Flash加载),其他复位不会。所以,如果发生了看门狗复位或者其他复位,这些全局变量和静态变量不会被重置。这可能导致旧数据残留,从而引起问题。

解决方案:
     - 在进入main函数后,首先检查复位原因,如果是非上电复位(比如看门狗复位),则执行全局数据重置。但这样做要非常小心,因为可能会破坏系统状态。或者,直接进行软件复位,让系统重新启动(通过NVIC_SystemReset()),并确保看门狗在复位后能够正常初始化,避免连续复位。

12. 使用J-Link的日志功能:
     - 可以使用J-Link的RTT Viewer工具来查看日志,这样不占用串口,且可以在调试时使用。同时,RTT日志可以更早地输出(在串口初始化之前),帮助定位问题。

综上所述,我们可以按照以下步骤进行:

步骤1:在main函数开始处读取并打印复位原因(如果串口还不能用,可以先将原因保存在变量中,稍后打印)。
步骤2:检查复位原因,如果是看门狗复位,则检查喂狗逻辑;如果是软件复位,则检查复位点;如果是其他复位,则检查硬件。
步骤3:检查线程堆栈大小,增加堆栈大小以避免溢出。
步骤4:确保硬件初始化在创建线程之前完成。
步骤5:在非上电复位时,可以打印警告,并考虑进行软件复位来清除所有状态(如果问题频繁,则先解决根本原因)。
步骤6:使用RTT Viewer替代串口日志,以便在系统启动早期获取日志。

由于问题偶发,可能需要长时间测试。同时,可以在关键位置增加日志,记录程序运行流程,以便在复位后查看复位前的操作。

示例代码:获取复位原因(以STM32为例)
```c
#include "stm32f4xx.h"  // 根据实际芯片型号包含

void print_reset_reason(void)
{
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_LPWRRST)) {
        rt_kprintf(" Low Power Resetn");
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) {
        rt_kprintf(" Window Watchdog Resetn");
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
        rt_kprintf(" Independent Watchdog Resetn");
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)) {
        rt_kprintf(" Software Resetn");
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)) {
        rt_kprintf(" Power On Resetn");
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)) {
        rt_kprintf(" External Pin Resetn");
    }
    else {
        rt_kprintf(" Unknown Resetn");
    }
    __HAL_RCC_CLEAR_RESET_FLAGS();
}
```

在main函数开始处调用:
```c
int main(void)
{
    // 初始化串口之前,可以先初始化一个简单的日志系统(比如RTT)或者稍后打印
    // 先初始化系统时钟等必要的硬件

    // 打印复位原因(如果此时串口未初始化,可以稍后打印,但先保存原因)
    // 如果使用RTT,则可以直接打印
    // print_reset_reason();

    // ... 其他初始化
}
```

由于在复位后,我们可能无法立即使用串口,因此建议:
  - 使用RTT(SEGGER RTT)来打印早期日志。
  - 或者,将复位原因保存在一个全局变量中,该变量在复位后不会被初始化(可以通过在特殊内存区域定义,或者通过检查复位标志后立即保存到全局变量中,这个全局变量在.bss段,在非上电复位时不会被清零)。然后在串口初始化后打印出来。

关于全局变量在复位后不被清零的问题:默认情况下,只有上电复位才会初始化.bss和.data。所以,如果是其他复位,全局变量会保持复位前的值。我们可以利用这一点来保存复位原因。

但注意:如果发生了看门狗复位,可能系统已经处于异常状态,此时全局变量可能已被破坏。所以,在获取复位原因后立即保存,并且尽快打印。

最后,如果问题仍然无法解决,需要更详细的日志和调试信息,可以考虑使用J-Link进行单步调试,观察复位发生时的具体情况。

由于你提到“偶发”,所以问题可能比较隐蔽,需要耐心排查。希望以上建议能帮助你定位问题。

针对您描述的问题——使用J-Link连接MCU时,RT-Thread运行异常(偶发重新初始化、线程未退出、变量错位、串口数据异常),以下是系统化的解决方案:




核心原因分析




  1. 偶发重新初始化



    • 硬件看门狗(IWDG/WWDG)超时触发复位

    • 堆栈溢出导致HardFault

    • 电源电压不稳定或外部干扰

    • 中断冲突(如SysTick/PendSV优先级问题)




  2. 串口线程异常



    • 线程未退出导致复用:复位后旧线程仍在运行,与新初始化线程冲突

    • 全局变量未复位:非冷启动时,.bss/.data段未清零

    • 硬件初始化顺序错误:串口外设未初始化完成时,线程已发送数据






解决方案


1. 定位复位原因


main() 函数开头添加复位标志检查:


#include   // 根据实际芯片型号修改

void print_reset_reason() {
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
        rt_kprintf("[复位] 独立看门狗复位n");
    }
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) {
        rt_kprintf("[复位] 窗口看门狗复位n");
    }
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)) {
        rt_kprintf("[复位] 软件复位n");
    }
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)) {
        rt_kprintf("[复位] 上电复位n");
    }
    __HAL_RCC_CLEAR_RESET_FLAGS(); // 清除复位标志
}

int main(void) {
    print_reset_reason();  // 第一步:打印复位原因
    // ... 其他初始化代码
}



2. 修复线程管理问题



  • 确保线程安全退出

    在系统复位前(如看门狗中断),强制结束所有线程:
    void WDG_IRQHandler(void) {
      rt_thread_t thread;
      rt_list_t* list = &rt_thread_defunct;
      // 遍历并删除所有僵尸线程
      while (list->next != list) {
          thread = rt_list_entry(list->next, struct rt_thread, tlist);
          rt_thread_delete(thread);
      }
      NVIC_SystemReset();  // 触发系统复位
    }


  • 串口线程初始化顺序修正

    确保硬件初始化完成后再启动线程:


    int main(void) {
      // 1. 初始化硬件外设
      rt_hw_usart_init();  // 初始化串口硬件
      rt_kprintf("系统启动n");  // 测试串口是否就绪

      // 2. 创建线程(必须在硬件初始化后)
      rt_thread_init(&serial_thread, ... , serial_entry, RT_NULL);
      rt_thread_startup(&serial_thread);

      // 3. 启动调度器
      rt_system_scheduler_start();
    }





3. 处理变量错位/未复位



  • 非冷启动时手动初始化全局变量

    main() 中根据复位类型重置全局数据:
    void init_global_vars() {
      extern uint8_t _sbss, _ebss;
      // 若非上电复位,手动清零.bss段
      if (!__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)) {
          memset(&_sbss, 0, &_ebss - &_sbss);
      }
    }

  • 关键变量添加 volatile

    防止编译器优化导致变量访问异常:
    volatile uint32_t system_counter = 0;




4. 堆栈溢出检测



  • 增大线程堆栈

    rtconfig.h 中增加栈大小:
    #define RT_THREAD_STACK_SIZE 1024  // 默认栈大小
    #define SERIAL_THREAD_STACK_SIZE 2048  // 串口线程专用栈

  • 启用RT-Thread堆栈检测

    在Shell中定期执行 list_thread,检查栈使用率:
    msh >list_thread
    thread   pri  status      sp     stack size max used left tick
    serial    10  running 0x00000060 0x00000800    85%   10




5. 中断与看门狗配置



  • 调整中断优先级

    确保系统中断优先级最低:
    NVIC_SetPriority(PendSV_IRQn, 0xFF); // 最低优先级
    NVIC_SetPriority(SysTick_IRQn, 0xFE);

  • 合理喂养看门狗

    在空闲线程或专用线程中喂狗:
    void wdg_feed_thread(void* param) {
      while (1) {
          IWDG_Refresh();
          rt_thread_mdelay(500);
      }
    }




6. 调试技巧



  • 使用J-Link RTT Viewer

    替代串口输出,避免硬件初始化依赖:
    #include 
    SEGGER_RTT_printf(0, "RTT日志: 系统已启动n");

  • 捕获HardFault

    HardFault_Handler 中打印堆栈:
    void HardFault_Handler(void) {
      rt_kprintf("HardFault! LR=0x%08xn", __get_LR());
      while (1);
    }




验证步骤



  1. 烧录修复后的固件,通过J-Link RTT观察启动日志。

  2. 触发看门狗复位,确认复位原因打印正确。

  3. 压力测试:连续运行24小时,检查是否仍存在复位/线程错位。

  4. 使用 list_thread 定期监控线程状态和堆栈使用。


通过以上方法,可系统性解决复位、线程冲突和变量错位问题,确保系统稳定运行。

举报

更多回帖

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