沁恒微 蓝牙 芯片 TMOS 使用
发布时间:2026/9/25 2:37:01来源:尧图网络
学习沁恒微蓝牙工程中的 TMOS 使用 ...... by 矜辰所致 ...... 细节框架更新增加 ms 延时说明 2026/1/14 ...... 增加 TMOS API 函数记录 2026/4/11前言在沁恒微蓝牙芯片 EVT 中针对蓝牙应用有一个任务管理系统TMOS可以理解为一个极简的 “ 操作系统 ” 作为调度核心处理多任务切换。要使用沁恒微 RISC-V 蓝牙芯片跑蓝牙就得要学会使用 TMOS所以本文我们就来学习一下TMOS 的使用。推荐博文沁恒 蓝牙 TMOS 调度机制.我是矜辰所致全网同名尽量用心写好每一系列文章不浮夸不将就认真对待学知识的我们矜辰所致金石为开目录前言一、 基础介绍1.1 TMOS 需要初始化吗1.2 使用流程二、 使用说明2.1 使用示例2.1.1 任务注册2.1.2 启动任务2.1.2.1 ms 延时设置2.1.3 事件处理2.2 TMOS API 接口2.3 新建示例测试2.4 消息传递三、 注意事项3.1 TMOS 支持的休眠时间3.2 其他说明结语一、 基础介绍在官方 EVT 里面的 BLE 例程文件夹下面有一份《沁恒低功耗蓝牙软件开发参考手册》 里面有关于 TMOS 的基本使用说明介绍用户通过注册任务Task将自定义的事件Event添加到 TMOS 的任务链表中由TMOS进行调度运行。每个 Task 用户最多可以自定义15个事件0x8000 为系统预留的消息传递固定的每个任务都有不可以自己定义每个任务通过一个 16 位的变量区分不同的事件如果某一位置位表示事件运行了。简单来说就是一个任务 TASK 可以管理 15 件自定义的事情而且可以建立多个任务。一般是把同功能或者相近的事情 放到同一个任务下处理。比如对于 某一个 TASK 他的事件定义如下/* hal task Event */#defineLED_BLINK_EVENT0x0001#defineHAL_KEY_EVENT0x0002#defineHAL_REG_INIT_EVENT0x2000#defineHAL_TEST_EVENT0x4000还有消息传递事件是所有任务都固定的#defineSYS_EVENT_MSG(0x8000)// A message is waiting event具体地理解还得结合我们下文的应用我们本文会按照流程来测试一遍。1.1 TMOS 需要初始化吗这里需要单独说明一下TMOS 任务是专门针对蓝牙而做出的系统只有在BLE_LibInit()之后才可以正常的注册任务正常操作。在蓝牙例程中BLE_LibInit()都放在了CH58x_BLEInit函数中在工程最开始的部分不跑蓝牙想用 TMOS 也需要初始化BLE_LibInit()需要用到蓝牙库。1.2 使用流程TMOS 使用的流程如下系统初始化包括CH58X_BLEInit()内部调用BLE_LibInit()用于初始化蓝牙协议栈任务注册通过TMOS_ProcessEventRegister()注册任务这一步必须在 BLE_LibInit() 之后否则注册会失败返回 0xFF错误示例:halTaskID TMOS_ProcessEventRegister(HAL_ProcessEvent);启动任务事件使用tmos_start_task添加任务。示例:tmos_start_task(halTaskID, HAL_REG_INIT_EVENT, 800);事件处理逻辑在HAL_ProcessEvent中实现事件触发的处理逻辑。主循环中调用TMOS_SystemProcess()这是 TMOS 任务调度器的核心函数必须持续调用系统才能轮询并执行任务事件。二、 使用说明在官方文档《沁恒低功耗蓝牙软件开发参考手册》中也有所有关于 TMOS 的 API 的说明大家根据文档说明以及结合示例学习效果才是最好。在实际应用中我们都是建议在官方例程上去修改在应用层做自己的应用所以一些初始化的东西其实根据官方的初始化框架即可我们基本不需要修改。我们这里重点在于初始化完成以后大家如何应用 TMOS。2.1 使用示例TMOS 的相关函数都是在蓝牙库CH58xBLE_LIB.h中声明的。我们根据使用流程介绍需要用到的 API 函数 。2.1.1 任务注册返回的tmosTaskID就是一个uint8_t数据类型0xFF 为错误其他为任务 ID 参数为回调函数指针。//typedef uint8_t tmosTaskID;//return 0xFF - error,others-task idexterntmosTaskIDTMOS_ProcessEventRegister(pTaskEventHandlerFn eventCb);示例从机中现成的任务//Peripheral_TaskID 默认初始化为 0xFF 如果成功就变成其他的不是 FFPeripheral_TaskIDTMOS_ProcessEventRegister(Peripheral_ProcessEvent);2.1.2 启动任务启动任务 就是 开启任务中定义好的任务事件1个任务里面可以有15个自定义的事件。有2个函数可以启动任务事件一个是立即启动一个是延时 time*625μs 后启动分别使用下面的函数//立即启动taskID任务中对应的event事件调用一次执行一次。bStatus_ttmos_set_event(tmosTaskID taskID,tmosEvents event);//延迟time*625μs后启动taskID任务中对应的event事件调用一次执行一次。BOOLtmos_start_task(tmosTaskID taskID,tmosEvents event,tmosTimer time);上面延时为什么是 time*625μs 因为 TMOS 系统时钟单位为 625us。任务调用一次执行一次如果是循环的任务需要再重新开启任务。示例在从机SBP_START_DEVICE_EVT后面加上自己测试的事件。 #defineSBP_START_DEVICE_EVT0x0001#defineSBP_PERIODIC_EVT0x0002#defineSBP_READ_RSSI_EVT0x0004#defineSBP_PARAM_UPDATE_EVT0x0008#defineSBP_PHY_UPDATE_EVT0x0010#defineMY_TEST_EVT0x0400#defineRTC_EVT0x0020...tmos_set_event(Peripheral_TaskID,SBP_START_DEVICE_EVT);tmos_start_task(Peripheral_TaskID,RTC_EVT,1600);tmos_set_event(Peripheral_TaskID,MY_TEST_EVT);2.1.2.1 ms 延时设置对于演示启动的时间是参数 time*625μs 在 EVT 中还提供了设置成 ms 的方式相关定义以及使用方式如下#defineMS1_TO_SYSTEM_TIME(x)((x)*1000/SYSTEM_TIME_MICROSEN)// transform unit in ms to unit in 625us ( attentional bias )tmos_start_task(Peripheral_TaskID,MY_UART_TEST_EVT,MS1_TO_SYSTEM_TIME(100));//100ms后执行2.1.3 事件处理在 任务注册 的时候参数指向回调函数我们的事件处理都是在回调函数中执行的框架如下其中SYS_EVENT_MSG是系统预留的系统消息传递事件每个任务都有的且都是固定的消息是一个带有数据的事件用于协议栈各层之间传递数据支持同时添加多个消息。上面我们直接在从机例程的基础上新建一个周期打印的事件做测试写法结构就根据示例来就行。我们来看一下测试效果为了更直观我关闭了从机收到扫描打印的数据部分测试效果每秒打印一次测试数据2.2 TMOS API 接口上文说过的那份 《沁恒低功耗蓝牙软件开发参考手册》手册里面有全部 API 说明为了方便自己查询把上面讲到过的和其他可能用到的记录一下//注册事件回调函数一般用于注册任务时首先执行。tmosTasklDTMOS_ProcessEventRegister(pTaskEventHandIerFn eventCb)//立即启动taskID任务中对应的event事件调用一次执行一次。bStatus_ttmos_set_event(tmosTaskID taskID,tmosEvents event)//延迟time*625μs后启动taskID任务中对应的event事件调用一次执行一次。bStatus_ttmos_start_task(tmosTaskID taskID,tmosEvents event,tmosTimer time)/* 延迟time*625μs 执行event 事件调用一次循环执行除非运行tmos_stop_task 关掉。 注意上面start 是调用一次执行一次这里是调用一次循环执行 */bStatus_ttmos_start_reload_task(tmosTaskID taskID,tmosEvents event,tmosTimer time)//停止一个event事件调用此函数后该事件将不会生效。bStatus_ttmos_stop_event(tmosTaskID taskID,tmosEvents event)//清理一个已经超时的event事件注意不能在它自己的event函数内执行。bStatus_ttmos_clear_event(tmosTaskID taskID,tmosEvents event)还有一些在 TMOS 下实现的 C 语言库函数功能的函数较C语言库函数更加节省内存空间//生成伪随机数。uint32_ttmos_rand(void)//把存储区src1和存储区src2的前len个字节进行比较。booltmos_memcmp(constvoid*src1,constvoid*src2,uint32_tlen)//比较给定数据是否都是给定数值。booltmos_isbufset(uint8_t*buf,uint8_tval,uint32_tlen)//计算字符串pString的长度直到空结束字符但不包括空结束字符。uint32_ttmos_strlen(char*pString)//复制字符Value到参数pDst所指向的字符串的前len个字符。voidtmos_memset(void*pDst,uint8_tValue,uint32_tlen)//从存储区src复制len个字节到存储区dst。voidtmos_memcpy(void*dst,constvoid*src,uint32_tlen)关于tmos_memcpy效率测试可参考博文https://www.cnblogs.com/JayWellsBlog/p/18640330 下图来自此博文说明请查看原文仅供参考2.3 新建示例测试本来在上面已经展示过使用示例只不过是在从机示例上面修改但是想要测试下消息传递不想影响蓝牙从机任务的消息接收所以想想还是单独写一个简单的示例把不跑蓝牙只测试 TMOS。这里直接在 main.c 里面实现就行了这里代码和下一小节消息传递一起放直接往下面看就行了省的重复代码。2.4 消息传递好了上面搭建好示例我们来测试一下2个任务之间的消息传递这里直接上一下代码看完代码和效果再说注意点#includeCONFIG.h#includeHAL.h#includeobserver.h__attribute__((aligned(4)))uint32_tMEM_BUF[BLE_MEMHEAP_SIZE/4];#if(defined(BLE_MAC))(BLE_MACTRUE)constuint8_tMacAddr[6]{0x84,0xC2,0xE4,0x03,0x02,0x02};#endif#definemessage_len20#defineTASK1_TEST_EVENT0x0001#defineTASK1_SEND_EVENT(0x00011)#defineTASK2_TEST_EVENT0x0001staticuint8_tMYTEST1_TaskIDINVALID_TASK_ID;// Task ID for internal task/event processingstaticuint8_tMYTEST2_TaskIDINVALID_TASK_ID;// Task ID for internal task/event processing__HIGH_CODE__attribute__((noinline))voidMain_Circulation(){while(1){TMOS_SystemProcess();}}uint16_tTask1_ProcessEvent(uint8_ttask_id,uint16_tevents){// VOID task_id; // TMOS required parameter that isnt used in this functionif(eventsSYS_EVENT_MSG){uint8_t*msg;uint8_treceived_message[message_len];msgtmos_msg_receive(MYTEST1_TaskID);// 接收消息if(msg!NULL){tmos_memcpy(received_message,msg,message_len);// 将消息内容复制到本地缓冲区tmos_msg_deallocate(msg);// 释放消息所占用的内存空间PRINT(111 Received message: %s\r,received_message);// 打印接收到的消息内容}else{PRINT(111 receive error\r\n);}return(events^SYS_EVENT_MSG);}if(eventsTASK1_TEST_EVENT){PRINT(11111,goto task1 send event..\r\n);tmos_start_task(MYTEST1_TaskID,TASK1_SEND_EVENT,1600);return(events^TASK1_TEST_EVENT);}if(eventsTASK1_SEND_EVENT){PRINT(111 send msg to task2 ..\r\n);PRINT(111 back to task1 test event ..\r\n);uint8_t*msg;uint8_tmessage[message_len]Hello, TMOS!;msgtmos_msg_allocate(message_len);// 申请内存空间if(msg!NULL){tmos_memcpy(msg,message,message_len);// 复制消息内容到申请的内存空间tmos_msg_send(MYTEST2_TaskID,msg);// 发送消息到接收任务/* 可以给自己发送但是不能同时发送2个任务 取消注释不会报错,但是只有 TASK2 能够收到消息 */// tmos_msg_send( MYTEST1_TaskID, msg );PRINT(111 send OK\r\n);}else{PRINT(111 send error\r\n);}tmos_start_task(MYTEST1_TaskID,TASK1_TEST_EVENT,1600);return(events^TASK1_SEND_EVENT);}// Discard unknown eventsreturn0;}uint16_tTask2_ProcessEvent(uint8_ttask_id,uint16_tevents){// VOID task_id; // TMOS required parameter that isnt used in this functionif(eventsSYS_EVENT_MSG){uint8_t*msg;uint8_treceived_message[message_len];msgtmos_msg_receive(MYTEST2_TaskID);// 接收消息if(msg!NULL){tmos_memcpy(received_message,msg,message_len);// 将消息内容复制到本地缓冲区tmos_msg_deallocate(msg);// 释放消息所占用的内存空间PRINT(222 Received message: %s\r,received_message);// 打印接收到的消息内容}else{PRINT(222 receive error\r\n);}return(events^SYS_EVENT_MSG);}if(eventsTASK2_TEST_EVENT){PRINT(22222\r\n);tmos_start_task(MYTEST2_TaskID,TASK2_TEST_EVENT,1600);return(events^TASK2_TEST_EVENT);}// Discard unknown eventsreturn0;}intmain(void){HSECFG_Capacitance(HSECap_18p);SetSysClock(SYSCLK_FREQ);GPIOA_SetBits(GPIO_Pin_14);GPIOPinRemap(ENABLE,RB_PIN_UART0);GPIOA_ModeCfg(GPIO_Pin_15,GPIO_ModeIN_PU);GPIOA_ModeCfg(GPIO_Pin_14,GPIO_ModeOut_PP_5mA);UART0_DefInit();PRINT(%s\n,VER_LIB);CH58x_BLEInit();HAL_Init();MYTEST1_TaskIDTMOS_ProcessEventRegister(Task1_ProcessEvent);MYTEST2_TaskIDTMOS_ProcessEventRegister(Task2_ProcessEvent);tmos_start_task(MYTEST1_TaskID,TASK1_TEST_EVENT,1600);tmos_start_task(MYTEST2_TaskID,TASK2_TEST_EVENT,1600);Main_Circulation();}/******************************** endfile main ******************************/测试效果上面代码大家直接可以搬过去运行就是注意接收函数tmos_msg_receive的参数要搞清楚是填写的要接收消息的任务 ID 三、 注意事项老规矩本小节为补充说明会参考博主自己测试前人使用经验、沁恒微官方论坛等等保持更新。3.1 TMOS 支持的休眠时间TMOS 任务设置时间最大为 23.5 小时。最小休眠时间官方设置的大概在 1ms 但是实际上晶振起振需要大概 2ms 左右所以还是建议最小间隔长一点至于至少几 ms 起可以用博主没有测试因为实际使用也不可能会有这么短时间的任务周期执行大家感兴趣可以自行测试。这个时间怎么来的通过如下路径CH58x_BLEInit- 当开启了休眠会有一个类似空闲回调函数的操作会执行cfg.idleCB CH58x_LowPower;。这个CH58x_LowPower函数就是 TMOS 休眠时候调用的函数在这个函数内部有如下代码/若睡眠时间小于最小睡眠时间或大于最大睡眠时间则不睡眠if((time_sleepSLEEP_RTC_MIN_TIME)||(time_sleepSLEEP_RTC_MAX_TIME)){SYS_RecoverIrq(irq_status);return2;}里面定义了最小休眠时间和最大休眠时间大家可以自己跳转查看我把有关的宏定义都放出来#if(CLK_OSC32K1)#defineFREQ_RTC32000#else#defineFREQ_RTC32768#endif#defineRTC_MAX_COUNT0xA8C00000#defineCLK_PER_US(1.0/((1.0/FREQ_RTC)*1000*1000))#defineUS_TO_RTC(us)((uint32_t)((us)*CLK_PER_US0.5))#defineSLEEP_RTC_MIN_TIMEUS_TO_RTC(1000)#defineSLEEP_RTC_MAX_TIME(RTC_MAX_COUNT-1000*1000*30)最终算下来 SLEEP_RTC_MIN_TIME 33SLEEP_RTC_MAX_TIME 2 770 000 000这个值是 RTC 时钟周期数32768 Hz 时钟的 tick 数。换算成真实时间33 / 32768 ≈ 1.01 ms2 770 000 000 / 32768 ≈ 84 555 s ≈ 23.5 h3.2 其他说明TMOS 系统时钟单位为 625us是以 RTC 为基准得到的时间。不能在中断中调用 TMOS 函数。TMOS 和 低功耗蓝牙的协议栈 使用同一块分配的内存任务的分配是需要消耗内存的蓝牙协议栈用掉一些以后剩余的是 TMOS 可以用的。所以它的任务注册是有限制的主要和芯片的内存有关。有位大佬个人测试过CH592 支持注册 25 个任务左右CH585 支持注册 64 个任务 0~63。个人测试只做参考实际应用当然用不了那么多也不建议那么多。使用蓝牙过程中不要在单个 TMOS 任务中执行超过连接间隔一半时长的任务否则将影响蓝牙通讯。连接间隔理解参考博文BLE 蓝牙连接参数详解同理在中断中也一样不要执行超过连接间隔一半时长的任务。在事件生效执行的代码中调用延时执行函数。tmos_start_task时延时时间以当前事件生效时间点为基准偏移。所以对调用延时执行函数tmos_start_task在生效执行的代码中摆放的位置没有要求。结语本文我们介绍了 TMOS 的使用对于应用来说它可以说是很简便的使用流程和示例文中也做了比较详细的说明。官方使用 TMOS 把很多复杂管理和调度都已经实现好了包括我们在使用蓝牙低功耗休眠的时候也可以靠 TMOS 自动管理相当的方便。除了文中用到的 API在还有一些比如tmos_memcmp、tmos_memcpy的函数较C语言库函数更加节省内存空间 大家可自行查看官方《沁恒低功耗蓝牙软件开发参考手册》就在下载的 EVT 包的 BLE 例程目录下面。另外强烈建议大家看一看博主的另外一篇测评文章 沁恒 蓝牙 TMOS 调度机制 可以让大家更好的理解 TMOS 的使用 。好了本文就到这里。谢谢大家
网站建设高端定制企业官网