A knock is not a tilt
The roller is loose in its cell, so it rattles when the board is carried or knocked, and every rattle shows up in the scan. The fix is to believe a reading only after the same one five times in a row, 10 ms apart. That costs 50 ms of delay and removes every flicker shorter than that.
A rattle is a real change
The roller is loose on purpose; that is how it follows gravity. The same looseness means it bounces when the board is moved. Every time it lifts off its contacts, the scan reads none, and when it lands again it may land on a side for a scan and roll on.
Those are not errors in the scan. The roller really did lose contact, for a few milliseconds. A sketch that acts on every scan acts on each of them: a counter counts them, an alarm sounds for them, a display flickers through them.
Believe it five times
The top lane is every scan, 10 ms apart. The board is tipped from left to right at 100 ms, and knocked at 380 ms. The timing is illustrative: the switch's drawing gives no bounce time and nobody has put this one on a scope. The shape is the one a rolling contact is known for.
With a filter of one, every scan is believed and one tilt and one knock become a string of changes. With five, a new reading has to come back five times running, 50 ms, before the sketch believes it. The crossing, the rattle and the knock are all shorter than that, and the sketch sees one change, left to right.
With three, 30 ms, the rattle and the knock are filtered but the roller's trip across the cell is not, and the filter reports a moment of none on the way. That is true, and usually not what you want.
The cost is delay
Every filter trades noise for lateness. Five scans means a real tilt is believed 50 ms after the roller settles, on top of the time the roller takes to roll. For a tilt switch, that is nothing a person would notice.
The sketch is the last build's scan with a counter added: candidate is the
side the scans currently agree on, count is how many times running, and
believed changes only when count reaches STABLE_READS. The low side
lights from believed, so the LEDs stop flickering too. The push button
book's debouncing with
millis() does the
same job with a clock rather than a count.
The code
The scan every 10 ms, and a filter: a new side is believed only after STABLE_READS identical scans in a row. It prints each believed change and keeps the low side lit, as in the last build.
/*
4-Direction Tilt Sensor - a filtered read TK19 / /p/tk19
Wiring. Count from the square pad on the TinkerBlock board, parts
up, header at the bottom:
GND -> GND
VCC -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
A -> D2 on an Uno, GPIO 25 on an ESP32, GPIO 4 on an
ESP32-S3, GP10 on a Raspberry Pi Pico
B -> D3, GPIO 26, GPIO 5, GP11 (same order)
C -> D4, GPIO 27, GPIO 6, GP12
D -> D5, GPIO 32, GPIO 7, GP13
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)
No library needed.
*/
// The GPIO numbers A, B, C and D are wired to.
// Uno: 2 3 4 5. ESP32: 25 26 27 32. ESP32-S3: 4 5 6 7. Pico: 10 11 12 13.
const int PIN_A = 4;
const int PIN_B = 5;
const int PIN_C = 6;
const int PIN_D = 7;
const int STABLE_READS = 5; // 5 scans, 10 ms apart: 50 ms
const int SCAN_MS = 10;
const int NO_SIDE = 0;
const int LEFT_DOWN = 1;
const int TOP_DOWN = 2;
const int RIGHT_DOWN = 3;
const int HEADER_DOWN = 4;
const char* SIDE_NAME[] = {"none", "left", "top", "right", "header"};
void releaseAll() {
pinMode(PIN_A, INPUT_PULLUP);
pinMode(PIN_B, INPUT_PULLUP);
pinMode(PIN_C, INPUT_PULLUP);
pinMode(PIN_D, INPUT_PULLUP);
}
void holdLow(int pin) {
digitalWrite(pin, LOW); // LOW first, then output
pinMode(pin, OUTPUT);
}
bool readsLow(int pin) {
return digitalRead(pin) == LOW;
}
int scanTilt() {
releaseAll();
holdLow(PIN_A);
delayMicroseconds(10);
bool ab = readsLow(PIN_B); // left
bool da = readsLow(PIN_D); // header
releaseAll();
holdLow(PIN_C);
delayMicroseconds(10);
bool bc = readsLow(PIN_B); // top
bool cd = readsLow(PIN_D); // right
releaseAll();
if (ab) return LEFT_DOWN;
if (da) return HEADER_DOWN;
if (bc) return TOP_DOWN;
if (cd) return RIGHT_DOWN;
return NO_SIDE;
}
void holdPair(int side) {
if (side == LEFT_DOWN) { holdLow(PIN_A); holdLow(PIN_B); }
if (side == TOP_DOWN) { holdLow(PIN_B); holdLow(PIN_C); }
if (side == RIGHT_DOWN) { holdLow(PIN_C); holdLow(PIN_D); }
if (side == HEADER_DOWN) { holdLow(PIN_D); holdLow(PIN_A); }
}
int candidate = NO_SIDE; // what the scans agree on right now
int count = 0; // how many scans in a row
int believed = -1; // what the sketch acts on
void setup() {
Serial.begin(115200);
releaseAll();
}
void loop() {
int side = scanTilt();
if (side == candidate) {
if (count < STABLE_READS) count++;
} else {
candidate = side; // something new: start counting again
count = 1;
}
if (count >= STABLE_READS && candidate != believed) {
believed = candidate;
Serial.println(SIDE_NAME[believed]);
}
if (believed >= 0) holdPair(believed); // light the believed side
delay(SCAN_MS);
}candidate is the side the scans are currently agreeing on and count is how many times running. believed changes only when count reaches STABLE_READS. A knock that lifts the roller off for one or two scans resets count and changes nothing.
The same filter in MicroPython: scan every 10 ms, and believe a side only after STABLE_READS identical scans in a row.
"""
4-Direction Tilt Sensor - filtered read, MicroPython TK19 / /p/tk19
Wiring. Count from the square pad on the TinkerBlock board, parts
up, header at the bottom:
GND -> GND
VCC -> 3V3 (never 5V beside a 3.3 V board)
A -> GPIO 25 on an ESP32, GPIO 4 on an ESP32-S3, GP10 on a Pico
B -> GPIO 26, GPIO 5, GP11 (same order)
C -> GPIO 27, GPIO 6, GP12
D -> GPIO 32, GPIO 7, GP13
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 and time are built in.
"""
from machine import Pin
import time
# The GPIO numbers A, B, C and D are wired to.
# ESP32: 25 26 27 32. ESP32-S3: 4 5 6 7. Pico: 10 11 12 13.
PIN_A, PIN_B, PIN_C, PIN_D = 4, 5, 6, 7
STABLE_READS = 5 # 5 scans, 10 ms apart: 50 ms
SCAN_MS = 10
a = Pin(PIN_A, Pin.IN, Pin.PULL_UP)
b = Pin(PIN_B, Pin.IN, Pin.PULL_UP)
c = Pin(PIN_C, Pin.IN, Pin.PULL_UP)
d = Pin(PIN_D, Pin.IN, Pin.PULL_UP)
PAIRS = {"left": (a, b), "top": (b, c), "right": (c, d), "header": (d, a)}
def release_all():
for p in (a, b, c, d):
p.init(Pin.IN, Pin.PULL_UP)
def hold_low(p):
p.init(Pin.OUT, value=0)
def scan_tilt():
release_all()
hold_low(a)
time.sleep_us(10)
ab = b.value() == 0
da = d.value() == 0
release_all()
hold_low(c)
time.sleep_us(10)
bc = b.value() == 0
cd = d.value() == 0
release_all()
if ab:
return "left"
if da:
return "header"
if bc:
return "top"
if cd:
return "right"
return "none"
candidate = "none" # what the scans agree on right now
count = 0 # how many scans in a row
believed = None # what the sketch acts on
while True:
side = scan_tilt()
if side == candidate:
count = min(count + 1, STABLE_READS)
else:
candidate = side # something new: start counting again
count = 1
if count >= STABLE_READS and candidate != believed:
believed = candidate
print(believed)
for p in PAIRS.get(believed, ()):
hold_low(p) # light the believed side
time.sleep_ms(SCAN_MS)A knock that lifts the roller for a scan or two restarts the count and changes nothing. Raise STABLE_READS if the board is mounted somewhere that shakes; each step adds 10 ms before a real tilt is believed.
When it does not work
Something is moving the board for longer than 50 ms at a time: a motor, a table being bumped, a hand carrying it. Fix the block down firmly so it moves only with the thing it is meant to measure, or raise STABLE_READS. Each step adds 10 ms of delay.
It waits for five identical scans, 50 ms, after the roller settles, and the roller itself takes a moment to cross the cell. For a tilt switch that is usually fine. Lower STABLE_READS if you need faster, and accept more flicker.
Its maker's drawing does not say, and nobody has measured it for this book. The figure here is illustrative. 50 ms is a comfortable margin for a rolling contact, and the sketch lets you change it.
The idea is the same, a change believed only once it has stayed put, and either works. Counting identical scans is simpler here, because the scan already runs on a steady 10 ms beat.
A box that knows it has been tipped over, and keeps saying so.
A tip-over alarm →Edit this page — content/books/four-direction-tilt-sensor/a-knock-is-not-a-tilt.mdx
Questions about this product
See what other owners have asked, and read their solutions.
4-Direction Tilt Sensor
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.