【FreeRTOS】完善任务的实现,延时列表,空闲任务

86 阅读4分钟

什么是临界段

image.png

image.png

SysTick系统定时器介绍

image.png

image.png

CTRL寄存器

image.png

LOAD寄存器

image.png

VAL寄存器

image.png

CALIB寄存器(还不知道干啥用)

image.png

异常优先级字段

image.png

image.png

初始化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 ); 
}

这是它的地址

image.png

查手册得到地址。

image.png

看CTRL寄存器介绍,重点是Bit0,Bit1,Bit2。

  Bit0:0 关闭定时器,1 打开定时器
  Bit1: 0 关闭溢出异常中断, 1 打开溢出中断异常
  Bit2: 0 选择时钟源AHB/8   1 选择时钟源AHB

image.png

image.png

image.png

image.png 通过或操作,把CTRL寄存器的Bit0,Bit1,Bit2都置1

image.png

设置LOAD的值,1s中断100次,10ms一次中断。

image.png

image.png

SysTick中断服务函数

image.png

void xPortSysTickHandler( void )
{
	/* 关中断 */
    vPortRaiseBASEPRI();
    
    /* 更新系统时基 */
    xTaskIncrementTick();

	/* 开中断 */
    vPortClearBASEPRIFromISR();
}

更新系统时基

void xTaskIncrementTick( void )
{
    TCB_t *pxTCB = NULL;
    BaseType_t i = 0;
    
    /* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;

    
    /* 扫描就绪列表中所有线程的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();
}

延时列表的实现

TCB控制块新增了一个成员变量 xTicksToDelay用来记录延时。

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

任务1新增的代码,不用delay(xxx)延时,改成vTaskDelay(x)用SysTick的方式。

Task1_entry()
/* 任务1 */
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;
    
    /* 获取当前任务的TCB */
    pxTCB = pxCurrentTCB;
    
    /* 设置延时时间 */
    pxTCB->xTicksToDelay = xTicksToDelay;
    
    /* 任务切换 */
    taskYIELD();
}
portYIELD()
#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 );											\
}

每一次SysTick计数中断一次,就更新一次延时值。

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;

    
    /* 扫描就绪列表中所有线程的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();
}

xTickCount每次更新都是在这里更新

/* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;

把就绪列表所有任务有延时的就-1,没延时不管。遍历完后执行触发PendSV切换任务。

    /* 扫描就绪列表中所有线程的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 --;
		}
	}

如果当前的任务是空闲任务则判定任务1和任务2是否延时到期并进行对应的切换,如果当前是任务1或任务2,则判定下一个任务的延时是否到期以及当前任务是否有延时,并进行对应的切换操作。

void vTaskSwitchContext( void )
{
	/* 如果当前线程是空闲线程,那么就去尝试执行线程1或者线程2,
       看看他们的延时时间是否结束,如果线程的延时时间均没有到期,
       那就返回继续执行空闲线程 */
	if( pxCurrentTCB == &IdleTaskTCB )
	{
		if(Task1TCB.xTicksToDelay == 0)
		{            
                    pxCurrentTCB =&Task1TCB;
		}
		else if(Task2TCB.xTicksToDelay == 0)
		{
                    pxCurrentTCB =&Task2TCB;
		}
		else
		{
                    return;		/* 线程延时均没有到期则返回,继续执行空闲线程 */
		} 
	}
	else
	{
		/*如果当前线程是线程1或者线程2的话,检查下另外一个线程,如果另外的线程不在延时中,就切换到该线程
        否则,判断下当前线程是否应该进入延时状态,如果是的话,就切换到空闲线程。否则就不进行任何切换 */
		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;		/* 返回,不进行切换,因为两个线程都处于延时中 */
			}
		}
	}
}

空闲任务

/* 获取空闲任务的内存 */
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
TCB_t IdleTaskTCB;
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, 
                                    StackType_t **ppxIdleTaskStackBuffer, 
                                    uint32_t *pulIdleTaskStackSize )
{
                *ppxIdleTaskTCBBuffer=&IdleTaskTCB;
                *ppxIdleTaskStackBuffer=IdleTaskStack; 
                *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

在调度函数里面启用。

/*======================================创建空闲任务start==============================================*/     
    TCB_t *pxIdleTaskTCBBuffer = NULL;               /* 用于指向空闲任务控制块 */
    StackType_t *pxIdleTaskStackBuffer = NULL;       /* 用于空闲任务栈起始地址 */
    uint32_t ulIdleTaskStackSize;
    
    /* 获取空闲任务的内存:任务栈和任务TCB */
    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================================================*/