µCOS-III从入门到精通 第五章(中断管理)

参考教程:【正点原子】手把手教你学UCOS-III实时操作系统_哔哩哔哩_bilibili

一、中断的回顾与深入

1、概述

(1)让CPU打断正常运行的程序,转而去处理紧急的事件(程序),这个过程就叫中断,受理的事件称为中断服务程序。

(2)中断执行机制,可简单概括为三步:

①外设产生中断请求(GPIO外部中断、定时器中断等)。

②CPU停止执行当前程序,转而去执行中断处理程序(ISR)。

③执行完毕,返回被打断的程序处,继续往下执行。

2、中断优先级及分组

(1)ARM Cortex-M使用了8位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器,但STM32只用了中断优先级配置寄存器的高4位[7 : 4],所以STM32提供了最大16级(0~15)的中断优先等级(中断优先级数值越小,等级越高)。

(2)STM32的中断优先级可以分为抢占优先级和子优先级:

①抢占优先级:抢占优先级高的中断可以打断正在执行但抢占优先级低的中断。

②子优先级:当同时发生具有相同抢占优先级的两个中断时,子优先级数值小的优先执行。

(3)中断优先级分组一共有5种分配方式,对应着中断优先级分组的5个组,通过调用函数NVIC_PriorityGroupConfig(NVIC_PriorityGroup_x)可完成设置,建议将所有优先级位指定为抢占优先级位(即NVIC_PriorityGroup_4),方便µC/OS-III管理。

优先级分组 

抢占优先级 

子优先级 

优先级配置寄存器高 4 位

NVIC_PriorityGroup_0 

0 级抢占优先级 

0-15 级子优先级 

0bit 用于抢占优先级
4bit 用于子优先级

NVIC_PriorityGroup_1 

0-1 级抢占优先级 

0-7 级子优先级 

1bit 用于抢占优先级
3bit 用于子优先级

NVIC_PriorityGroup_2 

0-3 级抢占优先级 

0-3 级子优先级 

2bit 用于抢占优先级
2bit 用于子优先级

NVIC_PriorityGroup_3 

0-7 级抢占优先级 

0-1 级子优先级 

3bit 用于抢占优先级
1bit 用于子优先级

NVIC_PriorityGroup_4 

0-15 级抢占优先级 

0 级子优先级 

4bit 用于抢占优先级
0bit 用于子优先级

(4)µC/OS-III的中断管理范围可通过宏CPU_CFG_KA_IPL_BOUNDA设置,如将该宏定义为4,即管理中断优先级范围为4~15。

(5)在中断服务函数中,如果调用到µC/OS-III的API函数,那么该中断优先级必须在µC/OS-III所管理的范围内。

3、中断相关寄存器

(1)STM32F103C8T6中有三个系统中断优先级配置寄存器,分别为SHPR1、SHPR2、SHPR3。

①SHPR1寄存器的地址为0xE000ED18,SHPR2寄存器的地址为0xE000ED1C,SHPR3寄存器的地址为0xE000ED20。

②PendSV中断设置最低优先级,这是为了保证系统任务切换不会阻塞系统其它中断的响应。

③SysTick中断设置µC/OS-III所管理的最高优先级,以保证系统时钟节拍的精度。

(2)STM32F103C8T6中有三个中断屏蔽寄存器,分别为PRIMASK、FAULTMASK和BASEPRI,µC/OS-III主要用到了BASEPRI和PRIMASK,其中中断管理就是利用BASEPRI这个寄存器,当需要关闭大部分中断时则使用PRIMASK这个寄存器。

①BASEPRI用于屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断,另外,进入临界区以及退出临界区,就是通过操作BASEPRI寄存器实现的。(BASEPRI寄存器也是低4位无效的,配置时需注意这一点)

②PRIMASK寄存器有32bit,但只有bit0有效,是可读可写的,将PRIMASK寄存器设置为1可屏蔽除NMI和HardFault外的所有异常和中断,将PRIMASK寄存器清0可使能中断。

二、µC/OS-III中断管理实验

1、原理图与实验目标

(1)原理图:

(2)实验目标:

①使用两个定时器TIM2和TIM3,TIM2中断的优先级为3,相应中断服务程序为控制LED1状态翻转,TIM3中断的优先级为6,相应中断服务程序为控制LED2状态翻转,系统所管理的优先级范围为4~15。

②设计2个任务——start_task、task3:

[1]start_task:用于初始化CPU库、配置Systick中断及优先级,并创建task3任务,然后删除自身。

[2]task3:中断测试任务,任务中将调用关中断和开中断函数来体现对中断的管理作用。

③预期实验现象:

[1]程序下载到板子上后,两个LED灯闪烁。

[2]按下按键1,LED2停止闪烁,LED1仍继续闪烁。

[3]按下按键2,LED2恢复闪烁。

2、实验步骤

(1)将“任务创建和删除实验”的工程文件夹复制一份,在拷贝版中进行实验。

(2)在stm32教程中找到“定时器定时中断”实验,复制其中的Timer.c文件和Timer.h文件,将其添加到本实验的项目中,如下图所示。

(3)将key.c文件以及UCOS_experiment.c文件中的阻塞函数更改为Delay_ms死延时函数,因为OSTimeDly函数的底层中调用了开中断函数,不利于实验现象的观察。

(4)在Timer.c文件中完成TIM2和TIM3初始化函数的实现,并在Timer.h文件中声明。

#include "stm32f10x.h"                  // Device header

void Timer2_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);	  //开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;			//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;		//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;		//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);		//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);		//清除定时器更新标志位
	//TIM_TimeBaseInit函数末尾,手动产生了更新事件
	//若不清除此标志位,则开启中断后,会立刻进入一次中断
	//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);		//开启TIM2的更新中断
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;				//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;	//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;	//指定NVIC线路的抢占优先级为3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//指定NVIC线路的响应优先级为0
	NVIC_Init(&NVIC_InitStructure);	//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

void Timer3_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	  //开启TIM3的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;			//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;		//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;		//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);		//将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);	//清除定时器更新标志位
	//TIM_TimeBaseInit函数末尾,手动产生了更新事件
	//若不清除此标志位,则开启中断后,会立刻进入一次中断
	//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);		//开启TIM3的更新中断
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;				//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;	//选择配置NVIC的TIM3线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;	//指定NVIC线路的抢占优先级为6
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//指定NVIC线路的响应优先级为0
	NVIC_Init(&NVIC_InitStructure);	//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

(5)删除UCOS_experiment.c文件中关于task1和task2的内容,并更改task3函数的具体实现,按下按键1,µC/OS-III关中断,按下按键2,µC/OS-III开中断。

#include "stm32f10x.h"                  // Device header
#include "os.h"
#include "cpu.h"
#include "LED.h"
#include "Key.h"
#include <stdio.h>
#include "Delay.h"

/* START_TASK 任务 配置
 * 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define     START_TASK_PRIO         1
#define     START_TASK_STACK_SIZE   256
CPU_STK     start_task_stack[START_TASK_STACK_SIZE];
OS_TCB      start_task_tcb;
void start_task(void);

/* TASK3 任务 配置
 * 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define     TASK3_PRIO              2
#define     TASK3_STACK_SIZE        256
CPU_STK     task3_stack[TASK3_STACK_SIZE];
OS_TCB      task3_tcb;
void task3(void);

void UCOS_Test(void)
{
	OS_ERR err;
	
	OSInit(&err);    //初始化μC/OS-III

	//创建Start Task
    OSTaskCreate (&start_task_tcb,
                  "start_task",
                  (OS_TASK_PTR)start_task,
                  NULL,
                  START_TASK_PRIO,
                  start_task_stack,
                  START_TASK_STACK_SIZE / 10,
                  START_TASK_STACK_SIZE,
                  0,
                  0,
                  0,
                  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                  &err);
    
    /* 开始任务调度 */
    OSStart(&err);
}

void start_task(void)              
{
    OS_ERR err;   //接收错误代码使用,对错误代码进行分情况处理可增强程序鲁棒性
    CPU_Init();     //初始化CPU库
    CPU_SR_ALLOC();
    
	//滴答定时器重装载值 = 系统主频 / 滴答定时器中断频率(滴答定时器是递减计数)
    CPU_INT32U cnts = SystemCoreClock / OS_CFG_TICK_RATE_HZ;
    OS_CPU_SysTickInit(cnts);   //配置Systick中断及优先级
    
    CPU_CRITICAL_ENTER();           //进入临界区(关中断)
                    
    //创建task3
    OSTaskCreate (&task3_tcb,
                  "task3",
                  (OS_TASK_PTR)task3,
                  0,
                  TASK3_PRIO,
                  task3_stack,
                  TASK3_STACK_SIZE / 10,
                  TASK3_STACK_SIZE,
                  0,
                  0,
                  0,
                  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                  &err);
    
    CPU_CRITICAL_EXIT();            //退出临界区(开中断)
    OSTaskDel(NULL, &err);            //删除任务自身
}

void task3(void)
{
	uint8_t key = 0;
	CPU_SR_ALLOC();
	while(1)
	{
		key = Key_GetNum();    //读取按键键值
		if(key == 1)   CPU_CRITICAL_ENTER();           //关中断
		if(key == 2)   CPU_CRITICAL_EXIT();             //开中断
		Delay_ms(10);
	}
}

(6)main.c文件的主函数中需要配置NVIC中断优先级分组,然后初始化两个定时器(先分组,再配置,顺序不能反),并在主函数外实现两个定时器分别对应的中断服务程序,TIM2中断服务程序控制LED1状态翻转,TIM3中断服务程序控制LED2状态翻转。

#include "stm32f10x.h"                  // Device header
#include "LED.h"
#include "Key.h"
#include "UCOS_experiment.h"
#include "Timer.h"

int main(void)
{
	/*模块初始化*/
	Key_Init();          //Key初始化
	LED_Init();         //LED初始化
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);	//配置NVIC为分组4
	                    //即抢占优先级范围:0~15,响应优先级范围:0
	Timer2_Init();      //定时器2中断初始化
	Timer3_Init();      //定时器3中断初始化
	
	UCOS_Test();
	
	while (1)
	{
		
	}
}

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		LED1_Turn();      //LED1状态翻转
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

void TIM3_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
	{
		LED2_Turn();      //LED2状态翻转
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

(7)程序完善好后点击“编译”,然后将程序下载到开发板上。

3、程序执行流程

(1)main函数全流程:

①初始化按键模块、LED模块,配置NVIC中断优先级分组,初始化定时器TIM2和TIM3。

②调用UCOS_Test函数(此处未画出中断可能将main函数打断)。

(2)测试函数全流程(此处未画出中断可能将测试函数打断):

①创建任务start_task。

②开启任务调度器。

(3)多任务调度执行阶段(发生在开启任务调度器以后):

①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断,接着start_task任务创建任务task3,然后删除自身,接着退出临界区,让出CPU资源。(此处未画出中断可能将start_task打断)

②start_task任务自我删除后,除了空闲任务以外,只剩下task3任务需要执行。

③按下按键1,中断优先级数值大于等于4的中断都被µC/OS-III关闭,即TIM3中断被关闭,但TIM2中断仍为打开状态。

④按下按键2,µC/OS-III打开所有能管理的中断,即TIM2中断和TIM3中断均为打开状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值