参考

cmsis_os2文档
单片机内存管理CmemTable

rtx5源码位置

D:\soft\keil\armpack\ARM\CMSIS\5.8.0\CMSIS\RTOS2\RTX\Source

常见问题

内存碎片

频繁 malloc/free 或 new/delete 引起内存碎片,造成分配内存失败

解法
  1. 保证只在启动时一次性 new malloc完的,不再释放 (可提前报错)
  2. 单片机内存分配/释放通常线程不安全,应在主线程(启动线程)中串行完成资源或线程的创建。
  3. 容器长度分配足够, 不要动态扩容
  4. 用自定义内存池,自己控制内存分配和释放
  5. 用静态内存分配
  6. 尽可能用栈变量

字节对齐(类型强转)

指针强转 #define NLENGTH( p ) (*((uint16_t *) ( p ))) 即当左值,又当右值

解法

左值和右值分别实现

#define NLENGTH(p)		 ((uint16_t)((p)[0] | ((p)[1] << 8)))

#define NLENGTH_SET(p, value)  do { \
    (p)[0] = (uint8_t)((value) & 0xFF);       /* 低字节 */ \
    (p)[1] = (uint8_t)(((value) >> 8) & 0xFF); /* 高字节 */ \
} while(0)

字节对齐( 结构体 sizeof 大小与实际不符)

解法
#pragma pack(1)
类型定义到此处
#pragma pack()

栈溢出

解法
  1. 栈估算
  2. 栈监控
  3. 栈分配稍微大于长时运行栈的最大用量
  4. 避免深递归,深层调用

辅助命令

addr2line下载

# 配置环境变量
PS D:\USART_TFT> $env:PATH += ";D:\soft\keil\ARM\ARMCC\bin"
# axf 转 汇编
PS D:\USART_TFT> fromelf --text -c -d --output=my_USART_TFT.s  ./USART_TFT.axf
# axf 转 bin
PS D:\USART_TFT> fromelf --bin -o  my_USART_TFT.bin  ./USART_TFT.axf
# 崩溃地址它还原到C代码位置, 0x0800FBCE是崩溃地址PC, 0x080133E7是返回地址LR
PS D:\USART_TFT>  addr2line -e USART_TFT.axf -afpiC 0x0800FBCE  0x080133E7
或
PS D:\USART_TFT>  arm-none-eabi-addr2line -e USART_TFT.axf -afpiC 0x0800FBCE  0x080133E7
0x0800fbce: ManPost at man.cpp:77
0x080133e7: Leave at mythread.cpp:55
# 列出符号表 名称、地址、类型
# 查询是否存在某个函数或变量
$ nm USART_TFT.axf | grep user_malloc
08014819 T user_malloc
# 显示符号表
$ objdump -t USART_TFT.axf
# 显示段信息
$ objdump -h USART_TFT.axf
# 显示文件各部分大小
PS D:\USART_TFT> size USART_TFT.axf
   text    data     bss     dec     hex filename
  71868     164   25192   97224   17bc8 USART_TFT.axf


bsp_stack.h

#ifndef BSP_STACK_H
#define BSP_STACK_H
#include "stdint.h"
/** 线程栈和裸机栈是独立的 **/
#ifdef __cplusplus
extern "C" {
#endif

// 填充裸机栈底区域为 MAGIC_VALUE,用于标识栈的使用情况
void     BspStackFillMagic(void);

// 获取当前栈使用的空间,即从栈顶到栈指针的距离
uint32_t BspGetCurrentStackUsage(void);

// 获取裸机系统占用的最大栈空间
// 这个函数通过遍历栈底区域来找到栈的最大使用空间
uint32_t BspGetMaxStackUsage(void);

// 获取栈的总大小,通常与栈大小宏 BSP_STACK_SIZE 相同
uint32_t BspGetStackTotal(void);

// 获取当前任务的剩余栈空间
// 此函数用于监控任务栈剩余空间,防止栈溢出
uint32_t BspOsThreadGetStackSpace(void);

// 检查裸机栈是否发生溢出
// 如果栈底的 MAGIC_VALUE 被修改,则表示发生了栈溢出
uint8_t  BspIsStackOverflow(void);

#ifdef __cplusplus
}
#endif
#endif

bsp_stack.c

/* bsp_stack.c */
#include "bsp_stack.h"
#include "stm32g0xx_hal.h"
#include "cmsis_os2.h"
/* 与 startup_stm32g070xx.s 中 Stack_Size EQU 保持一致 */
#define BSP_STACK_SIZE    0x1000U
#define MAGIC_VALUE   0xDEADBEEFU

/* __initial_sp 在 __MICROLIB 下已默认导出,无需改汇编 */
extern uint32_t __initial_sp;

#define STACK_TOP     ((uint32_t)&__initial_sp)
#define STACK_BOTTOM  (STACK_TOP - BSP_STACK_SIZE)

void BspStackFillMagic(void)
{
    uint32_t *bottom     = (uint32_t *)STACK_BOTTOM;
    uint32_t *current_sp = (uint32_t *)__get_MSP();

    for (uint32_t *p = bottom; p < current_sp; p++) {
        *p = MAGIC_VALUE;
    }
}

uint32_t BspGetCurrentStackUsage(void)
{
    uint32_t sp = __get_MSP();
    if (sp >= STACK_TOP) return 0U;
    return STACK_TOP - sp;
}

uint32_t BspGetMaxStackUsage(void)
{
    uint32_t *bottom = (uint32_t *)STACK_BOTTOM;
    uint32_t *top    = (uint32_t *)STACK_TOP;

    uint32_t *p = bottom;
    while ((p < top) && (*p == MAGIC_VALUE)) {
        p++;
    }
    return STACK_TOP - (uint32_t)p;
}

uint32_t BspGetStackTotal(void)
{
    return BSP_STACK_SIZE;
}

uint8_t BspIsStackOverflow(void)
{
    uint32_t *bottom = (uint32_t *)STACK_BOTTOM;
    for (int i = 0; i < 4; i++) {
        if (bottom[i] != MAGIC_VALUE) return 1U;
    }
    return 0U;
}

uint32_t BspOsThreadGetStackSpace(void){
	 //线程栈大小
  //osThreadGetStackSize(osThreadGetId());
  return osThreadGetStackSpace(osThreadGetId());
}

用法

main.c

main入口填充裸机栈 BspStackFillMagic();
stackBuf 测试裸机栈, 让裸机栈至少占用300

int main(void)
{
	 volatile uint8_t stackBuf[300];
	 uint32_t i;
    for (i = 0; i < sizeof(stackBuf); i++)
    {
        stackBuf[i] = 0xAA; 
    }
  BspStackFillMagic();
	BspInit();
	osKernelInitialize();		
  __ctMain.Start();	
	osKernelStart();				
  while (1)
  {
   
  }

}

打开cmsis_os2 堆栈水印开关

RTX5 要求栈大小必须是 8 字节倍数 800可以 , 801不行

#define OS_STACK_WATERMARK   1
//当前线程栈容量
osThreadGetStackSize(osThreadGetId());
//当前线程剩余栈
 osThreadGetStackSpace(osThreadGetId());

RTX_Config.h 配置

Keil链接脚本

STM32G070CB 36 KB RAM+ 128KB ROM

BOOT (28KB)

0x8000000,0x7000

USART_BOOT.sct


LR_IROM1 0x08000000 0x00007000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00007000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00009000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

在这里插入图片描述

APP

0x8007000,0x20000 (128KB) (BOOT占用了28KB,实际剩余100KB,写大没关系)

USART_APP.sct

LR_IROM1 0x08007000 0x00020000  {    ; 加载区域,大小为 0x20000 字节
  ER_IROM1 0x08007000 0x00020000  {  ; 加载地址 = 执行地址
   *.o (RESET, +First)              ; 包含所有目标文件(.o),包括 RESET 部分和第一个部分
   *(InRoot$$Sections)              ; 包含根部分的所有节
   .ANY (+RO)                       ; 包含所有只读(RO)段
   .ANY (+XO)                       ; 包含所有执行(XO)段
  }
  RW_IRAM1 0x20000000 0x00009000  {  ; 可读写数据区域(RW),大小为 0x9000 字节
   .ANY (+RW +ZI)                   ; 包含所有读写(RW)段和零初始化(ZI)段
  }
}

在这里插入图片描述

裸机栈的返回

栈向下增长
Stack_Size EQU 0x1000
[ __initial_sp , __initial_sp-0x1000 )

观察裸机栈的占用

&__initial_sp-300

图中表明裸机栈大约使用了350

在这里插入图片描述

HardFault_Handler 问题排查

进入 HardFault_Handler 后外设中断不再进入
但 串口的阻塞式发送一般是能用的
如果不能用 就 io模拟串行协议 , 将 崩溃现场 写入存储器, 或 发出来, ming_msl就特别适合, 一个gpio就能把崩溃现场发出

注意: 崩溃的代码 和烧录进去的代码 一定是 完全一样的代码
这里是在崩溃以后,再次以debug进入

stm32g070cbtx 芯片

stm32g0xx_it.c 片段

/* 
 * 核心思路:HardFault_Handler 必须是 naked 函数,
 * 这样编译器不会生成 prologue,LR 里还保留着 EXC_RETURN 值。
 * 把 LR 作为参数传给 C 函数再做判断。
 */

void HardFault_Handler_C(uint32_t exc_return)
{
    uint32_t* hardfault_args;

    /*
     * EXC_RETURN (进入异常时 LR 的值):
     *   bit[2] = 1 → 异常前用的是 PSP(线程模式)
     *   bit[2] = 0 → 异常前用的是 MSP(handler 模式或主线程)
     */
    if (exc_return & 0x4) {
        hardfault_args = (uint32_t*)__get_PSP();   // 普通任务触发
    } else {
        hardfault_args = (uint32_t*)__get_MSP();   // 中断/主循环触发
    }

    /* Cortex-M 异常帧(自动压栈顺序):
     * [0]=R0  [1]=R1  [2]=R2  [3]=R3
     * [4]=R12 [5]=LR  [6]=PC  [7]=xPSR
     */
    uint32_t stacked_r0  = hardfault_args[0];
    uint32_t stacked_r1  = hardfault_args[1];
    uint32_t stacked_r2  = hardfault_args[2];
    uint32_t stacked_r3  = hardfault_args[3];
    uint32_t stacked_r12 = hardfault_args[4];
    uint32_t stacked_lr  = hardfault_args[5];  // 出错函数的返回地址
    uint32_t stacked_pc  = hardfault_args[6];  // ★ 出错指令的地址 ★
    uint32_t stacked_psr = hardfault_args[7];

    /* 防止编译器优化掉未使用变量(调试时在此打断点) */
    (void)stacked_r0; (void)stacked_r1;
    (void)stacked_r2; (void)stacked_r3;
    (void)stacked_r12;

    while (1)
    {
        printf("=== HardFault ===\r\n");
			  printf("IsStackOver  = 0x%08lX \r\n", BspIsStackOverflow());
			  printf("MaxStackUsage  = 0x%08lX \r\n", BspGetMaxStackUsage());
        printf("PC  = 0x%08lX \r\n", stacked_pc);
        printf("LR  = 0x%08lX  \r\n", stacked_lr);
        printf("PSR = 0x%08lX\r\n", stacked_psr);
        printf("EXC_RETURN = 0x%08lX (%s stack)\r\n",
               exc_return,
               (exc_return & 0x4) ? "PSP" : "MSP");
        for (int i = 0; i < 6000000; i++);
    }
}

__asm void HardFault_Handler(void)
{
    IMPORT HardFault_Handler_C    /* 声明外部 C 符号 */
    MOV    R0, LR                 /* EXC_RETURN → 参数 R0 */
    LDR    R1, =HardFault_Handler_C  /* 加载完整 32-bit 地址 */
    BX     R1                     /* 无范围限制的跳转 */
}

串口打印崩溃现场


[19:57:01.811]收←◆=== HardFault ===
IsStackOver  = 0x00000000 
MaxStackUsage  = 0x0000015C 
PC  = 0x08010996 
LR  = 0x08010FC5  
PSR = 0x61000000
EXC_RETURN = 0xFFFFFFFD (PSP stack)

[19:57:02.576]收←◆=== HardFault ===
IsStackOver  = 0x00000000 
MaxStackUsage  = 0x0000015C 
PC  = 0x08010996 
LR  = 0x08010FC5  
PSR = 0x61000000
EXC_RETURN = 0xFFFFFFFD (PSP stack)

崩溃位置

在这里插入图片描述

无法通过地址反推源码位置的情况

在0x08010996 打断点, 或在map文件 找 <= 0x08010996 最近的符号位置 0x08010971 是 appWriteFormProcess
或找找 LR=0x08010FC5 附近,崩溃的返回地址

    appReadEventProcess(Z8ModRmsg*)          0x0801093d   Thumb Code    50  appserverrouter.o(i._Z19appReadEventProcessP9Z8ModRmsg)
    appWriteFormProcess(Z8ModRmsg*)          0x08010971   Thumb Code    60  appserverrouter.o(i._Z19appWriteFormProcessP9Z8ModRmsg)
    appConfigFormProcess(Z8ModRmsg*)         0x080109c9   Thumb Code    80  appserverrouter.o(i._Z20appConfigFormProcessP9Z8ModRmsg)
    appConfigZoneProcess(Z8ModRmsg*)         0x08010a1d   Thumb Code   118  appserverrouter.o(i._Z20appConfigZoneProcessP9Z8ModRmsg)

stm32 通用芯片

//人造一个崩溃位置
*(volatile uint32_t*)0x5A5A5A5A = 0x5A5A5A5A;

stm32g0xx_it.c 片段

/* ============================================================
 * HardFault_Handler — Compatible with M0+/M3/M4
 *   STM32G0   (Cortex-M0+) : no CFSR, no FPU
 *   STM32F103 (Cortex-M3)  : CFSR present, no FPU
 *   STM32F407 (Cortex-M4)  : CFSR present, FPU with extended frame
 *
 * Toolchain support:
 *   Keil AC5  (__CC_ARM)
 *   Keil AC6  (__ARMCC_VERSION >= 6000000)
 *   GCC / STM32CubeIDE (__GNUC__)
 * ============================================================ */

#include <stdint.h>
#include <stdio.h>

/* ----------------------------------------------------------
 * 1. SCB registers
 *    HFSR : available on all Cortex-M variants
 *    CFSR / MMFAR / BFAR : M3 and above only
 * ---------------------------------------------------------- */
#define SCB_HFSR   (*(volatile uint32_t*)0xE000ED2CU)

#if (__CORTEX_M >= 3)
  #define SCB_CFSR   (*(volatile uint32_t*)0xE000ED28U)
  #define SCB_MMFAR  (*(volatile uint32_t*)0xE000ED34U)
  #define SCB_BFAR   (*(volatile uint32_t*)0xE000ED38U)
  #define SCB_CPACR  (*(volatile uint32_t*)0xE000ED88U)
#endif

/* ----------------------------------------------------------
 * 2. Fault reason decoder  (M3 / M4 only)
 * ---------------------------------------------------------- */
#if (__CORTEX_M >= 3)
static void decode_cfsr(uint32_t cfsr)
{
    /* ---- MemManage Fault (bits[7:0]) ---- */
    if (cfsr & 0x000000FFU) {
        printf("  [MemManage]");
        if (cfsr & (1U << 0)) printf(" IACCVIOL");
        if (cfsr & (1U << 1)) printf(" DACCVIOL");
        if (cfsr & (1U << 3)) printf(" MUNSTKERR");
        if (cfsr & (1U << 4)) printf(" MSTKERR");
        if (cfsr & (1U << 5)) printf(" MLSPERR");
        if (cfsr & (1U << 7)) printf(" MMARVALID -> MMFAR=0x%08lX",
                                      (unsigned long)SCB_MMFAR);
        printf("\r\n");
    }

    /* ---- Bus Fault (bits[15:8]) ---- */
    if (cfsr & 0x0000FF00U) {
        printf("  [BusFault]");
        if (cfsr & (1U <<  8)) printf(" IBUSERR");
        if (cfsr & (1U <<  9)) printf(" PRECISERR");
        if (cfsr & (1U << 10)) printf(" IMPRECISERR");
        if (cfsr & (1U << 11)) printf(" UNSTKERR");
        if (cfsr & (1U << 12)) printf(" STKERR");
        if (cfsr & (1U << 13)) printf(" LSPERR");
        if (cfsr & (1U << 15)) printf(" BFARVALID -> BFAR=0x%08lX",
                                       (unsigned long)SCB_BFAR);
        printf("\r\n");
    }

    /* ---- Usage Fault (bits[31:16]) ---- */
    if (cfsr & 0xFFFF0000U) {
        printf("  [UsageFault]");
        if (cfsr & (1U << 16)) printf(" UNDEFINSTR");
        if (cfsr & (1U << 17)) printf(" INVSTATE");
        if (cfsr & (1U << 18)) printf(" INVPC");
        if (cfsr & (1U << 19)) printf(" NOCP");
        if (cfsr & (1U << 24)) printf(" UNALIGNED");
        if (cfsr & (1U << 25)) printf(" DIVBYZERO");
        printf("\r\n");
    }
}
#endif /* __CORTEX_M >= 3 */

/* ----------------------------------------------------------
 * 3. C fault handler (called from naked assembly entry)
 * ---------------------------------------------------------- */
void HardFault_Handler_C(uint32_t exc_return)
{
    uint32_t *frame;

    /*
     * EXC_RETURN bit[2]:
     *   1 -> exception was using PSP (thread / task mode)
     *   0 -> exception was using MSP (handler / privileged mode)
     */
    if (exc_return & 0x4U) {
        frame = (uint32_t *)__get_PSP();
    } else {
        frame = (uint32_t *)__get_MSP();
    }

    /*
     * Standard Cortex-M exception frame (identical for M0+/M3/M4 basic frame):
     *   [0]=R0  [1]=R1  [2]=R2  [3]=R3
     *   [4]=R12 [5]=LR  [6]=PC  [7]=xPSR
     *
     * On M4 with FPU active (EXC_RETURN bit[4] == 0), an extended frame
     * appends S0-S15 + FPSCR + reserved at [8..25].
     * The basic frame offsets above are unchanged — PC is always at [6].
     */
    const uint32_t r0  = frame[0];
    const uint32_t r1  = frame[1];
    const uint32_t r2  = frame[2];
    const uint32_t r3  = frame[3];
    const uint32_t r12 = frame[4];
    const uint32_t lr  = frame[5];  /* return address of faulting function */
    const uint32_t pc  = frame[6];  /* *** address of faulting instruction *** */
    const uint32_t psr = frame[7];

    /* Prevent compiler from optimizing these out.
     * Set a breakpoint here and inspect the register view. */
    (void)r0; (void)r1; (void)r2; (void)r3; (void)r12;

    /* Read fault registers before any further stack activity */
    const uint32_t hfsr = SCB_HFSR;
#if (__CORTEX_M >= 3)
    const uint32_t cfsr = SCB_CFSR;
#endif

    while (1)
    {
        printf("\r\n======== HardFault ========\r\n");

        /* --- Fault context --- */
        printf("PC         = 0x%08lX  <- disassemble this\r\n", (unsigned long)pc);
        printf("LR         = 0x%08lX\r\n",                       (unsigned long)lr);
        printf("PSR        = 0x%08lX\r\n",                       (unsigned long)psr);
        printf("EXC_RETURN = 0x%08lX  (%s stack)\r\n",
               (unsigned long)exc_return,
               (exc_return & 0x4U) ? "PSP" : "MSP");

        /* --- Optional stack health (define these in your BSP) --- */
#if defined(BSP_STACK_CHECK_ENABLED)
        printf("StackOverflow = %lu\r\n",   (unsigned long)BspIsStackOverflow());
        printf("MaxStackUsage = %lu B\r\n", (unsigned long)BspGetMaxStackUsage());
#endif

        /* --- HFSR (all Cortex-M variants) --- */
        printf("HFSR = 0x%08lX", (unsigned long)hfsr);
        if (hfsr & (1U << 1))  printf("  VECTTBL");
        if (hfsr & (1U << 30)) printf("  FORCED");
        if (hfsr & (1U << 31)) printf("  DEBUGEVT");
        printf("\r\n");

        /* --- CFSR / MMFAR / BFAR (M3 and M4 only) --- */
#if (__CORTEX_M >= 3)
        printf("CFSR = 0x%08lX\r\n", (unsigned long)cfsr);
        decode_cfsr(cfsr);
#endif

        /* --- FPU extended frame info (M4 only) --- */
#if (__CORTEX_M >= 4)
        {
            uint8_t fpu_enabled = (SCB_CPACR & (0xFU << 20U)) ? 1U : 0U;
            uint8_t fpu_frame   = fpu_enabled && !(exc_return & 0x10U);
            printf("FPU enabled = %d  extended_frame = %d\r\n",
                   fpu_enabled, fpu_frame);
        }
#endif

        for (volatile int i = 0; i < 6000000; i++);
    }
}

/* ----------------------------------------------------------
 * 4. Naked assembly entry point
 *    Passes EXC_RETURN (LR) as the first argument (R0) to the
 *    C handler. Must be naked — any prologue would corrupt the
 *    stack frame layout before we read it.
 * ---------------------------------------------------------- */

/* ---- Keil AC5 (ARMCC) ---- */
#if defined(__CC_ARM)

__asm void HardFault_Handler(void)
{
    IMPORT HardFault_Handler_C
    MOV    R0, LR                    /* EXC_RETURN -> arg0 (R0)         */
    LDR    R1, =HardFault_Handler_C  /* full 32-bit address, no range limit */
    BX     R1
}

/* ---- Keil AC6 (Clang-ARM) or GCC / STM32CubeIDE ---- */
#elif defined(__ARMCC_VERSION) || defined(__GNUC__)

__attribute__((naked)) void HardFault_Handler(void)
{
    __asm volatile(
        "MOV  R0, LR                    \n"
        "LDR  R1, =HardFault_Handler_C  \n"
        "BX   R1                        \n"
    );
}

#else
  #error "Unsupported toolchain. Add a naked wrapper for your compiler."
#endif

崩溃日志

[13:26:17.826]收←◆
======== HardFault ========
PC         = 0x0800326A  <- disassemble this
LR         = 0x08005425
PSR        = 0x61000000
EXC_RETURN = 0xFFFFFFFD  (PSP stack)
HFSR = 0x40000000  FORCED
CFSR = 0x00000400
  [BusFault] IMPRECISERR

在这里插入图片描述
在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐