STM32CubeMX+FreeRTOS学习笔记(二)

FreeRTOS中断配置和临界段

中断简介

STM32的中断很重要,它不同于if、switch等条件检测语句,它是由硬件产生的。在程序中,代码默认按顺序执行,当遇到中断触发时,就会打断当前执行代码并且调到中断服务函数中去执行中断任务。
这种情况在汇编语言中的表现形式一般是:

  1. 保存用到的寄存器(保护现场)
  2. 处理终端
  3. 回复用到的寄存器(恢复现场)
  4. 中断返回

Cortex-M处理器内核提供了一个用于中断管理的嵌套向量中断控制器(NVIC),最多支持240个中断请求(IRQ)、一个不可屏蔽中断(NMI)、一个滴答定时器中断(Systick)和多个系统异常(硬件错误、总线错误、使用错误、PendSV等等)。Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器, 这些寄存器大多数都在 NVIC 和系统控制块(SCB)中。

中断优先级

Cortex-M处理器有三个固定优先级和256个可编程优先级,最多有128个抢占优先级,但具体数目需要由芯片厂商自行设定。绝大多数芯片都会进行剪裁,一般优先级个数是8、16和32个。Cortex-M处理器最多有256个可编程优先级,说明控制优先级的寄存器共有8位,这个优先级是MSB(最高位)对齐的,所以一般都是前3或4位是用来表达优先级的。
例如:

Bit7Bit6Bit5Bit4Bit3Bit2Bit1Bit0
置位置位置位置位返回0返回0返回0返回0

STM32就使用了前四位作为优先级表达位,因此STM32就有16个可编程优先级:0x00(最高优先级)、0x10-0xF0。此外中断可以被屏蔽,这时需要用到3个特殊寄存器:PRIMASK、FAULTMASK、BASEPRI。

PRIMASK 和 FAULTMASK 寄存器

在需要进行对时序严格要求的任务时,我们需要暂时屏蔽所有中断来保证任务的精确完成,这时可以使用 PRIMASK 寄存器,PRIMASK 用于禁止除复位 、NMI 和 HardFalut 外的所有异常和中断,可以通过汇编语言CPS指令来控制PRIMASK。

CPSIE I; //清除 PRIMASK(使能中断) 
CPSID I; //设置 PRIMASK(禁止中断)

以上代码等同于

MOVS    R0,   #0
MSR  PRIMASK, R0 ;//将 0 写入 PRIMASK 以使能中断

MOVS    R0,   #1
MSR  PRIMASK, R0 ;//将 1 写入 PRIMASK 禁止所有中断

BASEPRI 寄存器

当需要精准控制低于哪些优先级的中断需要关闭时,这时就可以使用BASEPRI寄存器,这个寄存器可以进行更加准确的屏蔽。例如屏蔽优先级0x80及以下的中断:

MOV         R0,         #0X60 
MSR         BASEPRI,      R0

解除屏蔽只需要将0写入BASEPRI寄存器即可

MOV         R0,          #0
MSR         BASEPRI,      R0

FreeRTOS中断配置宏

configPRIO_BITS

优先级表达位,STM32为前4位,所以此宏为4

configLIBRARY_LOWEST_INTERRUPT_PRIORITY

此宏表示最低优先级,STM32最低优先级为0~15,所以此宏为15,当处理器为其他芯片时,需要按手册修改。

configKERNEL_INTERRUPT_PRIORITY

该宏定义如下:

#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

该宏是设置内核中断优先级,内容是将 configLIBRARY_LOWEST_INTERRUPT_PRIORITY左移四位,因为中断表达需要用高4位来表达,所以需要左移4位。除此外,也可以直接写为:

#define configKERNEL_INTERRUPT_PRIORITY                 0xF0 

宏 configKERNEL_INTERRUPT_PRIORITY 用来设置 PendSV 和滴答定时器的中断优先级, port.c 中有如下定义:

#define portNVIC_PENDSV_PRI         ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI        ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )

PendSV和SysTcik的中断优先级设置是操作0xE000-0xED20地址的,这样一次写入的是个32位的数据,SysTick和PendSV的优先级寄存器分别对应这个32位数据的最高8位和次高8位,因此是一个左移16位,一个左移24位。

configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

此宏定义了FreeRTOS可管理的最大优先级,程序生成后默认设置的是5,所以优先级高于5(优先级数小于5)的都不归FreeRTOS管。

configMAX_SYSCALL_INTERRUPT_PRIORITY

此宏是由configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY左移4位而来的,原因和宏configKERNEL_INTERRUPT_PRIORITY一样。此宏设置好以后,低于此优先级的中断可以安全的调用 FreeRTOS的API函数,高于此优先级的中断FreeRTOS是不能禁止的,中断服务函数也不能调用FreeRTOS的API函数!

FreeRTOS开关中断

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

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

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

临界段代码

临界段代码也称为临界区,是指哪些必须完成运行,且不能中途打断的代码,比如某些外设的初始化过程需要遵守严格的时序,因此初始化过程不能被打断,所以我们在进行外设初始化时可以添加临界段代码。
FreeRTOS与临界段代码保护有关的函数有4个:taskENTER_CRITICAL()、
和taskEXIT_CRITICAL()、taskENTER_CRITICAL_FROM_ISR()、taskEXIT_CRITICAL_FROM_ISR(),这四个函数其实是宏定义,在task.h文件中有定义。这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。

任务级临界段代码保护

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 的中断得不到及响应,所以临界区代码一定要精简。

中断级临界段代码保护

函数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);   //清除中断标志位
}

FreeRTOS中断测试实验

创建工程

### 任务级临界段代码保护
打开串口1,参数默认。
在这里插入图片描述
配置是时钟树
在这里插入图片描述

配置定时器3和定时器5
在这里插入图片描述
在这里插入图片描述
配置定时器中断优先级
在这里插入图片描述
配置FreeRTOS并创建任务
在这里插入图片描述
创建工程
在这里插入图片描述
在这里插入图片描述

查看代码

打开工程可以在stm32f1xx_hal_cortex.c文件中找到定制器中断优先级配置代码。

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
  if(tim_baseHandle->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspInit 0 */
  /* USER CODE END TIM3_MspInit 0 */
    /* TIM3 clock enable */
    __HAL_RCC_TIM3_CLK_ENABLE();
    /* TIM3 interrupt Init */
    HAL_NVIC_SetPriority(TIM3_IRQn, 4, 0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);
  /* USER CODE BEGIN TIM3_MspInit 1 */
  /* USER CODE END TIM3_MspInit 1 */
  }
  else if(tim_baseHandle->Instance==TIM5)
  {
  /* USER CODE BEGIN TIM5_MspInit 0 */
  /* USER CODE END TIM5_MspInit 0 */
    /* TIM5 clock enable */
    __HAL_RCC_TIM5_CLK_ENABLE();
    /* TIM5 interrupt Init */
    HAL_NVIC_SetPriority(TIM5_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(TIM5_IRQn);
  /* USER CODE BEGIN TIM5_MspInit 1 */
  /* USER CODE END TIM5_MspInit 1 */
  }
}

可以从中看出TIM3优先级为4,大于FreeRTOS所能管理的优先级,TIM优先级为5,恰好在FreeRTOS所能管理的优先级之中。
先在main.c中将定时器回调函数代码补全:

定时器回调函数代码补全

打开main.c文件,补全HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)函数中缺少的定时器3和定时器5的代码。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */
	if(htim->Instance == TIM3)
	{
		printf("TIM3 输出.......\r\n");
	}
  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */
	if(htim->Instance == TIM5)
	{
		printf("TIM5 输出.......\r\n");
	}
  /* USER CODE END Callback 1 */
}

编写测试代码

打开freertos.c文件,找到void extitask_test(void const * argument)函数,添加自己的代码:

void extitask_test(void const * argument)
{
	static uint32_t total_num = 0;
  /* USER CODE BEGIN extitask_test */
  /* Infinite loop */
  for(;;)
  {
		total_num += 1;
		if(total_num == 5)
		{
			printf("关闭中断.............\r\n");
			portDISABLE_INTERRUPTS();
			HAL_Delay(5000);
			printf("打开中断.............\r\n");
			portENABLE_INTERRUPTS();
		}
  }
  /* USER CODE END extitask_test */
}

查看运行结果

打开串口助手可以看到当中断关闭时只有定时器3在工作,而定时器5则失效,当中断重新打开时,定时器3和定时器5又重新一起计时了。
在这里插入图片描述

小结

当我们使用FreeRTOS时,要注意有些外设的驱动代码需要严格连续执行,不能被打断,这时我们可以使用临界段代码来保护现场。但是只需要屏蔽某些优先级的中断时,就可以使用中断屏蔽代码。
本节工程创建的部分步骤有所省略,详细步骤可参考STM32CubeMX+FreeRTOS学习笔记(一)

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值