freertos知识总结

文章详细介绍了如何在STM32平台上进行FreeRTOS内核的移植,包括下载内核源码、添加到工程、配置中断服务函数,以及任务创建、任务调度、时间片设置、任务优先级管理等方面的内容。同时,展示了如何使用串口进行调试,并通过任务切换时间验证了调度器的正确性。此外,还提到了事件组的概念和使用方法,作为同步与互斥的一种机制。
摘要由CSDN通过智能技术生成
  1. 内核移植

首先是下载内核源码

借用韦东山老师的一张图片对源码的介绍

有了内核源码之后就将源码添加到一个裸机工程下面,FreeRTOS 帮我们实现了 SysTick 的启动的配置:在 port.c 文件 中已经实现 vPortSetupTimerInterrupt()函数,并且 FreeRTOS 通用的 SysTick 中断服务函数 也实现了:在 port.c 文件中已经实现 xPortSysTickHandler()函数,所以移植的时候只需要我 们在stm32f10x_it.c 中就需要我们注释掉 PendSV_Handler()与 SVC_Handler()这两个函数 。最后选好头文件路径即可。

添加完成后,运行一下,没有错误即可。

任务调度

任务是一个无限循环且不带返回值的 C 函数。

创建任务函数

参数说明

任务函数样式

void ATaskFunction( void *pvParameters )
{
    /* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
    int32_t lVariableExample = 0;
    
    /* 任务函数通常实现为一个无限循环 */
    for( ;; )
    {
        /* 任务的代码 */
    }

    /* 如果程序从循环中退出,一定要使用vTaskDelete删除自己
     * NULL表示删除的是自己
     */
    vTaskDelete( NULL );
    
    /* 程序不会执行到这里, 如果执行到这里就出错了 */
}

移植内核的更改设置

将中断函数里面的中断服务函数, PendSV_Handler()与 SVC_Handler()函数,系统时钟函数Systick_handler屏蔽。

在FreeRTOSConfig.h配置文件中,更换为freertos的中断服务函数

#define xPortPendSVHandler  PendSV_Handler
#define vPortSVCHandler      SVC_Handler
#define xPortSysTickHandler  SysTick_Handler

随便找个串口的文件,拷贝到当前工程中,用于调试任务切换打印信息

void task1(void *pvParameters)
{
    
    while(1)
    {
        a=1;
        int u=0;
        USART_SendString(USART1,temp1);
        for(u=0;u<1000;u++)
        {
        }};
}

void task2(void *pvParameters)
{
    
    while(1)
    {
        a=0;
        int u=0;
        USART_SendString(USART1,temp2);
        for(u=0;u<1000;u++)
        {
        }
        
    };
}

            
int main(void)
{    
    
    Usart1_Init();
    xTaskCreate(task1, "Task 1", 1000, NULL, 1, NULL);//创建任务
    xTaskCreate(task2, "Task 2", 1000, NULL, 1, NULL);
    vTaskStartScheduler();//开启任务调度器
    return 0;
}

结果如下

到现在,我们以及基本完成内核的移植工作,用为任务已经在调度切换了,但很明显,因为这个串口中断的问题,我们不停的将字符串放到串口的移位寄存器中,导致任务的执行时间不均,注释掉串口,光用变量来观察任务的执行时间

很明显,任务的调度时间分配均匀,很合理。

删除任务
void vTaskDelete( TaskHandle_t xTaskToDelete );
参数是任务的句柄,NULL表示删除自己
tick
  1. 两次中断之间的时间称为时间片

  1. 时间片的长度由configTICK_RATE_HZ 这个宏决定;

任务优先级的设置
  1. 获取优先级函数:

#if ( INCLUDE_uxTaskPriorityGet == 1 )

    UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
    {
    TCB_t *pxTCB;
    UBaseType_t uxReturn;

        taskENTER_CRITICAL();
        {
            /* If null is passed in here then it is the priority of the that
            called uxTaskPriorityGet() that is being queried. */
            pxTCB = prvGetTCBFromHandle( xTask );
            uxReturn = pxTCB->uxPriority;
        }
        taskEXIT_CRITICAL();

        return uxReturn;
    }

#endif /* INCLUDE_uxTaskPriorityGet */

在内核中INCLUDE_uxTaskPriorityGet已经被设置为1了,函数的参数是任务的句柄,NULL表示自己。函数的返回值是任务的优先级

2.设置任务优先级函数

void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority ) PRIVILEGED_FUNCTION;
参数1:任务的句柄,NULL表示自己
参数2:设置的优先级
无返回值
任务的状态
  1. 运行态:正在运行的任务,

  1. 就绪态:即将运行的任务,挂在就绪链表里的函数

  1. 阻塞态:被迫停止:等待某个事件发生或者延时,不占用cpu资源。包含任务被挂起、任务被延时、任务 正在等待信号量、读写队列或者等待读写事件等。

  1. 挂起态:主动停止:占用cpu资源

延时函数
void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给Tick */

BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
                            const TickType_t xTimeIncrement );
/* pxPreviousWakeTime: 上一次被唤醒的时间
 * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
 * 单位都是Tick Count
 */
空闲任务

一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。

空闲任务优先级为0:它不能阻碍用户任务运行

空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

任务调度

通过配置宏来控制调度方法,支持的话就将对应的宏配置为1

  1. 支持抢占吗:configUSE_PREEMPTION

支持抢占时,高优先级任务在就绪链表中就绪时,立马就可以执行;不支持抢占时,只能等当前运行的任务主动释放cpu控制权,其它任务不能打断当前运行的任务,阻塞时间到了,高优先级任务准备好了都不可以打断。

  1. 在支持抢占时,是否支持任务的轮转执行:configUSE_TIME_SLICING

支持轮转时,同级任务轮转获得cpu的使用权

  1. 在支持抢占和支持任务的轮转执行时,是否支持空闲任务的让位:configIDLE_SHOULD_YIELD

支持时,空闲任务运行时间很短,就算是遇到同级任务也会让位;不支持时,就和普通任务一样。

同步与互斥
.任务通知
.事件组:

事件组可以简单的认为是一个整数,其中的高8位留给内核使用,数的长度由宏configUSE_16_BIT_TICKS决定,如果该宏设置为1,则表明事件组的长度为16位,设置为0,则表明事件组的长度为32,内核中初始设置为0,就是留了24位的用于事件组。

#define configUSE_16_BIT_TICKS        0

作用:

事件组用它的每一位来表明一件事件是否发生,事件发生这对应的一位就被设置为1,一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位,可以等待某一位或多位置1.

使用:

一.事件组的创建:

//动态创建
//无参数
//返回值是事件组的句柄
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

    EventGroupHandle_t xEventGroupCreate( void )
    {
    EventGroup_t *pxEventBits;//创建一个EventGroup_t类型结构体指针

        /* Allocate the event group. */
        pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) );/*分配结构的内存*/

        if( pxEventBits != NULL )
        {
            pxEventBits->uxEventBits = 0;//将事件组设置为0
            vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );

            #if( configSUPPORT_STATIC_ALLOCATION == 1 )
            {
                /* Both static and dynamic allocation can be used, so note this
                event group was allocated statically in case the event group is
                later deleted. */
                pxEventBits->ucStaticallyAllocated = pdFALSE;
            }
            #endif /* configSUPPORT_STATIC_ALLOCATION */

            traceEVENT_GROUP_CREATE( pxEventBits );
        }
        else
        {
            traceEVENT_GROUP_CREATE_FAILED();
        }

        return ( EventGroupHandle_t ) pxEventBits;//返回指针
    }

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

//静态创建
/* 创建一个事件组,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
#if( configSUPPORT_STATIC_ALLOCATION == 1 )

    EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )
    {
    EventGroup_t *pxEventBits;

        /* A StaticEventGroup_t object must be provided. */
        configASSERT( pxEventGroupBuffer );

        /* The user has provided a statically allocated event group - use it. */
        pxEventBits = ( EventGroup_t * ) pxEventGroupBuffer; /*lint !e740 EventGroup_t and StaticEventGroup_t are guaranteed to have the same size and alignment requirement - checked by configASSERT(). */

        if( pxEventBits != NULL )
        {
            pxEventBits->uxEventBits = 0;
            vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );

            #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
            {
                /* Both static and dynamic allocation can be used, so note that
                this event group was created statically in case the event group
                is later deleted. */
                pxEventBits->ucStaticallyAllocated = pdTRUE;
            }
            #endif /* configSUPPORT_DYNAMIC_ALLOCATION */

            traceEVENT_GROUP_CREATE( pxEventBits );
        }
        else
        {
            traceEVENT_GROUP_CREATE_FAILED();
        }

        return ( EventGroupHandle_t ) pxEventBits;
    }

#endif /* configSUPPORT_STATIC_ALLOCATION */

删除事件

//传入要删除任务的句柄
void vEventGroupDelete(EventGroupHandle_t xEventGroup)

二.创建完事件后,就是设置位

//xEventGroup: 任务句柄,表示哪个事件组
/*uxBitsToSet:一个无符号32位常量,类型在源码定义如下:typedef TickType_t EventBits_t;
typedef uint32_t TickType_t;*/
//返回值:返回原来的事件值(无意义)
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )

//在中断里的使用
//xEventGroup:任务句柄,表示哪个事件组
//uxBitsToSet:一个无符号32位常量,如上
//pxHigherPriorityTaskWoken:有没有导致更高优先级的任务进入就绪态? pdTRUE:有, pdFALSE:没有
//返回值:pdPASS:成功, pdFALSE:失败
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
                                      const EventBits_t uxBitsToSet,
                                      BaseType_t *pxHigherPriorityTaskWoken )
三.能设置事件组的位后,就是具体使用事件组,等待某些事件的产生
//xEventGroup:事件组句柄
//uxBitsToWaitFor:等待的位,例如:等待第一位就设置0x1,等待1,2位就设置0x3
//xClearOnExit:清除的位,pdTRUE: 清除uxBitsToWaitFor指定的位,pdFALSE: 不清除
//xWaitForAllBits:测试方法,pdTRUE: 等待的位,全部为1;pdFALSE: 等待的位,某一个为1即可
/*xTicksToWait:事件未发生,任务阻塞的时间。事件发生,返回返回的是"非阻塞条件成立"时的事件值;超时,返回超时时刻的事件值,它可设置为portMAX_DELAY:一定等到成功才返回;可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count*/
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, 
                                    const EventBits_t uxBitsToWaitFor,
                                     const BaseType_t xClearOnExit,
                                     const BaseType_t xWaitForAllBits, 
                                    TickType_t xTicksToWait )
事件组实验
#include "FreeRTOS.h"
#include "task.h"
#include "list.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Led.h"
#include "Usart.h"
#include "event_groups.h"

EventGroupHandle_t event_handle;//事件组句柄


volatile int a=0;
void task1(void *pvParameters)
{
    
    while(1)
    {
        
        //三个tick后,将事件组0位设置为1
        vTaskDelay( pdMS_TO_TICKS( 50UL ));
        
        xEventGroupSetBits(event_handle, 0x01);
    }
        

}

void task2(void *pvParameters)
{
    
    while(1)
    {a=1;
        //等待事件组的第0位置1
        xEventGroupWaitBits( event_handle,0x1,pdTRUE,pdTRUE, portMAX_DELAY );
        a=0;
        vTaskDelay( pdMS_TO_TICKS( 20UL ));
        
        
    }
}

            
int main(void)
{    
    event_handle=xEventGroupCreate();
    xTaskCreate(task1,"task1",500,NULL,2,NULL);
    xTaskCreate(task2,"task2",500,NULL,2,NULL);
    vTaskStartScheduler();
    while(1){}//无用,运行不到这里来
    return 0;
}

结果

分析:启动任务调度器之后,就绪链表指向任务2,所以任务二先执行;a=1,但此时,任务在死等事件组的第1位置1,进入阻塞;任务1就运行,任务1延迟5再将事件组置位,置位后任务2接着运行,将a设置为0后,又阻塞2,任务1执行,阻塞2时,切换到任务2,将a设置为1,以此循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值