以下內容轉載自安富萊電子:http://forum.armfly.com/forum.php
1、臨界段
代碼的臨界段也稱為臨界區,一旦這部分代碼開始執行,則不允許任何中斷打斷。為確保臨界段代碼的執行不被中斷,在進入臨界段之前須關中斷,而臨界段代碼執行完畢后,要立即開中斷。
FreeRTOS 源碼中就有多處臨界段的處理,跟 FreeRTOS 一樣,uCOS-II 和 uCOS-III 源碼中都是有臨界段的,而 RTX 的源碼中不存在臨界段。另外,除了 FreeRTOS 操作系統源碼所帶的臨界段以外,用戶寫應用的時候也有臨界段的問題,比如以下兩種:
2、任務代碼臨界段處理
FreeRTOS 任務代碼中臨界段的進入和退出主要是通過操作寄存器 basepri 實現的。進入臨界段前操作寄存器 basepri 關閉了所有小於等於宏定義 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY所定義的中斷優先級,這樣臨界段代碼就不會被中斷干擾到,而且實現任務切換功能的 PendSV 中斷和滴答定時器中斷是最低優先級中斷,所以此任務在執行臨界段代碼期間是不會被其它高優先級任務打斷的。退出臨界段時重新操作 basepri 寄存器,即打開被關閉的中斷(這里我們不考慮不受 FreeRTOS 管理的更高優先級中斷)。FreeRTOS 進入和退出臨界段的函數如下:
通過上面的兩個函數 vPortEnterCritical 和 vPortExitCritical 可以看出,進入臨界段和退出臨界段是通過函數調用開關中斷函數 portENABLE_INTERRUPTS 和 portDISABLE_INTERRUPTS 實現的。細心的讀者還會發現上面的這兩個函數都對變量 uxCriticalNesting 進行了操作。這個變量比較重要,用於臨界段的嵌套計數。初學的同學也許會問這里直接的開關中斷不就可以了嗎,為什么還要做一個嵌套計數呢?主要是因為直接的開關中斷方式不支持在開關中斷之間的代碼里再次執行開關中斷的嵌套處理,假如當前們
的代碼是關閉中斷的,嵌套了一個含有開關中斷的臨界區代碼后,退出時中斷就成開的了,這樣就出問題了。通過嵌套計數就有效地防止了用戶嵌套調用函數 taskENTER_CRITICAL 和 taskEXIT_CRITICAL 時出錯。
經過這么多次的宏定義后,終於來到了最終的原始函數。FreeRTOS 的這種層層調用宏定義的方法在帶來便利操作的同時,卻讓用戶在分析源碼的時候非常不方便。通過上面的源碼實現可以看出,FreeRTOS 的開關全局中斷是通過操作寄存器 basepri 實現的,關於這個寄存器,已經在 “FreeRTOS— 中斷優先級配置” 進行了詳細的講解,這里不再贅述。
使用舉例:
使用的時候一定要保證成對使用
嵌套使用舉例:
3 、中 斷 服 務 程 序 臨 界 段 處 理
與任務代碼里臨界段的處理方式類似,中斷服務程序里面臨界段的處理也有一對開關中斷函數。
通過上面的源碼可以看出,中斷服務程序里面的臨界段代碼的開關中斷也是通過寄存器 basepri 實現的。初學的同學也許會問,這里怎么沒有中斷嵌套計數了呢?是的,這里換了另外一種實現方法,通過保存和恢復寄存器 basepri 的數值就可以實現嵌套使用。如果大家研究過 uCOS-II 或者 III 的源碼,跟這里的實現方式是一樣的,具體看下面的使用舉例。
使用舉例:
使用的時候一定要保證成對使用
4 、開 關 中 斷 的 實 現
FreeRTOS 也專門提供了一組開關中斷函數,實現比較簡單,其實就是前面 第2小節里面臨界段進入和退出函數的精簡版本,主要區別是不支持中斷嵌套。具體實現如下:
從上面的源碼可以看出,FreeRTOS 的全局中斷開關是通過操作寄存器 basepri 實現的,關於這個寄存器,前面已經講過,這里不再贅述。
使用舉例:
使用的時候一定要保證成對使用
5 、BSP 板 級 支 持 包 中 開 關 中 斷 的 特 別 處 理
前面為大家講解了 FreeRTOS 臨界段的處理方法和開關中斷方法,加上了 FreeRTOS 操作系統后,我們實際編寫的外設驅動又該怎么修改呢?因為外設驅動編寫時,有些地方有用到開關中斷操作,這里以教程配套的 STM32F103,F407 和 F429 開發板為例進行說明,這三種開發板的外設驅動的編寫架構都是統一的,用戶只需將 bsp.h 文件里面的宏定義:
臨界段開關中斷實驗,實驗現象:
K1鍵按下 掛起任務VTaskLED
K2鍵按下 啟動單次定時器中斷,50ms后在定時器中斷將任務vTaskLED恢復
接口消息處理函數:
static void vTaskTaskUserIF(void *pvParameters) { while(1) { if(ucKeyCode != 0) { switch( ucKeyCode) { /* K1鍵按下 掛起任務VTaskLED */ case 1: taskENTER_CRITICAL(); /* 進入臨界區 */ printf("K2鍵按下,掛起任務vTaskLED\r\n"); taskEXIT_CRITICAL(); /* 退出臨界區 */ vTaskSuspend(xHandleTaskLED1); ucKeyCode = 0; break; /* K2鍵按下 啟動單次定時器中斷,50ms后在定時器中斷將任務vTaskLED恢復 */ case 2: taskENTER_CRITICAL(); /* 進入臨界區 */ printf("K3鍵按下,啟動單次定時器中斷,50ms后在定時器中斷將任務vTaskLED恢復\r\n"); taskEXIT_CRITICAL(); /* 退出臨界區 */ BASIC_TIMx_Mode_Config(); ucKeyCode = 0; break; /* 其他的鍵值不處理 */ default: break; } vTaskDelay(20); } } }
K1鍵按下,程序執行case1,把原本閃爍的LED燈掛起。
k2鍵按下,程序執行case2,然后調用定時器初始化結構體,BASIC_TIMx_Mode_Config();
void BASIC_TIMx_Mode_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 開啟TIMx_CLK,x[6,7] RCC_APB1PeriphClockCmd(BASIC_TIMx_CLK, ENABLE); /* 累計 TIM_Period個后產生一個更新或者中斷*/ //當定時器從0計數到49,即為50次,為一個定時周期 TIM_TimeBaseStructure.TIM_Period = 50-1; //定時器時鍾源TIMxCLK = 2 * PCLK1 // PCLK1 = HCLK / 4 // => TIMxCLK=HCLK/2=SystemCoreClock/2=90MHz // 設定定時器頻率為=TIMxCLK/(TIM_Prescaler+1)=1MHz TIM_TimeBaseStructure.TIM_Prescaler = 90-1; /* T = 50/1000000 = 50us */ // 初始化定時器TIMx, x[2,3,4,5] TIM_TimeBaseInit(BASIC_TIMx, &TIM_TimeBaseStructure); // 清除定時器更新中斷標志位 TIM_ClearFlag(BASIC_TIMx, TIM_FLAG_Update); // 開啟定時器更新中斷 TIM_ITConfig(BASIC_TIMx,TIM_IT_Update,ENABLE); // 使能定時器 TIM_Cmd(BASIC_TIMx, ENABLE); }
定時器開始定時,執行定時器中斷函數,
void BASIC_TIMx_IRQHandler(void) { if ( TIM_GetITStatus( BASIC_TIMx, TIM_IT_Update) != RESET ) { ulHighFrequencyTimerTicks++; TimeOut++; TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update); } if(TimeOut == 1000) //50us*1000 = 50ms { TIM_Cmd(BASIC_TIMx, DISABLE); //不使能定時器,此時定時器不定時 TimeOut = 0; //讓TimeOut重新置零 BaseType_t xYieldRequired; UBaseType_t uxSavedInterruptStatus; uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); /* 進入臨界區 */ { /* 用戶可以在這里添加臨界段代碼,我們這里暫時未用到 */ } portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); /* 退出臨界區 */ /* 恢復掛起任務 */ xYieldRequired = xTaskResumeFromISR(xHandleTaskLED1); /* 退出中斷后是否需要執行任務切換 */ if( xYieldRequired == pdTRUE ) { portYIELD_FROM_ISR(xYieldRequired); } } }
定時器定時到50ms時,TIM_Cmd(BASIC_TIMx, DISABLE); //不使能定時器,此時定時器不定時。
等到下次按下K2的時候再調用用定時器初始化結構體,並使能定時器 TIM_Cmd(BASIC_TIMx, ENABLE);
notice
/*===========================================可屏蔽的中斷優先級配置====================================================*/ /* * 用於配置STM32的特殊寄存器basepri寄存器的值,用於屏蔽中斷,當大於basepri值的優先級的中斷將被全部屏蔽。basepri只有4bit有效, * 默認只為0,即全部中斷都沒有被屏蔽。configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY配置為:5,意思就是中斷優先級大於5的中斷都被屏蔽。 * 當把配置好的優先級寫到寄存器的時候,是按照8bit來寫的,所以真正寫的時候需要經過轉換,公式為: * ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我們配置的真正的優先級。經過這個公式之后得到的是下面的這個宏: * configMAX_SYSCALL_INTERRUPT_PRIORITY * * 在FreeRTOS中,關中斷是通過配置basepri寄存器來實現的,關掉的中斷由配置的basepri的值決定,小於basepri值的 * 中斷FreeRTOS是關不掉的,這樣做的好處是系統設計者可以人為的控制那些非常重要的中斷不能被關閉,在緊要的關頭必須被響應。 * 而在UCOS中,關中斷是通過控制PRIMASK來實現的,PRIMASK是一個單1的二進制位,寫1則除能除了NMI和硬 fault的所有中斷。當UCOS關閉 * 中斷之后,即使是你在系統中設計的非常緊急的中斷來了都不能馬上響應,這加大了中斷延遲的時間,如果是性命攸關的場合,那后果估計挺嚴重。 * 相比UCOS的關中斷的設計,FreeRTOS的設計則顯得人性化很多。 * */ #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2 #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )