[原创] 蓝牙4.0协议栈按键流程分析笔记

wateras1   2014-1-22 16:03 楼主
之前在蓝牙技术群看到好多网友不知道按键流程到底是什么情况,平时也没时间,在群里也一两句说不明白,也就说了下可以去看下zigbee按键流程过程,其实都是相通的,现在特意发帖分享下,希望能起到一个抛砖引玉的作用。
在介绍蓝牙按键流程分析之前,我们需要了解一个概念,那就是就是OSAL。什么是OSAL呢?
可能大伙对于OS是比较了解的,学了计算机的搞过OS的也基本接触过,简单来说就是一个操作系统抽象层,可以理解为运行在 CC2540 上的操作系统,说操作系统还不能算,TI的OSAL只实现了任务切换和消息机制。并且把协议栈的代码、硬件处理的代码,用户程序的代码等分别放到了 OSAL 层的不同任务处理函数中去了,各任务函数之间通过消息机制、同一个任务之间通过事件的的方式来通信。

什么是 EVENT 事件?
OSAL 为每个任务函数分配了一个 16 位的事件变量,每一位代表一个事件,最高位为 0x8000
表示为系统事件 SYS_EVENT_MSG。其余的15 位留给用户自定义需要的事件。通常事件由定时
器启动,比如一秒后我要点亮 LED2,这就需要发送一个点亮 LED2 的事件,然后等待定时器1s后溢出,于是启动点亮 LED2事件,事件会调用相应的hal 层API点亮LED2。


什么是 MSG 消息

MSG 是比 EVENT 事件更具体并且可以携带数据的一种通信方式,MSG 的标记是按数值,而不是按位。比如 0x01 和 0x02 是两个不同的消息,但对于事件 0x03 则是 0x01 事件和 0x02 事件的组合。MSG 收发使用 osal_msg_send()和 osal_msg_receive();当调用 osal_msg_send()发送一个 msg 的同时会在 EVENT 列表中触发一个 message ready event。(请注意最后一句话,这句话点出了为什么按键时间的触发为何会导致系统事件也接受到了)


现在以 SimpleBLEPeripheral 为例说明按键流程

在 SimpleBLEPeripheral 任务初始化函数中有这样一条代码:

  // Register for all key events - This app will handle all key events
  RegisterForKeys( simpleBLEPeripheral_TaskID );


这个函数来自 OnBoard.c 源文件中
/*********************************************************************
* Keyboard Register function
*
* The keyboard handler is setup to send all keyboard changes to
* one task (if a task is registered).
*
* If a task registers, it will get all the keys. You can change this
* to register for individual keys.
*********************************************************************/
uint8 RegisterForKeys( uint8 task_id )
{
  // Allow only the first task
  if ( registeredKeysTaskID == NO_TASK_ID )
  {
    registeredKeysTaskID = task_id;
    return ( true );
  }
  else
    return ( false );
}


向一个全局变量 registeredKeysTaskID中赋值自己的任务 ID,调用了这个函数就能成功注册按键服务,那这个全局变量在何时使用呢?
分析到这里,感觉有点迷糊了,我们可以从顶到下分析。任何一个程序都是从main函数开始的,这点我们要坚信。所以我们首先找到这个main函数
打开SimpleBLEPeripheral_Main.c文件可以看到/**************************************************************************************************
* @fn          main
*
* @brief       Start of application.
*
* @param       none
*
* @return      none
**************************************************************************************************
*/
int main(void)
{
  /* Initialize hardware */
  HAL_BOARD_INIT();   

  // Initialize board I/O
  InitBoard( OB_COLD );

  /* Initialze the HAL driver */
  HalDriverInit();

  /* Initialize NV system */
  osal_snv_init();

  /* Initialize LL */

  /* Initialize the operating system */
  osal_init_system();

  /* Enable interrupts */
  HAL_ENABLE_INTERRUPTS();

  // Final board initialization
  InitBoard( OB_READY );

  #if defined ( POWER_SAVING )
    osal_pwrmgr_device( PWRMGR_BATTERY );
  #endif

  /* Start OSAL */
  osal_start_system(); // No Return from here

  return 0;
}
我们打开 InitBoard( OB_READY );可以看到如下代码
/*********************************************************************
* @fn      InitBoard()
* @brief   Initialize the CC2540DB Board Peripherals
* @param   level: COLD,WARM,READY
* @return  None
*/
void InitBoard( uint8 level )
{
  if ( level == OB_COLD )
  {
    // Interrupts off
    osal_int_disable( INTS_ALL );
    // Turn all LEDs off
    HalLedSet( HAL_LED_ALL, HAL_LED_MODE_OFF );
    // Check for Brown-Out reset
//    ChkReset();
  }
  else  // !OB_COLD
  {
    /* Initialize Key stuff */
    OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;
    //OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
    HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
  }
}

看到我上面标注的函数了吧?那个是一个按键回调服务注册函数,注册了一个OnBoard_KeyCallback函数
HalKeyConfig 函数的实现:
将上述的回调函数的地址复制给了函数指针变量。通过跟踪发现该函数的指针变量在按键的轮询函数中调用了,如下图:
/**************************************************************************************************
* @fn      HalKeyPoll
*
* @brief   Called by hal_driver to poll the keys
*
* @param   None
*
* @return  None
**************************************************************************************************/
void HalKeyPoll (void)
{
  uint8 keys = 0;
  uint8 notify = 0;
#if defined (CC2540_MINIDK)
  if (!(HAL_KEY_SW_1_PORT & HAL_KEY_SW_1_BIT))    /* Key is active low */
  {
    keys |= HAL_KEY_SW_1;
  }
  if (!(HAL_KEY_SW_2_PORT & HAL_KEY_SW_2_BIT))    /* Key is active low */
  {
    keys |= HAL_KEY_SW_2;
  }
#else
  if (!(HAL_KEY_SW_6_PORT & HAL_KEY_SW_6_BIT))    /* Key is active low */
  {
    keys |= HAL_KEY_SW_6;
  }


  if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT))  /* Key is active HIGH */
  {
    keys = halGetJoyKeyInput();
  }
#endif


  /* If interrupts are not enabled, previous key status and current key status
   * are compared to find out if a key has changed status.
   */
  if (!Hal_KeyIntEnable)
  {
    if (keys == halKeySavedKeys)
    {
      /* Exit - since no keys have changed */
      return;
    }
    else
    {
      notify = 1;
    }
  }
  else
  {
    /* Key interrupt handled here */
    if (keys)
    {
      notify = 1;
    }
  }


  /* Store the current keys for comparation next time */
  halKeySavedKeys = keys;


  /* Invoke Callback if new keys were depressed */
  if (notify && (pHalKeyProcessFunction))
  {
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);


  }
}

在这里底层的按键查询函数调用一个函数指针,而非具体的函数,这样就将处理按键的接口留给了上层,上层应用中,叧需解析的函数指针传入的参数 1:keys 就知道是哪个按键被按下了。
我们再回到刚才的 OnBoard_KeyCallback 回调函数处,该回调函数代码如下:
/*********************************************************************
* @fn      OnBoard_KeyCallback
*
* @brief   Callback service for keys
*
* @param   keys  - keys that were pressed
*          state - shifted
*
* @return  void
*********************************************************************/
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
{
  uint8 shift;
  (void)state;

  // shift key (S1) is used to generate key interrupt
  // applications should not use S1 when key interrupt is enabled
  shift = (OnboardKeyIntEnable == HAL_KEY_INTERRUPT_ENABLE) ? false : ((keys & HAL_KEY_SW_6) ? true : false);

  if ( OnBoard_SendKeys( keys, shift ) != SUCCESS )  //就是这句话将按键消息上传到应用层去处理的
  {
    // Process SW1 here
    if ( keys & HAL_KEY_SW_1 )  // Switch 1
    {
    }
    // Process SW2 here
    if ( keys & HAL_KEY_SW_2 )  // Switch 2
    {
    }
    // Process SW3 here
    if ( keys & HAL_KEY_SW_3 )  // Switch 3
    {
    }
    // Process SW4 here
    if ( keys & HAL_KEY_SW_4 )  // Switch 4
    {
    }
    // Process SW5 here
    if ( keys & HAL_KEY_SW_5 )  // Switch 5
    {
    }
    // Process SW6 here
    if ( keys & HAL_KEY_SW_6 )  // Switch 6
    {
    }
  }

  /* If any key is currently pressed down and interrupt
     is still enabled, disable interrupt and switch to polling */
  if( keys != 0 )
  {
    if( OnboardKeyIntEnable == HAL_KEY_INTERRUPT_ENABLE )
    {
      OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
      HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
    }
  }
  /* If no key is currently pressed down and interrupt
     is disabled, enable interrupt and turn off polling */
  else
  {
    if( OnboardKeyIntEnable == HAL_KEY_INTERRUPT_DISABLE )
    {
      OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;
      HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
    }
  }
}


进入按键消息发送函数中可以看到

/*********************************************************************
* @fn      OnBoard_SendKeys
*
* @brief   Send "Key Pressed" message to application.
*
* @param   keys  - keys that were pressed
*          state - shifted
*
* @return  status
*********************************************************************/
uint8 OnBoard_SendKeys( uint8 keys, uint8 state )
{
  keyChange_t *msgPtr;


  if ( registeredKeysTaskID != NO_TASK_ID )
  {
    // Send the address to the task
    msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );
    if ( msgPtr )
    {
      msgPtr->hdr.event = KEY_CHANGE;
      msgPtr->state = state;
      msgPtr->keys = keys;


      osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
    }
    return ( SUCCESS );
  }
  else
    return ( FAILURE );
}


主要是将按键时间,状态和按键值打包到信息中最后通过 osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );发送到注册了按键服务的应用层去 ,最终用户按了哪个按键,如何响应该按键在系统事件 SYS_EVENT_MSG 中处理。疑问又来了,为什么通过 osal_msg_send 收发的消息会出现在 SYS_EVENT_MSG 中呢?这个疑问,就是刚才我说过的osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr )会产生一个 a message  ready event in the destination tasks event list.
/*********************************************************************
* @fn      osal_msg_send
*
* @brief
*
*    This function is called by a task to send a command message to
*    another task or processing element.  The sending_task field must
*    refer to a valid task, since the task ID will be used
*    for the response message.  This function will also set a message
*    ready event in the destination tasks event list.
*
*
* @param   uint8 destination_task - Send msg to Task ID
* @param   uint8 *msg_ptr - pointer to new message buffer
*
* @return  SUCCESS, INVALID_TASK, INVALID_MSG_POINTER
*/
uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr )
{
  return ( osal_msg_enqueue_push( destination_task, msg_ptr, FALSE ) );
}


/*********************************************************************
* @fn      simpleBLEPeripheral_ProcessOSALMsg
*
* @brief   Process an incoming task message.
*
* @param   pMsg - message to process
*
* @return  none
*/
static void simpleBLEPeripheral_ProcessOSALMsg( osal_event_hdr_t *pMsg )
{
  switch ( pMsg->event )
  {
  #if defined( CC2540_MINIDK )
    case KEY_CHANGE:                  //按键处理事件,用户产生的按键都是在这里处理   
      simpleBLEPeripheral_HandleKeys( ((keyChange_t *)pMsg)->state, ((keyChange_t *)pMsg)->keys );
      break;
  #endif // #if defined( CC2540_MINIDK )


  default:
    // do nothing
    break;
  }
}

在 SimpleBLEPeripheral 中,对按键的响应如下:joystick right(SW2)收发广播的开启和关闭

/*********************************************************************
* @fn      simpleBLEPeripheral_HandleKeys
*
* @brief   Handles all key events for this device.
*
* @param   shift - true if in shift/alt.
* @param   keys - bit field for key events. Valid entries:
*                 HAL_KEY_SW_2
*                 HAL_KEY_SW_1
*
* @return  none
*/
static void simpleBLEPeripheral_HandleKeys( uint8 shift, uint8 keys )
{
  uint8 SK_Keys = 0;


  VOID shift;  // Intentionally unreferenced parameter


  if ( keys & HAL_KEY_SW_1 )
  {
    SK_Keys |= SK_KEY_LEFT;
  }


  if ( keys & HAL_KEY_SW_2 )
  {


    SK_Keys |= SK_KEY_RIGHT;


    // if device is not in a connection, pressing the right key should toggle
    // advertising on and off
    if( gapProfileState != GAPROLE_CONNECTED )
    {
      uint8 current_adv_enabled_status;
      uint8 new_adv_enabled_status;


      //Find the current GAP advertisement status
      GAPRole_GetParameter( GAPROLE_ADVERT_ENABLED, ¤t_adv_enabled_status ); //这个函数主要就是获得广播的开启和关闭状态


      if( current_adv_enabled_status == FALSE )
      {
        new_adv_enabled_status = TRUE;
      }
      else
      {
        new_adv_enabled_status = FALSE;
      }


      //change the GAP advertisement status to opposite of current status
      GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &new_adv_enabled_status );
    }


  }


  // Set the value of the keys state to the Simple Keys Profile;
  // This will send out a notification of the keys state if enabled
  SK_SetParameter( SK_KEY_ATTR, sizeof ( uint8 ), &SK_Keys );
}
#endif // #if defined( CC2540_MINIDK )
现在按键基本分析完了,其实CC2540学习板子是默认开启广播,但是Keyfob就需要按键手动开启了。接下来我会继续分析协议栈的其他功能,如有不足之外,望补充~~~~~~~~~~(花了我几个小时写出来,蛋疼。写文章太费劲了)


















淘宝:https://viiot.taobao.com/Q群243090717 多年专业物联网行业经验,个人承接各类物联网外包项目

回复评论 (17)

感谢分享!
点赞  2014-1-22 16:21
擦!看了个开头
本打算往下看,实在是看不下去了!有些难啊!
称重传感器就找鸿瑞测控
点赞  2014-1-22 16:31
引用: 旧梦随风 发表于 2014-1-22 16:31 擦!看了个开头 本打算往下看,实在是看不下去了!有些难啊!
确实,论述比较全面细致循序渐进原理彻底的文章确实几乎看不到,尤其是涉及到底层的资料,大多只是大致原理性的概述和OSAL框架过程的一点介绍而进行一些快速的简单应用。 不过这已经算是难得啦,这不,楼主都说写得累人了 本帖最后由 wangfuchong 于 2014-1-22 16:52 编辑
没工作,没女人老婆,没宽带 ,  没钱
点赞  2014-1-22 16:48
学习了
昵称:灰小子
点赞  2014-1-22 19:00
谢谢楼主的分享精神
结合楼主的分析我再读读这部分代码
点赞  2014-7-31 09:20
楼主写的不错,我现在正在头疼BLE4.0的函数问题,好多看不懂,底层资料大部分是原理,看着看着就乱了,头疼啊。
成功者找方法,失败者找理由
点赞  2014-7-31 09:40
对了,忘了说一句,很期待楼主后面的文章,感觉看了你写的文章之后,瞬间就明白了一点啊,嘿嘿
成功者找方法,失败者找理由
点赞  2014-7-31 09:42
引用: 651076842 发表于 2014-7-31 09:42
对了,忘了说一句,很期待楼主后面的文章,感觉看了你写的文章之后,瞬间就明白了一点啊,嘿嘿

现在不搞蓝牙了,所以分享蓝牙的文章比较少了,目前专注UHF RFID了
淘宝:https://viiot.taobao.com/Q群243090717 多年专业物联网行业经验,个人承接各类物联网外包项目
点赞  2014-7-31 16:10
您好,您有蓝牙4.0协议栈源代码吗?蓝牙开发初学者,不胜感激!!
点赞  2014-12-20 21:04
有这个操作系统的源码吗?
点赞  2014-12-21 00:26
欢迎大家加入wifi、ble兴趣群 422240210
专注智能产品的研究与开发,专注于电子电路的生产与制造……QQ:2912615383,电子爱好者群: void
点赞  2015-1-10 14:50
4.0是不是只支持andriod 4.3以上的系统啊。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
我的博客
点赞  2015-2-5 08:47
引用: zhaojun_xf 发表于 2015-2-5 08:47
4.0是不是只支持andriod 4.3以上的系统啊。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

是的,怎么了?也在玩蓝牙4.0?

淘宝:https://viiot.taobao.com/Q群243090717 多年专业物联网行业经验,个人承接各类物联网外包项目
点赞  2015-2-5 09:57
最近准备玩,可以好多手机都是4.22的,这个比较麻烦。。。。。。。。。。。。。。。。。。。。。。。。。。
我的博客
点赞  2015-2-5 10:03
引用: zhaojun_xf 发表于 2015-2-5 10:03
最近准备玩,可以好多手机都是4.22的,这个比较麻烦。。。。。。。。。。。。。。。。。。。。。。。。。。



确实 ,现在很多手机还不支持,这个限制了蓝牙4.0的推广,不过手机厂商应该会陆续升级他们的产品。
淘宝:https://viiot.taobao.com/Q群243090717 多年专业物联网行业经验,个人承接各类物联网外包项目
点赞  2015-2-5 16:27
已读完,感谢!
点赞  2015-8-19 11:38
已经读完,感谢分享!
点赞  2015-8-19 11:39
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复