Building a low-cost badge that plays the Imperial March
Mar 17, 2025
Have you ever wanted a tiny device that could play chip tunes at the push of a button? I certainly did, which is why I created this simple badge powered by a coin cell battery and an ATtiny13a microcontroller that plays the first 12 measures of the Imperial March.

- Project Goals
- The Challenge
- How It Works
- Challenges Overcome
- Conclusion
- Projects based on this implementation
- Links
Project Goals
I set out with three simple goals:
- Make it loud
- Keep it cheap
- Power it with a coin cell battery
The Challenge
The ATtiny13a has only 1KB of flash program memory — this post is almost 1kb in length. Nevertheless, I was determined to see how much I could fit of the Imperial March on it, along with all the code needed to make it play and handle power management.
How It Works
Sound Generation
Normally, when driving a piezo buzzer, you can connect one side to a microcontroller pin and the other to ground. But the results aren’t particularly loud.
To get more volume, I use a technique where both sides of the piezo are driven by microcontroller pins, creating an alternating current square wave. Since both pins actively participate (instead of one being tied to ground), this effectively doubles the voltage across the piezo.
But I didn’t stop there! To make it even louder, I implemented a simple charge pump voltage doubler circuit. This further doubles the voltage across the piezo (minus the voltage drops across the diodes), resulting in a much louder sound than would be possible with the native voltage of the 3V coin cell.
Since piezo buzzers are primarily voltage rather than current driven, this is an easy way to get a lot more loudness without using much additional power. This is why piezos are used in basic digital watches.

Power Management
Battery life is crucial for a device like this. The ATtiny13a has an ultra-low power sleep mode that draws less than 100nA of current. When the button is pressed, the microcontroller wakes up, plays the tune, and then goes back to sleep. This approach eliminates the need for a physical power switch, though it does not protect against accidental presses.
The firmware also allows a user to cancel the song by pressing the button again while it’s playing.
LED Animation
To add a bit more flair to the badge, I added an LED to the design that animates according to the music. The animation is super simple to conserve code space—it just reflects the duration of the current note in brightness.
The Code
The code is split into a few key components:
- A Python script that converts RTTTL (Ring Tone Text Transfer Language) format to a much more compact binary representation
- The ATtiny13a C++ firmware that handles:
- Sound generation
- LED animation
- Button debouncing
- Power management (sleep mode)
To keep the program size small, I used several techniques:
- Bit manipulation to pack the note data efficiently (12 bits for frequency, 4 bits for duration)
- Carefully written C code with direct port manipulation and without division or even multiplication operators being used.

The Circuit
The circuit uses:
- ATtiny13a microcontroller
- Piezo buzzer
- Two diodes, two capacitors and four transitors for the charge pump
- A tactile button
- An LED for visual feedback
- A coin cell battery
The voltage doubler works by alternately charging the capacitors and then effectively placing them in series with the supply, nearly doubling the voltage swing across the piezo buzzer.
The Microcontroller
The ATtiny13 is a low-spec, low cost microcontroller from the early 2000’s, designed for simplicity and for low-power battery operation.
It only has 1kb of memory for program storage and 64 bytes of RAM, and only a single timer.
Differential charge pump subcircuit
A good way to get started is to drive a piezo off of one microcontroller pin. The result might not be loud enough to compete with any other noise in the room, though.
Remember, a piezo buzzer is like a capacitor: current doesn’t really flow through it, what happens is the piezo disc physically deforms according to a voltage differential across it. So voltage makes it louder without adding much current.
So the next step is to drive both sides of the piezo using two microcontroller pins working in opposition. Rather than one side of the piezo being high or low and the other being ground, instead we connect a microcontroller pin to each side and alternate both of them high and low in opposition. Using two pins on the same port register we can swap them virtually simultaneously. Now the piezo disc will flex in one direction and then flex in the opposite direction, doubling the effective amplitude.
But how can we make it even louder? Using a really basic charge pump square wave voltage doubler circuit. This further doubles the voltage across the piezo, minus the voltage drop of the diodes. That’s the design used here. You should be able to chain more voltage doublers in series to keep making it louder, if you wanted.
See How a differential charge pump voltage doubler works for more discussion of that circuit.
RTTTL to Compact Format Converter
The Python RTTTL converter (convert.py
) converts music into the C code format. RTTTL is a text-based format originally used for ringtones for Nokia cellphones in the late 1990’s and early 2000’s and there are still plenty of ancient ringtone sharing sites from 20 years ago still up on the internet to find single-voice chiptunes tracks. However, the ascii string representation of the format is far too inefficient for our very limited memory to use directly.
The script:
- Takes a standard RTTTL string as input (like
"starwars:d=4,o=5,b=180:8f,8f,8f,2a#.,2f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8c6,2a#.6,f.6,8d#6,8d6,8d#6,2c6"
) - Parses it using a custom RTTTL parser
- Finds the minimum note duration to use as a base unit
- Transforms frequencies to microsecond periods (better for direct use by the microcontroller)
- Compresses durations to small integers relative to the base duration
- Outputs compact C code with the encoded note data (using struct packing to fit within our memory constraints)
- Generates a macro to convert the compressed duration values back to milliseconds

Each note is encoded with just 16 bits - 12 for frequency and 4 for duration.
typedef struct Note {
const uint16_t freq: 12;
const uint8_t dur: 4;
} Note;
What makes this compression technique possible is how it leverages the inherent properties of Western music:
-
Discrete pitches: Western music uses a limited set of standardized pitches (12 semitones per octave). This discrete nature means we can represent notes as simple numeric values rather than continuous frequencies.
-
Mathematical relationships: The equal temperament system creates predictable mathematical relationships between notes. Each semitone represents a frequency ratio of approximately 1.059 (the 12th root of 2), making the entire system easy to compress algorithmically.
-
Rhythmic proportionality: Western music notation uses a proportional system where note durations relate to each other as simple fractions of powers of two (1/2, 1/4, 1/8, etc.). This is exactly what the code leverages when it finds the smallest common divisor for durations.
-
Limited performance parameters: Traditional notation primarily encodes just pitch and duration (with some articulation).
The script handles this as follows:
-
Frequency conversion: Instead of storing actual frequencies in Hertz, the script converts them to period values (microseconds per cycle) using the formula
1000000/frequency
. This is directly usable by microcontrollers for generating tones through timer control. -
Duration normalization: The script finds the smallest note duration in the piece and then searches for a common divisor that allows all durations to be represented as small integers. Western music’s proportional duration system (notes related as simple fractions like 1/2, 1/4, 1/8) makes this especially effective.
-
Efficient reconstruction: The
generate_mult()
function in the converter creates a macro that efficiently reconstructs actual duration values using bitshifts and additions rather than multiplication, which is faster on small microcontrollers:
#define APPLY_DURATION(x) (x << 8) + (x << 6) + (x << 4) + (x << 1)
The ATtiny13 definitely does not have a hardware multiplier!
This approach allows us to represent 30 seconds of music in just a few hundred bytes instead of several kilobytes, which is crucial given our 1KB flash limitation. The discrete pitch system of Western music (12 semitones per octave with predictable mathematical relationships) and its proportional rhythm system are perfectly suited for this kind of algorithmic compression.
LED Animation
To add a bit more fun to the badge, I included an LED that animates according to the music. Given the extreme code size constraints, I needed an animation approach that would consume minimal program space while still looking interesting.
The animation works by modulating the LED brightness in sync with each note. Looking at the code:
// give the led something to do
PORTB |= _BV(LED_PIN);
uint16_t subduty = min(duration, duty);
delayMicroseconds(subduty);
PORTB &= ~_BV(LED_PIN);
delayMicroseconds(duty - subduty);
This implementation is highly efficient - the LED brightness is controlled by the same timing mechanism that generates the tones, so we get the visual effect almost for free in terms of code space. The LED turns on for a portion of each cycle proportional to the note’s duration.
The LED will be brighter for higher notes and dimmer for lower notes, and off for pauses - all without requiring any additional timers or complex logic. I found that forcing a brief off period between consecutive notes added to the visual effect.
Power Management
The microcontroller spends most of its time in deep sleep mode, drawing only nanowatts of power. A single button press wakes it up, plays the tune, and then it goes back to sleep. This means there’s no need for a power switch – the battery should last for thousands of plays.
If you want to stop the tune midway, just press the button again, and it will immediately go back to sleep mode.
Challenges Overcome
Getting everything to fit in 1KB of flash was probably the biggest challenge. I had to optimize the music format and carefully write the code to minimize instruction size. The LED animation was designed to take as little space as possible.
The other major challenge was getting decent volume from a 3V coin cell battery. The charge pump design really helps here, effectively boosting the voltage across the piezo without drawing more current from the battery.
Conclusion
This project proves you can do a lot with very little. Despite severe constraints in terms of power, memory, and cost, it’s possible to create something fun and functional. The badge always gets a smile when I press the button and the Imperial March starts playing from this tiny circuit.
If you’d like to build your own, all the code and circuit diagrams are available in my GitHub repository.