中科蓝讯SDK开发——SDK按键功能简析
中科蓝讯SDK开发——SDK按键功能简析
在使用中科蓝讯芯片开发时候,一些 UI 上的功能通常都会使用到按键操作,当然自己在软件中添加按键功能并不困难,但是中科蓝讯的 SDK 中通常都已经做好了常用的按键功能检测,足以满足大部分操作需求,并且中科蓝讯不同芯片的 SDK 按键的操作判断都是类似的,直接使用 SDK 中做好的按键检测可以很大程度上帮助开发者节省开发时间,下面为大家介绍如何使用 SDK 中做好的按键检测实现相应功能。
一、按键的类型
有些芯片只有单独的一个引脚作为 POWER KEY;
需要注意的是,使用外部的 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;
........
}
}
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;
}
烧录工具--按键配置
延伸阅读
共同关键字:中科蓝讯 SDK
中科蓝讯 SDK 开发环境安装及 Downloader 配置
中科蓝讯 SDK 开发——工程浅析
中科蓝讯 SDK 开发——耳机充电配置
中科蓝讯 SDK TWS 组队和蓝牙配对过程分析
中科蓝讯 SDK 开发——TWS 左右声道分配
参考文献:
[2] 蓝皮书 TWS 开发板使用说明 — 中科蓝讯