學習內容
中斷概念
中斷是計算機系統中一種重要的事件驅動機制,用于在特定條件下打斷正在執行的程序,并跳轉到預定義的中斷處理程序中執行特定的操作。當發生中斷時,處理器會立即中止當前正在執行的指令,保存當前的執行狀態,并執行相應的中斷處理程序。
中斷可以由多種事件觸發,例如硬件設備的狀態改變、定時器溢出、外部信號等。常見的中斷事件包括鍵盤輸入、鼠標移動、網絡數據到達等。
中斷的作用是實現對實時事件的及時響應。通過中斷,計算機系統能夠在發生特定事件時立即中斷當前任務,執行與該事件相關的處理程序,以確保及時處理和響應事件。中斷能夠提高系統的實時性、可靠性和可處理性。
中斷的處理過程包括以下步驟:
- 中斷觸發:某個事件觸發中斷,如硬件設備發出中斷請求信號。
- 中斷響應:處理器檢測到中斷請求,并立即中止當前執行的指令。
- 保存現場:處理器保存當前的執行狀態(如程序計數器、寄存器等),以便在中斷處理完成后能夠恢復執行。
- 中斷服務程序執行:處理器跳轉到預定義的中斷服務程序(ISR),執行與中斷相關的操作。
- 中斷處理完成:中斷服務程序執行完成后,處理器恢復之前保存的執行狀態,并繼續執行原來的程序。
通過合理使用中斷,可以實現并發處理、異步事件處理和實時響應,提高系統的性能和可靠性。中斷在各種計算機系統中廣泛應用,包括嵌入式系統、操作系統和實時系統等。
?
ARM中的中斷優先級
ARM Cortex-M 使用了 4 位寬的寄存器來配置中斷的優先等級,這個寄存器就是中斷優先級配置寄存器。
GD32中,分為搶占優先級和子優先級。(數值越小,優先級越高)
分組 | 搶占優先級 | 響應優先級 |
分組0 | 0(可取值為0) | 4(可取值為0到15) |
分組1 | 1(可取值為0到1) | 3(可取值為0到7) |
分組2 | 2(可取值為0到3) | 2(可取值為0到3) |
分組3 | 3(可取值為0到7) | 1(可取值為0到1) |
分組4 | 4(可取值為0到15) | 0(可取值為0) |
通常我們在代碼中來進行配置:
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
全局中斷的意思是,把全局的中斷優先級大概定義到一個區間,具體到哪種類型的中斷,自行去配置合適的優先級。因此,我們對于一些中斷源可以通過以下代碼來配置優先級:
nvic_irq_enable(xxx_irqn, 6, 0); // >= 5
上面中的全局中斷配置,配置為搶占優先級為4,響應優先級為0,那么就把具體中斷源的區間給框定了。搶占優先級取值介于0到15,響應優先級只能為0。因此,上面配置具體中斷源優先級為5和0,在全局中斷源的范疇內。當然你設置一個范圍不在范疇內,是不生效,或者會出現其他問題。
通常在FreeRTOS配置外設的中斷優先級都在5及以上 (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY)
FreeRTOS的中斷優先級
在FreeRTOSConfig.h
中,configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
配置了中斷優先級:
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
默認位置為5。
在GD32中,優先級的數值越小,優先級越高。通過優先級分組可以知道,優先級分為0到15個等級。那么FreeRTOS中的這個優先級是什么含義呢?
開啟中斷和關閉中斷:
portENABLE_INTERRUPTS();
portDISABLE_INTERRUPTS();
優先級配置注意
低于或等于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
優先級的中斷函數(數值越大,優先級越低),FreeRTOS的API函數才能對其生效。例如configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
為5,則中斷函數配置的參數必須為[5, 15]的任意值。(所有優先級均為搶占優先級時)
?
中斷優先級和任務優先級
示例
- 創建timer中斷,每秒打印數據
- 創建任務,掃描按鍵事件
- 當按鍵按下,關閉所有中斷,當按鍵再次按下,開啟中斷
- 觀察效果
注意:vTaskDelay和delay_1ms的有區別
//main.c
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "usart0.h"TaskHandle_t start_handler;
TaskHandle_t task_key_handler;void task_key(void *pvParameters) {uint32_t flag = 0;FlagStatus pre_state = RESET;while(1) {FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);if(SET == state && pre_state == RESET) {// 當前高電平, 上一次為低電平,按下pre_state = state;if(flag == 0) {printf("disable \r\n");// 關閉中斷portDISABLE_INTERRUPTS();} else if(flag == 1) {// 開啟中斷printf("enable \r\n");portENABLE_INTERRUPTS();}flag++;if(flag > 1) flag = 0;} else if(RESET == state && pre_state == SET) {// 當前高電平, 上一次為低電平,抬起pre_state = state;}delay_1ms(20);}
}void Usart0_recv(uint8_t *data, uint32_t len) {printf("recv: %s\r\n", data);
}static void GPIO_config() {// 時鐘初始化rcu_periph_clock_enable(RCU_GPIOA);// 配置GPIO模式gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
}#define PRESCALER 10000
#define FREQ 1static void TIMER_config() {// 時鐘配置rcu_periph_clock_enable(RCU_TIMER5);// 復位定時器timer_deinit(TIMER5);rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);timer_parameter_struct tps;timer_struct_para_init(&tps);tps.prescaler = PRESCALER - 1; // 分頻系數 240 000 000tps.period = SystemCoreClock / PRESCALER / FREQ - 1; // 周期計數值 1Hztimer_init(TIMER5, &tps);nvic_irq_enable(TIMER5_DAC_IRQn, 5, 0);timer_interrupt_enable(TIMER5, TIMER_INT_UP);timer_enable(TIMER5);
}void TIMER5_DAC_IRQHandler(void) {if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_UP)) {printf("timer\r\n");}//清除中斷標志位timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP);
}void start_task(void *pvParameters) {GPIO_config();Usart0_init();TIMER_config();printf("start\r\n");taskENTER_CRITICAL();xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);vTaskDelete(start_handler);taskEXIT_CRITICAL();
}int main(void)
{nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);xTaskCreate(start_task, "start_task", 128, NULL, 1, &start_handler);vTaskStartScheduler();while(1) {}
}
?