The first read
Six wires, no library, and a sketch that scans the switch five times a second and prints which edge is down: left, top, right, header, or none. Two lines are held LOW in turn, B and D are read each time, and no line is ever driven HIGH.
Six wires
GND to GND. VCC to your board's own supply: 5V on an Uno, 3V3 on the three 3.3 V boards. A, B, C and D to four digital pins, in that order.
The pins are the same in every sketch in this book: D2 to D5 on an Uno, GPIO 25, 26, 27 and 32 on an ESP32, GPIO 4 to 7 on an ESP32-S3, and GP10 to GP13 on a Pico. Each can be an output and an input with a pull-up, and none is one the chip reads at boot, which matters because the roller may be joining two of them while the board resets.
The sketch
Three small functions do the work. releaseAll() makes every line an input
with its pull-up. holdLow() holds one line LOW. scanTilt() holds A LOW
and reads B and D, then holds C LOW and reads them again, exactly as in no
line goes to
ground,
and lets everything go.
Two details are there to protect the pins. holdLow() writes LOW before it
makes the pin an output, so the pin goes straight to LOW; the other way
round, an Uno's pin drives HIGH for a moment first. And nothing in the sketch
ever writes HIGH.
delayMicroseconds(10) gives each line time to settle after the switch. A
pull-up is tens of kilohms, and it takes a moment to lift a line back up.
What you should see
The serial monitor at 115200 prints a line five times a second. Tip the board one way, then another, holding it a moment each time:
left
left
none
top
top
right
headerA none between two sides is the roller crossing the cell, or resting
against a single contact. The LEDs look dark while this sketch runs: each
line is held LOW for a few microseconds per scan, too short to see. The next
article keeps the low side lit.
The code
No library. scanTilt() holds A LOW and reads B and D, then holds C LOW and reads them again, and returns which edge is down. Change the four PIN_ numbers to the GPIOs you wired.
/*
4-Direction Tilt Sensor - first 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
(every LED hangs from VCC onto a line your pin holds)
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;
// Which edge is down. NO_SIDE: the roller is joining no pair.
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"};
// Every line an input with its pull-up. No line is ever driven HIGH.
void releaseAll() {
pinMode(PIN_A, INPUT_PULLUP);
pinMode(PIN_B, INPUT_PULLUP);
pinMode(PIN_C, INPUT_PULLUP);
pinMode(PIN_D, INPUT_PULLUP);
}
// Hold one line LOW: LOW first, then output, so it never goes HIGH.
void holdLow(int pin) {
digitalWrite(pin, LOW);
pinMode(pin, OUTPUT);
}
bool readsLow(int pin) {
return digitalRead(pin) == LOW;
}
// Hold A LOW and read B and D, then hold C LOW and read them again.
int scanTilt() {
releaseAll();
holdLow(PIN_A);
delayMicroseconds(10); // let the lines settle
bool ab = readsLow(PIN_B); // A-B joined: left edge down
bool da = readsLow(PIN_D); // D-A joined: header edge down
releaseAll();
holdLow(PIN_C);
delayMicroseconds(10);
bool bc = readsLow(PIN_B); // B-C joined: top edge down
bool cd = readsLow(PIN_D); // C-D joined: right edge down
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 setup() {
Serial.begin(115200);
releaseAll();
}
void loop() {
Serial.println(SIDE_NAME[scanTilt()]);
delay(200); // five scans a second, to keep it readable
}Every released line is INPUT_PULLUP and the only value the sketch ever writes is LOW. holdLow() writes LOW before switching the pin to an output, so the pin never drives HIGH even for a moment. The delay(200) only keeps the output readable.
The same scan in MicroPython, for an ESP32, an ESP32-S3 or a Pico. init() switches a pin between an input with its pull-up and an output held at 0.
"""
4-Direction Tilt Sensor - first 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
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)
def release_all():
# Every line an input with its pull-up. Never driven HIGH.
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) # let the lines settle
ab = b.value() == 0 # A-B joined: left edge down
da = d.value() == 0 # D-A joined: header edge down
release_all()
hold_low(c)
time.sleep_us(10)
bc = b.value() == 0 # B-C joined: top edge down
cd = d.value() == 0 # C-D joined: right edge down
release_all()
if ab:
return "left"
if da:
return "header"
if bc:
return "top"
if cd:
return "right"
return "none"
while True:
print(scan_tilt())
time.sleep_ms(200) # five scans a secondThere is no Uno here: an Uno cannot run MicroPython. init(Pin.OUT, value=0) sets the level before the direction, so the pin goes straight to LOW. Stop it with Ctrl-C in Thonny's shell.
When it does not work
No line ever followed the one held LOW. Check that the four pin numbers in the sketch are the four GPIOs you wired, in the order A, B, C, D, and that the cable is not one pin over: count from the square pad. Then tip the board firmly, a good way past level, and hold it still for a moment.
Two lines are swapped, usually in the wiring. Check the order from the square pad: GND, VCC, A, B, C, D. The names also assume the board is parts up with the header at the bottom; hold it another way round and the names turn with it.
That is the roller staying where it last rolled. Nothing pushes it back to the middle, so level usually reads the last side, or none. Level is not a reading covers it.
Set Tools > USB CDC On Boot to Enabled and upload again. Without it the S3's USB port does not bring up a serial port at boot, so the sketch runs with nowhere to print.
The same scan, with the two LEDs on the downhill side held lit.
Lighting the low side →Edit this page — content/books/four-direction-tilt-sensor/the-first-read.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.