一不做,二不休,下面是事件的测试代码:
// //-- main.c // #include <stdio.h> #include <stdlib.h> #include "stm32f10x.h" #include "tn.h" //-- 空闲任务栈的大小,以字为单位 17+32个字,即49个字 #define IDLE_TASK_STACK_SIZE (TN_MIN_STACK_SIZE + 32) //-- 中断堆栈大小,以字为单位 #define INTERRUPT_STACK_SIZE (TN_MIN_STACK_SIZE + 32) //-- 用户任务的堆栈大小 #define TASK_A_STK_SIZE (TN_MIN_STACK_SIZE + 32) #define TASK_B_STK_SIZE (TN_MIN_STACK_SIZE + 32) //-- 用户任务优先级,0最高 #define TASK_A_PRIORITY 2 #define TASK_B_PRIORITY 1 //-- 消费者信息队列中的项目数量 #define CONS_QUE_BUF_SIZE 50 //-- 等待内存和空闲信息的最大超时时间 #define WAIT_TIMEOUT 10 /******************************************************************************* * DATA ******************************************************************************/ //-- Allocate arrays for stacks: stack for idle task // and for interrupts are the requirement of the kernel; // others are application-dependent. // 不是创建任务时,会分配堆栈吗? 这里要做一次? // We use convenience macro TN_STACK_ARR_DEF() for that. TN_STACK_ARR_DEF(idle_task_stack, IDLE_TASK_STACK_SIZE); TN_STACK_ARR_DEF(interrupt_stack, INTERRUPT_STACK_SIZE); TN_STACK_ARR_DEF(task_a_stack, TASK_A_STK_SIZE); TN_STACK_ARR_DEF(task_b_stack, TASK_B_STK_SIZE); //-- 任务结构, 当成C++公有的类,task_a b c 是类实列化的对像 static struct TN_Task task_a; static struct TN_Task task_b; //-- 为其定义队列和缓冲器 static struct TN_DQueue cons_que; void *cons_que_buf[ CONS_QUE_BUF_SIZE ]; //-- 事件组,基于队列的 static struct TN_EventGrp que_example_events; //-- 队列状态的旗号 enum E_QueExampleFlag { /// 0000 /// Flag indicating that consumer task is initialized QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT = (1 << 0), //1 /// /// Flag indicating that producer task is initialized QUE_EXAMPLE_FLAG__TASK_PRODUCER_INIT = (1 << 1), //2 /// /// Flag indicating that consumer's queue A is not empty QUE_EXAMPLE_FLAG__MSG_A = (1 << 2), //4 /// /// Flag indicating that consumer's queue B is not empty QUE_EXAMPLE_FLAG__MSG_B = (1 << 3), //8 }; /******************************************************************************* * FUNCTIONS ******************************************************************************/ //-- to printf int fputc(int c, FILE *stream) { return ITM_SendChar(c); } //-- init system timer void hw_init(void) { SysTick_Config(SYS_FREQ / SYS_TMR_FREQ); // 配置SysTick } //-- system timer 中断 void SysTick_Handler(void) { tn_tick_int_processing(); // 周期性执行 } //-- a task 生产者 void task_a_body(void *par) { printf("New task a is OK!\n"); tn_eventgrp_wait( //-- 等待消费者队列初始化完成,有消费才进行生产,附合正常思维 &que_example_events, QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT, TN_EVENTGRP_WMODE_AND, TN_NULL, TN_WAIT_INFINITE ); tn_eventgrp_modify( //-- 调整事件状态,生产者已初始化OK &que_example_events, TN_EVENTGRP_OP_SET, QUE_EXAMPLE_FLAG__TASK_PRODUCER_INIT ); enum TN_RCode tn_rc; static int p_msg=0x55; for(;;) { tn_rc = tn_is_task_context() //-- 将其发送给消费者任务 ? tn_queue_send(&cons_que, (void *)&p_msg, WAIT_TIMEOUT) : tn_queue_isend_polling(&cons_que, (void *)&p_msg); if (tn_rc == TN_RC_OK){ printf("task_a_queue_send_success\n"); p_msg++; } tn_task_sleep(60); } } //-- b task 消费者 void task_b_body(void *par) { printf("New task b is OK!\n"); //-- 消费者任务已初始化,调整事件状态 tn_eventgrp_modify( &que_example_events, //-- 要调整的事件 TN_EVENTGRP_OP_SET, //-- 事件调整为SET QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT//-- 状态的旗号__消费者已经初始化OK ); enum TN_RCode rc; char* dat; TN_UWord flags_pattern = 0; int *p_msg=0; for(;;) { //-- 等待消息(即连接标志),可能永远等待 enum TN_RCode rc = tn_eventgrp_wait( &que_example_events, // 取事件 (QUE_EXAMPLE_FLAG__MSG_A), // 队列旗号QUE_EXAMPLE_FLAG__MSG_A TN_EVENTGRP_WMODE_OR, // OR模式 &flags_pattern, // 指向实际事件模式的 `TN_UWord` 变量的指针 TN_WAIT_INFINITE // 超时 ); if (rc == TN_RC_OK){ if (flags_pattern & QUE_EXAMPLE_FLAG__MSG_B){ rc = tn_queue_receive(&cons_que, (void *)&p_msg, 0); if (rc == TN_RC_OK){ printf("task_b_msg = 0x%X\n", (int)*p_msg); p_msg = NULL; } } } } } //-- 空闲回调,从空闲任务中定期调用 void idle_task_callback (void) { ; } //-- 创建应用任务, void init_task_create(void) { //-- 初始化各种板载外围设备,如闪存、显示器,等等。 //-- 初始化各种程序模块,----- 免去再建一个APP_init函数,没必要 //-- 创建队列cons_que tn_queue_create(&cons_que, (void *)cons_que_buf, CONS_QUE_BUF_SIZE); //-- 创建事件que_example_events tn_eventgrp_create(&que_example_events, (0)); //-- 将应用程序的公共事件组与队列连接起来,并加上状态的旗号 tn_queue_eventgrp_connect( &cons_que, //-- 要连接的队列 &que_example_events, //-- 要连接的事件 (QUE_EXAMPLE_FLAG__MSG_A) //-- 状态的旗号,处理时的依据 ); //-- 以下是任务建立,推介只激活一个任务,然后在该任务依次激活所有任务. tn_task_create( &task_a, //-- task structure 任务a对像的地址 task_a_body, //-- task body function 任务A的回调函数 TASK_A_PRIORITY, //-- task priority 任务A的优先级 task_a_stack, //-- task stack 任务A的堆栈地址,类似数组 TASK_A_STK_SIZE, //-- task stack size (in words) 任务A的堆栈长度 NULL, //-- task function parameter 函数参数 TN_TASK_CREATE_OPT_START //-- creation option 任务创建后是否应立即激活 ); tn_task_create( &task_b, task_b_body, TASK_B_PRIORITY, task_b_stack, TASK_B_STK_SIZE, NULL, TN_TASK_CREATE_OPT_START // 激活 ); } /******************************************************************************* * 主程序入口 ******************************************************************************/ int main(void) { //-- 无条件关中断,初始化OS心跳 tn_arch_int_dis(); hw_init(); //-- call to tn_sys_start() never returns 对tn_sys_start()的调用从不返回 tn_sys_start( idle_task_stack, // 空闲堆栈地址 IDLE_TASK_STACK_SIZE, // 空闲堆栈大小 interrupt_stack, // 中断栈 INTERRUPT_STACK_SIZE, // 中断栈大小 init_task_create, // 用户任务回调 idle_task_callback // 空闲任务回调 ); //-- unreachable 无法到达 return 1; }
运行效果如下:
大概的工作原理:
这是用列队邦定到事件组的一个测试代码,即然有队列就有生产和消费关系,见A任务和B任务。在开始之前,除了正常的创建队列,创建任务之外。
我们还要使用tn_queue_eventgrp_connect的OS接口,对事件与队列进行邦定,还要建立对应的E_QueExampleFlag状态标示号。最后通过
tn_eventgrp_wait的OS接口等待事件的到来,只要其他任务往队列里放入信息时,就会产生事件,来源为队列的QUE_EXAMPLE_FLAG__MSG_A状态号。在TNeo中,
以STM32为例,就是32种事件,一个事件占用一个bit位。如果是16位单片机,就是有16种事件,也是一个BIT一个事件,这种模设计有助于提升速度,优先级也是这个套路。
有些问题:
通常来说队列用于任务的异步通讯的,而事件是用于任务的同步通讯的。所以上面的测试,到底测试了个啥??有没有丢掉队列单独测试事件方法??
代码流程:
从tn_eventgrp.h的文件来看,貌似要邦定队列用,可以一对多,多对一,多对多,应该是比较方便的,不能以常规的思维看待TNeo.
//-- 创建队列cons_que tn_queue_create(&cons_que, (void *)cons_que_buf, CONS_QUE_BUF_SIZE); //-- 创建事件que_example_events tn_eventgrp_create(&que_example_events, (1<<31)); //-- 初始事件模式:(1<<31)bit位 //-- 将应用程序的公共事件组与队列连接起来,并加上状态的旗号 tn_queue_eventgrp_connect( &cons_que, //-- 要连接的队列 &que_example_events, //-- 要连接的事件 QUE_EXAMPLE_FLAG__MSG_A //-- 状态的旗号,处理时的依据 );
tn_eventgrp_create(&que_example_events, (1<<31)); events是一个事件结构的变量,1<<31占一个bit位,在32bit单片机中,最多就是32个事件。
大意就是,第31bit位邦定事件为que_example_events,其他的XXbit位可以邦定XXX_events.这个要理解
tn_queue_eventgrp_connect这是事件组连接队列API函数,大意就是队列cons_que与事件que_example_events连接起来,cons_que产生的事件旗号为QUE_EXAMPLE_FLAG__MSG_A,
旗号QUE_EXAMPLE_FLAG__MSG_A为enum结构,其实可以bit位来表示,如(1<<2),(1<<3),都行,只是数字没有QUE_EXAMPLE_FLAG__MSG_A更能望文生义,便于人类理解
关于事件队列的消费者:
首先消费者的任务应当比生产者任务高,必竟是先有市场消费,才有工厂生产。这个思维要理解就OK
tn_eventgrp_modify( &que_example_events, //-- 要调整的事件 TN_EVENTGRP_OP_SET, //-- 事件调整为SET QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT//-- 状态的旗号__消费者已经初始化OK );
modify函数为调整events事件状态为OP_SET, OP_SET相当于使能events事件,OP_CLEAR为清除事件,当成中断源开启和关闭就可以。
QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT表示消费者任务已经初始化完成。
//-- 等待消息,可能永远等待 enum TN_RCode rc = tn_eventgrp_wait( &que_example_events, //-- 取事件 (QUE_EXAMPLE_FLAG__MSG_A), //-- 队列旗号QUE_EXAMPLE_FLAG__MSG_A TN_EVENTGRP_WMODE_OR, //-- OR模式 &flags_pattern, //-- 指向实际事件模式的 `TN_UWord` 变量的指针 TN_WAIT_INFINITE //-- 超时 );
_MSG_A前面指定的队列旗号,_OR模式不是很理解,flags_pattern就是事件创建时的(1<<31)bit位。 大意就是tn_eventgrp_wait函数,一直等待_events的产生。
一旦产生了_events,再进一步确认事件源是不是QUE_EXAMPLE_FLAG__MSG_A。也可以邦定了三个队列,可能像这样:
(QUE_EXAMPLE_FLAG__MSG_A)|(QUE_EXAMPLE_FLAG__MSG_B)|(QUE_EXAMPLE_FLAG__MSG_B), //-- 队列旗号QUE_EXAMPLE_FLAG__MSG_A _B _C
关于事件队列的生产者:
tn_eventgrp_wait( //-- 等待消费者队列初始化完成,有消费才进行生产,附合正常思维 &que_example_events, QUE_EXAMPLE_FLAG__TASK_CONSUMER_INIT, TN_EVENTGRP_WMODE_AND, TN_NULL, TN_WAIT_INFINITE );
生产者任务,应先等待消费者任务的初始化完成。tn_eventgrp_wait上文讲过了,回访阅读便可。_AND还是不理解其作用,等待模式有AND OR AUTOCLR,是对多个旗帜号进行运算??
tn_eventgrp_modify( //-- 调整事件状态,生产者已初始化OK &que_example_events, TN_EVENTGRP_OP_SET, QUE_EXAMPLE_FLAG__TASK_PRODUCER_INIT );
上文也讲过modify,表示生产者初化OK!!
tn_rc = tn_is_task_context() //-- 将其发送给消费者任务 ? tn_queue_send(&cons_que, (void *)&p_msg, WAIT_TIMEOUT) : tn_queue_isend_polling(&cons_que, (void *)&p_msg);
发送队列信息,这样消费者可以收到事件。
时间:20220404
事件组绝对是强大的存在,此前的测试例程只是一个队列事件,还有很多没有测试。
事件由TN_UWord进行描述,正如之前的(1<<31)bit作为一个事件,这个事件把队列联接在一起,所以只要向队列丢数据就会产生对应的事件
很多OS对像可以产生事件,如下图:
因此,事件组是最后一道工序,也就是说其他的队列互斥等等都初始化OK后,最后进行事件链接,而且还可以无限嵌套。
时间:20220405
最简单事件测试,这次没用到队列,看下面代码:
//-- 事件组 static struct TN_EventGrp example_events; tn_eventgrp_create(&example_events, (1<<30)); //-- 初始事件模式TN_UWord:30bit //-- c task 优先级--低 void task_c_body(void *par) { printf("New task c is OK!\n"); tn_eventgrp_modify( &example_events, //-- 事件30bit TN_EVENTGRP_OP_SET, //-- 事件调整为SET,就是打开可以上下文转换 0X0100//-- 状态的号_0x0100 ); for(;;) { tn_task_sleep(100); } } //-- d task 待用任务 优先级--高 void task_d_body(void *par) { printf("New task d is OK!\n"); TN_UWord flags_pattern = 0; for(;;) { enum TN_RCode rc = tn_eventgrp_wait( &example_events, //-- 取事件 0x0100, //-- 状态号 TN_EVENTGRP_WMODE_OR, //-- OR模式 &flags_pattern, //-- 指向实际事件模式的 `TN_UWord` 变量的指针 TN_WAIT_INFINITE //-- 超时 ); if (rc == TN_RC_OK){ if (flags_pattern & 0x0100){ // 如果是tim_example_events事件,又是0x0100状态 printf("example_events TEST OK!!\n");// 打印测试信息 } } } }
简单原理就是(1<<30)bit事件,等待0x0100状态,你可以在任意时间和地方通过tn_eventgrp_modify进行设置状态号,
对方通过tn_eventgrp_wait等待该事件的对应状态。确实可达到任务同步的目的