七、FreeRTOS事件和常用函数接口

基本概念

事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
每一个事件组只需要很少的 RAM 空间来保存事件组的状态。 事件组存储在一个
EventBits_t 类 型 的 变 量 中 , 该 变 量 在 事 件 组 结 构 体 中 定 义 。 如 果 宏
configUSE_16_BIT_TICKS 定义为 1, 那么变量 uxEventBits 就是 16 位的, 其中有 8 个位用
来存储事件组; 而如果宏 configUSE_16_BIT_TICKS 定义为 0, 那么变量 uxEventBits 就是
32 位 的 , 其 中 有 24 个 位 用 来 存 储 事 件 组 。 在 STM32 中 , 我 们 一 般 将
configUSE_16_BIT_TICKS 定义为 0,那么 uxEventBits 是 32 位的,有 24 个位用来实现事
件标志组。 每一位代表一个事件, 任务通过“逻辑与”或“逻辑或”与一个或多个事件建
立关联,形成一个事件组。事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣
的所有事件任一件发生即可被唤醒;事件“逻辑与” 则被称为是关联型同步,指的是任务
感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。

在 FreeRTOS事件中,每个事件获取的时候,用户可以选择感兴趣的事件,并且选择读取事件信息标记,它有三个属性,分别是逻辑与,逻辑或以及是否清除标记。当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记来判断当前接收的事件是否满足要求,如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下去。
 

 运作机制

接收事件时,可以根据感兴趣的参事件类型接收事件的单个或者多个事件类型。事件接收成功后,必须使用xClearOnExit选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位。用户可以自定义通过传入参数xWaitForAllBits选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。
设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为l,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清О操作事件不与任务相关联,事件相互独立,一个32位的变量(事件集合,实际用于表示事件的只有24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(О表示该事件类型未发生、1表示该事件类型已经发生)。

函数接口

xEventGroupCreate()  用于创建一个事件组,并返回对应的句柄

使用前要先创建事件句柄

vEventGroupDelete()事件删除函数,传入要删除的事件句柄

  xEventGroupSetBits()  事件组置位函数(任务)

xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。简单来说,就是设置我们自己定义的事件标志位为1,并且看看有没有任务在等待这个事件,有的话就唤醒它。

  xEventGroupWaitBits()  等待事件函数

既然标记了事件的发生,那么我怎么知道他到底有没有发生,这也是需要一个函数来获取事件是否已经发生,FreeRTOS提供了一个等待指定事件的函数―—xEventGroupWaitBits(),通过这个函数,任务可以知道事件标志组中的哪些位,有什么事件发生了,然后通过“逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件信息。在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态,当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态。这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也会返回一个不能确定的事件值,所以需要判断任务所等待的事件是否真的发生。

 xEventGroupClearBits()清除事件组指定的位, 如果在获取事件的时候没有将对应的标志位清除, 那么就需要用这个函数来进行显式清除

事件组实验

K1和K2分别触发事件A1,A2,在LED任务中判断A1,A2都发生,则LED翻转,发送串口信息。uxEventBits是32位的,事件 24个位用来存储事件标志组,每一位表示一个事件,可以通过逻辑与和逻辑或来设置关联型同步或者独立型同步。

main.c如下

#include "stm32f10x.h"

#include "EXC_Gpio.h"
#include "EXC_Usart.h"  
#include "EXC_Led.h"
#include "EXC_Key.h"

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
 /* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle;
static TaskHandle_t LED_Task_Handle = NULL;/*LED_Task 任务句柄 */

static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task 任务句柄 */
/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */
 
//static EventGroupHandle_t Event_Handle =NULL;

 /************************** 宏定义 *********************************/
 /*
  * 当我们在写应用程序的时候,可能需要用到一些宏定义。
  */
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位 0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位 1
 
/*
*************************************************************************
*                             函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void LED_Task(void* pvParameters);/*LED_Task任务实现 */


static void KEY_Task(void* pvParameters);/* KEY_Task任务实现 */


static void BSP_Init(void);/* 用于初始化板载相关资源 */


/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/

int main(void)
{
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
	BSP_Init();
   /* 创建 AppTaskCreate 任务 */
	xReturn = xTaskCreate((TaskFunction_t	)AppTaskCreate,		//任务函数
                        (const char* 	)"AppTaskCreate",		//任务名称
                        (uint32_t 		)256,	//任务堆栈大小
                        (void* 		  	)NULL,				//传递给任务函数的参数
                        (UBaseType_t 	)3, 	//任务优先级
                        (TaskHandle_t*   )AppTaskCreate_Handle);	//任务控制块指针
                            //任务控制块
    if(pdPASS == xReturn)/* 创建成功 */
    {
        vTaskStartScheduler();   /* 启动任务,开启调度 */

    }  
    else
    {
        return -1;        
    }
	while(1);
}

/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
    taskENTER_CRITICAL();           //进入临界区
    BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */

    /* 创建 LED_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )LED_Task,/*任务入口函数 */
                        (const char* )"LED_Task",/* 任务名字 */
                        (uint16_t )128, /* 任务栈大小 */
                        (void* )NULL, /* 任务入口函数参数 */
                        (UBaseType_t )2, /* 任务的优先级 */
                        (TaskHandle_t*)&LED_Task_Handle);/*任务控制块指针 */
    if (pdPASS == xReturn)
    {
        printf("创建 LED_Task 任务成功!\r\n");
    }
    

    
    /* 创建 KEY_Task 任务 */
    xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */ 
                         (const char* )"KEY_Task",/* 任务名字 */
                         (uint16_t )512, /* 任务栈大小 */
                         (void* )NULL,/* 任务入口函数参数 */
                         (UBaseType_t )4, /* 任务的优先级 */
                         (TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */
     if (pdPASS == xReturn)
     {
         printf("创建 KEY_Task 任务成功!\n\n");
     }
     
    vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务

    taskEXIT_CRITICAL();            //退出临界区
}


/**********************************************************************
  * @ 函数名  : LED_Task
  * @ 功能说明: LED_Task 任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  * @ Note    :  
  ********************************************************************/
static void LED_Task(void* parameter)
{	
	uint32_t r_event = 0; /* 定义一个事件接收变量 */
	uint32_t last_event = 0;/* 定义一个保存事件的变量 */
	BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
	/* 任务都是一个无限循环,不能返回 */
    while (1) 
    {
		/* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
		uint32_t ulBitsToClearOnExit,
		uint32_t *pulNotificationValue,
		TickType_t xTicksToWait );
		* ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取
		反值进行按位与运算,当此参数为 Oxfffff 或者 ULONG_MAX 的时候就会将任务通知值清零。
		* ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将
		任务通知值与此参数的取反值进行按位与运算,当此参数为 0xfffff 或者 ULONG MAX 的时候
		就会将任务通知值清零。
		* pulNotification Value:此参数用来保存任务通知值。
		* xTick ToWait:阻塞时间。
		*
		* 返回值: pdTRUE:获取到了任务通知。 pdFALSE:任务通知获取失败。
		*/
		//获取任务通知 ,没获取到则一直等待
        xReturn = xTaskNotifyWait(0x0, //进入函数的时候不清除任务 bit
								  ULONG_MAX, //退出函数的时候清除所有的 bitR
								  &r_event, //保存任务通知值
								  portMAX_DELAY); //阻塞时间
		if ( pdTRUE == xReturn ) 
		{
			last_event |= r_event;
			/* 如果接收完成并且正确 */
			if (last_event == (KEY1_EVENT|KEY2_EVENT)) 
			{
				last_event = 0; /* 上一次的事件清零 */
				printf ( "Key1 与 Key2 都按下\r\n");
				LED1_TOGGLE; //LED1 反转
			} else /* 否则就更新事件 */
			last_event = r_event; /* 更新上一次触发的事件 */
		}

		vTaskDelay(20);
    }
}


/**********************************************************************
* @ 函数名 : KEY_Task
* @ 功能说明: KEY_Task 任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void KEY_Task(void* parameter)
{
    while (1) 
    {
        /* KEY1 被按下 */
        if ( Key_Scan(K1) == KEY_ON ) 
        {
            printf("K1 被按下\r\n");
            xTaskNotify((TaskHandle_t)LED_Task_Handle,//接收任务通知的任务句柄
                        (uint32_t)KEY1_EVENT, //要触发的事件
                        (eNotifyAction)eSetBits); //设置任务通知值中的位              
        }
        /* KEY2 被按下 */
        if ( Key_Scan(K2) == KEY_ON ) 
        {
            printf("K2 被按下\r\n");
            xTaskNotify((TaskHandle_t)LED_Task_Handle,//接收任务通知的任务句柄
                        (uint32_t)KEY2_EVENT, //要触发的事件
                        (eNotifyAction)eSetBits); //设置任务通知值中的位     
        }
        vTaskDelay(20);
    }
}
/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    USART_init(SYSTEM_UART,115200,1);
    printf("串口初始化成功\r\n");
    /* LED 初始化 */
    LED3Color_Set(LEDBLUE);

    Key_Init(K1);
    Key_Init(K2);
    ILI9341_Init();
    LCD_SetFont(&Font8x16);
    LCD_SetColors(RED,BLACK);
    ILI9341_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH);	/* 清屏,显示全黑 */
    ILI9341_DispString_EN(50,0,"Hello world!");

}

/********************************END OF FILE****************************/

实验现象
            按下两个按键(可以不同时)后LED灯翻转,串口发送信息。
           

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
上册共分为10部分,这是第1部分 Windows内核情景分析(上册).part01.rar 基本信息 作者: 毛德操 出版社:电子工业出版社 ISBN:9787121081149 上架时间:2009-5-25 出版日期:2009 年5月 开本:16开 页码:1465 版次:1-1 所属分类:计算机 > 操作系统 > Windows 内容简介回到顶部↑ 本书通过分析ReactOS的源代码介绍了Windows内核各个方面的结构、功能、算法与具体实现。全书从“内存管理”、“进程”、“进程间通信”、“设备驱动”等多个方面进行分析介绍,所有的分析都有ReactOS的源代码(以及部分由微软公开的源代码)作为依据,使读者能深入理解Windows内核的方方面面,也可以使读者的软件开发能力和水平得到提高。. 本书可供大学有关专业的高年级学生和研究生用做教学参考,也可供广大的软件工程师,特别是从事系统软件研发的工程师用于工作参考或用做进修教材。... 目录回到顶部↑ 上 册. 第1章 概述 1 1.1 Windows操作系统发展简史 1 1.2 用户空间和系统空间 3 1.3 Windows内核 4 1.4 开源项目ReactOS及其代码 9 1.5 Windows内核函数的命名 10 第2章 系统调用 12 2.1 内核与系统调用 12 2.2 系统调用的内核入口KiSystemService() 22 2.3 系统调用的函数跳转 29 2.4 系统调用的返回 32 2.5 快速系统调用 35 2.6 从内核中发起系统调用 42 第3章 内存管理 44 3.1 内存区间的动态分配 47 3.1.1 内核对用户空间的管理 48 3.1.2 内核对于物理页面的管理 60 3.1.3 虚存页面的映射 67 3.1.4 Hyperspace的临时映射 78 .3.1.5 系统空间的映射 86 3.1.6 系统调用NtAllocateVirtualMemory() 90 3.2 页面异常 97 3.3 页面的换出 107 3.4 共享映射区(Section) 115 3.5 系统空间的缓冲区管理 133 第4章 对象管理 136 4.1 对象与对象目录 136 4.2 对象类型 148 4.3 句柄和句柄表 162 4.4 对象的创建 169 4.5 几个常用的内核函数 179 4.5.1 ObReferenceObjectByHandle() 179 4.5.2 ObReferenceObjectByPointer() 187 4.5.3 ObpLookupEntryDirectory() 188 4.5.4 ObpLookupObjectName() 192 4.5.5 ObOpenObjectByName() 209 4.5.6 ObReferenceObjectByName() 213 4.5.7 ObDereferenceObject() 214 4.6 对象的访问控制 218 4.7 句柄的遗传和继承 218 4.8 系统调用NtDuplicateObject() 223 4.9 系统调用NtClose() 233 第5章 进程与线程 241 5.1 概述 241 5.2 Windows进程的用户空间 253 5.3 系统调用NtCreateProcess() 273 5.4 系统调用NtCreateThread() 284 5.5 Windows的可执行程序映像 300 5.6 Windows的进程创建和映像装入 305 5.7 Windows DLL的装入和连接 329 5.8 Windows的APC机制 358 5.9 Windows线程的调度和切换 381 5.9.1 x86系统结构与线程切换 382 5.9.2 几个重要的数据结构 385 5.9.3 线程的切换 388 5.9.4 线程的调度 395 5.10 线程和进程的优先级 409 5.11 线程本地存储TLS 421 5.12 进程挂靠 434 5.13 Windows的跨进程操作 442 5.14 Windows线程间的相互作用 450 第6章 进程间通信 467 6.1 概述 467 6.2 共享内存区(Section).. 469 6.3 线程的等待/唤醒机制 470 6.4 信号量(Semaphore) 499 6.5 互斥门(Mutant) 505 6.6 事件(Event) 512 6.7 命名管道(Named Pipe)和信插(Mailslot) 516 6.8 本地过程调用(LPC) 521 6.9 视窗报文

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值