FreeRTOS开启任务调度函数xPortStartScheduler详解

news2024/11/26 12:31:33

在FreeRTOS中,创建完任务后需要调用vTaskStartScheduler开启调度器,在这个函数主要就是创建空闲任务然后调用xPortStartScheduler函数开启任务的调度,本篇文章就以Cortex-M7为例来分析一下这个函数具体做了什么事,并深入理解其中的断言。

函数源码如下:

BaseType_t xPortStartScheduler( void )
{
    configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
	/* 该port.c不支持CM7的这两个修订版本 */
    configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
    configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
	/* 一般打开了这个宏 */
    #if ( configASSERT_DEFINED == 1 )
        {
            volatile uint32_t ulOriginalPriority;
            volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
            volatile uint8_t ucMaxPriorityValue;

            ulOriginalPriority = *pucFirstUserPriorityRegister;

            *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

            ucMaxPriorityValue = *pucFirstUserPriorityRegister;

            ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

            ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;

            while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
            {
                ulMaxPRIGROUPValue--;
                ucMaxPriorityValue <<= ( uint8_t ) 0x01;
            }

            #ifdef __NVIC_PRIO_BITS
                {
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
                }
            #endif

            #ifdef configPRIO_BITS
                {
                    configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
                }
            #endif

            ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
            ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

            *pucFirstUserPriorityRegister = ulOriginalPriority;
        }
    #endif /* configASSERT_DEFINED */

    portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;

    vPortSetupTimerInterrupt();

    uxCriticalNesting = 0;

    vPortEnableVFP();

    *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;

    prvPortStartFirstTask();
	/* 后面三行理论上不会执行到 */
    vTaskSwitchContext();
    prvTaskExitError();
    return 0;
}

首先,宏定义configMAX_SYSCALL_INTERRUPT_PRIORITY 不能为0,它用于配置系统中允许的最高中断优先级,以便实现在中断服务程序中调用 FreeRTOS API 函数时,确保对系统的保护。下面是该宏定义的原型:

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2
#define configPRIO_BITS 4
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

其中configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY为FreeRTOS中允许的最高优先级;而configPRIO_BITS表示处理器所支持的中断优先级位数。以CM7为例,它的configPRIO_BITS为4,在CM7内核中使用二进制0bxxxx0000来表示优先级,其中xxxx为CM7中支持的4位优先级,所以需要下面的移位操作获得最终的configMAX_SYSCALL_INTERRUPT_PRIORITY

在FreeRTOS在PendSV异常中执行上下文切换函数vTaskSwitchContext时,将设置basepri寄存器为configMAX_SYSCALL_INTERRUPT_PRIORITY ,这可以使优先级比configMAX_SYSCALL_INTERRUPT_PRIORITY低的中断被屏蔽。

常见问题 :在系统外设中断中,我们需要调用FreeRTOS的函数。如在串口中断函数中释放一个信号量来通知任务处理数据,但是程序会卡死在中断中。
答: 外设中断的优先级如果没有设置的话,默认为0,它低于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY而不受FreeRTOS的管理,如果在该优先级的中断中调用FreeRTOS函数,有可能会导致一些非原子操作的混乱。比如系统正在执行一个受FreeRTOS管理的中断且正在对一个信号量进行操作,此时产生了一个不受FreeRTOS管理的中断,在中断中也调用FreeRTOS信号量函数,那么有可能会使得之前中断保存的上下文并不是实际的上下文,从而产生错误。故需要执行NVIC_SetPriority设置外设中断的优先级,且优先级的数值需要大于等于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY


接下来来分析一下这一段代码:

#define portFIRST_USER_INTERRUPT_NUMBER       ( 16 )
#define portNVIC_IP_REGISTERS_OFFSET_16       ( 0xE000E3F0 )
#define portMAX_8_BIT_VALUE                   ( ( uint8_t ) 0xff )
#define portMAX_PRIGROUP_BITS                 ( ( uint8_t ) 7 )
#define portTOP_BIT_OF_BYTE                   ( ( uint8_t ) 0x80 )

volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( volatile uint8_t * const ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;

ulOriginalPriority = *pucFirstUserPriorityRegister;
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;

while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
    ulMaxPRIGROUPValue--;
    ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}

portNVIC_IP_REGISTERS_OFFSET_16+16,即0xE000E400,对应Cortex-M7处理器中的NVIC_IPR0寄存器,它用于配置中断优先级。其中每个IPRx寄存器代表4个中断的8位优先级,IPR0~IPR59总共240个中断的优先级。
在这里插入图片描述

还有一个问题,为什么要定义portNVIC_IP_REGISTERS_OFFSET_16呢?通过上表可以看到在NVIC手册中并没有标明这个地址存放的是什么寄存器,但我们来看一下Cortex-M7的异常中断表:
在这里插入图片描述
由此我们可以推断出,在IPR0寄存器之前,保存的是这16个异常的优先级,加起来正好256个中断。
所以,上面的代码就是取出中断优先级为0的,即第一个用户中断优先级配置寄存器,然后往里面写0xFF来测试8位优先级是几位有效的。比如低2位没有用到,往IPR寄存器写0xFF,读出来就是0xFC。然后用这个掩码和configMAX_SYSCALL_INTERRUPT_PRIORITY按位与得到ucMaxSysCallPriority

对于一个8位优先级来说,优先级有8种分组(0~7)。所以最后计算优先级分组的最高值ulMaxPRIGROUPValue,它的值就等于(7-ucMaxPriorityValue高位1的个数)


接下来进入两个断言中:

#define portMAX_PRIGROUP_BITS                 ( ( uint8_t ) 7 )

#ifdef __NVIC_PRIO_BITS
{
	configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
}
#endif

#ifdef configPRIO_BITS
{
	configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
}
#endif

这两个断言都需要计算(系统的最大优先级组位数portMAX_PRIGROUP_BITS-最大可接受的优先级组值ulMaxPRIGROUPValue),它的结果表示优先级组的位数。比如优先级组为3,表示IPR寄存器的高4位用来配置优先级。
__NVIC_PRIO_BITS是一个由处理器提供的宏定义,在我的芯片中等于4,表示硬件中可用的优先级位数,而对于configPRIO_BITS来说,它就等于__NVIC_PRIO_BITS

#ifdef __NVIC_PRIO_BITS
	/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
	#define configPRIO_BITS __NVIC_PRIO_BITS
#else
	#define configPRIO_BITS 4 /* 15 priority levels */
#endif

为了确保正确性,需要确保这两个值相等。因为configMAX_SYSCALL_INTERRUPT_PRIORITY是由configPRIO_BITS决定的,而configMAX_SYSCALL_INTERRUPT_PRIORITY将赋值给basepri寄存器。如果设置不对,可能导致本不能调用FreeRTOS函数的某优先级的中断可以调用FreeRTOS函数,从而导致某些错误,如信号量的不统一。


由于后续还需要判断特殊寄存器AIRCR的优先级分组字段[10:8]位的正确性,这里求出AIRCR寄存器中[10:8]位的掩码以备后续使用。

#define portPRIORITY_GROUP_MASK               ( 0x07UL << 8UL )
#define portPRIGROUP_SHIFT                    ( 8UL )

ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

/* IPR0用来测试得到内核的最大优先级的掩码和组,最后需要将其初始值恢复给IPR0 */
*pucFirstUserPriorityRegister = ulOriginalPriority;

接着,将SysTick和PendSV设置为最低优先级

/*  将PendSV和SysTick设置为最低优先级 */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;

这样做的目的如下:

  • SysTick:SysTick为FreeRTOS的时钟,用于任务延时和时间管理。若设置为最高优先级,则会使IRQ响应速度变慢。
  • PendSV:PendSV中断用于任务切换,不会打断正在执行的高优先级的中断,在中断结束后,再进行任务切换

总得来说,就是要保证任务的实时性,还要保证它们不会抢占其他中断或任务,避免产生不可预期行为和竞争条件。


最后就执行以下代码:

/* 配置和使能SysTick中断 */
vPortSetupTimerInterrupt();

/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0;

/* Ensure the VFP is enabled - it should be anyway. */
vPortEnableVFP();

/* Lazy save always. */
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;

/* Start the first task. */
prvPortStartFirstTask();

(1)uxCriticalNesting是临界区嵌套层数,FreeRTOS允许临界区嵌套,这个变量是为了避免在较低级别的临界区内部发生不必要的任务切换或中断处理,从而提高系统的可靠性和响应性。
(2)VFP为浮点运算单元,表示可以在任务中使用浮点数运算,并且可以调用相关的浮点库函数。使能之后,在PendSV的开始处将保存VFP寄存器高16位的浮点上下文到栈中(AAPCS规定子例程调用必须保存s16-s31,而不需要保存s0-s15),并在结束处出栈。

开启FPU后,在调用函数时,硬件还将保存FPSCR字段,它有68个字节,很影响性能。可以通过将FPCCR中的LSPEN位置1来使能Lazy Context Save。这意味着只要在中断不使用FPU,就可以不用将68字节的浮点状态压入堆栈。

最后,就是调用prvPortStartFirstTask开始第一个任务的调度,这个在我之前的文章有具体地进行分析:FreeRTOS第一个任务的创建和调度详解(SVC异常)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/533985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文献阅读:A Lite Distributed Semantic Communication System for Internet of Things

目录 动机&#xff1a;为什么作者想要解决这个问题&#xff1f;贡献&#xff1a;作者在这篇论文中完成了什么工作(创新点)&#xff1f;规划&#xff1a;他们如何完成工作&#xff1f;理由&#xff1a;通过什么实验验证它们的工作结果自己的看法 动机&#xff1a;为什么作者想要…

Python遍历大量表格文件并筛选出表格内数据缺失率低的文件

本文介绍基于Python语言&#xff0c;针对一个文件夹下大量的Excel表格文件&#xff0c;基于其中每一个文件内、某一列数据的特征&#xff0c;对其加以筛选&#xff0c;并将符合要求与不符合要求的文件分别复制到另外两个新的文件夹中的方法。 首先&#xff0c;我们来明确一下本…

【Linux】多线程 --- POSIX信号量+懒汉模式的线程池+其他常见锁

Linux system sprinkle flowers 文章目录 一、POSIX信号量1.阻塞队列实现的生产消费模型代码不足的地方&#xff08;无法事前得知临界资源的就绪状态&#xff09;2.信号量的理解3.初步看一下信号量的操作接口4.环形队列实现的生产消费模型5.环形队列的代码编写&#xff08;维持…

百度将凭借人工智能改变游戏规则并实现盈利?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 稳健的财务业绩 在2022年第四季度&#xff0c;百度&#xff08;BIDU&#xff09;的收入为48亿美元(331亿人民币)&#xff0c;比分析师预测的高出了1.72亿美元&#xff0c;但同比下降了约8%。从细分业务来看&#xff0c;百度…

Android:你真的会用Toast吗(介绍安卓好看简约的Toast快速解锁方法)

目录 概要 开源库地址 如何使用 1、首先我们现在根目录下的build.gradle中添加以下依赖 2、然后我们在我们的模块目录&#xff08;通常是app&#xff09;下的build.gradle中添加以下依赖 3、 然后这一步是可选的&#xff0c;你可以在你的app模块下任意位置&#xff0c;添加以下…

ANR原理篇 - ANR弹框是如何显示出来的

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、ANR弹框是如何显示流程1.1 找到弹框对应类1.2 查找AppNotRespondingDialog引用…

Python程序员职业现状分析,想提高竞争力,就要做到这六点

现今程序员群体数量已经高达几百万&#xff0c;学历和收入双高&#xff0c;月薪普遍过万。今天&#xff0c;我们就围绕90后程序员人群分析、职业现状、Python程序员分析等&#xff0c;进行较为全面的报告分析和观点论述。 一、程序员人群分析 人数规模上&#xff1a;截当前程…

javaweb系列-JSON对象、BOM对象、DOM对象

1.5.1.3 JSON对象 在 JavaScript 中自定义对象特别简单&#xff0c;其语法格式如下&#xff1a; <body><script>//自定义对象var user {name: "tom",age: 20,gendar: "male",eat: function () { //函数alert("吃饭啦");}};aler…

Xcode多个子工程结合联编开发SDK

Xcode版本&#xff1a;Version 14.3 (14E222b) 这是啥&#xff1f; chat&#xff1a; Xcode 多个子工程结合联编可以用于开发 SDK&#xff0c;这种开发方法是在一个主工程中包含多个子工程&#xff0c;每个子工程代表 SDK 中不同的模块&#xff0c;每个子工程都可以独立编译。…

AutoDL平台租借GPU详解

AutoDL平台租借GPU详解&#xff08;2023年&#xff09; 一、AutoDL租用GPU 1.1 创建实例 首先进入AutoDL官网&#xff1a;AutoDL-品质GPU租用平台-租GPU就上AutoDL进行学生注册登录&#xff08;学生有优惠&#xff09;点击右上角的控制台&#xff0c;进入AutoDL的主页&#…

人工智能应用--深度学习原理与实战--神经网络的工作原理

机器学习是将输入(比如图像)映射到目标(比如标签“猫”)&#xff0c;并建立映射规则(即模型)。在深度学习中&#xff0c;神经网络通过一系列数据变换层来实现这种输入到目标的映射&#xff0c;本章节我们具体来看这种学习过程是如何实现的。 学习内容 1、理解层(Layer)及权重(…

Java调用C#

由于项目采用Hybrid热更&#xff0c;走纯C#开发&#xff0c;目前战斗由客户端到服务端&#xff08;客户端提供dll&#xff09;&#xff0c;服务端负责调用&#xff0c;故需要走Java 调C# dll逻辑。 1、JNI&#xff1a;不支持泛型&#xff08;pb&#xff09;没法转C成功 2、JN…

MatebookE2022款i7集显 华为智能磁吸键盘(DRC-W76)原装出厂Win11系统恢复原厂OEM系统

HUAWEI华为笔记本电脑&#xff0c;Matebook E 2022款 i7 集显 华为智能磁吸键盘 16GB512GB (DRC-W76)原装出厂Windows11系统恢复原厂OEM系统 系统自带所有驱动、办公软件、华为电脑管家等预装软件 链接&#xff1a;https://pan.baidu.com/s/1t7bczFO_RvD31g1uIZoGgw?pwdq2g0 …

前端面试题整理2

目录 1.讲解es6新增map和set&#xff1f; 2.Ts的枚举和元组是什么&#xff1f; 3.vue3中的beforeEnter钩子函数怎么用&#xff1f; 4.获取数据时&#xff0c;加载loading动画&#xff0c;在哪取消比较好Diff算法的优化在哪&#xff1f; 5.Jq中的$(this)和this的区别&#x…

单片机--STM32

【1】课程回顾 【2】课程介绍 1.单片机简介 单片机是单片微型计算机的简称&#xff0c;Mcu是Microcontroller的简称&#xff0c;也就是嵌入式微控制器。采用集成电路技术将具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、定时器/计时器、多种I/O口和中断系统…

P1772 [ZJOI2006] 物流运输

题目提供者 洛谷 难度 提高/省选- 题目描述 物流公司要把一批货物从码头 A 运到码头 B。由于货物量比较大&#xff0c;需要 n 天才能运完。货物运输过程中一般要转停好几个码头。 物流公司通常会设计一条固定的运输路线&#xff0c;以便对整个运输过程实施严格的管理和跟…

20230516使用python3确认三门问题

最烧脑的悖论&#xff0c;意识为什么会影响未来&#xff1f;颠覆你认知的三门问题播报文章 小红虾实验室 2023-04-09 06:08 四川 好看视频优创联盟,优质科学领域创作者 关注 对于懂概率的人来说&#xff0c;他中大奖的概率将成倍增加&#xff0c;甚至获奖率能够达到100%。 今…

【量化交易笔记】6.布林带的实现

上一讲介绍A股移动平均值&#xff08;MA&#xff09;指标&#xff0c;本讲我们来讲解布林布的实现。 布林线&#xff08;BOLL&#xff09;技术指标简介 布林线&#xff08;Bollinger Bands&#xff0c;BOLL&#xff09;又称布林带&#xff0c;是约翰布林&#xff08;John Bol…

dbForge Studio for SQL Server Crack

dbForge Studio for SQL Server Crack 增加了对源代码管理中的数据操作语言(DML)触发器排序的支持。 添加了对不使用EXEC/EXECUTE关键字调用过程/函数的语法支持。 在语法检查中添加了对EXEC命令的支持。 dbForge Studio for SQL Server是一个IDE&#xff0c;用于SQL Server中的…

抖音本地生活服务商贴牌小程序

作为社交电商平台的一部分&#xff0c;抖音本地生活服务的市场前景非常广阔。以下是抖音本地生活服务商市场前景的几个方面&#xff1a; 巨大的用户群体&#xff1a;抖音拥有数亿的用户&#xff0c;这些用户中有很多人需要本地生活服务&#xff0c;如美食、酒店、旅游等&am…