FreeRTOS入门--任务

目录

一、什么是任务

二、创建任务---xTaskCreate函数

三、任务的删除

 四、任务优先级

 1.阻塞状态(Blocked)

2.暂停状态(Suspended)

3.就绪状态(Ready)

五、Delay

六、调度算法 


一、什么是任务

在FreeRTOS中,任务就是一个函数,原型如下:

void ATaskFunction( void *pvParameters );

要注意的是:
        这个函数不能返回
        同一个函数,可以用来创建多个任务;换句话说,多个任务可以运行同一个函数

        函数内部,尽量使用局部变量:
        每个任务都有自己的栈
        每个任务运行这个函数时
        任务A的局部变量放在任务A的栈里、任务B的局部变量放在任务B的栈里
        不同任务的局部变量,有自己的副本

        任务示例如下:

void ATaskFunction( void *pvParameters )
{
/* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
int32_t lVariableExample = 0;
/* 任务函数通常实现为一个无限循环 */
for( ;; )
{
/* 任务的代码 */
}
/* 如果程序从循环中退出,一定要使用vTaskDelete删除自己
* NULL表示删除的是自己
*/
vTaskDelete( NULL );
/* 程序不会执行到这里, 如果执行到这里就出错了 */
}

二、创建任务---xTaskCreate函数

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务

参数描述:
pvTaskCode
        函数指针,可以简单地认为任务就是一个C函数。
        它稍微特殊一点:永远不退出,或者退出时要调用"vTaskDelete(NULL)"
pcName
        任务的名字,FreeRTOS内部不使用它,仅仅起调试作用。
        长度为:configMAX_TASK_NAME_LEN
usStackDepth
        每个任务都有自己的栈,这里指定栈大小。
        单位是word,比如传入100,表示栈大小为100 word,也就是400字节。
        最大值为uint16_t的最大值。
        怎么确定栈的大小,并不容易,很多时候是估计。
        精确的办法是看反汇编码。
pvParameters

        调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
uxPriority
        优先级范围:0~(configMAX_PRIORITIES – 1)
        数值越小优先级越低,:更高优先级的、或者后面创建的任务先运行。
        如果传入过大的值,xTaskCreate会把它调整为(configMAX_PRIORITIES – 1)
pxCreatedTask
        用来保存xTaskCreate的输出结果:task handle。
        以后如果想操作这个任务,比如修改它的优先级,就需要这个handle。
        如果不想使用该handle,可以传入NULL。
返回值
        成功:pdPASS;
        失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因只有内存不足)
        注意:返回值是pdFAIL不对。
        pdFAIL是0,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY是-1。

多个任务可以使用同一个函数;

void vTaskFunction( void *pvParameters )
{
    const char *pcTaskText = pvParameters;
    volatile uint32_t ul; /* volatile用来避免被优化掉 */
    /* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        /* 打印任务的信息 */
        printf(pcTaskText);
        /* 延迟一会(比较简单粗暴) */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
        }
    }
}
static const char *pcTextForTask1 = "T1 run\r\n";
static const char *pcTextForTask2 = "T2 run\r\n";
int main( void )
{
    prvSetupHardware();
    xTaskCreate(vTaskFunction, "Task 1", 1000, (void *)pcTextForTask1, 1, NULL);
    xTaskCreate(vTaskFunction, "Task 2", 1000, (void *)pcTextForTask2, 1, NULL);
    /* 启动调度器 */
    vTaskStartScheduler();
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    return 0;
}

三、任务的删除

        删除任务时使用的函数如下:

void vTaskDelete( TaskHandle_t xTaskToDelete );

pvTaskCode
        任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。
        也可传入NULL,这表示删除自己。
        自杀: vTaskDelete(NULL)
        被杀:别的任务执行vTaskDelete(pvTaskCode) ,pvTaskCode是自己的句柄
        杀人:执行vTaskDelete(pvTaskCode) ,pvTaskCode是别的任务的句柄

FreeRTOS一天一个小知识之任务延时函数vTaskDelay-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/simplemethane/article/details/116998825以下是这篇文章中谈到的延迟的内容:

        Dealy的延时,是通过CPU做循环的方式来延时,CPU在延时中是做不了其他东西的,大大浪费了CPU的效率!而且非常危险!

        所以大家在裸机中如果要需要很长时间延时的话,建议用定时器来延时。

void vTaskDelay( const TickType_t xTicksToDelay )
    {
        BaseType_t xAlreadyYielded = pdFALSE;

        /* A delay time of zero just forces a reschedule. */
        if( xTicksToDelay > ( TickType_t ) 0U )
        {
            configASSERT( uxSchedulerSuspended == 0 );
            vTaskSuspendAll();
            {
                traceTASK_DELAY();

                /* A task that is removed from the event list while the
                 * scheduler is suspended will not get placed in the ready
                 * list or removed from the blocked list until the scheduler
                 * is resumed.
                 *
                 * This task cannot be in an event list as it is the currently
                 * executing task. */
                prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
            }
            xAlreadyYielded = xTaskResumeAll();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* Force a reschedule if xTaskResumeAll has not already done so, we may
         * have put ourselves to sleep. */
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

FreeRTOS这个任务执行是这样的。首先TASK1创建,然后在创建TASK2

        TASK先执行, 执行到GPIO_SetBits(GPIOC,GPIO_Pin_2);    下一句vTaskDelay(500);   延时500ms,其实就是任务挂起500ms,CPU此时不会执行TASK的任务,去执行处于就绪态的TASK2,   当TASK2的GPIO_ResetBits(GPIOC,GPIO_Pin_3);    执行好了之后执行下一条 vTaskDelay(200);此时TASK1延时500ms,TASK延时200ms。

        这时候FreeRTOS是没有执行处于就绪态的任务的,只有执行空闲任务 。此时由于TASK2是延时200ms,比TASK2延时的500ms要快,所以TASK2比TASK1更早进入就绪态,此时CPU执行  GPIO_SetBits(GPIOC,GPIO_Pin_3);    这一语句,执行好了之后TASK2又延时800ms,进入挂起态。当TASK1延时500ms到,TASK1进入就绪态,

        执行GPIO_ResetBits(GPIOC,GPIO_Pin_2);     i++;语句,执行完之后,TASK1又进入500ms的延时,进入挂起态~

任务堆栈

        任务堆栈用来保存任务现场(CPU寄存器值),创建任务的时候需要指定任务堆栈,任务堆栈的变量类型为StackType_t,再次运行任务时会从上次中断的地方开始运行

        所以在FreeRTOS中的延时函数,只是任务挂起和任务恢复而已

FreeRTOS入门--任务_第1张图片

FreeRTOS入门--任务_第2张图片

//任务一
void vTask1( void *pvParameters )
{
const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );
BaseType_t ret;
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务的信息 */
printf("Task1 is running\r\n");
ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
if (ret != pdPASS)
printf("Create Task2 Failed\r\n");
// 如果不休眠的话, Idle任务无法得到执行
// Idel任务会清理任务2使用的内存
// 如果不休眠则Idle任务无法执行, 最后内存耗尽
vTaskDelay( xDelay100ms );
}

//任务二
void vTask2( void *pvParameters )
{
/* 打印任务的信息 */
printf("Task2 is running and about to delete itself\r\n");
// 可以直接传入参数NULL, 这里只是为了演示函数用法
vTaskDelete(xTask2Handle);
}

//main函数
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}

FreeRTOS入门--任务_第3张图片

main函数中创建任务1,优先级为1。任务1运行时,它创建任务2,任务2的优先级是2。
任务2的优先级最高,它马上执行。
任务2打印一句话后,就删除了自己。
任务2被删除后,任务1的优先级最高,轮到任务1继续运行,它调用vTaskDelay() 进入Block状
态
任务1 Block期间,轮到Idle任务执行:它释放任务2的内存(TCB、栈)
时间到后,任务1变为最高优先级的任务继续执行。
如此循环。

        在任务1的函数中,如果不调用vTaskDelay,则Idle任务用于没有机会执行,它就无法释放创建任务2是分配的内存。而任务1在不断地创建任务,不断地消耗内存,最终内存耗尽再也无法创建新的任务。

FreeRTOS入门--任务_第4张图片

 四、任务优先级

        高优先级的任务先运行。
        优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。

        FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行
        对于相同优先级的、可运行的任务,轮流执行
举例子:
        厨房着火了,当然优先灭火
        喂饭、回复信息同样重要,轮流做

对于同优先级的任务,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。
        "一会"怎么定义?
        人有心跳,心跳间隔基本恒定。
        FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。

假设t1、t2、t3发生时钟中断
        两次中断之间的时间被称为时间片(time slice、tick period)
        时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就是10ms

FreeRTOS入门--任务_第5张图片

相同优先级的任务怎么切换呢?请看下图:
        任务2从t1执行到t2
        在t2发生tick中断,进入tick中断处理函数:
        选择下一个要运行的任务
                执行完中断处理函数后,切换到新的任务:任务1
                任务1从t2执行到t3
        从图中可以看出,任务运行的时间并不是严格从t1,t2,t3哪里开始

FreeRTOS入门--任务_第6张图片

        在FreeRTOS中,系统时钟节拍的特点就是周期性中断,既然要产生中断那就需要定时器,所以在这里就是使用了一个24位的定时器,采用向下计数的方式,然后可以产生周期性的中断。
            系统在使用的时候,一般是在FreeRTOSConfig.h里面进行配置
                #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
            这是一个常用的配置,系统的节拍频率设置为1000,也就是说系统的节拍周期为1ms,这也是最为典型的一种设置。

vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms
// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms

        注意,基于Tick实现的延时并不精确,比如vTaskDelay(2) 的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。

FreeRTOS入门--任务_第7张图片

        使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。

        将毫秒数换算成了tick数
          #define pdMS_TO_TICKS( xTimeInMs )    ( ( TickType_t ) ( ( ( TickType_t ) ( xTimeInMs ) * ( TickType_t ) configTICK_RATE_HZ ) / ( TickType_t ) 1000U ) )

        SysTick 定时器被捆绑在 NVIC 中,用于产生 SysTick 异常(异常号: 15), 滴答定时器是一个 24 位的递减计数器,支持中断。 
            使用比较简单, 专门用于给操作系统提供时钟节拍。
            FreeRTOS 的系统时钟节拍可以在配置文件 FreeRTOSConfig.h 里面设置:
                #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
            如上所示的宏定义配置表示系统时钟节拍是 1KHz,即 1ms。

void vTask3( void *pvParameters )
{
const TickType_t xDelay3000ms = pdMS_TO_TICKS( 3000UL );
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务的信息 */
printf("T3\r\n");
// 如果不休眠的话, 其他任务无法得到执行
vTaskDelay( xDelay3000ms );
}
}

修改优先级:

使用uxTaskPriorityGet来获得任务的优先级:

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。
使用vTaskPrioritySet 来设置任务的优先级:

void vTaskPrioritySet( TaskHandle_t xTask,
UBaseType_t uxNewPriority );

使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;
参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。

五、任务状态

void vTask1( void *pvParameters )
{
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务的信息 */
printf("T1\r\n");
}
}
void vTask2( void *pvParameters )
{
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务的信息 */
printf("T2\r\n");
}
}
void vTask3( void *pvParameters )
{
const TickType_t xDelay3000ms = pdMS_TO_TICKS( 3000UL );
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务的信息 */
printf("T3\r\n");
// 如果不休眠的话, 其他任务无法得到执行
vTaskDelay( xDelay3000ms );
}
}
/***********************************************/
//main函数:
int main()
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}

        如果把任务3中的vTaskDelay调用注释掉,那么任务1、任务2根本没有执行的机会,任务1、任务2被"饿死"了(starve)。

FreeRTOS入门--任务_第8张图片

 1.阻塞状态(Blocked)

在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:
    任务要等待某个事件,事件发生后它才能运行
    在等待事件过程中,它不消耗CPU资源
    在等待事件的过程中,这个任务就处于阻塞状态(Blocked)
在阻塞状态的任务,它可以等待两种类型的事件:
时间相关的事件
        可以等待一段时间:我等2分钟
        也可以一直等待,直到某个绝对时间:我等到下午3点
同步事件:这事件由别的任务,或者是中断程序产生
        例子1:任务A等待任务B给它发送数据
        例子2:任务A等待用户按下按键

在等待一个同步事件时,可以加上超时时间。

        比如等待队里数据,超时时间设为10ms:
        10ms之内有数据到来:成功返回
        10ms到了,还是没有数据:超时返回

2.暂停状态(Suspended)

在日常生活的例子中,母亲正在电脑前跟同事沟通,母亲可以暂停:
        好烦啊,我暂停一会
        领导说:你暂停一下
FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

        参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。
        要退出暂停状态,只能由别人来操作:
                别的任务调用:vTaskResume
                中断程序调用:xTaskResumeFromISR
        实际开发中,暂停状态用得不多。

3.就绪状态(Ready)

        这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。

五、Delay

有两个Delay函数:
        vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态
        vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。

void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给
Tick */
/* pxPreviousWakeTime: 上一次被唤醒的时间
* xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
* 单位都是Tick Count
*/
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );

        使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick中断
        使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间至少是n个Tick中断退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会,所以可以使用xTaskDelayUntil来让任务周期性地运行

FreeRTOS入门--任务_第9张图片

int main( void )
{
prvSetupHardware();
/* Task1的优先级更高, Task1先执行 */
xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
void vTask1( void *pvParameters )
{
const TickType_t xDelay50ms = pdMS_TO_TICKS( 50UL );
TickType_t xLastWakeTime;
int i;
/* 获得当前的Tick Count */
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
flag = 1;
/* 故意加入多个循环,让程序运行时间长一点 */
for (i = 0; i <5; i++)
printf( "Task 1 is running\r\n" );
##if 1
vTaskDelay(xDelay50ms);
##else
vTaskDelayUntil(&xLastWakeTime, xDelay50ms);
##endif
}
}
void vTask2( void *pvParameters )
{
for( ;; )
{
flag = 0;
printf( "Task 2 is running\r\n" );
}
}

使用Keil的逻辑分析观察flag变量的bit波形,如下:
flag为1时表示Task1在运行,flag为0时表示Task2在运行,也就是Task1处于阻塞状态
        vTaskDelay:指定的是阻塞的时间
        vTaskDelayUntil:指定的是任务执行的间隔、周期

FreeRTOS入门--任务_第10张图片

六、调度算法 

static volatile int flagIdleTaskrun = 0; // 空闲任务运行时flagIdleTaskrun=1
static volatile int flagTask1run = 0; // 任务1运行时flagTask1run=1
static volatile int flagTask2run = 0; // 任务2运行时flagTask2run=1
static volatile int flagTask3run = 0; // 任务3运行时flagTask3run=1
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
void vTask1( void *pvParameters )
{
/* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        flagIdleTaskrun = 0;
        flagTask1run = 1;
        flagTask2run = 0;
        flagTask3run = 0;
        /* 打印任务的信息 */
        printf("T1\r\n");
    }
}
void vTask2( void *pvParameters )
{
/* 任务函数的主体一般都是无限循环 */
    for( ;; )
    {
        flagIdleTaskrun = 0;
        flagTask1run = 0;
        flagTask2run = 1;
        flagTask3run = 0;
        /* 打印任务的信息 */
        printf("T2\r\n");
    }
}
void vTask3( void *pvParameters )
{
        const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
        /* 任务函数的主体一般都是无限循环 */
        for( ;; )
        {
            flagIdleTaskrun = 0;
            flagTask1run = 0;
            flagTask2run = 0;
            flagTask3run = 1;
            /* 打印任务的信息 */
            printf("T3\r\n");
            // 如果不休眠的话, 其他任务无法得到执行
            vTaskDelay( xDelay5ms );
        }    
}

        提供了一个空闲任务的钩子函数:      

void vApplicationIdleHook(void)
{
    flagIdleTaskrun = 1;
    flagTask1run = 0;
    flagTask2run = 0;
    flagTask3run = 0;
    /* 故意加入打印让flagIdleTaskrun变为1的时间维持长一点 */
    printf("Id\r\n");
}

          抢占时:高优先级任务就绪时,就可以马上执行
        不抢占时:优先级失去意义了,既然不能抢占就只能协商了,图中任务1一直在运行(一点都没有协商精神),其他任务都无法执行。即使任务3的vTaskDelay 已经超时、即使它的优先级更高,都没办法执行。

FreeRTOS入门--任务_第11张图片FreeRTOS入门--任务_第12张图片

你可能感兴趣的:(c#,开发语言)