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.
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.
/*
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.
The same button and patterns in MicroPython. Each pattern is a tuple of times, on first; time.ticks_ms and ticks_diff do what millis() does, and survive the counter wrapping.
"""
Vibration Motor - patterns on a button, MicroPython TK30 / /p/tk30
Wiring. Count from the square pad on the TinkerBlock board, parts
up, header at the bottom:
GND -> GND
VCC -> 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 -> GPIO 4 on an ESP32 or ESP32-S3, GP15 on a Raspberry
Pi Pico
TK04 Push Button: GND to GND, VCC to 3V3, SIGNAL to GPIO 25 on an
ESP32, GPIO 5 on an ESP32-S3, GP14 on a 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 and time are built in.
"""
import time
from machine import Pin
# The GPIO number SIGNAL is wired to. ESP32: 4. ESP32-S3: 4. Pico: 15.
MOTOR_PIN = 4
# The TK04's SIGNAL. ESP32: 25. ESP32-S3: 5. Pico: 14.
BUTTON_PIN = 5
# On, off, on ... in ms, on first.
TAP_MS = (100,)
DOUBLE_MS = (100, 120, 100)
LONG_MS = (400,)
ALARM_MS = (200, 200, 200, 200, 200)
READ_MS = 20 # read the button this often
motor = Pin(MOTOR_PIN, Pin.OUT, value=0)
button = Pin(BUTTON_PIN, Pin.IN) # the TK04 has its own pull-down
pattern = None
at = 0
since = 0
last_read = 0
was_pressed = False
while True:
now = time.ticks_ms()
if time.ticks_diff(now, last_read) >= READ_MS:
last_read = now
pressed = button.value() == 1
if pressed and not was_pressed and pattern is None:
print("press")
pattern, at, since = DOUBLE_MS, 0, now
motor.on() # every pattern starts on
was_pressed = pressed
if pattern and time.ticks_diff(now, since) >= pattern[at]:
at += 1
since = now
if at == len(pattern): # the end of the list
pattern = None
motor.off()
else:
motor.value(1 if at % 2 == 0 else 0) # even: onSwap DOUBLE_MS for TAP_MS, LONG_MS or ALARM_MS in the press branch. A press while a pattern plays is ignored. Stop it with Ctrl-C, then motor.off() if it stopped mid-buzz.
When it does not work
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.
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 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.
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.
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
Questions about this product
See what other owners have asked, and read their solutions.
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.