Skip to content

4. Controlling a Buzzer with PWM

This guide explains how to control an electronic buzzer using Pulse Width Modulation (PWM) on the Raspberry Pi. We'll use a C++ program that interfaces with the Linux PWM subsystem to generate different musical tones.

Required Hardware

  • Raspberry Pi (any model with hardware PWM support)
  • Electronic buzzer (piezo buzzer)
  • Jumper wires
  • Breadboard (optional, but recommended)

Hardware Setup

This guide uses GPIO 12 (PWM Channel 0) for the buzzer connection. Connect the components as follows:

  1. Connect the positive terminal of the buzzer to GPIO 12
  2. Connect the negative terminal of the buzzer to GND (ground)
graph LR
    subgraph RPi["Raspberry Pi"]
        GPIO12["GPIO 12 (PWM)"]
        GND["GND"]
    end

    subgraph Buzzer["Buzzer"]
        Pos["+"]
        Neg["-"]
    end

    GPIO12 --- R["Resistor (Optional)"]
    R --- Pos
    Neg --- GND

    style Buzzer fill:#ffd93d

What is PWM?

Pulse Width Modulation (PWM) is a technique that generates analog signals using digital means. It works by rapidly switching a digital signal between on and off states. The ratio of "on" time to the total time period is called the duty cycle.

For buzzers, changing the PWM frequency produces different tones, allowing us to create musical notes.

Code Explanation

This program uses the Linux sysfs interface to control hardware PWM on the Raspberry Pi to play a C major scale:

#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <vector>

/**
 * Simple electronic buzzer control program
 *
 * Uses Raspberry Pi hardware PWM to make sounds with a buzzer.
 * Connection: Connect the buzzer to GPIO 12 (PWM channel 0) and GND.
 */
int main()
{
    // PWM pin configuration
    constexpr int kPwmPin = 12;    // GPIO 12 (PWM channel 0)
    constexpr int kPwmChannel = 0; // PWM channel

    // PWM path definitions
    const std::string pwm_chip_path = "/sys/class/pwm/pwmchip0";
    const std::string pwm_path =
        pwm_chip_path + "/pwm" + std::to_string(kPwmChannel);

    // Musical note frequencies (Hz) with international note names
    const std::vector<std::pair<int, std::string>> notes = {
        {262, "C4 (Middle C)"},
        {294, "D4"},
        {330, "E4"},
        {349, "F4"},
        {392, "G4"},
        {440, "A4"},
        {494, "B4"},
        {523, "C5"}};

    std::cout << "Raspberry Pi Buzzer PWM Test" << std::endl;
    std::cout << "Connect buzzer to GPIO " << kPwmPin << std::endl;

    try
    {
        // 1. Export PWM channel
        std::cout << "Initializing PWM channel..." << std::endl;

        // Check if already exported
        std::ifstream check_file(pwm_path + "/period");
        if (!check_file.good())
        {
            // Export the PWM channel
            std::ofstream export_file(pwm_chip_path + "/export");
            if (!export_file)
            {
                std::cerr << "Failed to export PWM" << std::endl;
                std::cerr << "Please run with administrator privileges (sudo)"
                          << std::endl;
                return 1;
            }
            export_file << kPwmChannel;
            export_file.close();

            // Wait for initialization
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        std::cout << "PWM channel initialization complete" << std::endl;

        // 2. Play the musical scale
        std::cout << "Playing C major scale..." << std::endl;

        for (const auto& [frequency, name] : notes)
        {
            // Display note name and frequency
            std::cout << "♪ " << name << " (" << frequency << "Hz)"
                      << std::endl;

            // Set period (in nanoseconds)
            uint32_t period_ns =
                static_cast<uint32_t>(1'000'000'000 / frequency);
            std::ofstream period_file(pwm_path + "/period");
            if (!period_file)
            {
                std::cerr << "Failed to set period" << std::endl;
                break;
            }
            period_file << period_ns;
            period_file.close();

            // Set duty cycle to 50%
            std::ofstream duty_file(pwm_path + "/duty_cycle");
            if (!duty_file)
            {
                std::cerr << "Failed to set duty cycle" << std::endl;
                break;
            }
            duty_file << period_ns / 2; // 50% duty cycle
            duty_file.close();

            // Enable PWM
            std::ofstream enable_file(pwm_path + "/enable");
            if (!enable_file)
            {
                std::cerr << "Failed to enable PWM" << std::endl;
                break;
            }
            enable_file << "1";
            enable_file.close();

            // Play sound for 500 milliseconds
            std::this_thread::sleep_for(std::chrono::milliseconds(500));

            // Disable PWM (stop sound)
            enable_file.open(pwm_path + "/enable");
            if (enable_file)
            {
                enable_file << "0";
                enable_file.close();
            }

            // Short pause between notes
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        std::cout << "Performance complete!" << std::endl;
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error occurred: " << e.what() << std::endl;
        return 1;
    }

    std::cout << "Program finished" << std::endl;
    return 0;
}

Key Program Elements

  1. PWM Configuration:

    • The program uses GPIO 12, which is connected to hardware PWM channel 0
    • PWM is accessed through the Linux sysfs interface at /sys/class/pwm/pwmchip0
  2. Musical Notes:

    • The program defines frequencies for a C major scale
    • Each note is represented by its frequency in Hertz (Hz)
  3. PWM Control Process:

    • Export the PWM channel to make it available for use
    • Set the period based on the desired frequency
    • Set the duty cycle to 50% (equal time on and off)
    • Enable the PWM to start producing sound
    • Disable the PWM to stop the sound
  4. Mathematical Relationship:

    • Period (in seconds) = 1 / Frequency
    • Period (in nanoseconds) = 1,000,000,000 / Frequency
    • 50% duty cycle = period / 2

Compiling and Running

  1. Save the code to a file, e.g., pwm_buzzer.cpp

  2. Compile the program:

    g++ -o pwm_buzzer pwm_buzzer.cpp -std=c++11 -pthread
    
  3. Run the program with root privileges (needed for PWM access):

    sudo ./pwm_buzzer
    
  4. The program will play the C major scale (C4, D4, E4, F4, G4, A4, B4, C5).

How It Works in Detail

  1. PWM Export: Before using a PWM channel, it must be "exported" to user space via the sysfs interface.

  2. Setting Period: The period determines the frequency of the sound:

    • Frequency = 1 / Period
    • For example, for Middle C (262 Hz): Period ≈ 3.8 milliseconds or 3,800,000 nanoseconds
  3. Setting Duty Cycle: The duty cycle affects the strength of the sound. A 50% duty cycle provides maximum acoustic output for many piezo buzzers.

  4. PWM Enable/Disable: Writing "1" to the enable file starts the PWM output, while writing "0" stops it.

  5. Timing Control: The program uses std::this_thread::sleep_for() to control the duration of each note.

Advanced Considerations

  1. Real-time Performance:

    • This program uses file I/O for PWM control, which may not be suitable for timing-critical applications
    • For better real-time performance, consider using direct register access or specialized libraries
  2. Melody Creation:

    • You can extend this program to play full melodies by defining sequences of notes with different durations
  3. Volume Control:

    • Changing the duty cycle can affect perceived volume, though the effect is limited with simple buzzers
    • For better volume control, consider using a transistor amplifier circuit
  4. Multiple PWM Channels:

    • Raspberry Pi typically has 2 hardware PWM channels
    • More channels can be simulated using GPIO and software PWM

Troubleshooting

  1. No Sound:

    • Verify buzzer polarity (some buzzers are polarized)
    • Ensure GPIO 12 is properly connected
    • Check if your Raspberry Pi model supports hardware PWM on the used pin
  2. Permission Issues:

    • The "Failed to export PWM" error usually means the program needs administrator privileges
    • Always run with sudo
  3. PWM Already in Use:

    • If another process is using the PWM channel, you might get errors
    • The program checks for this and tries to use the channel if already exported
  4. Configuration Issues:

    • Some Raspberry Pi configurations may require enabling PWM in config.txt
    • Add dtoverlay=pwm to /boot/config.txt if necessary

Taking It Further

You can expand this program to:

  1. Play well-known melodies by defining sequences of notes and durations
  2. Create an interactive piano by mapping keyboard keys to frequencies
  3. Implement MIDI playback for more complex musical applications
  4. Control multiple buzzers for richer sound output