沁恒 蓝牙 TMOS 调度机制
测试说明一下 TMOS 的调度机制 ...... 矜辰所致
前言
在之前我们学习过 TMOS 的使用,随着我们学习使用的深入,在实际应用中,为了保证某些任务/事件的执行顺序和实时性,我们开始考虑 TMOS 的调度是怎样的一个逻辑:
- 不同任务的不同事件同时置位的执行顺序?
- 同一个任务的不同事件同时置位的执行顺序?
- 一次轮询执行会执行几个事件?
- 在主循环中,加入其他进操作,对 TMOS 轮询有啥影响?
所以本文我们来测试说明一下 TMOS 调度的一些问题。
相关博文:
沁恒微 蓝牙 芯片 TMOS 使用
.
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!
一、基础说明
TMOS 的基础使用 请参考 沁恒微 蓝牙 芯片 TMOS 使用 。
使用 TMOS 调度器,会在主循环里面一直轮询:
__HIGH_CODE
__attribute__((noinline))
void Main_Circulation()
{
while(1)
{
TMOS_SystemProcess();
}
}
只有开始执行 TMOS_SystemProcess() ,才会开始执行任务,这个函数就表示 TMOS 轮询一次,根据任务的优先级(先注册的任务会有更高的优先级)去执行任务,当然也包括蓝牙协议栈的核心任务(蓝牙协议栈的任务应该是最高优先级,这个是库内部实现的事情)。
需要知道的是,TMOS 的阻塞运行的,它一定是执行完了某个任务的事件之后,再进行下一个任务的执行。 所以之前文章我们说到,不要在单个 TMOS 任务中执行超过连接间隔一半时长的任务,就是因为如果阻塞时间过长,就会影响蓝牙通讯。
问题:对于本文来说,我们还要搞清楚,如果我一个优先级高的任务 a ,我执行完了一个事件 1 返回以后,我还有一个事件 2 也置位了,它会继续执行优先级高的任务 a 里面的事件 2,还是会去执行低优先级 b 里面置位的任务。
二、测试说明
我们知道 TMOS 会轮询,但是为了搞清任务执行的顺序 和 一次执行的数量 这些相关的问题 ,我们可以来进行一些测试。
2.1 测试一(任务执行顺序和一次轮询执行多少任务)
思路如下:
1、主循环中加入其他操作一直执行
2、然后使用两个任务a,b,
3、每个任务设置 2 个事件, a1,a2,b1,b2
4、在初始化的时候,就直接把这 4 个事件置位(就等于 TMOS 在一次扫描的时候,这两个任务都有事件需要执行。)
5、观察执行效果
其实通过这个测试可以说明很多问题,我们先看测试代码:
#define a1_EVENT 0x0001
#define a2_EVENT (0x0001<<1)
#define b1_EVENT 0x0001
#define b2_EVENT (0x0001<<1)
static uint8_t TASK_a_ID = INVALID_TASK_ID; // Task ID for internal task/event processing
static uint8_t TASK_b_ID = INVALID_TASK_ID; // Task ID for internal task/event processing
uint16_t Task_a_ProcessEvent(uint8_t task_id, uint16_t events)
{
if(events & SYS_EVENT_MSG){
return (events ^ SYS_EVENT_MSG);
}
if(events & a1_EVENT){
PRINT("a1 run\n");
return (events ^ a1_EVENT);
}
if(events & a2_EVENT){
PRINT("a2 run\n");
return (events ^ a2_EVENT);
}
return 0;
}
uint16_t Task_b_ProcessEvent(uint8_t task_id, uint16_t events)
{
if(events & SYS_EVENT_MSG){
return (events ^ SYS_EVENT_MSG);
}
if(events & b1_EVENT){
PRINT("b1 run\n");
return (events ^ b1_EVENT);
}
if(events & b2_EVENT){
PRINT("b2 run\n");
return (events ^ b2_EVENT);
}
return 0;
}
__HIGH_CODE
__attribute__((noinline))
void Main_Circulation()
{
while(1)
{
TMOS_SystemProcess();
PRINT("AAA!\n");
}
}
int main(void)
{
//前面神略...
PRINT("%s\n", VER_LIB);
CH58x_BLEInit();
HAL_Init();
TASK_a_ID = TMOS_ProcessEventRegister(Task_a_ProcessEvent);
TASK_b_ID = TMOS_ProcessEventRegister(Task_b_ProcessEvent);
PRINT("TASK_a_ID : %d\n", TASK_a_ID);
PRINT("TASK_b_ID : %d\n", TASK_b_ID);
tmos_set_event(TASK_b_ID,b2_EVENT);
tmos_set_event(TASK_b_ID,b1_EVENT);
tmos_set_event(TASK_a_ID,a2_EVENT);
tmos_set_event(TASK_a_ID,a1_EVENT);
// GAPRole_PeripheralInit();
// Peripheral_Init();
Main_Circulation();
}
大家可以先 猜测一下程序运行结果, a1,b1,a2,b2, 和 AAA 会怎么运行 。
测试结果如下:

如果我们把注册 ID 的语句调换一下:
TASK_b_ID = TMOS_ProcessEventRegister(Task_b_ProcessEvent);
TASK_a_ID = TMOS_ProcessEventRegister(Task_a_ProcessEvent);
测试结果如下:

测试小结
通过这个测试结果,我们可以回答之前的几个问题:
-
一次
TMOS_SystemProcess();只会进入一个任务的回调函数 。
注意,这里为什么不说一次只执行一个事件,是因为执行几个事件不是应用层自己的业务逻辑,并不是 TMOS 决定的,TMOS 发现有任务事件置位了,就会进入该任务的回调函数,函数里面怎么执行,是应用层自己写的,注意我们在每个事件执行里面都写了return语句!
一个任务回调函数返回了,TMOS 跳转到下一个任务的时候会暂时释放控制权,在主循环里打印了AAA 。 -
先注册的任务优先级更高(注册回调函数时返回 ID 值越小),TMOS 最开始执行的时候,按照优先级的顺序执行,但是执行返回以后,TMOS 不会从新遍历整个任务列表,它会往下面轮询其他的任务,即便刚刚执行完的任务有更高优先级并且还有事件置位,它也会去找下一个任务。
-
主函数中加入其他操作,会在 TMOS 一次轮询结束之后,顺序执行。
2.1 测试二(事件执行顺序和数量说明)
事件的执行其实完全是应用层面的事情,如下语句:
if(events & b1_EVENT){
PRINT("b1 run\n");
return (events ^ b1_EVENT);
}
每次执行完了就退出返回,如果我们执行完事件继续让他置位,那么它后面的事件永远不会执行。
关于这个点 ,直接看程序很好理解,只不过有很多伙伴因为 TMOS 的关系,所以会想着,这些事件有没有优先级,同一个任务中事件与事件之间会不会抢占之类的问题,往往就忽略了,这只是一个正常的回调函数,一个普通的程序逻辑。
我们也可以做个简单的测试,在上面测试基础上添加:
1、主循环中加入其他操作一直执行
2、然后使用两个任务a,b,
3、每个任务设置 2 个事件, a1,a2,b1,b2
4、在初始化的时候,就直接把这 4 个事件置位(就等于 TMOS 在一次扫描的时候,这两个任务都有事件需要执行。)
5、在每一个事件执行代码中,依然把本事件置位。
6、观察执行效果
测试代码(其他地方不变,):
uint16_t Task_a_ProcessEvent(uint8_t task_id, uint16_t events)
{
if(events & SYS_EVENT_MSG){
return (events ^ SYS_EVENT_MSG);
}
if(events & a1_EVENT){
PRINT("a1 run\n");
tmos_set_event(TASK_a_ID,a1_EVENT);
return (events ^ a1_EVENT);
}
if(events & a2_EVENT){
PRINT("a2 run\n");
tmos_set_event(TASK_a_ID,a2_EVENT);
return (events ^ a2_EVENT);
}
return 0;
}
uint16_t Task_b_ProcessEvent(uint8_t task_id, uint16_t events)
{
if(events & SYS_EVENT_MSG){
return (events ^ SYS_EVENT_MSG);
}
if(events & b1_EVENT){
PRINT("b1 run\n");
tmos_set_event(TASK_b_ID,b1_EVENT);
return (events ^ b1_EVENT);
}
if(events & b2_EVENT){
PRINT("b2 run\n");
tmos_set_event(TASK_b_ID,b2_EVENT);
return (events ^ b2_EVENT);
}
return 0;
}
测试结果如下:

结果很好理解,因为每次进入任务回调,写在前面的事件先执行,然后如果执行完毕置位了,下一次轮询到自己依旧还是执行写在前面的 。
我们可以想象,如果我们在每个事件处理程序中,我们不 return ,我们可以把事件全部执行,比如如下代码:
uint16_t Task_a_ProcessEvent(uint8_t task_id, uint16_t events)
{
if(events & SYS_EVENT_MSG){
return (events ^ SYS_EVENT_MSG);
}
if(events & a1_EVENT){
PRINT("a1 run\n");
tmos_set_event(TASK_a_ID,a1_EVENT);
// return (events ^ a1_EVENT);
events ^= a1_EVENT;
}
if(events & a2_EVENT){
PRINT("a2 run\n");
tmos_set_event(TASK_a_ID,a2_EVENT);
// return (events ^ a2_EVENT);
events ^= a2_EVENT;
}
return events; // 统一返回剩下未处理的事件比较合理
}
uint16_t Task_b_ProcessEvent(uint8_t task_id, uint16_t events)
{
if(events & SYS_EVENT_MSG){
return (events ^ SYS_EVENT_MSG);
}
if(events & b1_EVENT){
PRINT("b1 run\n");
tmos_set_event(TASK_b_ID,b1_EVENT);
// return (events ^ b1_EVENT);
events ^= b1_EVENT;
}
if(events & b2_EVENT){
PRINT("b2 run\n");
tmos_set_event(TASK_b_ID,b2_EVENT);
// return (events ^ b2_EVENT);
events ^= b2_EVENT;
}
return events;
}
测试结果如下:

和说明的一样,通过这种方式,我们可以实现在一次轮询中实现多个事件同时执行(前提是这些事件都置位了),当然我们也可以选择性的只选众多事件中的某几个同时执行,也可以控制我们事件的先后执行顺序。
测试小结
通过这个测试结果,我们又可以来回答几个问题:
-
一次轮询能够执行的事件,是可以通过应用层的代码控制的,可以一次执行多个事件。
但是一次轮询只会进一次任务的回调函数。 -
事件的执行循序也是纯应用层代码控制,哪个
if(events & )写在前面,就先执行哪一个。
三、总结说明
本小节总结一下 测试的结论以及 一些对应的说明,如果对下面说明理解不了,还是需要去看一下上文的测试过程,上面每个测试后面都有小结说明,内容说明与上文小节会存在重复的说明,大家可以加深下印象(个人总结,有错误指出)。
-
TMOS 调度器只认任务,而不会去认事件,事件是任务自己的内部处理的:
对调度器来说,每个任务只有一个 16 位的events变量。调度器看到这个变量不为 0,就直接把整个events传给该任务的注册回调函数,剩下的事全由任务自己内部处理。 -
先注册的任务优先级更高(注册回调函数时返回 ID 值越小),TMOS 最开始执行的时候,按照优先级的顺序执行。
-
程序中每执行一次
TMOS_SystemProcess();TMOS 进行轮询一次, 每轮询一次只执行一个任务(只进一个任务的回调函数)。 -
TMOS 每次轮询,并不是遍历所有的任务,在执行完某个任务的时候,它的 ”指针 “ 指向了下一个有事件需要执行的任务。
-
TMOS 一次调度 执行事件数量和先后顺序取决于你自己的业务逻辑需求,可以一次执行多个事件。
-
蓝牙任务内部也会有 任务 ID(ID 号最小,这个是库内部处理的事情,理论上来说应该是这样),蓝牙任务的执行也是和上面一样的 TMOS 轮询逻辑
如果加上蓝牙做这个测试,可能需要考虑到 TMOS 轮询到蓝牙任务时候的一些情况。比如博主个人测试的时候,有时候连续打印了2个AAA,才执行应用层自己的测试任务。 -
TMOS 任务是阻塞操作,需要注意程序运行的时间。和之前说明的一样,不要在单个 TMOS 任务中执行超过连接间隔一半时长的任务,防止影响蓝牙通信。(这点通过我们上面的学习应该也很好理解,蓝牙操作也是 TMOS 的一个任务,其他任务阻塞,执行不到蓝牙任务。)
-
主循环中加入其他操作,会在 TMOS 一次轮询结束之后,顺序执行,在实际应用中,可以把自己的业务逻辑放在主循环,但是这样不受 TMOS 管理,跑蓝牙相比之下容易影响蓝牙的时间线,需要特别注意自己业务的时间,不要阻塞太久,影响蓝牙通信 。
-
实际应用中,如果有阻塞时间比较长的操作,可以拆分成多个小任务,同理,如果主循环中自己的其他业务有阻塞时间比较长的操作,也是拆分的思想,可以在自己其他业务逻辑中合理穿插
TMOS_SystemProcess();使得期间可以进行 TMOS 调度,保证蓝牙通信。 -
还是为了避免阻塞时间过长,可以把代码放在 RAM 中运行,增加程序运行速率。
在代码之前使用__HIGH_CODE修饰(但是也要注意使用芯片的 RAM 空间是否够用)。 -
其他一些基础说明,请参考 沁恒微 蓝牙 芯片 TMOS 使用 。
结语
本文我们探讨了一下 TMOS 的调度机制,希望通过本文的一些测试,能让大家更加理解任务事件关系以及 TMOS 的调度机制,对大家今后使用 TMOS 有一定帮助。
好了,本文就到这里。谢谢大家!
更多推荐



所有评论(0)