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

本文详细介绍了FreeRTOS中的计数信号量,包括任务间和中断方式的实现,以及相关API函数的使用。通过实验例程展示了如何在STM32F103开发板上使用计数信号量实现任务同步。重点讨论了xSemaphoreCreateCounting、xSemaphoreGive、xSemaphoreGiveFromISR和xSemaphoreTake四个函数的使用方法。
摘要由CSDN通过智能技术生成

第21章

FreeRTOS计数信号量

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

本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-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函数,而像RTX,uCOS-II和III是仅提供了一个信号量功能,设置不同的初始值就可以分别实现二值信号量和计数信号量。当然,FreeRTOS使用计数信号量也能够实现同样的效果。

实际上信号量还有很多其它用法,而且极具挑战性,可以大大的开拓大家的视野,有兴趣的同学可以阅读一下《The

Little Book Of Semaphores》,作者是Allen

B. Downy。

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

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

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

运行条件:

u

创建2个任务Task1和Task2。

u

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

运行过程描述如下:

u

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

u

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

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

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

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

下面我们通过如下的框图来说明一下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内核的STM32F103和Cortex-M4内核的STM32F407,F429的NVIC优先级分组设置为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在线版手册:

这里我们重点的说以下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 *pxHigherPriorityTaskWoken

)

函数描述:

函数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 4

#endif

#define

configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x0f

#define

configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01

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

上面截图中打印出来的任务状态字母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

系统栈大小分配:

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

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值