Analog Grayscale

🎮 The Suspects Line Up

Microproject Image

Module Goal: measure how much light a surface reflects and discover that between white and black lives a whole civilization of grays: paper, cardboard, felt, fabric. Today your robot learns to judge materials by their tone. 

Gather your suspects: a piece of white paper, a piece of cardboard, and something dark (black felt, dark fabric or black paper). 

Goal: 

measure the reflection of each material and record your evidence. 

Concepts: 

Analog Input · Reflectivity · Evidence Collection

import machine	# Library that controls the board's pins
import time	# Library for pauses
# Sets up adc39 as an analog input for the grayscale sensor
# adc39 corresponds to A5 on the Sensor Shield V5.0
adc39 = machine.ADC(machine.Pin(39))
adc39.atten(machine.ADC.ATTN_11DB) # Sets the analog input range
while True:	# Repeat forever
    value = adc39.read_u16()	# Ask how much light the surface reflects (0 to 65535)
    print("Material value:", value)	# Send the raw evidence to the PC
    time.sleep_ms(300)	# Pause so the screen stays readable

Serial Monitor (hold the sensor 5 mm over each suspect, one at a time): 

Material value: 62400 ← white paper (reflects almost everything!) 

Material value: 61900 

Material value: 38700 ← cardboard (half the light comes back) 

Material value: 39200 

Material value: 6100 ← black felt (swallows the light) 

Material value: 5900 

💡 Note: the same infrared trick as the line follower (Module 14) but with a mission upgrade: instead of two extremes, you now have a spectrum. White, cardboard, black: three distinct neighborhoods of numbers. Write all three in your notebook these are the calibration points for the whole module. And notice the sensor's etiquette: it wants the surface 5–10 mm away, held still. Measuring discipline is part of the job.

🎮 Three Tones

Microproject Image

Goal: 

classify any surface into LIGHT / GRAY / DARK using YOUR three evidence values. 

Concepts: 

Multi-Zone Thresholds · Calibrated Boundaries 

Calibrate: paper ~62000, cardboard ~39000, felt ~6000. Boundaries between neighbors: below 20000 = DARK, above 50000 = LIGHT, between = GRAY. Tune with your own numbers.

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

# Sets up adc39 as an analog input for the grayscale sensor
# adc39 corresponds to A5 on the Sensor Shield V5.0
adc39 = machine.ADC(machine.Pin(39))
adc39.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

while True:	# Repeat forever
    value = adc39.read_u16()	# Raw reflectivity reading
    print("Material value:", value)	# Always show the raw evidence
    if value > 50000:	# Near your white-paper evidence?
        print("Tone: LIGHT bright material")	# Light zone
    elif value < 20000:	# Near your black-felt evidence?
        print("Tone: DARK light absorber")	# Dark zone
    else:	# The neighborhood in between
        print("Tone: GRAY the middle kingdom")	# Gray zone
    time.sleep_ms(300)	# Pause between verdicts

Serial Monitor: 

Material value: 61300 

Tone: LIGHT bright material 

Material value: 37600 

Tone: GRAY the middle kingdom 

Material value: 6200 

Tone: DARK light absorber 

💡 Note: three zones need only two fences a rule worth memorizing: N zones, N−1 thresholds. And look at the boundary placement: 50000 sits comfortably between paper and cardboard, 20000 between cardboard and felt each fence in the middle of its gap, with safety margin on both sides (the midpoint rule from 14.2, now applied twice). Test materials beyond your three suspects: a yellow sticky note? Your hand? Where do they land?

🎮 The Sliding Trend

Microproject Image

Goal: 

slide the sensor across materials and report the direction getting darker or getting lighter. 

Concepts: 

Delta · Dead Zone · Trend on the Move

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

# Sets up adc39 as an analog input for the grayscale sensor
# adc39 corresponds to A5 on the Sensor Shield V5.0
adc39 = machine.ADC(machine.Pin(39))
adc39.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

previous = adc39.read_u16()	# First reading = our starting point

while True:	# Watch forever
    value = adc39.read_u16()	# Fresh reading
    change = value - previous	# The DELTA: how far did the tone move?
    if change > 1500:	# Clearly MORE reflection than before?
        print("Getting LIGHTER (+", change, ")")	# Moving toward white
    elif change < -1500:	# Clearly LESS reflection?
        print("Getting DARKER (", change, ")")	# Moving toward black
    previous = value	# Save this reading for the next comparison
    time.sleep_ms(100)	# Quick checking for smooth slides

Serial Monitor (slide slowly from paper onto cardboard, then onto felt): 

Getting DARKER ( -18200 ) 

Getting DARKER ( -23400 ) 

Getting LIGHTER ( +6100 ) 

Getting DARKER ( -29800 ) 

💡 Note: the delta pattern's second appearance (soil 15.4) and the dead zone grew from 800 to 1500. Why? Because here the sensor is in motion, and your hand wobbles the distance, which wobbles the reading. The faster the context, the wider the dead zone must be noise tolerance is not a constant, it's a design decision you make per situation. Notice also what the delta does NOT say: it never names the material, only the direction of travel. Trend and identity are different questions, needing different tools.

🎮 The Official Table of Materials

Microproject Image

Goal: 

build a proper measurement protocol each material gets 5 samples, an average, and a verdict. This is how real labs measure: never trust one reading. 

Concepts: 

Accumulator · Averaging · Measurement Protocol

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

# Sets up adc39 as an analog input for the grayscale sensor
# adc39 corresponds to A5 on the Sensor Shield V5.0
adc39 = machine.ADC(machine.Pin(39))
adc39.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

print("=== MATERIAL TABLE ===")	# Protocol title
print("We will measure 3 materials, 5 samples each")	# Explain the procedure
for material in range(1, 4, 1):	# Three materials in total
    material = input(('Place material ' + ' under the sensor and press Enter... '))	# Wait for YOU to be ready
    total = 0	# The accumulator starts empty
    for i in range(1, 6, 1):	# Five samples: i counts 1..5
        total += adc39.read_u16()	# Add one reading to the total
        time.sleep_ms(150)	# Tiny pause between samples
    average = total // 5	# The calm, trustworthy value
    print("Material", material, "average:", average)	# Report the evidence
    if average > 50000:	# Near the paper zone?
        print("Verdict: LIGHT material")	# The verdict
    elif average < 20000:	# Near the felt zone?
        print("Verdict: DARK material")	# The verdict
    else:	# The gray neighborhood
        print("Verdict: GRAY material")	# The verdict
print("Table complete! Case closed.")	# Grand ending

Serial Monitor: 

=== MATERIAL TABLE === 

We will measure 3 materials, 5 samples each 

Place material 1 under the sensor and press Enter... 

Material 1 average: 62180 

Verdict: LIGHT material 

Place material 2 under the sensor and press Enter...

Material 2 average: 38940 

Verdict: GRAY material 

Place material 3 under the sensor and press Enter... 

Material 3 average: 6020 

Verdict: DARK material 

Table complete! Case closed. 

💡 Note: two professional habits live here. ① The averaging protocol: 5 samples instead of 1 the same smoothing you learned on light (13.4), now standard procedure. ② The input() handshake: the program waits for you to place each material the human and the program take turns, like a lab assistant saying "ready when you are." This turn-taking structure (measure on demand) is how real quality-control stations work on factory lines. Compare your averages with your raw readings from 17.1: the verdicts agree, but the numbers are calmer that calm is worth 5 extra lines of code.

🎮 The Darkness Championship

Microproject Image

Goal: 

put three materials in a competition the program measures each one, remembers the darkest so far, and crowns the CHAMPION OF DARKNESS at the end. 

Concepts: 

Best-So-Far Tracking · Competition Logic · Round-Based Programs

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

# Sets up adc39 as an analog input for the grayscale sensor
# adc39 corresponds to A5 on the Sensor Shield V5.0
adc39 = machine.ADC(machine.Pin(39))
adc39.atten(machine.ADC.ATTN_11DB) # Sets the analog input range

print("=== CHAMPIONSHIP OF DARKNESS ===")	# Mission title
print("Three materials enter. The darkest one wins!")	# The rules
champion = ""	# The name of the current champion
record = 999999	# Start with an impossible LOW value (lower = darker)

for round in range(1, 4, 1):	# Three competing rounds
    name = input("Name material " + str(round) + ": ")	# YOU name the challenger
    x = input('Place it under the sensor and press Enter... ')	# You place it
    total = 0	# Fresh accumulator for this challenger
    for i in range(1, 6, 1):	# Five official samples
        total += adc39.read_u16()	# Collect the evidence
        time.sleep_ms(150)	# Calm sampling
    average = total // 5	# The official score
    print(name, "scores:", average)	# Announce the score
    if average < record:	# Darker than the current champion?
        record = average	# New record!
        champion = name	# New champion crowned!
        print(">>>", name, "takes the lead!")	# Drama
    else:	# Not darker than the champion
        print("Not dark enough... the champion stands!")	# Respect
print("================================")	# Drum roll line
print("CHAMPION OF DARKNESS:", champion)	# The final verdict
print("Winning score:", record)	# The winning number

Serial Monitor:  

=== CHAMPIONSHIP OF DARKNESS === 

Three materials enter. The darkest one wins! 

Name material 1: Paper 

Place it under the sensor and press Enter... 

Paper scores: 62180 

Not dark enough... the champion stands! 

Name material 2: Cardboard 

Place it under the sensor and press Enter... 

Cardboard scores: 38940 

>>> Cardboard takes the lead! 

Name material 3: Black felt 

Place it under the sensor and press Enter... 

Black felt scores: 6020 

>>> Black felt takes the lead! 

CHAMPION OF DARKNESS: Black felt 

Winning score: 6020 

💡 Note: the best-so-far pattern finally gets its starring role the same logic that will find the closest wall (Module 19) and the strongest shake (Module 21). Notice the one twist that makes it work here: "best" means **LOWEST** value, so the comparison flips (`<` instead of `>`), and the impossible starting value becomes 999999 instead of 0. A pattern isn't memorized it's *adapted: direction of "better" is a dial you turn per mission. Run the championship with surprise materials (a coin? your sleeve? the desk?) and see who dethrones the felt. Case closed and the analog family with it.