05 - Passive Buzzer Application
This chapter describes the passive buzzer application example on the Pico-G1 expansion board — buzzer. It demonstrates how to generate a software square wave through the GPIO interface to drive a passive buzzer and produce sound. It is a fundamental example for learning GPIO timing control and audio signal generation, showing how to achieve precise timing control with software delays.
The application source code lives in the SDK directory source/app/05_buzzer/, providing a complete GPIO square-wave generation implementation — an important reference for learning audio control and timer programming.
1 Application Overview
1.1 Features
- GPIO square-wave generation: produces a square-wave signal of precise frequency via software delays
- Passive buzzer driving: demonstrates the difference between passive and active buzzers and how to use them
- Adjustable frequency: supports custom sound frequency (2~4kHz recommended)
- Duration control: supports custom sound duration
- Simple and efficient: minimal code, suitable for embedded learning and quick applications
1.2 Technical Specifications
| Parameter | Value |
|---|---|
Control pin | GPIO5_5 |
Default frequency | 2kHz (2000Hz) |
Default duration | 1 second |
Recommended frequency | 2~4kHz (loudest) |
GPIO interface | Linux GPIO character device v1 ABI |
Implementation | Software square wave (clock_gettime busy-wait) |
CPU usage | 100% of one core while sounding |
1.3 Test Case List
| index | Name | Test command | Expected result (success) | Possible causes of failure |
|---|---|---|---|---|
| 1 | Basic beep | ./buzzer | Buzzer sounds for 1 second (2kHz) then turns off automatically | Wrong GPIO pin configuration, damaged buzzer |
| 2 | Custom frequency | Change the frequency parameter in code | Buzzer sounds at the new frequency | Frequency outside the audible range |
| 3 | Custom duration | Change the duration parameter in code | Buzzer sounds for the new duration | Wrong duration parameter |
| 4 | Multiple beeps | Call the beep function in a loop | Buzzer produces several beeps ("beep beep beep") | Delays too short, causing continuous sound |
1.4 Directory Structure
source/app/05_buzzer/
├── Makefile # Build script
├── main.c # Main program
├── buzzer.c # Buzzer driver implementation
├── buzzer.h # Buzzer driver header
├── gpio_hal.c # GPIO HAL layer implementation
├── gpio_hal.h # GPIO HAL layer header
└── README.md # Documentation2 Hardware Connection
2.1 Pin Definition
| Signal | On-board GPIO | Node | Description |
|---|---|---|---|
| Control pin (CTRL/SIG) | GPIO5_5 | /dev/gpiochip5 line5 | Square-wave drive input |
| VCC | 3.3V or 5V | — | Power supply (per the buzzer module rating) |
| GND | GND | — | Ground |
2.2 Hardware Circuit
Standard passive buzzer wiring:
Pico-G1 Passive Buzzer Module
┌───────────┐ ┌──────────────┐
│ │ │ │
│ GPIO5_5 ──┼─────────┤ SIG/CTRL │
│ │ │ │
│ 3.3V ──┼─────────┤ VCC │
│ │ │ │
│ GND ──┼─────────┤ GND │
│ │ │ │
└───────────┘ └──────────────┘Choosing the supply voltage
- 3.3V supply: safe, low power, suitable for most applications
- 5V supply: louder, higher power; confirm the module supports it
- Prefer 3.3V first; try 5V only if the volume is insufficient
2.3 Passive vs. Active Buzzer
Passive buzzer (this module):
- Has no internal oscillator and must be driven with a square wave
- Applied DC only makes it click once; it will not sound continuously
- Frequency is adjustable; pitch follows the frequency
- Requires a continuous square-wave input to sound continuously
Active buzzer:
- Has an internal oscillator; sounds as soon as DC is applied
- Fixed frequency; pitch is not adjustable
- Simple to control: high = sound, low = stop
- The square wave from this module can also drive an active buzzer (via the average voltage of the square wave)
How to tell the buzzer types apart
- Check the label: active buzzers are usually marked "有源" or "Active", passive ones "无源" or "Passive"
- Check the package: active buzzers are usually slightly taller (internal oscillator circuit), passive ones thinner
- Test: apply DC — continuous sound means active; a single click means passive
2.4 Pin Multiplexing
Physical address configuration of the GPIO5_5 pin:
- Register address:
iocfg_reg60 @ 0x100C0040 - Default function: func0 = GPIO
- Alternate function: func1 = PWM0 (hardware PWM)
- Current configuration:
0x00001000(input enable | GPIO function)
GPIO5_5 is already in its GPIO function by default, so the IOCFG register does not need modification. To use hardware PWM, configure it as func1.
3 Build and Deployment
3.1 Prerequisites
Before building this application, make sure the following preparations are complete:
- SDK environment ready: set up the cross-compilation toolchain and SDK by following Development Environment Setup
- Hardware connected: the buzzer is correctly wired to GPIO5_5, VCC, and GND
3.2 Building the Application
# Set the toolchain path
export PATH=$PATH:<SDK>/tools/linux/toolchains/arm-gcc12.2.0-linux-uclibceabi/bin
# Enter the example directory
cd <SDK>/source/app/05_buzzer
# Build
make
# Clean
make cleanAfter a successful build, the executable buzzer is generated in the current directory.
3.3 Deploying to the Board
# Transfer to the development board with SCP
scp buzzer root@<board-IP>:/usr/bin/
# Or download via TFTP
tftp -g -r buzzer <board-IP>3.4 Running the Application
# Add execute permission
chmod +x /usr/bin/buzzer
# Run the buzzer example
/usr/bin/buzzerOnce started, the buzzer sounds for 1 second (2kHz square wave), turns off automatically, and the program exits.
3.5 Expected Output
Console output
/mnt # ./buzzer
[buzzer] pad 复用:GPIO5_5 -> func0(GPIO)
[buzzer] pad 0x100C0040 -> 0x00001000
[buzzer] GPIO5_5 就绪。
[buzzer] 响 1s(2kHz 方波)...
[buzzer] 已关闭,退出。Actual behavior
The buzzer produces a single continuous tone ("beeep—") that stops after 1 second.
Fixed and variable parts
- Fixed part: the sound duration and frequency (matches the fixed code)
- Variable part: none (this program's output is entirely fixed)
4 Internal Execution Logic
4.1 Application Architecture
This application uses a layered design consisting of a hardware abstraction layer, a driver layer, and an application layer:
// Application layer (main.c)
int main(int argc, char *argv[])
{
// 1. Initialize the buzzer
buzzer_init();
// 2. Beep for 1 second (2kHz)
buzzer_beep(1000, 2000);
// 3. Turn the buzzer off
buzzer_off();
buzzer_deinit();
return 0;
}4.2 Buzzer Driver Implementation
The buzzer driver implements software square-wave generation:
// Buzzer initialization
int buzzer_init(void)
{
// Configure GPIO5_5 as output
if (gpio_set_direction(BUZZER_GPIO_CHIP, BUZZER_GPIO_LINE, GPIO_OUTPUT) < 0) {
return -1;
}
// Initial state is low
gpio_set_value(BUZZER_GPIO_CHIP, BUZZER_GPIO_LINE, 0);
return 0;
}
// Beep function
void buzzer_beep(int duration_ms, int frequency_hz)
{
if (frequency_hz <= 0 || duration_ms <= 0)
return;
// Compute the half-period duration (microseconds)
int half_period_us = 1000000 / (2 * frequency_hz);
int cycles = (duration_ms * 1000) / (2 * half_period_us);
struct timespec start, current;
clock_gettime(CLOCK_MONOTONIC, &start);
int state = 0;
for (int i = 0; i < cycles; i++) {
// Toggle the GPIO state
state = !state;
gpio_set_value(BUZZER_GPIO_CHIP, BUZZER_GPIO_LINE, state);
// Delay precisely for half a period
clock_gettime(CLOCK_MONOTONIC, ¤t);
long elapsed_us = (current.tv_sec - start.tv_sec) * 1000000 +
(current.tv_nsec - start.tv_nsec) / 1000;
long target_us = (2 * i + 1) * half_period_us;
long delay_us = target_us - elapsed_us;
if (delay_us > 0) {
usleep(delay_us);
}
}
// Return to low
gpio_set_value(BUZZER_GPIO_CHIP, BUZZER_GPIO_LINE, 0);
}
// Turn the buzzer off
void buzzer_off(void)
{
gpio_set_value(BUZZER_GPIO_CHIP, BUZZER_GPIO_LINE, 0);
}
// Buzzer cleanup
void buzzer_deinit(void)
{
buzzer_off();
}4.3 Software Square-Wave Principle
The software square wave is realized by toggling the GPIO at timed intervals:
Key points:
- Frequency control: different frequencies are achieved by controlling the half-period duration
- Duty cycle: 50% (high and low times are equal)
- Precision guarantee:
clock_gettime()provides accurate timestamps
4.4 Precise Delay Implementation
Precise delays using clock_gettime() and usleep():
// Read the monotonic clock (unaffected by system time adjustments)
clock_gettime(CLOCK_MONOTONIC, ¤t);
// Compute the elapsed time (microseconds)
long elapsed_us = (current.tv_sec - start.tv_sec) * 1000000 +
(current.tv_nsec - start.tv_nsec) / 1000;
// Compute the target time
long target_us = (2 * i + 1) * half_period_us;
// Compute the remaining delay
long delay_us = target_us - elapsed_us;
// Perform the delay
if (delay_us > 0) {
usleep(delay_us);
}Why use the monotonic clock
CLOCK_MONOTONIC: unaffected by system time adjustments, keeping timing accurateCLOCK_REALTIME: affected by system time adjustments, not suitable for timing applications
5 Key Programming Points
5.1 GPIO Output Operations
Control flow:
- Configure the GPIO direction as output
- Set the initial value low
- Toggle the GPIO state periodically
- Return it low when finished
Notes:
- Delay precisely after every toggle
- Use
clock_gettime()to guarantee time precision - Avoid overly long delays that would distort the frequency
5.2 Frequency and Pitch
Different frequencies correspond to different musical pitches:
| Frequency | Pitch | Note name | Description |
|---|---|---|---|
| 261Hz | Do (C4) | Middle C | Reference tone, low |
| 294Hz | Re (D4) | — | — |
| 330Hz | Mi (E4) | — | — |
| 349Hz | Fa (F4) | — | — |
| 392Hz | Sol (G4) | — | — |
| 440Hz | La (A4) | Concert A | Tuning reference |
| 494Hz | Si (B4) | — | — |
| 523Hz | Do (C5) | High C | One octave higher |
| 1000~2000Hz | — | — | Common buzzer range |
| 2000~4000Hz | — | — | Loudest range |
Recommended frequencies
- 2000Hz: moderate loudness, sensitive to the human ear
- 2700Hz: louder, a common alarm frequency
- 4000Hz: very loud, but possibly harsh
5.3 Duration and Rhythm Control
Combine different durations to create rhythms:
// Short "beep"
buzzer_beep(100, 2700);
usleep(100000); // 100ms interval
// Long "beeeep"
buzzer_beep(500, 2700);
usleep(200000); // 200ms interval
// Double "beep-beep"
buzzer_beep(100, 2700);
usleep(50000); // 50ms interval
buzzer_beep(100, 2700);
usleep(200000);5.4 CPU Usage Issue
Drawbacks of the software square wave:
- Occupies 100% of a single CPU core while sounding
- Cannot run complex multitasking at the same time
- Higher power consumption
Solutions:
- Hardware PWM: use the PWM0 function of GPIO5_5 for near-zero CPU usage
- Multithreading: run the square-wave generation in a separate thread
- Dedicated audio chip: use an external audio driver chip
6 Code Customization
6.1 Changing the Frequency and Duration
Edit the call parameters in main.c:
// buzzer_beep(duration_ms, frequency_hz);
buzzer_beep(1000, 2000); // 1 second, 2kHz
buzzer_beep(500, 2700); // 0.5 second, 2.7kHz
buzzer_beep(100, 4000); // 0.1 second, 4kHz (short chirp)6.2 Multiple-Beep Effects
Call it in a loop inside main():
// "Beep beep beep" effect
for (int i = 0; i < 3; i++) {
buzzer_beep(100, 2700);
usleep(100000); // 100ms interval
}
// "Beep—beep—beep" effect (SOS rhythm)
for (int i = 0; i < 3; i++) {
buzzer_beep(200, 2700);
usleep(100000);
}
usleep(200000);
for (int i = 0; i < 3; i++) {
buzzer_beep(500, 2700);
usleep(200000);
}
usleep(200000);
for (int i = 0; i < 3; i++) {
buzzer_beep(200, 2700);
usleep(100000);
}6.3 Playing a Simple Melody
Implement a simple song player:
// Simple scale
#define NOTE_C4 262
#define NOTE_D4 294
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_G4 392
#define NOTE_A4 440
#define NOTE_B4 494
#define NOTE_C5 523
// Note structure
struct note {
int frequency;
int duration;
};
// "Twinkle Twinkle Little Star" melody
struct note twinkle_star[] = {
{NOTE_C4, 500}, {NOTE_C4, 500}, {NOTE_G4, 500}, {NOTE_G4, 500},
{NOTE_A4, 500}, {NOTE_A4, 500}, {NOTE_G4, 1000},
{NOTE_F4, 500}, {NOTE_F4, 500}, {NOTE_E4, 500}, {NOTE_E4, 500},
{NOTE_D4, 500}, {NOTE_D4, 500}, {NOTE_C4, 1000},
};
void play_melody(struct note *melody, int length)
{
for (int i = 0; i < length; i++) {
buzzer_beep(melody[i].duration, melody[i].frequency);
usleep(melody[i].duration * 1000);
}
}
int main()
{
buzzer_init();
play_melody(twinkle_star, sizeof(twinkle_star)/sizeof(twinkle_star[0]));
buzzer_deinit();
return 0;
}7 Troubleshooting
| Problem | Possible cause | Solution |
|---|---|---|
| Buzzer silent | Wrong buzzer type, wrong wiring, wrong GPIO configuration | Confirm it is a passive buzzer, check wiring, verify GPIO output |
| Only a single click | It is an active buzzer being driven with a square wave | Hold the pin high continuously instead of using a square wave |
| Sound too quiet | Frequency off the resonance point, insufficient supply voltage | Adjust the frequency to 2700~4000Hz, raise the supply voltage |
| Muffled sound | Frequency too low | Raise the frequency above 2000Hz |
| Harsh sound | Frequency too high | Lower the frequency to the 2000~3000Hz range |
| Inaccurate frequency | Imprecise delays | Use hardware PWM or improve the delay algorithm |
8 Advanced Extensions
8.1 Using Hardware PWM
Switch to hardware PWM for near-zero CPU usage:
# List the PWM controllers
ls /sys/class/pwm/
# Export the PWM channel
echo 0 > /sys/class/pwm/pwmchip0/export
# Configure the PWM parameters
echo 2000000 > /sys/class/pwm/pwmchip0/pwm0/period # 2kHz period (500ns)
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle # 50% duty cycle
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable # Enable PWM
# Disable PWM
echo 0 > /sys/class/pwm/pwmchip0/pwm0/enableAdvantages of hardware PWM
- Near-zero CPU usage
- Precise frequency with no jitter
- Other tasks can run at the same time
8.2 Multithreaded Implementation
Run the square-wave generation in a separate thread:
#include <pthread.h>
struct buzzer_params {
int duration_ms;
int frequency_hz;
};
void *buzzer_thread(void *arg)
{
struct buzzer_params *params = (struct buzzer_params *)arg;
buzzer_beep(params->duration_ms, params->frequency_hz);
free(params);
return NULL;
}
// Asynchronous beep
void buzzer_beep_async(int duration_ms, int frequency_hz)
{
pthread_t thread;
struct buzzer_params *params = malloc(sizeof(struct buzzer_params));
params->duration_ms = duration_ms;
params->frequency_hz = frequency_hz;
pthread_create(&thread, NULL, buzzer_thread, params);
pthread_detach(thread);
}8.3 Volume Control
Achieve volume control by changing the duty cycle:
void buzzer_beep_volume(int duration_ms, int frequency_hz, int volume_percent)
{
if (volume_percent < 0 || volume_percent > 100)
volume_percent = 50;
int half_period_us = 1000000 / (2 * frequency_hz);
int on_us = half_period_us * volume_percent / 100;
int off_us = half_period_us * 2 - on_us;
for (int i = 0; i < duration_ms * frequency_hz; i++) {
gpio_set_value(BUZZER_GPIO_CHIP, BUZZER_GPIO_LINE, 1);
usleep(on_us);
gpio_set_value(BUZZER_GPIO_CHIP, BUZZER_GPIO_LINE, 0);
usleep(off_us);
}
}Duty cycle vs. volume
- 50% duty cycle: maximum volume
- 25% duty cycle: half the volume
- The relationship is nonlinear; the actual effect depends on the buzzer's characteristics
