1、TMOS簡介
TMOS是沁恆微電子針對藍牙協議棧開發的“操作系統”,是簡化版的OSAL(Operating System Abstraction Layer),即操作系統抽象層,一種以實現多任務為核心的系統資源管理機制。簡單而言,TMOS實現了類似操作系統的某些功能,但並不能稱之為真正意義上的操作系統。
2、TMOS工作機制分析
TMOS是通過時間片輪詢的方式實現多任務調度運行,實際上每次只有一個任務運行。系統時鍾來源於芯片RTC,單位為625us。
用戶通過注冊任務(Task)將自定義的事件(Event)添加到TMOS的任務鏈表中,由TMOS進行調度運行。
每個Task注冊后分配一個ID;每個Task最多包含16個Event,其中包括1個消息事件(0x8000)和15個自定義事件,采用BitMap的方式定義事件標志,如:(0x0001、0x0002、0x0004……0x8000)。
Event事件標志位,為1則運行,為0則不運行。
bit0 | bit1 | bit2 | bit3 | bit4 | bit5 | bit6 | bit7 | bit8 | bit9 | bit10 | bit11 | bit12 | bit13 | bit14 | bit15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 | 0/1 |
TMOS任務鏈表如下:
TMOS循環查詢任務鏈表,根據任務ID確定優先級,越小越高。每個任務運行完一個事件后便通過異或的方式清除已運行的事件,同時return未運行的事件標志,接着運行下一個任務;當任務調度系統運行一遍后,再次回來運行任務鏈表頭的一個事件,如此循環下去。
TMOS調度機制如下:
3、常用API分析
//注冊任務,傳入任務事件回調函數,返回任務ID。
tmosTaskID TMOS_ProcessEventRegister( pTaskEventHandlerFn eventCb );
//立即啟動taskID任務中對應的event事件,事件只執行一次
bStatus_t tmos_set_event( tmosTaskID taskID, tmosEvents event );
//定時time*625us后啟動taskID任務中對應的event事件,事件只執行一次
bStatus_t tmos_start_task( tmosTaskID taskID, tmosEvents event, tmosTimer time );
//停止一個定時事件
bStatus_t tmos_stop_task( tmosTaskID taskID, tmosEvents event );
//清理一個已經超時的event事件,不能在自己的event函數內執行
bStatus_t tmos_clear_event( tmosTaskID taskID, tmosEvents event );
//開始一個定時事件,不斷的執行,除非運行tmos_stop_task關掉,
bStatus_t tmos_start_reload_task( tmosTaskID taskID, tmosEvents event, tmosTimer time );
//獲取對應taskID 和event 的最后一個周期時長,返回0是沒有找到
tmosTimer tmos_get_task_timer( tmosTaskID taskID, tmosEvents event );
//TMOS時鍾初始化
bStatus_t TMOS_TimerInit( pfnGetSysClock fnGetClock );
//返回tmos系統運行時長,單位為625us,如1600=1s
uint32_t TMOS_GetSystemClock( void );
//tmos的系統處理函數,需要不斷在主函數中運行
void TMOS_SystemProcess( void );
/**************消息相關*************/
//發送消息函數,參數為消息想要發送到哪一層的taskID以及消息指針。當調用此函數時,對應參數taskID層的消息事件將會立即置1生效
bStatus_t tmos_msg_send( tmosTaskID taskID, uint8_t *msg_ptr );
// 接收消息函數,參數為需要接收任務taskID的消息。
uint8_t *tmos_msg_receive( tmosTaskID taskID );
//申請內存函數,發送消息之前需要先給消息申請內存空間。如果返回為NULL,則申請失敗
uint8_t *tmos_msg_allocate( uint16_t len );
//釋放消息占用內存的函數,處理完消息后需要釋放內存占用。
bStatus_t tmos_msg_deallocate( uint8_t *msg_ptr );
/******TMOS定義的函數,較C庫函數節省內存,功能類似******/
uint32_t tmos_rand( void ); // pseudo-random number
bool tmos_memcmp( const void *src1, const void *src2, uint32_t len ); // TRUE - same, FALSE - different
bool tmos_isbufset( uint8_t *buf, uint8_t val, uint32_t len ); // TRUE if all "val",FALSE otherwise
uint32_t tmos_strlen( char *pString );
uint32_t tmos_memset( void * pDst, uint8_t Value, uint32_t len );
void tmos_memcpy( void *dst, const void *src, uint32_t len ); // Generic memory copy.
4、TMOS 使用Demo
4.1 任務管理
4.1.1 示例代碼
tmos_demo_task.h
#ifndef _TMOS_DEMO_TASK_H_
#dedine _TMOS_DEMO_TASK_H_
#include "CH57x_common.h"
#include "CH57xBLE_LIB.H"
#include "stdint.h"
#define DEMO_TASK_TMOS_EVT_TEST_1 (0x0001<<0)
#define DEMO_TASK_TMOS_EVT_TEST_2 (0x0001<<1)
#define DEMO_TASK_TMOS_EVT_TEST_3 (0x0001<<2)
#define DEMO_TASK_TMOS_EVT_TEST_4 (0x0001<<3)
#define DEMO_TASK_TMOS_EVT_TEST_5 (0x0001<<4)
void demo_task_init(void);
#endif
tmos_demo_task.C
#include "tmos_demo_task.h"
//存儲 當前task id 的全局變量
tmosTaskID demo_task_id = INVALID_TASK_ID;
//task的event處理回調函數,需要在注冊task時候,傳進去
static uint16_t demo_task_process_event( uint8_t task_id, uint16_t events )
{
//event 處理
if(events & DEMO_TASK_TMOS_EVT_TEST_1)
{
PRINT("DEMO_TASK_TMOS_EVT_TEST_1 evt test \r\n");
return (events ^ DEMO_TASK_TMOS_EVT_TEST_1); //異或的方式清除該事件運行標志,並返回未運行的事件標志
}
//event 處理
if(events & DEMO_TASK_TMOS_EVT_TEST_2)
{
tmos_start_task(demo_task_id,DEMO_TASK_TMOS_EVT_TEST_3,1600);
PRINT("DEMO_TASK_TMOS_EVT_TEST_2 evt test \r\n");
return (events ^ DEMO_TASK_TMOS_EVT_TEST_2);
}
//event 處理
if(events & DEMO_TASK_TMOS_EVT_TEST_3)
{
tmos_start_task(demo_task_id,DEMO_TASK_TMOS_EVT_TEST_3,1600);
PRINT("DEMO_TASK_TMOS_EVT_TEST_3 evt test \r\n");
return (events ^ DEMO_TASK_TMOS_EVT_TEST_3);
}
// Discard unknown events
return 0;
}
//初始化task
//包括注冊函數,可以注冊后去開啟event
void demo_task_init( void )
{
//注冊task id,同事把該task的event處理函數傳進去
demo_task_id = TMOS_ProcessEventRegister( demo_task_process_event );
//立即開始一個event
tmos_set_event(demo_task_id,DEMO_TASK_TMOS_EVT_TEST_1);
//開始一個定時event,1s后產生,當前語句只會產生一次event
//可以在event產生后去開啟event,可以是別的task的,也可以是當前task的event
tmos_start_task(demo_task_id,DEMO_TASK_TMOS_EVT_TEST_2,1600);
}
4.1.2 使用方法
-
把上面"tmos_demo_task.c" 和 "tmos_demo_task.h" 兩個文件加到工程里面
-
在main函數里的while(1)之前上面調用函數 demo_task_init();
4.1.3 運行現象
- 芯片先運行
PRINT("DEMO_TASK_TMOS_EVT_TEST_1 evt test \r\n");
- 一秒后運行
PRINT("DEMO_TASK_TMOS_EVT_TEST_2 evt test \r\n");
- 然后按照1s一次不斷運行
PRINT("DEMO_TASK_TMOS_EVT_TEST_3 evt test \r\n");
4.2 消息管理
4.2.1 示例代碼
tmos_message_demo_message.h
#ifndef _TMOS_DEMO_MESSAGE_H_
#define _TMOS_DEMO_MESSAGE_H_
#include "CH57x_common.h"
#include "CH57xBLE_LIB.H"
#include "stdint.h"
// Test Task Events
#define TEST_EVENT_1 (0x0001<<0)
#define TEST_EVENT_2 (0x0001<<1)
void TMOS_init(void);
uint16_t test_process_event_1(uint8_t taskID,uint16_t event);
uint16_t test_process_event_2(uint8_t taskID,uint16_t event);
#endif
tmos_demo_message.c
#include "tmos_demo_message.h"
#define MSG_EVENT_TEST 0x10
uint8_t TestTaskID1 = INVALID_TASK_ID;
uint8_t TestTaskID2 = INVALID_TASK_ID;
void TMOS_init(void)
{
TestTaskID1 = TMOS_ProcessEventRegister(test_process_event_1);
TestTaskID2 = TMOS_ProcessEventRegister(test_process_event_2);
tmos_start_task( TestTaskID1, TEST_EVENT_1, 1600 );
tmos_start_task( TestTaskID2, TEST_EVENT_2, 1600 ); //延時啟動 TEST_EVENT_2事件 延時時間:1600*625us
}
//消息處理的函數
static void demo_task_process_TMOSMsg( tmos_event_hdr_t *pMsg )
{
switch ( pMsg->event )
{
case MSG_EVENT_TEST:
PRINT("pMsg->event=%x,pMsg->status=%x\r\n",pMsg->event,pMsg->status);
break;
default:
PRINT("pMsg->event %04x\r\n",pMsg->event);
break;
}
}
uint16_t test_process_event_1(uint8_t taskID,uint16_t events)
{
tmos_event_hdr_t *test_message;
if ( events & TEST_EVENT_1 )
{
PRINT("Run TEST_EVENT_1 in task 1\r\n");
//申請消息內存空間
test_message =(tmos_event_hdr_t*) tmos_msg_allocate(sizeof(tmos_event_hdr_t));
if(test_message)
{
test_message->event = MSG_EVENT_TEST;
test_message->status = 0x55;
//發送消息至 TestTaskID2 任務
tmos_msg_send(TestTaskID2 ,(uint8_t *)test_message);
}
tmos_start_task( taskID, TEST_EVENT_1, 1600 );
return ( events ^ TEST_EVENT_1 );
}
return 0;
}
uint16_t test_process_event_2(uint8_t taskID,uint16_t events)
{
//消息處理
if ( events & SYS_EVENT_MSG )
{
uint8_t *pMsg;
if ( (pMsg = tmos_msg_receive( taskID )) != NULL )
{
//消息處理
demo_task_process_TMOSMsg( (tmos_event_hdr_t *)pMsg );
//釋放消息空間
tmos_msg_deallocate( pMsg );
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
//事件處理
if ( events & TEST_EVENT_2 )
{
PRINT("Run TEST_EVENT_2 in task 2\r\n");
tmos_start_task( taskID, TEST_EVENT_2, 1600 );
return ( events ^ TEST_EVENT_2 );
}
return 0;
}
4.2.2 使用方法
- 把上面"tmos_demo_message.c" 和 "tmos_demo_message.h" 兩個文件加到工程里面
- 在main函數里的while(1)之前上面調用函數 demo_task_init();
4.2.3 運行現象
間隔1S,重復運行:
PRINT("Run TEST_EVENT_1 in task 1\r\n");
PRINT("Run TEST_EVENT_2 in task 2\r\n");
PRINT("pMsg->event=%x,pMsg->status=%x\r\n",pMsg->event,pMsg->status);
5、TMOS使用注意事項
- 禁止在中斷中調用任務調度函數
- 如果使用了ble,建議不要在單個任務中執行超過連接間隔一半時長的任務,否則將影響藍牙通訊
- 在事件生效執行的代碼中調用
tmos_start_task
函數時,延時時間以當前事件生效時間點為基准偏移,所以對調用延時執行函數在生效執行的代碼中擺放的位置沒有要求。 - 任務存在優先級,根據在xxx_ProcessEvent函數中判斷的先后順序決定,同時生效的任務,先執行先判斷,后執行后判斷。注意,執行完先判斷的事件任務后,要等到任務調度系統輪巡一遍后,才會執行后判斷的事件任務。
- 事件名按位定義,每一層taskID最多包含1個消息事件和15個任務事件(共16位)