A safe doesn't have buttons. It has a dial that you turn to the right to the first number, then to the left to the second, then to the right again to the third. This lesson builds that kind of lock with the rotary encoder. Every click of the dial is one number from 0 to 19, and the buzzer clicks with it, just like a real safe. Changing direction locks in a number. Press the knob after the third number: if the combination is right, the LED turns green and the servo opens the latch for ten seconds. Wrong, and it flashes red and you start again.
What you'll learn
- How to count steps and directions with a rotary encoder
- How to notice the moment the dial changes direction
- How to check a combination of three numbers stored in an array
- How to lock and unlock a latch with a servo
What you need
- UNO R3 board and USB cable (UNO R3 Starter Kit)
- Rotary encoder module (45-in-1 Sensor Kit)
- Servo motor SG90 with horns and screws (UNO R3 Starter Kit)
- Passive buzzer (UNO R3 Starter Kit)
- Red LED, green LED and 2 resistors, 220 Ω (UNO R3 Starter Kit)
- A small box with a lid, for the lock
- The printed parts from the 3D printing box below (safe dial), or the cardboard version described there
- Breadboard and jumper wires (UNO R3 Starter Kit)
Wiring

| From | To |
|---|---|
| Encoder CLK | UNO pin 2 |
| Encoder DT | UNO pin 3 |
| Encoder SW | UNO pin 4 |
| Encoder + and GND | 5V and GND |
| Servo signal (orange) | UNO pin 9 |
| Servo power (red) and GND (brown) | 5V and GND |
| Passive buzzer + | UNO pin 8 |
| Passive buzzer − | GND |
| UNO pin 5 | 220 Ω resistor, then the red LED's long leg |
| UNO pin 6 | 220 Ω resistor, then the green LED's long leg |
| Both LED short legs | GND |
Sensor modules from different makers can label and order their pins differently. Always follow the labels printed on your module.
The sketch
The Servo library comes with the Arduino IDE. Choose your own combination in SECRET, and find the servo angles that lock and open your latch.
#include <Servo.h>
const int CLK_PIN = 2, DT_PIN = 3, SW_PIN = 4;
const int SERVO_PIN = 9, BUZZER_PIN = 8, RED_PIN = 5, GREEN_PIN = 6;
const int NUMBERS = 20; // the dial goes from 0 to 19
const int SECRET[3] = {12, 5, 17}; // right to 12, left to 5, right to 17
const int LOCKED_ANGLE = 10, OPEN_ANGLE = 100;
Servo latch;
int position = 0; // the number the pointer is on
int direction = 0; // +1 = right (clockwise), -1 = left
int entered[3];
int count = 0; // numbers entered so far
int lastClk;
bool lastSw = HIGH;
unsigned long openUntil = 0;
void fail() {
Serial.println("Wrong combination. Start again.");
for (int i = 0; i < 3; i++) {
digitalWrite(RED_PIN, HIGH);
tone(BUZZER_PIN, 200, 150);
delay(250);
digitalWrite(RED_PIN, LOW);
delay(150);
}
}
void openLock() {
Serial.println("Correct! Open.");
digitalWrite(GREEN_PIN, HIGH);
latch.write(OPEN_ANGLE);
tone(BUZZER_PIN, 1500, 100);
delay(150);
tone(BUZZER_PIN, 2000, 200);
openUntil = millis() + 10000; // locks again after 10 s
}
void lock() {
latch.write(LOCKED_ANGLE);
digitalWrite(GREEN_PIN, LOW);
Serial.println("Locked.");
}
void setup() {
Serial.begin(9600);
pinMode(CLK_PIN, INPUT);
pinMode(DT_PIN, INPUT);
pinMode(SW_PIN, INPUT_PULLUP);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
latch.attach(SERVO_PIN);
lock();
lastClk = digitalRead(CLK_PIN);
}
void loop() {
int clk = digitalRead(CLK_PIN);
if (clk != lastClk && clk == LOW) { // one click of the dial
int step = digitalRead(DT_PIN) != clk ? 1 : -1;
if (direction != 0 && step != direction && count < 3) { // turned round: the number is locked in
entered[count++] = position;
Serial.print("Number ");
Serial.print(count);
Serial.print(": ");
Serial.println(position);
tone(BUZZER_PIN, 900, 40);
}
direction = step;
position = (position + step + NUMBERS) % NUMBERS;
tone(BUZZER_PIN, 3500, 3); // the safe's click
Serial.println(position);
}
lastClk = clk;
bool sw = digitalRead(SW_PIN);
if (sw == LOW && lastSw == HIGH) { // knob pressed: the last number counts too
delay(30);
if (millis() < openUntil) {
openUntil = 0;
lock();
} else {
if (count < 3) entered[count++] = position;
bool right = count == 3;
for (int i = 0; i < 3; i++) if (entered[i] != SECRET[i]) right = false;
if (right) openLock();
else fail();
count = 0;
direction = 0;
}
}
lastSw = sw;
if (openUntil != 0 && millis() > openUntil) {
openUntil = 0;
lock();
}
}
Open it
- Open the Serial Monitor at 9600 baud and turn the dial a few clicks: it counts up to the right and down to the left, from 19 back to 0.
- Turn right to 12. Turn left: the moment you change direction, 12 is locked in. Keep turning left to 5.
- Turn right again: 5 is locked in. Stop at 17 and press the knob. Green light, the latch opens!
- Press the knob again to lock it, or wait ten seconds.
How it works
The rotary encoder has two switches inside, CLK and DT, that open and close as you turn, a little out of step with each other. When CLK changes, DT says which way you turned: if it is different from CLK, clockwise; if it is the same, anticlockwise. Each click moves position by one, and % NUMBERS wraps it round, so 19 + 1 is 0, like on a real dial.
A safe doesn't need an Enter button for each number, because changing direction is the signal. The sketch remembers the last direction; when a click goes the other way, the number the pointer was on goes into the entered array. The third number is entered by pressing the knob.
Then a loop compares all three numbers with SECRET. Only if every one matches does right stay true. The lock doesn't say which number was wrong: that would make it much easier to crack!
Try this next
- Let the owner change the combination: hold the knob for three seconds while the lock is open, then dial a new one.
- Make it harder: four numbers, or 40 numbers by counting two clicks per number.
- Block the lock for a minute after three wrong tries.
Troubleshooting
- The board restarts when the servo moves: servos draw bursts of current. Use a good USB port or a powered hub, and check that the servo's red wire is on 5V.
- The dial counts the wrong way: swap the CLK and DT wires, or the two pin numbers.
- It jumps two numbers per click: your encoder makes two pulses per click. Only count every second one.
- A number is locked in by mistake: turn smoothly; a tiny turn back counts as a change of direction. Press the knob to start again.
- A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.