FreeRTOS学习8——开启任务调度器API函数简介

开启任务调度器API函数简介

    • 任务调度
      • 开启任务调度器API函数简介
        • **函数** **vTaskStartScheduler()**
        • **函数** **xPortStartScheduler()**
        • **函数** **prvStartFirstTask()**
        • **函数** **vPortSVCHandler()**
          • **注意**
          • 补充
            • **出栈/压栈汇编指令详解**

任务调度

开启任务调度器API函数简介

函数 vTaskStartScheduler()

​ 用于启动任务调度器,任务调度器启动后,FreeRTOS 便会开始进行任务调度,除非调用函数 xTaskEndScheduler()停止任务调度器,否则不会再返回

该函数内部如下

void vTaskStartScheduler( void )
{BaseType_t xReturn;/* 如果启用静态内存管理,则优先使用静态方式创建空闲任务 */
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{StaticTask_t * pxIdleTaskTCBBuffer = NULL;StackType_t * pxIdleTaskStackBuffer = NULL;uint32_t ulIdleTaskStackSize;vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,&pxIdleTaskStackBuffer,&ulIdleTaskStackSize);xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,configIDLE_TASK_NAME,ulIdleTaskStackSize,( void * ) NULL,portPRIVILEGE_BIT,pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer);if( xIdleTaskHandle != NULL ){xReturn = pdPASS;}else{xReturn = pdFAIL;}
}
#else/* 未启用静态内存管理,则使用动态方式创建空闲任务 */
{xReturn = xTaskCreate( prvIdleTask,configIDLE_TASK_NAME,configMINIMAL_STACK_SIZE,( void * ) NULL,portPRIVILEGE_BIT,&xIdleTaskHandle);
}
#endif/* 如果启用软件定时器,则需要创建定时器服务任务 */
#if ( configUSE_TIMERS == 1 )
{if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask();}else{mtCOVERAGE_TEST_MARKER();}
}
#endifif( xReturn == pdPASS ){
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
{/* 此函数用于添加一些附加初始化,不用理会 */freertos_tasks_c_additions_init();
}
#endif/* FreeRTOS 关闭中断,* 以保证在开启任务任务调度器之前或过程中,SysTick 不会产生中断,* 在第一个任务开始运行时,会重新打开中断。*/portDISABLE_INTERRUPTS();#if ( configUSE_NEWLIB_REENTRANT == 1 )
{/* Newlib 相关 */_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif/* 初始化一些全局变量* xNextTaskUnblockTime: 下一个距离取消任务阻塞的时间,初始化为最大值* xSchedulerRunning: 任务调度器运行标志,设为已运行* xTickCount: 系统使用节拍计数器,宏 configINITIAL_TICK_COUNT 默认为 0*/xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;/* 为任务运行时间统计功能初始化功能时基定时器* 是否启用该功能,可在 FreeRTOSConfig.h 文件中进行配置*/portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();/* 调试使用,不用理会 */traceTASK_SWITCHED_IN();/* 设置用于系统时钟节拍的硬件定时器(SysTick)* 会在这个函数中进入第一个任务,并开始任务调度* 任务调度开启后,便不会再返回*/if( xPortStartScheduler() != pdFALSE ){}else{}}else{/* 动态方式创建空闲任务和定时器服务任务(如果有)时,因分配给 FreeRTOS 的堆空间* 不足,导致任务无法成功创建 */configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}/* 防止编译器警告,不用理会 */( void ) xIdleTaskHandle;/* 调试使用,不用理会 */( void ) uxTopUsedPriority;
}

从上面的代码可以看出,函数 vTaskStartScheduler()主要做了六件事情。

  1. 创建空闲任务,根据是否支持静态内存管理,使用静态方式或动态方式创建空闲任务。

  2. 创建定时器服务任务,创建定时器服务任务需要配置启用软件定时器,创建定时器服务任务,同样是根据是否配置支持静态内存管理,使用静态或动态方式创建定时器服务任务。

  3. 关闭中断,使用 portDISABLE_INTERRUPT()关闭中断,这种方式只关闭受 FreeRTOS 管理的中断。关闭中断主要是为了防止 SysTick 中断在任务调度器开启之前或过程中,产生中断。FreeRTOS 会在开始运行第一个任务时,重新打开中断。

  4. 初始化一些全局变量,并将任务调度器的运行标志设置为已运行。

  5. 初始化任务运行时间统计功能的时基定时器,任务运行时间统计功能需要一个硬件定时器提供高精度的计数,这个硬件定时器就在这里进行配置,如果配置不启用任务运行时间统计功能的,就无需进行这项硬件定时器的配置。

  6. 最后就是调用函数 xPortStartScheduler()。

函数 xPortStartScheduler()

​ 函数 xPortStartScheduler()完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务

具体的代码如下所示:

BaseType_t xPortStartScheduler( void )
{
#if ( configASSERT_DEFINED == 1 )
{/* 检测用户在 FreeRTOSConfig.h 文件中对中断相关部分的配置是否有误,代码省略 */
}
#endif/* 设置 PendSV 和 SysTick 的中断优先级为最低优先级 (在中断管理中有详细介绍)*/portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;/* 配置 SysTick* 清空 SysTick 的计数值* 根据 configTICK_RATE_HZ 配置 SysTick 的重装载值* 开启 SysTick 计数和中断*/vPortSetupTimerInterrupt();/* 初始化临界区嵌套次数计数器为 0 */uxCriticalNesting = 0;/* 使能 FPU* 仅 ARM Cortex-M4/M7 内核 MCU 才有此行代码* ARM Cortex-M3 内核 MCU 无 FPU*/prvEnableVFP();/* 在进出异常时,自动保存和恢复 FPU 相关寄存器* 仅 ARM Cortex-M4/M7 内核 MCU 才有此行代码* ARM Cortex-M3 内核 MCU 无 FPU*/*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;/* 启动第一个任务 */prvStartFirstTask();/* 不会返回这里 */return 0;
}
  1. 在启用断言的情况下,函数 xPortStartScheduler()会检测用户在 FreeRTOSConfig.h 文件中对中断的相关配置是否有误,感兴趣的读者请自行查看这部分的相关代码。

  2. 配置 PendSV 和 SysTick 的中断优先级为最低优先级

  3. 调用函数 vPortSetupTimerInterrupt()配置 SysTick,函数 vPortSetupTimerInterrupt()首先会将 SysTick 当 前 计 数 值 清 空 , 并 根 据 FreeRTOSConfig.h 文件中配置的configSYSTICK_CLOCK_HZ(SysTick 时钟源频率)和 configTICK_RATE_HZ(系统时钟节拍频率)计算并设置 SysTick 的重装载值,然后启动 SysTick 计数和中断。(具体可结合函数内部和M3权威指南P134)

  4. 初始化临界区嵌套计数器为 0。

  5. 调用函数 prvEnableVFP()使能 FPU,因为 ARM Cortex-M3 内核 MCU 无 FPU,此函数仅在 ARM Cortex-M4/M7 内核 MCU 平台上被调用,执行改函数后 FPU 被开启。

  6. 接下来将 FPCCR 寄存器的[31:30]置 1,这样在进出异常时,FPU 的相关寄存器就会自动地保存和恢复,同样地,因为 ARM Cortex-M3 内核 MCU 无 FPU,此当代码仅在 ARM Cortex-M4/M7 内核 MCU 平台上被调用。

  7. 调用函数 prvStartFirstTask()启动第一个任务

函数 prvStartFirstTask()

函数 prvStartFirstTask()用于初始化启动第一个任务前的环境,主要是重新设置 MSP 指针,并使能全局中断

__asm void prvStartFirstTask( void )
{/* 8 字节对齐 */PRESERVE8ldr r0, =0xE000ED08 /* 0xE000ED08 为 VTOR 地址 */ldr r0, [ r0 ] 	 /* 获取 VTOR 的值 	 r0 = *r0 */ldr r0, [ r0 ]      /* 获取 MSP 的初始值 r0 = *r0 *//* 初始化 MSP */msr msp, r0/* 使能全局中断 */cpsie icpsie fdsbisb/* 调用 SVC 启动第一个任务 */svc 0nopnop
}

从上面的代码可以看出,函数 prvStartFirstTask()是一段汇编代码,解析如下所示:

  1. 首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,栈在任何时候都是需要 4 字节对齐的,而在调用入口得 8 字节对齐,在进行 C 编程的时候,编译器会自动完成的对齐的操作,而对于汇编,就需要开发者手动进行对齐。

  2. 接下来的三行代码是为了获得 MSP 指针的初始值,那么这里就能够引出两个问题:

    (1) 什么是 MSP 指针?

    程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,MCU 会自动更新 SP 指针,使 SP 指针指向最后一个入栈的元素,那么程序就可以根据 SP 指针来从栈中存取信息。对于正点原子的 STM32F1、STM32F4、STM32F7 和 STM32H7 开发板上使用的 ARM Cortex-M 的 MCU 内核来说,ARM Cortex-M 提供了两个栈空间,这两个栈空间的堆栈指针分别是 MSP(主堆栈指针)和 PSP(进程堆栈指针)。在 FreeRTOS 中 MSP 是给系统栈空间使用的,而 PSP 是给任务栈使用的,也就是说,FreeRTOS 任务的栈空间是通过 PSP 指向的,而在进入中断服务函数时,则是使用 MSP 指针。当使用不同的堆栈指针时,SP 会等于当前使用的堆栈指针。

    (2) 为什么是 0xE000ED08?

    0xE000ED08 是 VTOR(向量表偏移寄存器)的地址,VTOR 中保存了向量表的偏移地址。一般来说向量表是从其实地址 0x00000000 开始的,但是在有情况下,可能需要修改或重定向向量表的首地址,因此 ARM Corten-M 提供了 VTOR 对向量表进行从定向。而向量表是用来保存中断异常的入口函数地址,即栈顶地址的,并且向量表中的第一个字保存的就是栈底的地址,

    在 start_stm32xxxxxx.s 文件中有如下定义:

    __Vectors DCD __initial_sp 			; 栈底指针DCD Reset_Handler 					; Reset HandlerDCD NMI_Handler 					; NMI HandlerDCD HardFault_Handler 				; Hard Fault HandlerDCD MemManage_Handler 				; MPU Fault Handler
    

    ​ 以上就是向量表(只列出前几个)的部分内容,可以看到向量表的第一个元素就是栈指针的初始值,也就是栈底指针。

    ​ 在了解了这两个问题之后,接下来再来看看代码。首先是获取 VTOR 的地址,接着获取VTOR 的值,也就是获取向量表的首地址,最后获取向量表中第一个字的数据,也就是栈底指针了。

  3. 在获取了栈顶指针后,将 MSP 指针重新赋值为栈底指针。这个操作相当于丢弃了程序之前保存在栈中的数据,因为FreeRTOS从开启任务调度器到启动第一个任务都是不会返回的,是一条不归路,因此将栈中的数据丢弃,也不会有影响。

  4. 重新赋值 MSP 后,接下来就重新使能全局中断,因为之前在函数 vTaskStartScheduler()中关闭了受 FreeRTOS 的中断。

  5. 最后使用 SVC 指令,并传入系统调用号 0,触发 SVC 中断。

函数 vPortSVCHandler()

​ SVC 的中断服务函数

__asm void vPortSVCHandler( void )
{/* 8 字节对齐 */PRESERVE8/* 获取任务栈地址 */ldr r3, = pxCurrentTCB /* r3 指向优先级最高的就绪态任务的任务控制块 */ldr r1, [ r3 ] 		/* r1 为任务控制块地址 */ldr r0, [ r1 ] 		/* r0 为任务控制块的第一个元素(栈顶) *//* 模拟出栈,并设置 PSP */ldmia r0!, {r4-r11,r14} /* 任务栈弹出到 CPU 寄存器 */msr psp, r0 			  /* 设置 PSP 为任务栈指针 */isb/* 使能所有中断 */mov r0, # 0msr basepri,/* 使用 PSP 指针,并跳转到任务函数 */orr r14, # 0xdbx r14
}
  • 首先通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务就是系统将要运行的任务
  • 接下来通过任务的栈顶指针,将任务栈中的内容出栈到 CPU 寄存器中,任务栈中的内容在调用任务创建函数的时候,已经初始化了。然后再设置 PSP 指针,那么,这么一来,任务的运行环境就准备好了。
  • 通过往 BASEPRI 寄存器中写 0,允许中断
  • 最后通过两条汇编指令,使 CPU 跳转到任务的函数中去执行
  • R14 是链接寄存器 LR,在 ISR 中(此刻我们在 SVC 的 ISR 中),它记录了异常返回值 EXC_RETURN

EXC_RETURN 只有 6 个合法的值(M4、M7),如下表所示: (M3中不支持浮点单元故只有3个默认值

描述使用浮点单元未使用浮点单元
中断返回后进入Hamdler模式,并使用MSP0xFFFFFFE10xFFFFFFF1
中断返回后进入线程模式,并使用 MSP0xFFFFFFE90xFFFFFFF9
中断返回后进入线程模式,并使用 PSP0xFFFFFFED0xFFFFFFFD

EXC_RETURN 各比特位的描述如下表所示

image-20241101160954907

注意

SVC中断只在启动第一次任务时会调用一次,以后均不调用

中断产生时,硬件自动将xPSR,PC(R15),LR(R14),R12,R3-R0出/入栈;而R4~R11需要手动出/入栈

进入中断后硬件会强制使用MSP指针 ,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN

补充
出栈/压栈汇编指令详解

出栈(恢复现场),方向:从下往上(低地址往高地址)

作用:把当前任务的数据加载到CPU中

假设r0地址为0x04汇编指令示例:

ldmia r0!, {r4-r6} /* 任务栈r0地址由低到高,将r0存储地址里面的内容手动加载到 CPU寄存器r4、r5、r6 */

  • r0地址(0x04)内容加载到r4,此时地址r0 = r0+4 = 0x08
  • r0地址(0x08)内容加载到r5,此时地址r0 = r0+4 = 0x0C
  • r0地址(0x0C)内容加载到r6,此时地址r0 = r0+4 = 0x10

压栈(保存现场),方向:从上往下(高地址往低地址)

stmdb r0!, {r4-r6} } /* r0的存储地址由高到低递减,将r4、r5、r6里的内容存储到r0的任务栈里面。 */

作用:把CPU中正在处理的数据存储到对应任务的栈内

  • 地址:r0 = r0-4 = 0x0C,将r6的内容(寄存器值)存放到r0所指向地址(0x0C)
  • 地址:r0 = r0-4 = 0x08,将r5的内容(寄存器值)存放到r0所指向地址(0x08)
  • 地址:r0 = r0-4 = 0x04,将r4的内容(寄存器值)存放到r0所指向地址(0x04)

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

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

相关文章

SIGNAL TAP使用记录

一、首先编译工程 二、打开signal tap,并设置抓取时钟以及采样深度 二、点击set up,然后双击空白处,会弹出右侧窗口,点击filter选择pre_synthesis,这里选择综合前的信号观测,要确保左侧窗口内的信号是黑色…

Windows版 nginx安装,启动,目录解析,常用命令

Windows版 nginx安装,启动,目录解析,常用命令 一级目录二级目录三级目录 1. 下载2. 启动方式一:方式二: 3. 验证是否启动4. 安装目录解析5. 常用命令 一级目录 二级目录 三级目录 1. 下载 官网下载:ngi…

kafka相关面试题

文章目录 什么是消息中间件?kafka 是什么?有什么作用?kafka 的架构是怎么样的?Kafka Replicas是怎么管理的?如何确定当前能读到哪一条消息?生产者发送消息有哪些模式?发送消息的分区策略有哪些&…

Python | Leetcode Python题解之第519题随机翻转矩阵

题目: 题解: class Solution:def __init__(self, m: int, n: int):self.m mself.n nself.total m * nself.map {}def flip(self) -> List[int]:x random.randint(0, self.total - 1)self.total - 1# 查找位置 x 对应的映射idx self.map.get(x,…

SHEEL脚本编程

一、shell基本知识 Ⅰ、为什么要学习和使用shell编程 通过编程,简化日常的维护工作,使得管理员从简单的重复劳动解脱出来 Ⅱ、什么是shell shell的功能 Shell又称命令解释器,它能识别用户输入的各种命令,并传递给操作系统。它…

三、Kafka集群

一、Kafka集群的概念 1、目的 高并发、高可用、动态扩展。 主备数据架构、双活节点、灾备数据中心。 如果是服务的地理范围过大也可以使不同的集群节点服务不同的区域,降低网络延迟。 2、Kafka集群的基本概念 1)复制(镜像) kaf…

关于Android Studio Koala Feature Drop | 2024.1.2下载不了插件的解决办法

解决 androidStudio Settings->Plugins下载插件,点击install后没反应,同时插件描述相关显示不出来 第一步: 第二步: 点击设置,勾选Auto-detect proxy settings,输入网址 https://plugins.jetbrains.com…

笔记本双系统win10+Ubuntu 20.04 无法调节亮度亲测解决

sudo add-apt-repository ppa:apandada1/brightness-controller sudo apt-get update sudo apt-get install brightness-controller-simple 安装好后找到一个太阳的图标,就是这个软件,打开后调整brightness,就可以调整亮度,可…

若依微服务架构遇到的一些问题记录

一、nacos启动问题 需要看官网的准备工作,认真看,版本问题卡了两天 https://doc.ruoyi.vip/ruoyi-cloud/document/hjbs.html#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C 1.下载nacos,版本需要对应上 版本说明链接 2.记得运行数据库&#xff0…

Linux_shell编程

shell介绍 概念: 用户编写的shell命令通过shell解释器解释后交给linux内核去执行. shell是一个程序(解释器程序) 用户和linux内核的桥梁. Shell 是一个 C 语言编写的脚本语言,它是用户与 Linux 的桥梁,用户输入命令交给 Shell 处理 Shell 将相应的操作传…

Java 多线程(八)—— 锁策略,synchronized 的优化,JVM 与编译器的锁优化,ReentrantLock,CAS

前言 本文为 Java 面试小八股,一句话,理解性记忆,不能理解就死背吧。 锁策略 悲观锁与乐观锁 悲观锁和乐观锁是锁的特性,并不是特指某个具体的锁。 我们知道在多线程中,锁是会被竞争的,悲观锁就是指锁…

国内PLC市场份额报告,西门子老大的地位从未动摇

【导读】国内PLC市场占有率,西门子依然是老大。 PLC市场集中度很高,从销售额来看,TOP3厂家占据一半以上的市场份额,以外资品牌为主,其中西门子排名第一,2022年市场份额约47.1%;三菱排名第二&…

使用uniapp + Vue3 + uni.createInnerAudioContext()实现播放歌曲及歌词滚动、拖动进度条

一、大致效果 二、使用步骤 1.歌词详情页代码块 <template><view class"play"><view class"play_centent" :style"{ background-image: url( playInfo.siPic ) }"><div class"cover-mask" style"opacit…

无人机维护保养、部件修理更换技术详解

无人机作为一种精密的航空设备&#xff0c;其维护保养和部件修理更换是确保飞行安全、延长使用寿命的重要环节。以下是对无人机维护保养、部件修理更换技术的详细解析&#xff1a; 一、无人机维护保养技术 1. 基础构造理解&#xff1a; 熟悉无人机的基本构造&#xff0c;包括…

解决Redis缓存穿透(缓存空对象、布隆过滤器)

文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库 常见的解决方案有两种&#xff0c;分别…

【运动的&足球】足球场景目标检测系统源码&数据集全套:改进yolo11-ASF-P2

改进yolo11-RetBlock等200全套创新点大全&#xff1a;足球场景目标检测系统源码&#xff06;数据集全套 1.图片效果展示 项目来源 人工智能促进会 2024.11.03 注意&#xff1a;由于项目一直在更新迭代&#xff0c;上面“1.图片效果展示”和“2.视频效果展示”展示的系统图片或…

【STM32】GPIO通用输入输出口

文章目录 一、GPIO的概念二、STM32中GPIO的基本结构三、GPIO位结构输入部分分析输出部分分析GPIO的8种模式 四、GPIO相关函数 一、GPIO的概念 GPIO&#xff08;General Purpose Input Output&#xff09;&#xff0c;意为通用输入输出口&#xff0c;在嵌入式系统中&#xff0c;…

华为荣耀曲面屏手机下面空白部分设置颜色的方法

荣耀部分机型下面有一块空白区域&#xff0c;如下图红框部分 设置这部分的颜色需要在themes.xml里面设置navigationBarColor属性 <item name"android:navigationBarColor">android:color/white</item>

电子电气架构 --- 整车控制系统

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

STM32 HAL库 SPI驱动1.3寸 OLED屏幕

目录 参考硬件引脚与接线 点亮屏幕CubeMX 配置OLED 驱动程序代码 参考 基于STM32F103C8T6最小系统板HAL库CubeMX SPI驱动7针 OLED显示屏&#xff08;0.96寸 1.3寸通用&#xff09;0.96 oled HAL库驱动 SPI STM32SPI驱动0.96/1.3寸 OLED屏幕&#xff0c;易修改为DMA控制STM32驱…