freertos空闲任务、阻塞延时

freertos空闲任务、阻塞延时

    • 空闲任务
    • 阻塞延时
    • SysTick
    • 实验现象

阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态。

rtos中的延时叫阻塞延时,即任务需要延时的时候,会放弃CPU的使用权,进入阻塞状态。在任务阻塞的这段时间,CPU可以去执行其它的任务(如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务),当任务延时时间到,重新获取 CPU 使用权,任务继续运行。

空闲任务:处理器空闲的时候,运行的任务。当系统中没有其他就绪任务时,空闲任务开始运行,空闲任务的优先级是最低的。

空闲任务

定义空闲任务:

#define portSTACK_TYPE	uint32_t
typedef portSTACK_TYPE StackType_t;
/*定义空闲任务的栈*/
#define configMINIMAL_STACK_SIZE	( ( unsigned short ) 128 )
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
/*定义空闲任务的任务控制块*/
TCB_t IdleTaskTCB;

创建空闲任务:在vTaskStartScheduler调度器启动函数中创建。

/*任务控制块的结构体 */
typedef struct tskTaskControlBlock
{volatile StackType_t    *pxTopOfStack;    /* 栈顶 */ListItem_t			    xStateListItem;   /* 任务节点 */StackType_t             *pxStack; /* 任务栈起始地址 */char     pcTaskName[ configMAX_TASK_NAME_LEN ];/* 任务名称,字符串形式 */TickType_t xTicksToDelay; /* 用于延时 */} tskTCB;
typedef tskTCB TCB_t;/*获取获取空闲任务的内存:任务控制块、任务栈起始地址、任务栈大小*/
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{*ppxIdleTaskTCBBuffer=&IdleTaskTCB;//空闲任务的任务控制块*ppxIdleTaskStackBuffer=IdleTaskStack; //空闲任务的任务栈*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;//栈的大小
}void vTaskStartScheduler( void )
{
/*创建空闲任务start*/     TCB_t *pxIdleTaskTCBBuffer = NULL;               /* 用于指向空闲任务控制块 */StackType_t *pxIdleTaskStackBuffer = NULL;       /* 用于空闲任务栈起始地址 */uint32_t ulIdleTaskStackSize;/* 获取:任务控制块、任务栈起始地址、任务栈大小 */vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );    /*创建空闲任务*/xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask,              /* 任务入口 */(char *)"IDLE",                           /* 任务名称,字符串形式 */(uint32_t)ulIdleTaskStackSize ,           /* 任务栈大小,单位为字 */(void *) NULL,                            /* 任务形参 */(StackType_t *)pxIdleTaskStackBuffer,     /* 任务栈起始地址 */(TCB_t *)pxIdleTaskTCBBuffer );           /* 任务控制块 *//* 将任务添加到就绪列表 */                                 vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );/*创建空闲任务end*//* 手动指定第一个运行的任务 */pxCurrentTCB = &Task1TCB;/* 初始化系统时基计数器 */xTickCount = ( TickType_t ) 0U;/* 启动调度器 */if( xPortStartScheduler() != pdFALSE ){/* 调度器启动成功,则不会返回,即不会来到这里 */}
}//下面是空闲任务的任务入口,看到,里面什么都没做
//这个我用debug发现一直卡到这个for不动了。
//通过单步运行,发生了中断,程序也无法进入中断。
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{/* 防止编译器的警告 */( void ) pvParameters;for(;;){/* 空闲任务暂时什么都不做 */}
}

阻塞延时

任务函数如下:延时函数由软件延时替代为阻塞延时。

void Task1_Entry( void *p_arg )
{for( ;; ){
#if 0        flag1 = 1;delay( 100 );/*软件延时*/		flag1 = 0;delay( 100 );/* 线程切换,这里是手动切换 */portYIELD();
#elseflag1 = 1;vTaskDelay( 2 );/*阻塞延时*/		flag1 = 0;vTaskDelay( 2 );
#endif        }
}

任务函数里面调用了vTaskDelay阻塞延时函数,如下。

/*阻塞延时函数的定义 */
void vTaskDelay( const TickType_t xTicksToDelay )
{TCB_t *pxTCB = NULL;/* 获取当前任务的任务控制块 */pxTCB = pxCurrentTCB;/* 设置延时时间:xTicksToDelay个SysTick延时周期 */pxTCB->xTicksToDelay = xTicksToDelay;/* 任务切换 */taskYIELD();
}

然后vTaskDelay里面调用了taskYIELD函数,如下。目的是产生PendSV中断,进入PendSV中断服务函数。

/* Interrupt control and state register (SCB_ICSR):0xe000ed04* Bit 28 PENDSVSET: PendSV set-pending bit*/
#define portNVIC_INT_CTRL_REG		( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT		( 1UL << 28UL )#define portSY_FULL_READ_WRITE		( 15 )
/* Scheduler utilities. */
#define portYIELD()																\
{																				\/* 设置 PendSV 的中断挂起位,产生上下文切换 */								\portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\\/* Barriers are normally not required but do ensure the code is completely	\within the specified behaviour for the architecture. */						\__dsb( portSY_FULL_READ_WRITE );											\__isb( portSY_FULL_READ_WRITE );											\
}

PendSV中断服务函数如下,里面调用了vTaskSwitchContext上下文切换函数,目的是寻找最高优先级的就绪任务,然后更新pxCurrentTCB。

__asm void xPortPendSVHandler( void )
{
//	extern uxCriticalNesting;extern pxCurrentTCB;extern vTaskSwitchContext;PRESERVE8/* 当进入PendSVC Handler时,上一个任务运行的环境即:xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存 *//* 获取任务栈指针到r0 */mrs r0, pspisbldr	r3, =pxCurrentTCB		/* 加载pxCurrentTCB的地址到r3 */ldr	r2, [r3]                /* 加载pxCurrentTCB到r2 */stmdb r0!, {r4-r11}			/* 将CPU寄存器r4~r11的值存储到r0指向的地址 */str r0, [r2]                /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */				stmdb sp!, {r3, r14}        mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    /* 进入临界段 */msr basepri, r0dsbisbbl vTaskSwitchContext       /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */ mov r0, #0                  /* 退出临界段 */msr basepri, r0ldmia sp!, {r3, r14}        /* 恢复r3和r14 */ldr r1, [r3]ldr r0, [r1] 				/* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/ldmia r0!, {r4-r11}			/* 出栈 */msr psp, r0isbbx r14                      nop
}

vTaskSwitchContext上下文切换函数如下。

任务需要延时的时候,会放弃CPU的使用权,进入阻塞状态。在任务阻塞的这段时间,CPU可以去执行其它的任务(如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务),当任务延时时间到,重新获取 CPU 使用权,任务继续运行。

void vTaskSwitchContext( void )
{if( pxCurrentTCB == &IdleTaskTCB )//如果当前线程是空闲线程{if(Task1TCB.xTicksToDelay == 0)//如果线程1延时时间结束{            pxCurrentTCB =&Task1TCB;//切换到线程1}else if(Task2TCB.xTicksToDelay == 0)//如果线程2延时时间结束(线程1在延时中){pxCurrentTCB =&Task2TCB;//切换到线程2}else{return;		/* 线程延时均没有到期则返回,继续执行空闲线程 */} }else//当前任务不是空闲任务{if(pxCurrentTCB == &Task1TCB)//如果当前线程是线程1{if(Task2TCB.xTicksToDelay == 0)//如果线程2不在延时中{pxCurrentTCB =&Task2TCB;//切换到线程2}else if(pxCurrentTCB->xTicksToDelay != 0)//如果线程1进入延时状态(线程2也在延时中){pxCurrentTCB = &IdleTaskTCB;//切换到空闲线程}else {return;		/* 返回,不进行切换 */}}else if(pxCurrentTCB == &Task2TCB)//如果当前线程是线程2{if(Task1TCB.xTicksToDelay == 0)//如果线程1不在延时中{pxCurrentTCB =&Task1TCB;//切换到线程1}else if(pxCurrentTCB->xTicksToDelay != 0)//如果线程2进入延时状态(线程1也在延时中){pxCurrentTCB = &IdleTaskTCB;//切换到空闲线程}else {return;		/* 返回,不进行切换*/}}}
}

由上面代码可知,vTaskSwitchContext上下文切换函数通过看xTicksToDelay是否为零,来判断任务已经就绪or继续延时。

xTicksToDelay以什么周期递减,在哪递减。这个周期由SysTick中断提供。

SysTick

SysTick是系统定时器,重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。

下面是SysTick的初始化。

//main函数里面/* 启动调度器,开始多任务调度,启动成功则不返回 */vTaskStartScheduler();  //task.c里面调用了xPortStartScheduler函数
void vTaskStartScheduler( void )
{//.....省略部分代码/* 启动调度器 */if( xPortStartScheduler() != pdFALSE ){/* 调度器启动成功,则不会返回,即不会来到这里 */}
}//port.c里面
//xPortStartScheduler调度器启动函数,里面调用了vPortSetupTimerInterrupt函数初始化SysTick
BaseType_t xPortStartScheduler( void )
{/* 配置PendSV 和 SysTick 的中断优先级为最低 */portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;/* 初始化SysTick */vPortSetupTimerInterrupt();/* 启动第一个任务,不再返回 */prvStartFirstTask();/* 不应该运行到这里 */return 0;
}//system_ARMCM4.c文件
#define  XTAL            (50000000UL)     /* Oscillator frequency */
#define  SYSTEM_CLOCK    (XTAL / 2U)//FreeRTOSConfig.h文件
//系统时钟大小
#define configCPU_CLOCK_HZ			( ( unsigned long ) 25000000 )	
//SysTick每秒中断多少次,配置成100,10ms中断一次
#define configTICK_RATE_HZ			( ( TickType_t ) 100 )//下面初始化SysTick
/* SysTick 控制寄存器 */
#define portNVIC_SYSTICK_CTRL_REG			( * ( ( volatile uint32_t * ) 0xe000e010 ) )
/*SysTick 重装载寄存器*/
#define portNVIC_SYSTICK_LOAD_REG			( * ( ( volatile uint32_t * ) 0xe000e014 ) )
/*SysTick时钟源的选择*/
#ifndef configSYSTICK_CLOCK_HZ#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ//configSYSTICK_CLOCK_HZ=configCPU_CLOCK_HZ/* 确保SysTick的时钟与内核时钟一致 */#define portNVIC_SYSTICK_CLK_BIT	( 1UL << 2UL )//无符号长整形32位二进制,左移两位
#else#define portNVIC_SYSTICK_CLK_BIT	( 0 )
#endif#define portNVIC_SYSTICK_INT_BIT			( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT			( 1UL << 0UL )//初始化SysTick的函数如下
void vPortSetupTimerInterrupt( void )
{/* 设置重装载寄存器的值 */portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;/* 设置系统定时器的时钟等于内核时钟使能SysTick 定时器中断使能SysTick 定时器 */portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |portNVIC_SYSTICK_ENABLE_BIT ); 
}

初始化好SysTick,下面看看SysTick的中断服务函数。

现在就明白了,xTicksToDelay是以SysTick的中断周期递减的。

// port.c文件,SysTick中断服务函数
//里面调用了xTaskIncrementTick函数更新系统时基
void xPortSysTickHandler( void )
{/* 关中断 进入临界段*/vPortRaiseBASEPRI();/* 更新系统时基 */xTaskIncrementTick();/* 开中断 退出临界段*/vPortClearBASEPRIFromISR();
}//task.c文件,
static volatile TickType_t xTickCount 				= ( TickType_t ) 0U;
void xTaskIncrementTick( void )
{TCB_t *pxTCB = NULL;BaseType_t i = 0;/* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;//把xTickCount加1/* 扫描就绪列表中所有线程的xTicksToDelay,如果不为0,则减1 */for(i=0; i<configMAX_PRIORITIES; i++){pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );if(pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay --;}}/* 任务切换 */portYIELD();
}

实验现象

这个里面就可以看到,高电平时间是20ms,刚好是阻塞延时的20ms。而且两个任务波形相同,好像是CPU在同时做两件事。这就是阻塞延时的好处。

为什么呢,

一开始,所有任务都没有进入延时。

当一个任务放弃CPU后(进入延时),这一瞬间,CPU立即转向运行另一个任务(另一个任务也立即进入延时)。这是因为uvTaskDelay阻塞延时函数里面调用了taskYIELD()任务切换函数。所以产生PendSV中断,进入PendSV中断服务函数xPortPendSVHandler。

在那个PendSV中断服务函数里面,调用vTaskSwitchContext上下文切换函数,由于现在两个任务都在延时过程中,就开始切到空闲任务。

等到重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,进入系统定时器的中断函数中,改变xTicksToDelay,然后再次调用任务切换函数portYIELD()。目的是产生PendSV中断,进入PendSV中断服务函数。

然后再次调用vTaskSwitchContext上下文切换函数,判断现在两个任务是否还在延时,如果任务1不在延时,那么立即切到任务1,任务1里面又调用uvTaskDelay阻塞延时函数,再次套娃重复上面的活动。

所以波形上几乎同步。

在这里插入图片描述

之前用软件延时在任务函数里面写delay(100),这就属于cpu一直跑这个delay,跑完了才进行任务切换,如下图所示,一个任务高低电平全搞完,才切到下一个任务。

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

树莓派配置

树莓派配置1.安装操作系统2.修改镜像源3.配置VNC连接1.安装操作系统 安装操作系统&#xff0c;首先在官网下载https://www.raspberrypi.com/software/operating-systems/ 下载出来的解压后是一个光盘映像文件&#xff0c;接下来就需要把这个文件写入到树莓派的tf卡里。 安装树…

远程桌面连机器人

这里需要PC和机器人处于同一局域网下。 PC使用xhell连接机器人&#xff0c;通过 ifconfig 查看机器人的无线 IP 地址。 然后退出xshell&#xff0c;打开NoMachine软件进行远程桌面连接。输入机器人无线ip地址。 输入用户名和密码 然后就远程连接上了 现在开一个终端如下所示&…

RNNLM

RNNLM 基于RNN的语言模型称为RNNLM(Language Model)。 Embedding 层&#xff1a;将单词ID转化为单词的分布式表示&#xff08;单词向量&#xff09;。 RNN层&#xff1a;向下一层(上方)输出隐藏状态&#xff0c;同时也向下一时刻的RNN层(右边)输出隐藏状态。 对于“you say …

使用c#制作赛尔号登录器

使用c#制作赛尔号登录器 需求&#xff1a; 打开赛尔号官网&#xff0c;发现我的chrome浏览器无法运行Flash。这是因为Adobe 公司放弃了对 Flash Player 的支持。 那么如果我想要玩游戏&#xff0c;又不想下载别的浏览器&#xff0c;只好自己写一个登陆器了。 创建项目 首先…

普通RNN的缺陷—梯度消失和梯度爆炸

之前的RNN&#xff0c;无法很好地学习到时序数据的长期依赖关系。因为BPTT会发生梯度消失和梯度爆炸的问题。 RNN梯度消失和爆炸 对于RNN来说&#xff0c;输入时序数据xt时&#xff0c;RNN 层输出ht。这个ht称为RNN 层的隐藏状态&#xff0c;它记录过去的信息。 语言模型的任…

LSTM的结构

RNN和LSTM 简略表示RNN层&#xff1a;长方形节点中包含了矩阵乘积、偏置的和、tanh函数的变换。将下面这个公式表示成一个tanh节点。 LSTM&#xff1a;Long Short-Term Memory&#xff08;长短期记忆&#xff09;&#xff0c;长时间维持短期记忆。 LSTM与RNN的接口(输入输出)…

STM32 USART 补充

串口通讯的数据包&#xff1a;发送设备通过自身的TXD接口传输到接收设备的RXD接口。 串口通讯的协议层中&#xff0c;规定了数据包的内容&#xff0c;由起始位、主体数据、校验位、停止位组成&#xff0c;通讯双方的数据包格式要约定一致才能正常收发数据。 异步通讯&#xf…

ROS TF变换

静态坐标转换&#xff1a;机器人本体中心到雷达中心的转换。因为激光雷达可能没安装到机器人的中心。 动态坐标转换&#xff1a;机器人中心和里程计坐标的变换。机器人从起点出发后&#xff0c;里程计坐标相对于本体就会产生一个偏移&#xff0c;这个偏移随着机器人的运动不断…

ROS底盘控制节点 源码分析

先在机器人端通过launch文件启动底盘控制。 robot:~$ roslaunch base_control base_control.launch ... logging to /home/jym/.ros/log/3e52acda-914a-11ec-beaa-ac8247315e93/roslaunch-robot-8759.log Checking log directory for disk usage. This may take a while. Pres…

ROS + OpenCV

视觉节点测试 先进行一些测试。并记录数据。 圆的是节点&#xff0c;方的是话题。 1.robot_camera.launch robot:~$ roslaunch robot_vision robot_camera.launch ... logging to /home/jym/.ros/log/bff715b6-9201-11ec-b271-ac8247315e93/roslaunch-robot-8830.log Check…

ROS+雷达 运行数据记录

先测试一下雷达&#xff0c;记录数据。方便接下来分析源码。 1.roslaunch robot_navigation lidar.launch robot:~$ roslaunch robot_navigation lidar.launch ... logging to /home/jym/.ros/log/7136849a-92cc-11ec-acff-ac8247315e93/roslaunch-robot-9556.log Checking l…

ROS 找C++算法源码的方法

在gmapping的launch文件中看到&#xff0c;type“slam_gmapping”&#xff0c;这里的slam_gmapping是c编译后的可执行文件。 如果想要修改gmapping算法&#xff0c;就需要找到slam_gmapping的c源码。 但是这是用apt下载的包&#xff0c;是二进制类型的&#xff0c;没有下载出…

ros 雷达 slam 导航 文件分析

ros 雷达 slam 导航 文件分析robot_slam_laser.launchrobot_lidar.launchlidar.launchraplidar.launchkarto.launchgmapping.launchcartographer.launchrobot_navigation.launchmap.yamlmap.pgmamcl_params.yamlmove_base.launchcostmap_common_params.yamllocal_costmap_param…

Apprentissage du français partie 1

Apprentissage du franais partie 1 键盘转换图&#xff1a; 字母&#xff1a;26个 元音字母&#xff1a;a、e、i、o、u、y b浊辅音(声带)-p清辅音 d-t 音符 音符&#xff1a;改变字母发音。 &#xff1a;闭音符 [e] &#xff1a;开音符 /ε/ &#xff1a;长音符 /ε/…

stm32基本定时器

定时器分类 stm32f1系列&#xff0c;8个定时器&#xff0c;基本定时器(TIM6,7)、通用定时器(TIM2,3,4,5)、高级定时器(TIM1,8)。 基本定时器&#xff1a;16位&#xff0c;只能向上计数的定时器&#xff0c;只能定时&#xff0c;没有外部IO 通用定时器&#xff1a;16位&#…

stm32高级定时器 基础知识

stm32高级定时器 高级定时器时基单元&#xff1a; 包含一个16位自动重装载寄存器 ARR 一个16位的计数器CNT&#xff0c;可向上/下计数 一个16位可编程预分频器PSC&#xff0c;预分频器时钟源有多种可选&#xff0c;有内部的时钟、外部时钟。 一个8位的重复计数器 RCR&…

stm32 PWM互补输出

stm32高级定时器例子—stm32 PWM互补输出 定时器初始化结构体 TIM_TimeBaseInitTypeDef 时基结构体&#xff0c;用于定时器基础参数设置&#xff0c;与TIM_TimeBaseInit函数配合使用&#xff0c;完成配置。 typedef struct { TIM_Prescaler /*定时器预分频器设置&…

stm32 输入捕获 测量脉宽

选用通用定时器TIM5的CH1。 PA0接一个按键&#xff0c;默认接GND&#xff0c;当按键按下时&#xff0c;IO口被拉高&#xff0c;此时&#xff0c;可利用定时器的输入捕获功能&#xff0c;测量按键按下的这段高电平的时间。 宏定义方便程序升级、移植&#xff0c;举个例子&#…

stm32 PWM输入捕获

普通的输入捕获&#xff0c;可使用定时器的四个通道&#xff0c;一路捕获占用一个捕获寄存器. PWM输入&#xff0c;只能使用两个通道&#xff0c;通道1和通道2。 一路PWM输入占用两个捕获寄存器&#xff0c;一个捕获周期&#xff0c;一个捕获占空比。 这里&#xff0c;用通用…

直流有刷减速电机结构及其工作原理

寒假无聊拆了个直流有刷减速电机。下面介绍一下它的结构和工作原理 直流电机 直流电机和直流减速电机&#xff1a; 构造上相差的是一个减速齿轮组。 普通的直流电机当空载时&#xff0c;电机的转速由电压决定&#xff0c;直流减速电机的转速由齿轮组和电压决定。 齿轮组作…