CH57x 的藍牙中,為了方便其協議棧自身的管理,以及用戶的使用,使用一個非常輕量級的操作系統"TMOS",
TMOS 特點:
- TMOS 實際上就是OSAL的簡化版本,這是一個輪詢實現的系統,不支持搶占任務;
- TMOS 系統時鍾單位為 625us, 時鍾來源為RTC
- 多任務管理方式實際上只有一個任務在運行
- 可以使用任務調度的策略將多個任務進行調度,每個任務占用一定的時間,所有的任務通過時間分片的方式處理。
[一些常用的API說明]
//tmosTaskID taskID
//以不同的taskid 來區分不同的任務,越小優先級越高
//tmosEvents event
//每個task 下擁有的event,16bit,每bit代表一個event,對於同一個task,一共16個event,其中0x8000為系統使用,剩下15個為用戶使用
//注冊task id,一般用於注冊任務時候,首先執行的
tmosTaskID TMOS_ProcessEventRegister( pTaskEventHandlerFn eventCb );
//設置一個event,,根據taskid 和event 來決定具體的事件
bStatus_t tmos_set_event( tmosTaskID taskID, tmosEvents event );
//清理一個已經超時的event,不能在自己的event 函數內執行
tmos_clear_event( tmosTaskID taskID, tmosEvents event );
//開始一個定時事件,只執行一次,
//tmosTimer具體是 1600 = 1s
bStatus_t tmos_start_task( tmosTaskID taskID, tmosEvents event, tmosTimer time );
//開始一個定時事件,不斷的執行,除非運行tmos_stop_task關掉,
//tmosTimer具體是 1600 = 1s
bStatus_t tmos_start_reload_task( tmosTaskID taskID, tmosEvents event, tmosTimer time );
//停止一個定時事件
bStatus_t tmos_stop_task( tmosTaskID taskID, tmosEvents event );
//獲取對應taskid 和event 的最后一個周期時常,返回0是沒有找到.
tmosTimer tmos_get_task_timer( tmosTaskID taskID, tmosEvents event );
bStatus_t tmos_msg_send( tmosTaskID taskID, uint8_t *msg_ptr );
uint8_t *tmos_msg_receive( tmosTaskID taskID );
uint8_t *tmos_msg_allocate( uint16_t len );
bStatus_t tmos_msg_deallocate( uint8_t *msg_ptr );
uint8_t tmos_snv_read( uint8_t id, uint8_t len, void *pBuf);
//tmos的系統處理函數,需要不斷在主函數中運行
void TMOS_SystemProcess( void );
//返回tmos系統運行的clock,1600=1s
uint32_t TMOS_GetSystemClock( void );
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.
注冊任務
開始定時事件
[示例代碼]
源碼:
"tmos_demo_task.h"
#include <stdint.h>
#include "CH57x_common.h"
#include "CH57xBLE_LIB.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);
uint8 tmos_demo_task_send_msg( uint8_t task_id,uint8_t *data ,uint16_t length ) ;
"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 ) {
if ( events & SYS_EVENT_MSG ) {
uint8_t *pMsg;
if ( (pMsg = tmos_msg_receive( demo_task_id )) != NULL ) {
//We can’t get the length of the received message here,
//Length information is required when using variable length messages
PRINT("revice data:");
for(uint8_t i=0;i<8;i++){
PRINT("%02x ",pMsg[i]);
}
PRINT("\r\n");
// Release the TMOS message
tmos_msg_deallocate( pMsg );
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
//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");
uint8_t test_data[] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};
PRINT("tmos send fixed length data test\r\n");
tmos_demo_task_send_msg(demo_task_id,test_data,8);
return (events ^ DEMO_TASK_TMOS_EVT_TEST_3);
}
// Discard unknown events
return 0;
}
uint8 tmos_demo_task_send_msg( uint8_t task_id,uint8_t *data ,uint16_t length ) {
uint8_t *p_data;
if ( task_id != TASK_NO_TASK ) {
// Send the address to the task
p_data = tmos_msg_allocate(length);
if ( p_data ) {
tmos_memcpy(p_data,data,length);
tmos_msg_send( task_id, p_data );
return ( SUCCESS );
}
}
return ( FAILURE );
}
//初始化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);
}
使用:
- 把上面"tmos_demo_task.c" 和 "tmos_demo_task.h" 兩個文件加到工程里面
- 在main函數里的while(1)之前上面調用 demo_task_init();
現象:
- 芯片先運行
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");
並且對本task發送自定義固定長度消息,然后task里面獲取到系統消息,並且打印出來
[注意事項]
任務調度函數使用注意事項:
- 禁止在中斷中調用,包括操作任務和傳遞消息
- 如果使用了ble,建議不要在單個任務中執行超過連接間隔一半時長的任務,否則將影響藍牙通訊
- CH579/573系列的芯片上,受限於RAM的的大小,TMOS的CNT更新是在
TMOS_SystemProcess()
里面完成的,這意味着:- 如果在事件生效執行的代碼中調用延時執行函數時,延時時間以當前事件生效時間點為基准偏移,所以對調用延時執行函數在生效執行的代碼中擺放的位置沒有要求。
- 如果啟用睡眠,在睡眠醒來后立刻調用tmos_start_task之類的函數,會因tmos的cnt值還是睡眠之前的值並沒有更新, 導致相對事件誤差比較大(比如已經睡眠了1s,而tmos_start一個500ms的事件,這樣會導致tmos認為已經過去了1s,而不是剛剛開始這個task,所以會立刻執行這個task).
- 任務存在優先級,根據在xxx_ProcessEvent函數中判斷的先后順序決定,同時生效的任務,先執行先判斷,后執行后判斷。注意,執行完先判斷的事件任務后,要等到任務調度系統輪巡一遍后,才會執行后判斷的事件任務。
- 事件名按位定義,每一層taskID最多包含1個消息事件和15個任務事件(共16位)
[問題排查]
- tmos_start_task 事件卻沒有運行?
- 如果tmos_start_task返回值是false,則代表調用失敗
- 調用失敗可能是tmos的ram不夠了比如真的比較少,或者申請的ram 沒有釋放;
- 可能是task id 沒有申請
- 如果是調用成功,是不是其他地方給stop了
- 或者對應的event之前,被別的event給處理了,比如return (events ^ xxxx_EVT);
[高級使用]
消息管理
消息是一個帶有數據的事件,用於協議棧各層之間傳遞數據,支持同時添加多個消息。
//申請內存函數,發送消息之前需要先給消息申請內存空間。如果返回為NULL,則申請失敗。
extern u8 *tmos_msg_allocate( u16 len );
//發送消息函數,參數為消息想要發送到哪一層的taskID以及消息指針。當調用此函數時,對應參數taskID層的消息事件將會立即置1生效
extern bStatus_t tmos_msg_send( tmosTaskID taskID, u8 *msg_ptr );
//接收消息函數,參數為想要接收哪一層的taskID。
//這里我們並得不到消息的長度,如果是變長信息,需要在信息里面有消息長度指示,比如第一個字節為長度
extern u8 *tmos_msg_receive( tmosTaskID taskID );
//釋放消息占用內存的函數,處理完消息后需要釋放內存占用。
extern bStatus_t tmos_msg_deallocate( u8 *msg_ptr );