Rotary Potentiometer

🎮 The Knob of a Thousand Values

Microproject Image

Module Goal: leave the world of 1s and 0s behind forever. From today your robots understand in-between values: a knob at 37%, light at 61%, heat at 84%. The potentiometer is your first ANALOG sensor, and it opens every other analog module in the course (light, soil, rain, distance...).

Goal: 

watch a THOUSAND values flow through your screen as you turn the knob. 

Concepts: 

Analog Input · ADC · The 0–65535 Scale

📦 CONCEPT BOX What is an ANALOG signal? 

You know DIGITAL: a light switch. Two states. 1 or 0. Nothing between. 

ANALOG is a dimmer knob: it has many values every position between the extremes is valid. 

  • On a digital pin, voltage is only ever 0 V or 3.3 V.
  • On an analog pin, voltage can be anywhere in between: 0 V, 1.2 V, 2.7 V, 3.1 V... continuously. 

The real world is mostly analog: temperature, light level, loudness, knob position none of them are just ON/OFF. 

But computers only understand numbers. So the ESP32 carries a special translator inside called the ADC Analog-to-Digital Converter. It measures the voltage on the pin and converts it into a number. 

The course scale (memorize this): 0 to 65535 16 bits, always. 

Pin at 0 V → the ADC returns 0 

Pin at 3.3 V → the ADC returns 65535 

Pin exactly in the middle → ~32768 

The reading command is new: adc2.read_u16() it returns that 0–65535 number. 

Your three families are now complete: 

  • DIGITAL 2 values (button, crash, touch, tilt, sound, radar, guardian)
  • ANALOG many values (today: the knob; soon: light, soil, rain, distance)
  • PWM the flicker trick that fakes in-between power (lamp, fan, car)
import machine	# Library that controls the board's pins
import time	# Library for pauses

# Sets up adc4 as an analog input for the potentiometer
# adc4 corresponds to A1 on the Sensor Shield V5.0
adc4 = machine.ADC(machine.Pin(4))
adc4.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

while True:	# Repeat forever
	# 16-bit is the default; just use adc4.read_u16()
    value = adc4.read_u16()	# Ask the ADC: what voltage is on the pin? (0 to 65535)
    print("Knob value:", value)	# Send the raw value to the PC
    time.sleep_ms(300)	# Pause so the screen stays readable

Serial Monitor (turn the knob slowly from one end to the other): 

Knob value: 312 

Knob value: 8450 

Knob value: 18902 

Knob value: 32768 ← middle of the turn 

Knob value: 47550 

Knob value: 59810 

Knob value: 65400 

💡 Note: compare with everything you've read before. The button could only ever say 1 or 0 this knob says 65,536 different things. That number is not random: 2 to the power of 16 = 65536. The u16 in read_u16() means "unsigned 16 bits" sixteen little switches inside the ADC, combining into one big number. Turn the knob to what LOOKS like the middle and check the screen: ~32768 means your eyes and the ADC agree.

🎮 Percentages and Zones

Microproject Image

Goal: 

convert the raw value into a friendly percentage, then classify it into zones. 

Concepts: 

Scaling · Integer Division Order · Zone Classification

import machine	# Library that controls the board's pins
import time	# Library for pauses

# Sets up adc4 as an analog input for the potentiometer
# adc4 corresponds to A1 on the Sensor Shield V5.0
adc4 = machine.ADC(machine.Pin(4))
adc4.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

while True:	# Repeat forever
	# 16-bit is the default; just use adc4.read_u16()
    value = adc4.read_u16()	# Raw reading: 0 to 65535
    percent = (value * 100) // 65535	# Scale to 0-100 percent
    print("Knob at", percent, "%")	# Report the percentage
    if percent < 33:	# Bottom third?
        print("Zone: LOW")	# Low zone
    elif percent < 66:	# Middle third?
        print("Zone: MEDIUM")	# Medium zone
    else:	# Top third
        print("Zone: HIGH")	# High zone
    time.sleep_ms(300)	# Pause between readings

Serial Monitor: 

Knob at 18 % 

Zone: LOW 

Knob at 51 % 

Zone: MEDIUM

 Knob at 87 % 

Zone: HIGH 

💡 Note: the percentage line hides a critical secret about order of operations: (value * 100) // 65535 multiply FIRST, divide LAST. If you divide first (value // 65535), you get 0 almost always (raw values are smaller than 65535!), and multiplying 0 by 100 gives... 0. Forever. This bug is called integer truncation // throws away decimals, so you must not throw them away too early. Multiplying before dividing protects the small numbers. Every programmer has hit this bug once; now you're immune.

🎮 Which Way Did It Turn?

Microproject Image

Goal: 

detect the knob's DIRECTION did it turn clockwise or counter-clockwise? 

Concepts: 

Comparing Consecutive Readings · Dead Zone · Direction Detection

import machine	# Library that controls the board's pins
import time	# Library for pauses

# Sets up adc4 as an analog input for the potentiometer
# adc4 corresponds to A1 on the Sensor Shield V5.0
adc4 = machine.ADC(machine.Pin(4))
adc4.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

prev = 0	# Remembers the previous reading

while True:	# Repeat forever
	# 16-bit is the default; just use adc4.read_u16()
    now = adc4.read_u16()	# Fresh reading
    if now > prev + 800:	# Clearly BIGGER than before (with a safety margin)
        print("Turning RIGHT (clockwise)")	# Direction report
    elif now < prev - 800:	# Clearly SMALLER than before
        print("Turning LEFT (counter-clockwise)")	# Direction report
    prev = now	# Save this reading for the next comparison
    time.sleep_ms(100)	# Quick checking

Serial Monitor (turn gently right, then left): 

Turning RIGHT (clockwise) 

Turning RIGHT (clockwise) 

Turning LEFT (counter-clockwise) 

Turning LEFT (counter-clockwise) 

💡 Note: why the mysterious +800 / −800? Because ADC readings are never perfectly still they jitter by tiny amounts even with your hand off the knob. The margin (a dead zone) means "ignore jitters smaller than 800" and only react to REAL turns. Try removing it (compare with now > prev) and watch the screen panic with false turns! Dead zones are how engineers ignore noise you will reuse this trick with every analog sensor to come.

🎮 Moving or Resting?

Microproject Image

Goal: 

detect movement bursts announce when the knob starts moving and when it settles. 

Concepts: 

Dead Zone · State Memory · Event Detection on Analog

import machine	# Library that controls the board's pins
import time	# Library for pauses

# Sets up adc4 as an analog input for the potentiometer
# adc4 corresponds to A1 on the Sensor Shield V5.0
adc4 = machine.ADC(machine.Pin(4))
adc4.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

prev = 0	# Remembers the previous reading
moves = 0	# Counts movement bursts
moving = False	# Remembers if we were moving last check

while True:	# Watch forever
	# 16-bit is the default; just use adc4.read_u16()
    now = adc4.read_u16()	# Fresh reading
    if now > prev + 800 or now < prev - 800:	# Real movement detected
        if moving == False:	# We were resting a moment ago...
            moves += 1	# ...so a NEW burst starts!
            print("Movement burst", moves, "started!")	# Announce the burst
        moving = True	# Remember: we are moving now
    else:	# No real change
        if moving == True:	# We WERE moving a moment ago...
            print("Movement stopped. The knob rests.")	# ...so the burst just ended
        moving = False	# Remember: we are resting now
    prev = now	# Save this reading
    time.sleep_ms(100)	# Checking pace

Serial Monitor (turn, pause, turn again): 

Movement burst 1 started! 

Movement stopped. The knob rests. 

Movement burst 2 started! 

Movement stopped. The knob rests. 

💡 Note: you just did something impressive you applied event detection to an analog signal. Digital sensors gave you events for free (pressed/not pressed); analog values change gradually, so YOU built the boundary: "a change bigger than 800 counts as movement." The moving variable remembers the state between checks the same memory trick as the touch lamp's mode. Analog + state memory = motion detection, gesture detection, shake detection. This pattern is about to become very valuable.

🎮 The Magic Bar

Microproject Image

Goal: 

build a live bar of # symbols in the Serial Monitor that GROWS and SHRINKS as you turn the knob like a video game volume bar. And you will build it with a loop that constructs the bar piece by piece. 

Concepts: 

Scaling · String Building · Loop Construction · Live Visualization

import machine	# Library that controls the board's pins
import time	# Library for pauses

# Sets up adc4 as an analog input for the potentiometer
# adc4 corresponds to A1 on the Sensor Shield V5.0
adc4 = machine.ADC(machine.Pin(4))
adc4.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

print("=== MAGIC BAR ===")	# Mission title
print("Turn the knob and watch the bar!")	# Instructions
while True:	# Run forever
	# 16-bit is the default; just use adc4.read_u16()
    value = adc4.read_u16()	# Raw reading: 0 to 65535
    percent = (value * 100) // 65535	# Scale to 0-100 percent
    bars = percent // 10	# How many # blocks? (0 to 10)
    bar = ""	# Start with an EMPTY bar
    for i in range(1, bars + 1, 1):	# Repeat once per block
        bar += "#"	# Add one more block to the bar
    print(bar, "|", percent, "%")	# Show the bar and the number
    time.sleep_ms(200)	# Refresh 5 times per second

Serial Monitor (turn the knob slowly from zero to max): 

| 0 % 

# | 9 %

 ### | 34 % 

##### | 52 % 

######## | 83 % 

########## | 100 % 

💡 Note: the most elegant trick of the module hides in three lines. ① bar = "" creates an empty box. ② The loop adds one # per repetition turn to 52% and bars becomes 5, so the loop runs 5 times and the bar grows to #####. ③ If the knob is at 0%, bars is 0, the loop never runs, and the bar stays empty the loop simply skips. You are now building data structures inside a variable, one piece at a time this exact idea (accumulate in a loop) will later build lists of sensor readings, messages, and game maps. Demo challenge: without looking at the percent number, can a classmate guess the knob position just by the bar's length? Your screen has become an instrument.