中断知识回顾
我们之前学习过裸机,应该很明白中断的概念,这里再复习一下相关知识,让CPU打断正常运行的程序,转而去处理紧急的事件(程序),就叫中断 。

ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器。但STM32,只用了中断优先级配置寄存器的高4位 [7 : 4],即bit4~bit7这四位,所以STM32提供了最大16级的中断优先等级。
STM32 的中断优先级可以分为抢占优先级和子优先级:
抢占优先级: 抢占优先级高的中断可以打断正在执行但抢占优先级低的中断。
子优先级:当同时发生具有相同抢占优先级的两个中断时,子优先级数值小的优先执行。

FreeRTOS为了方便管理,分组方式要选择NVIC_PriorityGroup_4:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
FreeRTOS中断管理
FreeRTOS中断特点:
1、低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断里才允许调用FreeRTOS 的API函数。
2、建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理。
3、中断优先级数值越小越优先,任务优先级数值越大越优先。
系统中断优先级配置
三个系统中断优先级配置寄存器,分别为 SHPR1、 SHPR2、 SHPR3。
这个 SHPR3(0xE000ED20 )寄存器,可以配置滴答定时器、PendSV的优先级,滴答定时器是为我们系统提供心跳节拍,而PendSV是任务切换、任务调度都是在PendSV中实现的。
在FreeRTOS中是如何配置PendSV和Systick中断优先级的?

其实没有什么内容,只是宏定义比较多,就是把寄存器的bit4~bit7给填充上,让这些有了优先级,Systick和PendSV的中断优先级都是15,就是最低的中断优先级。
为什么要设置成最低优先级呢?
是为了保证系统任务切换不会阻塞系统其他中断的响应。就是我中断可以打断你任务,但是你任务无法打断我中断。
中断屏蔽寄存器
三个中断屏蔽寄存器,分别为 PRIMASK、 FAULTMASK 和BASEPRI。

我们的FreeRTOS就是利用BASEPRI寄存器来管理中断的,就是中断优先级小于我们这里设置的阈值,就会被关闭。
比如: BASEPRI设置为0x50,代表中断优先级在5~15内的均被屏蔽,0~4的中断优先级正常执行。
可以看出,我们的FreeRTOS相当于接管了5~15的中断,让原本的5~15被屏蔽。

在中断服务函数中调度FreeRTOS的API函数需注意:
1、中断服务函数的优先级需在FreeRTOS所管理的范围内。
2、在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数。
中断管理实验
实验目的:学会使用FreeRTOS的中断管理。
本实验会使用两个定时器,一个优先级为4,一个优先级为6,注意:系统所管理的优先级范围:5~15。所以中断优先级为4的定时器,理论上FreeRTOS是无法进行控制的,所以我们来测试一下,到底能不能控制。(提前透露答案:不能)
目标设计:
将设计2个任务:start_task、task1。
start_task:用来创建task1任务 。
task1:中断测试任务,任务中将调用关中断和开中断函数来体现对中断的管理作用。
这一节课我们需要用到定时器,所以我们先把定时器的初始化代码写一下:
void Timer_Init(void)
{
//开启通用定时器TIM2、TIM3的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//选择时基单元的时钟是内部时钟(168MHz)
TIM_InternalClockConfig(TIM2);
TIM_InternalClockConfig(TIM3);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrcture;
TIM_TimeBaseInitStrcture.TIM_ClockDivision=TIM_CKD_DIV1; //指定采样分频。
TIM_TimeBaseInitStrcture.TIM_CounterMode=TIM_CounterMode_Up;//指定计数器模式
TIM_TimeBaseInitStrcture.TIM_Period=10000-1;//自动重装寄存器ARR的值
TIM_TimeBaseInitStrcture.TIM_Prescaler=16800-1;//预分频器PSC的值
TIM_TimeBaseInitStrcture.TIM_RepetitionCounter=0;//通用定时器没有重复计数器,填0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStrcture);
TIM_TimeBaseInitStrcture.TIM_ClockDivision=TIM_CKD_DIV1; //指定采样分频。
TIM_TimeBaseInitStrcture.TIM_CounterMode=TIM_CounterMode_Up;//指定计数器模式
TIM_TimeBaseInitStrcture.TIM_Period=10000-1;//自动重装寄存器ARR的值
TIM_TimeBaseInitStrcture.TIM_Prescaler=16800-1;//预分频器PSC的值
TIM_TimeBaseInitStrcture.TIM_RepetitionCounter=0;//通用定时器没有重复计数器,填0
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStrcture);
// 清除更新中断标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
//配置输出中断控制
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
//配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;//NVIC通道
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能该中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=4;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;//NVIC通道
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能该中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
TIM_Cmd(TIM3,ENABLE);
}
void TIM2_IRQHandler(){
if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
printf("优先级4,TIM2,正在运行!!\r\n");
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
void TIM3_IRQHandler(){
if (TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)
{
printf("优先级6,TIM3,正在运行!!\r\n");
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}
有了上面的初始化代码,我们的中断目前来讲,是都可以在串口每隔1s进行一次打印的,接下来我们来实现FreeRTOS的任务函数,在FreeRTOS中我们要实现开关中断的操作来证明FreeRTOS是可以控制中断的,所以我们要认识一个新的FreeRTOS的API函数:
portDISABLE_INTERRUPTS(); //关闭中断函数
portENABLE_INTERRUPTS(); //开启中断函数
void task1(void *pvParameters)
{
uint8_t task1_num = 0;
while(1)
{
task1_num++;
if(task1_num == 5)
{
task1_num = 0;
printf("关闭中断\r\n");
portDISABLE_INTERRUPTS();
delay_ms(5000);
printf("开启中断\r\n");
portENABLE_INTERRUPTS();
}
LED0_turn();
vTaskDelay(1000);//使用FreeRTOS自带的延时函数
}
}
注意,我们这里使用的是delay_ms而不是vTaskDelay,这是为什么呢?
因为vTaskDelay的实现有问题,它里面调用了临界区,临界区中的退出临界区的函数会调用到portENABLE_INTERRUPTS();
即开中断,就是vTaskDelay函数会变相的开启中断,所以不能用vTaskDelay。