STM32CubeMX学习笔记31---FreeRTOS中断管理

一、简介

1、异常与中断的基本概念

        异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理, 轻则系统出错,重则会导致系统毁灭性瘫痪。所以正确地处理异常,避免错误的发生是提 高软件鲁棒性(稳定性)非常重要的一环,对于实时系统更是如此。

       异常是指任何打断处理器正常执行,并且迫使处理器进入一个由有特权的特殊指令执 行的事件。异常通常可以分成两类:同步异常和异步异常。由内部事件(像处理器指令运 行产生的事件)引起的异常称为同步异常,例如造成被零除的算术运算引发一个异常,又 如在某些处理器体系结构中,对于确定的数据尺寸必须从内存的偶数地址进行读和写操作。 从一个奇数内存地址的读或写操作将引起存储器存取一个错误事件并引起一个异常(称为 校准异常)。

      异步异常主要是指由于外部异常源产生的异常,是一个由外部硬件装置产生的事件引 起的异步异常。同步异常不同于异步异常的地方是事件的来源,同步异常事件是由于执行 某些指令而从处理器内部产生的,而异步异常事件的来源是外部硬件装置。例如按下设备 某个按钮产生的事件。同步异常与异步异常的区别还在于,同步异常触发后,系统必须立 刻进行处理而不能够依然执行原有的程序指令步骤;而异步异常则可以延缓处理甚至是忽 略,例如按键中断异常,虽然中断异常触发了,但是系统可以忽略它继续运行(同样也忽 略了相应的按键事件)。

      中断,中断属于异步异常。所谓中断是指中央处理器 CPU 正在处理某件事的时候,外 部发生了某一事件,请求 CPU 迅速处理,CPU暂时中断当前的工作,转入处理所发生的事 件,处理完后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。

      中断能打断任务的运行,无论该任务具有什么样的优先级,因此中断一般用于处理比 较紧急的事件,而且只做简单处理,例如标记该事件,在使用 FreeRTOS 系统时,一般建 议使用信号量、消息或事件标志组等标志中断的发生,将这些内核对象发布给处理任务, 处理任务再做具体处理。

      通过中断机制,在外设不需要 CPU 介入时,CPU 可以执行其他任务,而当外设需要 CPU 时通过产生中断信号使 CPU 立即停止当前任务转而来响应中断请求。这样可以使 CPU 避免把大量时间耗费在等待、查询外设状态的操作上,因此将大大提高系统实时性以 及执行效率。

      此处读者要知道一点,FreeRTOS 源码中有许多处临界段的地方,临界段虽然保护了关 键代码的执行不被打断,但也会影响系统的实时,任何使用了操作系统的中断响应都不会 比裸机快。比如,某个时候有一个任务在运行中,并且该任务部分程序将中断屏蔽掉,也 就是进入临界段中,这个时候如果有一个紧急的中断事件被触发,这个中断就会被挂起, 不能得到及时响应,必须等到中断开启才可以得到响应,如果屏蔽中断时间超过了紧急中断能够容忍的限度,危害是可想而知的。所以,操作系统的中断在某些时候会有适当的中 断延迟,因此调用中断屏蔽函数进入临界段的时候,也需快进快出。当然 FreeRTOS 也能 允许一些高优先级的中断不被屏蔽掉,能够及时做出响应,不过这些中断就不受系统管理, 也不允许调用 FreeRTOS 中与中断相关的任何 API 函数接口。

FreeRTOS 的中断管理支持:

● 开/关中断。

●恢复中断。

●中断使能。

●中断屏蔽。

●可选择系统管理的中断优先级。

2、中断延迟

即使操作系统的响应很快了,但对于中断的处理仍然存在着中断延迟响应的问题,我们称之为中断延迟(Interrupt Latency) 。

中断延迟是指从硬件中断发生到开始执行中断处理程序第一条指令之间的这段时间。也就是:系统接收到中断信号到操作系统作出响应,并完成换到转入中断服务程序的时间。也可以简单地理解为:(外部)硬件(设备)发生中断,到系统执行中断服务子程序(ISR)的第一条指令的时间。

中断的处理过程是:外界硬件发生了中断后,CPU 到中断处理器读取中断向量,并且查找中断向量表,找到对应的中断服务子程序(ISR)的首地址,然后跳转到对应的 ISR 去做相应处理。这部分时间,我称之为:识别中断时间。

在允许中断嵌套的实时操作系统中,中断也是基于优先级的,允许高优先级中断抢断正在处理的低优先级中断,所以,如果当前正在处理更高优先级的中断,即使此时有低优先级的中断,也系统不会立刻响应,而是等到高优先级的中断处理完之后,才会响应。而即使在不支持中断嵌套,即中断是没有优先级的,中断是不允许被中断的,所以,如果当前系统正在处理一个中断,而此时另一个中断到来了,系统也是不会立即响应的,而只是等处理完当前的中断之后,才会处理后来的中断。此部分时间,我称其为:等待中断打开时间。

在操作系统中,很多时候我们会主动进入临界段,系统不允许当前状态被中断打断,故而在临界区发生的中断会被挂起,直到退出临界段时候打开中断。此部分时间,我称其为:关闭中断时间。

中断延迟可以定义为,从中断开始的时刻到中断服务例程开始执行的时刻之间的时间段。中断延迟 = 识别中断时间 + [等待中断打开时间] + [关闭中断时间]。
注意:“[ ]”的时间是不一定都存在的,此处为最大可能的中断延迟时间。

运作机制:
当中断产生时,处理机将按如下的顺序执行:

  1. 保存当前处理机状态信息
  2. 载入异常或中断处理函数到 PC 寄存器
  3. 把控制权转交给处理函数并开始执行
  4. 当处理函数执行完成时,恢复处理器状态信息
  5. 从异常或中断中返回到前一个程序执行点

中断使得 CPU 可以在事件发生时才给予处理,而不必让 CPU 连续不断地查询是否有相应的事件发生。通过两条特殊指令:关中断和开中断可以让处理器不响应或响应中断,在关闭中断期间,通常处理器会把新产生的中断挂起,当中断打开时立刻进行响应,所以会有适当的延时响应中断,故用户在进入临界区的时候应快进快出。中断发生的环境有两种情况:在任务的上下文中,在中断服务函数处理上下文中。

  • 任务在工作的时候,如果此时发生了一个中断,无论中断的优先级是多大,都会打断当前任务的执行,从而转到对应的中断服务函数中执行。

  • 在执行中断服务例程的过程中,如果有更高优先级别的中断源触发中断,由于当前处于中断处理上下文环境中,根据不同的处理器构架可能有不同的处理方式,比如新的中断等待挂起直到当前中断处理离开后再行响应;或新的高优先级中断打断当前中断处理过程,而去直接响应这个更高优先级的新中断源。后面这种情况,称之为中断嵌套。在硬实时环境中,前一种情况是不允许发生的,不能使响应中断的时间尽量的短。而在软件处理(软实时环境)上,FreeRTOS 允许中断嵌套,即在一个中断服务例程期间,处理器可以响应另外一个优先级更高的中断。

3、 FreeRTOS中断管理

ARM Cortex-M 系列内核的中断是由硬件管理的,而 FreeRTOS 是软件,它并不接管由硬件管理的相关中断(接管简单来说就是,所有的中断都由 RTOS 的软件管理,硬件来了中断时,由软件决定是否响应,可以挂起中断,延迟响应或者不响应),只支持简单的开关中断等,所以 FreeRTOS 中的中断使用其实跟裸机差不多的,需要我们自己配置中断,并且使能中断,编写中断服务函数,在中断服务函数中使用内核 IPC 通信机制,一般建议使用信号量、消息或事件标志组等标志事件的发生,将事件发布给处理任务,等退出中断后再由相关处理任务具体处理中断。

用户可以自定义配置 系统可管理的最高中断优 先 级 的宏定义 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY ,它是用于配置内核中的 basepri 寄存器的,当 basepri 设置为某个值的时候,NVIC 不会响应比该优先级低的中断,而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为 5 的时候,中断优先级数值在 0、1、2、3、4 的这些中断是不受 FreeRTOS 屏蔽的,也就是说即使在系统进入临界段的时候,这些中断也能被触发而不是等到退出临界段的时候才被触发,当然,这些中断服务函数中也不能调用 FreeRTOS 提供的 API 函数接口,而中断优先级在 5 到 15 的这些中断是可以被屏蔽的,也能安全调用 FreeRTOS 提供的 API 函数接口。

ARM Cortex-M NVIC 支持中断嵌套功能:当一个中断触发并且系统进行响应时,处理器硬件会将当前运行的部分上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR,R0,R1,R2,R3 以及 R12 寄存器。当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样的会打断当前运行的中断服务例程,然后把老的中断服务例程上下文的 PSR,R0,R1,R2,R3 和 R12 寄存器自动保存到中断栈中。这些部分上下文寄存器保存到中断栈的行为完全是硬件行为,这一点是与其他 ARM 处理器最大的区别(以往都需要依赖于软件保存上下文)。

另外,在 ARM Cortex-M 系列处理器上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理。而在 ARM7、ARM9 中,一般是先跳转进入 IRQ 入口,然后再由软件进行判断是哪个中断源触发,获得了相对应的中断服务例程入口地址后,再进行后续的中断处理。ARM7、ARM9 的好处在于,所有中断它们都有统一的入口地址,便于 OS 的统一管理。而 ARM Cortex-M 系列处理器则恰恰相反,每个中断服务例程必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义(或在起始代码中给出),在 STM32 上,默认采用起始代码给出:

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler

; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
………

 FreeRTOS 在 Cortex-M 系列处理器上也遵循与裸机中断一致的方法,当用户需要使用自定义的中断服务例程时,只需要定义相同名称的函数覆盖弱化符号即可。所以,FreeRTOS 在 Cortex-M 系列处理器的中断控制其实与裸机没什么差别。

4、FreeRTOS开关中断

FreeRTOS开关中断函数为portENABLE_INTERRUPTS()和portDISABLE_INTERRUPTS(),这两个函数其实是宏定义,在portmacro.h中有定义,如下:

#define portDISABLE_INTERRUPTS()                     vPortRaiseBASEPRI() 
#define portENABLE_INTERRUPTS()                     vPortSetBASEPRI(0)

函数vPortBASEPRI()传递了一个0,这就对应了上面说到的,开启中断是将0写入BASEPRI寄存器。

5 、临界区

CMSIS-RTOS没有临界区,但FreeRTOS与临界段代码保护有关的函数有4个:taskENTER_CRITICAL()、
和taskEXIT_CRITICAL()、taskENTER_CRITICAL_FROM_ISR()、taskEXIT_CRITICAL_FROM_ISR(),这四个函数其实是宏定义,在task.h文件中有定义。这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。所以必要时要是加上。

(1) 任务级临界段代码保护
taskENTER_CRITICAL()和taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个函数是成对使用的,这函数的定义如下:

#define taskENTER_CRITICAL()            portENTER_CRITICAL() 
#define taskEXIT_CRITICAL()             portEXIT_CRITICAL()

 而portENTER_CRITICAL()和portEXIT_CRITICAL()也是宏定义,在文件 portmacro.h中有定义。
任务级临界代码保护使用方法如下:

void CriticalTask_TEST(void const * argv)
{
	taskENTER_CRITICAL();                                                       //进入临界区
	total_num += 0.01f;   
	printf("total_num 的值为: %.4f\r\n",total_num);
	taskEXIT_CRITICAL();                                                        //退出临界区
	vTaskDelay(1000);
}

当进入临界区时,中断被屏蔽,临界区代码无法被打断,只有当所有的临界段代码都退出以后才会使能中断!
注:当进入临界区时,优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及响应,所以临界区代码一定要精简。

2 中断级临界段代码保护
函数taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中断级别临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY。这两个函数在文件task.h中有如下定义:

#define taskENTER_CRITICAL_FROM_ISR()        portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR(x)        portCLEAR_INTERRUPT_MASK_FROM_ISR(x)

中断级临界代码保护使用方法如下:

void TIM3_IRQHandler(void) 
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
	{
		status_value=taskENTER_CRITICAL_FROM_ISR();            //进入临界区
		total_num += 1;
		printf("float_num 的值为: %d\r\n",total_num);
		taskEXIT_CRITICAL_FROM_ISR(status_value);              //退出临界区
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);   //清除中断标志位
}

 二、STM32CubeMX设置

 1、配置RCC、USART1、时钟72M
2、配置SYS,将Timebase Source修改为除滴答定时器外的其他定时器。
3、初始化LED的两个引脚
4、开启FreeRTOS,v1与v2版本不同,一般选用v1即可
5、创建两个线程LED1,LED2。

以上步骤可参考:STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)-CSDN博客

 6、创建一个消息队列

  • Queue Name: 队列名称
  • Queue Size: 队列能够存储的最大单元数目,即队列深度
  • Queue Size: 队列中数据单元的长度,以字节为单位
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Buffer Name: 缓冲区名称
  • Buffer Size: 缓冲区大小
  • Conrol Block Name: 控制块名称
 7、打开USART1中断
8、将按键S1和S2设置成外部中断模式

可参考:STM32CubeMX学习笔记4-外部中断_stm32f407 外部中断初始化 cubemx-CSDN博客

9、生成代码

三、程序编程

程序逻辑:

(1)、按键按下触发外部中断时往消息队列发送对应的按键号

void delay(int ms)
{
	int i,j;
	for(i=0;i<ms;i++)
		for(j=0;j<110;j++);
	
}
 
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
 
	delay(10);  //延时消抖,不建议
	switch(GPIO_Pin){
        case GPIO_PIN_0:
					
					if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==1)
 //           printf("STM32F103 EXTI test this key is S4\r\n"); //A0
					osMessagePut(myQueue01Handle,4,0);
           break;
        case GPIO_PIN_2:
						
						if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2)==0)
//							printf("STM32F103 EXTI test this key is S3\r\n"); //E2
						osMessagePut(myQueue01Handle,2,0);
            break;
        case GPIO_PIN_3:
						
						if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==0)
//							printf("STM32F103 EXTI test this key is S2\r\n"); //E3
						osMessagePut(myQueue01Handle,3,0);
            break;
        case GPIO_PIN_4:
						
						if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0)
//              printf("STM32F103 EXTI test this key is S1\r\n"); //E4
						osMessagePut(myQueue01Handle,1,0);
            break;
    }
}

(2)、串口空闲中断触发时往消息队列发送数据5

uint8_t RxMsg[200];//只需存储数组即可

//空闲中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size){
  if(huart == &huart1){       //判断是否是串口一的中断
    __HAL_UNLOCK(huart);      //解锁串口状态
		osMessagePut(myQueue01Handle,5,0);
	  	
    HAL_UARTEx_ReceiveToIdle_IT(&huart1,RxMsg,200-1); //再次开启空闲中断
  }
}

/*
注:需要在main函数中先开启空闲中断!!
HAL_UARTEx_ReceiveToIdle_IT(&huart1,RxMsg,200-1); //开启空闲中断;
*/

(3)、任务LED1等待消息队列,并将输出对应数据

void LED1_Task1(void const * argument)
{
  /* USER CODE BEGIN LED1_Task1 */
  /* Infinite loop */

	osEvent event;
	int i=0;
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		printf("led1 run ..%d\n",i++);	
		event=osMessageGet(myQueue01Handle,osWaitForever);//等待消息队列
		if(osEventMessage==event.status)
		{
			if(event.value.v==5) //串口
			{
				printf("串口接收到的数据为:%s\r\n",RxMsg);
				memset(RxMsg,0,200);//清除接收到的数据	
			}
			else
			{
				printf("按下的按键为:S%d\r\n",event.value.v);
			}
		}
		
    osDelay(500);
  }
  /* USER CODE END LED1_Task1 */
}

(4)、任务LED2计数到10时进入临界区,计数到20时退出临界区

void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
	 osStatus xReturn = osErrorValue;
	int i=0;
  for(;;)
  { 
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);   //LED1状态每500s翻转一次
		printf("led run..%d\n",i++);

		if(i==10)
			 taskENTER_CRITICAL(), printf("进入临界区 \r\n");                                                   //进入临界区	
		if(i==20)
				taskEXIT_CRITICAL(),printf("退出临界区 \r\n");                                                      //退出临界区
		
	osDelay(1000);
    
  }
  /* USER CODE END LED2_Task03 */
}

四、 下载验证:

程序编译无误后下载到板子上,可以看到LED2进入临界区后屏蔽了其他任务的运行,连delay也会被屏蔽,当串口接收到数据后,LED1运行,并将数据返回。当按下按键时,LED1运行并输出对应的按键号。

 五、参考文献

STM32CubeMX学习笔记(36)——FreeRTOS实时操作系统使用(中断管理)_total_heap_size-CSDN博客

韦东山freeRTOS系列教程之【第十一章】中断管理(Interrupt Management)_c2000 rtos-CSDN博客

FreeRTOS中断管理 基于STM32_stm32 freertos 按键中断-CSDN博客

stm32cubemx hal学习记录:FreeRTOS中断管理_cubemx hall库外部中断如何使用-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值