FreeRTOS的源代码个人分析(基于KEIL下STM32F103的Demo) 四

2023-05-16

开始任务的实现分析:xPortStartScheduler()函数

FreeRTOS里开始任务是在main里调用vTaskStartScheduler函数来开始任务的,在调用这个函数后,系统会先自动的创建一个优先级最低(也就是0优先级)的空闲任务IdleTask,这个任务的作用是在所有用户的任务都被挂起,也就是当前没有用户所建立的任务在运行时,系统就会运行这个IdleTask。(但如果有用户任务的优先级也是0的话,那么用户任务会和IdleTask任务分时运行,所以一般设置任务优先级至少为1)。然后如果在config里设置了定时器,那么也会建立一个定时器的任务,定时器任务的优先级默认是最高优先级。
在完成这些准备工作之后,FreeRTOS就会调用xPortStartScheduler()函数来开启任务运行。

    #else
    {
        /* The Idle task is being created using dynamically allocated RAM. */
        xReturn = xTaskCreate(  prvIdleTask,
                                "IDLE", configMINIMAL_STACK_SIZE,
                                ( void * ) NULL,
                                ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                                &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
    }
    #endif /* configSUPPORT_STATIC_ALLOCATION */

    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_TIMERS */

    if( xReturn == pdPASS )
    {
        /* Interrupts are turned off here, to ensure a tick does not occur
        before or during the call to xPortStartScheduler().  The stacks of
        the created tasks contain a status word with interrupts switched on
        so interrupts will automatically get re-enabled when the first task
        starts to run. */
        portDISABLE_INTERRUPTS();

        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            /* Switch Newlib's _impure_ptr variable to point to the _reent
            structure specific to the task that will run first. */
            _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
        }
        #endif /* configUSE_NEWLIB_REENTRANT */

        xNextTaskUnblockTime = portMAX_DELAY;
        xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) 0U;

        /* If configGENERATE_RUN_TIME_STATS is defined then the following
        macro must be defined to configure the timer/counter used to generate
        the run time counter time base. */
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        /* Setting up the timer tick is hardware specific and thus in the
        portable interface. */
        if( xPortStartScheduler() != pdFALSE )
        {
            /* Should not reach here as if the scheduler is running the
            function will not return. */
        }

在调用xPortStartScheduler之前,初始化了三个变量的值,分别是xNextTaskUnblockTime,xSchedulerRunning和xTickCount。xNextTaskUnblockTime是下一个任务解挂的时间(系统时钟sysTick的Tick值),设置为0xFFFFFFFF,是滴答计时器不会计数到的一个值,xSchedulerRunning看名字就知道是系统运行标志位,xTickCount是当前sysTick时钟的Tick值。初始化这三个值后就在if括号里调用了xPortStartScheduler。注意在初始化三个变量之前还调用了portDISABLE_INTERRUPTS(),这个实现使用了内联汇编,主要内容就是给basepri寄存器写入0xbf,也就是屏蔽11级以下低优先级的中断(包括任务调度用的PendSV中断和滴答计时器中断)。

#define portDISABLE_INTERRUPTS()                vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()                 vPortSetBASEPRI( 0 )

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    __asm
    {
        /* Set BASEPRI to the max syscall priority to effect a critical
        section. */
        msr basepri, ulNewBASEPRI
        dsb
        isb
    }
}

因为configASSERT_DEFINED默认设置的为0,因此#if( configASSERT_DEFINED == 1 )下面的语句块无效,去除这一块后贴出精简后的的代码。

BaseType_t xPortStartScheduler( void )
{
    /* Make PendSV and SysTick the lowest priority interrupts. */
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

    /* Start the timer that generates the tick ISR.  Interrupts are disabled
    here already. */
    vPortSetupTimerInterrupt();

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

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

    /* Should not get here! */
    return 0;
}

这个函数内容其实不多,主要是设置CM3内核里,PENDSV和SYSTICK这两个系统中断的优先级,均设置为最低的15级,这样一来,当调用vPortEnterCritical(portENTER_CRITICAL)时(其设置了内核里BASEPRI寄存器的值为11,屏蔽了11以上更低优先级的中断),这两个中断会被屏蔽。然后调用vPortSetupTimerInterrupt,这个函数的作用就是设置滴答计时器的周期值和是能中断。

void vPortSetupTimerInterrupt( void )
    {
        /* Calculate the constants required to configure the tick interrupt. */
        #if configUSE_TICKLESS_IDLE == 1
        {
            ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
            xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
            ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
        }
        #endif /* configUSE_TICKLESS_IDLE */

        /* Configure SysTick to interrupt at the requested rate. */
        portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
        portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
    }

可以看到portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL; 这里的两个值见FreeRTOSConfig.h这个系统配置文件里的定义

#define configCPU_CLOCK_HZ          ( ( unsigned long ) 72000000 )  
#define configTICK_RATE_HZ          ( ( TickType_t ) 1000 )
#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ

可见默认的系统滴答频率是1000HZ,也就是1ms一个滴答,经过(72000000 / 1000)-1这个计算后装载给滴答计时器,这样滴答计时器就会没1ms溢出一次,计数值从0到71999(也就是说对于大于等于72000的计数值,是达不到的)。装载完计数值后就开启了滴答的中断,并使能滴答计时器。
在配置了滴答计时器后,下一行是初始化uxCriticalNesting这个变量为0,这个变量是标志当前进入临界区的嵌套层数,每当调用vPortEnterCritical(portENTER_CRITICAL)来屏蔽内核优先级11以下低优先级的中断(包括任务切换用的PendSV和滴答计时器中断)时,会让这个变量加1。调用vPortExitCritical这个函数来离开临界区时会减1,当这个变量为0时才会再度开启中断。
最后就是调用prvStartFirstTask()这个函数来开启第一个任务,让系统跑起来了:

__asm void prvStartFirstTask( void )
{
    PRESERVE8

    /* Use the NVIC offset register to locate the stack. */
    ldr r0, =0xE000ED08
    ldr r0, [r0]
    ldr r0, [r0]

    /* Set the msp back to the start of the stack. */
    msr msp, r0
    /* Globally enable interrupts. */
    cpsie i
    cpsie f
    dsb
    isb
    /* Call SVC to start the first task. */
    svc 0
    nop
    nop
}

这个函数是用内联汇编写的,0xE000ED08这个地址是向量表偏移量寄存器(VTOR)(见CM3权威指南中文版113页),保存的是向量表的地址。
这里写图片描述
一般情况向量表都是放在ROM里的,而且一般都是从0x00000000这个首地址开始存放。在第二篇里,降到main()里的prvSetupHardware函数里,有设置这个VTOR寄存器的一行代码,设置的就是0x00000000地址,因此现在通过ldr r0 [r0]读取这个寄存器的值得到的就是r0 = 0x00000000,通过Debug可以在左边栏里查看内核的各个寄存器的情况,也可以在代码上面一栏里看到编译后汇编代码与其对应的代码地址:
这里写图片描述
再次读取 ldr r0 [r0],得到的就是向量表第一个值也就是申请的主堆栈指针MSP的地址,然后将这个地址赋值给MSP寄存器。之后的cpsie i 和cpsie f这两个指令是开启全部的中断,然后调用svc 0,引发SVC中断,进入SVC中断服务—vPortSVCHandler里:

__asm void vPortSVCHandler( void )
{
    PRESERVE8

    ldr r3, =pxCurrentTCB   /* Restore the context. */
    ldr r1, [r3]            /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
    ldr r0, [r1]            /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0!, {r4-r11}     /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
    msr psp, r0             /* Restore the task stack pointer. */
    isb
    mov r0, #0
    msr basepri, r0
    orr r14, #0xd
    bx r14
}

这个函数也是用内联汇编写的,这个函数其实是xPortPendSVHandler服务的一部分,区别是不用保存当前任务下的各个寄存器数据和栈信息等,而是直接读取pxCurrentTCB然后跳转到这个任务去。首先是把re赋值为pxCurrentTCB这个变量地址,读取r3 ldr r1,[r3]就会得到这个指针指向的当前任务地址,也就是系统启动时第一个任务的地址。这个指针在调用xTaskCreate来创建任务时会被赋值,这个在后面分析xTaskCreate这个函数会说到。得到第一个任务的指针地址(存储在r1里)后,就可以跳转到这个任务地址去执行任务代码了,这个过程在第三篇分析 FreeRTOS进行任务切换的过程 的原理一样,不再重复叙述,不过要注意的是倒数第二行有个 orr r14, #0xd(按位或)。为什么要这么做可以见CM3权威指南中文版139页关于异常返回值,EXC_RETURN位段详解。
这里写图片描述
由这张图可见,这个按位或的作用是把R14寄存器的低4位设为D,在这个异常返回后进入线程模式,使用线程堆栈PSP,因为任务运行时要确保使用的是线程模式,只有发生中断或异常时,才让系统进入Handle模式并使用MSP。在xPortPendSVHandler里之所以没有这一行,是因为在进入这个异常前,系统正在跑任务,使用的就是线程模式和PSP,进入异常后变成Handle模式和MSP,但在异常返回时会自动回到这个异常发生前的模式也就是线程模式与PSP。在vPortSVCHandler这个函数被调用之前,系统一直是处于Handle模式并使用MSP的。(因为复位后就是Handle模式,因此大部分没用上系统的STM32的工程,都是让STM32处于Handle这个最高模式下运行的。)
在最后一行的bx r14被执行后,系统就会跳转到第一个任务进行执行了,然后系统就开始跑起来了!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS的源代码个人分析(基于KEIL下STM32F103的Demo) 四 的相关文章

  • FreeRTOS基础五:软件定时器

    软件定时器简介 软件定时器的作用 在指定的时间到来时执行指定的函数 或者以某个频率周期性地执行某个函数 被执行的函数叫做软件定时器回调函数 软件定时器由FreeRTOS内核实现 不需要硬件支持 软件定时器只有在软件定时器回调函数被调用时才需
  • freertos————互斥锁

    线程安全 多线程程序处于一个多变的环境 可访问的全局变量和堆数据随时可能被其他的线程改变 多个线程同时访问一个共享数据 可能造成严重的后果 出现问题的是之前移植了一个freemodbus的从站 多个任务访问全局变量保持寄存器区 导致最后读出
  • MDK5__配色方案的修改

    一 必要的知识 与MDK主题相关的文件有两个 在X Keil v5 UV4路径下 global propglobal prop def其中global prop def是系统默认的主题配置 如果修改过字体等 系统会生成一个global pr
  • for循环实现1-100之间偶数和

    package com itheima 04 需求 求出1 100之间偶数和 分析 A 定义求和变量 初始化值是0 B 获取1 100之间的数据 用for循环实现 C 把获取到的数据进行判断 看是否是偶数 如果是 就累加 D 输出求和结果
  • Keil不能正确生成.bin文件的解决办法

    1 打开keil IDE 然后打开help gt uVison Help 搜索fromelf关键字如下图1 然后再进入到右下角的索引找到fromelf命令行的语法和选项 找到 bin的说明如下 如红色标注所说 正是症结所在 即如果链接文件中
  • stm32f103zet6移植标准库的sdio驱动

    sdio移植 st官网给的标准库有给一个用于st出的评估板的sdio外设实现 但一是文件结构有点复杂 二是相比于国内正点原子和野火的板子也有点不同 因此还是需要移植下才能使用 当然也可以直接使用正点原子或野火提供的实例 但为了熟悉下sdio
  • FreeRTOS死机原因

    1 中断回调函数中没有使用中断级API xxFromISR 函数 xSemaphoreGiveFromISR uart busy HighterTask 正确 xSemaphoreGive uart busy 错误 2 比configMAX
  • FreeRTOS学习---“定时器”篇

    总目录 FreeRTOS学习 任务 篇 FreeRTOS学习 消息队列 篇 FreeRTOS学习 信号量 篇 FreeRTOS学习 事件组 篇 FreeRTOS学习 定时器 篇 FreeRTOS提供了一种软件定时器 用来快速实现一些周期性的
  • Unity动画机制 Animator与Animator Controller教程

    Chinar blog www chinar xin Unity动画机制 Animator Animation 本文提供全流程 中文翻译 Chinar 的初衷是将一种简单的生活方式带给世人 使有限时间 具备无限可能 Chinar 心分享 心
  • keil找不到device,怎么办?

    下载好的keil 准备调试程序 却发现这个问题 找不到我需要的芯片啊啊啊 头大 后面发现是缺少相应的pack 安装keil时 好像没有自动装上STM32系列芯片 所以得需要自己安装 百度一下 找一些资源 然后 把途中红色框住的 分别放在安装
  • Keil MDK编程环境下的 STM32 IAP下载(学习笔记)

    IAP下载 IAP的引入 不同的程序下载方式 ICP ICP In Circuit Programing 在电路编程 可通过 CPU 的 Debug Access Port 烧录代码 比如 ARM Cortex 的 Debug Interf
  • FreeRTOS多任务调度器基础

    Cortex M4中SysTick调度器核心 Cortex M4中的中断管理 Cortex M4中影子栈指针 Cortex M4中SVC和PendSV异常 1 Cortex M4中SysTick调度器核心 systick每一次中断都会触发内
  • SpringBoot整合ELK教程

    SpringBoot整合ELK教程 1 基础概念 ELK 即 Elasticsearch Logstash Kibana 组合起来可以搭建线上日志系统 本文主要讲解使用 ELK 来收集测试框架产生的日志 Elasticsearch 用于存储
  • FreeRTOSConfig.h 配置优化及深入

    本篇目标 基于上一篇的移植freertos stm32f4 freertos 上 修改 FreeRTOSConfig h 文件的相关配置来优化辅助 FreeRtos 的使用 并且建立一些基本功能 信号量 消息地列等 的简单应用位于 stm3
  • 如何在 Cortex-M3 (STM32) 上从 RAM 执行函数?

    我正在尝试从 Cortex M3 处理器 STM32 上的 RAM 执行函数 该函数会擦除并重写内部闪存 所以我肯定需要在 RAM 中 但我该怎么做呢 我尝试过的是 使用 memcpy 将函数复制到 RAM 中的字节数组 检查它是否正确对齐
  • 使用 GCC 编译器的 ARM 内核的堆栈回溯(当存在 MSP 到 PSP 切换时)

    核心 ARM Cortex M4 编译器 GCC 5 3 0 ARM EABI 操作系统 免费 RTOS 我正在使用 gcc 库函数 Unwind Reason Code Unwind Backtrace Unwind Trace Fn v
  • Push_back() 导致程序在进入 main() 之前停止

    我正在为我的 STM32F3 Discovery 板使用 C 进行开发 并使用 std deque 作为队列 在尝试调试我的代码 直接在带有 ST link 的设备上或在模拟器中 后 代码最终在 main 中输入我的代码之前在断点处停止 然
  • 显示多个 Google 路线的演示 (Google Maps API v3)

    大家好 有没有一个可以在单个 Google 地图上显示多个方向路线的功能演示 堆栈上的另一个问题 https stackoverflow com questions 3537676 google maps directions v3 mul
  • FreeRTOS 匈牙利表示法 [重复]

    这个问题在这里已经有答案了 我是 RTOS 和 C 编程的新手 而且我仍在习惯 C 的良好实践 因此 我打开了一个使用 FreeRTOS 的项目 我注意到操作系统文件使用匈牙利表示法 我知道一点符号 但面临一些新的 标准 FreeRTOS
  • UINT32_C 和 uint32_t 之间的区别

    据我所知后缀t in uint32 t denote t类型名称 但我想知道是什么C in UINT32 C有何不同 UINT32 C是一个定义整型常量的宏uint least32 t 例如 UINT32 C 123 Might expan

随机推荐

  • catkin_make学习总结

    catkin make学习总结 基础概念常用函数理解与注释其他有用的函数总结简单实例参考链接 基础概念 CMakeLists txt 文件中 xff0c 命令名字是不区分大小写的 xff0c 而参数和变量是大小写相关的ros的包 catki
  • 新手如何使用立创EDA完成电路设计

    软件简介 xff1a 立创EDA是一款基于浏览器的免费国产EDA绘图工具 下载方式 xff1a 百度 立创EDA 进入主页 xff0c 或主页点击 下载 客户端 xff0c 支持Wndows Luixus Mac系统下载安装 首先 xff0
  • Echart、Excel、highcharts、jfreechart对比

    Echart Excel highcharts jfreechart 柱状图 条形图 折线图 面积图 散点图 气泡图 K 线图 饼图 环形图 雷达图 力导布局图 和弦图 曲面图 地图
  • HAL库和标准库的区别

    本文回答来源于chat gpt4 xff0c 非原创 xff0c 也是我初学过程中所遇到的问题 xff0c 答案分享给大家 xff0c 如有侵权请联系删除 xff1a HAL 库 xff08 Hardware Abstraction Lay
  • 原来手机就能直接制作证件照,我也才知道,再也不用去照相馆了

    证件照选相信是我们大家日常所需 xff0c 但是去照相馆真的有点麻烦 xff0c 尤其是有时候仅仅只是需要换一个背景颜色 xff0c 其实不用这么麻烦 xff0c 现在手机上不仅能换背景颜色 xff0c 还能制作证件照 xff0c 还很简单
  • RTK基站 差分云共享技术,全套高精度定位解决方案

    针对区域内多个移动体高精度定位的需求 xff0c 为了最大程度的降低成本 xff0c FDISYSTEMS为DETA100系列具有联网功能的产品提供了免费的差分共享技术 xff0c 通过该技术可以将单一运载体从CORS服务器获取的差分修正R
  • STM32 Keil编程常见问题解决办法:(一)多行注释时出现红色下划线

    Problem xff1a 在STM32 Keil软件中进行多行注释时出现下图所示现象 xff0c 部分语句出现红色下划线 Solution xff1a 点击Keil软件上的小扳手 选择Text Completion 勾选ENTER TAB
  • 2017秋招求职历程总结

    2017秋招求职历程总结 从小的梦想就是有朝一日能够进入汽车行业工作 xff0c 很幸运刚毕业的第一份工作便实现了此梦想 xff0c 感谢大学遇到的那些人 终于在国庆之前拿到了一份还算满意的offer 9月1号从实习单位离职准备接下来的秋招
  • Win10常用命令:定时关机(shutdown命令)

    文章目录 一 单次 定时关机 xff1a Win 43 R 输入命令 xff1a 二 shutdown命令参数三 每天定时关机 一 单次 定时关机 xff1a Win 43 R 输入命令 xff1a 倒计时关机 xff1a shutdown
  • 如何实现超大文件(60G)传输给别人?

    2022 4 25 今天Ken问我要我工位上的一个虚拟机环境 xff0c 整个文件夹拷给他 但是这个CentOS的环境有60个G xff0c 我的U盘只有45G 想了几个办法 xff1a 压缩包 xff1a 用WinRAR压缩成压缩包 xf
  • CPU两大架构:X86与ARM的区别

    1 CPU 架构 Central Processing Unit Architecture X86 ARM MIPS PowerPC IA64 AMD64 x86 64 x64 是64位的CPU架构 区分ARM64 2 复杂指令集计算机CI
  • Linux(UOS、Ubuntu)虚拟机和Windows物理机之间无法复制粘贴

    我的UOS虚拟机和主机之间无法复制粘贴 xff0c 解决方案如下 xff1a 1 先更新一下软件列表 span class token function sudo span span class token function apt get
  • CMake、CMakeLists.txt

    2022 06 02 xff0c 今天开始研究cmake 不间断更新 一 说明 0 官方文档网址 xff1a www cmake org 1 cmake的定义 xff1a 高级编译配置工具 当多个人用不同的语言或者编译器开发一个项目 xff
  • ECMAScript6 入门 数组的扩展

    数组的扩展 1 xff1a 扩展运算符 xff1a 好比rest参数的逆运算 xff0c 将一个数组转换为用逗号分隔的参数序列 主要应用于函数调用 xff0c 将一个数组 xff0c 变为参数序列 如果扩展运算符后面是一个空的数组 xff0
  • CSDN排名记录

    文章目录 表格记录文字记录刷题记录 表格记录 时间 属性周排名总排名原创文章数收藏量粉丝数铁粉数2023年5月12日 xff1a 第20周3175593128893843912572023年5月6日 xff1a 第19周3211559128
  • 虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本。 模块“Upgrade”启动失败。 未能启动虚拟机。

    问题 xff1a 虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本 模块 Upgrade 启动失败 未能启动虚拟机 分析 xff1a 该虚拟机环境之前使用的VMware版本与你所使用的VMware版本不一致 大概
  • C++基础

    文章目录 推荐速成视频一 数据的输入输出cin xff1a 输入cin getline xff1a 读取一行内容cout xff1a 输出I O格式控制 二 C 43 43 函数重载三 类和对象1 struct与class 1 struct
  • Ch1. 逻辑结构、存储结构、时间复杂度、空间复杂度

    文章目录 一 逻辑结构 与 存储结构 1 逻辑结构 1 集合结构 2 线性结构 3 树形结构 4 图形结构 2 存储结构 物理结构 1 顺序存储 2 链式存储 3 索引存储 4 散列存储 概念 二 时间复杂度 空间复杂度
  • Ch2.线性表

    文章目录 第2章 线性表 一 顺序表 1 顺序表的定义 初始化 静态分配 动态分配 2 顺序表的插入 3 顺序表的删除 二 链表 1 单链表 1 单链表结点的数据类型定义 2 单链表的建立 头插法
  • FreeRTOS的源代码个人分析(基于KEIL下STM32F103的Demo) 四

    开始任务的实现分析 xff1a xPortStartScheduler 函数 FreeRTOS里开始任务是在main里调用vTaskStartScheduler函数来开始任务的 xff0c 在调用这个函数后 xff0c 系统会先自动的创建一