FreeRTOS实时操作系统(2)

前言:FreeRTOS内容较多,分篇发布,较为基础,旨在梳理知识,适合入门的同学   
(基于正点原子STM32F103开发板V2)
(对于本篇,若有疑问,欢迎在评论区留言,作者将在两小时内解答)
(本篇参考B站正点原子FreeRTOS)

目录

前言:FreeRTOS内容较多,分篇发布,较为基础,旨在梳理知识,适合入门的同学   (基于正点原子STM32F103开发板V2)(对于本篇,若有疑问,欢迎在评论区留言,作者将在两小时内解答)(本篇参考B站正点原子FreeRTOS)

1.任务的挂起与恢复

​编辑1.1任务挂起函数

1.2任务恢复函数(任务中恢复)

1.3任务恢复函数(中断中恢复)

1.4任务挂起与恢复实验

1.5总结

2.中断管理

2.1什么是中断?

2.2中断优先级分组设置

2.3中断优先级配置寄存器

2.4中断屏蔽寄存器

2.5中断管理实验

3.临界段代码保护

3.1临界段

3.2临界段代码保护函数

4.任务调度器的挂起和恢复

5.总结


1.任务的挂起与恢复

挂起:挂起任务类似暂停,可恢复; 删除任务,无法恢复

恢复:恢复被挂起的任务

FromISR:带FromISR后缀是在中断函数中专用的API函数


1.1任务挂起函数

void vTaskSuspend(TaskHandle_t xTaskToSuspend)

注意:

1.此函数用于挂起任务,使用时需将宏 INCLUDE_vTaskSuspend  配置为 1

2.无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复

3.当传入的参数为NULL,则代表挂起任务自身(当前正在运行的任务)

1.2任务恢复函数(任务中恢复)

void vTaskResume(TaskHandle_t xTaskToResume)

注意:

1.使用该函数注意宏:INCLUDE_vTaskSuspend必须定义为 1

2.任务无论被 vTaskSuspend() 挂起多少次,只需在任务中调用  vTakResume() 恢复一次,就可以继续运行,且被恢复的任务会进入就绪态(不支持嵌套)

1.3任务恢复函数(中断中恢复)

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)

返回值

pdTRUE:当恢复的任务优先级大于正在执行任务的优先级时,由于抢占式调度,任务优先级高的抢占任务优先级低的,进行任务切换

pdFALSE:当恢复的任务优先级小于于正在执行任务的优先级时,不需要进行任务切换

注意:

1.使用该函数注意宏:INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 必须定义为 1

2.该函数专用于中断服务函数中,用于解挂被挂起任务

3.中断服务程序中要调用freeRTOS的API函数则中断优先级不能高于FreeRTOS所管理的最高优先级(特别注意)

重点说明第3点:

中断服务程序(ISR)通常在非常短的周期内运行,并且不能阻塞。如果 ISR 的优先级高于任务,那么 ISR 可能会不断打断任务的执行,导致任务无法完成,不断打断任务的执行:由于 ISR 的优先级更高,每次 ISR 被触发时,它都会抢占当前正在执行的任务。这会导致以下情况:

  • 任务无法完成:如果任务正在处理一个复杂或耗时的操作,ISR 的频繁打断会导致任务无法完成其当前的工作。
  • 上下文切换:每次 ISR 打断任务时,操作系统都需要保存任务的状态(上下文),然后切换到 ISR。ISR 完成后,再恢复任务的状态并继续执行。这种频繁的上下文切换会消耗大量的系统资源,并可能导致性能下降。
  • 系统响应性降低:如果高优先级的中断频繁发生,低优先级任务可能会长时间得不到执行,从而降低系统的响应性和实时性

1.4任务挂起与恢复实验

(基于正点原子STM32F103开发板V2)

本实验在<动态/静态创建任务实验>的基础上进行修改

动态/静态创建任务实验的start_task、task1、task2与本次实验相同,task3在本次实验进行修改

第一步:

将任务 挂起/恢复 函数宏 INCLUDE_vTaskSuspend  配置为 1(默认为1)

路径:\User\FreeRTOSConfig.h

第二步:

修改task3

/**
 * @brief       task3
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task3(void *pvParameters)
{
    uint8_t key = 0;
    
    while (1)
    {
        key = key_scan(0);
        
        switch (key)
        {
            case KEY0_PRES:                     /* 挂起任务1 */
            {
                vTaskSuspend(Task1Task_Handler); //调用挂起任务函数
                break;
            }
            case KEY1_PRES:                     /* 恢复任务1 */
            {
                vTaskResume(Task1Task_Handler);
                break;
            }
            default:
            {
                break;
            }
        }
        
        vTaskDelay(10);
    }
}

实验现象:

每500ms,两块区域颜色变化,并计数+1

按下KEY0  task1挂起(暂停变色和计数),按下KEY1task1恢复挂起(继续变色和计数)

1.5总结

2.中断管理

2.1什么是中断?

中断:让CPU打断正常运行的程序,转而去处理紧急的事件(程序)中断优先级分组设置

2.2中断优先级分组设置

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

STM32 的中断优先级可以分为抢占优先级和子优先级

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

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

特点:

中断优先级数值越小越优先

所以,我们STM32一共有五个分配方式

通过调用函数HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)即可完成设置(在HAL_Init中设置)

FreeRTOS官网关于中断说明:https://www.freertos.org/RTOS-Cortex-M3-M4.html

注意事项:

1.低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断里才允许调用FreeRTOS 的API函数(本套实验低于5优先级,即5-15中断被允许调用API函数)

2.建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理(调用函数HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)

3.中断优先级数值越小越优先,任务优先级数值越大越优先

2.3中断优先级配置寄存器

三个系统中断优先级配置寄存器,分别为 SHPR1、 SHPR2、 SHPR3

SHPR1寄存器地址:0xE000ED18

SHPR2寄存器地址:0xE000ED1C

SHPR3寄存器地址:0xE000ED20

表出自:《Cortex M3权威指南(中文)》第286页

例如:

我们要设置PendSV的优先级,我们只要让SHPR3寄存器地址:0xE000ED20左移16位(8
+8)

我们要设置SysTick的优先级,我们只要让SHPR3寄存器地址:0xE000ED20左移24位(8
+8+8)

设置某位中断优先级路径

\Middlewares\FreeRTOS\portable\RVDS\ARM_CM3\port.c

配置FreeRTOS可管理的最高中断优先级 5-15

configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS): 

将 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 的值左移 (8 - configPRIO_BITS) 位。如果 configPRIO_BITS 是 4,那么这个表达式就变成了 15 << 4

在二进制中,15 是 0000 1111。左移 4 位后,它变成了 1111 0000,这样15配置的就是寄存器的高四位了

注意:

PendSV和SysTick设置最低优先级,保证系统任务切换不会阻塞系统其他中断的响应

2.4中断屏蔽寄存器

三个中断屏蔽寄存器,分别为 PRIMASK、 FAULTMASK 和BASEPRI

FreeRTOS所使用的中断管理就是利用的BASEPRI这个寄存器

BASEPRI:屏蔽优先级低于某一个阈值的中断

FreeRTOS中的逻辑澄清
低优先级任务B执行时: 当低优先级任务B正在执行时,理想情况下,你不希望任何低优先级(数值上更大)的中断打断它,但是你希望允许高优先级(数值上更小)的中断,比如高优先级任务A的中断,能够打断B。因此,BASEPRI应该设置为一个值,该值允许高于B优先级的中断发生,而屏蔽掉低于或等于B优先级的中断。

高优先级任务A执行时: 在高优先级任务A执行时,理论上没有必要调整BASEPRI,因为A已经处于较高优先级,系统中的其他中断应该都是低于A的优先级(除了可能的系统保留中断,这些通常由操作系统管理,不受用户控制)。因此,对于A的执行,不需要通过BASEPRI来保护它免受低优先级任务的中断

例如:

BASEPRI设置为0x50(5<<4,高四位执行中断),代表中断优先级在5~15内的均被屏蔽,0~4的中断优先级正常执行

(但即使5-15优先级的中断被屏蔽,在特定状态下会被触发,一旦触发,系统就会进入相应的中断服务例程(ISR)来处理这个中断)

关中断:

中断服务函数中调度FreeRTOS的API函数需特别注意:

1.中断服务函数的优先级需在FreeRTOS所管理的范围内

2.在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数

开中断:

 BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断

2.5中断管理实验

(基于正点原子STM32F103开发板V2)

第一步:

定义两个定时器 TIM3、TIM5

路径:

\Drivers\BSP\TIMER\btim.h

#ifndef __BTIM_H
#define __BTIM_H

#include "./SYSTEM/sys/sys.h"

/******************************************************************************************/
/* 基本定时器 定义 */

/* TIMX 中断定义 
 * 默认是针对TIM2~TIM5, TIM12~TIM17.
 * 注意: 通过修改这4个宏定义,可以支持TIM1~TIM17任意一个定时器.
 */
 
#define BTIM_TIM3_INT                       TIM3
#define BTIM_TIM3_INT_IRQn                  TIM3_IRQn
#define BTIM_TIM3_INT_IRQHandler            TIM3_IRQHandler
#define BTIM_TIM3_INT_CLK_ENABLE()          do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0)  /* TIM3 时钟使能 */

#define BTIM_TIM5_INT                       TIM5
#define BTIM_TIM5_INT_IRQn                  TIM5_IRQn
#define BTIM_TIM5_INT_IRQHandler            TIM5_IRQHandler
#define BTIM_TIM5_INT_CLK_ENABLE()          do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0)  /* TIM5 时钟使能 */

/******************************************************************************************/

void btim_tim3_int_init(uint16_t arr, uint16_t psc);
void btim_tim5_int_init(uint16_t arr, uint16_t psc);

#endif

第二步:

设置定时器优先级  TIM3 优先级为4  TIM5 优先级为6

路径:\Drivers\BSP\TIMER\btim.c

#include "./BSP/TIMER/btim.h"
#include "./SYSTEM/USART/usart.h"

TIM_HandleTypeDef g_tim3_handle;        /* 定时器3句柄 */
TIM_HandleTypeDef g_tim5_handle;        /* 定时器5句柄 */

/**
 * @brief       基本定时器TIM3定时中断初始化函数
 * @note
 *              基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              基本定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 时钟预分频数
 * @retval      无
 */
void btim_tim3_int_init(uint16_t arr, uint16_t psc)
{
    BTIM_TIM3_INT_CLK_ENABLE();                                      /* 使能TIM3时钟 */
    
    g_tim3_handle.Instance = BTIM_TIM3_INT;                          /* 通用定时器3 */
    g_tim3_handle.Init.Prescaler = psc;                              /* 分频 */
    g_tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP;             /* 向上计数器 */
    g_tim3_handle.Init.Period = arr;                                 /* 自动装载值 */
    g_tim3_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;       /* 时钟分频因子 */
    HAL_TIM_Base_Init(&g_tim3_handle);
    
    HAL_NVIC_SetPriority(BTIM_TIM3_INT_IRQn, 4, 0);  /* 设置中断优先级,抢占优先级4,子优先级0 */
    HAL_NVIC_EnableIRQ(BTIM_TIM3_INT_IRQn);          /* 开启ITM3中断 */
    
    HAL_TIM_Base_Start_IT(&g_tim3_handle);           /* 使能定时器3和定时器3更新中断 */
}

/**
 * @brief       基本定时器TIM5定时中断初始化函数
 * @note
 *              基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              基本定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 时钟预分频数
 * @retval      无
 */
void btim_tim5_int_init(uint16_t arr, uint16_t psc)
{
    BTIM_TIM5_INT_CLK_ENABLE();                                      /* 使能TIM5时钟 */
    
    g_tim5_handle.Instance = BTIM_TIM5_INT;                          /* 通用定时器5 */
    g_tim5_handle.Init.Prescaler = psc;                              /* 分频 */
    g_tim5_handle.Init.CounterMode = TIM_COUNTERMODE_UP;             /* 向上计数器 */
    g_tim5_handle.Init.Period = arr;                                 /* 自动装载值 */
    g_tim5_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;       /* 时钟分频因子 */
    HAL_TIM_Base_Init(&g_tim5_handle);
    
    HAL_NVIC_SetPriority(BTIM_TIM5_INT_IRQn, 6, 0);  /* 设置中断优先级,抢占优先级6,子优先级0 */
    HAL_NVIC_EnableIRQ(BTIM_TIM5_INT_IRQn);          /* 开启ITM5中断 */
    
    HAL_TIM_Base_Start_IT(&g_tim5_handle);           /* 使能定时器5和定时器5更新中断 */
}

/**
 * @brief       定时器中断服务函数
 * @param       无
 * @retval      无
 */
void BTIM_TIM3_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim3_handle);
}

/**
 * @brief       定时器中断服务函数
 * @param       无
 * @retval      无
 */
void BTIM_TIM5_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_tim5_handle);
}

/**
 * @brief       定时器更新中断回调函数
* @param        htim:定时器句柄指针
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //htim 参数可以用来判断是哪个定时器触发了事件
{
    if (htim == (&g_tim3_handle))
    {
        printf("TIM3输出\r\n");
    }
    else if (htim == (&g_tim5_handle))
    {
        printf("TIM5输出\r\n");
    }
}

第三步:

配置定时器 TIM3 和 TIM5

路径:\User\freertos_demo.c

任务设置与前两个实验一样

我们主要看start_task

void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           /* 进入临界区 */
    /* 初始化TIM3、TIM5 */
    btim_tim3_int_init(10000-1, 7200-1);
    btim_tim5_int_init(10000-1, 7200-1);
    /* 创建任务1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    vTaskDelete(StartTask_Handler); /* 删除开始任务 */
    taskEXIT_CRITICAL();            /* 退出临界区 */
}

重点解释   TIM3和5的配置

我们的实验基于正点原子STM32F103开发板V2,主频时钟频率为72Mhz

TIM3 和 TIM5 都通过 APB1 总线工作,APB1 的最大频率为系统时钟的 1/2,因此在 72 MHz 的情况下,APB1 的频率为 36 MHz

1. 第一个参数(10000-1

  • ARR(自动重装载寄存器)
    • 10000-1 设置了自动重装载值为 9999。这意味着定时器的计数范围是从 0 到 9999,共计 10000 个计数。当计数达到 9999 时,定时器将溢出并触发相应的中断(如果启用了中断)。

2. 第二个参数(7200-1

  • 预分频器(PSC)
    • 7200-1 设置了预分频器为 7199。预分频器的作用是将 TIM 时钟频率降低。以 36 MHz 的 TIM 时钟为例,设置预分频器为 7199 的计算方式如下:

3. 结果

  • 由于计数频率为 5 kHz,每个计数周期为 1 / 5000 秒,即 0.2 ms,所以,当计数到 9999(10000 次计数)时,实际的溢出时间为:10000×0.2 ms=2000 ms=2 s10000×0.2 ms=2000 ms=2 s
  • 计数频率:当你将定时器初始化为 9999 和 7199 时,定时器将以 5 kHz 的频率计数,并且每当计数到 9999 时,会溢出并触发中断。这将使得程序每 2秒(1 秒 / 5 kHz)执行一次相应的中断服务程序(ISR)

第四步:

编写开中断、关中断函数

路径:\User\freertos_demo.c


/**
 * @brief       task1
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task1(void *pvParameters)
{
    uint32_t task1_num = 0;
    
    while (1)
    {
        if (++task1_num == 5)
        {
            uint32_t task1_num = 0;
            printf("FreeRTOS关闭中断\r\n");
            portDISABLE_INTERRUPTS();       /* FreeRTOS关闭中断 */
            delay_ms(5000); //这里不用vTaskDelay是因为vTaskDelay会开启中断
            printf("FreeRTOS打开中断\r\n");
            portENABLE_INTERRUPTS();        /* FreeRTOS打开中断 */
        }
        
        vTaskDelay(1000);
    }
}

实验现象:

task1每执行五次,FreeRTOS打开/关闭中断

2.6总结

3.临界段代码保护

3.1临界段

临界段:临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,临界区直接屏蔽了中断

使用场景

3.2临界段代码保护函数

 FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断

中断级进入临界段:

中断级临界段的主要目的是防止在中断服务程序(ISR)中被其他中断打断,确保在处理关键任务时的数据一致性和完整性

任务级临界区调用格式示例:

taskENTER_CRITICAL() ; //关中断
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL()	; //开中断

中断级临界区调用格式示例:

ISR{

uint32_t  save_status;
save_status  = taskENTER_CRITICAL_FROM_ISR(); //备份save_status 中断屏蔽寄存器之前的值,然后关闭中断
{
        … …	/* 临界区 */
}
taskEXIT_CRITICAL_FROM_ISR(save_status );  //将之前save_status的值读回来

}

特点:

1.成对使用

2.支持嵌套

3.尽量保持临界段耗时短

特别注意:

不允许临界区代码量太大,执行时间太长,否则会造成延时中断,中断得不到及时响应,这在FreeRTOS中是不允许的

4.任务调度器的挂起和恢复

挂起任务调度器(关闭任务切换), 调用此函数不需要关闭中断

使用格式示例:

vTaskSuspendAll() ;
{
        … …	/* 内容 */
}
xTaskResumeAll()	;

特点:

1.与临界区不一样的是,挂起任务调度器,未关闭中断

2.它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应

3.挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全

5.总结

FreeRTOS官网:https://www.freertos.org/

正点原子STM32F103开发板V2   a盘资料: https://pan.baidu.com/s/1iebOVd87jBVtoudMijboIg 提取码:1ypa

FreeRTOS内容较多,内容分篇发布,感谢大家观看

连载中...  

作者留言:本人学生,大多数为网络资源,如有侵权,及时联系,如有错误的地方,欢迎指出

创作时间:2024.10.25

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值