一、什么是中断
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