中科藍訊SDK開發——SDK按鍵功能簡析
在使用中科藍訊芯片開發時候,一些 UI 上的功能通常都會使用到按鍵操作,當然自己在軟件中添加按鍵功能并不困難,但是中科藍訊的 SDK 中通常都已經做好了常用的按鍵功能檢測,足以滿足大部分操作需求,并且中科藍訊不同芯片的 SDK 按鍵的操作判斷都是類似的,直接使用 SDK 中做好的按鍵檢測可以很大程度上幫助開發者節省開發時間,下面為大家介紹如何使用 SDK 中做好的按鍵檢測實現相應功能。
一、按鍵的類型
在 SDK 中按鍵可以大致分為四個類型:
PWRKEY:POWER KEY 平時使用最多的按鍵,按鍵是接到芯片具有 WK 功能的引腳上,可以用做軟件開關機按鍵,例如 BT892X 系列的 PB5,
有些芯片只有單獨的一個引腳作為 POWER KEY;
AD KEY: AD KEY 通過在按鍵按下時 IO 口 ADC 采樣到的電壓值來判斷當前按下的按鍵,在 IO 口不夠用,但又需要多個按鍵時推薦使用,PWRKEY 也可以作為 AD KEY;
IO KEY:即一個 IO 對應一個按鍵。
TKEY: Touch KEY 這個是用的是芯片內部的 Touch IC,具體的配置可以看我另一篇介紹 Touch KEY 的文章《中科藍訊 SDK 開發——BT892XA2 芯片內置 Touch 使用》。
需要注意的是,使用外部的 Touch 芯片并不屬于這個類型。二、SDK 中的按鍵檢測
首先在 config.h 中打開需要使用的按鍵宏開關,對應在 setting 中的按鍵配置頁面也需要打開(本文章所粘貼代碼均為 SDK 源碼,由于部分代碼內容與所述內容無關或源碼過長,粘貼的源碼有省略,用“......”表示,完整內容可以查看 SDK)。
/***************************************************************************** * Module : User按鍵配置 (可以同時選擇多組按鍵) *****************************************************************************/ #define USER_ADKEY 1 //ADKEY的使用, 0為不使用 #define USER_ADKEY2 0 //ADKEY2的使用,0為不使用 #define USER_PWRKEY 1 //PWRKEY的使用,0為不使用 #define USER_IOKEY 1 //IOKEY的使用, 0為不使用
按鍵處理的相關函數都會在 bsp_key.c 中,按鍵的初始化在 key_init(void),在初始化中會對打開的按鍵功能進行初始化,例如配置 AD KEY 的 ADC 的通道,配置 IO KEY 對應的 IO 的初始化等。
void key_init(void) { u16 adc_ch = 0; key_var_init(); #if USER_IOKEY io_key_init(); #endif #if USER_ADKEY if (xcfg_cb.user_adkey_en) { adc_ch |= BIT(ADKEY_CH); #if ADKEY_PU10K_EN adcch_io_pu10k_enable(ADKEY_CH); //開內部10K上拉 #endif // ADKEY_PU10K_EN } #endif // USER_ADKEY ...... #if USER_PWRKEY if (sys_cb.wko_pwrkey_en) { #if POWER_KEY_USE_HIGHLEVEL adcch_io_pd10k_enable(ADCCH_WKO); #else adcch_io_pu10k_enable(ADCCH_WKO); #endif adc_ch |= BIT(ADCCH_WKO); pwr_usage_id = pwrkey_table[0].usage_id; if (xcfg_cb.pwrkey_config_en) { pwr_usage_id = key_config_table[xcfg_cb.pwrkey_num0]; } #if POWER_KEY_USE_HIGHLEVEL RTCCON13 |= BIT(0) | BIT(8) | BIT(12); //wk pin0 wakeup, input, pulldown10k enable #else RTCCON13 |= BIT(0) | BIT(4) | BIT(12); //wk pin0 wakeup, input, pullup10k enable #endif } else #endif // USER_PWRKEY { GPIOBDE &= ~BIT(5); GPIOBDIR |= BIT(5); GPIOBPU &= ~BIT(5); GPIOBPD &= ~BIT(5); RTCCON13 &= ~(BIT(0) | BIT(4) | BIT(12)); } ...... bsp_tkey_init(); }
按鍵的檢測 bsp_key_scan() 函數是放在 5ms 的中斷處理函數中連續檢測。
AT(.com_text.timer) void usr_tmr5ms_thread(void) { tmr5ms_cnt++; //5ms timer process dac_fade_process(); #if !USER_KEY_KNOB2_EN bsp_key_scan(); #endif ...... }

bsp_key_scan() 中的按鍵檢測主要可以看 key_val = bsp_key_scan_do()、key = bsp_key_process(key_val)、 msg_enqueue(key) 三個部分。
u8 bsp_key_scan(void) { u8 key_val; u16 key = NO_KEY; key_val = bsp_key_scan_do(); ...... key = bsp_key_process(key_val); ...... msg_enqueue(key); return key_val; }
第一部分,PWRKEY 按鍵按下,bsp_key_scan_do() 中, get_adc_val() 獲取對應 ADC 通道的 ADC 值,由于不同的按鍵串聯的電阻不同,按下按鍵后,PWRKEY IO 采集到的 ADC 值也會不同,最終在保存在 adc_cb.wko_val 中,并在 get_pwrkey() 中做具體按鍵的檢測。
u8 bsp_key_scan_do(void)
{
u8 key_val = NO_KEY;
if (!get_adc_val()) {
return NO_KEY;
}
#if USER_TKEY
key_val = bsp_tkey_scan();
#endif
#if USER_ADKEY
if (key_val == NO_KEY) {
key_val = get_adkey(adc_cb.key_val, xcfg_cb.user_adkey_en);
}
#endif // USER_ADKEY
#if USER_ADKEY2
if (key_val == NO_KEY) {
key_val = get_adkey2();
}
#endif // USER_ADKEY2
#if USER_PWRKEY
if ((key_val == NO_KEY) && (!PWRKEY_2_HW_PWRON)) {
key_val = get_pwrkey();
}
#endif // USER_PWRKEY
......
return key_val;
}
get_pwrkey() 通過判斷 adc_cb.wko_val 的值對應到定義好的 KEY 表中,得到對應的按鍵,調試過程中如果出現有的按鍵沒反應或者按鍵對應不上的情況,那么就打印出 adc_cb.key_val 的值,根據實際的值去修改 pwrkey_table[],或排查硬件設計問題。
static u8 get_pwrkey(void)
{
u8 num = 0;
u8 *ptr;
// //配置工具是否使能了PWRKEY?
if ((!xcfg_cb.user_pwrkey_en) && (!PWRKEY_2_HW_PWRON)) {
return NO_KEY;
}
// printf("adc_cb.wko_val == %d",adc_cb.wko_val);
while ((u8)adc_cb.wko_val > pwrkey_table[num].adc_val) {
num++;
}
//工具配置了PWRKEY的按鍵定義?
ptr = get_pwrkey_configure(num);
if (ptr != NULL) {
#if POWER_KEY_USE_HIGHLEVEL
if(num > 5){
#else
if (num > 4) {
#endif
return NO_KEY;
}
return key_config_table[*(ptr+num)];
}
return pwrkey_table[num].usage_id;
}
例如按下開發板最右邊的按鍵,此時 adc_cb.wko_val 中保存的 ADC 值為 0x8E ,對應 pwrkey_table[],0x70< adc_cb.wko_val <0xAF,那么可以得出按下的按鍵是 KEY_VOL_DOWN。
const adkey_tbl_t pwrkey_table[6] = {
#if POWER_KEY_USE_HIGHLEVEL
{0x0A, NO_KEY}, //P/P POWER 0
{0x34, NO_KEY}, //PREV/VOL- 1.5K
{0x70, NO_KEY}, //NEXT/VOL+ 3.9K
{0xAF, NO_KEY}, //VOL- 15K
{0xE1, KEY_PLAY_PWR_USER_DEF}, //VOL+ 33K
{0xFF, KEY_PLAY_PWR_USER_DEF},
#else
{0x0A, KEY_PLAY_PWR_USER_DEF}, //P/P POWER 0
{0x34, KEY_PREV_VOL_DOWN}, //PREV/VOL- 1.5K
{0x70, KEY_NEXT_VOL_UP}, //NEXT/VOL+ 3.9K
{0xAF, KEY_VOL_DOWN}, //VOL- 15K
{0xE1, KEY_VOL_UP}, //VOL+ 33K
{0xFF, NO_KEY},
#endif
};
第二部分,獲取到對應的按鍵后,回到 bsp_key_scan() ,在 bsp_key_process() 中,會返回對應的按鍵操作,如果需要檢測多擊功能需要打開多擊檢測的宏 USER_MULTI_PRESS_EN。
u16 bsp_key_process(u16 key_val)
{
u16 key_return = NO_KEY;
......
key_return = key_process(key_val);
//雙擊處理
#if USER_MULTI_PRESS_EN
//配置工具是否使能了按鍵2/3/4/5擊功能?
if (xcfg_cb.user_key_multi_press_en) {
key_return = key_multi_press_process(key_return);
}
#endif
return key_return;
#endif
}
這里檢測按鍵原廠在底層中已經做好單擊到五擊和長按的按鍵檢測,對應的返回值可以看到 bsp_key.h 中,以單擊按鍵 KEY_VOL_DOWN 為例,整個單擊過程會收到對應的按鍵消息宏為 K_VOL_DOWN(短按按下)、KU_VOL_DOWN(短按抬起),其他的按鍵操作也類似。這里也可以給大家一個小提示,開發板中只去做到五擊,如果需要更多擊的檢測實際上,去計數 KU_VOL_DOWN(短按抬起)的次數是可以簡單實現多擊功能的。
#define K_VOL_DOWN (KEY_VOL_DOWN | KEY_SHORT)
#define KU_VOL_DOWN (KEY_VOL_DOWN | KEY_SHORT_UP)
#define KL_VOL_DOWN (KEY_VOL_DOWN | KEY_LONG)
#define KLU_VOL_DOWN (KEY_VOL_DOWN | KEY_LONG_UP)
#define KH_VOL_DOWN (KEY_VOL_DOWN | KEY_HOLD)
#define KD_VOL_DOWN (KEY_VOL_DOWN | KEY_DOUBLE)
#define KTH_VOL_DOWN (KEY_VOL_DOWN | KEY_THREE)
最后 bsp_key_scan() 中的第三部分,msg_enqueue(key),則是將按鍵消息發到消息隊列中處理,這里按鍵按下的操作 KU_VOL_DOWN,在 SDK 中可以看到,最終 KEY_VOL_DOWN 按鍵單擊的消息處理會在 func_message(u16 msg) 中執行音量減。
void func_message(u16 msg)
{
switch (msg) {
......
case KU_VOL_DOWN:
case KL_VOL_DOWN:
case KH_VOL_DOWN:
case KL_VOL_UP_DOWN:
case KH_VOL_UP_DOWN:
if(bt_is_support_vol_ctrl() && bsp_bt_hid_vol_change(HID_KEY_VOL_DOWN)){
if (!sys_cb.incall_flag) {
#if WARNING_MIN_VOLUME
if (sys_cb.vol == 0) {
if (func_cb.mp3_res_play) {
func_cb.mp3_res_play(RES_BUF_MAX_VOL_MP3, RES_LEN_MAX_VOL_MP3);
}
}
#endif // WARNING_MIN_VOLUME
}
}else{
if (sys_cb.incall_flag) {
bsp_bt_call_volume_msg(KU_VOL_DOWN);
} else {
bsp_set_volume(bsp_volume_dec(sys_cb.vol));
bsp_bt_vol_change();
printf("current volume: %d\n", sys_cb.vol);
#if WARNING_MIN_VOLUME
if (sys_cb.vol == 0) {
if (func_cb.mp3_res_play) {
func_cb.mp3_res_play(RES_BUF_MAX_VOL_MP3, RES_LEN_MAX_VOL_MP3);
}
}
#endif // WARNING_MIN_VOLUME
if (func_cb.set_vol_callback) {
func_cb.set_vol_callback(0);
}
}
}
break;
........
}
}
以上三個部分,大致就是按鍵的檢測過程,都是 SDK 中已經做好的檢測功能,對于開發者來說,僅需要找到對應的按鍵操作消息宏,并在對應的 message() 函數中添加想要實現的功能即可。
三、Setting 配置按鍵功能
void func_bt_message(u16 msg)
{
int klu_flag = 0;
switch (msg) {
......
case KL_PLAY_USER_DEF:
......
user_def_key_msg(xcfg_cb.user_def_kl_sel);
......
break;
//SIRI, NEXT, PREV在長按抬鍵的時候響應,避免關機前切歌或呼SIRI了
case KLU_PLAY_PWR_USER_DEF:
if (f_bt.user_kl_flag) {
user_def_key_msg(xcfg_cb.user_def_kl_sel);
f_bt.user_kl_flag = 0;
}
break;
......
///三擊按鍵處理
case KTH_PLAY_USER_DEF:
case KTH_PLAY_PWR_USER_DEF:
user_def_key_msg(xcfg_cb.user_def_kt_sel);
break;
///四擊按鍵處理
case KFO_PLAY_USER_DEF:
case KFO_PLAY_PWR_USER_DEF:
user_def_key_msg(xcfg_cb.user_def_kfour_sel);
break;
///五擊按鍵處理
case KFI_PLAY_USER_DEF:
case KFI_PLAY_PWR_USER_DEF:
if (xcfg_cb.user_def_kfive_sel) {
user_def_key_msg(xcfg_cb.user_def_kfive_sel);
}
break;
......
}
user_def_key_msg() 會根據 setting 中配置的內容去完成相應的功能;
///檢查USER_DEF按鍵消息處理
bool user_def_key_msg(u8 func_sel)
{
u16 msg = NO_MSG;
if (!user_def_func_is_ready(func_sel)) {
return false;
}
if (func_sel == UDK_REDIALING) {
bt_call_redial_last_number(); //回撥電話
if (func_cb.mp3_res_play) {
func_cb.mp3_res_play(RES_BUF_REDIALING_MP3, RES_LEN_REDIALING_MP3);
}
} else if (func_sel == UDK_SIRI) { //SIRI
bt_hfp_siri_switch();
} else if (func_sel == UDK_NR) { //NR
bt_ctl_nr_sta_change();
} else if (func_sel == UDK_PREV) { //PREV
if(xcfg_cb.user_def_lr_en) {
msg = func_bt_tws_get_channel()? KU_PREV : KU_NEXT;
} else {
msg = KU_PREV;
}
user_def_track_msg(msg);
} else if (func_sel == UDK_NEXT) { //NEXT
if(xcfg_cb.user_def_lr_en) {
msg = func_bt_tws_get_channel()? KU_NEXT : KU_PREV;
} else {
msg = KU_NEXT;
}
user_def_track_msg(msg);
} else if (func_sel == UDK_MODE) { //MODE
func_message(KU_MODE);
} else if (func_sel == UDK_PHOTO) {
return bsp_bt_hid_photo(HID_KEY_VOL_UP); //拍照
} else if (func_sel == UDK_HOME) {
return bt_hid_consumer(HID_KEY_IOS_HOME); //IOS Home按鍵功能
} else if (func_sel == UDK_LANG) {
func_bt_switch_voice_lang(); //中英文切換
} else if (func_sel == UDK_PLAY_PAUSE) {
bt_music_play_pause();
} else if (func_sel == UDK_DUT) { //CBT 測試模式
if(func_cb.sta != FUNC_BT_DUT){
func_cb.sta = FUNC_BT_DUT;
sys_cb.discon_reason = 0;
}
} else if (func_sel == UDK_LOW_LATENCY) {
bool low_latency = bt_is_low_latency();
if (low_latency) {
bsp_tws_res_music_play(TWS_RES_MUSIC_MODE);
} else {
bsp_tws_res_music_play(TWS_RES_GAME_MODE);
}
} else if (func_sel == UDK_TWS_CLEAR){
#if BT_TWS_BONDING_CLEAR_EN
bt_tws_clr_bondlink_info();
#endif
} else { //VOL+, VOL-
func_message(get_user_def_vol_msg(func_sel));
}
return true;
}
燒錄工具--按鍵配置
例如在 setting 中,可以直接對 PWRKEY 配置對應的按鍵的操作,例如配置雙擊回撥,三擊切歌,等等。同樣的其他類型的按鍵也是可以在 setting 中直接配置功能,如果這里的配置不能滿足需求,則在代碼中對應的按鍵消息處理中添加代碼實現功能即可,此時不使用 setting 配置按鍵,可以將 setting 中按鍵配置關閉,或直接在代碼中注釋掉 user_def_key_msg(xcfg_cb.user_def_kt_sel)。

以上就是要分享的全部內容,內容有錯誤或者遺漏歡迎大家指出,有其他問題也可以在評論區提出,共同學習討論。
延伸閱讀
共同關鍵字:中科藍訊 SDK
中科藍訊 SDK 開發環境安裝及 Downloader 配置
中科藍訊 SDK 開發——工程淺析
中科藍訊 SDK 開發——耳機充電配置
中科藍訊 SDK TWS 組隊和藍牙配對過程分析
中科藍訊 SDK 開發——TWS 左右聲道分配
參考文獻
[1] 藍皮書 Downloader 可視化配置 — 中科藍訊
[2] 藍皮書 TWS 開發板使用說明 — 中科藍訊