Concepts First
- ADC keypad: several keys share one ADC pin; each key produces a different voltage, and the firmware identifies the key by which voltage range the sample falls in — one pin serves many keys.
- Resistor divider ladder: keys with different resistors to power/ground create stepped voltages such as 2370/1650/1100/0 mV;
g_key_target_mv[]holds these reference "key voltages". - adckey component: the SDK keypad component samples periodically (50 ms), debounces, detects long presses, and reports events via callbacks (PRESS / LONG_PRESS / RELEASE).
- Debounce & long press: mechanical keys bounce when pressed; the component filters with consecutive samples, and holding beyond
long_press_ms(1 s) counts as a long press. - Event callback:
adckey_demo_key_callbackprintsitem(which input),key(key number),event, and the sampled voltage.
Example Overview
This page is based on the adc_key_basic example in the official Bouffalo SDK (examples/peripherals/adc_key/adc_key_basic), demonstrating ADC multi-key detection + auxiliary ADC sampling:
- Configures two items: item0 is a 4-key resistor-ladder keypad (IO20 / ADC channel 0, 50 ms sampling, 1 s long press), item1 is an auxiliary ADC (IO21 / ADC channel 1, 1 s sampling);
- The key callback distinguishes PRESS / LONG_PRESS / RELEASE and prints the voltage, so short and long presses can trigger different behaviors;
- The ADC callback periodically prints the sample voltage — hook up a potentiometer or photoresistor for environment sensing;
- Bare-metal polling mode (
adckey_poll) and FreeRTOS mode (vTaskStartScheduler) are selectable viaCONFIG_FREERTOS.
Note
Key thresholds depend on the pull direction: with the default idle voltage of 3.2 V, g_key_target_mv[] is in descending order; with CONFIG_ADCKEY_DEFAULT_LOW (idle 0 V), use ascending 600/1200/1800/2400 mV.
Operation Steps
The example uses a resistor divider ladder: multiple keys on one ADC pin (default IO20 = ADC channel 0 on BL616) produce different voltages when pressed (2370/1650/1100/0 mV). Wire 4 keys with resistors accordingly, or simply tap IO20 to different divider points with jumper wires.
Open a terminal and enter the SDK ADC keypad example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):
cd examples/peripherals/adc_key/adc_key_basicRun the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:
make CHIP=bl616 BOARD=bl616dkConnect the board with a USB cable, hold the BOOT button (IO2 on the Ai-M61-32S-Kit), briefly press EN/RST to enter download mode, then flash (replace the serial port with the one on your computer):
make flash CHIP=bl616 COMX=/dev/ttyUSB0Open a serial tool (baud rate 2000000). Pressing different keys prints adckey: item=0 key=N event=PRESS/LONG_PRESS/RELEASE sampled=xxxxmV; holding over 1 s triggers LONG_PRESS first. The auxiliary ADC on IO21 prints adc item=1 sampled=xxxxmV every second.
Code Execution Flow
The flow from boot to reporting events is:
APIs Used by the Example
adckey_init(&cfg, &handle)
Creates an adckey instance from the config. cfg contains items and item_num; each item's .type is ADCKEY_ITEM_TYPE_KEY (keypad) or ADCKEY_ITEM_TYPE_ADC (plain ADC).
Parameters:
cfg:adckey_config_twith items / item_numhandle: returned instance handle
Returns: 0 on success; non-zero on failure
adckey_start / adckey_poll(handle)
adckey_start starts sampling (creating a task in FreeRTOS mode); in bare-metal mode, call adckey_poll repeatedly in the main loop to drive the state machine.
Parameters:
handle: adckey instance handle
Returns: 0 on success; non-zero on failure
adckey_event_data_t / adckey_adc_data_t(callback data)
Structures passed to callbacks: key events carry item_id, key_id, event (PRESS/LONG_PRESS/RELEASE), and sampled_mv; ADC data carries item_id and sampled_mv.
Parameters: —
Returns: —
Complete Code
The following is the complete source of adc_key_basic/main.c, identical to the official example (BL616/BL618 use the bflb_adc.h branch with IO20/IO21), collapsed by default:
📜 Click to expand adc_key_basic/main.c full code
#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
Keys don't respond or report the wrong key number?
Check the printed sampled=xxxxmV against 2370/1650/1100/0. If values are systematically low/high, check the divider resistors and pull direction (default idle is 3.2 V). You can also tune g_key_target_mv[] thresholds.
How are long and short presses distinguished?
By long_press_ms (1000 ms here): holding longer triggers one LONG_PRESS; a short press triggers PRESS (requires CONFIG_ADCKEY_SUPPORT_PRESS); release always reports RELEASE.
Can I use more or fewer keys?
Yes. Change the element count of g_key_target_mv[] and the corresponding divider values, keeping key_num in sync. Keep at least 300 mV between adjacent key voltages to avoid noise misdetection.
Why does the log say `ADC` instead of ADC_V2/V3?
BL616/BL618 use the first-generation ADC driver (bflb_adc.h), so DEMO_DRIVER_NAME prints ADC; BL616CL uses ADC_V2 and BL618DG uses ADC_V3. That's a chip difference, not a misconfiguration.

