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

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

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

【freeRTOS開發(fā)筆記】記一次坑爹的freeTOS升級

嵌入式物聯(lián)網(wǎng)開發(fā) ? 來源:嵌入式物聯(lián)網(wǎng)開發(fā) ? 作者:嵌入式物聯(lián)網(wǎng)開發(fā) ? 2022-07-11 09:15 ? 次閱讀

1 前言

筆者最近在做一個項目,簡單來說就是操作系統(tǒng)的替換,但是由于我們整個項目是需要兼容多個芯片平臺的,我們要做到工作就是將各大芯片原廠提供的SDK歸整起來,統(tǒng)一開發(fā)。 雖然芯片原廠都是基于freeRTOS來提供SDK,但是畢竟是不同廠商來開發(fā),自然他們基于的freeRTOS版本是不一樣的。 這個問題就被我們遇上了,A廠商提供的穩(wěn)定版本的SDK是基于freeRTOS-v9.0.0版本,而B廠商是freeRTOS-v10.4.4版本;面對這樣的困境,經(jīng)過我們內(nèi)部討論和評估,為了能最大程度兼容freeRTOS的新版本,我覺得采用10.4.4版本,這就意味著9.0.0版本的SDK就要升級了。

2 遇到的問題

2.1 版本差異

從時間跨度來說,這兩個版本是差異比較大的:

29 May 2021 @github-actions github-actions V10.4.4 8de8a9d
V9.0.0 165c24c @RichardBarry RichardBarry tagged this 25 May 2016

這么多年了,自然迭代的功能就非常多,其中API的實現(xiàn)方法改變就是一個在移植升級過程中非常頭疼的問題。

2.2 問題描述

本次遇到的主要問題是portENTER_CRITICALportEXIT_CRITICAL兩個適配接口完全不太一樣導(dǎo)致的,具體如下:

//v9.0.0版本中使用的宏定義的方式
#define GLOBAL_INT_DECLARATION()   uint32_t fiq_tmp, irq_tmp
#define GLOBAL_INT_DISABLE()       do{\
                                        fiq_tmp = portDISABLE_FIQ();\
                                        irq_tmp = portDISABLE_IRQ();\
                                    }while(0)


#define GLOBAL_INT_RESTORE()       do{                         \
                                        if(!fiq_tmp)           \
                                        {                      \
                                            portENABLE_FIQ();  \
                                        }                      \
                                        if(!irq_tmp)           \
                                        {                      \
                                            portENABLE_IRQ();  \
                                        }                      \
                                   }while(0)

#define portENTER_CRITICAL()        do{     \
                                                GLOBAL_INT_DECLARATION();\
                                                GLOBAL_INT_DISABLE(); 
#define portEXIT_CRITICAL()                 \
                                                GLOBAL_INT_RESTORE();\
                                      }while(0)
//v10.4.4版本中采用的是函數(shù)定義的方式
/* Critical section handling. */
void vPortEnterCritical( void );
void vPortExitCritical( void );

#define portENTER_CRITICAL()        vPortEnterCritical()
#define portEXIT_CRITICAL()            vPortExitCritical()

看樣子從功能上,好像是一樣的,但是真正到了替換編譯的時候就遇到問題了。 按照v9.0.0的定義方式,我kernel使用v9.0.0的代碼編譯自然沒有問題,但是我一旦切換到v10.4.4的kernel代碼,就報了下面的編譯錯誤:

os/core/freertos-v10.4.4/queue.c: In function 'xQueueGenericSend':
core/freertos-v10.4.4/queue.c:938:13: error: 'else' without a previous 'if'
             else
             ^
compilation terminated due to -Wfatal-errors.

core/freertos-v10.4.4/stream_buffer.c: In function 'xStreamBufferSend':
core/freertos-v10.4.4/stream_buffer.c:625:13: error: expected 'while' before 'do'
             taskEXIT_CRITICAL();
             ^
compilation terminated due to -Wfatal-errors.

3 如何解決

3.1 問題分析

一看上面的兩個問題,大概猜到了就是v9.0.0中使用的是do{}while(0)這種宏定義導(dǎo)致的。 找到v10.4.4的源碼看下它是什么調(diào)用的,為了簡潔且能說明問題,這里我刪除了一些無相關(guān)的代碼:

//queue.c中編譯報錯的函數(shù)
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition )
{
    for( ; ; )
    {
        taskENTER_CRITICAL();
        {
            if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
            {              
                taskEXIT_CRITICAL(); //這里報錯
                return pdPASS;
            }
            else
            {
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                    /* The queue was full and no block time is specified (or
                     * the block time has expired) so leave now. */
                    taskEXIT_CRITICAL();
                }
                else if( xEntryTimeSet == pdFALSE )
                {
                }
                else
                {
                }
            }
        }
        taskEXIT_CRITICAL();
    }

}

queue.c里面的報錯,這個是由于中間有個taskEXIT_CRITICAL調(diào)用,把do {} while(0)給打散了,導(dǎo)致下面的}else{就變成語法問題了,正如編譯報錯的那樣。

再看下tasks.c的報錯代碼:

//tasks.c中的編譯報錯代碼
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
                          const void * pvTxData,
                          size_t xDataLengthBytes,
                          TickType_t xTicksToWait )
{
    if( xTicksToWait != ( TickType_t ) 0 )
    {
        do
        {
            /* Wait until the required number of bytes are free in the message
             * buffer. */
            taskENTER_CRITICAL();
            {
                xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );

                if( xSpace < xRequiredSpace )
                {
                }
                else
                {
                    taskEXIT_CRITICAL();
                    break;
                }
            }
            taskEXIT_CRITICAL(); //這里報錯
    }
    else
    {
    }

與第一個編譯報錯類似,但是又不太一樣,當(dāng)然也都是do{}while(0)被打散引發(fā)的;這里的錯誤提示是:后一個while沒有前面的do來匹配。

3.2 細(xì)看錯誤代碼

既然那兩個接口是宏定義,自然我就可以查看到宏定義展開后的樣子,看下究竟是如何違背了語法規(guī)則? 使用gcc編譯器,我們只需要在CFLAGS加上-save-temps=obj選項,就可以同步輸出預(yù)編譯處理的文件,后綴名是.i

//queue.i對應(yīng)的代碼片段
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition )
{
    for( ; ; )
    {
        do{ uint32_t fiq_tmp, irq_tmp; do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0);;
        {
            if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == ( ( BaseType_t ) 2 ) ) )
            {
                do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0); //這里報錯
                return ( ( ( BaseType_t ) 1 ) );
            }
            else
            {
            }
        }
        do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0);
    }
}

//tasks.i對應(yīng)的代碼片段


size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
                          const void * pvTxData,
                          size_t xDataLengthBytes,
                          TickType_t xTicksToWait )
{
    if( xTicksToWait != ( TickType_t ) 0 )
    {
        do
        {
            do{ uint32_t fiq_tmp, irq_tmp; do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0);;
            {
                xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );

                if( xSpace < xRequiredSpace )
                {
                }
                else
                {
                    do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0); //這里報錯
                    break;
                }
            }
            do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0);

            ;
        } while( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == ( ( BaseType_t ) 0 ) );
    }
    else
    {
        ;
    }

通過.i文件,基本一看就知道啥問題了。就是這個萬惡的do{}while(0)被打散了,引發(fā)各種問題。

3.3 能不能把宏定義改為函數(shù)?

知道了上面的問題,歸根結(jié)底就是宏定義的問題,那么我能不能把宏定義轉(zhuǎn)換成函數(shù)呢? 之前我有一篇文章講過內(nèi)聯(lián)函數(shù),即staticinline的用法,具體參見:【gcc編譯優(yōu)化系列】static與inline的區(qū)別與聯(lián)系 參考這個方案,很快,我給出了一個staticinline的版本:

//portmacro.h中定義:
static inline void portENTER_CRITICAL(void)
{
    GLOBAL_INT_DECLARATION();
    GLOBAL_INT_DISABLE(); 
}
static inline void portEXIT_CRITICAL(void)
{
    GLOBAL_INT_DECLARATION();
    GLOBAL_INT_RESTORE();
}
static inline void portEXIT_CRITICAL_EARLY(void)       
{
    GLOBAL_INT_DECLARATION();
    GLOBAL_INT_RESTORE();
}

這個portEXITCRITICALEARLY是因為v9.0.0的代碼里面有,為了兼容v9.0.0的代碼編譯,我保留了下它。 同時這個GLOBAL_INT_DECLARATION這個我也改了一下,加上了extern

#define GLOBAL_INT_DECLARATION()   extern uint32_t fiq_tmp, irq_tmp //新的定義
//#define GLOBAL_INT_DECLARATION()   uint32_t fiq_tmp, irq_tmp //舊的定義
#endif

同時由于這兩個變量fiq_tmp,irq_tmp沒法在兩個函數(shù)中共享,所以得把它們定義成全局變量

//必須在其中的一個.c文件中定義,因為定義只能由一個,而extern申明可以有多個。
uint32_t fiq_tmp, irq_tmp;

經(jīng)過上面的宏定義轉(zhuǎn)內(nèi)聯(lián)函數(shù)的定義,一編譯,自然,那幾個編譯報錯的語法問題都迎刃而解了。 但是當(dāng)我燒錄到板子上運行時,卻遇到了問題,具體問題就是:系統(tǒng)會在不確認(rèn)的時間內(nèi)卡死,導(dǎo)致看門狗復(fù)位,這里面有可能是廠商的SDK封裝的問題,但是找廠商去修改SDK是不可能的,畢竟是由我們單方面升級了freeRTOS了,別人跑得好好的,就你不行。

3.4 能不能有其他解決辦法?

想到上一步,為何SDK會出問題,我想上面宏定義轉(zhuǎn)內(nèi)聯(lián)函數(shù)只是表象,真正改動的是把中斷標(biāo)記的那兩個變量全局化了;這樣帶來的問題就是全部線程都可以同時修改,這顯然違背了之前的設(shè)計初衷,所以它們一定不能全局化。 那么還有什么方法僅能保證代碼編譯過去,又能保證這兩個變量的訪問邏輯呢? 思思一想,還是得保留宏定義的寫法,但是宏定義得改一改。 之前不是老是出現(xiàn)do{}while(0)被打散嘛,我們能不能把do-while(0)去掉,試試看:

#if 1 //新版本
#define portENTER_CRITICAL()            GLOBAL_INT_DECLARATION();GLOBAL_INT_DISABLE()
#define portEXIT_CRITICAL()               GLOBAL_INT_RESTORE()
#define portEXIT_CRITICAL_EARLY()         GLOBAL_INT_RESTORE() 
#else
#define portENTER_CRITICAL()        do{     \
                                                GLOBAL_INT_DECLARATION();\
                                                GLOBAL_INT_DISABLE(); 
#define portEXIT_CRITICAL()                 \
                                                GLOBAL_INT_RESTORE();\
                                      }while(0)
#define portEXIT_CRITICAL_EARLY()     GLOBAL_INT_RESTORE() 
#endif

如此改動之后,編譯一下,又發(fā)現(xiàn)了一個報錯:

//報錯
core/freertos-v10.4.4/queue.c: In function 'xQueueGenericSend':
core/freertos-v10.4.4/queue.c:984:18: error: redeclaration of 'fiq_tmp' with no linkage
         prvLockQueue( pxQueue );

//對應(yīng)代碼
        taskEXIT_CRITICAL();

        /* Interrupts and other tasks can send to and receive from the queue
         * now the critical section has been exited. */

        vTaskSuspendAll();
        prvLockQueue( pxQueue ); //這里報錯

        /* Update the timeout state to see if it has expired yet. */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
        }

//對應(yīng)宏展開的代碼
        for( ; ; )
    {
        uint32_t fiq_tmp, irq_tmp;do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0);

        do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0);

        vTaskSuspendAll();
        uint32_t fiq_tmp, irq_tmp;do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0); { if( ( pxQueue )->cRxLock == ( ( int8_t ) -1 ) ) { ( pxQueue )->cRxLock = ( ( int8_t ) 0 ); } if( ( pxQueue )->cTxLock == ( ( int8_t ) -1 ) ) { ( pxQueue )->cTxLock = ( ( int8_t ) 0 ); } } do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0);

這里的主要問題就是,由于do{}while(0)去掉了,導(dǎo)致uint32_tfiq_tmp,irq_tmp在一個代碼段范圍內(nèi)被重復(fù)定義了,所以語法上報錯了。 為了解決這個問題,我們需要有個語法基礎(chǔ):在C里面,一個局部變量的作用域是在其包含的{}內(nèi),嵌套的{}可以有同名的變量名, 也就是說這樣的代碼時允許的:

{
    int a = 1;
    {
        int a = 1;
        {
            int a = 1;
        }
    }
}

雖然寫法上很丑陋,但是語法上是可行的。 根據(jù)這個理論,我們要改造下這個宏定義:

#if 1 //新代碼
#define prvLockQueue0( pxQueue )                            \
do {                                                       \
    taskENTER_CRITICAL();                                  \
    {                                                      \
        if( ( pxQueue )->cRxLock == queueUNLOCKED )        \
        {                                                  \
            ( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
        }                                                  \
        if( ( pxQueue )->cTxLock == queueUNLOCKED )        \
        {                                                  \
            ( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
        }                                                  \
    }                                                      \
    taskEXIT_CRITICAL();                                   \
} while(0)
#else
#define prvLockQueue( pxQueue )                            \
    taskENTER_CRITICAL();                                  \
    {                                                      \
        if( ( pxQueue )->cRxLock == queueUNLOCKED )        \
        {                                                  \
            ( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
        }                                                  \
        if( ( pxQueue )->cTxLock == queueUNLOCKED )        \
        {                                                  \
            ( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
        }                                                  \
    }                                                      \
    taskEXIT_CRITICAL()
#endif

這樣就可以完美解決了uint32_tfiq_tmp,irq_tmp重復(fù)定義的問題。 編譯一下,下載跑了一下,發(fā)現(xiàn)工作正常,至此算是把這個升級工作完成了。

3.5 還有個問題

升級過程中,還有一個問題,不過倒是比較好解決。 就是v9.0.0版本有個API接口叫xTaskIsTaskFinished;而v10.4.4已經(jīng)把這個函數(shù)刪除了,而SDK又調(diào)用了這個API,所以只能重新實現(xiàn)下這個函數(shù)

/* If not found, implemente it ! */
__attribute__ ((weak)) portBASE_TYPE xTaskIsTaskFinished( xTaskHandle xTask )
{
    LOG_HERE();
    /* always return false ! */
    return pdFALSE;
}

這里我加了weak聲明,也就是說當(dāng)內(nèi)核有實現(xiàn)這個函數(shù)時,用內(nèi)核的;反之,則使用這個實現(xiàn);這樣的好處就是,在v9.0.0上面是可以兼容編譯的,不會報重復(fù)定義的問題。但是如果去掉weak聲明,就會報錯誤。

4 經(jīng)驗總結(jié)

  • freeRTOS的版本不能亂升級,尤其系統(tǒng)跨度比較大的版本之間,嚴(yán)重情況下可能系統(tǒng)都跑不起來
  • do-while(0)似的宏定義不是萬能的,有些場景下也是會出錯的
  • C語言下大括號內(nèi)定義同名局部變量的問題的解決方法,值得借鑒
  • 宏定義轉(zhuǎn)內(nèi)聯(lián)函數(shù),看似一個最佳實踐,實則還是需要具體問題具體分析,否則會引入不必要的問題
  • 遇到問題,需要冷靜分析問題,解決一個問題還得看下關(guān)聯(lián)的問題有沒有影響
  • weak函數(shù)大有益處(下回寫文再細(xì)講)

5 更多分享

歡迎關(guān)注我的github倉庫01workstation,日常分享一些開發(fā)筆記和項目實戰(zhàn),歡迎指正問題。

同時也非常歡迎關(guān)注我的專欄:有問題的話,可以跟我討論,知無不答,謝謝大家。

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

    關(guān)注

    37

    文章

    7077

    瀏覽量

    124909
  • RTOS
    +關(guān)注

    關(guān)注

    24

    文章

    840

    瀏覽量

    120723
  • FreeRTOS
    +關(guān)注

    關(guān)注

    12

    文章

    487

    瀏覽量

    63714
收藏 人收藏
  • mzf5934659011

評論

相關(guān)推薦
熱點推薦

freeRTOS開發(fā)筆記】關(guān)注創(chuàng)建任務(wù)時傳入優(yōu)先級數(shù)值問題

freeRTOS開發(fā)筆記】關(guān)注創(chuàng)建任務(wù)時傳入的優(yōu)先級數(shù)值問題
的頭像 發(fā)表于 07-11 09:13 ?2994次閱讀
【<b class='flag-5'>freeRTOS</b><b class='flag-5'>開發(fā)筆記</b>】關(guān)注創(chuàng)建任務(wù)時傳入優(yōu)先級數(shù)值問題

安卓開發(fā)筆記

安卓開發(fā)筆記(中文)
發(fā)表于 04-26 10:57

基于STM32的USB程序開發(fā)筆記 匯總

忙了下午終于有時間整理了,基于STM32的USB程序開發(fā)筆記匯總,需要的親們點擊鏈接閱讀哈!{:4_95:}基于STM32的USB程序開發(fā)筆記)https://bbs.elecf
發(fā)表于 03-20 16:08

Modbus庫開發(fā)筆記之十一:關(guān)于Modbus協(xié)議棧開發(fā)的說明

們不就使用的最終結(jié)果負(fù)責(zé)。當(dāng)然如果發(fā)現(xiàn)任何的不足,我們非常并歡迎大家將發(fā)現(xiàn)的問題告知我們,以便我們持續(xù)的改進(jìn)之。本系列的全部分裝如下:Modbus庫開發(fā)筆記:實現(xiàn)功能的基本設(shè)計https
發(fā)表于 08-27 20:32

壇友經(jīng)驗分享之STM32的USB程序開發(fā)筆記

基于STM32的USB程序開發(fā)筆記)基于STM32的USB程序開發(fā)筆記(二)基于STM32的USB程序開發(fā)筆記(三)基于STM32的USB程序
發(fā)表于 09-04 17:42

基于STM32的USB程序開發(fā)筆記

基于STM32的USB程序開發(fā)筆記
發(fā)表于 04-24 09:23

一次網(wǎng)站設(shè)計稿的方法

一次網(wǎng)站設(shè)計稿
發(fā)表于 06-16 09:43

Odrive開發(fā)筆記 精選資料推薦

Odrive開發(fā)筆記文章目錄Odrive開發(fā)筆記接線配置進(jìn)入校準(zhǔn)測試用python來控制odrive電機控制介紹位置環(huán)速度環(huán)把從開始做odrive驅(qū)動無刷電機的所有過程都記錄下來接線1. 首先
發(fā)表于 09-02 07:33

求大佬分享CAN開發(fā)筆記

求大佬分享CAN開發(fā)筆記
發(fā)表于 02-07 06:16

lua開發(fā)筆記分享

lua開發(fā)筆記(1)單片機與luaPython與lua單片機與lua我第一次接觸lua是幾年前偶然發(fā)現(xiàn)了個單片機(MCU)的開源項目——NodeMCU。這個項目很有意思,他的目的是讓傳統(tǒng)程序員
發(fā)表于 02-08 06:12

基于STM32的USB程序開發(fā)筆記

基于STM32的USB程序開發(fā)筆記STM32 USB 源代碼及筆記下載.rar
發(fā)表于 10-09 06:05

STM32的USB程序開發(fā)筆記

STM32的USB程序開發(fā)筆記
發(fā)表于 09-29 14:55 ?27次下載
STM32的USB程序<b class='flag-5'>開發(fā)筆記</b>

基于LM3S網(wǎng)絡(luò)開發(fā)筆記3_多網(wǎng)頁開發(fā)

基于LM3S網(wǎng)絡(luò)開發(fā)筆記3_多網(wǎng)頁開發(fā)
發(fā)表于 10-11 08:52 ?4次下載
基于LM3S網(wǎng)絡(luò)<b class='flag-5'>開發(fā)筆記</b>3_多網(wǎng)頁<b class='flag-5'>開發(fā)</b>

基于LM3S網(wǎng)絡(luò)開發(fā)筆記1_開發(fā)平臺

基于LM3S網(wǎng)絡(luò)開發(fā)筆記1_開發(fā)平臺
發(fā)表于 10-11 08:57 ?4次下載
基于LM3S網(wǎng)絡(luò)<b class='flag-5'>開發(fā)筆記</b>1_<b class='flag-5'>開發(fā)</b>平臺

lua開發(fā)筆記(1)

lua開發(fā)筆記(1)單片機與luaPython與lua單片機與lua我第一次接觸lua是幾年前偶然發(fā)現(xiàn)了個單片機(MCU)的開源項目——NodeMCU。這個項目很有意思,他的目的是讓傳統(tǒng)程序員
發(fā)表于 12-05 11:51 ?8次下載
lua<b class='flag-5'>開發(fā)筆記</b>(1)

電子發(fā)燒友

中國電子工程師最喜歡的網(wǎng)站

  • 2931785位工程師會員交流學(xué)習(xí)
  • 獲取您個性化的科技前沿技術(shù)信息
  • 參加活動獲取豐厚的禮品