一、什么是中斷
1.1 基本概念
中斷,在單片機中占有非常重要的地位,幾乎任何一款單片機都會有中斷。。代碼默認地從上向下執行,遇到條件或者其他語句,會按照指定的地方跳轉。而在單片機執行代碼的過程中,難免會有一些突發的情況需要處理,這樣就會打斷當前的代碼,待處理完突發情況之后,程序會回到被打斷的地方繼續執行。
1.2 關於STM32的中斷
STM32具有十分強大的中斷系統,將中斷分為了兩個類型:內核異常和外部中斷。並將所有中斷通過一個表編排起來,下面是stm32中斷向量表的部分內容:
上圖-3到6這個區域被標黑了,這個區域就是內核異常。內核異常不能夠被打斷,不能被設置優先級(也就是說優先級是凌駕於外部中斷之上的)。常見的內核異常有以下幾種:復位(reset),不可屏蔽中斷(NMI),硬錯誤(Hardfault),其他的也可以在表上找到。
從第7個開始,后面所有的中斷都是外部中斷。外部中斷是我們必須學習掌握的知識,包含線中斷,定時器中斷,IIC,SPI等所有的外設中斷,可配置優先級。外部中斷的優先級分為兩種:搶占優先級和響應優先級。
什么是搶占優先級?
搶占優先級比較霸道,一言不和就插隊。搶占優先級高的,能夠打斷優先級低的任務,等優先級較高的任務執行完畢后,再回來繼續執行之前的任務。所以當存在多個搶占優先級不同的任務時,很有可能會產生任務的嵌套。
什么是響應優先級?
響應優先級則稍微謙遜些,比較有禮貌。響應優先級又被稱為次優先級,若兩個任務的搶占式優先級一樣,那么響應優先級較高的任務則先執行,且在執行的同時不能被下一個響應優先級更高的任務打斷,所以我說它比較有有禮貌
1.3中斷發生的過程
1.4中斷的作用
- 速度匹配:可以解決快速的CPU與慢速的外部設備之間傳送數據的矛盾。
- 分時操作:CPU可以分時為多個外部設備服務,提高計算機的利用率。
- 實時響應:CPU能夠及時處理應用系統的隨機事件,增強系統的實時性。
- 可靠性高:CPU可以處理設備故障及掉電等突發事件,提高系統可靠性。
2 HAL庫
2.1HAL的中斷
例如51單片機中的中斷函數,之前使用標准庫編寫程序時,中斷程序(函數)由我們自己實現。
而STM32的HAL庫的中斷處理函數是按照HAL處理機制來實現,如USART1,統一由HAL_UART_IRQHandler來進行處理,如下圖:
其它大部分外設(TIM、SPI、CAN...)中斷都類似,HAL進行統一處理。
也就是說,HAL已經幫我們把中斷處理函數寫好了,我們只需要調用相應函數來編寫應用程序就行了。
HAL_xxx_IRQHandler里面做了哪些處理? 我們以STM32F1的HAL_UART_IRQHandler為例:
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) { uint32_t isrflags = READ_REG(huart->Instance->SR); uint32_t cr1its = READ_REG(huart->Instance->CR1); uint32_t cr3its = READ_REG(huart->Instance->CR3); uint32_t errorflags = 0x00U; uint32_t dmarequest = 0x00U; /* If no error occurs */ errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE)); if(errorflags == RESET) { /* UART in mode Receiver -------------------------------------------------*/ if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { UART_Receive_IT(huart); return; } } /* If some errors occur */ if((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET))) { /* · ·刪減了部分代碼 · */ } /* End if some error occurs */ /* UART in mode Transmitter ------------------------------------------------*/ if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)) { UART_Transmit_IT(huart); return; } /* UART in mode Transmitter end --------------------------------------------*/ if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET)) { UART_EndTransmit_IT(huart); return; } }
這些和我們之前編寫的中斷處理函數是不是有類似之處?
這是無非就是接收中斷、發送中斷、錯誤中斷等一系列處理。只是這里又進行了再次封裝,比如接收中斷UART_Receive_IT。
當然,這個UART_Receive_IT接收中斷實現方式又可能存在不同。像F0、F1...就是直接調用這個接收中斷函數來進一步處理。
像L0、G0...是通過執行指針函數RxISR來進一步處理。G0的接收中斷處理為:huart->RxISR(huart);
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) { //刪除了前面代碼 /* If no error occurs */ errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE)); if (errorflags == 0U) { /* UART in mode Receiver ---------------------------------------------------*/ if (((isrflags & USART_ISR_RXNE_RXFNE) != 0U) && (((cr1its & USART_CR1_RXNEIE_RXFNEIE) != 0U) || ((cr3its & USART_CR3_RXFTIE) != 0U))) { if (huart->RxISR != NULL) { huart->RxISR(huart); } return; } } //刪除了后面代碼 }
2.2回調函數
在HAL庫中存在大量類似HAL_XXX_XXXCallback這樣的函數,這些都是回調函數。
回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。
回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。
三、進行一些嘗試(控制LED燈)
在STMCUBEX,新建一個項目,例如圖中用的單片機是STM32103C8
點擊sys,將debug選項改為Serial Wire
然后在Rcc里的High Speed Clock 中選擇Crystal/Ceramic Resonator
將PB0選為外部中斷觸發器(GPIO_EXTI0),PA1是控制led燈的,和將它選擇為GPIO_output
選擇PLLCLK,然后將后面的晶振頻率最大值改為72M赫茲
project里把toolchain那里改為MDK-ARM,版本選擇最新
選擇生成初始化文件,然后選擇生成代碼
創建過程
創建成功
打開項目,來到keil5,進入到mian.c里,接下來就是剛剛提到的在回調函數里寫代碼了,將回調函數重寫一遍就行了,代碼如下:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ GPIO_PinState b0_pin = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0); // 讀取b0的狀態 b0_pin=1-b0_pin; switch (GPIO_Pin){//判斷引腳 case GPIO_PIN_0: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1,1-b0_pin); // 將a1寫入與b0相同的電位 break; } }
結果展示:
四、中斷實現串口通信
設置大多數和上述相同 下面只展示不同的部分
project manager里的步驟也和上一個是一樣的,接下來就是在keil5里打開項目,進入main.c文件,在里面定義如下數據,注意不是在main函數里,而是在頭文件后
uint8_t aRxBuffer;//接收緩沖中斷 uint8_t Uart1_RxBuff[256];//接收緩沖 uint8_t Uart1_Rx_Cnt=0;//接收緩沖計數 uint8_t cAlmStr[]="DataOverflow(>256)";
在定義了
這個數據類型后插入代碼
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(Uart1_Rx_Cnt >= 255) //溢出判斷 { Uart1_Rx_Cnt = 0; memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); HAL_UART_Transmit(&huart1, (uint8_t *)&cAlmStr, sizeof(cAlmStr),0xFFFF); } else { Uart1_RxBuff[Uart1_Rx_Cnt++] = aRxBuffer; //接收數據轉存 if((Uart1_RxBuff[Uart1_Rx_Cnt-1] == 0x0A)||(Uart1_RxBuff[Uart1_Rx_Cnt-2] == 0x0D)) //判斷結束位 { HAL_UART_Transmit(&huart1, (uint8_t *)&Uart1_RxBuff, Uart1_Rx_Cnt,0xFFFF); //將收到的信息發送出去 Uart1_Rx_Cnt = 0; memset(Uart1_RxBuff,0x00,sizeof(Uart1_RxBuff)); //清空數組 } } HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1); //再開啟接收中斷 }
然后在主函數寫一個接收中斷函數
int main(void) { //初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); //接收中斷函數 HAL_UART_Receive_IT(&huart1,(uint8_t*)&aRxBuffer,1); while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
結果如下:
我們發送什么 就會接受到什么。
五、總結
中斷函數的學習過程是一次很大的自我挑戰,但是中斷函數在單片機中的運用可以說是無比的重要,他極大程度的節約了單片機的使用資源,學會了使用中斷單片機可以不同再像之前一樣,傻傻的等待我們的指令再去進行操作,這次的學習過程讓我受益匪淺。
六、參考鏈接
https://blog.csdn.net/m0_58414679/article/details/121060073?spm=1001.2014.3001.5501
https://blog.csdn.net/DP29syM41zyGndVF/article/details/113804917