passive buzzer/Playing it/08. Volume is the duty cycle
Playing it · 08 of 10

Volume is the duty cycle

tone() always sends a 50 % square wave, which is the loudest a note can be. To play it quieter, keep the frequency and shorten the HIGH part of each cycle with ledcWrite: 64 of 255 is about 3 dB down, 13 about 16 dB. Past 50 % it gets quieter again, and 255 is silence with the coil held on.

The only volume knob

A buzzer has no volume control. It is pulled in and let go once per cycle, and the frequency is the pitch. What can change is how the cycle is split: how long the pin stays HIGH before it goes LOW again. That split is the duty cycle.

Volume is the duty cycle
ledcWrite(BUZZER_PIN, …)128
Duty
50 %
Note, vs 128
loudest
Coil on
50 % of the time
About 128, half HIGH and half LOW: the loudest the note can be at this pitch. The disc is pulled for half of each cycle and released for the other half.

tone() always splits it in half, 50 %. That is the right choice for loudness: of every way to split a square wave, half and half puts the most into the note itself. Shorten the HIGH part and less of the drive is at the note's frequency, so the note is quieter.

The figure's number is that arithmetic, on the wave the pin sends, not a measurement of the sound. It says 64 of 255 is about 3 dB under the loudest, 26 about 10 dB and 13 about 16 dB. How the buzzer turns that into sound is its own business, so treat the steps as a guide.

Past halfway it goes wrong

The drive is symmetrical: 192 of 255 gives the same note strength as 64. But at 192 the coil is on for three quarters of every cycle instead of a quarter, so it draws more current for the same sound. To turn it down, go below 128, never above.

At 255 there is no LOW part at all. SIGNAL is simply HIGH, the disc is held in, the note is gone, and the coil draws its full current, about 200 mA from 3.3 V by its 16 Ω. The sketch's comment says never, and means it.

ledcAttach and ledcWrite

tone() cannot set a duty, so this sketch talks to the ESP32's PWM hardware directly. ledcAttach(pin, 2700, 8) sets the pin to 2700 Hz with duty in 8 bits, 0 to 255; ledcWrite(pin, duty) sets the split. These are the esp32 core 3.x names. Writing 0 is silence, with the pin LOW.

The code

tk37_volume.ino

ESP32 core 3.x only: ledcAttach sets the pin to 2.7 kHz with 8-bit duty, and ledcWrite sets how much of each cycle is HIGH. The loop steps from 50 % down to silence.

// TK37 Passive Buzzer: volume from the duty cycle, ESP32-S3.
//
// Wiring, TK37 header left to right (parts up, pins down):
//   GND    -> ESP32 GND
//   VCC    -> ESP32 3V3   (5V works too, and is louder)
//   NC     -> nothing
//   SIGNAL -> GPIO4
//
// Arduino IDE: Tools > Board > esp32 > ESP32S3 Dev Module,
// Tools > USB CDC On Boot > Enabled, then Tools > Port.
// esp32 core 3.x (ledcAttach). Serial Monitor at 115200.

const int BUZZER_PIN = 4;
const int HZ = 2700;  // the buzzer's resonance: its loudest pitch

// Duty, of 255. 128 is 50 %, the loudest; 0 is silent. Never
// 255: that is SIGNAL held HIGH, silent, with the coil on.
const int LEVELS[] = {128, 64, 26, 13, 0};

void setup() {
  Serial.begin(115200);
  ledcAttach(BUZZER_PIN, HZ, 8);  // 8 bits: duty 0 to 255
}

void loop() {
  for (int duty : LEVELS) {
    Serial.printf("duty %d of 255\n", duty);
    ledcWrite(BUZZER_PIN, duty);
    delay(700);
    ledcWrite(BUZZER_PIN, 0);     // 0: LOW, quiet, coil off
    delay(300);
  }
  delay(1500);
}

The steps are 128, 64, 26 and 13 of 255: about 0, 3, 10 and 16 dB under the loudest, worked out for the note in the drive, not measured at the buzzer. Never write 255. On an Uno, analogWrite is fixed at 490 Hz on D9, so this sketch is for the ESP32 family.

When it does not work

Turning the duty up to 255 made it silent.

255 is not the loudest setting. It is SIGNAL HIGH all the time, which holds the disc in and makes no sound, while the coil draws about 200 mA. The loudest is 128, half and half. Stay at or below it.

ledcAttach is not declared.

The sketch is written for the esp32 core 3.x, where ledcAttach(pin, frequency, bits) replaced the 2.x pair ledcSetup and ledcAttachPin. Update the esp32 boards package in Boards Manager, or rewrite those two lines for 2.x.

The quiet steps sound the same as the loud one.

Ears judge loudness roughly by ratio, so 3 dB down is only just noticeable, and very short pulses also change the sound's colour. Compare 128 with 13 rather than with 64. If even that sounds equal, check that nothing else calls tone() on the same pin.

Can analogWrite do this on an Uno?

Not usefully. analogWrite on an Uno runs at a fixed 490 Hz on D9, a low, quiet pitch on this buzzer, and tone() on an Uno is always 50 %. For volume on an Uno, move the pitch away from 2.7 kHz instead.

Where this goes next

The timer on an Uno, the channel on an ESP32, and what a second call does.

What tone() borrows

Edit this page — content/books/passive-buzzer/volume-is-the-duty-cycle.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Passive Buzzer

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.

Browse Modules and blocks on the forum