一、TMOS概述
蓝牙为了实现同多个设备相连,实现多功能和多任务,产生了调度问题。虽然软件和协议栈可扩充,但终究最底层的执行部门只有一个。为了实现多事件和多任务切换,需要把事件和任务对应,针对这种应用起了一个TMOS名字操作系统抽象层。
TMOS作为调度核心,BLE协议栈、profile定义、所有的应用都围绕它来实现。TMOS不是传统大家使用的操作系统,而是一个允许软件建立和执行事件的循环。
TMOS不是传统意义上的操作系统,而是一种以 实现多任务为核心的系统资源管理机制。多任务管理方式实际上只有一个任务在运行,但是可以使用任务调度的策略将多个任务进行调度,每个任务占用一定的时间(独占式,执行完当前任务退出,继续查询其他可执行任务),所有的任务通过时间分片的方式处理。
上图是官方提供的文档有关TMOS的截图,从中我们很容易知道,任务有优先级,但这种优先级只是在系统调度的才有意义,高优先级的任务并不能打断低优先级的任务。
任务与事件的关系:从上图中,我们可以得知,事件需要添加到任务链表中,而每个任务最多可添加15个自定义事件。
二、TMOS任务的注册及事件的处理
/**
* @brief register process event callback function
*
* @param eventCb-events callback function
*
* @return 0xFF - error,others-task id
*/
#define TMOS_ProcessEventRegister (( tmosTaskID (*) ( pTaskEventHandlerFn eventCb )) BLE_LIB_JT(19))
centralTaskId = TMOS_ProcessEventRegister( Central_ProcessEvent );
halTaskID = TMOS_ProcessEventRegister(HAL_ProcessEvent);
其中Central_ProcessEvent和HAL_ProcessEvent是事件处理回调函数。另外,对Central来说,还有一个非常重要的协议栈任务,这个任务的回调函数是:centralEventCB。我们开发产品的时候,大多数的工作都围绕着这三个回调函数以及进行。
// Simple BLE Observer Task Events
#define START_DEVICE_EVT 0x0001
#define START_DISCOVERY_EVT 0x0002
#define START_SCAN_EVT 0x0004
#define START_SVC_DISCOVERY_EVT 0x0008
#define START_PARAM_UPDATE_EVT 0x0010
#define START_PHY_UPDATE_EVT 0x0020
#define START_READ_OR_WRITE_EVT 0x0040
#define START_WRITE_CCCD_EVT 0x0080
#define START_READ_RSSI_EVT 0x0100
#define ESTABLISH_LINK_TIMEOUT_EVT 0x0200
以上是Central的任务centralTaskId下定义的事件。
我们启动事件的方式有两种,大家可以根据实际需要决定用哪种启动方式。
1、 立即启动事件
/**
* @brief start a event immediately
*
* @param taskID - task ID of event
* @param event - event value
*
* @return 0 - SUCCESS.
*/
#define tmos_set_event (( bStatus_t (*) ( tmosTaskID taskID, tmosEvents event )) BLE_LIB_JT(7))
tmos_set_event( centralTaskId, START_DEVICE_EVT );
2、延时启动事件
/**
* @brief start a event after period of time
*
* @param taskID - task ID to set event for
* @param event - event to be notified with
* @param time - timeout value
*
* @return TRUE,FALSE.
*/
#define tmos_start_task (( BOOL (*) ( tmosTaskID taskID, tmosEvents event, tmosTimer time )) BLE_LIB_JT(9))
tmos_start_task( centralTaskId, START_SVC_DISCOVERY_EVT, DEFAULT_SVC_DISCOVERY_DELAY );
接下来就是事件处理了,我们开发产品的重头戏都在这里。
我们可以在事件处理函数中启动另一个任务的事件链表中的事件。比如在我们最近的一个Central项目中,我们需要保存已配对的从机的一些信息,我们知道修改config.h中的
官方的BLE库会自动保存已配对从机的基本信息,但有时我们会发现,这些基本信息并不能满足我们的要求,我们可能要保存更多的信息。比如我们的项目中,我们需要保存读写从机的某些characters的handle,考虑到读写flash好象是HAL层的操作,所以我们把保存这些从机信息的事件定义在HAL层的任务ID链表中。
if ( events & START_WRITE_CCCD_EVT )
{
// Setup the GAP Bond Manager
#if 1
if ( centralProcedureInProgress == FALSE )
{
// Do a write
attWriteReq_t req;
req.cmd = FALSE;
req.sig = FALSE;
req.handle = centralCCCDHdl[centralCCCDHdl[1]];
centralCCCDHdl[1]++;
req.len = 2;
req.pValue = GATT_bm_alloc( centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0 );
if ( req.pValue != NULL )
{
req.pValue[0] = 1;
req.pValue[1] = 0;
if ( GATT_WriteCharValue( centralConnHandle, &req, centralTaskId ) == SUCCESS )
{
centralProcedureInProgress = TRUE;
tmos_start_task( halTaskID, HAL_SAVE_DEVICE_INFO_EVENT, MS1_TO_SYSTEM_TIME( 10 ) );
// BLEConnected = BLE_CONECTED;
}
else
{
GATT_bm_free( ( gattMsg_t * ) &req, ATT_WRITE_REQ );
}
}
}
#endif
return ( events ^ START_WRITE_CCCD_EVT );
}
tmos_start_task( halTaskID, HAL_SAVE_DEVICE_INFO_EVENT, MS1_TO_SYSTEM_TIME( 10 ) );用于HAL层任务ID的保存信息的事件HAL_SAVE_DEVICE_INFO_EVENT。
/* hal task Event */
#define LED_BLINK_EVENT 0x0001
#define HAL_KEY_EVENT 0x0002
#define HAL_PM_EVENT 0x0004
#define HAL_SAVE_DEVICE_INFO_EVENT 0x0008
#define HAL_SEND_CMD_EVENT 0x0010
#define HAL_REG_INIT_EVENT 0x2000
#define HAL_TEST_EVENT 0x4000
if (events & HAL_SAVE_DEVICE_INFO_EVENT)
{
SaveDeviceInfo();
BLEConnected = BLE_CONECTED;
return events ^ HAL_SAVE_DEVICE_INFO_EVENT;
}
三、TMOS任务间的数据传输
TMOS 为不同的任务传递提供了一种接收和发送数据的通信方案。数据的类型是任意的, 且在内存足够的情况下长度也可以是任意的。 可按照以下步骤发送一个数据:
1. 使用tmos_msg_allocate()函数为发送的数据申请内存,若申请成功,则返回内存 地址,若失败则返回NULL。
2. 将数据拷贝到内存中。
3. 调用tmos_msg_send()函数向指定的任务发送数据的指针。
#define KEY_CHANGE_MSG 0xF0
#define CMD_LEN 3
typedef struct REMOTE_CMD_STRUCT {
uint8_t len;
uint8_t cmd[CMD_LEN];
uint8_t SendFinish;
struct REMOTE_CMD_STRUCT * next;
}REMOTE_CMD_STRUCT;
typedef struct
{
tmos_event_hdr_t hdr;
uint8_t state;
uint8_t StartScanDevice;
REMOTE_CMD_STRUCT * remote_cmd;
} keyChange_t;
keyChange_t * msgPtr = ( keyChange_t * ) tmos_msg_allocate( sizeof(keyChange_t) );
if ( msgPtr )
{
msgPtr->hdr.event = KEY_CHANGE_MSG;
msgPtr->remote_cmd = pcurrent_cmd;
msgPtr->StartScanDevice = FALSE;
tmos_msg_send( AppTaskIDForKeyEvent, ( uint8_t * ) msgPtr );
}
数据发送成功后,SYS_EVENT_MSG 被置为有效,此时系统将执行 SYS_EVENT_MSG 事件, 在实践中通过调用 tmos_msg_receive()函数检索数据。在数据处理完成后,必须使用 tmos_msg_deallocate()函数释放内存。详情请参考例程。 假设将消息送给 central 的任务ID,central的系统事件将会接收到此消息。
/*********************************************************************
* @fn Central_ProcessEvent
*
* @brief Central Application Task event processor. This function
* is called to process all events for the task. Events
* include timers, messages and any other user defined events.
*
* @param task_id - The TMOS assigned task ID.
* @param events - events to process. This is a bit map and can
* contain more than one event.
*
* @return events not processed
*/
uint16_t Central_ProcessEvent( uint8_t task_id, uint16_t events )
{
if ( events & SYS_EVENT_MSG )
{
uint8_t *pMsg;
if ( ( pMsg = tmos_msg_receive( centralTaskId ) ) != NULL )
{
central_ProcessTMOSMsg( ( tmos_event_hdr_t * ) pMsg );
// Release the TMOS message
tmos_msg_deallocate( pMsg );
}
// return unprocessed events
return ( events ^ SYS_EVENT_MSG );
}
……
extern REMOTE_CMD_STRUCT * pcurrent_cmd;
static void centralProcessKeyMsg( keyChange_t * pMsg )
{
attWriteReq_t req;
if ( pMsg->StartScanDevice == TRUE )
{
SetupGAP_BondMgr();
GAPRole_CentralStartDiscovery( DEFAULT_DISCOVERY_MODE,
DEFAULT_DISCOVERY_ACTIVE_SCAN,
DEFAULT_DISCOVERY_WHITE_LIST );
}
else
{
if ( centralProcedureInProgress == FALSE )
{
req.cmd = FALSE;
req.sig = FALSE;
req.handle = Device_Ctrl.Handle;
req.len = pMsg->remote_cmd->len;
req.pValue = GATT_bm_alloc( centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0 );
if ( req.pValue != NULL )
{
tmos_memcpy( req.pValue, pMsg->remote_cmd->cmd, req.len );
if ( GATT_WriteCharValue( centralConnHandle, &req, centralTaskId ) == SUCCESS )
{
centralProcedureInProgress = TRUE;
}
else
{
GATT_bm_free( ( gattMsg_t * ) &req, ATT_WRITE_CMD );
}
pcurrent_cmd->SendFinish = TRUE;
}
}
}
}
/*********************************************************************
* @fn central_ProcessTMOSMsg
*
* @brief Process an incoming task message.
*
* @param pMsg - message to process
*
* @return none
*/
static void central_ProcessTMOSMsg( tmos_event_hdr_t *pMsg )
{
switch ( pMsg->event )
{
case GATT_MSG_EVENT :
centralProcessGATTMsg( ( gattMsgEvent_t * ) pMsg );
break;
case KEY_CHANGE_MSG :
centralProcessKeyMsg( ( keyChange_t * ) pMsg );
break;
}
}
注意:这里我们只列出了部分代码。
四、处理不同任务或不同事件的协作
前面三点基本上就把官方的TMOS任务框架介绍完了,这里我们介绍的是在项目开发过程中碰到的一些问题的处理方法,毕竟对官方的TMOS任务架构不是特别清楚,不敢说我们的处理方法是最好的,但起码是验证过的,可行的。
先阐明一下问题吧。我们这个项目是通过按键控制从机,说白了就是遥控器。因为从机是成熟产品,客户不打算重新开发,而从机要求按键按下、释放、保持时,都要发送不同的指令,我们的想法是在这三种状态下,分别如(三)所述发送按键消息,理论上应该是没有问题的,但在实现过程中,发现会出现前一个消息还没有处理,后一个消息又发出的情况,这样就导致了消息丢失的问题。所以,我们加入了协作机制,并用链表保存需要等待处理的按键消息。
REMOTE_CMD_STRUCT * pcurrent_cmd = NULL;
void Send_RemoteCmd_Event( void )
{
if ( pcurrent_cmd != NULL )
{
if (pcurrent_cmd->SendFinish)
{
if (pcurrent_cmd->next == NULL)
{
pcurrent_cmd = NULL;
return;
}
else {
pcurrent_cmd = pcurrent_cmd->next;
}
}
keyChange_t * msgPtr = ( keyChange_t * ) tmos_msg_allocate( sizeof(keyChange_t) );
if ( msgPtr )
{
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
// NeedWaitSleep = TRUE;
#endif
msgPtr->hdr.event = KEY_CHANGE_MSG;
msgPtr->remote_cmd = pcurrent_cmd;
msgPtr->StartScanDevice = FALSE;
tmos_msg_send( AppTaskIDForKeyEvent, ( uint8_t * ) msgPtr );
}
tmos_start_task(halTaskID, HAL_SEND_CMD_EVENT, MS1_TO_SYSTEM_TIME(50));
}
}
void Send_KeyPressedMsg( uint8_t * pvalue, uint8_t len, uint8_t NoBlinkLed )
{
if ( pcurrent_cmd == NULL )
{
pcurrent_cmd = &remote_cmd[0];
pcurrent_cmd->len = len;
memcpy( pcurrent_cmd->cmd, pvalue, len );
pcurrent_cmd->SendFinish = FALSE;
pcurrent_cmd->next = NULL;
if ( !NoBlinkLed )
HalLedBlink( HAL_LED_RED, 1, HAL_LED_DEFAULT_DUTY_CYCLE, HAL_LED_DEFAULT_FLASH_TIME );
Send_RemoteCmd_Event();
}
else
{
REMOTE_CMD_STRUCT * remote_cmd_tail;
remote_cmd_tail = pcurrent_cmd;
while(TRUE)
{
if (remote_cmd_tail->next == NULL)
break;
remote_cmd_tail = remote_cmd_tail->next;
}
if ( remote_cmd_tail == &remote_cmd[REMOTE_CMD_MAX_BUFF - 1] )
{
remote_cmd_tail->next = &remote_cmd[0];
}
else
{
remote_cmd_tail->next = remote_cmd_tail + 1;
}
remote_cmd_tail = remote_cmd_tail->next;
remote_cmd_tail->next = NULL;
remote_cmd_tail->len = len;
memcpy( remote_cmd_tail->cmd, pvalue, len );
remote_cmd_tail->SendFinish = FALSE;
tmos_start_task(halTaskID, HAL_SEND_CMD_EVENT, MS1_TO_SYSTEM_TIME(50));
}
return;
}
在实现这一构想过程中,我们走了一些弯路,前期的想法是在centralProcessKeyMsg函数中,发送按键指令后,判断pcurrent_cmd->next是否为NULL,如果是,pcurrent_cmd = pcurrent_cmd->next,否则pcurrent_cmd = NULL。 这样在发送按键消息时,我们判断pcurrent_cmd 是否为NULL,并作出不同的处理,理论上好象是可行的,但实际上,行不通,出现了pcurrent_cmd 应该为NULL却不是NULL的问题,分析应该是不同的任务、事件调度时,时间差导致。
最后的方案,虽然不是完整的代码,但实现我们的构想没有问题。而且目前测试,稳定可行。