CmBacktrace | 一款 ARM Cortex-M 系列 MCU 錯誤追蹤庫

嵌入式開源項目精選專欄

本專欄由Mculover666創建,主要內容爲尋找嵌入式領域內的優質開源項目,一是幫助開發者使用開源項目實現更多的功能,二是通過這些開源項目,學習大佬的代碼及背後的實現思想,提升自己的代碼水平,和其它專欄相比,本專欄的優勢在於:

不會單純的介紹分享項目,還會包含作者親自實踐的過程分享,甚至還會有對它背後的設計思想解讀

目前本專欄包含的開源項目有:

如果您自己編寫或者發現的開源項目不錯,歡迎留言或者私信投稿到本專欄,分享獲得雙倍的快樂!

1. CmBacktrace

本期給大家帶來的開源項目是 CmBacktrace,一款針對 ARM Cortex-M 系列 MCU 的錯誤代碼自動追蹤、定位,錯誤原因自動分析的開源庫,作者armink,目前收穫 611 個 star,遵循 MIT 開源許可協議。

目前 CmBacktrace支持以下功能:

  • 支持斷言(assert)和故障(Hard Fault)
  • 故障原因自動診斷
  • 輸出錯誤現場的 函數調用棧
  • 適配 Cortex-M0/M3/M4/M7 MCU;
  • 支持 IAR、KEIL、GCC 編譯器;

項目地址:https://github.com/armink/CmBacktrace

2. 移植CmBacktrace

2.1. 移植思路

在移植過程中主要參考兩個資料:項目的readme文檔和demo工程。

對於這些開源項目,其實移植起來也就兩步:

  • ① 添加源碼到裸機工程中;
  • ② 實現需要的接口即可;

2.2. 準備裸機工程

本文中我使用的是小熊派IoT開發套件,主控芯片爲STM32L431RCT6:

移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,需要初始化以下配置:

  • 配置一個串口用於打印信息
  • printf重定向

2.3. 添加CmBacktrace到工程中

① 複製CmBacktrace源碼到工程中:

② 在keil中添加 CmBacktrace 組件的源碼文件:

③ 添加CmBacktrace的頭文件路徑:

2.4. 去除原有的HardFault_Handler

stm32l4xx_it.c文件中註釋該函數,防止衝突:

2.5. 配置CmBacktrace

CmBacktrace的配置文件在cmb_cfg.h,針對不同的平臺和場景,需要自行手動配置:

本文使用的是Cortex-M4裸機平臺,配置如下:

至此,CmBacktrace移植、配置完成,接下來就可以愉快的使用了!

3. 使用CmBacktrace

使用時包含頭文件:

#include "cm_backtrace.h"

3.1. 初始化CmBacktrace

void cm_backtrace_init(const char *firmware_name, const char *hardware_ver, const char *software_ver);

該 API 用來初始化CmBacktrace,其中傳入的參數分別爲:

  • firmware_name:固件名稱,需與編譯器生成的固件名稱對應;
  • hardware_ver:固件對應的硬件版本號;
  • software_ver:固件的軟件版本號;

這三個參數用宏定義的方式,會比較方便:

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define APPNAME                        "CmBacktrace"
#define HARDWARE_VERSION               "V1.0.0"
#define SOFTWARE_VERSION               "V0.0.1"

/* USER CODE END PD */

在main函數中初始化CmBacktrace :

/* USER CODE BEGIN 2 */
printf("CmBacktrace Test...\r\n");
cm_backtrace_init(APPNAME, HARDWARE_VERSION, SOFTWARE_VERSION);

/* USER CODE END 2 */

3.2. 追蹤故障錯誤信息

庫本身提供了 HardFault 處理的彙編文件(cmb_fault.S),會在故障時自動調用 cm_backtrace_fault 方法,之前移植時已經添加,這裏直接人工製作一個錯誤,來看看使用效果。

定義一個除0錯誤的函數:

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
void fault_test_by_div0(void)
{
    volatile int * SCB_CCR = (volatile int *) 0xE000ED14; // SCB->CCR
    int x, y, z;

    *SCB_CCR |= (1 << 4); /* bit4: DIV_0_TRP. */

    x = 10;
    y = 0;
    z = x / y;
    printf("z:%d\n", z);
}
/* USER CODE END PV */

然後在初始化之後調用執行此函數:

/* USER CODE BEGIN 2 */
printf("CmBacktrace Test...\r\n");
cm_backtrace_init(APPNAME, HARDWARE_VERSION, SOFTWARE_VERSION);
fault_test_by_div0();
/* USER CODE END 2 */

編譯,下載運行,在串口助手中查看結果:

查看進一步的信息,需要使用addr2line工具,此工具在cmbacktrace的源碼中,按照你的電腦選擇32位的或者64位的:

將此工具複製到裸機工程的固件所在文件夾中:

此處是爲了演示方便,實際使用時請將此工具複製到任何一個地方,然後添加到環境變量中,這樣就可以在命令行中使用了。

打開cmd命令行,進入axf所在目錄,執行串口中提示的命令:

除了英文之外,還提供了中文支持,修改配置:

再次編譯下載,查看效果:

4. 設計思想解讀

4.1. 條件編譯的使用

CmBacktrace作爲一個與底層彙編指令打交道的庫,適配非常完善:

  • 支持 Cortex-M0/M3/M4/M7 MCU;
  • 支持 IAR、KEIL、GCC 編譯器;
  • 支持裸機平臺
  • 支持UCos/RT-Thread/FreeRTOS操作系統

要做到如此廣泛的支持,armink大神大量的使用了宏定義和條件編譯,代碼非常優雅,值得一讀,比如下面這段代碼用來處理不同編譯器生成的可執行文件名稱不同問題,如果是除MDK,IAR,GCC之外的編譯工具,編譯將報錯:

再比如下面這端代碼更是優雅,本來語言配置是留給用戶配置的,如果用戶忘了配置,此段代碼將設置一個默認值,編譯依然正常:

以上兩種條件編譯的用法在CmBacktrace中大量出現,在我們寫代碼的時候值得模仿和學習。

4.2. 如何追蹤錯誤

其實要做到自動追蹤錯誤,就是在系統進入故障的時候將CPU環境打印出來,便於分析定位錯誤。

CmBacktrace庫對於CPU環境的抽象是cmb_hard_fault_regs結構體,源碼在cmb_def.h

/**
 * Cortex-M fault registers
 */
struct cmb_hard_fault_regs{
  struct {
	//……
  } saved;

  union {
	//……
  } syshndctrl;                          // System Handler Control and State Register (0xE000ED24)

  union {
   //……
  } mfsr;                                // Memory Management Fault Status Register (0xE000ED28)
  unsigned int mmar;                     // Memory Management Fault Address Register (0xE000ED34)

  union {
   //……
  } bfsr;                                // Bus Fault Status Register (0xE000ED29)
  unsigned int bfar;                     // Bus Fault Manage Address Register (0xE000ED38)

  union {
    //……
  } ufsr;                                // Usage Fault Status Register (0xE000ED2A)

  union {
    //……
  } hfsr;                                // Hard Fault Status Register (0xE000ED2C)

  union {
   //……
  } dfsr;                                // Debug Fault Status Register (0xE000ED30)

  unsigned int afsr;                     // Auxiliary Fault Status Register (0xE000ED3C), Vendor controlled (optional)
};

① saved結構體:
發生錯誤時必須要保存R0-R12、LR、PC這些CPU中的寄存器組,本節講述的重點是PSR寄存器,全稱Program status register,程序狀態寄存器,包括三個,如圖:

  • Application Program Status Register (APSR)
  • Interrupt Program Status Register (IPSR)
  • Execution Program Status Register (EPSR)


因爲CPU中的寄存器都是32位的,避免浪費,這三個寄存器合併在一個PSR寄存器中,如圖:

  struct {
    unsigned int r0;                     // Register R0
    unsigned int r1;                     // Register R1
    unsigned int r2;                     // Register R2
    unsigned int r3;                     // Register R3
    unsigned int r12;                    // Register R12
    unsigned int lr;                     // Link register
    unsigned int pc;                     // Program counter
    union {
      unsigned int value;
      struct {
        unsigned int IPSR : 8;           // Interrupt Program Status register (IPSR)
        unsigned int EPSR : 19;          // Execution Program Status register (EPSR)
        unsigned int APSR : 5;           // Application Program Status register (APSR)
      } bits;
    } psr;                               // Program status register.
  } saved;

② syshndctrl共用體
此共用體用來存儲SCB_SHCSR寄存器的值,全稱System handler control and state register,系統處理程序控制和狀態寄存器,該寄存器指示出了系統處理程序是否啓用,通俗點說就是異常處理是否使能,內容如圖:

  union {
    unsigned int value;
    struct {
      unsigned int MEMFAULTACT    : 1;   // Read as 1 if memory management fault is active
      unsigned int BUSFAULTACT    : 1;   // Read as 1 if bus fault exception is active
      unsigned int UnusedBits1    : 1;
      unsigned int USGFAULTACT    : 1;   // Read as 1 if usage fault exception is active
      unsigned int UnusedBits2    : 3;
      unsigned int SVCALLACT      : 1;   // Read as 1 if SVC exception is active
      unsigned int MONITORACT     : 1;   // Read as 1 if debug monitor exception is active
      unsigned int UnusedBits3    : 1;
      unsigned int PENDSVACT      : 1;   // Read as 1 if PendSV exception is active
      unsigned int SYSTICKACT     : 1;   // Read as 1 if SYSTICK exception is active
      unsigned int USGFAULTPENDED : 1;   // Usage fault pended; usage fault started but was replaced by a higher-priority exception
      unsigned int MEMFAULTPENDED : 1;   // Memory management fault pended; memory management fault started but was replaced by a higher-priority exception
      unsigned int BUSFAULTPENDED : 1;   // Bus fault pended; bus fault handler was started but was replaced by a higher-priority exception
      unsigned int SVCALLPENDED   : 1;   // SVC pended; SVC was started but was replaced by a higher-priority exception
      unsigned int MEMFAULTENA    : 1;   // Memory management fault handler enable
      unsigned int BUSFAULTENA    : 1;   // Bus fault handler enable
      unsigned int USGFAULTENA    : 1;   // Usage fault handler enable
    } bits;
  } syshndctrl;                          // System Handler Control and State Register (0xE000ED24)

③ mfsr共用體、bfsr共用體、ufsr共用體

這個是故障追蹤得以實現的原理,它用來存儲寄存器SCB_CFSR的值,全稱Configurable fault status register,可配置的故障狀態寄存器,顧名思義,它指示出當前系統發生的是什麼故障,現在,CmBacktrace沒有那麼神祕了吧~

CFSR寄存器是一個32位的寄存器,但是它可以按字節訪問,所以就分爲MMFSR、
BFSR、UFSR三個,如圖:

其中具體每一位代表什麼故障類型,如圖:

我用紅框圈出的就是本文中演示的故障錯誤——除0錯誤,如果你對其它錯誤有興趣,請閱讀《STM32F10xxx Cortex-M3 programming manual》(編程手冊)第141頁。

源碼如下,可以看到其中每一位都和圖中對應:

  union {
    unsigned char value;
    struct {
      unsigned char IACCVIOL    : 1;     // Instruction access violation
      unsigned char DACCVIOL    : 1;     // Data access violation
      unsigned char UnusedBits  : 1;
      unsigned char MUNSTKERR   : 1;     // Unstacking error
      unsigned char MSTKERR     : 1;     // Stacking error
      unsigned char MLSPERR     : 1;     // Floating-point lazy state preservation (M4/M7)
      unsigned char UnusedBits2 : 1;
      unsigned char MMARVALID   : 1;     // Indicates the MMAR is valid
    } bits;
  } mfsr;                                // Memory Management Fault Status Register (0xE000ED28)
  unsigned int mmar;                     // Memory Management Fault Address Register (0xE000ED34)

  union {
    unsigned char value;
    struct {
      unsigned char IBUSERR    : 1;      // Instruction access violation
      unsigned char PRECISERR  : 1;      // Precise data access violation
      unsigned char IMPREISERR : 1;      // Imprecise data access violation
      unsigned char UNSTKERR   : 1;      // Unstacking error
      unsigned char STKERR     : 1;      // Stacking error
      unsigned char LSPERR     : 1;      // Floating-point lazy state preservation (M4/M7)
      unsigned char UnusedBits : 1;
      unsigned char BFARVALID  : 1;      // Indicates BFAR is valid
    } bits;
  } bfsr;                                // Bus Fault Status Register (0xE000ED29)
  unsigned int bfar;                     // Bus Fault Manage Address Register (0xE000ED38)

  union {
    unsigned short value;
    struct {
      unsigned short UNDEFINSTR : 1;     // Attempts to execute an undefined instruction
      unsigned short INVSTATE   : 1;     // Attempts to switch to an invalid state (e.g., ARM)
      unsigned short INVPC      : 1;     // Attempts to do an exception with a bad value in the EXC_RETURN number
      unsigned short NOCP       : 1;     // Attempts to execute a coprocessor instruction
      unsigned short UnusedBits : 4;
      unsigned short UNALIGNED  : 1;     // Indicates that an unaligned access fault has taken place
      unsigned short DIVBYZERO0 : 1;     // Indicates a divide by zero has taken place (can be set only if DIV_0_TRP is set)
    } bits;
  } ufsr;

剩餘的hfsr寄存器、dfsr寄存器、afsr寄存器知道了也沒多大意義,省略不講。

5. 項目工程源碼獲取和問題交流

目前我將CmBacktrace 源碼、我移植到小熊派STM32L431RCT6開發板的工程源碼上傳到了QQ羣裏(包含好幾份HAL庫,QQ相對速度快點),可以在QQ羣裏下載,有問題也可以在羣裏交流,當然也歡迎大家分享出來自己移植的工程到QQ羣裏

放上QQ羣二維碼:

接收更多精彩文章及資源推送,歡迎訂閱我的微信公衆號:『mculover666』。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章