vibration motor/Using it/09. Patterns you can feel
Using it · 09 of 10

Patterns you can feel

A hand tells vibration apart by rhythm, not strength: one tap, two taps, one long buzz, a repeating alarm. Each pattern is a list of on and off times, and a sketch that walks the list with millis() instead of delay() keeps reading its button while the motor runs. The build: a TK04 Push Button that plays a double tap on every press.

Rhythm, not strength

A hand is poor at telling one strength of buzz from another and good at counting. So the patterns that work are made of full-power pulses with gaps between them: one short tap, two taps, one long buzz, a steady alarm.

Patterns you can feel
Pattern
Pulses
2
Lasts
320 ms
Now
waiting
Two short pulses with a gap a hand can feel. Distinct from a tap without being longer.

Pick a pattern and play it. Every one is a list of times in milliseconds, on first, then off, then on. The motor shakes and the LED lights on every on time, as the real board does. None of the pulses is shorter than 100 ms: a coin motor takes a few tens of milliseconds to spin up, so a shorter pulse barely gets going.

A button to press

The second block is the TK04 Push Button: GND to GND, VCC to your board's logic supply, SIGNAL to D2 on an Uno, GPIO 25 on an ESP32, GPIO 5 on an ESP32-S3, GP14 on a Pico. Its VCC is different from the motor block's. The push button connects its own VCC to its SIGNAL when pressed, so VCC must be your board's logic voltage, 3V3 on the three 3.3 V boards. The motor block can stay on 5V.

Playing without waiting

The obvious way to play a pattern is digitalWrite, delay(100), digitalWrite, delay(120). It works, and while it runs the sketch does nothing else: a press during a delay is missed.

The sketch instead keeps three things: which pattern is playing, which time in it, and when that time began. Each pass of loop() asks update() one question: has the current time run out? If not, it returns at once. If so, it moves to the next time and sets the motor on or off, even places on and odd places off. At the 0 that ends the list it switches off and forgets the pattern.

So loop() goes round thousands of times a second, and in between it reads the button every 20 ms. Reading every 20 ms rather than every pass hides most of the button's bounce: the contacts settle in a few milliseconds, and the next read sees them settled.

What you should see

Press the button and the motor gives two short taps; the serial monitor at 115200 prints press. Press again while it is playing and nothing happens until it ends. Change play(DOUBLE_MS) to play(ALARM_MS) for three pulses at an even beat.

The code

A press on the TK04 plays DOUBLE_MS: on 100 ms, off 120, on 100. The pattern plays from loop() with millis(), so loop() never waits and the button is read throughout. Swap DOUBLE_MS for TAP_MS, LONG_MS or ALARM_MS in the press handler.

motor_patterns.ino
/*
  Vibration Motor - patterns on a button                  TK30 / /p/tk30

  Wiring. Count from the square pad on the TinkerBlock board, parts
  up, header at the bottom:

    GND    -> GND
    VCC    -> 5V: an Uno's 5V, the 5V pin of an ESP32 or ESP32-S3
              board on USB, a Pico's VBUS (3V3 works as well)
    NC     -> nothing   (unconnected on the board)
    SIGNAL -> D9 on an Uno, GPIO 4 on an ESP32 or ESP32-S3,
              GP15 on a Raspberry Pi Pico

  TK04 Push Button: GND to GND, VCC to your board's logic supply
  (5V on an Uno, 3V3 on the others), SIGNAL to D2 on an Uno, GPIO 25
  on an ESP32, GPIO 5 on an ESP32-S3, GP14 on a Pico.

  Arduino IDE
    Tools > Board                 your board, e.g. Arduino Uno
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    No library needed.
*/

// The pin SIGNAL is wired to.
// Uno: 9. ESP32: 4. ESP32-S3: 4. Pico: 15.
const int MOTOR_PIN = 9;
// The TK04's SIGNAL. Uno: 2. ESP32: 25. ESP32-S3: 5. Pico: 14.
const int BUTTON_PIN = 2;

// On, off, on ... in ms, on first. 0 ends the list.
const unsigned int TAP_MS[] = {100, 0};
const unsigned int DOUBLE_MS[] = {100, 120, 100, 0};
const unsigned int LONG_MS[] = {400, 0};
const unsigned int ALARM_MS[] = {200, 200, 200, 200, 200, 0};

const int READ_MS = 20;             // read the button this often

const unsigned int *pattern = nullptr;   // playing, or none
int at = 0;                              // which time in it
unsigned long since = 0;                 // when that time began
unsigned long lastRead = 0;
bool wasPressed = false;

void play(const unsigned int *p) {
  pattern = p;
  at = 0;
  since = millis();
  digitalWrite(MOTOR_PIN, HIGH);         // every pattern starts on
}

void update() {
  if (pattern == nullptr) return;
  if (millis() - since < pattern[at]) return;
  at++;
  since = millis();
  if (pattern[at] == 0) {                // the end of the list
    pattern = nullptr;
    digitalWrite(MOTOR_PIN, LOW);
    return;
  }
  digitalWrite(MOTOR_PIN, at % 2 == 0 ? HIGH : LOW);   // even: on
}

void setup() {
  Serial.begin(115200);
  pinMode(MOTOR_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT);   // the TK04 has its own pull-down
}

void loop() {
  if (millis() - lastRead >= READ_MS) {
    lastRead = millis();
    bool pressed = digitalRead(BUTTON_PIN) == HIGH;
    if (pressed && !wasPressed && pattern == nullptr) {
      Serial.println("press");
      play(DOUBLE_MS);
    }
    wasPressed = pressed;
  }
  update();
}

A press while a pattern is playing is ignored until it ends. Every pattern starts with an on time, and the list ends with 0. The motor runs at full power, so it needs no kick.

When it does not work

One press plays the pattern twice.

The button's contacts bounce, and a quick second edge after the pattern ends reads as a second press. The sketch reads the button every 20 ms, which hides most bounce; if it still happens, raise READ_MS to 30 or 50.

The short pulses are hard to feel.

A coin motor needs a few tens of milliseconds to spin up, so a pulse much under 100 ms barely gets going. Lengthen the on times rather than the gaps, and fix the board to what it should shake.

The button reads HIGH all the time.

The TK04's VCC is on the wrong supply or its SIGNAL is on a different pin from BUTTON_PIN. The push button, unlike the motor block, puts its own VCC on its SIGNAL when pressed, so its VCC must be your board's logic supply: 5V on an Uno, 3V3 on the others.

Can I use a different button?

Any switch that makes the pin HIGH when pressed works as written. For a bare button to GND, use pinMode(BUTTON_PIN, INPUT_PULLUP) and test for LOW instead of HIGH.

Where this goes next

Six symptoms, and which part of the chain each one points at.

When it does not buzz

Edit this page — content/books/vibration-motor/patterns-you-can-feel.mdx

Community

Questions about this product

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

Ask a question ↗

Vibration Motor

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