学习之路主要为FreeRTOS操作系统在STM32F103(STM32F103C8T6)上的运用,采用的是标准库编程的方式,使用的IDE为KEIL5。
注意!!!本学习之路可以通过购买STM32最小系统板以及部分配件的方式进行学习,也可以通过Proteus仿真的方式进行学习。
后续文章会同时发表在个人博客(jason1016.club)、CSDN;
视频会发布在bilibili(UID:399951374)
一、中断与FreeRTOS的关系
中断与FreeRTOS任务体系是共存的,通常我们把正常工作执行的工作放在任务,某个紧急工作触发或者需要单片机加急去做的工作放在中断。
操作系统和中断其实没有那么玄乎。中断就是中断,OS起到的作用只是管理中断运行不允许,起到一个调控作用。与任务无关,中断就是中断嘛。
二、FreeRTOS对中断的屏蔽(关闭)
在许多应用中,需要暂时屏蔽所有的中断一执行一些对时序要求严格的任务。
- PRIMASK 用于禁止除复位、 NMI 和 HardFalut 外的所有异常和中断
- FAULTMASK 比 PRIMASK 更狠,它可以连 HardFault 都屏蔽掉
- BASEPRI 寄存器相对柔和,只屏蔽优先级低于某一个阈值的中断(下面就引出我们中断保留的概念)
三、中断保留
但是我们有时候会觉得,一关就全关了,太傻了是不是,所以我们要对OS关闭的中断设置一个阈值,让它不会全部关掉,保留一些重要的中断是不是。
FreeRTOS是建立在正常裸机系统上的,所以裸机自己的中断是全部保留的。
那我觉得有些裸机中断不允许被OS控制怎么办,所以我们设置了一个OS中断阈值优先级寄存器。
意思就是得保留部分裸机中断级别,让FreeRTOS不参与进这部分级别中断的的开发。所以我们设计一个中断宏的阈值,高于此中断阈值的中断优先级不能被FreeRTOS操控。
大家可以自由设置,这里我设置为了 5。也就是高于 5 的优先级(优先级数小于 5)不归 FreeRTOS 管理!
configMAX_SYSCALL_INTERRUPT_PRIORITY==5
四、开关中断
关闭中断:vPortRaiseBASEPRI() 原理就是向BASEPRI寄存器写入上述configMAX_SYSCALL_INTERRUPT_PRIORITY的值,关闭阈值以下优先级的中断
portDISABLE_INTERRUPTS() //关闭中断(关闭MAX阈值中断优先级之后的中断)
portENABLE_INTERRUPTS() //开启中断
五、临界段保护
上面也讲到了OS可以对中断进行屏蔽,这是针对某些不能被中断打断的重要任务嘛。那我们统一将那些进行需要屏蔽中断来保护运行的代码称为临界段;那我们开启屏蔽就叫临界段保护。
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。
taskENTER_CRITICAL(); //临界段进入
taskEXIT_CRITICAL(); //临界段退出
代码:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "sys.h"
#include "interrupt.h"
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define INTERRUPT_TASK_PRIO 2 //任务优先级
#define INTERRUPT_STK_SIZE 256 //任务堆栈大小
TaskHandle_t INTERRUPTTask_Handler; //任务句柄
void interrupt_task(void *p_arg); //任务函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED
TIM3_Int_Init(10000-1,7200-1); //初始化定时器 3,定时器周期 1S
TIM5_Int_Init(10000-1,7200-1); //初始化定时器 5,定时器周期 1S
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建中断测试任务
xTaskCreate((TaskFunction_t )interrupt_task, //任务函数 (1)
(const char* )"interrupt_task", //任务名称
(uint16_t )INTERRUPT_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )INTERRUPT_TASK_PRIO, //任务优先级
(TaskHandle_t* )&INTERRUPTTask_Handler); //任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//中断测试任务函数
void interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
total_num+=1;
if(total_num==5)
{
printf("关闭中断.............\r\n");
portDISABLE_INTERRUPTS(); //关闭中断 (3)
delay_xms(5000); //延时 5s (4)
printf("打开中断.............\r\n"); //打开中断
portENABLE_INTERRUPTS();
}
LED0=~LED0;
vTaskDelay(1000);
}
}
//定时器 3 中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
printf("TIM3 输出.......\r\n");
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
//定时器 5 中断服务函数
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET) //溢出中断
{
printf("TIM4 输出.......\r\n");
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除中断标志位
}
本文为作者独立编写,本BLOG上所有的原创文章未经本人许可,不得用于商业用途及传统媒体。网络媒体转载请注明出处,否则属于侵权行为。