Using a buzzer with Arduino in pure C

Posted on 2014/10/15

2


Since one of my most viewed posts is Programming Arduino Uno in pure C, I wanted to write other posts about common Arduino functionalities implemented in C instead of the default language. This post is part of a series about programming Arduino applications in C, using a Debian computer with avr-gcc compiler.

Arduino UNO connected to KEPO buzzer

Arduino UNO connected to KEPO buzzer.

This time I’m using a buzzer that I bought at an electronics fair. The first thing I had to do was search for the datasheet, because the package had no markings apart from the brand name KEPO. After some searching I discovered that it’s the KPR-2310. It’s a piezoelectric buzzer made for 12V peak-to-peak square wave. The good thing is that the maximum current that this component consumes is 3mA, which is well below the 40mA of maximum current per I/O pin of the ATmega328P. This means I can power the buzzer directly without protection resistors. I was also afraid that since the buzzer was made for 12V, it wouldn’t work at the 5V level that an Arduino I/O can give; turns out it produces a sound that is quite loud anyway.

In order to produce a sound, I needed to move an I/O with a certain frequency. There are at least two ways to do that: I can directly move the I/O port or I can use the timer. I decided to use the Timer0 output OC0A, which is mapped to pin 6 of port D of ATmega328P, which is the pin 6 of Arduino UNO. This information can be retrieved from ATmega328P datasheet and from Arduino UNO schematics.

Timer0 has many modes of operations, and what suits my needs is the Clear Timer on Compare Match (CTC) Mode. With this mode I can toggle the OC0A pin with a frequency that I can tune quite well, thus creating a 50% duty cycle square wave to drive the buzzer. The frequency can be tuned by using the following formula taken from the datasheet:

f_{OCnx}=\frac{f_{clkIO}}{2\cdot N\cdot (1+OCRnx)}

Where f_{OCnx} is the waveform frequency,f_{clkIO} is 16MHz for the Arduino UNO, N is the prescaler and OCRnx is the relevant Output Compare Register, in my case the OCR0A since I want to change the OC0A pin. For example if I want to play a standard 440Hz note, I can set the prescaler N to 256 and the OCR0A register to 70. With this information I wrote the following code in buzzer.c that plays two notes, one of 440Hz and one of 880Hz:

#include <avr/io.h>
#include <util/delay.h>

enum t0_prescaler
{
    T0_PRESCALER_1 = _BV(CS00),
    T0_PRESCALER_8 = _BV(CS01),
    T0_PRESCALER_64 = _BV(CS00) | _BV(CS01),
    T0_PRESCALER_256 = _BV(CS02),
    T0_PRESCALER_1024 = _BV(CS02) | _BV(CS00),
};

static void t0_set_prescaler(enum t0_prescaler ps)
{
    TCCR0B = ps;
}

static unsigned short t0_get_prescaler_rate(enum t0_prescaler ps)
{
    unsigned short rate;
    switch(ps)
    {
        case T0_PRESCALER_1:
            rate = 1;
            break;
        case T0_PRESCALER_8:
            rate = 8;
            break;
        case T0_PRESCALER_64:
            rate = 64;
            break;
        case T0_PRESCALER_256:
            rate = 256;
            break;
        case T0_PRESCALER_1024:
            rate = 1024;
            break;
        default:
            rate = 0;
            break;
    }
    return rate;
}

static unsigned long div_round(unsigned long d, unsigned long q)
{
    return (d + (q/2)) / q;
}

static void t0_set_ctc_a(unsigned long hz, unsigned long timer_freq)
{
    OCR0A = div_round(timer_freq, hz*2) - 1;
    TCCR0A =
          _BV(COM0A0) // toggle
        | _BV(WGM01); // CTC
}

int main(void)
{
    unsigned long timer_freq;
    enum t0_prescaler ps = T0_PRESCALER_256;

    DDRD |= _BV(DDD6);
    t0_set_prescaler(ps);
    timer_freq = div_round(F_CPU, t0_get_prescaler_rate(ps));

    while(1)
    {
        t0_set_ctc_a(440, timer_freq);
        _delay_ms(200);
        t0_set_ctc_a(880, timer_freq);
        _delay_ms(200);
    }
    return 0;
}

Be aware that the code doesn’t check for valid values, so the frequencies that work might be limited. To upload the program into Arduino UNO, I launched the following commands:

$ avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p -o buzzer buzzer.c
$ avr-objcopy -O ihex -R .eeprom buzzer buzzer.hex
$ avrdude -F -V -c arduino -p ATMEGA328P -P /dev/ttyACM0 -b 115200 -U flash:w:buzzer.hex

I connected the buzzer to the GND pin and the pin 6 of the Arduino UNO and the buzzer played the two notes one after the other in an endless loop.

From here it’s possible to try other solutions, other pins and different ways to manage the notes, for example by using timed interrupts instead of delays for the note duration. It should be also possible to attach two buzzers to two different pins and play a polyphonic melody.

Posted in: Hardware