IoTSimulator

Send an SOS Signal with an ATtiny85 and a Buzzer

Press a button and watch a red LED flash while a buzzer beeps out the international SOS signal — three short, three long, three short. This project teaches you how to control timing precisely with a tiny 8-pin microcontroller.
Muhammad Ichsanul Fadhil
IoTSim Editor
September 22, 2026
Send an SOS Signal with an ATtiny85 and a Buzzer

Live project track

Interactive hardware & logic preview

SOS is the most recognized distress signal in the world. It is transmitted as three short flashes, three long flashes, and three short flashes again — and it does not matter whether you use light, sound, or radio. The rhythm is what carries the message. In this project, pressing a button turns your ATtiny85 into a pocket-sized SOS transmitter: a red LED flashes the pattern while a buzzer beeps it out loud at the same time.

In this project, you will use an ATtiny85 microcontroller, a push button, a red LED, and a piezo buzzer. The main idea is simple: Morse code is just timing. A short signal is called a dot, a long signal is called a dash, and different letters are spelled out by different combinations of dots and dashes. The letter S is three dots, O is three dashes, and your code controls how long each signal lasts.

How Morse Code Timing Works

Every Morse signal is built from one base unit of time — called T. In this project, one unit T equals 150 milliseconds, which is about the time it takes to blink once. A dot lasts exactly 1T (150 ms). A dash lasts exactly 3T (450 ms). Between signals inside the same letter, there is a 1T gap of silence. Between letters, the gap grows to 3T. Between full repetitions of the word SOS, you wait 7T before starting again.

SOS Morse code timing diagram showing dot and dash durations
SOS Morse code timing diagram showing dot and dash durations

The diagram above shows the full SOS pattern as a timeline. The three short blue blocks are the S dots (150 ms each), the three long red blocks are the O dashes (450 ms each), and then three short blue blocks repeat for the final S. Every block represents both the LED flash and the buzzer beep happening at exactly the same moment. Keeping the timing consistent is what makes the pattern recognizable.

SignalDurationWhat it means
Dot (·)150 ms (1T)One short beep and flash
Dash (—)450 ms (3T)One long beep and flash
Gap inside a letter150 ms (1T)Silence between signals
Gap between letters450 ms (3T)Pause between S and O
Gap between repetitions1050 ms (7T)Wait before SOS repeats

You do not need to memorize these numbers — your code handles them automatically using a single constant called DOT. Every other duration is just a multiple of DOT, so changing one number adjusts the entire speed of the signal.

How the LED and Buzzer Work Together

The ATtiny85 drives two output devices at the same time. On pin PB0 (physical pin 5), it switches the red LED on and off with digitalWrite(). On pin PB1 (physical pin 6), it runs the piezo buzzer using tone(), which makes the pin switch HIGH and LOW 1000 times every second. That rapid switching creates a pressure wave in the air that your ear hears as a 1 kHz beep.

How the ATtiny85 drives the piezo buzzer to produce sound
How the ATtiny85 drives the piezo buzzer to produce sound

The piezo buzzer contains a small ceramic disk. When voltage is applied, the disk bends slightly. When voltage is removed, it snaps back. Doing this 1000 times per second causes the disk to vibrate, pushing and pulling on the air around it and creating a sound wave. The ATtiny85 does this automatically with just one function call: tone(BUZZ_PIN, 1000). Calling noTone(BUZZ_PIN) stops the vibration instantly.

Both outputs — the LED and the buzzer — are turned on together at the start of each dot or dash, then turned off together at the end. This keeps the visual and acoustic signals perfectly synchronized throughout the entire SOS pattern.

Connecting the Components

The ATtiny85 is an 8-pin chip, so wiring is straightforward. The button goes to pin PB2, which is configured with an internal pull-up resistor, so you do not need any external resistor. The LED connects to PB0, and the buzzer connects to PB1. All three components share the same ground rail (physical pin 4).

ATtiny85 pinout diagram showing connections to button, LED, and buzzer
ATtiny85 pinout diagram showing connections to button, LED, and buzzer

The diagram shows which physical pins connect to which component. Pins 1, 2, and 3 on the left side are unused in this project. The right side carries all the active signals: power on pin 8, the button input on pin 7, the buzzer on pin 6, and the LED on pin 5. All three components return their ground connection to pin 4.

Pin Connection Map
Push Button
Pin 1.l
→
ATtiny85
PB2 (Pin 7)
Explanation
Button input with internal pull-up. Reads LOW when pressed.
Push Button
Pin 2.l
→
ATtiny85
GND (Pin 4)
Explanation
Ground return. Pressing the button pulls PB2 to ground.

Next, connect the red LED indicator circuit to physical pin 5 (PB0) through a 220Ω current-limiting resistor to emit visible optical Morse pulses:

Pin Connection Map
Red LED
Anode (+)
→
ATtiny85
PB0 (Pin 5)
Explanation
LED turns on when PB0 goes HIGH during each dot or dash.
Red LED
Cathode (−)
→
ATtiny85
GND (Pin 4)
Explanation
Ground return for the LED current path.

Finally, wire the piezo buzzer to physical pin 6 (PB1) so the beacon generates synchronized acoustic sound bursts alongside each optical flash:

Pin Connection Map
Piezo Buzzer
Pin + (1)
→
ATtiny85
PB1 (Pin 6)
Explanation
Buzzer receives a 1 kHz square wave from tone() during each signal.
Piezo Buzzer
Pin − (2)
→
ATtiny85
GND (Pin 4)
Explanation
Ground return for the buzzer current path.

Once wired, the button uses the ATtiny85's built-in pull-up resistor — INPUT_PULLUP in code — so no external components are needed. Pressing the button connects PB2 directly to ground, which the chip reads as a LOW signal and uses as the trigger to start broadcasting SOS.

Complete Code

The sketch below is the full program. Everything is built around one helper function called beep(), which turns the LED and buzzer on, waits for the right duration, then turns them off and leaves a short gap before the next signal.

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// ATtiny85 SOS Beacon
// Press the button → LED flashes + buzzer beeps in SOS pattern

const int BTN_PIN  = 2; // PB2, Physical Pin 7
const int LED_PIN  = 0; // PB0, Physical Pin 5
const int BUZZ_PIN = 1; // PB1, Physical Pin 6

const int DOT = 150; // one dot = 150 ms

void beep(int duration) {
  digitalWrite(LED_PIN, HIGH);
  tone(BUZZ_PIN, 1000);
  delay(duration);
  digitalWrite(LED_PIN, LOW);
  noTone(BUZZ_PIN);
  delay(DOT); // short gap between signals
}

void setup() {
  pinMode(BTN_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZ_PIN, OUTPUT);
}

void loop() {
  if (digitalRead(BTN_PIN) == LOW) {
    // S: three short dots
    for (int i = 0; i < 3; i++) beep(DOT);
    delay(DOT * 2); // letter gap

    // O: three long dashes
    for (int i = 0; i < 3; i++) beep(DOT * 3);
    delay(DOT * 2); // letter gap

    // S: three short dots
    for (int i = 0; i < 3; i++) beep(DOT);
    delay(DOT * 6); // word gap before repeating
  }
}

How the Code Works, Part by Part

The sketch has four sections: the pin declarations, the beep() helper, the setup() function, and the loop(). Each one has a specific job, and together they produce a complete, repeating SOS signal every time the button is held down.

▸ Pin Declarations and the DOT Constant

The three const int lines name the pins so the rest of the code reads like plain English instead of raw numbers. The DOT constant is the heart of the timing system — change it to 100 and the beacon speeds up, change it to 200 and it slows down. Every duration in the program is derived from this one value.

C++ Source
1
2
3
4
5
const int BTN_PIN  = 2; // PB2, Physical Pin 7
const int LED_PIN  = 0; // PB0, Physical Pin 5
const int BUZZ_PIN = 1; // PB1, Physical Pin 6

const int DOT = 150; // one dot = 150 ms

Using named constants also makes it easy to change pins if you wire the project differently. Instead of hunting through the whole sketch for the number 0, you just update LED_PIN in one place.

▸ The beep() Helper Function

The beep(duration) function is called with either DOT (150 ms) for a short signal or DOT * 3 (450 ms) for a long signal. It turns the LED and buzzer on at exactly the same moment, waits, then turns them both off and leaves a 1T gap of silence before the next signal can begin. That final delay(DOT) is the intra-signal gap that separates consecutive dots or dashes inside one letter.

C++ Source
1
2
3
4
5
6
7
8
void beep(int duration) {
  digitalWrite(LED_PIN, HIGH);
  tone(BUZZ_PIN, 1000);
  delay(duration);
  digitalWrite(LED_PIN, LOW);
  noTone(BUZZ_PIN);
  delay(DOT); // short gap between signals
}

Packaging the signal logic in its own function keeps the main loop() clean and readable. If you ever want to change the buzzer frequency, you only need to update the number inside tone() once, right here.

▸ Sending the SOS Pattern

The loop() polls the button every cycle. If the button is pressed (LOW), the program runs through three groups of calls: S is three short beep(DOT) calls, O is three long beep(DOT * 3) calls, and S again is three short calls. Between each letter, a delay(DOT * 2) adds the extra silence needed to reach a full 3T gap (the beep() function already adds 1T at the end, so only 2T more is needed). After the final S, a longer delay(DOT * 6) creates the 7T word pause before the pattern can restart.

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void loop() {
  if (digitalRead(BTN_PIN) == LOW) {
    // S: three short dots
    for (int i = 0; i < 3; i++) beep(DOT);
    delay(DOT * 2); // letter gap

    // O: three long dashes
    for (int i = 0; i < 3; i++) beep(DOT * 3);
    delay(DOT * 2); // letter gap

    // S: three short dots
    for (int i = 0; i < 3; i++) beep(DOT);
    delay(DOT * 6); // word gap before repeating
  }
}

Notice that as long as the button is held down, the SOS sequence will play all the way through before the if check runs again. Releasing the button mid-sequence will not cut it short — the current cycle finishes first, then the chip returns to idle waiting on the next pass through loop().

Fixing Common Problems

If the simulation does not behave as expected, here are the most common causes and how to fix them.

What you seeLikely causeWhat to try
Beacon fires on its own without pressing the buttonButton pin is floating or connected wronglyCheck that INPUT_PULLUP is set in setup() and that the button's second terminal goes to GND
LED flashes but buzzer stays silentBuzzer wired to the wrong pinConfirm the buzzer positive lead is on PB1 (physical pin 6), not PB0
Pattern timing feels 8× too slowATtiny85 running at 1 MHz instead of 8 MHzRe-flash the bootloader with 8 MHz Internal Clock selected in board settings
Pressing the button causes multiple rapid triggersButton bounce — the contact rattles for a few millisecondsAdd a small delay(50) after detecting the button press before starting the SOS loop

The most common issue for first-time ATtiny85 users is the clock speed. The chip ships defaulting to 1 MHz, which makes every timing value eight times too slow. If your dots sound like dashes, that is almost certainly the reason.

Try It in the Simulator

Click the Start Simulation (▶) button in the toolbar to bring the circuit to life. Then click the SOS Button on the canvas and watch the LED and buzzer respond together. You will see three short flashes, three long flashes, and three short flashes — the unmistakable SOS rhythm. The simulation pauses after each complete cycle before waiting for the next button press.

Keywords
#ATtiny85 #Pushbutton #LED #Buzzer #Morse Code #SOS #Emergency #AVR #Beginner
Total word count: 1549 words

Project discussion

Questions, feedback, and community insights
No discussions yet. Be the first to start!