Skip to content

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_callback prints item (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 via CONFIG_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

1
Hardware Preparation

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.

2
Enter the Example Directory

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_basic
3
Build the Project

Run the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:

make CHIP=bl616 BOARD=bl616dk
4
Flash the Firmware

Connect 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/ttyUSB0
5
Run and Verify

Open 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_t with items / item_num
  • handle: 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
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

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.

Released under the MIT License. Build Time 2026-09-11 14:52:23