What it cannot tell you · 09 of 11

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

Believe it five times
Same reading in a row before believing it
Scans
every 10 ms
Waits for
50 ms
Changes reported
1
5 reads in a row, 50 ms: the crossing, the rattle and the knock are all shorter than that, so the filter reports one change, left to right, at 250 ms. That is 150 ms after the board was tipped, and it is the price.

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.

tilt_filtered.ino
/*
  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.

When it does not work

It still flickers with the filter.

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 is slow to notice a tilt.

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.

How long does the switch bounce?

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.

Can I use the push button's millis() debounce instead?

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.

Where this goes next

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

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

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.

Browse Modules and blocks on the forum