CH579 CH573 CH582 CH592 蓝牙主机(Central)实例应用讲解(三)——TMOS任务解析

一、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_ProcessEventHAL_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的问题,分析应该是不同的任务、事件调度时,时间差导致。

最后的方案,虽然不是完整的代码,但实现我们的构想没有问题。而且目前测试,稳定可行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永远的元子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值