Why I Started Using This Tool

My proximity alarm could blink and print, but an alarm that can't make noise isn't much of an alarm. The plan was a small KSSG1203 buzzer that beeps louder and faster as something gets closer.

My first instinct was to wire the buzzer straight to a GPIO pin and toggle it. Two wires, done, right? Then I did the math. The buzzer is a little magnetic coil with a resistance of only a few tens of ohms, so on 3.3 V it wants something like 80 mA. A Pi GPIO pin should give about 8 mA by default, 16 mA at most. Wiring it directly would have overloaded the pin, and the coil's switch-off spike would have landed right on the Pi.

A GPIO pin is a signal, not a power supply. So the answer is the oldest trick in the hobby: let the pin control a ten-cent transistor, the 2N3904, and let the transistor carry the current. The pin turns a small tap, and the transistor opens the big valve. No C code today: just the wiring, the math behind one resistor, and testing it from the console.

Missed the hardware alive task? Start with Part 7: Blinking the Raspberry Pi's ACT LED From My Own C Thread (video).

Here's the video walkthrough of this post, if you'd rather watch it step by step:

Watch on YouTube

What It Does

The circuit is a low-side switch with three parts:

  • Buzzer from 3.3 V (pin 1) to the transistor's collector.
  • 2N3904 emitter to GND (pin 6).
  • GPIO18 (pin 12) through a 1 kΩ resistor to the base.

When GPIO18 goes high, about (3.3 − 0.7) / 1000 = 2.6 mA flows into the base. Even with the datasheet's worst-case gain of 30, that lets the transistor carry ~78 mA, so it switches fully on and the buzzer gets the full 3.3 V. The pin itself only ever supplies those 2.6 mA.

The other thing I learned: the KSSG1203 is a passive buzzer. On steady DC it just clicks. It needs a square wave, which turns out to be a gift: frequency sets the pitch, duty cycle sets the loudness, and how often I beep sets the urgency. That's why it's on GPIO18, one of the Pi's hardware PWM pins.

Before writing any code I tested it all over SSH with pinctrl: a click, a rough shell buzz, and a multimeter showing ~0.7 V on the base and ~0.2 V on the collector when on.

Step by Step

1. Recap: which pin?

Back in Part 3 we made a pin table for the whole project. The buzzer row was:

FunctionBCM GPIOPhysical pinNotes
Buzzer (through the 2N3904 transistor)GPIO1812Can do hardware PWM, handy for volume and tone
3.3 V power—1, 17Powers the buzzer
Ground—6, 9, 14, 20, …Any GND pin works

Remember: software talks in BCM numbers (GPIO18), the header uses physical numbers (pin 12). And the Pi's GPIO is 3.3 V only.

Why GPIO18? Because it's one of the few pins with hardware PWM: the chip itself can generate a square wave on it, very precisely, without our program toggling it. That will matter a lot in the code part.

2. Why not connect the buzzer straight to the pin?

The KSSG1203 is a magnetic buzzer. Inside is a tiny coil of wire and a thin metal disc. Current through the coil makes a magnet that pulls the disc; no current lets it spring back. Do that a few thousand times per second, and the disc vibrates: that's the sound.

A coil like that has a resistance of only a few tens of ohms. Check the datasheet for your exact part, but say it's around 40 Ω. Ohm's law: 3.3 V / 40 Ω ≈ 80 mA. Now compare with what a Pi GPIO pin can safely give:

Current
What the buzzer coil can drawtens of mA, maybe ~80 mA
What one GPIO pin should supplyabout 16 mA maximum (default drive is 8 mA)

The pin would be pushed far past its limit. At best the buzzer is weak and the pin voltage sags. At worst you slowly damage the GPIO, or the whole chip. There's a second problem too: a coil is inductive. When you switch it off, it "kicks back" with a voltage spike, and you don't want that spike landing straight on your Pi's pin.

So the rule is: a GPIO pin drives signals, not loads. If something needs more than a few milliamps, put a switch between it and the pin.

3. Passive vs active buzzer (this one matters!)

There are two kinds of buzzer, and they look almost the same:

Active buzzerPassive buzzer (ours)
InsideCoil + its own little oscillator circuitJust the coil and disc
On a steady DC voltageBeeps on its ownGives one click, then silence
UndersideUsually sealed in blackOften shows a green PCB
You controlOn / off onlyPitch, loudness and rhythm

Our KSSG1203 is passive: it's really a tiny speaker. That's more work, because we must make the square wave, but it gives us exactly the control the alarm needs:

  • Frequency of the square wave = pitch. Every transducer has a resonant frequency where it's loudest, usually a few kHz for this size. Your datasheet tells you the exact value.
  • Duty cycle (how much of each wave is "on") = loudness. A 50 % square wave is the loudest; a thin 5 % pulse is much quieter.
  • Beep period (how often we beep) = urgency. Slow beep-beep when far, fast when close.

Those last two are already waiting in our shared frame from Part 6:

    UINT8   buzzerVolume;                   /* 0..100 %                   */
    UINT16  buzzerPeriodMs;                 /* beep period, 0 = silent    */

Today we build the hardware that will make those numbers audible.

Quick test to tell them apart: touch the buzzer to a 3 V coin cell for a moment. A continuous beep means active. A single click means passive.

4. Meet the 2N3904

The 2N3904 is an NPN bipolar transistor, one of the most common parts in electronics. It has three legs:

LegNameJob
BBaseThe "tap handle": a small current goes in here
CCollectorThe big current comes in here...
EEmitter...and leaves here, to ground

The idea: a small current from base to emitter allows a much bigger current from collector to emitter. The ratio is called the current gain, or hFE (also written β). For the 2N3904 it's typically around 100, and the datasheet guarantees at least 30 even at 50 mA.

We use it as a switch, not an amplifier:

  • GPIO18 low (0 V) → no base current → transistor off → no buzzer current.
  • GPIO18 high (3.3 V) → base current flows → transistor fully on → buzzer current flows to ground.

When a transistor is turned on so hard that it can't let through any more current, we say it's saturated. That's what we want: in saturation the transistor is a near-perfect closed switch, with only about 0.1–0.3 V across it, so it stays cool.

Pinout: hold the 2N3904 with the flat face toward you and the legs pointing down. From left to right: E, B, C. Double-check with your datasheet, because some look-alike transistors (and some other parts in the same TO-92 package) use a different order. Getting this wrong is the number one mistake with this circuit.

5. The circuit

This is called a low-side switch: the buzzer sits between the supply and the transistor, and the transistor connects the "low side" of the buzzer to ground.

   Pi pin 1 (3.3 V) ──────────┐
                              │
                          ┌───┴───┐
                          │   +   │
                          │BUZZER │  KSSG1203 (passive)
                          │   -   │
                          └───┬───┘
                              │
                              │ C (collector)
                 1 kΩ       │/
 Pi pin 12 (GPIO18) ──/\/\/──┤   2N3904 (NPN)
                     R1   B  │\
                              │ E (emitter)
                              │
   Pi pin 6 (GND) ────────────┘

Follow the current:

  1. The control path: GPIO18 → 1 kΩ resistor → base → emitter → GND. Small current, a few mA.
  2. The power path: 3.3 V → buzzer + → buzzer − → collector → emitter → GND. The bigger current, and it never goes through the GPIO pin.

Both paths share the same ground. Everything in this project connects to the Pi's GND; that's how the Pi and the transistor agree on what "0 V" means.

6. The math: why 1 kΩ?

The base-emitter junction of a silicon transistor behaves like a diode: once it's on, it holds about 0.7 V. So with the pin at 3.3 V, the resistor sees the rest:

Ib = (Vpin − Vbe) / R1
   = (3.3 V − 0.7 V) / 1000 Ω
   = 2.6 mA

Two checks:

  1. Is it safe for the pin? 2.6 mA is well below the 8 mA default drive. ✔
  2. Is it enough to saturate the transistor? The transistor can let through roughly Ib × hFE. Worst case: 2.6 mA × 30 = 78 mA. Typical: 2.6 mA × 100 = 260 mA. Our buzzer needs a few tens of mA, so the transistor is driven hard on. ✔

And why not some other value?

Base resistorBase currentResult
None (0 Ω)Limited only by the pin, way too muchOverloads the GPIO pin. Never do this.
100 Ω~26 mAOver the pin's limit
1 kΩ~2.6 mAPin safe, transistor fully on
100 kΩ~0.026 mATransistor only half on: weak sound, transistor gets warm

1 kΩ sits right in the sweet spot. Values from about 470 Ω to 2.2 kΩ all work fine.

7. Two honest notes about our choices

Why 3.3 V and not 5 V? The Pi also has 5 V pins, and a buzzer on 5 V is louder. I chose 3.3 V because the KSSG1203 is a low-voltage part (check its rated voltage on your datasheet), it's plenty loud on a desk for a demo, and it keeps everything in the project at one voltage. If you ever want more volume, the circuit is the same; you'd just move the buzzer's + wire to pin 2 (5 V), and add the diode below.

What about the flyback diode? Remember the coil "kicks back" when switched off? In bigger circuits (relays, motors, 5 V and up) you always put a diode across the coil, called a flyback diode, to soak up that spike. For a tiny transducer on 3.3 V, the spike is small, and the 2N3904 is rated for 40 V between collector and emitter, so I'm leaving it out to keep the build simple. The best part: the transistor sits between the coil and the Pi, so the spike never reaches the GPIO pin anyway.

If you want the insurance (and if you ever move to 5 V, you should), it's one cheap part: a 1N4148 across the buzzer, striped end to +3.3 V, other end to the collector. It does nothing in normal operation and only conducts when the coil kicks back.

Silent at boot for free: what does GPIO18 do before our program runs? On the Pi 3, GPIO18 starts as an input with its internal pull-down switched on. That pulls the base towards 0 V, so the transistor stays off and the buzzer is silent during boot. Some people add a 10 kΩ resistor from base to GND to make that guaranteed in hardware; for this project the Pi's own pull-down does the job.

8. Build it on the breadboard

PartQtyNotes
KSSG1203 passive buzzer1Note the + marking (often also the longer leg)
2N3904 NPN transistor1Flat face toward you: E, B, C
1 kΩ resistor1Brown, black, red
Jumper wires (female-to-male)3Pi pins 1, 6, 12
Breadboard1

Power the Pi off first. Then:

  1. Push the 2N3904 into the breadboard so each leg is in its own row. Flat face toward you: E on the left, B in the middle, C on the right.
  2. Put the buzzer in so its − leg shares a row with C, and its + leg sits in a free row.
  3. Put the 1 kΩ resistor from the B row to a free row. That free row is your "GPIO18" row.
  4. Jumper: Pi pin 12 (GPIO18) → the resistor's free row.
  5. Jumper: Pi pin 1 (3.3 V) → the buzzer's + row.
  6. Jumper: Pi pin 6 (GND) → the E row.

Before powering up, trace it with your finger against the schematic: 3.3 V → buzzer → C. E → GND. GPIO18 → 1 kΩ → B. No wire from 3.3 V or GND directly to pin 12.

9. Test it from the console, before any code

Same philosophy as Part 3: prove the hardware with simple tools, so any bug later is in the code. SSH in and use pinctrl:

ssh au@au.local
pinctrl get 18           # should show an input, pulled down (ip pd | lo)

Test 1: a click. Turn the pin on, then off again quickly:

pinctrl set 18 op dh     # output, drive high: transistor on
pinctrl set 18 dl        # drive low: transistor off

You hear a click on each command, not a beep. That's perfect: it proves the wiring works and proves the buzzer is passive. A steady DC level just pulls the disc in and holds it there.

Don't leave the pin high for long. With DC on it, the coil carries its full current continuously and warms up, without making any sound. Click, then low again.

Test 2: a rough buzz. A passive buzzer needs a square wave, so let's make a slow, ugly one from the shell:

while true; do pinctrl set 18 dh; pinctrl set 18 dl; done     # Ctrl+C to stop
pinctrl set 18 dl

You'll hear a rough, low buzz. Each pinctrl call starts a whole new program, so this loop can only toggle the pin a few hundred times a second, far below the buzzer's resonant frequency, and the timing jitters. That's exactly why the code part uses hardware PWM: the chip makes a clean, exact square wave on GPIO18 with no CPU effort at all.

Test 3: the multimeter (optional, but very satisfying). Black probe on GND. Set the pin high with pinctrl set 18 op dh, measure quickly, then set it low again:

Measure (red probe)Pin high (on)Pin low (off)
Pin 12 (GPIO18)~3.3 V~0 V
Base (B)~0.7 V~0 V
Collector (C)~0.1–0.3 V (saturated)~3.3 V

That collector reading is the whole story of this post: when the transistor is on, the collector is pulled almost to ground, and the full 3.3 V sits across the buzzer.

Leave it safe: when you're done, put the pin back the way it started:

pinctrl set 18 ip pd     # input with pull-down: transistor off, silent

10. When things go wrong

ProblemWhat it meansFix
No click, no sound at allWiring break, or E/C swappedRe-check against the schematic; flat face toward you is E-B-C
Only clicks, never beeps on steady highNormal for a passive buzzerIt needs a square wave: Test 2, or PWM in the next part
Transistor gets hotPinout wrong, or base resistor too big (transistor half on)Check E-B-C; use ~1 kΩ
Buzzer sounds even with the pin lowCollector or buzzer − wired straight to GND, bypassing the transistorThe buzzer's − must go only to C
Very quiet3.3 V supply, or a square wave far from resonanceNormal for the shell test; PWM at the right frequency is much louder
Pi reboots when the pin goes highShort from 3.3 V to GNDPower off and trace every wire
pinctrl: Permission deniedYour user can't access the GPIOsudo pinctrl ...

11. What's next

The hardware is ready: a 3.3 V pin with a ten-cent muscle. Next we write the buzzer task in C: a new thread that reads distanceCm from the frame, turns it into buzzerVolume and buzzerPeriodMs, and drives GPIO18's hardware PWM. Far away: nearly silent. Close: loud and urgent.

Key Takeaways

  1. A GPIO pin is a signal, not a power supply. It should give only a few mA (8 mA default, ~16 mA max) at 3.3 V. Anything hungrier, like a buzzer coil, needs a switch in between.
  2. The 2N3904 NPN transistor is that switch. A small base current lets a much bigger collector current flow. Buzzer from 3.3 V to collector, emitter to GND: a low-side switch. Flat face toward you: E, B, C.
  3. 1 kΩ on the base: (3.3 − 0.7) / 1000 = 2.6 mA. Safe for the pin, and enough to saturate the transistor for tens of mA. Never connect the base straight to the pin.
  4. Our KSSG1203 is passive: on DC it only clicks. It needs a square wave: frequency = pitch, duty cycle = loudness, beep period = urgency. That's why it sits on GPIO18, a hardware PWM pin.
  5. Test with pinctrl before writing code: click on/off, a rough shell buzz, and a multimeter showing the collector drop to ~0.2 V. Finish with pinctrl set 18 ip pd.

Final Verdict

If you're new to electronics, this is the first circuit to learn. One transistor and one resistor, under 20 cents, and suddenly a 3.3 V pin can switch buzzers, relays, LED strips and small motors. Learn the math once and you'll use it for years.

Gotchas I hit, so you don't have to:

  • E-B-C, flat face toward you. Swap E and C and it either does nothing or gets warm. Check your datasheet.
  • Never skip the base resistor. Base straight to the pin overloads the GPIO.
  • Common ground. The transistor's emitter must go to the Pi's GND, or nothing works.
  • "It only clicks!" That's not broken. That's a passive buzzer on DC. It needs a square wave.
  • Don't hold the pin high for long while testing. The coil draws its full current and warms up without making a sound.
  • Flyback diode: I skipped it for a tiny transducer on 3.3 V. If you move to 5 V, or switch a relay or motor, add a 1N4148 across the load, stripe to the positive side.

Next up: the C code that turns distance into beeps using hardware PWM.

Related Videos