杰理AC695X AD按键机制讲解
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_time2 × 10ms = 20ms长按时间long_time × scan_time75 × 10ms = 750msHOLD时间hold_time × scan_time90 × 10ms = 900ms连击延时click_delay_time × scan_time20 × 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) | '')B. 相关API// ADC APIu32 adc_add_sample_ch(u32 ch);u32 adc_remove_sample_ch(u32 ch);u32 adc_get_value(u32 ch);u32 adc_get_voltage(u32 ch);// AD按键APIint adkey_init(const struct adkey_platform_data *adkey_data);u8 ad_get_key_value(void);// RTCVDD AD按键APIint adkey_rtcvdd_init(const struct adkey_rtcvdd_platform_data *rtcvdd_adkey_data);u8 adkey_rtcvdd_get_key_value(void);// 按键驱动APIint key_driver_init(void);u16 adkey_event_to_msg(u8 cur_task, struct key_event *key);int key_event_remap(struct sys_event *e);// 系统事件APIint sys_event_notify(struct sys_event *e);C. 参考文档ADC驱动文档:cpu/br23/adc_api.c按键驱动文档:apps/common/key/key_driver.c板级配置示例:apps/soundbox/board/br23/board_ac695x_demo/文档版本:v1.0最后更新:2025-01-05适用平台:AC695N SDK 3.1.2
2026.05.19了解详情