Elastic Sheep

Because elasticdog was already taken

Elastic Sheep header image 2

Driving a buzzer

October 12th, 2009 · No Comments · Project Wiggum

In this post I will add a buzzer to my prototype and show two ways to test it, first by bit-banging a pin then by using a hardware counter.

Connecting the buzzer

I am using a CEM-1203 magnetic buzzer (datasheet).

This buzzer has peak frequency responses at 400Hz, 800Hz, 2kHz and 4kHz.

To connect it to the ATMEGA168, I followed the suggestion from the datasheet: the buzzer if driven by a 2N3904 NPN transistor and a diode protects the transistor from reverse currents from the magnetic inductance.

CEM-1203 buzzer connection schematic

The transistor is connected to the PB1 pin of the ATMEGA168.

CEM-1203A buzzer connection

Manual pin toggling

The buzzer should be controlled by a square wave signal with 1/2 duty. I will generate this square wave by toggling the PB1 output with appropriate delays:

  /* Setup PB1 as an output */
  DDRB = _BV(PB1);
 
  /* Initial period */
  period = 1600;
 
  /* Main loop */
  for(;;)
  {
    PORTB |= _BV(DDB1);
    delay_us(period / 2);
 
    PORTB &= ~_BV(DDB1);
    delay_us(period / 2);
 
    counter++;
    if (counter == 200)
    {
      /* Reset the counter */
      counter = 0;
 
      /* Update the toggling period */
      if (period == 1600)
        period = 2000;
      else
        period = 1600;
    }
  }

The period variable contains the period in us of the generated square wave. The counter toggles the period from 2000us (500Hz) to 1600us (625Hz) every 200 periods.

Here is the result:

Annoying isn’t it ?

Toggling an output in a loop is the easiest way to control the buzzer but it doesn’t allow the main program to do much else at the same time. To achieve this you can delegate the square wave generation to a hardware timer.

Using an hardware timer

An hardware timer is a counter that is constantly incremented by the CPU clock. Actions can be triggered when the counter reaches a predefined value. It is useful to measure time intervals or generate signals either for the software with an interrupt or for the hardware by toggling a pin.

To generate a square wave I will use timer1 from the ATMEGA168 in CTC mode (Clear Timer on Compare). The 16-bit resolution of timer1 offers the greatest precision for selecting an output frequency. The timer is setup to toggle the OC1A/PB1 pin when it reaches the OCR1A value. By changing the OCR1A value, I can control the period of the OC1A signal. The following schema diagram excerpted from the ATMEGA168 datasheet illustrate the expected behavior:

ATMEGA168 CTC mode timing diagram

Here is the timer configuration:

  // Clear Timer on Compare Mode
  // Toggle OC1A/PB1 on Compare Match
  // clk/8 prescaling
  TCCR1A = _BV(COM1A0);
  TCCR1B = _BV(WGM12) | _BV(CS11);

I choose a clk/8 prescaler to obtain a 2MHz counter from my 16MHz main clock. So each 0.5us the counter value is increased. When it reaches OCR1A, OC1A is toggled and the counter value is reset to 0.

The output frequency is computed from the following formula: Fo = Fclk / (2.N.(1+OCR1A)), where N=8 is the clock prescaling factor and Fclk is 16MHz. To output a 400Hz square wave: OCR1A = 2500 – 1 = 2499, where 2500 is the period of the signal in ┬Ás.

The main loop of my test program is greatly simplified and I have now a better control on the duration of the tones:

  /* Initial period */
  period = (2500 - 1);
 
  /* Main loop */
  for(;;)
  {
    // Update the counter TOP value
    OCR1A = period;
 
    delay_ms(1000);
 
    /* Update the period */
    if (period == (2500 - 1))
      period = 5000 - 1; // 200 Hz
    else
      period = 2500 - 1; // 400 Hz
  }

The result:

Coming next

We have now finished with the test of the elements required by the MMI. In the next post I will connect a GPS to get NMEA data through the serial port.

Full source code

test_buzzer_manual.zip
test_buzzer_hw.zip

Tags: ·

No Comments so far ↓

There are no comments yet...Kick things off by filling out the form below.

Leave a Comment