概念先知道
- ADC 按键:把多个按键接在同一个 ADC 引脚上,按下不同键得到不同电压,程序靠“电压落在哪个区间”识别键号——一个引脚顶多个按键。
- 分压电阻阶梯:按键串不同阻值到电源/地,形成 2370/1650/1100/0mV 等阶梯电压;
g_key_target_mv[]就是这些“键电压”的参考值。 - adckey 组件:SDK 的按键组件,负责周期性采样(50ms)、消抖、长短按判定,通过回调上报事件(PRESS / LONG_PRESS / RELEASE)。
- 消抖与长按:机械按键按下瞬间电压会抖动,组件用连续采样过滤;连续按住超过
long_press_ms(1 秒)判定为长按。 - 事件回调:
adckey_demo_key_callback收到事件后打印item(第几路)、key(键号)、event与采样电压。
例程功能简介
本页对应博流官方 SDK 的 adc_key_basic 例程(examples/peripherals/adc_key/adc_key_basic),演示 ADC 多键识别 + 辅助 ADC 采样:
- 配置两路采集项:item0 为 4 键阶梯按键(IO20/ADC 通道 0,50ms 采样,长按 1 秒),item1 为辅助 ADC(IO21/ADC 通道 1,1 秒采样);
- 按键回调区分 PRESS / LONG_PRESS / RELEASE 并打印电压,长按与短按行为可分开响应;
- 辅助 ADC 回调周期性打印采样电压,可接电位器/光敏电阻做环境采样;
- 裸机模式
adckey_poll轮询与 FreeRTOS 模式(vTaskStartScheduler)二选一,由CONFIG_FREERTOS决定。
提示
按键电压阈值与上拉方向有关:默认空闲电压 3.2V,g_key_target_mv[] 按降序配置;若工程开启 CONFIG_ADCKEY_DEFAULT_LOW(空闲 0V),需改为升序 600/1200/1800/2400mV。
操作步骤
例程用 分压电阻阶梯:多个按键通过不同阻值接到同一个 ADC 引脚(BL616 默认 IO20=ADC 通道 0),按下不同键产生不同电压(2370/1650/1100/0mV)。可照此接 4 个按键 + 电阻,或直接用杜邦线把 IO20 依次短接到不同分压点。
在终端进入 SDK 的 ADC 按键例程目录(前提:已按快速开始(Linux)或Windows搭建好环境):
cd examples/peripherals/adc_key/adc_key_basic执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:
make CHIP=bl616 BOARD=bl616dk用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):
make flash CHIP=bl616 COMX=/dev/ttyUSB0打开串口助手(波特率 2000000)。按不同按键,串口打印 adckey: item=0 key=N event=PRESS/LONG_PRESS/RELEASE sampled=xxxxmV;按住超过 1 秒会先触发 LONG_PRESS;IO21 上接的辅助 ADC 每个 1 秒打印一次 adc item=1 sampled=xxxxmV。
代码执行流程
例程从启动到上报事件的流程如下:
例程调用的 API 介绍
adckey_init(&cfg, &handle)
根据配置创建 adckey 实例。cfg 内含 items(采集项数组)与 item_num;每个 item 用 .type 区分按键(ADCKEY_ITEM_TYPE_KEY)或纯 ADC(ADCKEY_ITEM_TYPE_ADC)。
参数:
cfg:adckey_config_t,含 items / item_numhandle:返回的实例句柄
返回值:0 成功;非 0 失败
adckey_start / adckey_poll(handle)
adckey_start 启动采样(FreeRTOS 模式创建任务);裸机模式在主循环反复调用 adckey_poll 驱动状态机。
参数:
handle:adckey 实例句柄
返回值:0 成功;非 0 失败
adckey_event_data_t / adckey_adc_data_t(回调数据)
事件回调收到的结构:按键事件含 item_id、key_id、event(PRESS/LONG_PRESS/RELEASE)、sampled_mv;ADC 数据含 item_id 与 sampled_mv。
参数:—
返回值:—
完整代码
以下为 adc_key_basic/main.c 完整源码,与官方示例一致(BL616/BL618 走 bflb_adc.h 分支,IO20/IO21),默认折叠,点击展开:
📜 点击展开 adc_key_basic/main.c 完整代码
#include <stdio.h>
#if defined(CONFIG_FREERTOS)
#include <FreeRTOS.h>
#include <task.h>
#endif
#include "adckey.h"
#include "bflb_gpio.h"
#include "bflb_mtimer.h"
#include "board.h"
#if defined(BL618DG)
#include "bflb_adc_v3.h"
#define DEMO_KEY_GPIO_PIN GPIO_PIN_13
#define DEMO_KEY_ADC_CHANNEL ADC_EXTERNAL_CHANNEL_1
#define DEMO_AUX_GPIO_PIN GPIO_PIN_28
#define DEMO_AUX_ADC_CHANNEL ADC_EXTERNAL_CHANNEL_4
#define DEMO_DRIVER_NAME "ADC_V3"
#elif defined(BL616CL)
#include "bflb_adc_v2.h"
#define DEMO_KEY_GPIO_PIN GPIO_PIN_5
#define DEMO_KEY_ADC_CHANNEL ADC_EXTERNAL_CHANNEL_5
#define DEMO_AUX_GPIO_PIN GPIO_PIN_4
#define DEMO_AUX_ADC_CHANNEL ADC_EXTERNAL_CHANNEL_4
#define DEMO_DRIVER_NAME "ADC_V2"
#else
#include "bflb_adc.h"
#define DEMO_KEY_GPIO_PIN GPIO_PIN_20
#define DEMO_KEY_ADC_CHANNEL ADC_CHANNEL_0
#define DEMO_AUX_GPIO_PIN GPIO_PIN_21
#define DEMO_AUX_ADC_CHANNEL ADC_CHANNEL_1
#define DEMO_DRIVER_NAME "ADC"
#endif
#define DEMO_KEY_PERIOD_MS (50u)
#define DEMO_AUX_PERIOD_MS (1000u)
#define DEMO_LONG_PRESS_MS (1000u)
#if defined(CONFIG_ADCKEY_DEFAULT_LOW)
static const uint16_t g_key_target_mv[] = { 600, 1200, 1800, 2400 };
#else
static const uint16_t g_key_target_mv[] = { 2370, 1650, 1100, 0 };
#endif
static adckey_handle_t g_adckey;
static void adckey_demo_key_callback(const adckey_event_data_t *event_data)
{
const char *event_name;
if (event_data == NULL) {
return;
}
switch (event_data->event) {
#if defined(CONFIG_ADCKEY_SUPPORT_PRESS)
case ADCKEY_EVENT_PRESS:
event_name = "PRESS";
break;
#endif
case ADCKEY_EVENT_LONG_PRESS:
event_name = "LONG_PRESS";
break;
case ADCKEY_EVENT_RELEASE:
event_name = "RELEASE";
break;
default:
event_name = "UNKNOWN";
break;
}
printf("adckey: item=%u key=%u event=%s sampled=%umV\r\n",
(unsigned)event_data->item_id,
(unsigned)event_data->key_id,
event_name,
(unsigned)event_data->sampled_mv);
}
static void adckey_demo_adc_callback(const adckey_adc_data_t *adc_data)
{
if (adc_data == NULL) {
return;
}
printf("adckey: adc item=%u sampled=%umV\r\n",
(unsigned)adc_data->item_id,
(unsigned)adc_data->sampled_mv);
}
static const adckey_item_config_t g_adckey_items[] = {
{
.type = ADCKEY_ITEM_TYPE_KEY,
.gpio_pin = DEMO_KEY_GPIO_PIN,
.adc_channel = DEMO_KEY_ADC_CHANNEL,
.sample_period_ms = DEMO_KEY_PERIOD_MS,
.config.key =
{
.target_mv = g_key_target_mv,
.key_num = sizeof(g_key_target_mv) / sizeof(g_key_target_mv[0]),
.long_press_ms = DEMO_LONG_PRESS_MS,
.callback = adckey_demo_key_callback,
},
},
{
.type = ADCKEY_ITEM_TYPE_ADC,
.gpio_pin = DEMO_AUX_GPIO_PIN,
.adc_channel = DEMO_AUX_ADC_CHANNEL,
.sample_period_ms = DEMO_AUX_PERIOD_MS,
.config.adc =
{
.callback = adckey_demo_adc_callback,
},
},
};
static const adckey_config_t g_adckey_cfg = {
#if defined(CONFIG_FREERTOS)
.task_stack_size = 1024,
.task_priority = 5,
#endif
.items = g_adckey_items,
.item_num = sizeof(g_adckey_items) / sizeof(g_adckey_items[0]),
};
#if defined(CONFIG_FREERTOS)
void vApplicationMallocFailedHook(void)
{
taskDISABLE_INTERRUPTS();
while (1) {}
}
void vAssertCalled(void)
{
taskDISABLE_INTERRUPTS();
while (1) {}
}
#endif
int main(void)
{
board_init();
printf("adc_key_basic demo using %s\r\n", DEMO_DRIVER_NAME);
printf("item0: key gpio=%u channel=%u period=%u ms\r\n",
(unsigned)DEMO_KEY_GPIO_PIN,
(unsigned)DEMO_KEY_ADC_CHANNEL,
(unsigned)DEMO_KEY_PERIOD_MS);
printf("item1: adc gpio=%u channel=%u period=%u ms\r\n",
(unsigned)DEMO_AUX_GPIO_PIN,
(unsigned)DEMO_AUX_ADC_CHANNEL,
(unsigned)DEMO_AUX_PERIOD_MS);
#if defined(CONFIG_ADCKEY_DEFAULT_LOW)
printf("Configure key target_mv in ascending order because idle voltage is 0mV.\r\n");
#else
printf("Configure key target_mv in descending order because idle voltage is 3200mV.\r\n");
#endif
if (adckey_init(&g_adckey_cfg, &g_adckey) != 0) {
printf("adckey_init failed\r\n");
while (1) {
bflb_mtimer_delay_ms(1000);
}
}
if (adckey_start(g_adckey) != 0) {
printf("adckey_start failed\r\n");
while (1) {
bflb_mtimer_delay_ms(1000);
}
}
#if defined(CONFIG_FREERTOS)
printf("adckey FreeRTOS mode started\r\n");
vTaskStartScheduler();
while (1) {}
#else
printf("adckey baremetal poll mode started\r\n");
while (1) {
(void)adckey_poll(g_adckey);
bflb_mtimer_delay_ms(1);
}
#endif
}FAQ
按键没反应或键号不对?
先看串口打印的 sampled=xxxxmV 是否落在 2370/1650/1100/0 附近;若整体偏低/偏高,检查分压电阻取值与上拉(默认空闲 3.2V)。也可按需要改 g_key_target_mv[] 阈值。
长按和短按怎么区分?
组件按 long_press_ms(例程 1000ms)判定:按下后持续超过该时间上报一次 LONG_PRESS,短按上报 PRESS(需开启 CONFIG_ADCKEY_SUPPORT_PRESS),松开统一上报 RELEASE。
可以用更多或更少按键吗?
可以。改 g_key_target_mv[] 的元素个数与对应分压值,并保持 key_num 一致;建议键电压间隔不小于 300mV,避免噪声导致误判。
为什么串口打印的是 `ADC` 而不是 ADC_V2/V3?
BL616/BL618 使用第一代 ADC 驱动(bflb_adc.h),所以 DEMO_DRIVER_NAME 打印 ADC;BL616CL 用 ADC_V2、BL618DG 用 ADC_V3,这是芯片差异,不是配置错误。

