Skip to main content

Multiple Button Input from ADC

Introduction

Normally, each push button uses its own GPIO pin. That is simple, but it does not scale well if you want a keypad, a menu controller, or several input buttons on a small board.

One useful trick is to connect multiple buttons to a single ADC pin using different resistor values. Each button creates a different voltage, and the Pico reads that voltage with the ADC. Your code then checks the measured value and decides which button was pressed.

This is a common technique in small embedded systems because it reduces pin usage while still allowing multiple inputs.

What This Tutorial Teaches

In this tutorial, you will learn:

  • how the Pico ADC works
  • how resistor ladders create different voltage levels
  • how to wire multiple buttons to one analog input
  • how to detect button presses by checking ADC ranges
  • how to build a simple menu-style input system from one pin

Why Use ADC for Multiple Buttons?

Advantages:

  • saves GPIO pins
  • useful for menu navigation
  • helpful in handheld projects and small control panels
  • works well when only one button is pressed at a time

Tradeoffs:

  • not ideal for detecting several buttons at the same time
  • requires threshold tuning
  • resistor tolerances can slightly change the measured values

How It Works

Each button connects the ADC pin through a different resistor path. When a button is pressed, the ADC sees a different voltage.

For example:

  • no button pressed -> ADC reads near full scale or near zero depending on the circuit
  • button 1 pressed -> ADC reads one voltage
  • button 2 pressed -> ADC reads a different voltage
  • button 3 pressed -> ADC reads another voltage

Your code does not check for exact values. Instead, it checks if the ADC reading falls inside a range.

Components Needed

ComponentQuantity
Raspberry Pi Pico or Pico W1
Breadboard1
Push buttons3 to 5
ResistorsSeveral
Jumper wiresSeveral
USB cable1

Suggested Resistor Values

You can build the button ladder with many resistor combinations. A simple starting set is:

  • 1k
  • 2.2k
  • 4.7k
  • 10k

The exact values are less important than making sure the resulting ADC readings are clearly separated.

Example Setup

This tutorial assumes:

  • buttons connect to ADC0
  • ADC0 is on GP26
  • only one button is pressed at a time

Images

Add your future images in this folder: mcp3208-one

/static/img/raspberry-pi-pico/tutorials/multiple-button-input-from-adc

Recommended filenames:

  • adc-button-ladder-diagram.png
  • adc-button-breadboard.png
  • adc-values-serial-output.png
  • adc-menu-controller.png

Example usage:

![ADC button ladder](/img/raspberry-pi-pico/tutorials/multiple-button-input-from-adc/adc-button-ladder-diagram.png)

Basic ADC Read Test

Before trying to identify buttons, confirm that the Pico can read the analog value.

from machine import ADC, Pin
import utime

adc = ADC(Pin(26)) # ADC0 on GP26

while True:
value = adc.read_u16()
print(value)
utime.sleep(0.2)

What to Do Here

  1. Run the script with no buttons pressed and note the value.
  2. Press each button one at a time.
  3. Write down the approximate ADC value for each button.
  4. Use those values to create threshold ranges.

Example ADC Readings

Your values will depend on your resistor choices, but an example might look like:

  • no button -> 65535
  • button 1 -> 52000
  • button 2 -> 38000
  • button 3 -> 22000
  • button 4 -> 9000

Do not hardcode exact values unless you have tested them. Use ranges instead.

Example 1 - Detect Which Button Was Pressed

from machine import ADC, Pin
import utime

adc = ADC(Pin(26))

def get_button(value):
if value > 60000:
return "No button"
elif 47000 <= value <= 56000:
return "Button 1"
elif 32000 <= value <= 43000:
return "Button 2"
elif 17000 <= value <= 27000:
return "Button 3"
elif 4000 <= value <= 13000:
return "Button 4"
else:
return "Unknown"

while True:
value = adc.read_u16()
button = get_button(value)
print("ADC:", value, "->", button)
utime.sleep(0.15)

Code Explanation

adc = ADC(Pin(26))

This configures GP26 as an analog input.

value = adc.read_u16()

This reads the analog input as a 16-bit number from 0 to 65535.

def get_button(value):

This helper function converts a raw ADC reading into a button label.

elif 47000 <= value <= 56000:
return "Button 1"

Each button is identified by a range rather than one exact ADC value. That makes the code more stable when resistor values and supply voltage vary slightly.

Example 2 - Debounced Button Reader

If you print values too fast, the output may flicker or repeat too much. A simple debounce check helps.

from machine import ADC, Pin
import utime

adc = ADC(Pin(26))
last_button = "No button"

def get_button(value):
if value > 60000:
return "No button"
elif 47000 <= value <= 56000:
return "Up"
elif 32000 <= value <= 43000:
return "Down"
elif 17000 <= value <= 27000:
return "Left"
elif 4000 <= value <= 13000:
return "Right"
else:
return "Unknown"

while True:
value = adc.read_u16()
button = get_button(value)

if button != last_button:
print("Pressed:", button)
last_button = button

utime.sleep(0.1)

Example 3 - Simple Menu Controller

This is where the technique becomes useful. One ADC pin can handle multiple navigation buttons.

from machine import ADC, Pin
import utime

adc = ADC(Pin(26))

menu = ["Start", "Settings", "Info", "About"]
index = 0
last_button = "No button"

def get_button(value):
if value > 60000:
return "No button"
elif 47000 <= value <= 56000:
return "Up"
elif 32000 <= value <= 43000:
return "Down"
elif 17000 <= value <= 27000:
return "Select"
else:
return "Unknown"

print("Menu item:", menu[index])

while True:
value = adc.read_u16()
button = get_button(value)

if button != last_button:
if button == "Up":
index = (index - 1) % len(menu)
print("Menu item:", menu[index])
elif button == "Down":
index = (index + 1) % len(menu)
print("Menu item:", menu[index])
elif button == "Select":
print("Selected:", menu[index])

last_button = button

utime.sleep(0.12)

Tuning the Thresholds

This is the most important part of the project.

When you wire your actual resistor ladder:

  1. print the raw ADC values
  2. press each button several times
  3. note the minimum and maximum readings for each button
  4. create thresholds with safe spacing between them

For example, if one button reads from 34500 to 36000, you might define:

elif 34000 <= value <= 36500:
return "Button 2"

Common Problems

Button values overlap

  • choose resistor values with more separation
  • measure the real ADC values again
  • widen or tighten your thresholds

Readings jump around too much

  • make sure the wiring is solid
  • shorten jumper wires if possible
  • add a small delay before reading again
  • consider averaging a few ADC samples

Two buttons pressed at once

This method is usually intended for one-button-at-a-time input. Multiple simultaneous presses can create unexpected voltages.

No clear value when idle

Make sure your ladder includes a proper reference path so the ADC pin is not floating when no button is pressed.

Improvement Ideas

Once the basic version works, you can extend it with:

  • averaging multiple ADC readings
  • menu navigation on an LCD or OLED
  • a handheld game controller
  • a custom PCB with a resistor ladder keypad
  • a project where buttons change LED effects or motor states

Summary

Using ADC for multiple button inputs is a clever way to save GPIO pins on the Raspberry Pi Pico. Instead of giving each button its own digital input, you use resistor values to generate different voltages and read them from one analog pin.

That makes this technique especially useful for:

  • menu systems
  • compact controllers
  • small handheld devices
  • projects where GPIO pins are limited

The most important part is not the exact resistor values. The important part is building a circuit where each button produces a clear, repeatable ADC range.