之前在藍牙技術(shù)群看到好多網(wǎng)友不知道按鍵流程到底是什么情況,平時也沒時間,在群里也一兩句說不明白,也就說了下可以去看下zigbee按鍵流程過程,其實都是相通的,現(xiàn)在特意發(fā)帖分享下,希望能起到一個拋磚引玉的作用。 在介紹藍牙按鍵流程分析之前,我們需要了解一個概念,那就是就是OSAL。什么是OSAL呢?可能大伙對于OS是比較了解的,學(xué)了計算機的搞過OS的也基本接觸過,簡單來說就是一個操作系統(tǒng)抽象層,可以理解為運行在 CC2540 上的操作系統(tǒng),說操作系統(tǒng)還不能算,TI的OSAL只實現(xiàn)了任務(wù)切換和消息機制。并且把協(xié)議棧的代碼、硬件處理的代碼,用戶程序的代碼等分別放到了 OSAL 層的不同任務(wù)處理函數(shù)中去了,各任務(wù)函數(shù)之間通過消息機制、同一個任務(wù)之間通過事件的的方式來通信。 什么是 EVENT 事件? OSAL 為每個任務(wù)函數(shù)分配了一個 16 位的事件變量,每一位代表一個事件,最高位為 0x8000 表示為系統(tǒng)事件 SYS_EVENT_MSG。其余的15 位留給用戶自定義需要的事件。通常事件由定時 器啟動,比如一秒后我要點亮 LED2,這就需要發(fā)送一個點亮 LED2 的事件,然后等待定時器1s后溢出,于是啟動點亮 LED2事件,事件會調(diào)用相應(yīng)的hal 層API點亮LED2。 什么是 MSG 消息 MSG 是比 EVENT 事件更具體并且可以攜帶數(shù)據(jù)的一種通信方式,MSG 的標記是按數(shù)值,而不是按位。比如 0x01 和 0x02 是兩個不同的消息,但對于事件 0x03 則是 0x01 事件和 0x02 事件的組合。MSG 收發(fā)使用 osal_msg_send()和 osal_msg_receive();當調(diào)用 osal_msg_send()發(fā)送一個 msg 的同時會在 EVENT 列表中觸發(fā)一個 message ready event。(請注意最后一句話,這句話點出了為什么按鍵時間的觸發(fā)為何會導(dǎo)致系統(tǒng)事件也接受到了) 現(xiàn)在以 SimpleBLEPeripheral 為例說明按鍵流程 在 SimpleBLEPeripheral 任務(wù)初始化函數(shù)中有這樣一條代碼: // Register for all key events - This app will handle all key events RegisterForKeys( simpleBLEPeripheral_TaskID ); 這個函數(shù)來自 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中賦值自己的任務(wù) ID,調(diào)用了這個函數(shù)就能成功注冊按鍵服務(wù),那這個全局變量在何時使用呢? 分析到這里,感覺有點迷糊了,我們可以從頂?shù)较路治?。任何一個程序都是從main函數(shù)開始的,這點我們要堅信。所以我們首先找到這個main函數(shù) 打開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); } } 看到我上面標注的函數(shù)了吧?那個是一個按鍵回調(diào)服務(wù)注冊函數(shù),注冊了一個OnBoard_KeyCallback函數(shù) HalKeyConfig 函數(shù)的實現(xiàn): 將上述的回調(diào)函數(shù)的地址復(fù)制給了函數(shù)指針變量。通過跟蹤發(fā)現(xiàn)該函數(shù)的指針變量在按鍵的輪詢函數(shù)中調(diào)用了,如下圖: /************************************************************************************************** * @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); } } 在這里底層的按鍵查詢函數(shù)調(diào)用一個函數(shù)指針,而非具體的函數(shù),這樣就將處理按鍵的接口留給了上層,上層應(yīng)用中,叧需解析的函數(shù)指針傳入的參數(shù) 1:keys 就知道是哪個按鍵被按下了。 我們再回到剛才的 OnBoard_KeyCallback 回調(diào)函數(shù)處,該回調(diào)函數(shù)代碼如下: /********************************************************************* * @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 ) //就是這句話將按鍵消息上傳到應(yīng)用層去處理的 { // 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); } } } 進入按鍵消息發(fā)送函數(shù)中可以看到 /********************************************************************* * @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 ); } 主要是將按鍵時間,狀態(tài)和按鍵值打包到信息中最后通過 osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );發(fā)送到注冊了按鍵服務(wù)的應(yīng)用層去 ,最終用戶按了哪個按鍵,如何響應(yīng)該按鍵在系統(tǒng)事件 SYS_EVENT_MSG 中處理。疑問又來了,為什么通過 osal_msg_send 收發(fā)的消息會出現(xiàn)在 SYS_EVENT_MSG 中呢?這個疑問,就是剛才我說過的osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr )會產(chǎn)生一個 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: //按鍵處理事件,用戶產(chǎn)生的按鍵都是在這里處理 simpleBLEPeripheral_HandleKeys( ((keyChange_t *)pMsg)->state, ((keyChange_t *)pMsg)->keys ); break; #endif // #if defined( CC2540_MINIDK ) default: // do nothing break; } } 在 SimpleBLEPeripheral 中,對按鍵的響應(yīng)如下:joystick right(SW2)收發(fā)廣播的開啟和關(guān)閉 /********************************************************************* * @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 ); //這個函數(shù)主要就是獲得廣播的開啟和關(guān)閉狀態(tài) 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 ) 現(xiàn)在按鍵基本分析完了,其實CC2540學(xué)習(xí)板子是默認開啟廣播,但是Keyfob就需要按鍵手動開啟了。接下來我會繼續(xù)分析協(xié)議棧的其他功能,如有不足之外,望補充~~~~~~~~~~(花了我?guī)讉€小時寫出來,蛋疼。寫文章太費勁了)
轉(zhuǎn)載自:http://bbs./forum.PHP?mod=viewthread&tid=428987 |
|