0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

FreeRTOS優(yōu)化與錯誤排查方法有哪些

汽車電子技術(shù) ? 來源:物聯(lián)網(wǎng)IoT開發(fā) ? 作者:杰杰 ? 2023-02-14 09:59 ? 次閱讀

寫在前面

主要是為剛接觸 FreeRTOS 的用戶指出那些新手通常容易遇到的問題。這里把最主要的篇幅放在棧溢出以及棧溢出檢測上,因為棧相關(guān)的問題是初學者遇到最多的問題。

printf-stdarg.c

當調(diào)用 C 標準庫 的函數(shù)時,棧空間使用量可能會急劇上升,特別是 IO 與字符串處理函數(shù),比如 sprintf()、printf()等。在 FreeRTOS 源碼包中有一個名為 printf-stdarg.c 的文件。這個文件實現(xiàn)了一個棧效率優(yōu)化版的小型 sprintf()、printf(),可以用來代替標準 C 庫函數(shù)版本。在大多數(shù)情況下,這樣做可以使得調(diào)用 sprintf()及相關(guān)函數(shù)的任務(wù)對??臻g的需求量小很多。

可能很多人都不知道freertos中有這樣子的一個文件,它放在第三方資料中,路徑為“ FreeRTOSv9.0.0\\FreeRTOS-Plus\\Demo\\FreeRTOS_Plus_UDP_and_CLI_LPC1830_GCC ”,我們發(fā)布工程的時候就無需依賴 C 標準庫 ,這樣子就能減少棧的使用,能優(yōu)化不少空間。

該文件源碼(部分):

1static int print( char **out, const char *format, va_list args )
 2{
 3    register int width, pad;
 4    register int pc = 0;
 5    char scr[2];
 6
 7    for (; *format != 0; ++format) {
 8        if (*format == '%') {
 9            ++format;
10            width = pad = 0;
11            if (*format == '\\0') break;
12            if (*format == '%') goto out;
13            if (*format == '-') {
14                ++format;
15                pad = PAD_RIGHT;
16            }
17            while (*format == '0') {
18                ++format;
19                pad |= PAD_ZERO;
20            }
21            for ( ; *format >= '0' && *format <= '9'; ++format) {
22                width *= 10;
23                width += *format - '0';
24            }
25            if( *format == 's' ) {
26                register char *s = (char *)va_arg( args, int );
27                pc += prints (out, s?s:"(null)", width, pad);
28                continue;
29            }
30            if( *format == 'd' || *format == 'i' ) {
31                pc += printi (out, va_arg( args, int ), 10, 1, width, pad, 'a');
32                continue;
33            }
34            if( *format == 'x' ) {
35                pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'a');
36                continue;
37            }
38            if( *format == 'X' ) {
39                pc += printi (out, va_arg( args, int ), 16, 0, width, pad, 'A');
40                continue;
41            }
42            if( *format == 'u' ) {
43                pc += printi (out, va_arg( args, int ), 10, 0, width, pad, 'a');
44                continue;
45            }
46            if( *format == 'c' ) {
47                /* char are converted to int then pushed on the stack */
48                scr[0] = (char)va_arg( args, int );
49                scr[1] = '\\0';
50                pc += prints (out, scr, width, pad);
51                continue;
52            }
53        }
54        else {
55        out:
56            printchar (out, *format);
57            ++pc;
58        }
59    }
60    if (out) **out = '\\0';
61    va_end( args );
62    return pc;
63}
64
65int printf(const char *format, ...)
66{
67    va_list args;
68
69    va_start( args, format );
70    return print( 0, format, args );
71}
72
73int sprintf(char *out, const char *format, ...)
74{
75    va_list args;
76
77    va_start( args, format );
78    return print( &out, format, args );
79}
80
81
82int snprintf( char *buf, unsigned int count, const char *format, ... )
83{
84    va_list args;
85
86    ( void ) count;
87
88    va_start( args, format );
89    return print( &buf, format, args );
90}

使用的例子與 C 標準庫基本一樣:

1int main(void)
 2{
 3    char *ptr = "Hello world!";
 4    char *np = 0;
 5    int i = 5;
 6    unsigned int bs = sizeof(int)*8;
 7    int mi;
 8    char buf[80];
 9
10    mi = (1 << (bs-1)) + 1;
11    printf("%s\\n", ptr);
12    printf("printf test\\n");
13    printf("%s is null pointer\\n", np);
14    printf("%d = 5\\n", i);
15    printf("%d = - max int\\n", mi);
16    printf("char %c = 'a'\\n", 'a');
17    printf("hex %x = ff\\n", 0xff);
18    printf("hex %02x = 00\\n", 0);
19    printf("signed %d = unsigned %u = hex %x\\n", -3, -3, -3);
20    printf("%d %s(s)%", 0, "message");
21    printf("\\n");
22    printf("%d %s(s) with %%\\n", 0, "message");
23    sprintf(buf, "justif: \\"%-10s\"\\n", "left"); printf("%s", buf);
24    sprintf(buf, "justif: \"%10s\"\\n", "right"); printf("%s", buf);
25    sprintf(buf, " 3: %04d zero padded\\n", 3); printf("%s", buf);
26    sprintf(buf, " 3: %-4d left justif.\\n", 3); printf("%s", buf);
27    sprintf(buf, " 3: %4d right justif.\\n", 3); printf("%s", buf);
28    sprintf(buf, "-3: %04d zero padded\\n", -3); printf("%s", buf);
29    sprintf(buf, "-3: %-4d left justif.\\n", -3); printf("%s", buf);
30    sprintf(buf, "-3: %4d right justif.\\n", -3); printf("%s", buf);
31
32    return 0;
33}

棧計算

每個任務(wù)都獨立維護自己的??臻g, 任務(wù)??臻g總量在任務(wù)創(chuàng)建時進行設(shè)定。uxTaskGetStackHighWaterMark()主要用來查詢指定任務(wù)的運行歷史中, 其棧空間還差多少就要溢出。這個值被稱為棧空間的 High Water Mark 。

函數(shù)原型:

1UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )

想要使用它,需要將對應(yīng)的宏定義打開:INCLUDE_uxTaskGetStackHighWaterMark

函數(shù)描述:

參數(shù) 說明
xTask 被查詢?nèi)蝿?wù)的句柄如果傳入 NULL 句柄,則任務(wù)查詢的是自身棧空間的高水線
返回值 任務(wù)??臻g的實際使用量會隨著任務(wù)執(zhí)行和中斷處理過程上下浮動。uxTaskGetStackHighWaterMark()返回從任務(wù)啟動執(zhí)行開始的運行歷史中,棧空間具有的最小剩余量。這個值即是??臻g使用達到最深時的剩下的未使用的??臻g。這個值越是接近 0,則這個任務(wù)就越是離棧溢出不遠。

如果不知道怎么計算任務(wù)棧大小,就使用這個函數(shù)進行統(tǒng)計一下,然后將任務(wù)運行時最大的??臻g作為任務(wù)??臻g的80%大小即可。即假設(shè)統(tǒng)計得到的任務(wù)棧大小為常量 A ,那么在創(chuàng)建線程的時候需要 X 大小的空間,那么 X * 80% = A ,算到的 X 作為任務(wù)棧大小就差不多了。

運行時棧檢測

FreeRTOS 包含兩種運行時棧檢測機制,由 FreeRTOSConfig.h 中的配置常量configCHECK_FOR_STACK_OVERFLOW 進行控制。這兩種方式都會增加上下切換開銷。

棧溢出鉤子函數(shù)(或稱回調(diào)函數(shù))由內(nèi)核在檢測到棧溢出時調(diào)用。要使用棧溢出鉤子函數(shù),需要進行以下配置:

  • 在 FreeRTOSConfig.h 中把 configCHECK_FOR_STACK_OVERFLOW 設(shè)為 1 或者 2 。
  • 提供鉤子函數(shù)的具體實現(xiàn),采用下面所示的函數(shù)名和函數(shù)原型。
1void vApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName );

補充說明:

  • 棧溢出鉤子函數(shù)只是為了使跟蹤調(diào)試??臻g錯誤更容易,而無法在棧溢出時對其進行恢復(fù)。函數(shù)的入口參數(shù)傳入了任務(wù)句柄和任務(wù)名,但任務(wù)名很可能在溢出時已經(jīng)遭到破壞。
  • 棧溢出鉤子函數(shù)還可以在中斷的上下文中進行調(diào)用
  • 某些微控制器在檢測到內(nèi)存訪問錯誤時會產(chǎn)生錯誤異常,很可能在內(nèi)核調(diào)用棧溢出鉤子函數(shù)之前就觸發(fā)了錯誤異常中斷。

方法1

configCHECK_FOR_STACK_OVERFLOW 設(shè)置為 1 時選用方法 1 。

任務(wù)被交換出去的時候,該任務(wù)的整個上下文被保存到它自己的??臻g中。這時任務(wù)棧的使用應(yīng)當達到了一個峰值。當 configCHECK_FOR_STACK_OVERFLOW 設(shè)為1 時,內(nèi)核會在任務(wù)上下文保存后檢查棧指針是否還指向有效??臻g。一旦檢測到棧指針的指向已經(jīng)超出任務(wù)棧的有效范圍,棧溢出鉤子函數(shù)就會被調(diào)用。

方法 1 具有較快的執(zhí)行速度,但棧溢出有可能發(fā)生在兩次上下文保存之間,這種情況不會被檢測到,因為這種檢測方式僅在任務(wù)切換中檢測。

方法2

configCHECK_FOR_STACK_OVERFLOW 設(shè)為 2 就可以選用方法 2 。方法 2在方法 1 的基礎(chǔ)上進行了一些補充。

當創(chuàng)建任務(wù)時,任務(wù)??臻g中就預(yù)置了一個標記。方法 2 會檢查任務(wù)棧的最后 20個字節(jié)的數(shù)據(jù),查看預(yù)置在這里的標記數(shù)據(jù)是否被覆蓋。如果最后 20 個字節(jié)的標記數(shù)據(jù)與預(yù)設(shè)值不同,則棧溢出鉤子函數(shù)就會被調(diào)用。

方法 2 沒有方法 1 的執(zhí)行速度快,但測試僅僅 20 個字節(jié)相對來說也是很快的。這種方法應(yīng)該可以檢測到任何時候發(fā)生的棧溢出,雖然理論上還是有可能漏掉一些情況,但這些情況幾乎是不可能發(fā)生的。

其它常見錯誤

在一個 Demo 應(yīng)用程序中增加了一個簡單的任務(wù),導致應(yīng)用程序崩潰

可能的情況:

  1. 任務(wù)創(chuàng)建時需要在內(nèi)存堆中分配空間。許多 Demo 應(yīng)用程序定義的堆空間大小只夠用于創(chuàng)建 Demo 任務(wù)——所以當任務(wù)創(chuàng)建完成后,就沒有足夠的剩余空間來增加其它的 任務(wù),隊列或信號

  2. 空閑任務(wù)是在 vTaskStartScheduler()調(diào)用中自動創(chuàng)建的。如果由于內(nèi)存不足而無法創(chuàng)建空閑任務(wù),vTaskStartScheduler()會直接返回。所以一般在調(diào)用 vTaskStartScheduler()后加上一條空循環(huán)for(;;) / while(1)可以使這種錯誤更加容易調(diào)試。

    如果要添加更多的任務(wù),可以增加內(nèi)存堆空間大?。ㄐ薷呐渲梦募蚴莿h掉一些已存在的 Demo任務(wù)。

在中斷中調(diào)用一個 API 函數(shù),導致應(yīng)用程序崩潰

需要做的第一件事是檢查中斷是否導致了棧溢出。

然后檢查API接口是否正確 ,除了具有后綴為FromISR函數(shù)名的 API 函數(shù),千萬不要在中斷服務(wù)程序中調(diào)用其它 API 函數(shù)。

除此之外,還需要注意中斷的優(yōu)先級:

FreeRTOSConfig.h文件中可以配置系統(tǒng)可管理的最高中斷優(yōu)先級數(shù)值,宏定義configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY是用于配置basepri寄存器的,當basepri設(shè)置為某個值的時候,會讓系統(tǒng)不響應(yīng)比該優(yōu)先級低的中斷,而優(yōu)先級比之更高的中斷則不受影響。就是說當這個宏定義配置為5的時候,中斷優(yōu)先級數(shù)值在0、1、2、3、4的這些中斷是不受FreeRTOS管理的,不可被屏蔽, 同時也不能調(diào)用FreeRTOS中的API函數(shù)接口 ,而中斷優(yōu)先級在5到15的這些中斷是受到系統(tǒng)管理,可以被屏蔽的,也可以調(diào)用FreeRTOS中的API函數(shù)接口。

臨界區(qū)無法正確嵌套

除了 taskENTER_CRITICA()和 taskEXIT_CRITICAL(),千萬不要在其它地方修改控制器的中斷使能位或優(yōu)先級標志。這兩個宏維護了一個嵌套深度計數(shù),所以只有當所有的嵌套調(diào)用都退出后計數(shù)值才會為 0,也才會使能中斷。

在調(diào)度器啟動前應(yīng)用程序就崩潰了

這個問題我也會遇到,如果一個中斷會產(chǎn)生上下文切換,則這個中斷不能在調(diào)度器啟動之前使能。這同樣適用于那些需要讀寫隊列或信號量的中斷。在調(diào)度器啟動之前,不能進行上下文切換。

還有一些 API 函數(shù)不能在調(diào)度器啟動之前調(diào)用。在調(diào)用 vTaskStartScheduler()之前,最好是限定只使用創(chuàng)建任務(wù),隊列和信號量的 API 函數(shù)。

比如有一些初始化需要中斷的,或者在初始化完成的時候回產(chǎn)生一個中斷,這些驅(qū)動的初始化最好放在一個任務(wù)中進行,我是這樣子處理的,在main函數(shù)中創(chuàng)建一個任務(wù),在任務(wù)中進行bsp初始化,然后再創(chuàng)建消息隊列、信號量、互斥量、事件以及任務(wù)等操作。

在調(diào)度器掛起時調(diào)用 API 函數(shù),導致應(yīng)用程序崩潰

調(diào)用 vTaskSuspendAll()使得調(diào)度器掛起,而喚醒調(diào)度器調(diào)用 xTaskResumeAll()。千萬不要在調(diào)度器掛起時調(diào)用其它 API 函數(shù)。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4237

    瀏覽量

    61969
  • FreeRTOS
    +關(guān)注

    關(guān)注

    12

    文章

    483

    瀏覽量

    61726
  • ??臻g
    +關(guān)注

    關(guān)注

    0

    文章

    5

    瀏覽量

    5423
收藏 人收藏

    評論

    相關(guān)推薦

    使用匯編知識排查疑難問題的方法

    那么,本篇文章,我將再介紹一個使用匯編知識排查疑難問題的方法,希望對大家有所幫助。
    發(fā)表于 07-27 10:31 ?597次閱讀

    利用符號模擬技術(shù)優(yōu)化錯誤診斷方法

    優(yōu)化基于區(qū)域模型錯誤診斷過程的方法。該方法首先使用基于區(qū)域模型錯誤診斷方法中電路劃分
    發(fā)表于 07-05 08:05

    電子書:STM32-FREERTOS快速學習知識解密

    ——應(yīng)用場景3STM32單片機中,FreeRTOS RAM使用情況及優(yōu)化方法4FreeRTOS中列表和列表項插入函數(shù)分析5FreeRTOS+
    發(fā)表于 05-09 14:30

    STM32 FreeRTOS RAM 使用情況及優(yōu)化方法實用資料分享~

    的一般方法,并給出在 FreeRTOS優(yōu)化 RAM 使用的方法,也由衷的期望讀者在使用其他 RTOS 時,可以通過相似的思路來解決問題。Free
    發(fā)表于 01-26 14:10

    什么方法可以查看FreeRTOS任務(wù)的運行狀態(tài)呢

    什么方法可以查看FreeRTOS任務(wù)的運行狀態(tài)呢?怎樣去查看FreeRTOS任務(wù)的運行狀態(tài)呢?
    發(fā)表于 11-02 07:59

    CH579無規(guī)律進入hardfault錯誤如何排查?

    CH579 程序運行時,偶爾進入 hardfault 錯誤 ,沒有什么規(guī)律,如何排查?謝謝
    發(fā)表于 07-26 07:24

    GPIO無法觸發(fā)中斷常規(guī)排查方法哪些?

    1、電源域是否打開 2、IOMUX是否設(shè)置對 3、是否配置了中斷方式,外部電平是否滿足條件 4、是否為輸入狀態(tài) 備注:這次分享的是,我們做展銳平臺GPIO排查方法,不同平臺、不同版本、不同項目都會
    發(fā)表于 11-24 16:11

    建立一個方法和套路來對 Load 高問題排查

    講解 Linux Load 高如何排查的話題屬于老生常談了,但多數(shù)文章只是聚焦了幾個點,缺少整體排查思路的介紹。所謂 “授人以魚不如授人以漁"。本文試圖建立一個方法和套路,來幫助讀者對 Load 高問題
    的頭像 發(fā)表于 12-28 14:18 ?5319次閱讀
    建立一個<b class='flag-5'>方法</b>和套路來對 Load 高問題<b class='flag-5'>排查</b>

    FreeRTOS中的API函數(shù)功能分析及調(diào)用方法

    FreeRTOS中的API函數(shù)功能分析及調(diào)用方法說明。
    發(fā)表于 03-26 11:50 ?32次下載

    單片機硬錯誤排查方法

    HardFault 錯誤調(diào)試定位方法1、首先更改 startup.s 的啟動文件,把里面的 HardFault_Handler 代碼段換成下面的代碼:HardFault_Handler
    發(fā)表于 12-16 16:54 ?0次下載
    單片機硬<b class='flag-5'>錯誤</b><b class='flag-5'>排查</b><b class='flag-5'>方法</b>

    科普系列:CAN總線錯誤幀及排查方法簡介

    作者|蒹葭小編|吃不飽CAN幀多種格式,錯誤幀作為CAN幀中獨特的一種,了解其作用,類型與產(chǎn)生原因,對于進行測試以及開發(fā)有很大的幫助,本文將對錯誤幀的相關(guān)基礎(chǔ)知識以及后續(xù)的分析排查
    的頭像 發(fā)表于 02-23 15:11 ?2853次閱讀
    科普系列:CAN總線<b class='flag-5'>錯誤</b>幀及<b class='flag-5'>排查</b><b class='flag-5'>方法</b>簡介

    雅馬哈YS/YSM系列貼片機故障排查方法

    雅馬哈YS/YSM系列貼片機故障排查方法
    的頭像 發(fā)表于 09-13 10:05 ?3030次閱讀
    雅馬哈YS/YSM系列貼片機故障<b class='flag-5'>排查</b><b class='flag-5'>方法</b>

    常見的電源適配器故障及排查方法哪些?

    常見的電源適配器故障及排查方法哪些? 電源適配器故障是使用電子設(shè)備時經(jīng)常遇到的問題之一。合理排查和解決電源適配器故障是確保電子設(shè)備正常運行的重要步驟。本文將詳細介紹常見的電源適配器故
    的頭像 發(fā)表于 11-24 14:08 ?5038次閱讀

    腳本錯誤scripterror怎么解決

    分析和排查。以下是一些常見的解決腳本錯誤方法: 檢查語法錯誤: 仔細檢查腳本中的代碼,看是否拼寫錯誤
    的頭像 發(fā)表于 11-26 14:46 ?5738次閱讀

    如何用示波器排查CAN的各種錯誤幀呢?

    如何用示波器排查CAN的各種錯誤幀呢? 導言: 控制器局域網(wǎng)絡(luò)(Controller Area Network,CAN)是一種常用的現(xiàn)場總線通信協(xié)議,廣泛應(yīng)用于汽車電子系統(tǒng)、工業(yè)自動化等領(lǐng)域。然而
    的頭像 發(fā)表于 12-07 11:09 ?954次閱讀