FreeRTOS 之 事件標誌組及實現FreeRTOS看門狗
事件標誌組是實現多工同步的有效機制之一。任務間事件標誌組的實現是指各個任務之間使用事件標誌組實現任務的通訊或者同步機制。FreeRTOS在event_groups.c/h
檔案中提供了事件標誌組的具體實現。
事件標誌組簡介
根據具體平臺的不同,FreeRTOS根據 configUSE_16_BIT_TICKS
定義的不同,事件組支援的數量有些區別,具體如下:
#define configUSE_16_BIT_TICKS 1 // 每建立一個事件標誌組,使用者可以使用的事件標誌是8個
或者
#define configUSE_16_BIT_TICKS 0 // 每建立一個事件標誌組,使用者可以使用的事件標誌是24個
那麼這個 8 或者 24 是怎麼來的呢?FreeRTOS根據configUSE_16_BIT_TICKS
的定義,如果該值為 1 就定義一個16位的變數,只使用其中的低8位作為事件標誌位;如果其值為 0 就定義一個32位的變數,使用其中的低24位作為事件標誌位。具體見event_groups.h
檔案中,關於EventBits_t
的定義:
/*
* The type that holds event bits always matches TickType_t - therefore the
* number of bits it holds is set by configUSE_16_BIT_TICKS (16 bits if set to 1,
* 32 bits if set to 0.
*
* \defgroup EventBits_t EventBits_t
* \ingroup EventGroup
*/
typedef TickType_t EventBits_t;
從上面的定義可知,其型別應該為TickType_t
型別,該型別具體定義如下:
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
/* 32-bit tick type on a 32-bit architecture, so reads of the tick count do
not need to be guarded with a critical section. */
#define portTICK_TYPE_IS_ATOMIC 1
#endif
至於為何只是用事件標誌組其中的一部分,這個在官方文件中沒有找到說明!
事件標誌組API函式
FreeRTOS的事件標誌組提供了11個API函式,下面對比較常用的幾個做一下介紹:
(1)EventGroupHandle_t xEventGroupCreate( void );
用來建立一個事件標誌組。每個事件組需要一個[非常]少量的RAM,用於儲存事件組的狀態。
如果使用xEventGroupCreate()建立事件組,則會從FreeRTOS堆中自動分配所需的RAM(具體為使用pvPortMalloc從堆上申請)。
引數:
無
返回值:
如果建立了事件組,則返回事件組的控制代碼。 如果沒有足夠的FreeRTOS堆可用於建立事件組,則返回NULL。
用法:
/* 定義事件組控制代碼,用來指向建立的事件標誌組. */
EventGroupHandle_t xCreatedEventGroup;
/* 建立事件標誌組 */
xCreatedEventGroup = xEventGroupCreate();
/* 判斷是否建立成功? */
if( xCreatedEventGroup == NULL )
{
/* 沒有足夠堆時,可能會失敗 */
}
else
{
/* 建立成功. */
}
(2)EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer );
該函式同樣用來建立一個事件標誌組。用來存放事件標誌組狀態的RAM空間需要程式設計者提供。通過引數pxEventGroupBuffer傳遞該ARM空間 。該函式允許在編譯時靜態分配RAM。
引數:
pxEventGroupBuffer:
指向用來儲存事件標誌組的RAM空間
返回值:
如果建立了事件組,則返回事件組的控制代碼。 如果pxEventGroupBuffer
為NULL,則返回NULL。
用法:
/* 事件組控制代碼 */
EventGroupHandle_t xEventGroupHandle;
/* 定義一個事件標誌組 */
StaticEventGroup_t xCreatedEventGroup;
/* 建立. */
xEventGroupHandle = xEventGroupCreateStatic( &xCreatedEventGroup );
/* 檢查是否建立成功 */
configASSERT( xEventGroupHandle );
要使用該函式,
configSUPPORT_STATIC_ALLOCATION
必須被置為 1
(3)void vEventGroupDelete( EventGroupHandle_t xEventGroup );
刪除以前使用對xEventGroupCreate()的呼叫建立的事件組。
引數:
xEventGroup:
要被刪除的事件標誌組的控制代碼
返回值:
NULL。
阻塞在事件標誌組上的任務被刪除時,將強制取消阻塞,並且返回事件組標誌值為 0
(4)EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait );
讀RTOS事件組中的位,可選擇進入阻塞狀態(具有超時)以等待一個位或一組位被置位。
引數:
xEventGroup:
被讀取的事件標誌組
uxBitsToWaitFor:
要檢測的位(對要檢測的位為1。該值不能為 0)
xClearOnExit:
退出時是否清楚該位(取值:pdTRUE 或pdFALSE
)
xWaitForAllBits:
是否檢測到所有位都被置位再返回
xTicksToWait :
延時
返回值:
測試返回值可以知道哪些位被置位了。 如果xEventGroupWaitBits()返回是因為它的超時了,則肯定有等待的位沒有被成功置位。 如果xEventGroupWaitBits()返回是因為它等待的所有位均被設定,則返回的值是任何位被自動清除之前的事件組值(xClearOnExit引數設定為pdTRUE時會清楚結果)
用法:
#define BIT_0 ( 1 << 0 )
#define BIT_4 ( 1 << 4 )
void aFunction( EventGroupHandle_t xEventGroup )
{
EventBits_t uxBits;
const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS;
uxBits = xEventGroupWaitBits(
xEventGroup, /* 要檢測事件組控制代碼. */
BIT_0 | BIT_4, /* 需要檢測的位. */
pdTRUE, /* BIT_0 & BIT_4 退出時要被清楚. */
pdFALSE, /* 不用等待BIT_0 和 BIT_4 均被置位(任一一個被置位均返回)*/
xTicksToWait );/* 超時時間 */
if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) )
{
/* 均被置位. */
}
else if( ( uxBits & BIT_0 ) != 0 )
{
/* 只有 BIT_0 被置位. */
}
else if( ( uxBits & BIT_4 ) != 0 )
{
/* 只有 BIT_4 被置位. */
}
else
{
/* 超時 */
}
}
- 不能再中斷中呼叫該函式
- 該函式的返回值並不可靠,因為有可能會有更高優先順序的任務將該值給更改了。需要使用者自己確保該值的可靠性!
(5)EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
設定RTOS事件組中的位(標誌)。
引數:
xEventGroup:
被設定的事件標誌組
uxBitsToSet :
要設定的位
返回值:
返回在呼叫xEventGroupSetBits()時事件組的值。
用法:
#define BIT_0 ( 1 << 0 )
#define BIT_4 ( 1 << 4 )
void aFunction( EventGroupHandle_t xEventGroup )
{
EventBits_t uxBits;
/* 將 bit 0 和 bit 4 置位. */
uxBits = xEventGroupSetBits(
xEventGroup, /* 將被更新的事件組. */
BIT_0 | BIT_4 );/* 要被設定的位. */
if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) )
{
/* bit 0 and bit 4 均被置位. */
}
else if( ( uxBits & BIT_0 ) != 0 )
{
/* 只有Bit 0 被置位 */
}
else if( ( uxBits & BIT_4 ) != 0 )
{
/* 只有Bit 4 被置位 . */
}
else
{
/* 置位失敗. */
}
}
- 不能再中斷中呼叫該函式
- 該函式的返回值並不可靠,由以下兩個原因
A. 因為有可能會有更高優先順序的任務在該函式返回前將該值給更改了。
B. 可能被xEventGroupWaitBits()
給更改了
(6)BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken );
設定RTOS事件組中的位(標誌)。 可以從中斷服務程式(ISR)呼叫的xEvent GroupSet Bits()版本。
引數:
xEventGroup:
被設定的事件標誌組
uxBitsToSet:
要設定的位
pxHigherPriorityTaskWoken:
呼叫此函式將導致訊息被髮送到RTOS守護程序任務。 如果守護程序任務的優先順序高於當前執行任務的優先順序(中斷任務中斷),則* pxHigherPriorityTaskWoken將被xEventGroupSetBitsFromISR()設定為pdTRUE,表示在中斷退出之前應該請求上下文切換。 因此,*pxHigherPriorityTaskWoken必須初始化為pdFALSE。
返回值:
如果訊息傳送到RTOS守護程式任務,則返回pdPASS,否則返回pdFAIL。 如果定時器服務佇列已滿,將返回pdFAIL。
用法:
#define BIT_0 ( 1 << 0 )
#define BIT_4 ( 1 << 4 )
/* 事件組控制代碼 */
EventGroupHandle_t xEventGroup;
void anInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken, xResult;
/* xHigherPriorityTaskWoken 必須被初始化位 pdFALSE. */
xHigherPriorityTaskWoken = pdFALSE;
/* 對 bit 0 和 bit 4 置位. */
xResult = xEventGroupSetBitsFromISR(
xEventGroup,
BIT_0 | BIT_4,
&xHigherPriorityTaskWoken );
/* Was the message posted successfully? */
if( xResult != pdFAIL )
{
/* 如果xHigherPriorityTaskWoken現在設定為pdTRUE,那麼應該請求上下文切換。 The macro used is port specific and will
be either portYIELD_FROM_ISR() or portEND_SWITCHING_ISR() - refer to
the documentation page for the port being used. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
}
- INCLUDE_xEventGroupSetBitFromISR必須被置 1
- configUSE_TIMERS 必須被置 1
- INCLUDE_xTimerPendFunctionCall 必須被置 1
- 從ISR設定位將把設定操作推遲到RTOS守護程序任務(也稱為定時器服務任務)
- 如果設定操作必須立即完成(在應用程式執行建立的任務之前),那麼FreeRTOS守護程式任務的優先順序必須高於使用事件組的任何應用程式任務的優先順序。
- 設定事件組中的位不是確定性操作,因為可能正在等待一個或多個位被設定的未知數量的任務。 FreeRTOS不允許在中斷或臨界段中執行非確定性操作。 因此,xEventGroupSetBitFromISR()傳送訊息到RTOS守護程序任務,以便在守護程序任務的上下文中執行設定操作 - 其中使用排程程式鎖來代替臨界段。
(7)EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
清除RTOS事件組中的位(標誌)。
引數:
xEventGroup:
被清除的事件標誌組
uxBitsToSet:
要清除的位
返回值:
清除指定位前的事件組的值。
用法:
#define BIT_0 ( 1 << 0 )
#define BIT_4 ( 1 << 4 )
void aFunction( EventGroupHandle_t xEventGroup )
{
EventBits_t uxBits;
/* Clear bit 0 and bit 4 in xEventGroup. */
uxBits = xEventGroupClearBits(
xEventGroup, /* The event group being updated. */
BIT_0 | BIT_4 );/* The bits being cleared. */
if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) )
{
/* Both bit 0 and bit 4 were set before xEventGroupClearBits()
was called. Both will now be clear (not set). */
}
else if( ( uxBits & BIT_0 ) != 0 )
{
/* Bit 0 was set before xEventGroupClearBits() was called. It will
now be clear. */
}
else if( ( uxBits & BIT_4 ) != 0 )
{
/* Bit 4 was set before xEventGroupClearBits() was called. It will
now be clear. */
}
else
{
/* Neither bit 0 nor bit 4 were set in the first place. */
}
}
- 不能再中斷中呼叫該函式
(8)BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
可以從中斷呼叫的xEventGroupClearBits()版本。 清除操作推遲到RTOS守護程式任務 - 這也稱為定時器服務任務。 守護程式任務的優先順序由FreeRTOSConfig.h中的configTIMER_TASK_PRIORITY設定設定。
引數:
xEventGroup:
被清除的事件標誌組
uxBitsToSet:
要清除的位
返回值:
清除指定位前的事件組的值。
用法:
#define BIT_0 ( 1 << 0 )
#define BIT_4 ( 1 << 4 )
/* This code assumes the event group referenced by the
xEventGroup variable has already been created using a call to
xEventGroupCreate(). */
void anInterruptHandler( void )
{
BaseType_t xSuccess;
/* Clear bit 0 and bit 4 in xEventGroup. */
xSuccess = xEventGroupClearBitsFromISR(
xEventGroup, /* The event group being updated. */
BIT_0 | BIT_4 );/* The bits being cleared. */
if( xSuccess == pdPASS )
{
/* The command was sent to the daemon task. */
}
else
{
/* The clear bits command was not sent to the daemon task. */
}
}
(9)EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup );
返回RTOS事件組中事件位(事件標誌)的當前值。
引數:
xEventGroup:
被讀取的事件標誌組
返回值:
該函式被呼叫時,事件標誌組的值
- 不能再中斷中呼叫該函式
(10)EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup );
可以從中斷呼叫的xEventGroupGetBits()版本。
引數:
xEventGroup:
被讀取的事件標誌組
返回值:
該函式被呼叫時,事件標誌組的值
- 不能再中斷中呼叫該函式
(11)EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait );
可以從中斷呼叫的xEventGroupGetBits()版本。
引數:
xEventGroup:
正在設定和測試位的事件組。
uxBitsToSet:
在確定是否(並且可能等待)由uxBitsToWait引數指定的所有位之前,在事件組中設定的一個或多個位。 例如,將uxBitsToSet設定為0x04以在置位事件組中第2位。
uxBitsToWaitFor:
指示要在事件組中測試的一個或多個位的按位值。 例如,將uxBitsToWaitFor設定為0x05以等待位0和位2.將uxBitsToWaitFor設定為0x07以等待位0和位1和位2。
xTicksToWait :
超時時間。
返回值:
事件組在等待位被置位或塊時間到期時的值。 測試返回值以知道設定了哪些位。
- 不能再中斷中呼叫該函式
事件組實現看門狗
在嵌入式系統中,看門狗是一個必不可少的元件。在使用了作業系統後,如何正確的實現看門狗成了一個問題!
思路: 讓所有執行緒每隔一段時間上報一次“我還活著”事件給監視程式,當監視程式發現其中一個執行緒在這段時間內沒有上報“我還活著”事件時就停止喂狗。
定義監視的任務事件標誌組位
/**
* @brief 最多一次監測MAX_TASK_NUM個任務,如果多於該數,則需要定義多個事件標誌組
*/
#define WDG_BIT_DOWN_TASK_1 (1 << 0)
#define WDG_BIT_DOWN_TASK_2 (1 << 1)
#define WDG_BIT_DOWN_TASK_3 (1 << 2)
#define WDG_BIT_DOWN_TASK_4 (1 << 3)
#define WDG_BIT_TASK_ALL ( WDG_BIT_DOWN_TASK_1 | WDG_BIT_DOWN_TASK_2 | WDG_BIT_DOWN_TASK_3 | WDG_BIT_DOWN_TASK_4 )
監視任務
監視任務負責在規定時間內檢測個事件標誌組位,已達到監測其他任務執行的目的。
/**
* @brief 看門狗任務
* @param argument
* @retval None
*/
void vTaskWDG(void * argument)
{
static TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS*10; /* 最大延遲1s */
EventBits_t uxBits;
/* 建立事件標誌組 */
xCreatedEventGroup = xEventGroupCreate();
if(xCreatedEventGroup == NULL)
{
/* 沒有建立成功,使用者可以在這裡加入建立失敗的處理機制 */
return;
}
while(1)
{
/* 等待所有任務發來事件標誌 */
uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件標誌組控制代碼 */
WDG_BIT_TASK_ALL, /* 等待WDG_BIT_TASK_ALL被設定 */
pdTRUE, /* 退出前WDG_BIT_TASK_ALL被清除,這裡是TASK_BIT_ALL都被設定才表示“退出”*/
pdTRUE, /* 設定為pdTRUE表示等待TASK_BIT_ALL都被設定*/
xTicksToWait); /* 等待延遲時間 */
if((uxBits & WDG_BIT_TASK_ALL) == WDG_BIT_TASK_ALL)
{
vWDG_Feed();
}
else
{
/* 通過變數uxBits簡單的可以在此處檢測那個任務長期沒有發來執行標誌 */
}
}
}
這樣,在其他任務中,不斷呼叫xEventGroupSetBits,給相應的位進行置位即可!一旦有任務沒有正常置位,則該任務停止喂狗!
當然,這樣就出現了一個問題:有的任務可能需要阻塞相當長的時間,這個時間已經遠遠超過了看門狗的時限。又或者說,使用者手動掛起了一個任務,看門狗必須暫停監視該任務。
解決這個問題也很簡單,只需要稍微更改一下看門狗任務即可:
- 首先,必須有函式可以同步看門狗任務,處理任務掛起、恢復問題。
- 其次,對於長時間等待任務,看門狗任務可以以固定頻率喂狗,在規定的最大時間到時,檢測所有事件,如果這時還有沒有置位的事件,則認為出錯!