跟着野火学FreeRTOS:第一段(空闲任务与阻塞延时的实现)

news2024/11/22 15:34:05

     在前一小节中,任务操作里面的延时就是直接让 C P U CPU CPU干等着,啥也不干,这样会极大的浪费 C P U CPU CPU的资源。这一小节即将要讲到的阻塞延时就是当任务有延时需要的时候让 C P U CPU CPU不要干等着,而是去执行其它的任务,充分利用 C P U CPU CPU资源,就算此时没有任何任务需要执行那我们就可以执行我们专门建立的一个空闲任务,这个空闲任务可以上什么都不干或者进入低功耗模式来节能,当相应任务的延时到期的时候再转去执行对应的任务,这样的话 C P U CPU CPU的利用率将大大提高。那么我们首先来建立一个空闲任务,这个任务的建立和在前一小节中任务1和任务2的建立基本没有区别,只是这个任务需要插入到就绪列表数组中索引为0的链表中,这是因为空闲任务在系统中的优先级是最低的,只有当前没有任务执行或任务需要延迟的时候采取执行它,就绪列表数组的索引号就代表着挂到该元素对应的链表里面的任务的优先级,索引号越大,优先级越高。空闲任务对应的任务控制块,任务栈以及任务对应的函数的定义和空闲任务的创建如下所示。

TaskHandle_t IdleTask_Handle;
#define IDLETASK_STACK_SIZE                    20
StackType_t IdleTaskStack[IDLETASK_STACK_SIZE];
TCB_t IdleTaskTCB;

/*
*****************************************************************************
                                Idle Task 
*****************************************************************************
*/
void IdleTask_Entry( void *p_arg )
{
    while(1);
}

    IdleTask_Handle = xTaskCreateStatic( (TaskFunction_t)IdleTask_Entry,  
					                  (char *)"IdleTask",              
					                  (uint32_t)IDLETASK_STACK_SIZE ,  
					                  (void *) NULL,                
					                  (StackType_t *)IdleTaskStack,    
					                  (TCB_t *)&IdleTaskTCB );         
                              
    vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)(&IdleTaskTCB))->xStateListItem ) );	


     这里需要注意的是在 F r e e R T O S FreeRTOS FreeRTOS的源码中空闲任务的创建是在源文件 t a s k s . c tasks.c tasks.c中的 v T a s k S t a r t S c h e d u l e r vTaskStartScheduler vTaskStartScheduler接口里面创建的,而不是像我这里这样,我这里只是为了讲解方便(野火的讲解中空闲任务的创建也是在源文件 t a s k s . c tasks.c tasks.c中的 v T a s k S t a r t S c h e d u l e r vTaskStartScheduler vTaskStartScheduler接口里面创建的), F r e e R T O S FreeRTOS FreeRTOS的源码中空闲任务对应的函数名也不是我这里的名字而是 p r v I d l e T a s k prvIdleTask prvIdleTask,不同的配置之下 F r e e R T O S FreeRTOS FreeRTOS的源码中空闲任务的创建接口可能也不是这里的静态创建接口 x T a s k C r e a t e S t a t i c xTaskCreateStatic xTaskCreateStatic(使用用户定义的 R A M RAM RAM空间)而是使用动态创建接口 x T a s k C r e a t e xTaskCreate xTaskCreate(使用动态分配的 R A M RAM RAM空间)。
     空闲任务创建完毕之后,下一步我们要来看一下阻塞延时如何实现。为了实现阻塞延时这里的任务控制块里面加了一个元素 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay,但是要注意的是在 F r e e R T O S FreeRTOS FreeRTOS的源码中任务控制块中是没有这个元素的,这里只不过是为了讲解的便利,在 F r e e R T O S FreeRTOS FreeRTOS的源码中阻塞延时的实现应该用到了延时列表(后面的章节会讲到)。这个元素的值表示当前任务需要延时的时间单位的个数,时间单位是配置的 S y s T i c k SysTick SysTick的周期,只有当任务对应的任务控制块里面的元素 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay的值为0的时候才会再次恢复执行。下面我们再来看一下阻塞延时对应的接口 v T a s k D e l a y vTaskDelay vTaskDelay,如下所示。这个接口比较简单,只是简单的将当前正在执行的任务的任务控制块的元素 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay的值赋值为需要延时的 S y s T i c k SysTick SysTick的周期的个数,然后进行一次任务切换因为这里已经打算对当前任务进行延时了,那肯定要切换到其它需要运行的任务或空闲任务。相应的这里相对于前一小节任务1和任务2对应的函数也有所改变,任务对应的函数中使用接口 v T a s k D e l a y vTaskDelay vTaskDelay进行延时操作且不再在里面调用任务切换接口。

void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;

    pxTCB = pxCurrentTCB;

    pxTCB->xTicksToDelay = xTicksToDelay;
    
    taskYIELD();
}
/*
*****************************************************************************
                                Task 1
*****************************************************************************
*/
void Task1_Entry( void *p_arg )
{
    while(1)
	{
		    flag1 = 1;
		    vTaskDelay(2);		
		    flag1 = 0;
		    vTaskDelay(2);	
    }
}
/*
*****************************************************************************
                                Task 2
*****************************************************************************
*/
void Task2_Entry( void *p_arg )
{
    while(1)
	{
		    flag2 = 1;
		    vTaskDelay(2);			
		    flag2 = 0;
		    vTaskDelay(2);	
    }
}

     有了前面的逻辑在前一小节中的保存上文之后寻找最高优先级的接口 v T a s k S w i t c h C o n t e x t vTaskSwitchContext vTaskSwitchContext(相对于源码中的定义,这里做了大量精简,在 t a s k s . c tasks.c tasks.c这个文件中定义。虽然这里也没有实现优先级)的逻辑也得变一下。

  • 如果当前任务是空闲任务,则先看任务1需不需要再延时,如果不需要就切换到任务1执行,如果需要就看任务2需不需要再延时,如果不需要就切换到任务2执行,如果需要就继续执行空闲任务。
  • 如果当前任务是任务1,则先看任务2需不需要再延时,如果不需要就切换到任务2执行,如果需要就看任务1需不需要再延时,如果需要就切换到空闲任务,如果不需要就继续执行任务1。
  • 如果当前任务是任务2,则先看任务1需不需要再延时,如果不需要就切换到任务1执行,如果需要就看任务2需不需要再延时,如果需要就切换到空闲任务,如果不需要就继续执行任务2。
void vTaskSwitchContext( void )
{
    if( pxCurrentTCB == &IdleTaskTCB )
	{
	    if(Task1TCB.xTicksToDelay == 0)
		{            
            pxCurrentTCB =&Task1TCB;
		}
		else if(Task2TCB.xTicksToDelay == 0)
		{
            pxCurrentTCB =&Task2TCB;
		}
		else
		{
			return;	
		}
    }
	else
	{
		if(pxCurrentTCB == &Task1TCB)
		{
		    if(Task2TCB.xTicksToDelay == 0)
			{
                pxCurrentTCB =&Task2TCB;
			}
			else if(pxCurrentTCB->xTicksToDelay != 0)
			{
                pxCurrentTCB = &IdleTaskTCB;
			}
			else 
			{
			    return;	
			}
		}
		else if(pxCurrentTCB == &Task2TCB)
		{
	        if(Task1TCB.xTicksToDelay == 0)
			{
                pxCurrentTCB =&Task1TCB;
			}
			else if(pxCurrentTCB->xTicksToDelay != 0)
			{
                pxCurrentTCB = &IdleTaskTCB;
			}
			else 
			{
			    return;		
			}
		}
    }
}

     前面我们提到任务控制块里面加了一个元素 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay,它表示当前任务需要延时的时间单位的个数,时间单位是配置的 S y s T i c k SysTick SysTick的周期,只有当任务对应的任务控制块里面的元素 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay的值为0的时候表示延时结束。既然延时的单位是 S y s T i c k SysTick SysTick的周期,那么每一个任务的任务控制块里面的元素 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay只要不为0,在每进一次 S y s T i c k SysTick SysTick中断函数的时候都应该进行减一的操作。我们先看 S y s T i c k SysTick SysTick的初始化的接口 v P o r t S e t u p T i m e r I n t e r r u p t vPortSetupTimerInterrupt vPortSetupTimerInterrupt,这个接口在 F r e e R T O S FreeRTOS FreeRTOS源码中的 p o r t . c port.c port.c文件中定义。这个接口没啥好说的,只是使能了 S y s T i c k SysTick SysTick中断且中断的周期为10ms。我们前面说过这一部分的代码例子是不依赖于任何的硬件环境的,所以具体的系统时钟为多少,我们直接看工程里面的 s y s t e m A R M C M 3. c system_ARMCM3.c systemARMCM3.c文件里面的宏 S Y S T E M C L O C K SYSTEM_CLOCK SYSTEMCLOCK定义的值( 25000000 H Z 25000000HZ 25000000HZ),且按照这个值去配置, S y s T i c k SysTick SysTick直接使用系统时钟而没有使用系统时钟除8的时钟。 S y s T i c k SysTick SysTick初始化的接口在 x P o r t S t a r t S c h e d u l e r xPortStartScheduler xPortStartScheduler接口中调用。

/*
 * Setup the SysTick timer to generate the tick interrupts at the required
 * frequency.
 */
#if ( configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0 )

    __weak 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 */

        /* Stop and clear the SysTick. */
        portNVIC_SYSTICK_CTRL_REG = 0UL;
        portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;

        /* 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_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
    }

#endif /* configOVERRIDE_DEFAULT_TICK_CONFIGURATION */
/*-----------------------------------------------------------*/

     下面来看一下 S y s T i c k SysTick SysTick的中断函数 x P o r t S y s T i c k H a n d l e r xPortSysTickHandler xPortSysTickHandler,该函数在 F r e e R T O S FreeRTOS FreeRTOS源码中的 p o r t . c port.c port.c文件中定义(这里有修改)。 S y s T i c k SysTick SysTick的中断函数调用接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick对处于就绪列表中的每一个任务,如果其任务控制块的 x T i c k s T o D e l a y xTicksToDelay xTicksToDelay元素的值不为0则做减一操作,调用之后会进行触发任务的切换操作,因为此时某些任务的延时已经结束或者某些任务开始了延时需要重新恢复执行。接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick F r e e R T O S FreeRTOS FreeRTOS源码中的 t s a k s . c tsaks.c tsaks.c文件中定义(这里有大量精简修改)。这里 S y s T i c k SysTick SysTick的中断函数在调用接口 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick的时候将 x T a s k I n c r e m e n t T i c k xTaskIncrementTick xTaskIncrementTick当做临界段处理,通过调用 v P o r t R a i s e B A S E P R I vPortRaiseBASEPRI vPortRaiseBASEPRI v P o r t C l e a r B A S E P R I F r o m I S R vPortClearBASEPRIFromISR vPortClearBASEPRIFromISR来进出和退出临界段。

void xPortSysTickHandler( void )
{
    /* The SysTick runs at the lowest interrupt priority, so when this interrupt
     * executes all interrupts must be unmasked.  There is therefore no need to
     * save and then restore the interrupt mask value as its value is already
     * known - therefore the slightly faster vPortRaiseBASEPRI() function is used
     * in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
    vPortRaiseBASEPRI();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* A context switch is required.  Context switching is performed in
             * the PendSV interrupt.  Pend the PendSV interrupt. */
            //portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
			portYIELD();
        }
    }

    vPortClearBASEPRIFromISR();
}
BaseType_t xTaskIncrementTick( void )
{
    TCB_t *pxTCB = NULL;
    BaseType_t i = 0;
    
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;

	for(i=0; i<configMAX_PRIORITIES; i++)
	{
        pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );
	  	if(pxTCB->xTicksToDelay > 0)
	  	{
	  	      pxTCB->xTicksToDelay --;
	  	}
	}
    return pdTRUE;	
}

     好了为了实现空闲任务和阻塞延时,在前一小节上需要做的修改都已经介绍完,下面我们来实际跑跑代码看看,和上一小节一样这里还是使用 K E I L − M D K KEIL−MDK KEILMDK自带的 L o g i c A n a l y z e r Logic\quad Analyzer LogicAnalyzer来看结果,结果如图1所示。这里就好像是两个任务在同时执行一样。这是因为这里的延时没有让 C P U CPU CPU干等待而是转而去执行需要执行的其它任务或空闲任务。相关工程代码在这里。

 
图1.

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

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

相关文章

取消paypal免密支付绑定平台

在设置支付中-》自动支付-》取消特定平台即可。

随机森林 2(决策树)

通过 随机森林 1 的介绍&#xff0c;相信大家对随机森林都有了一个初步的认知&#xff0c;知道了随机和森林分别指的是什么&#xff0c;以及决策树根据什么选择内部节点。本文将会从森林深入到树&#xff0c;去看一下决策树是如何构建的。网上很多文章都讲了决策树如何构建&…

【已解决】解决Springboot项目访问本地图片等静态资源无法访问的问题

今天在开发一个招聘系统的时候&#xff0c;有投递简历功能&#xff0c;有投递就会有随之而来的查看简历对吧&#xff0c;我投递过的简历&#xff0c;另存为一个文件夹&#xff0c;就是说本地磁盘(或者服务器)有一个专门存放投递过的简历的文件夹&#xff0c;用于存放PDF&#x…

用户管理第2节课--idea 2023.2 后端--实现基本数据库操作(操作user表)

一、模型user对象>和数据库的字段关联 & 自动生成 【其中涉及删除表数据&#xff0c;一切又从零开始】 二、模型user对象>和数据库的字段关联 2.1在model文件夹下&#xff0c;新建 user对象 2.1.1 概念 大家可以想象我们现在的数据是存储在数据库里的&…

Java中的时间日期类⭐️通过具体案例分析下开发中常用到的几种时间日期格式类的使用

小伙伴们大家好&#xff0c;系统中不少模块都要用到时间日期&#xff0c;来分析总结下项目中用到的些日期类 目录 一、时间日期类 1.java.util.Calendar&#xff1a; 2.java.util.Date&#xff1a; 3.java.time.LocalDate、java.time.LocalTime、java.time.LocalDateTime&…

Ubuntu 常用命令之 netstat 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 netstat 是一个非常有用的命令行工具&#xff0c;它可以帮助我们监控和诊断网络问题。在 Ubuntu 系统中&#xff0c;我们可以使用 netstat 命令来查看网络连接、路由表、接口统计等信息。 netstat 命令的参数有很多&#xff0c;以…

anaconda 安装 使用 pytorch onnx onnxruntime

一&#xff1a;安装 如果不是 x86_64&#xff0c;需要去镜像看对应的版本 安装 Anaconda 输入命令 bash Anaconda3-2021.11-Linux-x86_64.sh 然后输入 yes 表示同意 确认安装的路径&#xff0c;一般直接回车安装在默认的 /home/你的名字/anaconda3 很快就安装完毕。输入 yes…

【Vue中给输入框加入js验证_blur失去焦点进行校验】

【Vue中给输入框加入js验证_blur失去焦点进行校验】 通俗一点就是给输入框加个光标离开当前文本输入框时&#xff0c;然后对当前文本框内容进行校验判断 具体如下&#xff1a; 1.先给文本框加属性 blur“validatePhoneNumber” <el-input v-model“entity.telephone” blur…

航空港务数据大屏为航空港的可持续发展提供有力支撑!

随着经济的发展&#xff0c;不断加建与扩建民用机场&#xff0c;空港行业规模不断扩大。在不断引进和消化发达国家先进技术的同时&#xff0c;中国深入开展了对新技术和新材料的研究&#xff0c;极大地丰富和发展了中国的机场建设技术。且各项机场建设计划均已落实推进&#xf…

下午好~ 我的论文【yolov5】(第四期)

文章目录 简介模型Mosaic数据增强自适应锚框计算自适应图片缩放Focus结构CSP结构 NeckCIOU_Lossnms非极大值抑制代码最后 简介 YOLO V4没过多久YOLO V5就出来了。YOLO V5的模型架构是与V4非常相近的。 模型 Yolov5官方代码中&#xff0c;给出的目标检测网络中一共有4个版本&…

列举mfc140u.dll丢失的解决方法,常见的mfc140u.dll问题

在使用电脑的过程中&#xff0c;有时会遇到mfc140u.dll文件丢失的问题&#xff0c;导致一些应用程序无法正常启动。本文将介绍mfc140u.dll丢失的常见原因&#xff0c;并提供相应的解决办法。同时&#xff0c;还会列举一些与mfc140u.dll丢失相关的常见问题和解答。 第一部分&…

基于FPGA的简易BPSK和QPSK

1、框图 2、顶层 3、m_generator M序列的生成&#xff0c;输出速率为500Kbps 4、S2P是串并转换模块 将1bit的m序列转换到50M时钟下的2bit M序列数据&#xff08;就有4个象限&#xff09;&#xff1b; 5、my_pll是生成256M的时钟作为载波&#xff0c;因为sin和cos信号的…

数据管理平台Splunk Enterprise本地部署结合内网穿透实现远程访问

文章目录 前言1. 搭建Splunk Enterprise2. windows 安装 cpolar3. 创建Splunk Enterprise公网访问地址4. 远程访问Splunk Enterprise服务5. 固定远程地址 前言 Splunk Enterprise是一个强大的机器数据管理平台&#xff0c;可帮助客户分析和搜索数据&#xff0c;以及可视化数据…

汽车大灯led恒流驱动芯片OC5501 可高低亮调光

LED恒流驱动芯片是一种在汽车大灯中常见的关键部件。它能提供稳定的电流&#xff0c;使LED灯光亮度均匀、稳定&#xff0c;并且延长LED的寿命。 传统的汽车大灯使用的是卤素灯泡&#xff0c;而现在越来越多的汽车厂商开始使用LED大灯。LED大灯具有更好的能效和更长的寿命&…

08 Vue3中的v-text指令

概述 v-text指令主要是用来渲染文本内容&#xff0c;和双大括号的效果基本一致&#xff0c;所以使用场景非常少。 一般情况下&#xff0c;我们都会使用双大括号语法去渲染文本内容&#xff0c;而不是使用v-text指令。 基本用法 我们创建src/components/Demo08.vue&#xff…

【超图】SuperMap iClient3D for WebGL/WebGPU ——颜色(1)

作者&#xff1a;taco 在项目中通常会出现&#xff0c;高亮对象。给对象设置颜色的一些问题。那么针对颜色设置在超图的 iClient3D for WebGl/WebGPU 中又提供了哪些方式呢&#xff1f;本篇文章将介绍一些颜色的设置方法。以及一些颜色的参数设置。 在iClient3D for WebGl/WebG…

ChimeraX使用教程-安装及基本操作

ChimeraX使用教程-安装及基本操作 1、访问https://www.cgl.ucsf.edu/chimerax/download.html进行下载&#xff0c;然后安装 安装完成后&#xff0c;显示界面 2、基本操作 1、点击file&#xff0c;导入 .PDB 文件。 &#xff08;注&#xff1a;在 alphafold在线预测蛋白》点…

比特币和区块链并非游离在法律之外

​​发表时间&#xff1a;2023年12月01日 近年来&#xff0c;围绕区块链监管的讨论&#xff0c;已经成为政策制定者、行业领袖和区块链爱好者之间越来越重要的话题。随着各国政府在促进创新和确保消费者保护之间寻求着平衡&#xff0c;有关区块链监管的持续讨论反映出这项变革性…

Ansible的脚本---Playbook剧本编写

playbook的组成部分 1、 tasks&#xff1a;任务 在目标主机上需要执行的操作。使用模块定义这些操作。每个任务都是一个模块的调用。 2、 variables&#xff1a;变量 用于存储和传递数据。类似于shell脚本中的变量。变量可以自定义。可以在playbook当中定义为全局变量&…

计算机网络3

1.三种通信方式 2.失真 &#xff08;码间串扰&#xff1a;码元传输速率过快导致&#xff09; 3.奈氏准则 4.香农定理 5.基带and宽带信号 6.编码与调制 ​​​​​​​7.数据交换方式 1.电路交换及优缺点 2.报文交换及优缺点 3.分组交换及优缺点 8.数据交换方式的选择和特点…