AD按键机制完整文档
目录
概述
AD按键(ADC Key)是一种通过模拟数字转换器(ADC)检测按键状态的按键方案。通过在按键电路中串联不同阻值的电阻,形成分压电路,ADC采样得到不同的电压值,从而识别不同的按键。
主要特点
- 节省IO口:多个按键共用一个ADC通道
- 成本低:只需要一个ADC通道和若干电阻
- 支持多按键:最多支持10个按键(ADKEY_MAX_NUM = 10)
- 灵活配置:支持内部/外部上拉,可配置电阻值
核心文件
apps/common/key/adkey.c# AD按键驱动实现
apps/common/key/adkey_rtcvdd.c # RTCVDD AD按键实现
apps/common/key/key_driver.c # 按键驱动框架
include_lib/system/device/adkey.h# AD按键头文件
include_lib/system/device/key_driver.h # 按键驱动头文件
cpu/br23/adc_api.c # ADC底层API硬件层
1. 硬件电路原理
VDD (3.3V)
|
R_UP (上拉电阻: 10K内部 或 22K外部)
|
+-----> ADC_PIN (连接到ADC通道)
|
R_KEY (按键电阻: 0R, 3K, 6.2K, 9.1K, 15K, 24K, 33K, 51K, 100K, 220K)
|
SW (按键开关)
|
GND2. 分压计算公式
当按键按下时,ADC采样到的电压值:
ADC_VALUE = 0x3FF * R_KEY / (R_KEY + R_UP)其中:
0x3FF(1023) 是ADC的最大值(10位ADC)R_KEY是按键串联的电阻值R_UP是上拉电阻值
3. GPIO配置
// GPIO配置(adkey.c: adkey_init)gpio_set_die(adkey_pin, 0); // 禁用数字输入gpio_set_direction(adkey_pin, 1); // 设置为输入gpio_set_pull_down(adkey_pin, 0); // 禁用下拉if (extern_up_en) {
gpio_set_pull_up(adkey_pin, 0); // 使用外部上拉,禁用内部上拉} else {
gpio_set_pull_up(adkey_pin, 1); // 使用内部上拉(10K)}4. ADC通道定义
// 支持的ADC通道(adc_api.h)#define AD_CH_PA1(0x0)#define AD_CH_PA5(0x1)#define AD_CH_PA6(0x2)#define AD_CH_PA10 (0x3)#define AD_CH_PB1(0x5)#define AD_CH_PB3(0x6)// ... 更多通道驱动层
1. 数据结构
平台数据结构
struct adkey_platform_data {u8 enable;// 是否使能AD按键u8 adkey_pin; // AD按键对应的GPIO引脚u8 extern_up_en;// 是否使用外部上拉(1:外部, 0:内部10K)u32 ad_channel; // ADC通道u16 ad_value[ADKEY_MAX_NUM];// 电压阈值数组(10个)u8key_value[ADKEY_MAX_NUM]; // 按键值数组(10个)};扫描参数结构
struct key_driver_para {const u32 scan_time;// 按键扫描频率(单位: ms)u8 last_key;// 上一次的按键值u8 filter_value;// 用于按键消抖u8 filter_cnt;// 消抖累加值const u8 filter_time; // 消抖延时次数const u8 long_time; // 长按判定次数const u8 hold_time; // HOLD判定次数u8 press_cnt; // 按下计数u8 click_cnt; // 单击次数u8 click_delay_cnt; // 连击延时计数const u8 click_delay_time;// 连击延时次数u8 notify_value;// 待发送按键值u8 key_type;// 按键类型(KEY_DRIVER_TYPE_AD)u8(*get_value)(void); // 获取按键值函数指针};2. 初始化流程
// 1. ADC初始化(adc_api.c: adc_init)void adc_init() {// 配置ADC时钟// 配置ADC寄存器// 初始化VBAT和VBG采样// 注册ADC中断// 启动ADC定时扫描(2ms)}// 2. AD按键初始化(adkey.c: adkey_init)int adkey_init(const struct adkey_platform_data *adkey_data) {
__this = adkey_data;// 添加ADC采样通道adc_add_sample_ch(__this->ad_channel);// 配置GPIOgpio_set_die(__this->adkey_pin, 0);
gpio_set_direction(__this->adkey_pin, 1);
gpio_set_pull_down(__this->adkey_pin, 0);if (__this->extern_up_en) {
gpio_set_pull_up(__this->adkey_pin, 0);// 外部上拉} else {
gpio_set_pull_up(__this->adkey_pin, 1);// 内部上拉}return 0;
}// 3. 注册按键扫描定时器(key_driver.c: key_driver_init)int key_driver_init(void) {extern const struct adkey_platform_data adkey_data;extern struct key_driver_para adkey_scan_para;err = adkey_init(&adkey_data);if (err == 0) {// 注册10ms定时扫描sys_s_hi_timer_add((void *)&adkey_scan_para,
key_driver_scan,
adkey_scan_para.scan_time);
}
}3. ADC采样机制
// ADC采样队列(adc_api.c)struct adc_info_t {u32 ch;// ADC通道u16 value; // 采样值};static struct adc_info_t adc_queue[ADC_MAX_CH];// 添加采样通道u32 adc_add_sample_ch(u32 ch) {for (i = 0; i < ADC_MAX_CH; i++) {if (adc_queue[i].ch == ch) {break;// 已存在} else if (adc_queue[i].ch == -1) {
adc_queue[i].ch = ch;
adc_queue[i].value = 1;break;// 添加成功}
}return i;
}// 获取ADC值u32 adc_get_value(u32 ch) {for (int i = 0; i < ADC_MAX_CH; i++) {if (adc_queue[i].ch == ch) {return adc_queue[i].value;
}
}return 0;
}// ADC定时扫描(2ms周期)void adc_scan(void *priv) {
u8 next_ch = adc_get_next_ch(cur_ch);
adc_queue[cur_ch].value = adc_sample(adc_queue[next_ch].ch);
cur_ch = next_ch;
}4. 按键值获取
// 获取AD按键值(adkey.c: ad_get_key_value)u8 ad_get_key_value(void) {
u8 i;if (!__this->enable) {return NO_KEY;
}// 获取ADC采样值ad_data = adc_get_value(__this->ad_channel);// 遍历电压阈值数组,匹配按键for (i = 0; i < ADKEY_MAX_NUM; i++) {if ((ad_data <= __this->ad_value[i]) &&
(__this->ad_value[i] < 0x3ffL)) {return __this->key_value[i];// 返回对应的按键值}
}return NO_KEY;// 没有按键按下}扫描层
1. 扫描参数配置
// AD按键扫描参数(adkey.c)struct key_driver_para adkey_scan_para = {
.scan_time= 10, // 扫描频率: 10ms.last_key = NO_KEY, // 上一次按键值.filter_time= 2,// 消抖延时: 2次(20ms).long_time= 75, // 长按判定: 75次(750ms).hold_time= (75 + 15),// HOLD判定: 90次(900ms).click_delay_time = 20, // 连击延时: 20次(200ms).key_type = KEY_DRIVER_TYPE_AD,
.get_value= ad_get_key_value,
};2. 按键扫描状态机
// 按键扫描函数(key_driver.c: key_driver_scan)static void key_driver_scan(void *_scan_para) {struct key_driver_para *scan_para = _scan_para;// 1. 获取当前按键值cur_key_value = scan_para->get_value();// 2. 按键消抖处理if (cur_key_value != scan_para->filter_value) {
scan_para->filter_cnt = 0;
scan_para->filter_value = cur_key_value;return;// 第一次检测,返回}if (scan_para->filter_cnt < scan_para->filter_time) {
scan_para->filter_cnt++;return;// 消抖未完成}// 3. 按键状态判断if (cur_key_value != scan_para->last_key) {// 按键状态改变if (cur_key_value == NO_KEY) {// 按键抬起if (scan_para->press_cnt >= scan_para->long_time) {
key_event = KEY_EVENT_UP;// 长按后抬起goto _notify;
}
scan_para->click_delay_cnt = 1;// 开始连击延时} else {// 按键按下scan_para->press_cnt = 1;if (cur_key_value != scan_para->notify_value) {
scan_para->click_cnt = 1;// 新按键,重新计数scan_para->notify_value = cur_key_value;
} else {
scan_para->click_cnt++;// 连击计数}
}
} else {// 按键状态未改变if (cur_key_value == NO_KEY) {// 没有按键按下if (scan_para->click_cnt > 0) {// 有待处理的按键消息if (scan_para->click_delay_cnt > scan_para->click_delay_time) {// 连击延时到,判断点击次数if (scan_para->click_cnt >= 5) {
key_event = KEY_EVENT_FIRTH_CLICK;// 五击} else if (scan_para->click_cnt >= 4) {
key_event = KEY_EVENT_FOURTH_CLICK; // 四击} else if (scan_para->click_cnt >= 3) {
key_event = KEY_EVENT_TRIPLE_CLICK; // 三击} else if (scan_para->click_cnt >= 2) {
key_event = KEY_EVENT_DOUBLE_CLICK; // 双击} else {
key_event = KEY_EVENT_CLICK;// 单击}
key_value = scan_para->notify_value;goto _notify;
} else {
scan_para->click_delay_cnt++;// 继续延时}
}
} else {// 按键持续按下scan_para->press_cnt++;if (scan_para->press_cnt == scan_para->long_time) {
key_event = KEY_EVENT_LONG;// 长按} else if (scan_para->press_cnt == scan_para->hold_time) {
key_event = KEY_EVENT_HOLD;// HOLDscan_para->press_cnt = scan_para->long_time;
} else {return;// 继续等待}
key_value = cur_key_value;goto _notify;
}
}
_notify:// 4. 发送按键事件key_value &= ~BIT(7);// 清除特殊标志位e.type = SYS_KEY_EVENT;
e.u.key.init = 1;
e.u.key.type = scan_para->key_type;
e.u.key.event = key_event;
e.u.key.value = key_value;
e.u.key.tmr = timer_get_ms();
scan_para->click_cnt = 0;
scan_para->notify_value = NO_KEY;
e.arg = (void *)DEVICE_EVENT_FROM_KEY;if (key_event_remap(&e)) {
sys_event_notify(&e);// 发送系统事件}
_scan_end:
scan_para->last_key = cur_key_value;
}3. 按键事件类型
// 按键事件定义(event.h)enum {KEY_EVENT_CLICK, // 0: 单击KEY_EVENT_LONG,// 1: 长按KEY_EVENT_HOLD,// 2: HOLD(持续长按)KEY_EVENT_UP,// 3: 抬起KEY_EVENT_DOUBLE_CLICK,// 4: 双击KEY_EVENT_TRIPLE_CLICK,// 5: 三击KEY_EVENT_FOURTH_CLICK,// 6: 四击KEY_EVENT_FIRTH_CLICK, // 7: 五击KEY_EVENT_USER,// 8: 用户自定义KEY_EVENT_MAX,
};4. 时间计算
| 事件类型 | 计算公式 | 默认值 |
|---|---|---|
| 消抖时间 | filter_time × scan_time | 2 × 10ms = 20ms |
| 长按时间 | long_time × scan_time | 75 × 10ms = 750ms |
| HOLD时间 | hold_time × scan_time | 90 × 10ms = 900ms |
| 连击延时 | click_delay_time × scan_time | 20 × 10ms = 200ms |
映射层
1. 按键映射表结构
// 按键映射表类型(task_key.c)typedef const u16(*type_key_ad_table)[KEY_EVENT_MAX];// 不同模式的按键表extern const u16 bt_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 music_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 fm_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 record_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 linein_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 rtc_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 pc_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 spdif_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];extern const u16 idle_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX];2. 按键表管理
// 按键表映射数组(task_key.c)static const type_key_ad_table ad_table[APP_TASK_MAX_INDEX] = {
[APP_BT_TASK] = bt_key_ad_table,
[APP_MUSIC_TASK]= music_key_ad_table,
[APP_FM_TASK] = fm_key_ad_table,
[APP_RECORD_TASK] = record_key_ad_table,
[APP_LINEIN_TASK] = linein_key_ad_table,
[APP_RTC_TASK]= rtc_key_ad_table,
[APP_PC_TASK] = pc_key_ad_table,
[APP_SPDIF_TASK]= spdif_key_ad_table,
[APP_IDLE_TASK] = idle_key_ad_table,
};3. 按键事件映射函数
// 按键事件转消息(task_key.c: adkey_event_to_msg)u16 adkey_event_to_msg(u8 cur_task, struct key_event *key) {if (ad_table[cur_task] == NULL) {return KEY_NULL;
}
type_key_ad_table cur_task_ad_table = ad_table[cur_task];// 通过按键值和事件类型索引,获取对应的消息return cur_task_ad_table[key->value][key->event];
}4. 按键表示例(BT模式)
// BT模式的AD按键表(adkey_table.c)const u16 bt_key_ad_table[KEY_AD_NUM_MAX][KEY_EVENT_MAX] = {// [按键值] = {单击, 长按, HOLD, 抬起, 双击, 三击}[0] = {
KEY_CHANGE_MODE,// 单击:切换模式KEY_POWEROFF, // 长按:关机KEY_POWEROFF_HOLD,// HOLD:关机保持KEY_NULL, // 抬起:无KEY_TWS_SEARCH_REMOVE_PAIR,// 双击:TWS配对KEY_NULL// 三击:无},
[1] = {
KEY_MUSIC_PREV, // 单击:上一曲KEY_VOL_DOWN, // 长按:音量减KEY_VOL_DOWN, // HOLD:持续音量减KEY_NULL,
KEY_NULL,
KEY_NULL
},
[2] = {
KEY_MUSIC_PP, // 单击:播放/暂停KEY_CALL_HANG_UP, // 长按:挂断电话KEY_NULL,
KEY_NULL,
KEY_CALL_LAST_NO, // 双击:回拨KEY_NULL
},
[3] = {
KEY_MUSIC_NEXT, // 单击:下一曲KEY_VOL_UP, // 长按:音量加KEY_VOL_UP, // HOLD:持续音量加KEY_NULL,
KEY_NULL,
KEY_NULL
},
[4] = {
KEY_NULL,
KEY_MODE, // 长按:模式切换KEY_NULL,
KEY_NULL,
KEY_NULL,
KEY_NULL
},
};5. 按键重映射
// 按键事件重映射(app_task_switch.c: app_key_event_remap)int app_key_event_remap(struct sys_event *e) {struct key_event *key = &e->u.key;int msg = KEY_NULL;switch (key->type) {case KEY_DRIVER_TYPE_AD:case KEY_DRIVER_TYPE_RTCVDD_AD:// AD按键事件转消息msg = adkey_event_to_msg(app_curr_task, key);break;case KEY_DRIVER_TYPE_IO:// IO按键事件转消息msg = iokey_event_to_msg(app_curr_task, key);break;case KEY_DRIVER_TYPE_IR:// 红外按键事件转消息msg = irkey_event_to_msg(app_curr_task, key);break;// ... 其他按键类型}if (msg != KEY_NULL) {
key->value = msg;// 更新按键值为消息return true;
}return false;
}应用层
1. 按键消息处理流程
// 应用层按键消息处理(app_common.c: app_common_key_msg_deal)int app_common_key_msg_deal(struct sys_event *event) {int ret = false;struct key_event *key = &event->u.key;int key_event = event->u.key.event;int key_value = event->u.key.value;if (key_event == KEY_NULL) {return false;
}// UI接管检查if (key_is_ui_takeover()) {
ui_key_msg_post(key_event);return false;
}// 处理按键消息switch (key_event) {case KEY_POWEROFF:// 关机处理break;case KEY_CHANGE_MODE:// 模式切换break;case KEY_MUSIC_PP:// 播放/暂停break;case KEY_VOL_UP:// 音量加break;case KEY_VOL_DOWN:// 音量减break;// ... 更多按键处理}return ret;
}2. 按键消息定义
// 按键消息定义(key_event_deal.h)enum {// 基础按键KEY_POWER_START = 0,
KEY_POWER,
KEY_PREV,
KEY_NEXT,
KEY_OK,
KEY_CANCLE,
KEY_MENU,
KEY_MODE,// 音量控制KEY_VOLUME_DEC = 10,
KEY_VOLUME_INC,
KEY_VOL_UP,
KEY_VOL_DOWN,// 音乐控制KEY_MUSIC_PP,
KEY_MUSIC_PREV,
KEY_MUSIC_NEXT,
KEY_MUSIC_STOP,// 通话控制KEY_CALL_LAST_NO,
KEY_CALL_HANG_UP,
KEY_CALL_ANSWER,// 模式切换KEY_CHANGE_MODE,
KEY_BT_MODE,
KEY_MUSIC_MODE,
KEY_FM_MODE,
KEY_LINEIN_MODE,// 电源控制KEY_POWEROFF,
KEY_POWEROFF_HOLD,// TWS功能KEY_TWS_SEARCH_REMOVE_PAIR,// 其他功能KEY_REVERB_OPEN,
KEY_EQ_MODE,
KEY_MINOR_OPT,
KEY_NULL = 0xFFFF,
};3. 按键音效
// 按键音效播放(key_driver.c)#if TCFG_KEY_TONE_ENaudio_key_tone_play();// 播放按键提示音#endif配置说明
1. 板级配置文件
配置文件位置:apps/soundbox/board/br23/board_xxx/board_xxx_cfg.h
//*********************************************************************************//// adkey 配置////*********************************************************************************//#define TCFG_ADKEY_ENABLE ENABLE_THIS_MOUDLE// 是否使能AD按键#define TCFG_ADKEY_LED_IO_REUSE DISABLE_THIS_MOUDLE // ADKEY和LED IO复用#define TCFG_ADKEY_PORT IO_PORTB_01 // AD按键端口#define TCFG_ADKEY_AD_CHANNEL AD_CH_PB1 // AD通道#define TCFG_ADKEY_EXTERN_UP_ENABLE ENABLE_THIS_MOUDLE// 是否使用外部上拉// 上拉电阻配置#if TCFG_ADKEY_EXTERN_UP_ENABLE#define R_UP220 // 22K,外部上拉阻值#else#define R_UP100 // 10K,内部上拉默认10K#endif2. 电压阈值配置
// 按键电阻配置(必须从小到大填电阻)#define TCFG_ADKEY_AD0(0) // 0R#define TCFG_ADKEY_AD1(0x3ffL * 30 / (30 + R_UP)) // 3K#define TCFG_ADKEY_AD2(0x3ffL * 62 / (62 + R_UP)) // 6.2K#define TCFG_ADKEY_AD3(0x3ffL * 91 / (91 + R_UP)) // 9.1K#define TCFG_ADKEY_AD4(0x3ffL * 150/ (150+ R_UP)) // 15K#define TCFG_ADKEY_AD5(0x3ffL * 240/ (240+ R_UP)) // 24K#define TCFG_ADKEY_AD6(0x3ffL * 330/ (330+ R_UP)) // 33K#define TCFG_ADKEY_AD7(0x3ffL * 510/ (510+ R_UP)) // 51K#define TCFG_ADKEY_AD8(0x3ffL * 1000 / (1000 + R_UP)) // 100K#define TCFG_ADKEY_AD9(0x3ffL * 2200 / (2200 + R_UP)) // 220K#define TCFG_ADKEY_VDDIO(0x3ffL)// VDDIO// 电压阈值(取相邻两个电阻值的中间值)#define TCFG_ADKEY_VOLTAGE0 ((TCFG_ADKEY_AD0 + TCFG_ADKEY_AD1) / 2)#define TCFG_ADKEY_VOLTAGE1 ((TCFG_ADKEY_AD1 + TCFG_ADKEY_AD2) / 2)#define TCFG_ADKEY_VOLTAGE2 ((TCFG_ADKEY_AD2 + TCFG_ADKEY_AD3) / 2)#define TCFG_ADKEY_VOLTAGE3 ((TCFG_ADKEY_AD3 + TCFG_ADKEY_AD4) / 2)#define TCFG_ADKEY_VOLTAGE4 ((TCFG_ADKEY_AD4 + TCFG_ADKEY_AD5) / 2)#define TCFG_ADKEY_VOLTAGE5 ((TCFG_ADKEY_AD5 + TCFG_ADKEY_AD6) / 2)#define TCFG_ADKEY_VOLTAGE6 ((TCFG_ADKEY_AD6 + TCFG_ADKEY_AD7) / 2)#define TCFG_ADKEY_VOLTAGE7 ((TCFG_ADKEY_AD7 + TCFG_ADKEY_AD8) / 2)#define TCFG_ADKEY_VOLTAGE8 ((TCFG_ADKEY_AD8 + TCFG_ADKEY_AD9) / 2)#define TCFG_ADKEY_VOLTAGE9 ((TCFG_ADKEY_AD9 + TCFG_ADKEY_VDDIO) / 2)3. 按键值配置
// 按键值配置(对应10个按键)#define TCFG_ADKEY_VALUE0 0#define TCFG_ADKEY_VALUE1 1#define TCFG_ADKEY_VALUE2 2#define TCFG_ADKEY_VALUE3 3#define TCFG_ADKEY_VALUE4 4#define TCFG_ADKEY_VALUE5 5#define TCFG_ADKEY_VALUE6 6#define TCFG_ADKEY_VALUE7 7#define TCFG_ADKEY_VALUE8 8#define TCFG_ADKEY_VALUE9 94. 平台数据实例化
// 板级配置文件(board_xxx.c)const struct adkey_platform_data adkey_data = {
.enable = TCFG_ADKEY_ENABLE,
.adkey_pin = TCFG_ADKEY_PORT,
.ad_channel = TCFG_ADKEY_AD_CHANNEL,
.extern_up_en = TCFG_ADKEY_EXTERN_UP_ENABLE,
.ad_value = {
TCFG_ADKEY_VOLTAGE0,
TCFG_ADKEY_VOLTAGE1,
TCFG_ADKEY_VOLTAGE2,
TCFG_ADKEY_VOLTAGE3,
TCFG_ADKEY_VOLTAGE4,
TCFG_ADKEY_VOLTAGE5,
TCFG_ADKEY_VOLTAGE6,
TCFG_ADKEY_VOLTAGE7,
TCFG_ADKEY_VOLTAGE8,
TCFG_ADKEY_VOLTAGE9,
},
.key_value = {
TCFG_ADKEY_VALUE0,
TCFG_ADKEY_VALUE1,
TCFG_ADKEY_VALUE2,
TCFG_ADKEY_VALUE3,
TCFG_ADKEY_VALUE4,
TCFG_ADKEY_VALUE5,
TCFG_ADKEY_VALUE6,
TCFG_ADKEY_VALUE7,
TCFG_ADKEY_VALUE8,
TCFG_ADKEY_VALUE9,
},
};5. AUX检测配置(可选)
// AUX检测电阻配置#define AUX_DET_R2200 // 外部AUX DET电阻220K#define ADC_AUX(0x3ffL*AUX_DET_R / (AUX_DET_R+R_UP))// AUX插入时电压#define ADC_AUX_IN ((TCFG_ADKEY_AD8 + ADC_AUX)/2) // AUX插入阈值// AUX模式下的按键电压(并联电阻计算)#define Rmul(R1,R2)((R1*R2)/(R1+R2))#define ADC_AUX_220(0x3ffL*Rmul(AUX_DET_R,2200)/(Rmul(AUX_DET_R,2200) + R_UP))#define ADC_AUX_100(0x3ffL*Rmul(AUX_DET_R,1000)/(Rmul(AUX_DET_R,1000) + R_UP))// ... 更多AUX按键电压配置// AUX模式按键阈值#define TCFG_ADKEY_AUX_VOLTAGE0((ADC_AUX_3 + ADC_AUX_0)/2)#define TCFG_ADKEY_AUX_VOLTAGE1((ADC_AUX_62+ ADC_AUX_3)/2)// ... 更多AUX阈值配置数据流程
1. 完整数据流程图
┌─────────────────────────────────────────────────────────────────┐
│硬件层│
│按键按下 → 分压电路 → ADC采样 → GPIO输入│
└────────────────────────┬────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────┐
│ADC层 │
│adc_sample() → ADC中断 → adc_queue[ch].value │
│(2ms周期扫描所有ADC通道)│
└────────────────────────┬────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────┐
│驱动层│
│ad_get_key_value() → adc_get_value() → 电压匹配 → 返回按键值 │
└────────────────────────┬────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────┐
│扫描层│
│key_driver_scan() (10ms周期) │
│├─ 获取按键值 │
│├─ 消抖处理 (20ms) │
│├─ 状态判断 │
││ ├─ 按键按下 → 计数开始 │
││ ├─ 按键抬起 → 判断单击/双击/三击 │
││ └─ 按键持续 → 判断长按/HOLD│
│└─ 生成按键事件 │
└────────────────────────┬────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────┐
│映射层│
│key_event_remap() → adkey_event_to_msg() │
│按键值 + 事件类型 → 查表 → 按键消息│
└────────────────────────┬────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────┐
│事件层│
│sys_event_notify() → 发送系统事件│
└────────────────────────┬────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────────┐
│应用层│
│app_common_key_msg_deal() → 处理按键消息 → 执行功能│
└─────────────────────────────────────────────────────────────────┘2. 时序图
时间轴 →
0ms 10ms20ms30ms750ms 900ms 1000ms
│ │ │ │ │ │ │
按键按下 │ │ │ │ │ │
│ │ │ │ │ │ │
├─消抖──┤ │ │ │ │ │
│(20ms) │ │ │ │ │
│ │ │ │ │ │ │
│ └───────┴───────┴───────┤ │ │
│ │ │ │
│长按事件 │ │
│(750ms)│ │
│ │ │ │
│ └───────┤ │
│ │ │
│HOLD事件 │
│(900ms)│
│ │ │
│ └───────┤
│ │
按键抬起抬起事件
│(1000ms)3. 按键状态转换图
┌──────────┐
│NO_KEY│ (初始状态)
└─────┬────┘
│
按键按下
│
↓
┌──────────┐
┌─────┤PRESS │ (按下状态)
│ └─────┬────┘
│ │
│持续按下
│ │
│ ↓
│ ┌──────────┐
│ │ LONG │ (长按状态,750ms)
│ └─────┬────┘
│ │
│持续按下
│ │
│ ↓
│ ┌──────────┐
│ │ HOLD │ (HOLD状态,900ms)
│ └─────┬────┘
│ │
按键抬起按键抬起
│ │
↓ ↓
┌──────────┐ ┌──────────┐
│CLICK │ │UP│ (抬起事件)
└─────┬────┘ └──────────┘
│
连击延时
│
↓
┌──────────┐
│ DOUBLE/│ (双击/三击/...)
│ TRIPLE │
└──────────┘4. 按键值匹配流程
ADC采样值 (ad_data)
│
↓
遍历电压阈值数组 (ad_value[0..9])
│
├─ ad_data <= ad_value[0] ? → 返回 key_value[0]
├─ ad_data <= ad_value[1] ? → 返回 key_value[1]
├─ ad_data <= ad_value[2] ? → 返回 key_value[2]
├─ ad_data <= ad_value[3] ? → 返回 key_value[3]
├─ ad_data <= ad_value[4] ? → 返回 key_value[4]
├─ ad_data <= ad_value[5] ? → 返回 key_value[5]
├─ ad_data <= ad_value[6] ? → 返回 key_value[6]
├─ ad_data <= ad_value[7] ? → 返回 key_value[7]
├─ ad_data <= ad_value[8] ? → 返回 key_value[8]
├─ ad_data <= ad_value[9] ? → 返回 key_value[9]
│
└─ 都不匹配 → 返回 NO_KEY特殊功能
1. IO复用功能
AD按键支持与LED、IR等外设IO复用:
// IO复用配置#define TCFG_ADKEY_LED_IO_REUSEENABLE_THIS_MOUDLE// ADKEY和LED IO复用#define TCFG_ADKEY_IR_IO_REUSE ENABLE_THIS_MOUDLE// ADKEY和IR IO复用#define TCFG_ADKEY_LED_SPI_IO_REUSEENABLE_THIS_MOUDLE// ADKEY和LED SPI IO复用// IO复用进入(adkey.c)u8 adc_io_reuse_enter(u32 ch) {if (ch == __this->ad_channel) {// 挂起LED/IR功能led_io_suspend();
ir_io_suspend();// 配置GPIO为ADC模式gpio_set_die(__this->adkey_pin, 0);
gpio_set_direction(__this->adkey_pin, 1);// ...}return 0;
}// IO复用退出(adkey.c)u8 adc_io_reuse_exit(u32 ch) {if (ch == __this->ad_channel) {// 恢复LED/IR功能led_io_resume();
ir_io_resume();
}return 0;
}2. RTCVDD AD按键
RTCVDD AD按键是一种特殊的AD按键实现,使用RTCVDD作为参考电压:
// RTCVDD AD按键平台数据(adkey.h)struct adkey_rtcvdd_platform_data {u8 enable;
u8 adkey_pin;
u8 adkey_num;
u32 ad_channel;
u32 extern_up_res_value;// 外部上拉电阻值u16 res_value[ADKEY_MAX_NUM]; // 电阻值数组(从大到小)u8key_value[ADKEY_MAX_NUM];
};// RTCVDD AD按键获取值(adkey_rtcvdd.c)u8 adkey_rtcvdd_get_key_value(void) {
u16 rtcvdd_value = 2 * adc_get_value(AD_CH_RTCVDD);
u32 ad_value = adc_get_value(__this->ad_channel);// 自动匹配VDDIO电平if (rtcvdd_auto_match_vddio_lev(rtcvdd_value)) {return NO_KEY;
}// 匹配按键值// ...}3. 动态按键配置(AUX检测)
支持根据AUX插入状态动态切换按键配置:
// 动态按键配置(adkey.c: my_key_con)void my_key_con(void) {if (ad_data > TCFG_ADKEY_VOLTAGE9) {// 没有AUX插入,使用正常按键配置adkey_data.ad_value[0] = TCFG_ADKEY_VOLTAGE0;
adkey_data.ad_value[1] = TCFG_ADKEY_VOLTAGE1;// ...power_on_flag = 1;
} else if (ad_data > ADC_AUX_IN) {// AUX插入,使用AUX按键配置adkey_data.ad_value[0] = TCFG_ADKEY_AUX_VOLTAGE0;
adkey_data.ad_value[1] = TCFG_ADKEY_AUX_VOLTAGE1;// ...power_on_flag = 1;
}
}// 定时检测AUX状态void sys_my_key_con(void) {
sys_timer_add(NULL, my_key_con, 100);// 100ms检测一次}4. 按键特殊标志
支持按键特殊处理标志(BIT(7)):
// 只响应单击事件的按键#define KEY_EVENT_CLICK_ONLY_SUPPORT1// 在按键扫描中处理if (scan_para->notify_value & BIT(7)) {// BIT(7)标志,只发送单击事件key_event = KEY_EVENT_CLICK;
key_value = scan_para->notify_value;goto _notify;
}// 发送前清除标志key_value &= ~BIT(7);调试方法
1. 打印ADC值
// 在 ad_get_key_value() 中添加打印u8 ad_get_key_value(void) {
ad_data = adc_get_value(__this->ad_channel);static int num = 0;if(num++ > 30) {printf("ad_value = %d
", ad_data);
num = 0;
}// ...}2. 打印按键事件
// 在 key_driver_scan() 中添加打印_notify:printf("key_value: 0x%x, event: %d
", key_value, key_event);
sys_event_notify(&e);3. 电压阈值计算工具
# Python脚本计算电压阈值def calc_adkey_voltage(r_key, r_up=220):"""
计算AD按键电压值
r_key: 按键电阻值(单位:K欧)
r_up: 上拉电阻值(单位:K欧)
"""adc_max = 0x3FF# 10位ADC最大值voltage = int(adc_max * r_key / (r_key + r_up))return voltage# 示例r_up = 220# 22K上拉keys = [0, 3, 6.2, 9.1, 15, 24, 33, 51, 100, 220]print("按键电阻 ADC值 阈值")for i in range(len(keys)):
adc_val = calc_adkey_voltage(keys[i], r_up)if i < len(keys) - 1:
threshold = (calc_adkey_voltage(keys[i], r_up) +
calc_adkey_voltage(keys[i+1], r_up)) // 2else:
threshold = (calc_adkey_voltage(keys[i], r_up) + 0x3FF) // 2print(f"{keys[i]}K {adc_val} {threshold}")4. 常见问题排查
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 按键无响应 | ADC通道未初始化 | 检查 adc_add_sample_ch() 是否调用 |
| 按键误触发 | 消抖时间太短 | 增加 filter_time 参数 |
| 按键值错误 | 电压阈值配置错误 | 重新计算并配置电压阈值 |
| 长按不触发 | long_time 配置太大 | 减小 long_time 参数 |
| 连击不触发 | click_delay_time 太短 | 增加 click_delay_time 参数 |
| IO复用冲突 | LED/IR未正确挂起 | 检查 adc_io_reuse_enter/exit |
总结
AD按键机制是一个完整的多层架构系统:
- 硬件层:通过分压电路实现多按键共用一个ADC通道
- ADC层:2ms周期采样所有ADC通道,维护采样队列
- 驱动层:初始化GPIO和ADC,提供按键值获取接口
- 扫描层:10ms周期扫描,实现消抖、长按、连击等事件判定
- 映射层:根据当前模式和按键值/事件类型,映射到具体的按键消息
- 应用层:处理按键消息,执行相应的功能
整个系统通过定时器驱动,事件驱动的方式工作,具有良好的实时性和可扩展性。
附录
A. 相关宏定义
// 按键相关#define NO_KEY0xff#define KEY_NOT_SUPPORT 0x01#define ADKEY_MAX_NUM 10#define KEY_AD_NUM_MAX10#define KEY_EVENT_MAX 9// 按键类型#define KEY_DRIVER_TYPE_IO0x0#define KEY_DRIVER_TYPE_AD0x1#define KEY_DRIVER_TYPE_RTCVDD_AD 0x2#define KEY_DRIVER_TYPE_IR0x3#define KEY_DRIVER_TYPE_TOUCH 0x4#define KEY_DRIVER_TYPE_RDEC0x5// 系统事件#define SYS_KEY_EVENT 0x0001#define DEVICE_EVENT_FROM_KEY (('K' << 24) | ('E' << 16) | ('Y' << 8) | '





