FreeRTOS學(xué)習(xí)記錄(轉(zhuǎn))

原文地址:FreeRTOS學(xué)習(xí)記錄-ESP32 - 能跑就行_NPJX - 博客園 (cnblogs.com)

筆記的目的是胖秒,借此學(xué)習(xí)一下ESP32麸折,且快速回顧一下FreeRTOS,在需要時蒂教,可以快速找到對應(yīng)的概念和API接口肾扰。

ESP32使用FreeRTOS與原生FreeRTOS應(yīng)用程序入口有所不同畴嘶,

在 ESP-IDF 中使用 FreeRTOS 的用戶 永遠(yuǎn)不應(yīng)調(diào)用 vTaskStartScheduler()vTaskEndScheduler()。相反集晚,ESP-IDF 會自動啟動 FreeRTOS窗悯。用戶必須定義一個 void app_main(void) 函數(shù)作為用戶應(yīng)用程序的入口點,并在 ESP-IDF 啟動時被自動調(diào)用偷拔。

  • 通常蒋院,用戶會從 app_main 中啟動應(yīng)用程序的其他任務(wù)。
  • app_main 函數(shù)可以在任何時候返回(應(yīng)用終止前)莲绰。
  • app_main 函數(shù)由 main 任務(wù)調(diào)用欺旧。

以下FreeRTOS筆記無特殊說明外,默認(rèn)為單核

任務(wù)

創(chuàng)建任務(wù)

  • 使用xTaskCreate()創(chuàng)建任務(wù)時,任務(wù)內(nèi)存動態(tài)分配。
  • 使用xTaskCreateStatic()創(chuàng)建任務(wù)時智听,任務(wù)內(nèi)存靜態(tài)分配筝蚕,即由用戶提供。

執(zhí)行任務(wù)

  • 只能處于以下任一狀態(tài):運行中、就緒、阻塞或掛起。
  • 任務(wù)函數(shù)通常為無限循環(huán)鲫尊。
  • 任務(wù)函數(shù)不應(yīng)返回。

刪除任務(wù)

  • 使用vTaskDelete()刪除任務(wù)躬厌,若任務(wù)句柄為NULL马昨,則會刪除當(dāng)前正在運行的任務(wù)
  • 注意事項:
    • 請保證刪除任務(wù)時,任務(wù)是處于已知的狀態(tài)
      • 比如任務(wù)內(nèi)部扛施,運行完成鸿捧,且釋放了任務(wù)內(nèi)分配的資源,再進(jìn)行刪除疙渣,不然會造成內(nèi)存泄露
      • 刪除持有互斥鎖的任務(wù)匙奴,會導(dǎo)致別的任務(wù)永久鎖死

打印任務(wù)信息

  • 使用vTaskList()羅列出所有任務(wù)的當(dāng)前狀態(tài),以及堆棧信息
  • 使用uxTaskGetStackHighWaterMark()獲取任務(wù)棧剩余空間妄荔,越接近0越代表接近溢出泼菌,可以通過這個值谍肤,監(jiān)測任務(wù)函數(shù)棧空間是否充足

空閑任務(wù)

  • ESP-IDF會隱式創(chuàng)建一個優(yōu)先級為 0 的空閑任務(wù)哗伯。當(dāng)沒有其他任務(wù)準(zhǔn)備運行時荒揣,空閑任務(wù)運行并有以下作用:

    • 釋放已刪除任務(wù)的內(nèi)存
    • 執(zhí)行應(yīng)用程序的空閑函數(shù)

任務(wù)看門狗定時器 (TWDT)

  • 任務(wù)看門狗定時器 (TWDT) 用于監(jiān)視特定任務(wù),確保任務(wù)在配置的超時時間內(nèi)執(zhí)行焊刹。

  • TWDT 主要監(jiān)視每個 CPU 的空閑任務(wù)

  • TWDT 是基于定時器組 0 中的硬件看門狗定時器構(gòu)建的系任。超時發(fā)生時會觸發(fā)中斷。

  • 可以在用戶代碼中定義函數(shù) esp_task_wdt_isr_user_handler 來接收超時事件虐块,并擴展默認(rèn)行為俩滥。

  • 調(diào)用以下函數(shù),用 TWDT 監(jiān)視任務(wù):

    • esp_task_wdt_init()初始化 TWDT 并訂閱空閑任務(wù)贺奠。
    • esp_task_wdt_add()為其他任務(wù)訂閱 TWDT霜旧。
    • 訂閱后,應(yīng)從任務(wù)中調(diào)用esp_task_wdt_reset()來喂 TWDT儡率。
    • esp_task_wdt_delete()可以取消之前訂閱的任務(wù)挂据。
    • esp_task_wdt_deinit取消訂閱空閑任務(wù)并反初始化 TWDT。
  • 注意事項:

    • 擦除較大的 flash 區(qū)域可能會非常耗時喉悴,并可能導(dǎo)致任務(wù)連續(xù)運行棱貌,觸發(fā) TWDT 超時。以下兩種方法可以避免這種情況:

      • 延長看門狗超時時間箕肃。
      • 在擦除 flash 區(qū)域前,調(diào)用esp_task_wdt_init()今魔,增加看門狗超時時間勺像。

消息隊列

消息隊列就是通過 RTOS 內(nèi)核提供的服務(wù),任務(wù)或中斷服務(wù)子程序可以將一個消息(注意错森,F(xiàn)reeRTOS 消息隊列傳遞的是實際數(shù)據(jù)吟宦,并不是數(shù)據(jù)地址,RTX涩维,uCOS-II 和 uCOS-III 是傳遞的地址)放入到隊列殃姓。

同樣,一個或者多個任務(wù)可以通過 RTOS 內(nèi)核服務(wù)從隊列中得到消息瓦阐。通常蜗侈,先進(jìn)入消息隊列的消息先傳 給任務(wù),也就是說睡蟋,任務(wù)先得到的是最先進(jìn)入到消息隊列的消息踏幻,即先進(jìn)先出的原則(FIFO),F(xiàn)reeRTOS 的消息隊列支持 FIFO 和 LIFO 兩種數(shù)據(jù)存取方式戳杀。

  • 消息隊列和全局變量相比该面,在FreeRTOS里更具以下優(yōu)勢:
*   使用消息隊列可以讓 RTOS 更有效管理任務(wù)夭苗,而全局變量是無法做到
    
    
    1.  比如,任務(wù)的超時等待機制隔缀,用全局變量則需要用戶自己去實現(xiàn)题造。
*   消息隊列支持FIFO,更有利于數(shù)據(jù)處理
    
    
*   使用全局?jǐn)?shù)組猾瘸,還需要處理多任務(wù)的訪問沖突界赔,而消息隊列就處理好了這個問題
    
    
*   消息隊列可以有效解決中斷與任務(wù)之間通信問題
  • 使用消息隊列傳輸數(shù)據(jù)時有兩種方法:
1.  拷貝:把數(shù)據(jù)、把變量的值復(fù)制進(jìn)隊列里
2.  引用:把數(shù)據(jù)须妻、把變量的地址復(fù)制進(jìn)隊列里

消息隊列--任務(wù)之間通信

image.png

消息隊列--中斷與任務(wù)之間通信

image.png
  • 注意

    • 在中斷發(fā)送消息需要使用 xQueueSendFromISR仔蝌,且不支持超時設(shè)置,所以發(fā)送前要通過函數(shù) xQueueIsQueueFullFromISR 檢測 消息隊列是否滿
    • 在中斷中處理越快越好荒吏,防止低于該優(yōu)先級的異常無法正常響應(yīng)
    • 最好不要在中斷中處理消息隊列敛惊,只發(fā)送
    • 中斷服務(wù)程序中一定要調(diào)用專用于中斷的消息隊列函數(shù),即以 FromISR 結(jié)尾的函數(shù)绰更。

創(chuàng)建消息隊列

  • 動態(tài)分配
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,  /* 消息個數(shù) */
                            UBaseType_t uxItemSize );   /* 每個消息大小瞧挤,單位字節(jié) */
  • 靜態(tài)分配(一般不用這個)
QueueHandle_t xQueueCreateStatic( UBaseType_t uxQueueLength,    /* 消息個數(shù) */
                                  UBaseType_t uxItemSize,       /* 每個消息大小,單位字節(jié) */
                                  uint8_t *pucQueueStorageBuffer, /* 如果uxItemSize非0儡湾,pucQueueStorageBuffer必須指向一個uint8_t數(shù)組特恬,此數(shù)組大小至少為"uxQueueLength * uxItemSize" */
                                  StaticQueue_t *pxQueueBuffer ); /* 必須執(zhí)行一個StaticQueue_t結(jié)構(gòu)體,用來保存隊列的數(shù)據(jù)結(jié)構(gòu) */
 

寫消息隊列

/*
 *  等同于xQueueSendToBack徐钠,往隊列尾部寫入數(shù)據(jù)癌刽,如果沒有空間,阻塞時間為xTicksToWait
 */
BaseType_t xQueueSend( QueueHandle_t xQueue,        /* 消息隊列句柄 */
                       const void * pvItemToQueue,  /* 要傳遞數(shù)據(jù)地址 */
                       TickType_t xTicksToWait );   /* 等待消息隊列有空間的最大等待時間 */
 
/* 
 * 往隊列尾部寫入數(shù)據(jù)尝丐,如果沒有空間显拜,阻塞時間為xTicksToWait
 */
BaseType_t xQueueSendToBack(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );
/* 
 * 往隊列尾部寫入數(shù)據(jù),此函數(shù)可以在中斷函數(shù)中使用爹袁,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );
 
/* 
 * 往隊列頭部寫入數(shù)據(jù)远荠,如果沒有空間,阻塞時間為xTicksToWait
 */
BaseType_t xQueueSendToFront(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );
 
 
/* 
 * 往隊列頭部寫入數(shù)據(jù)失息,此函數(shù)可以在中斷函數(shù)中使用譬淳,不可阻塞
 */
BaseType_t xQueueSendToFrontFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

讀消息隊列

/* 讀到一個數(shù)據(jù)后,隊列中該數(shù)據(jù)會被移除 */
 
BaseType_t xQueueReceive( QueueHandle_t xQueue,     /* 消息隊列句柄 */
                          void * const pvBuffer,    /* bufer指針盹兢,隊列的數(shù)據(jù)會被復(fù)制到這個buffer -*+*/
                          TickType_t xTicksToWait );/* 等待消息隊列有空間的最大等待時間 */
 
BaseType_t xQueueReceiveFromISR( QueueHandle_t    xQueue,
                                 void             *pvBuffer,
                                 BaseType_t       *pxTaskWoken );

刪除消息隊列

/* vQueueDelete()只能刪除使用動態(tài)方法創(chuàng)建的隊列邻梆,它會釋放內(nèi)存 */
void vQueueDelete( QueueHandle_t xQueue );

查詢消息隊列

/*
 * 返回隊列中可用數(shù)據(jù)的個數(shù)
 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
 
/*
 * 返回隊列中可用空間的個數(shù)
 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

覆蓋/窺視

  • 覆蓋

    當(dāng)隊列長度為1時(隊列長度必須為1才可以使用),可以使用xQueueOverwrite()xQueueOverwriteFromISR()來覆蓋數(shù)據(jù)

    在FreeRTOS中蛤迎,隊列的發(fā)送和接收操作是原子的确虱,也就是說,在執(zhí)行這些操作期間替裆,不會被中斷或其他任務(wù)打斷校辩。這確保了數(shù)據(jù)的完整性和可靠性窘问。

    而覆蓋操作是一種特殊情況,它允許在隊列滿時替換隊列中最早的消息宜咒,并添加新的消息惠赫。

    為了保持原子性,只有在隊列長度為1時故黑,才能保證覆蓋操作的一致性儿咱。

    如果隊列長度大于1,那么在進(jìn)行覆蓋操作時场晶,可能會涉及多個元素的移動和替換混埠。 由于隊列操作必須是原子的,這將涉及更復(fù)雜的同步和保護(hù)機制诗轻,以確保數(shù)據(jù)的一致性钳宪。這不僅增加了實現(xiàn)的復(fù)雜度,還可能引入競爭條件和死鎖等問題扳炬。

/* 覆蓋隊列
 * xQueue: 寫哪個隊列
 * pvItemToQueue: 數(shù)據(jù)地址
 * 返回值: pdTRUE表示成功, pdFALSE表示失敗
 */
BaseType_t xQueueOverwrite(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue
                      );
 
BaseType_t xQueueOverwriteFromISR(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue,
                           BaseType_t *pxHigherPriorityTaskWoken
                      );
  • 窺視

    想讓隊列中的數(shù)據(jù)供多方讀取吏颖,也就是說讀取時不要移除數(shù)據(jù),要留給后來人恨樟。那么可以使用"窺視"半醉,也就是xQueuePeek()xQueuePeekFromISR()。這些函數(shù)會從隊列中復(fù)制出數(shù)據(jù)劝术,但是不移除數(shù)據(jù)缩多。

    這也意味著,如果隊列中沒有數(shù)據(jù)养晋,那么"偷看"時會導(dǎo)致阻塞瞧壮;一旦隊列中有數(shù)據(jù),以后每次"偷看"都會成功匙握。

/* 偷看隊列
* xQueue: 偷看哪個隊列
* pvItemToQueue: 數(shù)據(jù)地址, 用來保存復(fù)制出來的數(shù)據(jù)
* xTicksToWait: 沒有數(shù)據(jù)的話阻塞一會
* 返回值: pdTRUE表示成功, pdFALSE表示失敗
*/
BaseType_t xQueuePeek(
                        QueueHandle_t xQueue,
                        void * const pvBuffer,
                        TickType_t xTicksToWait
                    );

BaseType_t xQueuePeekFromISR(
                               QueueHandle_t xQueue,
                               void *pvBuffer,
                           );

隊列集合

知道有這個即可,實際很少用到陈轿!

隊列郵箱

郵箱的概念圈纺,其實就是,長度為1的消息隊列麦射,使用覆蓋函數(shù)蛾娶,之所以是覆蓋函數(shù),就是不管隊列有沒有值潜秋,都能及時更新蛔琅,不會陷入阻塞的情況,發(fā)送任務(wù)發(fā)送數(shù)據(jù)后峻呛,隊列就成了郵箱罗售,其他任務(wù)都在”訂閱“郵箱辜窑,使用窺視函數(shù)去,只獲取值寨躁,不刪除數(shù)據(jù)穆碎,達(dá)到一個發(fā)送,多個接收职恳。

這個也是知道概念即可所禀,具體用到的函數(shù)也是上述幾個。

具體流程如下


image.png

使用注意

  1. 分辨數(shù)據(jù)源
    1. 當(dāng)有多個發(fā)送任務(wù)放钦,通過同一個隊列發(fā)出數(shù)據(jù)色徘,接收任務(wù)如何分辨數(shù)據(jù)來源?數(shù)據(jù)本身帶有"來源"信息操禀,比如寫入隊列的數(shù)據(jù)是一個結(jié)構(gòu)體褂策,結(jié)構(gòu)體中的lDataSouceID用來表示數(shù)據(jù)來源:
typedef struct {
    ID_t eDataID;
    int32_t lDataValue;
}Data_t;
/* 不同的發(fā)送任務(wù),先構(gòu)造好結(jié)構(gòu)體床蜘,填入自己的eDataID辙培,再寫隊列;接收任務(wù)讀出數(shù)據(jù)后邢锯,根據(jù)eDataID就可以知道數(shù)據(jù)來源了 */
  1. 傳輸大塊數(shù)據(jù)
    1. 因為FreeRTOS的隊列使用拷貝傳輸扬蕊,如果是用uint32_t類型,那就拷貝4字節(jié)丹擎,如果用uint8_t類型尾抑,那就拷貝1字節(jié),但是如果要拷貝很大的數(shù)據(jù)蒂培,比如uint8_t data[1000]再愈,寫隊列的時候直接拷貝1000個字節(jié)嗎?讀隊列連續(xù)讀1000個字節(jié)护戳?翎冲?那樣效率未免也太低了吧。更為合適的方法是 使用地址來間接傳數(shù)據(jù)
/* 比如 send_task 里面 malloc(1000)字節(jié)媳荒,把地址通過隊列寫進(jìn)去
    在 recive_task 里面 free() 釋放空間抗悍,使用時要注意,成對出現(xiàn)
 
    不要未使用就釋放內(nèi)存钳枕,導(dǎo)致野指針
    也不要忘記釋放內(nèi)存缴渊,導(dǎo)致內(nèi)存泄漏
 */
void send_task(void *arg)
{
    QueueHandle_t QueueHandle1 = (QueueHandle_t)arg;
    BaseType_t status;
 
    while(1) {
        char *pStrSend = malloc(1000);  //分配內(nèi)存鱼炒,并拷貝數(shù)據(jù)(我忽略了這步)
        status = xQueueSend(QueueHandle1, &pStrSend , 5000);
        if (status == pdPASS) {
            printf("send success\n");
        } else {
            printf("send fail\n");
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}
 
void recive_task(void *arg)
{
    QueueHandle_t QueueHandle1 = (QueueHandle_t)arg;
    BaseType_t status;
    char *pStrRecive;
 
    while(1) {
        status = xQueueReceive(QueueHandle1, &pStrRecive, 0);
        if (status == pdPASS) {
            // 在這里處理數(shù)據(jù),隨后并釋放空間
            free(pStrRecive);
            printf("pStrRecive:%s\n\n", pStrRecive);
        } else {
            printf("recive fail\n");
        }
        
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

信號量

  • 信號:起通知作用
  • 量:還可以用來表示資源的數(shù)量
    • 當(dāng)"量"沒有限制時指蚁,它就是"計數(shù)型信號量"(Counting Semaphores)
    • 當(dāng)"量"只有0菩佑、1兩個取值時擎鸠,它就是"二進(jìn)制信號量"(Binary Semaphores)
  • 支持的動作都一樣,give--給出資源缘圈,take--拿走資源,計數(shù)值減1

二進(jìn)制信號量跟計數(shù)型的唯一差別绢涡,就是計數(shù)值的最大值被限定為1雄可。

信號量的"give"数苫、"take"雙方并不需要相同虐急,可以用于生產(chǎn)者-消費者場合:即多個任務(wù)產(chǎn)生信號量止吁,多個任務(wù)消費信號量

計數(shù)型信號量

/* 創(chuàng)建一個計數(shù)型信號量敬惦,返回它的句柄俄删。
 * 此函數(shù)內(nèi)部會分配信號量結(jié)構(gòu)體 
 * uxMaxCount: 最大計數(shù)值
 * uxInitialCount: 初始計數(shù)值
 * 返回值: 返回句柄奏路,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
 
/* 創(chuàng)建一個計數(shù)型信號量,返回它的句柄。
 * 此函數(shù)無需動態(tài)分配內(nèi)存潜叛,所以需要先有一個StaticSemaphore_t結(jié)構(gòu)體威兜,并傳入它的指針
 * uxMaxCount: 最大計數(shù)值
 * uxInitialCount: 初始計數(shù)值
 * pxSemaphoreBuffer: StaticSemaphore_t結(jié)構(gòu)體指針
 * 返回值: 返回句柄椒舵,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, 
                                                 UBaseType_t uxInitialCount, 
                                                 StaticSemaphore_t *pxSemaphoreBuffer );

二進(jìn)制信號量

信號量在創(chuàng)建后是空的狀態(tài)笔宿,在調(diào)用take獲取之前泼橘,需要先give釋放一個資源出來

/* 創(chuàng)建一個二進(jìn)制信號量炬灭,返回它的句柄重归。
 * 此函數(shù)內(nèi)部會分配信號量結(jié)構(gòu)體 
 * 返回值: 返回句柄育苟,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );
 
/* 創(chuàng)建一個二進(jìn)制信號量宙搬,返回它的句柄勇垛。
 * 此函數(shù)無需動態(tài)分配內(nèi)存闲孤,所以需要先有一個StaticSemaphore_t結(jié)構(gòu)體讼积,并傳入它的指針
 * 返回值: 返回句柄勤众,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

give/take 信號量

  • 關(guān)于give的函數(shù)
/* 在任務(wù)中使用吕朵,釋放信號量
 * xSemaphore:信號量句柄
 * 返回值: pdTRUE表示成功,
 *          如果二進(jìn)制信號量的計數(shù)值已經(jīng)是1努溃,再次調(diào)用此函數(shù)則返回失斘嗨啊第队;
 *          如果計數(shù)型信號量的計數(shù)值已經(jīng)是最大值,再次調(diào)用此函數(shù)則返回失敗
 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
 
/* 在ISR中使用晾蜘,釋放信號量
 * xSemaphore:信號量句柄
 * pxHigherPriorityTaskWoken:如果釋放信號量導(dǎo)致更高優(yōu)先級的任務(wù)變?yōu)榱司途w態(tài)剔交,
 *                            則*pxHigherPriorityTaskWoken = pdTRUE
 * 返回值: pdTRUE表示成功,
 *          如果二進(jìn)制信號量的計數(shù)值已經(jīng)是1岖常,再次調(diào)用此函數(shù)則返回失斀甙啊;
 *          如果計數(shù)型信號量的計數(shù)值已經(jīng)是最大值晒夹,再次調(diào)用此函數(shù)則返回失敗
 */
BaseType_t xSemaphoreGiveFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );
 
  • 關(guān)于take的函數(shù)
/* 在任務(wù)中使用丐怯,獲取信號量
 * xSemaphore:信號量句柄
 * xTicksToWait:阻塞時間读跷,0:不阻塞些膨,馬上返回, portMAX_DELAY: 一直阻塞直到成功矛洞,
 *               其他值: 阻塞的Tick個數(shù)沼本,可以使用pdMS_TO_TICKS()來指定阻塞時間為若干ms
 * 返回值: pdTRUE表示成功
 */
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );
 
 
/* 在ISR中使用,獲取信號量
 * xSemaphore:信號量句柄
 * pxHigherPriorityTaskWoken:如果獲取信號量導(dǎo)致更高優(yōu)先級的任務(wù)變?yōu)榱司途w態(tài)辫红,
 *                            則*pxHigherPriorityTaskWoken = pdTRUE
 * 返回值: pdTRUE表示成功,
 */
BaseType_t xSemaphoreTakeFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );

獲取信號量數(shù)量

/* * xSemaphore: 信號量句柄 * 返回值:   信號量個數(shù) */uxSemaphoreGetCount( xSemaphore );

刪除信號量

對于動態(tài)創(chuàng)建的信號量,不再需要它們時名惩,可以刪除它們以回收內(nèi)存娩鹉。
vSemaphoreDelete可以用來刪除二進(jìn)制信號量弯予、計數(shù)型信號量。

/*
 * xSemaphore: 信號量句柄祠挫,你要刪除哪個信號量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

互斥量

互斥量的用途:用來實現(xiàn)互斥訪問

值只有 0 和 1

資源互斥的核心在于:誰上鎖等舔,就只能由誰開鎖 (代碼上又并沒有實現(xiàn)這點甚牲,只是約定成俗丈钙,誰上鎖誰開鎖)

提及兩個概念:

  • 對變量的非原子化訪問

    修改變量雏赦、設(shè)置結(jié)構(gòu)體、在16位的機器上寫32位的變量俏橘,這些操作都是非原子的寥掐。也就是它們的操作過程都可能被打斷,如果被打斷的過程有其他任務(wù)來操作這些變量怎茫,就可能導(dǎo)致沖突轨蛤。

  • 函數(shù)重入

    “可重入的函數(shù)"是指:多個任務(wù)同時調(diào)用它、任務(wù)和中斷同時調(diào)用它缝呕,函數(shù)的運行也是安全的供常。可重入的函數(shù)也被稱為"線程安全”(thread safe)源祈。 每個任務(wù)都維持自己的棧香缺、自己的CPU寄存器原献,如果一個函數(shù)只使用局部變量,那么它就是線程安全的倔撞。 函數(shù)中一旦使用了全局變量、靜態(tài)變量躏啰、其他外設(shè)给僵,它就不是"可重入的",如果改函數(shù)正在被調(diào)用蹲诀,就必須阻止其他任務(wù)、中斷再次調(diào)用它痕慢。

    任務(wù)A訪問這些全局變量守屉、函數(shù)代碼時滨巴,獨占它恭取,就是上個鎖。這些全局變量攒发、函數(shù)代碼必須被獨占地使用惠猿,它們被稱為臨界資源。

互斥量有以下特點需要注意:

  • 剛創(chuàng)建的互斥量可以被成功"take"
  • “take"互斥量成功的任務(wù)趾访,被稱為"holder”,只能由它"give"互斥量藏鹊;別的任務(wù)"give"不成功
  • 在ISR中不能使用互斥量
  • 互斥量會去“繼承”,企圖獲取互斥量的任務(wù)的優(yōu)先級

創(chuàng)建互斥量

互斥量初始值為1

/* 創(chuàng)建一個互斥量竿痰,返回它的句柄。
 * 此函數(shù)內(nèi)部會分配互斥量結(jié)構(gòu)體 
 * 返回值: 返回句柄蟹倾,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );
 
/* 創(chuàng)建一個互斥量肌厨,返回它的句柄柑爸。
 * 此函數(shù)無需動態(tài)分配內(nèi)存,所以需要先有一個StaticSemaphore_t結(jié)構(gòu)體譬圣,并傳入它的指針
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

刪除互斥量

/*
 * xSemaphore: 信號量句柄,你要刪除哪個信號量, 互斥量也是一種信號量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

獲取、釋放互斥量

/* 釋放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
 
/* 釋放(ISR版本) */
BaseType_t xSemaphoreGiveFromISR(
                       SemaphoreHandle_t xSemaphore,
                       BaseType_t *pxHigherPriorityTaskWoken
                   );
 
/* 獲得 */
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );
/* 獲得(ISR版本) */
xSemaphoreGiveFromISR(
                       SemaphoreHandle_t xSemaphore,
                       BaseType_t *pxHigherPriorityTaskWoken
                   );

死鎖

死鎖又分為兩種情況:

  • 互斥死鎖

    • 假設(shè)有2個互斥量M1、M2份招,2個任務(wù)A、B:

      • A獲得了互斥量M1
      • B獲得了互斥量M2
      • A還要獲得互斥量M2才能運行谐腰,結(jié)果A阻塞
      • B還要獲得互斥量M1才能運行,結(jié)果B阻塞
      • A砸西、B都阻塞衅疙,再無法釋放它們持有的互斥量
      • 死鎖發(fā)生炼蛤!
  • 自我死鎖

    • 任務(wù)A獲得了互斥鎖M
    • 它調(diào)用一個庫函數(shù)
    • 庫函數(shù)要去獲取同一個互斥鎖M,于是它阻塞:任務(wù)A休眠,等待任務(wù)A來釋放互斥鎖兽愤!
    • 死鎖發(fā)生浅萧!

為了解決上訴死鎖問題,又衍生出一種 遞歸互斥量

遞歸互斥量

遞歸互斥量實現(xiàn)了:誰上鎖就由誰解鎖帝簇。

遞歸鎖 一般互斥量x
創(chuàng)建 xSemaphoreCreateRecursiveMutex xSemaphoreCreateMutex
獲得 xSemaphoreTakeRecursive xSemaphoreTake
釋放 xSemaphoreGiveRecursive xSemaphoreGive

假設(shè)任務(wù)1 需要去對資源A訪問,并且資源A需要對資源B進(jìn)行訪問,如果每次訪問都加一個普通互斥量途样,那這對代碼維護(hù)也十分麻煩,就有了遞歸互斥量


image.png

事件標(biāo)志組

事件標(biāo)志組是實現(xiàn)多任務(wù)同步的有效機制之一裆站。

事件標(biāo)志組和全局變量相比,在FreeRTOS里更具以下優(yōu)勢:

  1. 使用事件標(biāo)志組可以讓 RTOS 內(nèi)核有效地管理任務(wù),而全局變量是無法做到
    1. 比如宏胯,任務(wù)的超時等機制羽嫡, 用全局變量則需要用戶自己去實現(xiàn)。
  2. 使用了全局變量就要防止多任務(wù)的訪問沖突肩袍,而使用事件標(biāo)志組則處理好了這個問題。
  1. 使用事件標(biāo)志組可以有效地解決中斷服務(wù)程序和任務(wù)之間的同步問題氛赐。
    TickType_t 數(shù)據(jù)類型可以是 16 位數(shù)或者 32 位數(shù)

創(chuàng)建事件標(biāo)志組

/* 創(chuàng)建一個事件組魂爪,返回它的句柄。
 * 此函數(shù)內(nèi)部會分配事件組結(jié)構(gòu)體 
 * 返回值: 返回句柄艰管,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreate( void );
 
/* 創(chuàng)建一個事件組滓侍,返回它的句柄。
 * 此函數(shù)無需動態(tài)分配內(nèi)存牲芋,所以需要先有一個StaticEventGroup_t結(jié)構(gòu)體撩笆,并傳入它的指針
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );

刪除事件組

/*
 * xEventGroup: 事件組句柄缸浦,你要刪除哪個事件組
 */
void vEventGroupDelete( EventGroupHandle_t xEventGroup )

等待事件標(biāo)志位

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
                                 const EventBits_t uxBitsToWaitFor,
                                 const BaseType_t xClearOnExit,
                                 const BaseType_t xWaitForAllBits,
                                 TickType_t xTicksToWait );

設(shè)置事件標(biāo)志位

  • 使用xEventGroupSetBits()夕冲,不可以在中斷服務(wù)程序中調(diào)用此函數(shù)

    • 返回當(dāng)前的事件標(biāo)志組數(shù)值

    • 用戶通過函數(shù)設(shè)置的標(biāo)志位,并不一定會保留到此函數(shù)的返回值中裂逐,下面舉兩種情況:

      1. 調(diào)用此函數(shù)的過程中耘擂,其它高優(yōu)先級的任務(wù)就緒了,并且也修改了事件標(biāo)志絮姆,此函數(shù)返回的事件 標(biāo)志位會發(fā)生變化。
      2. 調(diào)用此函數(shù)的任務(wù)是一個低優(yōu)先級任務(wù)秩霍,通過此函數(shù)設(shè)置了事件標(biāo)志后篙悯,讓一個等待此事件標(biāo)志的高優(yōu)先級任務(wù)就緒了,會立即切換到高優(yōu)先級任務(wù)去執(zhí)行铃绒,相應(yīng)的事件標(biāo)志位會被函數(shù) xEventGroupWaitBits 清除掉鸽照,等從高優(yōu)先級任務(wù)返回到低優(yōu)先級任務(wù)后,函數(shù) xEventGroupSetBits 的返回值已經(jīng)被修改颠悬。
/* 設(shè)置事件組中的位
 * xEventGroup: 哪個事件組
 * uxBitsToSet: 設(shè)置哪些位? 
 *              如果uxBitsToSet的bitX, bitY為1, 那么事件組中的bitX, bitY被設(shè)置為1
 *               可以用來設(shè)置多個位矮燎,比如 0x15 就表示設(shè)置bit4, bit2, bit0
 * 返回值: 返回原來的事件值(沒什么意義, 因為很可能已經(jīng)被其他任務(wù)修改了)
 */
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
                                    const EventBits_t uxBitsToSet );
 
 
/* 設(shè)置事件組中的位
 * xEventGroup: 哪個事件組
 * uxBitsToSet: 設(shè)置哪些位? 
 *              如果uxBitsToSet的bitX, bitY為1, 那么事件組中的bitX, bitY被設(shè)置為1
 *               可以用來設(shè)置多個位,比如 0x15 就表示設(shè)置bit4, bit2, bit0
 * pxHigherPriorityTaskWoken: 有沒有導(dǎo)致更高優(yōu)先級的任務(wù)進(jìn)入就緒態(tài)? pdTRUE-有, pdFALSE-沒有
 * 返回值: pdPASS-成功, pdFALSE-失敗
 */
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
                                      const EventBits_t uxBitsToSet,
                                      BaseType_t * pxHigherPriorityTaskWoken );

中斷中設(shè)置事件標(biāo)志位

  • 使用xEventGroupSetBitsFromISR()赔癌,在中斷中使用的是這個
  • 設(shè)置事件組時诞外,有可能導(dǎo)致多個任務(wù)被喚醒,這會帶來很大的不確定性灾票。所以xEventGroupSetBitsFromISR函數(shù)不是直接去設(shè)置事件組峡谊,而是給一個FreeRTOS后臺任務(wù)(daemon task)發(fā)送隊列數(shù)據(jù),由這個任務(wù)來設(shè)置事件組。

事件組同步

有一個事情需要多個任務(wù)協(xié)同既们,使用xEventGroupSync()函數(shù)可以同步多個任務(wù):

  • 可以設(shè)置某位濒析、某些位,表示自己做了什么事
  • 可以等待某位啥纸、某些位忧设,表示要等等其他任務(wù)
  • 期望的時間發(fā)生后,xEventGroupSync()才會成功返回纵苛。
  • xEventGroupSync成功返回后劳坑,會清除事件
EventBits_t xEventGroupSync(    EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet,
                                const EventBits_t uxBitsToWaitFor,
                                TickType_t xTicksToWait );

任務(wù)通知

任務(wù)通知,簡單概括名船,就是具體通知到哪個任務(wù)去運行

我們使用隊列绰上、信號量、事件組等等方法時渠驼,并不知道對方是誰蜈块。使用任務(wù)通知時,可以明確指定:通知哪個任務(wù)迷扇。

使用隊列百揭、信號量、事件組時蜓席,我們都要事先創(chuàng)建對應(yīng)的結(jié)構(gòu)體器一,雙方通過中間的結(jié)構(gòu)體通信:


image.png

使用任務(wù)通知時,任務(wù)結(jié)構(gòu)體TCB中就包含了內(nèi)部對象厨内,可以直接接收別人發(fā)過來的"通知":


image.png

任務(wù)通知的特性

  • 優(yōu)勢
    • 效率更高:使用任務(wù)通知來發(fā)送事件祈秕、數(shù)據(jù)給某個任務(wù)時,效率更高雏胃。比隊列请毛、信號量、事件組都有大的優(yōu)勢瞭亮。
    • 更節(jié)省內(nèi)存:使用其他方法時都要先創(chuàng)建對應(yīng)的結(jié)構(gòu)體方仿,使用任務(wù)通知時無需額外創(chuàng)建結(jié)構(gòu)體。
  • 限制
    • 不能發(fā)送數(shù)據(jù)給ISR: ISR并沒有任務(wù)結(jié)構(gòu)體统翩,所以無法使用任務(wù)通知的功能給ISR發(fā)送數(shù)據(jù)仙蚜。但是ISR可以使用任務(wù)通知的功能,發(fā)數(shù)據(jù)給任務(wù)厂汗。
    • 數(shù)據(jù)只能給該任務(wù)獨享
      使用隊列委粉、信號量、事件組時娶桦,數(shù)據(jù)保存在這些結(jié)構(gòu)體中艳丛,其他任務(wù)匣掸、ISR都可以訪問這些數(shù)據(jù)。使用任務(wù)通知時氮双,數(shù)據(jù)存放入目標(biāo)任務(wù)中碰酝,只有它可以訪問這些數(shù)據(jù)。 在日常工作中戴差,這個限制影響不大送爸。因為很多場合是從多個數(shù)據(jù)源把數(shù)據(jù)發(fā)給某個任務(wù),而不是把一個數(shù)據(jù)源的數(shù)據(jù)發(fā)給多個任務(wù)暖释。
    • 無法緩沖數(shù)據(jù)
      使用隊列時袭厂,假設(shè)隊列深度為N,那么它可以保持N個數(shù)據(jù)球匕。 使用任務(wù)通知時纹磺,任務(wù)結(jié)構(gòu)體中只有一個任務(wù)通知值,只能保持一個數(shù)據(jù)亮曹。 無法廣播給多個任務(wù) 使用事件組可以同時給多個任務(wù)發(fā)送事件橄杨。 使用任務(wù)通知,只能發(fā)個一個任務(wù)照卦。 如果發(fā)送受阻式矫,發(fā)送方無法進(jìn)入阻塞狀態(tài)等待 假設(shè)隊列已經(jīng)滿了,使用xQueueSendToBack()給隊列發(fā)送數(shù)據(jù)時役耕,任務(wù)可以進(jìn)入阻塞狀態(tài)等待發(fā)送完成采转。 使用任務(wù)通知時,即使對方無法接收數(shù)據(jù)瞬痘,發(fā)送方也無法阻塞等待故慈,只能即刻返回錯誤。

通知狀態(tài)和通知值

每個任務(wù)都有一個結(jié)構(gòu)體:TCB(Task Control Block)框全,里面有2個成員:

  • 一個是uint8_t類型察绷,用來表示通知狀態(tài)
  • 一個是uint32_t類型,用來表示通知值
typedef struct tskTaskControlBlock
{
    ......
    /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
    volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    ......
} tskTCB;

通知狀態(tài)有3種取值

/* 任務(wù)沒有在等待通知竣况,也是初始狀態(tài) */
#define taskNOT_WAITING_NOTIFICATION              ( ( uint8_t ) 0 )
/* 任務(wù)在等待通知 */
#define taskWAITING_NOTIFICATION                  ( ( uint8_t ) 1 )
/* 任務(wù)接收到了通知,也被稱為pending(有數(shù)據(jù)了筒严,待處理) */
#define taskNOTIFICATION_RECEIVED                 ( ( uint8_t ) 2 )

任務(wù)通知的使用

使用任務(wù)通知丹泉,可以實現(xiàn)輕量級的隊列(長度為1)、郵箱(覆蓋的隊列)鸭蛙、計數(shù)型信號量摹恨、二進(jìn)制信號量、事件組娶视。

任務(wù)通知有2套函數(shù)晒哄,簡化版睁宰、專業(yè)版,列表如下:

簡化版函數(shù)的使用比較簡單寝凌,它實際上也是使用專業(yè)版函數(shù)實現(xiàn)的
專業(yè)版函數(shù)支持很多參數(shù)柒傻,可以實現(xiàn)很多功能

簡化版 專業(yè)版
發(fā)出通知 xTaskNotifyGive vTaskNotifyGiveFromISR xTaskNotify xTaskNotifyFromISR
取出通知 ulTaskNotifyTake xTaskNotifyWait

具體這塊內(nèi)容,使用時再找demo看即可较木,大概了解使用就好红符,一般較少使用

軟件定時器

  • 軟件定時器分為兩種狀態(tài):
    • 運行(Running、Active):運行態(tài)的定時器伐债,當(dāng)指定時間到達(dá)之后预侯,它的回調(diào)函數(shù)會被調(diào)用
    • 冬眠(Dormant):冬眠態(tài)的定時器還可以通過句柄來訪問它,但是它不再運行峰锁,它的回調(diào)函數(shù)不會被調(diào)用
  • 軟件定時器工作原理:
    • 首先萎馅,F(xiàn)reeRTOS有個Tick中斷,軟件定時器是基于Tick運行虹蒋,按照非操作系統(tǒng)的理解糜芳,我們是在Tick中斷里計數(shù),達(dá)到值就調(diào)用定時器回調(diào)千诬,但是在RTOS里耍目,它不允許在內(nèi)核、在中斷中執(zhí)行不確定的代碼:如果定時器函數(shù)很耗時徐绑,會影響整個系統(tǒng)邪驮。

      所以,F(xiàn)reeRTOS中傲茄,不在Tick中斷中執(zhí)行定時器函數(shù)毅访。

    • 在FreeRTOS中,有個RTOS守護(hù)任務(wù)(RTOS Daemon Task)盘榨,該任務(wù)跟普通任務(wù)基本一樣喻粹,不過守護(hù)任務(wù)的流程只有

      • 處理命令:從命令隊列里取出命令、處理
      • 執(zhí)行定時器的回調(diào)函數(shù)
        <mark style="margin: 0px; padding: 0px;">定時器的回調(diào)函數(shù)是在守護(hù)任務(wù)中被調(diào)用的草巡,守護(hù)任務(wù)不是專為某個定時器服務(wù)的守呜,它還要處理其他定時器</mark>。注意如下:
      • 回調(diào)函數(shù)要盡快實行山憨,不能進(jìn)入阻塞狀態(tài)
      • 不要調(diào)用會導(dǎo)致阻塞的API函數(shù)查乒,比如vTaskDelay()
      • 可以調(diào)用xQueueReceive()之類的函數(shù),但是超時時間要設(shè)為0:即刻返回郁竟,不可阻塞

創(chuàng)建軟件定時器

/* 使用動態(tài)分配內(nèi)存的方法創(chuàng)建定時器
 * pcTimerName:定時器名字, 用處不大, 盡在調(diào)試時用到
 * xTimerPeriodInTicks: 周期, 以Tick為單位
 * uxAutoReload: 類型, pdTRUE表示自動加載, pdFALSE表示一次性
 * pvTimerID: 回調(diào)函數(shù)可以使用此參數(shù), 比如分辨是哪個定時器
 * pxCallbackFunction: 回調(diào)函數(shù)
 * 返回值: 成功則返回TimerHandle_t, 否則返回NULL
 */
TimerHandle_t xTimerCreate( const char * const pcTimerName, 
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction );
 
/* 使用靜態(tài)分配內(nèi)存的方法創(chuàng)建定時器
 * pcTimerName:定時器名字, 用處不大, 盡在調(diào)試時用到
 * xTimerPeriodInTicks: 周期, 以Tick為單位
 * uxAutoReload: 類型, pdTRUE表示自動加載, pdFALSE表示一次性
 * pvTimerID: 回調(diào)函數(shù)可以使用此參數(shù), 比如分辨是哪個定時器
 * pxCallbackFunction: 回調(diào)函數(shù)
 * pxTimerBuffer: 傳入一個StaticTimer_t結(jié)構(gòu)體, 將在上面構(gòu)造定時器
 * 返回值: 成功則返回TimerHandle_t, 否則返回NULL
 */
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                                 TickType_t xTimerPeriodInTicks,
                                 UBaseType_t uxAutoReload,
                                 void * pvTimerID,
                                 TimerCallbackFunction_t pxCallbackFunction,
                                 StaticTimer_t *pxTimerBuffer );

回調(diào)函數(shù)類型

void ATimerCallback( TimerHandle_t xTimer );
 
typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );

刪除軟件定時器

/* 刪除定時器
 * xTimer: 要刪除哪個定時器
 * xTicksToWait: 超時時間
 * 返回值: pdFAIL表示"刪除命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

啟動玛迄、暫停、復(fù)位

  • 啟動

    啟動定時器就是設(shè)置它的狀態(tài)為運行態(tài)(Running棚亩、Active)

    這些函數(shù)的xTicksToWait表示的是蓖议,把命令寫入命令隊列的超時時間虏杰。命令隊列可能已經(jīng)滿了,無法馬上把命令寫入隊列里勒虾,可以等待一會纺阔。

    xTicksToWait不是定時器本身的超時時間,不是定時器本身的"周期"从撼。

    如果定時器已經(jīng)被啟動州弟,但是它的函數(shù)尚未被執(zhí)行,再次執(zhí)行xTimerStart()函數(shù)相當(dāng)于執(zhí)行xTimerReset()低零,重新設(shè)定它的啟動時間婆翔。

/* 啟動定時器
 * xTimer: 哪個定時器
 * xTicksToWait: 超時時間
 * 返回值: pdFAIL表示"啟動命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
 
/* 啟動定時器(ISR版本)
 * xTimer: 哪個定時器
 * pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
 *                            如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
 *                            則"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要進(jìn)行任務(wù)調(diào)度
 * 返回值: pdFAIL表示"啟動命令"無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerStartFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );
  • 暫停

    停止定時器就是設(shè)置它的狀態(tài)為冬眠(Dormant),讓它不能運行

/* 停止定時器
 * xTimer: 哪個定時器
 * xTicksToWait: 超時時間
 * 返回值: pdFAIL表示"停止命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
 
/* 停止定時器(ISR版本)
 * xTimer: 哪個定時器
 * pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
 *                            如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
 *                            則"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要進(jìn)行任務(wù)調(diào)度
 * 返回值: pdFAIL表示"停止命令"無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerStopFromISR(    TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );
 
  • 復(fù)位

    xTimerReset()函數(shù)

    • 讓定時器的狀態(tài)從冬眠態(tài)轉(zhuǎn)換為運行態(tài)掏婶,相當(dāng)于使用xTimerStart()函數(shù)
    • 如果定時器已經(jīng)處于運行態(tài)啃奴,使用xTimerReset()函數(shù)就相當(dāng)于重新確定超時時間。
/* 復(fù)位定時器
 * xTimer: 哪個定時器
 * xTicksToWait: 超時時間
 * 返回值: pdFAIL表示"復(fù)位命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
 
/* 復(fù)位定時器(ISR版本)
 * xTimer: 哪個定時器
 * pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
 *                            如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
 *                            則"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要進(jìn)行任務(wù)調(diào)度
 * 返回值: pdFAIL表示"停止命令"無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerResetFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );

修改周期

/* 修改定時器的周期
 * xTimer: 哪個定時器
 * xNewPeriod: 新周期
 * xTicksToWait: 超時時間, 命令寫入隊列的超時時間 
 * 返回值: pdFAIL表示"修改周期命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerChangePeriod(   TimerHandle_t xTimer,
                                 TickType_t xNewPeriod,
                                 TickType_t xTicksToWait );
 
/* 修改定時器的周期
 * xTimer: 哪個定時器
 * xNewPeriod: 新周期
 * pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
 *                            如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
 *                            則"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要進(jìn)行任務(wù)調(diào)度
 * 返回值: pdFAIL表示"修改周期命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
 *        pdPASS表示成功
 */
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
                                      TickType_t xNewPeriod,
                                      BaseType_t *pxHigherPriorityTaskWoken );

定時器ID

  • 用途:
    • 可以用來標(biāo)記定時器雄妥,表示自己是什么定時器
    • 可以用來保存參數(shù)最蕾,給回調(diào)函數(shù)使用
  • 接口類型:
    • 更新ID:使用vTimerSetTimerID()函數(shù)
    • 查詢ID:查詢pvTimerGetTimerID()函數(shù)
      這兩個函數(shù)不涉及命令隊列,它們是直接操作定時器結(jié)構(gòu)體
/* 獲得定時器的ID
 * xTimer: 哪個定時器
 * 返回值: 定時器的ID
 */
void *pvTimerGetTimerID( TimerHandle_t xTimer );
 
/* 設(shè)置定時器的ID
 * xTimer: 哪個定時器
 * pvNewID: 新ID
 * 返回值: 無
 */
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

中斷處理

FreeRTOS中很多API函數(shù)都有兩套:一套在任務(wù)中使用老厌,另一套在ISR中使用瘟则。后者的函數(shù)名含有"FromISR"后綴。

為什么要引入兩套API函數(shù)枝秤?(在任務(wù)中醋拧、在ISR中,這些函數(shù)的功能是有差別的)

  • 很多API函數(shù)會導(dǎo)致任務(wù)計入阻塞狀態(tài):
*   運行這個函數(shù)的**任務(wù)**進(jìn)入阻塞狀態(tài)
*   比如寫隊列時淀弹,如果隊列已滿丹壕,可以進(jìn)入阻塞狀態(tài)等待一會
  • ISR調(diào)用API函數(shù)時,ISR不是"任務(wù)"薇溃,ISR不能進(jìn)入阻塞狀態(tài)

如果使用一套函數(shù)的話菌赖,則需要在函數(shù)內(nèi)部進(jìn)行判斷,這樣大量增加復(fù)雜代碼沐序,會更難以測試琉用,并且不同平臺內(nèi)部框架也不一樣,這也大大加大了代碼的復(fù)雜度策幼。

中斷的延遲處理

在中斷中處理內(nèi)容邑时,盡量要快,這是因為:

  • 其他低優(yōu)先級的中斷無法被處理:實時性無法保證垄惧。
  • 用戶任務(wù)無法被執(zhí)行:系統(tǒng)顯得很卡頓刁愿。
  • 如果運行中斷嵌套绰寞,這會更復(fù)雜到逊,ISR越快執(zhí)行約有助于中斷嵌套铣口。

如果這個硬件中斷的處理,就是非常耗費時間呢觉壶?對于這類中斷的處理就要分為2部分:

  • ISR:盡快做些清理脑题、記錄工作,然后觸發(fā)某個任務(wù)
  • 任務(wù):更復(fù)雜的事情放在任務(wù)中處理

資源管理

臨界資源

要獨占式地訪問臨界資源铜靶,有3種方法:

  • 公平競爭:比如使用互斥量叔遂,誰先獲得互斥量誰就訪問臨界資源,這部分內(nèi)容前面講過争剿。
  • 誰要跟我搶已艰,我就滅掉誰:
*   中斷要跟我搶?我屏蔽中斷
*   其他任務(wù)要跟我搶蚕苇?我禁止調(diào)度器哩掺,不運行任務(wù)切換

在任務(wù)中屏蔽中斷

/* 在任務(wù)中,當(dāng)前時刻中斷是使能的
 * 執(zhí)行這句代碼后涩笤,屏蔽中斷
 */
taskENTER_CRITICAL();
 
/* 訪問臨界資源 */
 
/* 重新使能中斷 */
taskEXIT_CRITICAL();

在ISR中屏蔽中斷

void vAnInterruptServiceRoutine( void )
{
    /* 用來記錄當(dāng)前中斷是否使能 */
    UBaseType_t uxSavedInterruptStatus;
    
    /* 在ISR中嚼吞,當(dāng)前時刻中斷可能是使能的,也可能是禁止的
     * 所以要記錄當(dāng)前狀態(tài), 后面要恢復(fù)為原先的狀態(tài)
     * 執(zhí)行這句代碼后蹬碧,屏蔽中斷
     */
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    
    /* 訪問臨界資源 */
 
    /* 恢復(fù)中斷狀態(tài) */
    taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
    /* 現(xiàn)在舱禽,當(dāng)前ISR可以被更高優(yōu)先級的中斷打斷了 */
}

暫停調(diào)度器

/* 暫停調(diào)度器 */
void vTaskSuspendAll( void );
 
/* 恢復(fù)調(diào)度器
 * 返回值: pdTRUE表示在暫定期間有更高優(yōu)先級的任務(wù)就緒了
 *        可以不理會這個返回值
 */
BaseType_t xTaskResumeAll( void );
 
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恩沽,隨后出現(xiàn)的幾起案子誊稚,更是在濱河造成了極大的恐慌,老刑警劉巖飒筑,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件片吊,死亡現(xiàn)場離奇詭異,居然都是意外死亡协屡,警方通過查閱死者的電腦和手機俏脊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肤晓,“玉大人爷贫,你說我怎么就攤上這事〔购叮” “怎么了漫萄?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盈匾。 經(jīng)常有香客問我腾务,道長,這世上最難降的妖魔是什么削饵? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任岩瘦,我火速辦了婚禮未巫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘启昧。我一直安慰自己叙凡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布密末。 她就那樣靜靜地躺著握爷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪严里。 梳的紋絲不亂的頭發(fā)上新啼,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音刹碾,去河邊找鬼师抄。 笑死,一個胖子當(dāng)著我的面吹牛教硫,可吹牛的內(nèi)容都是我干的叨吮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞬矩,長吁一口氣:“原來是場噩夢啊……” “哼茶鉴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起景用,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涵叮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伞插,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體割粮,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年媚污,在試婚紗的時候發(fā)現(xiàn)自己被綠了舀瓢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耗美,死狀恐怖京髓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情商架,我是刑警寧澤堰怨,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站蛇摸,受9級特大地震影響备图,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一揽涮、第九天 我趴在偏房一處隱蔽的房頂上張望砸烦。 院中可真熱鬧,春花似錦绞吁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至购岗,卻和暖如春汰聋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喊积。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工烹困, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乾吻。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓髓梅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绎签。 傳聞我的和親對象是個殘疾皇子枯饿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容