今天介紹一下z-stack的key驅動程序。先看一下我板子上的按鍵連接圖
可以看到總共有六個按鍵,不算復位鍵,up、down、left、right四個爲搖桿的方向鍵,PUSH爲搖桿的中心鍵,這個五個鍵接的是P20,通過接到P06的AD轉換功能來判斷出是哪個按鍵按下。當按下五個鍵中任何一個時,P20就變爲高電平,在上升沿觸發中斷標誌。TI官方的板子跟這個電路圖有點不同,z-stack裏面key驅動是按照TI的官方板子寫的,我把其中需要改的地方改過來了,等哈會提到。
先看hal_key.h這個頭文件
#define HAL_KEY_SW_1 0x01 // Joystick up
#define HAL_KEY_SW_2 0x02 // Joystick right
#define HAL_KEY_SW_5 0x04 // Joystick center
#define HAL_KEY_SW_4 0x08 // Joystick left
#define HAL_KEY_SW_3 0x10 // Joystick down P06
#define HAL_KEY_SW_6 0x20 // Button S5 if available
#define HAL_KEY_SW_7 0x40 // Button S2 if available P05 板子上是P0.5
這個源程序的定義,我板子上沒有SW6,其中SW7對應的是板子的Button S2,如上圖連接的是P05,這裏對按鍵採用位圖的定義,即某一位對應一個按鍵,對後面的按鍵的處理有很好的幫助,值得借鑑。
然後定義了key回調函數的函數指針typedef void (*halKeyCBack_t) (uint8 keys, uint8 state);參數keys即爲上面按鍵定義的組合,如keys=HAL_KEY_SW_1|HAL_KEY_SW_5,參數state爲keys的狀態,在上面可以看到這兩個定義
/* Key state - shift or normal */
#define HAL_KEY_STATE_NORMAL 0x00
#define HAL_KEY_STATE_SHIFT 0x01
HAL_KEY_STATE_NORMAL對應普通按鍵,HAL_KEY_STATE_SHIFT對應的搖桿的按鍵。接下來看下hal_key.c這個源文件。先看下這個兩個宏定義
#define HAL_KEY_DEBOUNCE_VALUE 25 //中斷方式 中斷之後延遲25ms進入按鍵處理程序
#define HAL_KEY_POLLING_VALUE 100 //查詢方式 每100ms檢查是否有按鍵按下
正像我後面作出的註釋,z-stack對按鍵的處理採用了中斷和查詢方式。中斷方式和查詢方式有一半相同點有一半不同,相同的是在按鍵按下之後過某個設定的時間後設置事件標誌HAL_KEY_EVENT,然後在下一次系統輪詢中對此事件進行處理調用相關的按鍵處理函數,不同的是對於查詢方式,CPU需要定期檢查按鍵的狀態,當檢測到按鍵按下延遲100ms向HAL層發出HAL_KEY_EVENT事件,對於中斷方式,當按鍵按下時,則立刻發出HAL_KEY_EVENT事件,不需要CPU的檢查,中斷方式比查詢方式實時性好。這兩個宏定義就是針對兩種不同key處理方式而設置的延遲時間!在後面再講key的處理過程。先看看key驅動都是什麼東東!
/* SW_6 is at P0.1 */
/*板子上面沒有SW_6,改成SW_7 at P0.5*/
#define HAL_KEY_SW_7_PORT P0
#define HAL_KEY_SW_7_BIT BV(5)
#define HAL_KEY_SW_7_SEL P0SEL
#define HAL_KEY_SW_7_DIR P0DIR
對於我的板子 上面沒有SW_6而SW_7對應於S2 at P0.5,所以講所有的HAL_KEY_SW_6_X改成HAL_KEY_SW_7_X,且之前的設置是在P01,而SW_7是在P05,所以相關的寄存器設置都相應改過來。這裏有一大堆宏定義看datasheet,對於學過單片機的同學就很簡單,這裏不一一列舉出來了。
看一下幾個全局變量halKeySavedKeys /* used to store previous key state in polling mode */如註釋,用於查詢方式的保存之前按鍵的狀態。pHalKeyProcessFunction這個是按鍵的回調函數。HalKeyConfigured是key是否配置,如果是,則置爲TRUE,否則FALSE;Hal_KeyIntEnable表示是否採用中斷方式,如果是則爲TRUE;
全局函數halProcessKeyInterrupt()是對按鍵的處理函數;halGetJoyKeyInput()是獲取搖桿按鍵的具體按鍵值,即通過調用它就知道是具體哪個按鍵按下了。
按鍵初始化
HalKeyInit()
{
/* Initialize previous key to 0 */
halKeySavedKeys = 0;
HAL_KEY_SW_7_SEL &= ~(HAL_KEY_SW_7_BIT); /* Set pin function to GPIO */
HAL_KEY_SW_7_DIR &= ~(HAL_KEY_SW_7_BIT); /* Set pin direction to Input */
HAL_KEY_JOY_MOVE_SEL &= ~(HAL_KEY_JOY_MOVE_BIT); /* Set pin function to GPIO */
HAL_KEY_JOY_MOVE_DIR &= ~(HAL_KEY_JOY_MOVE_BIT); /* Set pin direction to Input */
/* Initialize callback function */
pHalKeyProcessFunction = NULL;
/* Start with key is not configured */
HalKeyConfigured = FALSE;
}
初始化做的工作就是設置按鍵對應的P05,P20,方向爲輸入,功能爲GPIO,然後置回調函數爲空,將HalKeyConfigured 置爲FALSE,即還沒有配置key。
key配置函數
void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)
{
/* Enable/Disable Interrupt or */
Hal_KeyIntEnable = interruptEnable;
/* Register the callback fucntion */
pHalKeyProcessFunction = cback;
/* Determine if interrupt is enable or not */
if (Hal_KeyIntEnable)
{
/* Rising/Falling edge configuratinn */
PICTL &= ~(HAL_KEY_SW_7_EDGEBIT); /* Clear the edge bit */
/* For falling edge, the bit must be set. */
#if (HAL_KEY_SW_7_EDGE == HAL_KEY_FALLING_EDGE)
PICTL |= HAL_KEY_SW_7_EDGEBIT;
#endif
/* Interrupt configuration:
* - Enable interrupt generation at the port
* - Enable CPU interrupt
* - Clear any pending interrupt
*/
HAL_KEY_SW_7_ICTL |= HAL_KEY_SW_7_ICTLBIT;
HAL_KEY_SW_7_IEN |= HAL_KEY_SW_7_IENBIT;
HAL_KEY_SW_7_PXIFG &= ~(HAL_KEY_SW_7_BIT);
/* Rising/Falling edge configuratinn */
//糾正錯誤將HAL_KEY_JOY_MOVE_ICTL改成PICTL
PICTL &= ~(HAL_KEY_JOY_MOVE_EDGEBIT); /* Clear the edge bit */
/* For falling edge, the bit must be set. */
#if (HAL_KEY_JOY_MOVE_EDGE == HAL_KEY_FALLING_EDGE)
PICTL |= HAL_KEY_JOY_MOVE_EDGEBIT;
#endif
/* Interrupt configuration:
* - Enable interrupt generation at the port
* - Enable CPU interrupt
* - Clear any pending interrupt
*/
HAL_KEY_JOY_MOVE_ICTL |= HAL_KEY_JOY_MOVE_ICTLBIT;
HAL_KEY_JOY_MOVE_IEN |= HAL_KEY_JOY_MOVE_IENBIT;
HAL_KEY_JOY_MOVE_PXIFG = ~(HAL_KEY_JOY_MOVE_BIT);
/* Do this only after the hal_key is configured - to work with sleep stuff */
if (HalKeyConfigured == TRUE)
{
osal_stop_timerEx( Hal_TaskID, HAL_KEY_EVENT); /* Cancel polling if active */
}
}
else /* Interrupts NOT enabled */
{
HAL_KEY_SW_7_ICTL &= ~(HAL_KEY_SW_7_ICTLBIT); /* don't generate interrupt */
HAL_KEY_SW_7_IEN &= ~(HAL_KEY_SW_7_IENBIT); /* Clear interrupt enable bit */
osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE); /* Kick off polling */
}
/* Key now is configured */
HalKeyConfigured = TRUE;
}
將參數interruptEnable傳遞給Hal_KeyIntEnable,表示是否採用中斷方式,然後將回調函數賦值給pHalKeyProcessFunction,配置函數根據key不同方式採用了不同的配置。如果是中斷方式,就將相應中斷的屏蔽位置1,使能中斷,清零相應的中斷標誌位。最後
if (HalKeyConfigured == TRUE)
{
osal_stop_timerEx( Hal_TaskID, HAL_KEY_EVENT); /* Cancel polling if active */
}
這句代碼又是幹什麼呢?其實這個是在進入睡眠模式之前用的,如果在進入睡眠之前重新配置了key,則就停止key事件的觸發,即系統不接收按鍵的事件了。如果是查詢模式就禁用相應中斷。並最後調用
osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE);這是一個系統軟定時器,即想任務Hal_TaskID在經過HAL_KEY_POLLING_VALUE即100ms後發送HAL_KEY_EVENT事件。
最後將HalKeyConfigured置爲TRUE說明已經配置好了。
按鍵讀取鍵值函數
uint8 HalKeyRead ( void )
{
uint8 keys = 0;
#ifdef HAL_BOARD_CC2530EB_REV17
if ( !(HAL_KEY_SW_7_PORT & HAL_KEY_SW_7_BIT)) /* Key is active LOW */
#elif defined (HAL_BOARD_CC2530EB_REV13)
if (!(HAL_KEY_SW_7_PORT & HAL_KEY_SW_7_BIT)) /* Key is active low */
#endif
{
keys |= HAL_KEY_SW_7; //即板子上的P0.5
}
if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT)) /* Key is active low */
//扯淡 明顯是active high
{
keys |= halGetJoyKeyInput();
}
return keys;
}
條件編譯可以先不用管。這裏我根據我板子上改正源程序中的幾個地方。對於SW7是低電平按鍵有效,而源程序中是高電平,對於搖桿按鍵是高電平有效,源程序是低電平。如果是HAL_KEY_SW_7_PORT 爲低電平,則keys就包含了SW_7,如果HAL_KEY_JOY_MOVE_PORT爲高電平則調用halGetJoyKeyInput()這個函數根據ADC讀取按鍵的電壓值來判斷是哪個鍵按下了,最後keys就是這些按鍵的組合,因爲這些按鍵是按位定義的,所以剛好將他們相或就能組合在一起,這就是這樣定義的好處。
按鍵查詢函數
void HalKeyPoll (void)
{
uint8 keys = 0;
if (!(HAL_KEY_SW_7_PORT & HAL_KEY_SW_7_BIT)) /* Key is active low */
{
keys |= HAL_KEY_SW_7;
}
if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT)) /* Key is active HIGH */
{
keys |= halGetJoyKeyInput();
}
/* 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;
}
/* Store the current keys for comparation next time */
halKeySavedKeys = keys;
}
else
{
/* Key interrupt handled here */
}
/* Invoke Callback if new keys were depressed */
if (keys && (pHalKeyProcessFunction))
{
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
}
}
按鍵查詢函數就是根據查詢到的按鍵值最終調用按鍵回調函數,這個回調函數由用戶實現。
看下中斷方式的按鍵中斷函數
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )
{
if (HAL_KEY_SW_7_PXIFG & HAL_KEY_SW_7_BIT)
{
halProcessKeyInterrupt();
}
/*
Clear the CPU interrupt flag for Port_0
PxIFG has to be cleared before PxIF
*/
HAL_KEY_SW_7_PXIFG = 0;
HAL_KEY_CPU_PORT_0_IF = 0;
}
這是其中P0的中斷函數,調用的halProcessKeyInterrupt(); 這個函數,
void halProcessKeyInterrupt (void) //按鍵的中斷方式
{
bool valid=FALSE;
if (HAL_KEY_SW_7_PXIFG & HAL_KEY_SW_7_BIT) /* Interrupt Flag has been set */
{
HAL_KEY_SW_7_PXIFG = ~(HAL_KEY_SW_7_BIT); /* Clear Interrupt Flag */
valid = TRUE;
}
if (HAL_KEY_JOY_MOVE_PXIFG & HAL_KEY_JOY_MOVE_BIT) /* Interrupt Flag has been set */
{
HAL_KEY_JOY_MOVE_PXIFG = ~(HAL_KEY_JOY_MOVE_BIT); /* Clear Interrupt Flag */
valid = TRUE;
}
if (valid)
{
osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);
}
}
這就是中斷方式的發送按鍵事件的函數當中斷標誌置位後,將其清零,最終調用系統定時器函數,在HAL_KEY_DEBOUNCE_VALUE即25ms之後發送HAL_KEY_EVENT事件,這個事件會被HAL任務處理函數Hal_ProcessEvent()所接收並處理,這個函數在hal_drivers.c文件中。以上就是按鍵驅動的幾個重要函數。下面看一下整個按鍵處理的流程。
我們看一下main函數第二次調用InitBoard函數此時level!=OB_COLD則
OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
看到沒,這裏採用查詢方式,回調函數配置爲OnBoard_KeyCallback。因爲在HalKeyConfig函數中查詢方式的話調用的是osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE);即經過HAL_KEY_POLLING_VALUE向HAL驅動層發送HAL_KEY_EVENT事件,此時看一下HAL驅動層的任務處理函數Hal_ProcessEvent()
{
......
if (events & HAL_KEY_EVENT)
{
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
/* Check for keys */
HalKeyPoll();
/* if interrupt disabled, do next polling */
if (!Hal_KeyIntEnable)
{
osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
}
#endif // HAL_KEY
return events ^ HAL_KEY_EVENT;
}
.......
}
看到麼,在任務處理函數中調用了按鍵查詢函數HalKeyPoll();這個函數在上面講過了就是查詢是哪個按鍵按下了,如果有按鍵按下就會調用按鍵的回調函數。如果沒有就直接返回了。最後繼續調用osal_start_timerEx延遲100ms向hal層發送按鍵事件。即查詢方式話,系統每隔100ms查詢是否有按鍵按下,如果有就進行相關的處理,如果沒有則繼續查詢。查詢的方式相對來說比較耗CPU,而且如果需要用按鍵的話系統不能進入睡眠模式,中斷方式就不同,可以在任何時候處理按鍵。其實按鍵對於我們開發ZigBee項目來說很重要,比如按下一個鍵之後協調器建立網絡,或者按下某個鍵,協調器就允許其他節點來跟它綁定!但是在最終的產品中可以不需要按鍵模塊,這個只是在開發過程中比較有用!
以上就是z-stack的整個key驅動的詳解過程,如果有理解不到位的地方還希望指出來!