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

【FreeRTOS操作系统教程】第23章 FreeRTOS互斥信号量

(2016-09-03 16:18:31)
标签:

fatfs

freertos

stemwin

stm32

usb

分类: FreeRTOS

第23章      FreeRTOS互斥信号量


本章节讲解FreeRTOS重要的资源共享机制---互斥信号量(Mutex,即Mutual Exclusion的缩写)。注意,建议初学者学习完前两个章节的信号量后再学习本章节的互斥信号量

FreeRTOS中互斥信号量的源码实现是基于消息队列实现的。

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

23.1 互斥信号量

23.2 互斥信号量API函数

23.3 实验例程说明

23.4       总结

 

 

23.1 互斥信号量

23.1.1  互斥信号量的概念及其作用

互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子,让大家有一个形象的认识,进而引出要讲解的互斥信号量。

运行条件:

u  让两个任务Task1Task2都运行串口打印函数printf,这里我们就通过二值信号量实现对函数printf的互斥访问。如果不对函数printf进行互斥访问,串口打印容易出现乱码。

u  用计数信号量实现二值信号量只需将计数信号量的初始值设置为1即可。

代码实现:

u  创建二值信号量

static SemaphoreHandle_t  xSemaphore = NULL;

 

 

static void AppObjCreate (void)

{

    

     xSemaphore = xSemaphoreCreateBinary();

    

     if(xSemaphore == NULL)

    {

        

    }

    

    

     xSemaphoreGive(xSemaphore);

}

u  通过二值信号量实现对printf函数互斥访问的两个任务

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 300;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

        

         xSemaphoreTake(xSemaphore, portMAX_DELAY);

        

         printf("任务vTaskLED在运行\r\n");

         bsp_LedToggle(1);

         bsp_LedToggle(4);

        

         xSemaphoreGive(xSemaphore);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

 

static void vTaskMsgPro(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 300;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

        

         xSemaphoreTake(xSemaphore, portMAX_DELAY);

        

         printf("任务vTaskMsgPro在运行\r\n");

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

         xSemaphoreGive(xSemaphore);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

有了上面二值信号量的认识之后,互斥信号量与二值信号量又有什么区别呢?互斥信号量可以防止优先级翻转,而二值信号量不支持,下面我们就讲解一下优先级翻转问题。

23.1.2 优先级翻转问题

下面我们通过如下的框图来说明一下优先级翻转的问题,让大家有一个形象的认识。

【FreeRTOS操作系统教程】第23章 <wbr>FreeRTOS互斥信号量 

运行条件:

u  创建3个任务Task1Task2Task3,优先级分别为321。也就是Task1的优先级最高。

u  任务Task1Task3互斥访问串口打印printf,采用二值信号实现互斥访问。

u  起初Task3通过二值信号量正在调用printf,被任务Task1抢占,开始执行任务Task1,也就是上图的起始位置。

运行过程描述如下:

u  任务Task1运行的过程需要调用函数printf,发现任务Task3正在调用,任务Task1会被挂起,等待Task3释放函数printf

u  在调度器的作用下,任务Task3得到运行,Task3运行的过程中,由于任务Task2就绪,抢占了Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务Task1需要等待Task2执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务Task1等待低优先级任务Task2完成。所以这种情况被称之为优先级翻转问题

u  任务Task2执行完毕后,任务Task3恢复执行,Task3释放互斥资源后,任务Task1得到互斥资源,从而可以继续执行。

上面就是一个产生优先级翻转问题的现象。

23.1.3 FreeRTOS互斥信号量的实现

FreeRTOS互斥信号量是怎么实现的呢?其实相对于二值信号量,互斥信号量就是解决了一下优先级翻转的问题。下面我们通过如下的框图来说明一下FreeRTOS互斥信号量的实现,让大家有一个形象的认识。

【FreeRTOS操作系统教程】第23章 <wbr>FreeRTOS互斥信号量

运行条件:

u  创建2个任务Task1Task2,优先级分别为13,也就是任务Task2的优先级最高。

u  任务Task1Task2互斥访问串口打印printf

u  使用FreeRTOS的互斥信号量实现串口打印printf的互斥访问。

运行过程描述如下:

u  低优先级任务Task1执行过程中先获得互斥资源printf的执行。此时任务Task2抢占了任务Task1的执行,任务Task1被挂起。任务Task2得到执行。

u  任务Task2执行过程中也需要调用互斥资源,但是发现任务Task1正在访问,此时任务Task1的优先级会被提升到与Task2同一个优先级,也就是优先级3这个就是所谓的优先级继承(Priority inheritance),这样就有效地防止了优先级翻转问题。任务Task2被挂起,任务Task1有新的优先级继续执行。

u  任务Task1执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务Task2获得互斥资源后开始执行。

 

上面就是一个简单的FreeRTOS互斥信号量的实现过程。

23.1.4FreeRTOS中断方式互斥信号量的实现

互斥信号量仅支持用在FreeRTOS的任务中,中断函数中不可使用。

23.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操作系统教程】第23章 <wbr>FreeRTOS互斥信号量

 

这里我们重点的说以下3个函数:
      (1)    xSemaphoreCreateMutex ()
      (2)    xSemaphoreGive ()
      (3)    xSemaphoreTake ()

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

23.2.1 函数xSemaphoreCreateMutex

函数原型:

SemaphoreHandle_t xSemaphoreCreateMutex( void )

函数描述:

函数xSemaphoreCreateMutex用于创建互斥信号量。

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

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

1.     此函数是基于函数xQueueCreateMutex实现的:

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

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

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

#define configUSE_MUTEXES   1

使用举例:

static SemaphoreHandle_t  xMutex = NULL;

 

 

static void AppObjCreate (void)

{

    

xMutex = xSemaphoreCreateMutex();

 

     if(xSemaphore == NULL)

    {

       

    }

}

23.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  xMutex = NULL;

 

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 200;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

        

         xSemaphoreTake(xMutex, portMAX_DELAY);

        

         printf("任务vTaskLED在运行\r\n");

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

         xSemaphoreGive(xMutex);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

23.2.3函数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  xMutex = NULL;

 

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 200;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

        

         xSemaphoreTake(xMutex, portMAX_DELAY);

        

         printf("任务vTaskLED在运行\r\n");

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

         xSemaphoreGive(xMutex);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

23.3实验例程说明

23.3.1STM32F103开发板实验

配套例子:

V4-323_FreeRTOS实验_互斥信号量

实验目的:

1.     学习FreeRTOS的互斥信号量。

实验内容:

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

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

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

              vTaskUserIF任务 :按键消息处理。

              vTaskLED任务    :实现串口的互斥访问,防止多个任务同时访问造成串口打印乱码。

              vTaskMsgPro任务:实现串口的互斥访问,防止多个任务同时访问造成串口打印乱码。

              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_MUTEXES            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  #define configUSE_MUTEXES                     1

使能互斥信号量。

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操作系统教程】第23章 <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操作系统教程】第23章 <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)

{

    

    xMutex = xSemaphoreCreateMutex();

    

     if(xMutex == 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:

                      

                       xSemaphoreTake(xMutex, portMAX_DELAY);                      

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

                       xSemaphoreGive(xMutex);

                       break;

                  

                  

                   default:                    

                       break;

              }

         }

        

         vTaskDelay(20);

     }

}

 

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 200;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

        

         xSemaphoreTake(xMutex, portMAX_DELAY);

        

         printf("任务vTaskLED在运行\r\n");

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

         xSemaphoreGive(xMutex);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

 

static void vTaskMsgPro(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 300;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

        

         xSemaphoreTake(xMutex, portMAX_DELAY);

        

         printf("任务vTaskMsgPro在运行\r\n");

         bsp_LedToggle(1);

         bsp_LedToggle(4);

        

         xSemaphoreGive(xMutex);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

 

static void vTaskStart(void *pvParameters)

{

    while(1)

    {

        

         bsp_KeyScan();

        vTaskDelay(10);

    }

}

0

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

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

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

新浪公司 版权所有