iis服务器助手广告广告
返回顶部
首页 > 资讯 > 操作系统 >freertos实时操作系统空闲任务阻塞延时示例解析
  • 184
分享到

freertos实时操作系统空闲任务阻塞延时示例解析

2024-04-02 19:04:59 184人浏览 八月长安
摘要

目录前言空闲任务阻塞延时SysTick实验现象前言 阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态。 rtos中的延时叫阻塞延时,即任务需要延时的时候,会放弃CPU的

前言

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

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 )
{
     

    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 ) );
    

                                         
    
    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();
#else
		flag1 = 1;
        vTaskDelay( 2 );		
		flag1 = 0;
        vTaskDelay( 2 );
#endif        
	}
}

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


void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;
    
    pxTCB = pxCurrentTCB;
    
    pxTCB->xTicksToDelay = xTicksToDelay;
    
    taskYIELD();
}

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


#define portNVIC_INT_CTRL_REG		( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT		( 1UL << 28UL )
#define portSY_FULL_READ_WRITE		( 15 )

#define portYIELD()																\
{																				\
									\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\																				\
							\
	__dsb( portSY_FULL_READ_WRITE );											\
	__isb( portSY_FULL_READ_WRITE );											\
}

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

__asm void xPortPendSVHandler( void )
{
//	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;
	PRESERVE8
    
    
	mrs r0, psp
	isb
	ldr	r3, =pxCurrentTCB		
	ldr	r2, [r3]                

	stmdb r0!, {r4-r11}			
	str r0, [r2]                				
                               	stmdb sp!, {r3, r14}        
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext        
	mov r0, #0                  
	msr basepri, r0
	ldmia sp!, {r3, r14}        
	ldr r1, [r3]
	ldr r0, [r1] 				
	ldmia r0!, {r4-r11}			
	msr psp, r0
	isb
	bx 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 )
{
    
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
    
    vPortSetupTimerInterrupt();
	
	prvStartFirstTask();
	
	return 0;
}
//system_ARMCM4.c文件
#define  XTAL            (50000000UL)     
#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

#define portNVIC_SYSTICK_CTRL_REG			( * ( ( volatile uint32_t * ) 0xe000e010 ) )

#define portNVIC_SYSTICK_LOAD_REG			( * ( ( volatile uint32_t * ) 0xe000e014 ) )

#ifndef configSYSTICK_CLOCK_HZ
	#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ//configSYSTICK_CLOCK_HZ=configCPU_CLOCK_HZ
	
	#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;
    
    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;
    
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;//把xTickCount加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,跑完了才进行任务切换,如下图所示,一个任务高低电平全搞完,才切到下一个任务。

以上就是freertos实时操作系统空闲任务阻塞延时示例解析的详细内容,更多关于freertos空闲任务阻塞延时的资料请关注编程网其它相关文章!

--结束END--

本文标题: freertos实时操作系统空闲任务阻塞延时示例解析

本文链接: https://www.lsjlt.com/news/145031.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • freertos实时操作系统空闲任务阻塞延时示例解析
    目录前言空闲任务阻塞延时SysTick实验现象前言 阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态。 rtos中的延时叫阻塞延时,即任务需要延时的时候,会放弃CPU的...
    99+
    2022-11-13
  • FreeRTOS实时操作系统空闲任务的阻塞延时实现
    目录什么是阻塞延时、为什么需要空闲任务空闲任务的实现阻塞延时的实现xTicksToDelay 递减SysTick初始化仿真什么是阻塞延时、为什么需要空闲任务 RTOS中的延时叫阻塞延...
    99+
    2022-11-13
  • FreeRTOS实时操作系统空闲任务的阻塞延时怎么实现
    这篇文章主要介绍“FreeRTOS实时操作系统空闲任务的阻塞延时怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“FreeRTOS实时操作系统空闲任务的阻塞延时怎么实现”文章能帮助大家解决问题。...
    99+
    2023-06-29
  • FreeRTOS实时操作系统的内核控制示例解析
    目录前言1.强制上下文切换宏2.进入临界区宏3.退出临界区宏4.禁止可屏蔽中断宏5.使能可屏蔽中断宏6.启动调度器6.1函数描述7.停止调度器7.1函数描述8.挂起调度器8.1函数描...
    99+
    2022-11-13
  • FreeRTOS实时操作系统支持时间片示例详解
    目录什么是时间片时间片实现关键taskSELECT_HIGHEST_PRIORITY_TASK()taskRESET_READY_PRIORITY()什么是时间片 时间片就是同一个优...
    99+
    2022-11-13
  • FreeRTOS实时操作系统的任务概要讲解
    目录1. 任务和协程(Co-routines)1.1任务的特性1.2任务概要2. 任务状态3.任务优先级4.实现一个任务5.空闲任务和空闲任务钩子(idle task和Idle Ta...
    99+
    2022-11-13
  • FreeRTOS实时操作系统的任务应用函数详解
    目录1.获取任务系统状态1.1函数描述1.2参数描述1.3返回值1.4用法举例2.获取当前任务句柄2.1函数描述2.2返回值3.获取空闲任务句柄3.1函数描述3.2返回值4.获取任务...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作