A key that remembers
Each press moves to the next mode, off, red, green, blue, and the key shows the mode it is in. It needs one press to be one step, so the sketch waits for the contacts to stop bouncing: 20 ms of a steady reading before it believes a change.
The light says the state
The first sketch lit the key while it was held, which tells you nothing you did not know. This one gives the key a memory: a mode that each press advances, and a colour for each mode.
| Mode | Colour |
|---|---|
| 0 | off |
| 1 | red |
| 2 | green |
| 3 | blue |
After blue it wraps round to off. Replace the colours with meanings and it is a control: a mode switch for a project, armed and disarmed, a count of something.
One press has to be one step
A metal contact does not close cleanly. It touches, springs apart, touches again, for a few milliseconds, and each touch is a rising edge your pin can see. A sketch that steps on every rising edge can step two or three times for one press, and the key jumps from off past red to blue. The figure's waveform is a picture of the behaviour rather than a measurement of this switch, whose maker is not recorded; Cherry specifies its own MX switches at under 5 ms of bounce.
Wait until it stops moving
The sketch keeps the last raw reading and the time it last changed. Every
change restarts that clock. Only when the reading has stayed the same for
DEBOUNCE_MS, 20 ms, and differs from the last accepted state, does the
sketch accept it:
if (now - lastChange >= DEBOUNCE_MS && reading != state) {
state = reading;
if (state == HIGH) { // one real press: next mode20 ms is far longer than the bounce and far shorter than a person can press twice, so it costs nothing you can feel. The TK04 book takes the same code apart line by line.
What you should see
Each press prints one line and changes the colour once:
mode 1 red
mode 2 green
mode 3 blue
mode 0 offOn an ESP32, an ESP32-S3 or a Pico, with VCC on 3V3, expect red to be the brightest of the three.
The code
Adafruit NeoPixel for the light, digitalRead and millis() for the key. Every change in the reading restarts a clock; once the reading has held for DEBOUNCE_MS and differs from the last settled state, the sketch accepts it, and on each accepted press steps the mode, shows its colour and prints it.
/*
Mechanical Key and LED - a key that remembers TK96 / /p/tk96
Each press moves to the next mode, and the key shows the mode it
is in: off, red, green, blue, then off again.
Wiring. Count from the square pad on the TinkerBlock board, key
up, header at the bottom:
GND -> GND
VCC -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
(pressed, BUTTON gives your pin whatever VCC is)
WS2812 -> D6 on an Uno, GPIO 4 on an ESP32, GPIO 5 on an
ESP32-S3, GP14 on a Raspberry Pi Pico
BUTTON -> D2 on an Uno, GPIO 25 on an ESP32, GPIO 4 on an
ESP32-S3, GP15 on a Raspberry Pi Pico
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
Library Manager: install "Adafruit NeoPixel".
*/
#include <Adafruit_NeoPixel.h>
// Uno: 2. ESP32: 25. ESP32-S3: 4. Pico: 15.
const int BUTTON_PIN = 4;
// Uno: 6. ESP32: 4. ESP32-S3: 5. Pico: 14.
const int LED_PIN = 5;
const unsigned long DEBOUNCE_MS = 20;
Adafruit_NeoPixel key(1, LED_PIN, NEO_GRB + NEO_KHZ800);
const int MODES = 4;
const uint32_t COLOUR[MODES] = { 0x000000, 0xFF0000, 0x00FF00,
0x0000FF };
const char *NAME[MODES] = { "off", "red", "green", "blue" };
int mode = 0; // what the key remembers
int lastReading = LOW;
int state = LOW;
unsigned long lastChange = 0;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT); // the block has its own pull-down
key.begin();
key.show(); // a dark frame: mode 0 is off
}
void loop() {
int reading = digitalRead(BUTTON_PIN);
unsigned long now = millis();
if (reading != lastReading) { // the contacts moved: restart the clock
lastReading = reading;
lastChange = now;
}
if (now - lastChange >= DEBOUNCE_MS && reading != state) {
state = reading;
if (state == HIGH) { // one real press: next mode
mode = (mode + 1) % MODES;
key.setPixelColor(0, COLOUR[mode]);
key.show();
Serial.print("mode ");
Serial.print(mode);
Serial.print(" ");
Serial.println(NAME[mode]);
}
}
}The mode is the sketch's memory; the colour only reports it. Nothing uses delay(), so the loop reads the key thousands of times a second. The sketch compiles for an ESP32-S3 and an Uno.
View on GitHub · blocks/tk96-mechanical-key/arduino/key_modes/key_modes.ino @ v1.8The same sketch in MicroPython, for an ESP32, an ESP32-S3 or a Pico. time.ticks_ms() and ticks_diff() do what millis() does, and survive the counter wrapping round.
"""
Mechanical Key and LED - a key that remembers TK96 / /p/tk96
Each press moves to the next mode, and the key shows the mode it
is in: off, red, green, blue, then off again.
Wiring. Count from the square pad on the TinkerBlock board, key
up, header at the bottom:
GND -> GND
VCC -> 3V3 (never 5V: pressed, BUTTON gives your pin VCC)
WS2812 -> GPIO 4 on an ESP32, GPIO 5 on an ESP32-S3,
GP14 on a Raspberry Pi Pico
BUTTON -> GPIO 25 on an ESP32, GPIO 4 on an ESP32-S3,
GP15 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
Nothing to install: machine, neopixel and time are built in.
"""
from machine import Pin
from neopixel import NeoPixel
import time
# ESP32: 25 and 4. ESP32-S3: 4 and 5. Pico: 15 and 14.
BUTTON_PIN = 4
LED_PIN = 5
DEBOUNCE_MS = 20
COLOUR = [(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255)]
NAME = ["off", "red", "green", "blue"]
button = Pin(BUTTON_PIN, Pin.IN) # no pull: the block has its own
key = NeoPixel(Pin(LED_PIN), 1)
key[0] = COLOUR[0]
key.write() # a dark frame: mode 0 is off
mode = 0 # what the key remembers
last_reading = 0
state = 0
last_change = time.ticks_ms()
while True:
reading = button.value()
now = time.ticks_ms()
if reading != last_reading: # the contacts moved: restart
last_reading = reading
last_change = now
settled = time.ticks_diff(now, last_change) >= DEBOUNCE_MS
if settled and reading != state:
state = reading
if state == 1: # one real press: next mode
mode = (mode + 1) % len(COLOUR)
key[0] = COLOUR[mode]
key.write()
print("mode", mode, "", NAME[mode])There is no Uno here: an Uno cannot run MicroPython. VCC goes to 3V3, so green and blue will be dimmer than red. Stop it with Ctrl-C.
View on GitHub · blocks/tk96-mechanical-key/micropython/key_modes.py @ v1.8When it does not work
The contacts bounced and the sketch counted more than one press. Check DEBOUNCE_MS is still 20 and that the step happens only when state changes to HIGH, not on every loop while the reading is HIGH. A bounce longer than 20 ms is unusual; try 30 if a worn switch still skips.
On a 3.3 V board that is expected: VCC is on 3V3, under the LED's 3.5 V minimum, and green and blue need the most voltage. On an Uno, with VCC on 5V, all three should look alike. For full colour on a 3.3 V board see the divider in One VCC, two jobs.
It is meant not to. The sketch steps once when the key goes down, then waits for it to come up and go down again. Holding it is one press, however long.
The mode is a variable in RAM, so a reset or power cut starts again at off. To keep it, save it in the ESP32's Preferences or the Uno's EEPROM when it changes, and read it back in setup().
The key and the light fail apart. Which half is it, and where to look.
When it misbehaves →Edit this page — content/books/mechanical-key/a-key-that-remembers.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Mechanical Key and LED
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.