Cortex-M中斷
中斷是指計算機運行過程中,出現某些意外情況需主機干預時,機器能自動停止正在運行的程序并轉入處理新情況的程序(中斷服務程序),處理完畢后又返回原被暫停的程序繼續運行。Cortex-M內核的MCU提供了一個用于中斷管理的嵌套向量中斷控制器(NVIC)。
當多個中斷來臨的時候,處理器應響應哪一個中斷是由中斷的優先級來決定的,高優先級(優先級的編號小)的中斷首先得到響應,可以搶占低優先級的中斷。Cortex-M處理器有三個固定優先級和256個可編程優先級,最多有128個搶占等級,優先級配置寄存器是8位寬的,處理器還把優先級分為高低兩段:搶占優先級和亞優先級,但實際的優先級數量是由芯片廠商決定的,STM32選擇了4位作為優先級,只有16個優先級,FreeRTOS的中斷配置沒有處理亞優先級這種情況,所以全是搶占優先級,設置分組時候,選擇中斷優先級分組4,優先級分組配置在HAL_Init()中。
FreeRTOS中斷配置宏
-
configPRIO_BITS
設置MCU使用幾位優先級,STM32使用的是4位 -
configLIBRARY_LOWEST_INTERRUPT_PRIORITY
設置最低優先級,STM32配置使用組4,都是搶占優先級,最低優先級為15
-
configKERNEL_INTERRUPT_PRIORITY
設置內核中斷優先級 -
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
系統可管理的最大優先級
優先級數低于5不歸FreeRTOS管理 -
configMAX_SYSCALL_INTERRUPT_PRIORITY
此宏設置好后,優先級低于此的中斷可以安全調用FreeRTOS的API函數,優先級高于此的中斷FreeRTOS是不能禁止的,中斷服務函數也不能調用FreeRTOS的API函數。
由于高于configMAX_SYSCALL_INTERRUPT_PRIORITY的優先級不會被FreeRTOS內核屏蔽,因此對于那些實時性要求嚴格的任務就可以使用這些優先級。
FreeRTOS開關中斷
開關中斷函數為portENABLE_INTERRUPTS和portDISABLE_INTERRUPTS
當關閉中斷后,優先級低于configMAX_SYSCALL_INTERRUPT_PRIORITY(優先級數大于configMAX_SYSCALL_INTERRUPT_PRIORITY)的中斷將被屏蔽,高于configMAX_SYSCALL_INTERRUPT_PRIORITY(優先級數小于configMAX_SYSCALL_INTERRUPT_PRIORITY)的不會被屏蔽
臨界段代碼
臨界段代碼也叫臨界區,是指那些必須完整運行,不能被打斷的代碼段。FreeRTOS在進入臨界段代碼的時候需要關閉中斷,當處理完臨界段代碼以后再打開中斷。
FreeRTOS與臨界段代碼保護有關的函數有4個:taskENTER_CRITICAL、taskEXIT_CRITICAL、taskENTER_CRITICAL_FROM_ISR、taskEXIT_CRITICAL_FROM_ISR,前兩個是任務級別的臨界段代碼保護,后兩個是中斷級的臨界區保護。
中斷測試
start_task:創建另一個任務
interrupt_task:中斷測試實驗,任務中會調用FreeRTOS的關中斷函數portDISABLE_INTERRUPTS來將中斷關閉一段時間。
任務設置
//任務優先級
#define START_TASK_PRIO 1
//任務堆棧大小
#define START_STK_SIZE 256
//任務句柄
TaskHandle_t StartTask_Handler;
//任務函數
void start_task(void *pvParameters);//任務優先級
#define INTERRUPT_TASK_PRIO 2
//任務堆棧大小
#define INTERRUPT_STK_SIZE 256
//任務句柄
TaskHandle_t INTERRUPTTask_Handler;
//任務函數
void interrupt_task(void *p_arg);
main函數
int main(void)
{HAL_Init(); //初始化HAL庫 Stm32_Clock_Init(360,25,2,8); //設置時鐘,180Mhzdelay_init(180); //初始化延時函數LED_Init(); //初始化LED uart_init(115200); //初始化串口TIM3_Init(10000-1,9000-1); //初始化定時器3,定時周期1STIM5_Init(10000-1,9000-1); //初始化定時器5,定時周期1S//創建開始任務xTaskCreate((TaskFunction_t )start_task, //任務函數(const char* )"start_task", //任務名稱(uint16_t )START_STK_SIZE, //任務堆棧大小(void* )NULL, //傳遞給任務函數的參數(UBaseType_t )START_TASK_PRIO, //任務優先級(TaskHandle_t* )&StartTask_Handler); //任務句柄 vTaskStartScheduler(); //開啟任務調度
}
任務函數
//開始任務任務函數
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); //進入臨界區//創建中斷測試任務xTaskCreate((TaskFunction_t )interrupt_task, //任務函數(const char* )"interrupt_task", //任務名稱(uint16_t )INTERRUPT_STK_SIZE, //任務堆棧大小(void* )NULL, //傳遞給任務函數的參數(UBaseType_t )INTERRUPT_TASK_PRIO, //任務優先級(TaskHandle_t* )&INTERRUPTTask_Handler); //任務句柄vTaskDelete(StartTask_Handler); //刪除開始任務taskEXIT_CRITICAL(); //退出臨界區
}//中斷測試任務函數
void interrupt_task(void *pvParameters)
{static u32 total_num=0;while(1){total_num+=1;if(total_num==5) {printf("關閉中斷.............\r\n");portDISABLE_INTERRUPTS(); //關閉中斷delay_xms(5000); //延時5sprintf("打開中斷.............\r\n"); //打開中斷portENABLE_INTERRUPTS();}LED0=~LED0;vTaskDelay(1000);}
}
中斷初始化
TIM_HandleTypeDef TIM3_Handler; //定時器3句柄
TIM_HandleTypeDef TIM5_Handler; //定時器5句柄//通用定時器3中斷初始化
//arr:自動重裝值。
//psc:時鐘預分頻數
//定時器溢出時間計算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定時器工作頻率,單位:Mhz
//這里使用的是定時器3!(定時器3掛在APB1上,時鐘為HCLK/2)
void TIM3_Init(u16 arr,u16 psc)
{TIM3_Handler.Instance = TIM3;TIM3_Handler.Init.Period = arr;TIM3_Handler.Init.Prescaler = psc;TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;TIM3_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;HAL_TIM_Base_Init(&TIM3_Handler);
}//通用定時器5中斷初始化
//arr:自動重裝值(TIM2,TIM5是32位的!!)
//psc:時鐘預分頻數
void TIM5_Init(u32 arr,u16 psc)
{TIM5_Handler.Instance = TIM3;TIM5_Handler.Init.Period = arr;TIM5_Handler.Init.Prescaler = psc;TIM5_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;TIM5_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;HAL_TIM_Base_Init(&TIM5_Handler);
}//定時器底冊驅動,開啟時鐘,設置中斷優先級
//此函數會被HAL_TIM_Base_Init()函數調用
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM3){__HAL_RCC_TIM3_CLK_ENABLE();HAL_NVIC_EnableIRQ(TIM3_IRQn);HAL_NVIC_SetPriority(TIM3_IRQn,4,0);HAL_TIM_Base_Start_IT(&TIM3_Handler); //開啟定時器并更新中斷,以后每次更新中斷,都會調用TIM3_IRQHandler}else if(htim->Instance == TIM5){__HAL_RCC_TIM5_CLK_ENABLE();HAL_NVIC_EnableIRQ(TIM5_IRQn);HAL_NVIC_SetPriority(TIM5_IRQn,5,0);HAL_TIM_Base_Start_IT(&TIM5_Handler); //開啟定時器并更新中斷,以后每次更新中斷,都會調用TIM3_IRQHandler}
}
//定時器3中斷服務函數
void TIM3_IRQHandler(void)
{HAL_TIM_IRQHandler(&TIM3_Handler);
}//定時器5中斷服務函數
void TIM5_IRQHandler(void)
{HAL_TIM_IRQHandler(&TIM5_Handler);
}//回調函數,定時器中斷服務函數調用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM3){printf("TIM3輸出.....\r\n");}else if(htim->Instance == TIM5){printf("TIM5輸出.....\r\n");}
}
運行結果
一開始沒有關閉中斷,所以TIM3和TIM5都正常運行,當interrupt_task運行5次之后,此時由于TIM5的中斷優先級為5,等于configMAX_SYSCALL_INTERRUPT_PRIORITY,因此TIM5會被關閉。但是,TIM3的中斷優先級高于configMAX_SYSCALL_INTERRUPT_PRIORITY,不會被關閉,所以TIM3正常運行,中斷運行5s后調用函數portENABLE_INTERRUPTS重新打開中斷,重新打開中斷后TIM5恢復運行。