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

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

(2016-09-01 17:29:54)
标签:

fatfs

freertos

stemwin

stm32

usb

分类: FreeRTOS

第21章      FreeRTOS计数信号量


本章节开始讲解FreeRTOS任务间的同步和资源共享机制,计数信号量。FreeRTOS中计数信号量的源码实现是基于消息队列实现的。

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

21.1 信号量

21.2 计数信号量API函数

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

21.4 实验例程说明(中断方式通信)

21.5       总结

 

 

21.1 信号量

21.1.1  信号量的概念及其作用

信号量(semaphores)是20世纪60年代中期Edgser Dijkstra发明的。使用信号量的最初目的是为了给共享资源建立一个标志,该标志表示该共享资源被占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。

实际的应用中,信号量的作用又该如何体现呢?比如有个30人的电脑机房,我们就可以创建信号量的初始化值是30,表示30个可用资源,不理解的初学者表示信号量还有初始值?是的,信号量说白了就是共享资源的数量。另外我们要求一个同学使用一台电脑,这样每有一个同学使用一台电脑,那么信号量的数值就减一,直到30台电脑都被占用,此时信号量的数值就是0。如果此时还有几个同学没有电脑可以使用,那么这几个同学就得等待,直到有同学离开。有一个同学离开,那么信号量的数值就加1,有两个就加2,依此类推。刚才没有电脑用的同学此时就有电脑可以用了,有几个同学用,信号量就减几,直到再次没有电脑可以用,这么一个过程就是使用信号量来管理共享资源的过程。

平时使用信号量主要实现以下两个功能:

(1)两个任务之间或者中断函数跟任务之间的同步功能,这个和前面章节讲解的事件标志组是类似的。其实就是共享资源为1的时候。

(2)多个共享资源的管理,就像上面举的机房上机的例子。

针对这两种功能,FreeRTOS分别提供了二值信号量和计数信号量,其中二值信号量可以理解成计数信号量的一种特殊形式,即初始化为仅有一个资源可以使用,只不过FreeRTOS对这两种都提供了API函数,而像RTXuCOS-IIIII是仅提供了一个信号量功能,设置不同的初始值就可以分别实现二值信号量和计数信号量。当然,FreeRTOS使用计数信号量也能够实现同样的效果。

实际上信号量还有很多其它用法,而且极具挑战性,可以大大的开拓大家的视野,有兴趣的同学可以阅读一下《The Little Book Of Semaphores》,作者是Allen B. Downy

本章节主要为大家讲解计数信号量。

21.1.2 FreeRTOS任务间计数信号量的实现

任务间信号量的实现是指各个任务之间使用信号量实现任务的同步或者资源共享功能。下面我们通过如下的框图来说明一下FreeRTOS计数信号量的实现,让大家有一个形象的认识。

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

 

运行条件:

u  创建2个任务Task1Task2

u  创建计数信号量可用资源为1

运行过程描述如下:

u  任务Task1运行过程中调用函数xSemaphoreTake获取信号量资源,如果信号量没有被任务Task2占用,Task1将直接获取资源。如果信号量被Task2占用,任务Task1将由运行态转到阻塞状态,等待资源可用。一旦获取了资源并使用完毕后会通过函数xSemaphoreGive释放掉资源。

u  任务Task2运行过程中调用函数xSemaphoreTake获取信号量资源,如果信号量没有被任务Task1占用,Task2将直接获取资源。如果信号量被Task1占用,任务Task2将由运行态转到阻塞状态,等待资源可用。一旦获取了资源并使用完毕后会通过函数xSemaphoreGive释放掉资源。

 

上面就是一个简单的FreeRTOS任务间计数信号量的使用过程。

21.1.3 FreeRTOS中断方式计数信号量的实现

FreeRTOS中断方式信号量的实现是指中断函数和FreeRTOS任务之间使用信号量。信号量的中断方式主要是用于实现任务同步,与前面章节讲解事件标志组中断方式是一样的。

下面我们通过如下的框图来说明一下FreeRTOS中断方式信号量的实现,让大家有一个形象的认识。

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

 

运行条件:

u  创建一个任务Task1和一个串口接收中断。

u  信号量的初始值为0,串口中断调用函数xSemaphoreGiveFromISR释放信号量,任务Task1调用函数xSemaphoreTake获取信号量资源。

运行过程描述如下:

u  任务Task1运行过程中调用函数xSemaphoreTake,由于信号量的初始值是0,没有信号量资源可用,任务Task1由运行态进入到阻塞态。

u  Task1阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中调用函数xSemaphoreGiveFromISR释放信号量资源,信号量数值加1,此时信号量计数值为1,任务Task1由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态,任务Task1获得信号量后,信号量数值减1,此时信号量计数值又变成了0

u  再次循环执行时,任务Task1调用函数xSemaphoreTake由于没有资源可用再次进入到阻塞态,等待串口释放信号量资源,如此往复循环。

上面就是一个简单的FreeRTOS中断方式信号量同步过程。实际应用中,中断方式的消息机制要注意以下四个问题:

u  中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。

u  实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。

u  中断服务程序中一定要调用专用于中断的信号量设置函数,即以FromISR结尾的函数。

u  在操作系统中实现中断服务程序与裸机编程的区别。

l  如果FreeRTOS工程的中断函数中没有调用FreeRTOS的信号量API函数,与裸机编程是一样的。

l  如果FreeRTOS工程的中断函数中调用了FreeRTOS的信号量API函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程稍有区别,详见21.4小节实验例程说明(中断方式):

l  另外强烈推荐用户将Cortex-M3内核的STM32F103Cortex-M4内核的STM32F407F429NVIC优先级分组设置为4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。

l  用户要在FreeRTOS多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。

21.2计数信号量API函数

使用如下18个函数可以实现FreeRTOS的信号量(含计数信号量,二值信号量和互斥信号)

(1)    xSemaphoreCreateBinary()
    (2)    xSemaphoreCreateBinaryStatic()
    (3)    vSemaphoreCreateBinary()
    (4)    xSemaphoreCreateCounting()
    (5)    xSemaphoreCreateCountingStatic()
    (6)    xSemaphoreCreateMutex()
    (7)    xSemaphoreCreateMutexStatic()
    (8)    xSem'CreateRecursiveMutex()
    (9)    xSem'CreateRecursiveMutexStatic()
    (10)    vSemaphoreDelete()
    (11)    xSemaphoreGetMutexHolder()
    (12)    uxSemaphoreGetCount()
    (13)    xSemaphoreTake()
    (14)    xSemaphoreTakeFromISR()
    (15)    xSemaphoreTakeRecursive()
    (16)    xSemaphoreGive()
    (17)    xSemaphoreGiveRecursive()
    (18)    xSemaphoreGiveFromISR()

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

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

 

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

     (1)    xSemaphoreCreateCounting ()
(2)    xSemaphoreGive ()
(3)    xSemaphoreGiveFromISR ()
(4)    xSemaphoreTake ()

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

21.2.1 函数xSemaphoreCreateCounting

函数原型:

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,     

                                            UBaseType_t uxInitialCount);

函数描述:

函数xSemaphoreCreateCounting用于创建计数信号量。

u  1个参数是设置此计数信号量支持的最大计数值。

u  2个参数是设置计数信号量的初始值。

u  返回值,如果创建成功会返回消息队列的句柄,如果由于FreeRTOSConfig.h文件中heap大小不足,无法为此消息队列提供所需的空间会返回NULL

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

1.     此函数是基函数xQueueCreateCountingSemaphore实现的:

#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )       \

xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

函数xQueueCreateCountingSemaphore的实现是基于消息队列函数xQueueGenericCreate实现的。

2.     使用此函数要在FreeRTOSConfig.h文件中使能宏定义:

#define configUSE_COUNTING_SEMAPHORES   1

使用举例:

static SemaphoreHandle_t  xSemaphore = NULL;

 

 

static void AppObjCreate (void)

{

    

     xSemaphore = xSemaphoreCreateCounting(1, 0);

    

     if(xSemaphore == NULL)

    {

       

    }

}

21.2.2函数xSemaphoreGive

函数原型:

xSemaphoreGive( SemaphoreHandle_t xSemaphore );

函数描述:

函数xSemaphoreGive用于在任务代码中释放信号量。

u  1个参数是信号量句柄。

u  返回值,如果信号量释放成功返回pdTRUE,否则返回pdFALSE,因为计数信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。

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

1.     此函数是基于消息队列函数xQueueGenericSend实现的:

#define  xSemaphoreGive( xSemaphore )                                               \

xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, \

queueSEND_TO_BACK )

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

3.     使用此函数前,一定要保证用函数xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()创建了信号量。

4.     此函数不支持使用xSemaphoreCreateRecursiveMutex()创建的信号量。

使用举例:

static SemaphoreHandle_t  xSemaphore = NULL;

 

 

 

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_K2:

                       printf("K2键按下,直接发送同步信号给任务vTaskMsgPro\r\n");

                       xSemaphoreGive(xSemaphore);

                  

                  

                   default:                    

                       break;

              }

         }

        

         vTaskDelay(20);

     }

}

21.2.3函数xSemaphoreGiveFromISR

函数原型:

xSemaphoreGiveFromISR

      (

        SemaphoreHandle_t xSemaphore,                 

        signed BaseType_t *pxHigherPriorityTaskWoke  

      )

函数描述:

函数xSemaphoreGiveFromISR用于中断服务程序中释放信号量。

u  1个参数是信号量句柄。

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

u  返回值,如果信号量释放成功返回pdTRUE,否则返回errQUEUE_FULL

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

1.     此函数是基于消息队列函数xQueueGiveFromISR实现的:

#define   xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )     \ xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

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

3.     使用此函数前,一定要保证用函数xSemaphoreCreateBinary()或者 xSemaphoreCreateCounting()创建了信号量。

4.     此函数不支持使用xSemaphoreCreateMutex ()创建的信号量。

使用举例:

static SemaphoreHandle_t  xSemaphore = NULL;

 

 

static void TIM2_IRQHandler (void)

{

     BaseType_t xHigherPriorityTaskWoken = pdFALSE;

 

    

     ……

    

    

     xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);

 

    

     portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

}

21.2.4函数xSemaphoreTake

函数原型:

xSemaphoreTake( SemaphoreHandle_t xSemaphore,

                 TickType_t xTicksToWait );  

函数描述:

函数xSemaphoreTake用于在任务代码中获取信号量。

u  1个参数是信号量句柄。

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

u  返回值,如果创建成功会获取信号量返回pdTRUE,否则返回pdFALSE

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

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

2.     如果消息队列为空且第2个参数为0,那么此函数会立即返回。

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

使用举例:

static SemaphoreHandle_t  xSemaphore = NULL;

 

 

static void vTaskMsgPro(void *pvParameters)

{

     BaseType_t xResult;

     const TickType_t xMaxBlockTime = pdMS_TO_TICKS(300);

    

    while(1)

    {

         xResult = xSemaphoreTake(xSemaphore, (TickType_t)xMaxBlockTime);

        

         if(xResult == pdTRUE)

         {

             

              printf("接收到同步信号\r\n");

         }

         else

         {

             

              bsp_LedToggle(1);

              bsp_LedToggle(4);

         }

    }

}

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

21.3.1STM32F103开发板实验

配套例子:

V4-319_FreeRTOS实验_计数信号量

实验目的:

1.     学习FreeRTOS的计数信号量。

2.     使用计数号量实现任务同步功能。

实验内容:

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

2.     K2键按下,直接发送同步信号给任务vTaskMsgPro

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

              vTaskUserIF任务 :按键消息处理。

              vTaskLED任务   LED闪烁。

              vTaskMsgPro任务:使用函数xSemaphoreTake接收任务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 configUSE_COUNTING_SEMAPHORES   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

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

【FreeRTOS操作系统教程】第21章 <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操作系统教程】第21章 <wbr>FreeRTOS计数信号量

 

u  FreeROTS初始化:

 

int main(void)

{

    

     __set_PRIMASK(1); 

    

    

     bsp_Init();

    

    

     vSetupSysInfoTest();

    

    

     AppTaskCreate();

 

    

     AppObjCreate();

    

   

    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 AppObjCreate (void)

{

    

     xSemaphore = xSemaphoreCreateCounting(1, 0);

    

     if(xSemaphore == NULL)

    {

        

    }

}

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");

                       xSemaphoreGive(xSemaphore);

                  

                  

                   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)

{

     BaseType_t xResult;

     const TickType_t xMaxBlockTime = pdMS_TO_TICKS(300);

    

    while(1)

    {

         xResult = xSemaphoreTake(xSemaphore, (TickType_t)xMaxBlockTime);

        

         if(xResult == pdTRUE)

         {

             

              printf("接收到同步信号\r\n");

         }

         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 | 产品答疑

新浪公司 版权所有