Thursday, February 25, 2021

Playing music and tones using a piezo buzzer - library for AVR microcontrollers using timer interrupt | ATmega328P, ATmega88

This is a library for playing chiptunes music using PWM and a piezo buzzer. Tones with different frequencies and timeouts can also be produced. The library uses a timer interrupt so the functions are non-blocking meaning that while a song or tone is generated the CPU can do other things. Timer 1 is used for generating the musical notes and another 8 bit selectable timer (0 or 2) is used to trigger an interrupt every 1ms. When a note ends the code inside the interrupt will calculate the PWM frequency of timer 1 based on the next note.

Optionally a led can be made to blink with the music rhythm.

The piezo buzzer must be connected to pins OCR1A and OCR1B which are set to have opposite polarity. This way you can make the buzzer louder with a lower voltage because the buzzer will see 10V even if the microcontroller outputs 5V.

Playing music and tones using a piezo buzzer - library for AVR microcontrollers using timer interrupt | ATmega328P, ATmega88

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 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
Passive or active piezoelectric buzzers with resonant case

Piezoelectric diaphragm
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 PWM signal.

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. 

Fun fact: applying a DC voltage for a long time can damage the buzzer.

Driving a piezoelectric buzzer with a microcontroller

The library will output the PWM for driving the buzzer on pins OCR1A and OCR1B. This way you can make the buzzer louder with a lower voltage because the buzzer will see 10V even if the microcontroller outputs 5V.

Piezoelectric buzzer connected directly to microcontroller pins

There is another method of driving the buzzer using a transistor. The advantage is that it uses only one pin and also a higher voltage can be used to make the buzzer louder.

Driving a piezo buzzer using a 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.

Playing music on a piezoelectric buzzer using PWM and a microcontroller

Because each musical note is basically a certain audio frequency, all we need to generate tones and music is a 16 bit timer for PWM and an array that holds the notes and their duration. Let's see an example of a buzzer melody.

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_A4,-4,	NOTE_F4,	NOTE_G4,	NOTE_F4,-2,

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.

NOTE_x are defines that represents note frequencies. The defines are located inside the pitches.h header file. For example NOTE_C4 represents 262 Hertz and 4 means octave 4:

#define NOTE_C4  262

NOTE_C5 is one octave higher and it's frequency is 523 Hertz. The higher the octave the higher the pitch.

#define NOTE_C5  523

After a note must be it'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.

Note (pun unintended) that 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.

Where to get the songs

I have included down below a few songs but if you need more you can find at this link 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.

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: 0 or 2


Optionally you can set this define to 1 if you wish to blink a led on music and set the DDR, PORT and PIN number accordingly


Setup function

void chipTunes_Init(void)

Sets the OCR1A and OCR1B pins to output, enables Timer 1 in PWM, Phase and Frequency Correct mode, enables Timer 0 or 2 to trigger an interrupt every 1ms and finally the global interrupts are enabled.

Tone generation using PWM

void chipTunes_Tone(uint16_t tone, uint16_t duration_ms)

tone: the tone frequency in Herts
duration_ms:tone duration in milliseconds

This function is non-blocking meaning the CPU can do other things while the tone is output.

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 Timer 1 with a prescaler of 8, starts Timer 0 or 2 and sets a flag indicating that a tune is currently playing.

Stopping the song or tone

void chipTunes_Stop(void)

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 tempo. By default they look like this:

const int *chipTunes_Playlist[] = {

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 on how to play a playlist of chiptunes:

#include "chipTunes.h"

int main(void){

	const uint8_t NR_OF_TUNES = sizeof(chipTunes_Tempo);
	uint8_t tunes = 0;
		// Whait for the song to end then move to the next one
		if(chipTunes_IsPlaying() == 0 && NR_OF_TUNES){
			// Delay between songs
			chipTunes_Play(chipTunes_Playlist[tunes], chipTunes_Tempo[tunes]);
			if(tunes > NR_OF_TUNES - 1) tunes = 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 Beethovem
Cannon in D - Pachelbel
Ode to Joy - Beethoven's Symphony No. 9
Happy Birthday

Like explained before, most of the songs inside the "tunes.h" are from this website but with the duplicate durations 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) - about note duration - many things about piezoelectric transducers - and again more things about buzzers

No comments:

Post a Comment