1. Introduction
關於處理event, 有幾個要考慮的點
- 要怎麼偵測event? 通常會用 interrupt, 但也可以用polling的方式
- 如果使用 interrupt, 要在 ISR (Interrupt service routine) 裡面處裡多少事情? 相對地要在ISR外面處理多少事情? 是否 ISR 裡面的事情要愈少愈好?
- ISR 裡的 code 要如何跟外面的 code 溝通? 尤其是 asynchronous 的code
在這些API裡, 只有 FromISR 結尾的 function 才可以在 ISR 裡呼叫, 以及以 FROM_ISR 結尾的 macro 可以在ISR裡使用
2. Binary Semaphores used for Synchronization
整個概念如下圖, 當 interrupt 發生時, ISR 解開 semaphore 並讓 handler task 變成 ready state, 然後 ISR return, 這時 handler task 有最高的 priority 並執行, 當它執行完, 等下一個 semaphore, 進入block state, 並讓低 priority 的 task 執行
在使用 semaphore 時, 分成 take & give, 實作上, 它就像是長度為1的queue, 它一樣要設定 block time, 相關的限制也相同
2.1 Create Semaphore
void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );
xSemaphoreHandle 是 semaphore 的型態, 在使用它之前必需先 create
2.2 Take Semaphore
portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait );
如果把 xTicksToWait 設成 portMAX_DELAY, 表示要永遠等下去
xSemaphoreTake 不能在 ISR 裡面使用
可能的回傳值 :
- pdPASS : 成功拿到 semaphore, 如果 block time > 0, 表示在 timeout 前拿到 semaphore
- pdFALSE : 沒能在 block time timeout 前拿到 semaphore
2.3 Give Semaphore in ISR
portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore, portBASE_TYPE *pxHigherPriorityTaskWoken );
如果有 high priority task 在等這個 semaphore, 然後呼叫 xSemaphoreGiveFromISR 造成目前的 task 被 scheduler switch out, 那麼會把 pxHigherPriorityTaskWoken 這個 pointer 裡面的值設成 pdTRUE, 表示要處裡目前要被 switch out 的 task 的 context switch
可能的回傳值 :
- pdPASS : 成功
- pdFALSE : 如果目前的 semaphore 已經被 give 了, 會回傳失敗
2.4 sample code
static void vPeriodicTask( void *pvParameters ) { for( ;; ) { /* 等 500ms 準備送出 sofeware interrupt */ vTaskDelay( 500 / portTICK_RATE_MS ); /* 送出 interrupt, 前後夾 log */ vPrintString( "Periodic task - About to generate an interrupt.\r\n" ); __asm{ int 0x82 } /* This line generates the interrupt. */ vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" ); } } static void vHandlerTask( void *pvParameters ) { /* As per most tasks, this task is implemented within an infinite loop. */ for( ;; ) { /* take semaphore */ xSemaphoreTake( xBinarySemaphore, portMAX_DELAY ); vPrintString( "Handler task - Processing event.\r\n" ); } } static void __interrupt __far vExampleInterruptHandler( void ) { static portBASE_TYPE xHigherPriorityTaskWoken; /* 先設成 false, 這樣如果有 task 被switch out時會設成ture, 才能分辨 */ xHigherPriorityTaskWoken = pdFALSE; /* give semaphore */ xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken ); if( xHigherPriorityTaskWoken == pdTRUE ) { /* 做 context switch, 其中 portSWITCH_CONTEXT() 是 Open Watcom Dos 的port, 其它平台可能有不同的 port */ portSWITCH_CONTEXT(); } } int main( void ) { /* 建 semaphore */ vSemaphoreCreateBinary( xBinarySemaphore ); /* 設定 interrupt handler */ _dos_setvect( 0x82, vExampleInterruptHandler ); if( xBinarySemaphore != NULL ) { /* 建立要take semaphore 的 task, 它的 priority 是 3 */ xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL ); /* 建立定期發出 software interrupt 的 task */ xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL ); vTaskStartScheduler(); } /* it should never reach here */ for( ;; ); }
3. Counting semaphores
使用 binary semaphore 的缺點是, 在 ISR unblock binary semaphore之後, 並且讓handle task執行時, 如果這時又再有一個 interrupt, 這時候 handle task也只會做最初的那次, counting semaphore 可以解決這種情況
可以將 counting semaphore 想成是長度為n的queue, 只是我們不在乎裡面的data
通常 counting semaphore 用在兩個用途: 記錄次數, 與管理資源
底下是它的 api
xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount, unsigned portBASE_TYPE uxInitialCount );
其中 uxMaxCount 是最大值, uxInitialCount是初始值, 成功的話回傳 semaphore handle
4. Use queue in ISR
在 ISR 裡使用 queue, 有相對應的 function:
portBASE_TYPE xQueueSendToFrontFromISR( xQueueHandle xQueue, void *pvItemToQueue portBASE_TYPE *pxHigherPriorityTaskWoken ); portBASE_TYPE xQueueSendToBackFromISR( xQueueHandle xQueue, void *pvItemToQueue portBASE_TYPE *pxHigherPriorityTaskWoken ); BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxHigherPriorityTaskWoken );
這些其實就是 xQueueSendToFront(), xQueueSendToBack() 以及 xQueueReceive()
4.1 Sample code
static void vIntegerGenerator( void *pvParameters ) { portTickType xLastExecutionTime; unsigned portLONG ulValueToSend = 0; int i; /* Initialize the variable used by the call to vTaskDelayUntil(). */ xLastExecutionTime = xTaskGetTickCount(); for( ;; ) { /* 每 200ms block */ vTaskDelayUntil( &xLastExecutionTime, 200 / portTICK_RATE_MS ); /* 寫入queue 5次, 這些值將從ISR讀出 */ for( i = 0; i < 5; i++ ) { xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 ); ulValueToSend++; } /* 觸發 software interrupt 0x82 */ vPrintString( "Generator task - About to generate an interrupt.\r\n" ); __asm{ int 0x82 } /* This line generates the interrupt. */ vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" ); } } static void __interrupt __far vExampleInterruptHandler( void ) { static portBASE_TYPE xHigherPriorityTaskWoken; static unsigned long ulReceivedNumber; /* 宣告成 static const 確保它們不是 ISR 產生的 */ static const char *pcStrings[] = {"St0\r\n", "St1\r\n", "St2\r\n", "St3\r\n"}; xHigherPriorityTaskWoken = pdFALSE; /* Loop until the queue is empty. */ while( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber, &xHigherPriorityTaskWoken ) != errQUEUE_EMPTY ) { /* Mask最後兩個bit, 然後把對應的String放進queue裡 */ ulReceivedNumber &= 0x03; xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken ); } /* 如果寫入 queue 造成 high priority task 變成 ready, 就要做context switch */ if( xHigherPriorityTaskWoken == pdTRUE ) { /* NOTE: 這個context switch 是給 Open Watcom DOS port 使用, 其它平台有不同的port */ portSWITCH_CONTEXT(); } } static void vStringPrinter( void *pvParameters ) { char *pcString; for( ;; ) { /* Block on the queue to wait for data to arrive. */ xQueueReceive( xStringQueue, &pcString, portMAX_DELAY ); vPrintString( pcString ); } } int main( void ) { /* 建queue */ xIntegerQueue = xQueueCreate( 10, sizeof( unsigned long ) ); xStringQueue = xQueueCreate( 10, sizeof( char * ) ); /* 設定 interrupt handler. */ _dos_setvect( 0x82, vExampleInterruptHandler ); /* low priority period task */ xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL ); /* High priority interrupt handler */ xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL ); vTaskStartScheduler(); for( ;; ); }
沒有留言:
張貼留言