加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

【FreeRTOS操作系统教程】第24章 FreeRTOS任务计数信号量

(2016-09-05 16:16:07)
标签:

fatfs

freertos

stemwin

stm32

usb

分类: FreeRTOS

第24章      FreeRTOS任务计数信号量



本章节为大家讲解FreeRTOS计数信号量的另一种实现方式----基于任务通知(Task Notifications)的计数信号量,这里我们将这种方式实现的计数信号量称之为任务计数信号量。任务计数信号量效率更高,需要的RAM空间更小。当然,缺点也是有的,它没有第21章介绍的计数信号量实现的功能全面。

本章教程配套的例子含Cortex-M3内核的STM32F103Cortex-M4内核的STM32F407以及F429

24.1 任务通知(Task Notifications)介绍

24.2 任务计数信号量

24.3 任务计数信号量API函数

24.4 实验例程说明(任务间通信)

24.5 实验例程说明(中断方式)

24.6       总结

 

 

24.1  任务通知(Task Notifications)介绍

FreeRTOS每个已经创建的任务都有一个任务控制块(task control block),任务控制块就是一个结构体变量,用于记录任务的相关信息。结构体变量中有一个32位的变量成员ulNotifiedValue是专门用于任务通知的。

通过任务通知方式可以实现计数信号量,二值信号量,事件标志组和消息邮箱(消息邮箱就是消息队列长度为1的情况)。使用方法与前面章节讲解的事件标志组和信号量基本相同,只是换了不同的函数来实现。任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改变量ulNotifiedValue实现的:

(1)设置接收任务控制块中的变量ulNotifiedValue可以实现消息邮箱。

(2)如果接收任务控制块中的变量ulNotifiedValue还没有被其接收到,也可以用新数据覆盖原有数据,这就是覆盖方式的消息邮箱。

(3)设置接收任务控制块中的变量ulNotifiedValuebit0-bit31数值可以实现事件标志组。

(4)设置接收任务控制块中的变量ulNotifiedValue数值进行加一或者减一操作可以实现计数信号量和二值信号量。

介绍了这么多,那么问题来了,采用这种方式有什么优势呢?根据官方的测试数据,唤醒由于信号量和事件标志组而处于阻塞态的任务,速度提升了45%,而且这种方式需要的RAM空间更小。但这种方式实现的信号量和事件标志组也有它的局限性,主要表现在以下两个方面:

(1)任务通知方式仅可以用在只有一个任务等待信号量,消息邮箱或者事件标志组的情况,不过实际项目项目中这种情况也是最多的。

(2)使用任务通知方式实现的消息邮箱替代前面章节讲解的消息队列时,发送消息的任务不支持超时等待,即消息队列中的数据已经满了,可以等待消息队列有空间可以存新的数据,而任务通知方式实现的消息邮箱不支持超时等待。

24.2  任务计数信号量

前面第21章,我们对计数信号量进行了讲解,计数信号量就是对一个变量进行计数,变量的范围是从0到用户创建计数信号量时所设置的大小。当计数变量大于0的时候计数信号量管理的资源才可以使用,计数变量的具体数值就是可用的资源大小。

本章节讲解的任务计数信号量与第21章讲解的计数信号量要实现的功能是一样的,不同的是调用的函数和使用的计数变量:

(1)任务计数信号量的计数变量是通过任务控制块中的一个32位变量ulNotifiedValue实现计数。第21章讲解的计数信号量创建后会有自己的计数变量。

(2)任务计数信号量是通过函数ulTaskNotifyTake()替代第21章讲解的函数xSemaphoreTake()实现资源获取,即对计数信号量数值进行减一操作。

(3)任务计数信号量是通过函数xTaskNotifyGive() vTaskNotifyGiveFromISR()替代第21章讲解的函数xSemaphoreGive() xSemaphoreGiveFromISR()实现资源释放,即对计数信号量的数值进行加一操作。

 

具体任务间任务计数信号量的实现过程和中断方式任务计数信号量的实现过程参看第21.1.2小节和21.1.3小节即可。实际项目中,如果使用计数信号量和任务计数信号量都能实现相应功能,强烈建议使用任务计数信号量。

24.3 任务计数信号量API函数

 使用如下9个函数可以实现FreeRTOS的任务信号量(含任务计数信号量和任务二值信号量):
    (1)    xTaskNotifyGive() 
    (2)    vTaskNotifyGiveFromISR()
    (3)    ulTaskNotifyTake()
    (4)    xTaskNotify()
    (5)    xTaskNotifyAndQuery()
    (6)    xTaskNotifyAndQueryFromISR()
    (7)    xTaskNotifyFromISR()
    (8)    xTaskNotifyWait()
    (9)    xTaskNotifyStateClear()

关于这9个函数的讲解及其使用方法可以看FreeRTOS在线版手册:

【FreeRTOS操作系统教程】第24章 <wbr>FreeRTOS任务计数信号量

 

这里我们重点的说以下3个函数:

(1)    xTaskNotifyGive

(2)    vTaskNotifyGiveFromISR

(3)    ulTaskNotifyTake

因为本章节配套的例子使用的是这3个函数。

24.3.1   函数xTaskNotifyGive

函数原型:

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

函数描述:

函数xTaskNotifyGive用于释放信号量(含任务二值信号量,任务计数信号量)。

u  1个参数是任务句柄。

u  返回值,仅有一个返回值pdPASS

使用这个函数要注意以下问题:

1.     任务信号量的初始计数值是0。任务信号量不像前面章节讲解的信号量,无需单独创建即可使用。

2.     默认配置此函数可以使用的的宏定义已经在FreeRTOS.h文件中使能:

#define  configUSE_TASK_NOTIFICATIONS        1

当然,如果用户不需要使用任务通知功能相关的函数,可以在FreeRTOSConfig.h文件中配置此宏定义为0来禁止,这样创建的每个任务可以节省8个字节的需求。

3.     此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是vTaskNotifyGiveFromISR

使用举例:

static TaskHandle_t xHandleTaskMsgPro = NULL;

 

 

 

static void vTaskTaskUserIF(void *pvParameters)

{

     uint8_t ucKeyCode;

 

    while(1)

    {

         ucKeyCode = bsp_GetKey();

        

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

                             

                  

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送消息给任务vTaskMsgPro \r\n");

                       xTaskNotifyGive(xHandleTaskMsgPro);

                  

                  

                   default:                    

                       break;

              }

         }

        

         vTaskDelay(20);

     }

}

 

24.3.2  函数vTaskNotifyGiveFromISR

函数原型:

void vTaskNotifyGiveFromISR(

                     TaskHandle_t xTaskToNotify,             

                     BaseType_t *pxHigherPriorityTaskWoken );

函数描述:

函数xTaskNotifyGive用于释放信号量(含任务二值信号量,任务计数信号量)。

u  1个参数是任务句柄。

u  2个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。

使用这个函数要注意以下问题:

1.     任务信号量的初始计数值是0。任务信号量不像前面章节讲解的信号量,无需单独创建即可使用。

2.     默认配置此函数可以使用的的宏定义已经在FreeRTOS.h文件中使能:

#define  configUSE_TASK_NOTIFICATIONS        1

当然,如果用户不需要使用任务通知功能相关的函数,可以在FreeRTOSConfig.h文件中配置此宏定义为0来禁止,这样创建的每个任务可以节省8个字节的需求。

3.     此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是xTaskNotifyGive

使用举例:

static TaskHandle_t xHandleTaskMsgPro = NULL;

 

 

static void TIM2_IRQHandler (void)

{

     BaseType_t xHigherPriorityTaskWoken = pdFALSE;

 

    

     ……

 

    

     vTaskNotifyGiveFromISR(xHandleTaskMsgPro, &xHigherPriorityTaskWoken);

 

    

     portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

}

24.3.3  函数ulTaskNotifyTake

函数原型:

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,

                            TickType_t xTicksToWait );  

函数描述:

函数xTaskNotifyGive用于释放信号量(含任务二值信号量,任务计数信号量)。

u  1个参数配置为pdFALSE表示函数返回前用于任务信号量的内部变量ulNotifiedValue(详情看24.1小节的解释说明)数值减一,这种方式用于任务计数信号量。参数配置为pdTRUE表示函数返回前用于任务信号量的内部变量ulNotifiedValue(详情看24.1小节的解释说明)数值被清零,这种方式用于任务二值信号量。

u  2个参数是没有任务信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。

使用这个函数要注意以下问题:

1.     任务信号量的初始计数值是0。任务信号量不像前面章节讲解的信号量,无需单独创建即可使用。

2.     默认配置此函数可以使用的的宏定义已经在FreeRTOS.h文件中使能:

#define  configUSE_TASK_NOTIFICATIONS        1

当然,如果用户不需要使用任务通知功能相关的函数可以在FreeRTOSConfig.h文件中配置此宏定义为0来禁止,这样创建的每个任务可以节省8个字节的需求。

3.     如果用户将FreeRTOSConfig.h文件中的宏定义INCLUDE_vTaskSuspend配置为1且第2个参数配置为portMAX_DELAY,那么此函数会永久等待直到信号量可用。

使用举例:

 

static void vTaskMsgPro(void *pvParameters)

{

     const TickType_t xBlockTime = pdMS_TO_TICKS(500);

     uint32_t ulNotifiedValue;

    

    while(1)

    {

          

         ulNotifiedValue = ulTaskNotifyTake(pdFALSE, 

                                               xBlockTime);

         if( ulNotifiedValue > 0 )

        {

             

              printf("任务vTaskMsgPro接收到消息,ulNotifiedValue = %d\r\n", ulNotifiedValue);

        }

        else

        {

             

              bsp_LedToggle(1);

              bsp_LedToggle(4);

        }

    }

}

24.4实验例程说明(任务间通信)

24.4.1STM32F103开发板实验

配套例子:

V4-325_FreeRTOS实验_任务计数信号量

实验目的:

1.     学习FreeRTOS的任务通知实现计数信号量。

实验内容:

1.     K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。

2.     K2键按下,发送任务计数信号量同步消息给任务vTaskMsgPro

3.     各个任务实现的功能如下:

              vTaskUserIF任务 :按键消息处理。

              vTaskLED任务   LED闪烁。

              vTaskMsgPro任务:使用函数ulTaskNotifyTakee接收任务vTaskTaskUserIF发送的同步信号。

              vTaskStart任务   :启动任务,也是最高优先级任务,这里实现按键扫描。

FreeRTOS的配置:

FreeRTOSConfig.h文件中的配置如下:

 

#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)

 #include

 extern volatile uint32_t ulHighFrequencyTimerTicks;

#endif

 

#define configUSE_PREEMPTION         1

#define configUSE_IDLE_HOOK          0

#define configUSE_TICK_HOOK          0

#define configCPU_CLOCK_HZ           ( ( unsigned long ) 72000000 )  

#define configTICK_RATE_HZ           ( ( TickType_t ) 1000 )

#define configMAX_PRIORITIES         ( 5 )

#define configMINIMAL_STACK_SIZE     ( ( unsigned short ) 128 )

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

#define configMAX_TASK_NAME_LEN      ( 16 )

#define configUSE_TRACE_FACILITY      1

#define configUSE_16_BIT_TICKS       0

#define configIDLE_SHOULD_YIELD      1

 

 

#define configGENERATE_RUN_TIME_STATS                1

#define configUSE_STATS_FORMATTING_FUNCTIONS         1

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()     (ulHighFrequencyTimerTicks = 0ul)

#define portGET_RUN_TIME_COUNTER_VALUE()             ulHighFrequencyTimerTicks

//#define portALT_GET_RUN_TIME_COUNTER_VALUE           1

 

 

#define configUSE_CO_ROUTINES            0

#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

 

 

 

#define INCLUDE_vTaskPrioritySet          1

#define INCLUDE_uxTaskPriorityGet         1

#define INCLUDE_vTaskDelete               1

#define INCLUDE_vTaskCleanUpResources      0

#define INCLUDE_vTaskSuspend              1

#define INCLUDE_vTaskDelayUntil           1

#define INCLUDE_vTaskDelay                1

 

 

#ifdef __NVIC_PRIO_BITS

    

     #define configPRIO_BITS              __NVIC_PRIO_BITS

#else

     #define configPRIO_BITS                   

#endif

 

 

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY              0x0f

 

 

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY         0x01

几个重要选项说明:

u  #define configUSE_PREEMPTION        1

使能抢占式调度器

u  #define configCPU_CLOCK_HZ     ( ( unsigned long ) 72000000 )   

系统主频72MHz

u  #define configTICK_RATE_HZ              ( ( TickType_t ) 1000 )

系统时钟节拍1KHz,即1ms

u  #define configMAX_PRIORITIES          ( 5 )

定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4不包含5,对于这一点,初学者要特别的注意。

u  #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。

u  configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY          0x01

定义受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOSAPI的最高优先级。为了进一步说明这个宏定义的的作用,解释如下:

l  使用CM内核的MCU,官方强烈建议将NVIC的优先级分组配置为全抢占式优先级,全部配置为抢占式优先级的好处就是方便管理。

l  对于STM32来说,设置NVIC的优先级分组为4时,NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)就是全部配置为抢占式优先级。又因为STM32的优先级设置仅使用CM内核8bit中的高4bit,即只能区分2^4 = 16种优先级。因此当优先级分组设置为4的时候可供用户选择抢占式优先级为015,共16个优先级,配置为0表示最高优先级,配置为15表示最低优先级,不存在子优先级。

l  这里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY0x01表示用户可以在抢占式优先级为115的中断里面调用FreeRTOSAPI函数,抢占式优先级为0的中断里面是不允许调用的。

更多关于这个参数说明请参看第12章。

FreeRTOS任务调试信息(按K1按键,串口打印):

【FreeRTOS操作系统教程】第24章 <wbr>FreeRTOS任务计数信号量

 

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:

#define tskBLOCKED_CHAR          ( 'B' )  任务阻塞

#define tskREADY_CHAR          ( 'R' )  任务就绪

#define tskDELETED_CHAR           ( 'D' )  任务删除

#define tskSUSPENDED_CHAR   ( 'S' )  任务挂起

程序设计:

u  任务栈大小分配:

vTaskUserIF任务   2048字节

vTaskLED任务     2048字节

vTaskMsgPro任务 :2048字节

vTaskStart任务    2048字节

任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

u  系统栈大小分配:

【FreeRTOS操作系统教程】第24章 <wbr>FreeRTOS任务计数信号量

 

u  FreeROTS初始化:

 

int main(void)

{

    

     __set_PRIMASK(1); 

    

    

     bsp_Init();

    

    

     vSetupSysInfoTest();

    

    

     AppTaskCreate();

    

   

    vTaskStartScheduler();

 

    

     while(1);

}

u  硬件外设初始化

硬件外设的初始化是在bsp.c文件实现:

 

void bsp_Init(void)

{

    

    

          NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

 

     bsp_InitUart();   

     bsp_InitLed();    

     bsp_InitKey();    

}

u  FreeRTOS任务创建:

 

static void AppTaskCreate (void)

{

    xTaskCreate( vTaskTaskUserIF,  

                 "vTaskUserIF",    

                 512,              

                 NULL,             

                 1,                 

                 &xHandleTaskUserIF );  /* 任务句柄  */

    

    

     xTaskCreate( vTaskLED,          

                 "vTaskLED",        

                 512,               

                 NULL,              

                 2,                 

                 &xHandleTaskLED );

    

     xTaskCreate( vTaskMsgPro,           

                 "vTaskMsgPro",          

                 512,                    

                 NULL,                   

                 3,                      

                 &xHandleTaskMsgPro );  /* 任务句柄  */

    

    

     xTaskCreate( vTaskStart,            

                 "vTaskStart",           

                 512,                    

                 NULL,                   

                 4,                      

                 &xHandleTaskStart );   

}

u  四个FreeRTOS任务的实现:

 

static void vTaskTaskUserIF(void *pvParameters)

{

     uint8_t ucKeyCode;

     uint8_t pcWriteBuffer[500];

    

 

    while(1)

    {

         ucKeyCode = bsp_GetKey();

        

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                  

                   case KEY_DOWN_K1:          

                       printf("=================================================\r\n");

                       printf("任务名      任务状态 优先级   剩余栈 任务序号\r\n");

                       vTaskList((char *)&pcWriteBuffer);

                       printf("%s\r\n", pcWriteBuffer);

                  

                       printf("\r\n任务名       运行计数         使用率\r\n");

                       vTaskGetRunTimeStats((char *)&pcWriteBuffer);

                       printf("%s\r\n", pcWriteBuffer);

                       break;

                  

                  

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送消息给任务vTaskMsgPro \r\n");

                       xTaskNotifyGive(xHandleTaskMsgPro);

                  

                  

                   default:                    

                       break;

              }

         }

        

         vTaskDelay(20);

     }

}

 

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 200;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

 

static void vTaskMsgPro(void *pvParameters)

{

     const TickType_t xBlockTime = pdMS_TO_TICKS(500);

     uint32_t ulNotifiedValue;

    

    while(1)

    {

          

         ulNotifiedValue = ulTaskNotifyTake(pdFALSE,

                                               xBlockTime);

         if( ulNotifiedValue > 0 )

        {

             

              printf("任务vTaskMsgPro接收到消息,ulNotifiedValue = %d\r\n", ulNotifiedValue);

        }

        else

        {

             

              bsp_LedToggle(1);

              bsp_LedToggle(4);

        }

    }

}

 

 

static void vTaskStart(void *pvParameters)

{

    while(1)

    {

        

         bsp_KeyScan();

        vTaskDelay(10);

    }

}

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有