上一篇博客我写到了如何创建博客,这篇文章我们来实现阻塞延时。发布这篇博客的同时,也上传了对应的资源。欢迎下载学习!
空闲任务是不允许阻塞的。
如何实现阻塞延时?
1.在freeRTOS.h的typedef struct tskTaskControlBlock()
中添加xTicksToDelay
函数。如下:
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; // 栈顶
ListITEM_t xStateListItem; // 任务节点
StackType_t *pxStack; // 任务栈起始地址
char pcTaskName[configMAX_TASK_NAME_LEN]; // 任务名称,字符串形式
TickType_t xTicksToDelay; // 用于阻塞延时的变量,单位为SysTick的一个周期
}tskTCB;
2.修改void vTaskSwitchContext(void)()
(这是任务选择函数)函数。因为没有空闲任务时,只有任务一,任务二,只能在两个任务中来回切换,但现在有了空闲任务,所以也要把空闲任务计算在内。在task.c中声明(因为在main.c中已经定义了)extern TCB_t xTicksToDelay;
,再修改上面的那个函数。
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; // 返回,不进行切换,因为两个线程都在延时中。
}
}
}
}
在task.c中实现延时函数。
当xTicksToDelay
变为零时,表示延时结束。而它是以Systick
中断为周期。(操作系统最小的时间周期就是Systick
的时间周期。)
// 任务延时函数
void vTaskDelay( const TickType_t xTicksToDelay )
{
TCB_t *pxTCB = NULL;
// 获取当前任务的TCB
pxTCB = pxCurrentTCB;
// 设置延时时间
pxTCB -> xTicksToDelay = xTicksToDelay;
// 任务切换
taskYIELD();
}
这些都是实现了任务的切换,但是如何实现任务的定时计数呢?
3.实现SysTick中断服务函数。
在port.c中实现。
static portFORCE_INLINE void vPortClearBASEPRIFromISR( void )
{
__asm
{
msr basepri, #0
}
}
// Systick中断服务函数
void xPortSysTickHandler( void )
{
// 关中断
vPortRaiseBASEPRI();
// 更新中断系统时基
xTaskIncrementTick();
// 开中断
vPortClearBASEPRIFromISR();
}
再在port.c中实现xTaskIncrementTick()
函数。
首先定义:
static volatile TickType_t xTickCount = (TickType_t)0U;
在task.h中声明void xTaskIncrementTick();
在port.c中写函数xTaskIncrementTick()
:
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();
}
在freeRTOSConfig.h中添加:
#define configCPU_CLOCK_HZ ((unsigned long) 25000000) // 系统时钟大小
#define configTICK_RATE_HZ ((TickType_t) 100) // SysTick每秒中断多少次 这里对应的是10ms
在port.c中添加:
// SysTick配置寄存器
#define portNVIC_SYSTICK_CTRL_REG (*((volatile uint32_t *) 0xe000e010))
#define portNVIC_SYSTICK_LOAD_REG (*((volatile uint32_t *) 0xe000e014))
在port.c中的调度器函数中初始化: vPortSetupTimerInterrupt()
。
调度器函数中添加:
// 初始化SysTick
vPortSetupTimerInterrupt();
记得声明下面的函数呀!
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);
}
#define portNVIC_SYSTICK_INT_BIT (1UL << 1UL)
#define portNVIC_SYSTICK_ENABLE_BIT (1UL << 0UL)
main.c中的软件延时函数也要换为系统延时函数:
// 定义一个任务函数
void Task1_Entry(void *p_arg)
{
for(;;)
{
flag1 = 1;
vTaskDelay(2);
flag1 = 0;
vTaskDelay(2);
}
}
// 定义一个任务函数
void Task2_Entry(void *p_arg)
{
for(;;)
{
flag2 = 1;
vTaskDelay(2);
flag2 = 0;
vTaskDelay(2);
}
}
int main(void)
{
prvInitialiseTaskLists();
// 创建两个任务
Task1_Handle = xTaskCreateStatic(Task1_Entry,
"Task1_Entry",
TASK1_STACK_SIZE,
NULL,
Task1Stack,
&Task1TCB );
/* 将任务添加到就绪列表 */
vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
Task2_Handle = xTaskCreateStatic(Task2_Entry,
"Task2_Entry",
TASK2_STACK_SIZE,
NULL,
Task2Stack,
&Task2TCB );
/* 将任务添加到就绪列表 */
vListInsertEnd( &( pxReadyTasksLists[2] ), &( ((TCB_t *)(&Task2TCB))->xStateListItem ) );
// 核心函数是vListInsert,将任务控制块的列表项插入到列表中
// vListInsert(&pxReadyTasksLists[2],&Task1TCB.xStateListItem);
// 手动切换一个任务,启动一个调度器
vTaskStartScheduler();
for(;;)
{
}
}
// 定义一个任务函数
void Task1_Entry(void *p_arg)
{
for( ; ; )
{
flag1 = 1;
vTaskDelay(2);
flag1 = 0;
vTaskDelay(2);
}
}
// 定义一个任务函数
void Task2_Entry(void *p_arg)
{
for( ; ; )
{
flag2 = 1;
vTaskDelay(2);
flag2 = 0;
vTaskDelay(2); // 延时20ms
}
}
// 获取空闲任务的内存
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;
}
如果有错误,欢迎指明!
欢迎交流学习!欢迎批评指正!
完!