一.、按键的类型
在 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 中的按键检测
下面以 BT892X 的按键检测为例简单讲解 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
......
}
拿到 BT892XA2 的开发板,可以看到每一侧的耳机板上对应有三个按键,这三个按键在是接在 PWRKEY(PB5) 上,并且在 PWRKEY 中使用了 AD KEY 的功能,因此一个 PWRKEY IO 可以连接三个按键,那么就以最右边这个按键为例讲解按键检测的大致流程。
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 配置按键功能
除了上面列出来的在软件上去添加需要的按键操作外,对于一些简单的按键操作,开发者可以在 setting 中去配置实现,可以看到 func_bt_message() 中的按键处理下,会执行 user_def_key_msg(xcfg_cb.user_def_kfive_sel)。
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)。
以上就是要分享的全部内容,内容有错误或者遗漏欢迎大家指出,有其他问题也可以在评论区提出,共同学习讨论。
参考文献:
[1] 蓝皮书 Downloader 可视化配置 — 中科蓝讯
[2] 蓝皮书 TWS 开发板使用说明 — 中科蓝讯
评论