This is a library for playing monophonic music using PWM and a piezoelectric buzzer. Monophonic means it can play only one note at a time. Regardless, it can produce some nice music. Optionally a led can be made to blink with the music rhythm.
Apart from playing songs it can also be used for tone generation useful in projects where audio indicators are needed.
All you need is a 16-bit timer that can be one of the following: timer1, timer3 and timer4 (provided that the microcontroller has them) and an 1ms interval interrupt where to place the main function. This is needed because the notes must have a certain duration.
Playing music on a piezoelectric buzzer using PWM and a microcontroller
To make a song first we need musical notes. These can be found in the file "pitches.h". Here are just two octaves from the file and look like this
#define NOTE_C4 262 #define NOTE_CS4 277 #define NOTE_D4 294 #define NOTE_DS4 311 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_FS4 370 #define NOTE_G4 392 #define NOTE_GS4 415 #define NOTE_A4 440 #define NOTE_AS4 466 #define NOTE_B4 494 #define NOTE_C5 523 #define NOTE_CS5 554 #define NOTE_D5 587 #define NOTE_DS5 622 #define NOTE_E5 659 #define NOTE_F5 698 #define NOTE_FS5 740 #define NOTE_G5 784 #define NOTE_GS5 831 #define NOTE_A5 880 #define NOTE_AS5 932 #define NOTE_B5 988
For example NOTE_G4 is the note G in octave 4, NOTE_CS5 is the note C sharp in octave 5. After each note is it's frequency in Hertz. The higher the frequency the higher the pitch.
These notes can be used to make songs like this
const int happy_birthday[] PROGMEM = { NOTE_C4,4, NOTE_C4,8, NOTE_D4,-4, NOTE_C4, NOTE_F4, NOTE_E4,-2, NOTE_C4,4, NOTE_C4,8, NOTE_D4,-4, NOTE_C4, NOTE_G4, NOTE_F4,-2, NOTE_C4,4, NOTE_C4,8, NOTE_C5,-4, NOTE_A4, NOTE_F4, NOTE_E4, NOTE_D4, NOTE_AS4,4, NOTE_AS4,8, NOTE_A4,-4, NOTE_F4, NOTE_G4, NOTE_F4,-2, MUSIC_END };
This will play the well known "Happy Bird Day" song. The array has the PROGMEM attribute which means it will only be stored in flash memory not in flash memory AND RAM memory as it will be the case without it. This is very important because without this attribute the RAM will be filled very quickly.
After the note is the note's duration. 1 is a whole note, 2 is half a note, 4 is
a quarter note and so on. So a higher number means a shorter note. A negative
duration means a dotted note. For example -4 means a dotted quarter note, that
is, a quarter plus an eighteenth or note duration * 1.5.
Not every note has a duration after it. That means the duration hasn't changed. Only when the duration of the note changes there is a number after it. Every note can have a duration after it even if it's the same duration but this will only increase the size of the array.
MUSIC_END is a flag and represents a 0.
To put a rest (pause) between notes use REST, duration. Same as NOTE,duration. REST represents 1.
There is also these defines: __CHIP_TUNES_START_MARKER__. __CHIP_TUNES_END_MARKER__, __CHIP_TUNES_GOTO_MARKER__. In some melodies there are sections that repeats and these markers move the pointer to the section that needs repeating thus saving lots of flash memory.
To know how exactly the notes are played by the microcontroller check out the main code, but basically the 16-bit timer is set to generate a PWM frequency based on the note.
Where to get the songs
I have included down below a few songs but if you need more you can find at this link https://dragaosemchama.com/en/2019/02/songs-for-arduino/. There are many sites where you can find music sheets for different songs provided you know music theory. I personally don't. Because the songs provided at that link have repeated duration thus increasing the array size, I have made a header file "tunes.h" with some songs where I have removed the repeated duration and duplicate sections.
Types of buzzers
Before going any further I should clarify regarding the types of buzzers for those who are not familiar with them.
There are passive buzzers (or piezoelectric transducers but buzzer is shorter) and active buzzers.
Active piezoelectric buzzers
This types of buzzers have built-in circuitry that when a DC voltage is applied to them they buzz at a fixed frequency. They cannot produce music but they are easy to drive and can be used as sound indicators. They look like passive buzzers from the outside.
Passive piezoelectric buzzers
There are two main types of passive buzzers: just the piezoelectric diaphragm
and the diaphragm inside a resonant case.
![]() |
Passive or active piezoelectric buzzers with resonant case |
![]() |
Piezoelectric diaphragm |
The resonant case makes the buzzer sound louder while the diaphragm could be used for a postcard or hand-watch because of the very low profile. The passive buzzers needs to be controlled by an AC or pulsed DC voltage (PWM).
There are also magnetic buzzers and buzzers with a feedback wire for self resonance. Links about them can be found at the end of this page.
Did you know: applying a DC voltage for a long time can degrade the buzzer.
Driving and connecting a piezoelectric buzzer with a microcontroller
There are two ways to drive the piezo buzzer with a microcontroller:
- having the buzzer connected directly to the microcontroller using 1 or 2 pins. With 2 pins the buzzer will be louder because of the push-pull
- using a NPN transistor to drive the buzzer and 1 microcontroller pin
The second method is my preferred way because you can use higher voltages than the microcontroller can provide which will make the buzzer louder.
The library supports both methods and if you have the buzzer connected directly to the microcontroller then you could use two pins in push-pull mode to make the buzzer louder with a lower voltage such as 5V. For example if Timer1 is used the buzzer could be connected on pins OCR1A and OCR1B. This way the buzzer will see 10V even if the microcontroller outputs 5V.
Driving the buzzer directly with the microcontroller pins
When using 2 pins the buzzer is connected across OCnA and OCnB n representing the selected timer number. If only one pin is used the buzzer is connected between OCnA or OCnB and ground.
Is recommended to place a resistor between 10 to 100 ohm in series because the buzzer is capacitive and could stress the microcontroller pin gate driver. I tested it with 100 ohm and is sounded good.
![]() |
1 pin method: the buzzer is wired to OC1A and ground |
![]() |
2 pins method: the piezo buzzer is connected to OC1A and OC1B because Timer1 was selected |
Driving the piezo buzzer using an NPN transistor
Since the piezoelectric transducer is capacitive, the resistor in parallel with the buzzer also improves the high frequency response by dissipating quicker the energy stored in the piezoelectric element while the transistor is off, and also protects the transistor from voltage spikes because there is also inductance.
Finally the code
Include the file "chipTunes.h". This file will include "pitches.h" and
"tunes.h", so they must be downloaded and placed in the same location as
"chipTunes.h".
#include "chipTunes.h"
Select the timer: 1, 3 or 4. These settings are inside the chipTunes.h file in the user setup section.
#define CHIP_TUNES_TIMER CHIP_TUNES_TIMER4
Specify where the pin is located. For 2 pins method this pin is where OCnA is. For 1 pin method this can be either OCnA or OCnB. This is an example for pin PC4.
#define CHIP_TUNES_PIN1_DDR DDRC #define CHIP_TUNES_PIN1_PORT PORTC #define CHIP_TUNES_PIN1_PIN PC4
In case 2 pins are used, next is the second pin. If only 1 pin is used comment out this lines
#define CHIP_TUNES_PIN2_DDR DDRB #define CHIP_TUNES_PIN2_PORT PORTB #define CHIP_TUNES_PIN2_PIN PB2
For one pin method this setting specifies which channel to use: OCnA or OCnB
#define CHIP_TUNES_CHANNEL CHANNEL_A
Optionally if you wish to blink a led on music, set this to true, false otherwise
#define CHIP_TUNES_BLINK_LED 0
Setup function
void chipTunes_Init(void)
This function is used to set up the timer and pins
Tone generation using PWM
void chipTunes_Tone(uint16_t tone, uint16_t duration_ms)
tone: the tone frequency in Hertz
duration_ms: tone duration in milliseconds
This function is non-blocking meaning the CPU can do other things while the tone is output. For this function to work the following code must be in the 1ms interrupt routine
if(chipTunes_IsPlaying()) chipTunes_ISR();
Main function
void chipTunes_ISR(void)
This function must be inside an ISR function that triggers every 1 millisecond, like so
if(chipTunes_IsPlaying()) chipTunes_ISR();
You can find here the millis.h library for AVR that can do that https://www.programming-electronics-diy.xyz/2021/01/millis-and-micros-library-for-avr.html
Playing a song
void chipTunes_Play(const int *melody, uint8_t tempo)
melody: the array name that includes the musical notes.
tempo: tempo of the song. The higher the number the faster the
song will be played.
Starts the timer and sets a flag indicating that a tune is currently playing.
Stopping the song or tone
void chipTunes_Stop(void)
Setting the volume
void chipTunes_SetVolume(volume)
volume: a number from 0 to 50. Default value is 50 which is the maximum volume.
This kinda works. Values over 10 to 50 barely make any difference but if you want a quieter buzzer start from a value of 1.
Check if a song or tone is playing
char chipTunes_IsPlaying(void)
If a song or tone is currently playing returns 1 and 0 otherwise.
Generating alarm tone
void chipTunes_alert_alarm(uint8_t vuvuzela)
vuvuzela: if 0 the alarm will sound like a siren, if 1 it will sound like a vuvuzela.
Check the video to hear how the sounds sound. This function is a blocking function and will exit after the alarm is done. After that a 200ms delay is added because usually you want it to play it multiple times.
Creating a playlist
To be able to play a specific song together with it's tempo, you need an array for the playlist and another one for the tempo. By default they look like this. Add more or comment out the ones that are not used
const int *chipTunes_Playlist[] = { tetris_theme, fur_elise, cannon_in_d_pachelbel, greensleeves, happy_birthday, ode_to_joy }; const uint8_t *chipTunes_Tempo[] = { 144, // tetris_theme 80, // fur_elise 100, // cannon_in_d_pachelbel 70, // greensleeves 140, // happy_birthday 114, // ode_to_joy };
Code example:
#include <avr/io.h> #include "chipTunes.h" const int star_wars_theme[] PROGMEM = { NOTE_AS4,8, NOTE_AS4, NOTE_AS4, //1 NOTE_F5,2, NOTE_C6, NOTE_AS5,8, NOTE_A5, NOTE_G5, NOTE_F6,2, NOTE_C6,4, NOTE_AS5,8, NOTE_A5, NOTE_G5, NOTE_F6,2, NOTE_C6,4, NOTE_AS5,8, NOTE_A5, NOTE_AS5, NOTE_G5,2, NOTE_C5,8, NOTE_C5, NOTE_C5, NOTE_F5,2, NOTE_C6, NOTE_AS5,8, NOTE_A5, NOTE_G5, NOTE_F6,2, NOTE_C6,4, NOTE_AS5,8, NOTE_A5, NOTE_G5, NOTE_F6,2, NOTE_C6,4, //8 NOTE_AS5,8, NOTE_A5, NOTE_AS5, NOTE_G5,2, NOTE_C5,-8, NOTE_C5,16, NOTE_D5,-4, NOTE_D5,8, NOTE_AS5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_G5,4, NOTE_D5,8, NOTE_E5,4, NOTE_C5,-8, NOTE_C5,16, NOTE_D5,-4, NOTE_D5,8, NOTE_AS5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_C6,-8, NOTE_G5,16, NOTE_G5,2, REST,8, NOTE_C5, //13 NOTE_D5,-4, NOTE_D5,8, NOTE_AS5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_G5,4, NOTE_D5,8, NOTE_E5,4, NOTE_C6,-8, NOTE_C6,16, NOTE_F6,4, NOTE_DS6,8, NOTE_CS6,4, NOTE_C6,8, NOTE_AS5,4, NOTE_GS5,8, NOTE_G5,4, NOTE_F5,8, NOTE_C6,1, MUSIC_END }; int main(void){ chipTunes_Init(); chipTunes_Play(star_wars_theme, 108); while (1){ } } // 1ms interrupt. It can be the millis library at this link // https://www.programming-electronics-diy.xyz/2021/01/millis-and-micros-library-for-avr.html ISR(){ if(chipTunes_IsPlaying()) chipTunes_ISR(); }
Code example on how to play a playlist of tunes:
#include "chipTunes.h" int main(void){ chipTunes_Init(); const uint8_t NR_OF_TUNES = sizeof(chipTunes_Tempo); uint8_t tune = 0; while(1){ // Whait for the song to end then move to the next one if(chipTunes_IsPlaying() == 0 && NR_OF_TUNES){ // Delay between songs _delay_ms(2000); chipTunes_Play(chipTunes_Playlist[tune], chipTunes_Tempo[tune]); tune++; if(tune > NR_OF_TUNES - 1) tune = 0; } } }
// 1ms interrupt. It can be the millis library at this link // https://www.programming-electronics-diy.xyz/2021/01/millis-and-micros-library-for-avr.html ISR(){ if(chipTunes_IsPlaying()) chipTunes_ISR(); }
Download
v2.0
chipTunes - the main library code
pitches - contains the musical notes that chipTunes needs
Contains the following songs:
Dart Vader theme (Imperial March) - Star wars
Tetris theme - (Korobeiniki)
Mario Main Theme
Mario Underworld Melody
Fur Elise - Ludwig van Beethoven
Cannon in D - Pachelbel
Greensleeves
Ode to Joy - Beethoven's Symphony No. 9
Happy Birthday
Most of the songs inside the "tunes.h" are from this website
https://dragaosemchama.com/en/2019/02/songs-for-arduino/
but with the duplicate duration removed. Leave him a nice comment.
Each song is credited separately.
Other Resources
Fundamental Rhythm Explained for Beginners by Piano Lessons On The Web
(12:49)
https://www.musictheory.net/lessons/11 - about note duration
https://en.wikipedia.org/wiki/Octave
https://www.murata.com/~/media/webrenewal/support/library/catalog/products/sound/p15e.ashx - many things about piezoelectric transducers
https://www.cuidevices.com/product-spotlight/piezo-and-magnetic-buzzers - and again more things about buzzers
something is missing for me to work, not letting me put the ISR method in
ReplyDelete