There is a set of projects that require some kind of audio output. For example a simple alarm bell. This project shows how we can produce different kinds of audio effects by just using an AVR MCU and a speaker/buzzer. We will use 8-bit Timer, PWM technique and some pre-calculations to generate right sounds. Tu run this project just hook a small speaker/buzzer with resistor connected in series between PB0 and GND and you are ready. The code is on Github, click here.
Parts Required
- ATtiny13 – i.e. MBAVR-1 development board
- Resistor – 1kΩ
- Speaker / Buzzer (without internal generator)
Circuit Diagram
Frequency Table (A4 = 440 Hz)
Frequencies for equal-tempered scale, where A4 = 440 Hz. Column 3 contains a real frequencies of first octave which has been measured for ATtiny13 @1.2MHz on PB0 (see oscilloscope screenshots)
Note | Frequency (Hz) | ATtiny13 frequency (Hz) | Wavelength (cm) |
---|---|---|---|
C0 | 16.35 | 16.63 | 2109.89 |
C#0/Db0 | 17.32 | 17.61 | 1991.47 |
D0 | 18.35 | 18.59 | 1879.69 |
D#0/Eb0 | 19.45 | 19.56 | 1774.20 |
E0 | 20.60 | 20.54 | 1674.62 |
F0 | 21.83 | 21.53 | 1580.63 |
F#0/Gb0 | 23.12 | 23.48 | 1491.91 |
G0 | 24.50 | 24.46 | 1408.18 |
G#0/Ab0 | 25.96 | 26.42 | 1329.14 |
A0 | 27.50 | 27.40 | 1254.55 |
A#0/Bb0 | 29.14 | 29.35 | 1184.13 |
B0 | 30.87 | 31.31 | 1117.67 |
C1 | 32.70 | – | 1054.94 |
C#1/Db1 | 34.65 | – | 995.73 |
D1 | 36.71 | – | 939.85 |
D#1/Eb1 | 38.89 | – | 887.10 |
E1 | 41.20 | – | 837.31 |
F1 | 43.65 | – | 790.31 |
F#1/Gb1 | 46.25 | – | 745.96 |
G1 | 49.00 | – | 704.09 |
G#1/Ab1 | 51.91 | – | 664.57 |
A1 | 55.00 | – | 627.27 |
A#1/Bb1 | 58.27 | – | 592.07 |
B1 | 61.74 | – | 558.84 |
C2 | 65.41 | – | 527.47 |
C#2/Db2 | 69.30 | – | 497.87 |
D2 | 73.42 | – | 469.92 |
D#2/Eb2 | 77.78 | – | 443.55 |
E2 | 82.41 | – | 418.65 |
F2 | 87.31 | – | 395.16 |
F#2/Gb2 | 92.50 | – | 372.98 |
G2 | 98.00 | – | 352.04 |
G#2/Ab2 | 103.83 | – | 332.29 |
A2 | 110.00 | – | 313.64 |
A#2/Bb2 | 116.54 | – | 296.03 |
B2 | 123.47 | – | 279.42 |
C3 | 130.81 | – | 263.74 |
C#3/Db3 | 138.59 | – | 248.93 |
D3 | 146.83 | – | 234.96 |
D#3/Eb3 | 155.56 | – | 221.77 |
E3 | 164.81 | – | 209.33 |
F3 | 174.61 | – | 197.58 |
F#3/Gb3 | 185.00 | – | 186.49 |
G3 | 196.00 | – | 176.02 |
G#3/Ab3 | 207.65 | – | 166.14 |
A3 | 220.00 | – | 156.82 |
A#3/Bb3 | 233.08 | – | 148.02 |
B3 | 246.94 | – | 139.71 |
C4 | 261.63 | – | 131.87 |
C#4/Db4 | 277.18 | – | 124.47 |
D4 | 293.66 | – | 117.48 |
D#4/Eb4 | 311.13 | – | 110.89 |
E4 | 329.63 | – | 104.66 |
F4 | 349.23 | – | 98.79 |
F#4/Gb4 | 369.99 | – | 93.24 |
G4 | 392.00 | – | 88.01 |
G#4/Ab4 | 415.30 | – | 83.07 |
A4 | 440.00 | – | 78.41 |
A#4/Bb4 | 466.16 | – | 74.01 |
B4 | 493.88 | – | 69.85 |
C5 | 523.25 | – | 65.93 |
C#5/Db5 | 554.37 | – | 62.23 |
D5 | 587.33 | – | 58.74 |
D#5/Eb5 | 622.25 | – | 55.44 |
E5 | 659.25 | – | 52.33 |
F5 | 698.46 | – | 49.39 |
F#5/Gb5 | 739.99 | – | 46.62 |
G5 | 783.99 | – | 44.01 |
G#5/Ab5 | 830.61 | – | 41.54 |
A5 | 880.00 | – | 39.20 |
A#5/Bb5 | 932.33 | – | 37.00 |
B5 | 987.77 | – | 34.93 |
C6 | 1046.50 | – | 32.97 |
C#6/Db6 | 1108.73 | – | 31.12 |
D6 | 1174.66 | – | 29.37 |
D#6/Eb6 | 1244.51 | – | 27.72 |
E6 | 1318.51 | – | 26.17 |
F6 | 1396.91 | – | 24.70 |
F#6/Gb6 | 1479.98 | – | 23.31 |
G6 | 1567.98 | – | 22.00 |
G#6/Ab6 | 1661.22 | – | 20.77 |
A6 | 1760.00 | – | 19.60 |
A#6/Bb6 | 1864.66 | – | 18.50 |
B6 | 1975.53 | – | 17.46 |
C7 | 2093.00 | – | 16.48 |
C#7/Db7 | 2217.46 | – | 15.56 |
D7 | 2349.32 | – | 14.69 |
D#7/Eb7 | 2489.02 | – | 13.86 |
E7 | 2637.02 | – | 13.08 |
F7 | 2793.83 | – | 12.35 |
F#7/Gb7 | 2959.96 | – | 11.66 |
G7 | 3135.96 | – | 11.00 |
G#7/Ab7 | 3322.44 | – | 10.38 |
A7 | 3520.00 | – | 9.80 |
A#7/Bb7 | 3729.31 | – | 9.25 |
B7 | 3951.07 | – | 8.73 |
See equations for the Frequency Table
Software
This code is written in C and can be compiled using the avr-gcc. More details on how compile this project is here.
/** * Copyright (c) 2016, Łukasz Marcin Podkalicki <lpodkalicki@gmail.com> * ATtiny13/007 * Simple tone generator. * -- * Settings: * FUSE_L=0x6A * FUSE_H=0xFF * F_CPU=1200000 */ #include <avr/io.h> #include <avr/interrupt.h> #include <avr/pgmspace.h> #include <util/delay.h> #define BUZZER_PIN PB0 #define N_1 (_BV(CS00)) #define N_8 (_BV(CS01)) #define N_64 (_BV(CS01)|_BV(CS00)) #define N_256 (_BV(CS02)) #define N_1024 (_BV(CS02)|_BV(CS00)) typedef struct s_note { uint8_t OCRxn; // 0..255 uint8_t N; } note_t; typedef struct s_octave { note_t note_C; note_t note_CS; note_t note_D; note_t note_DS; note_t note_E; note_t note_F; note_t note_FS; note_t note_G; note_t note_GS; note_t note_A; note_t note_AS; note_t note_B; } octave_t; /* All calculations below are prepared for ATtiny13 default clock source (1.2MHz) F = F_CPU / (2 * N * (1 + OCRnx)), where: - F is a calculated PWM frequency - F_CPU is a clock source (1.2MHz) - the N variable represents the prescaler factor (1, 8, 64, 256, or 1024). */ PROGMEM const octave_t octaves[8] = { { // octave 0 .note_C = {142, N_256}, // 16.35 Hz .note_CS = {134, N_256}, // 17.32 Hz .note_D = {127, N_256}, // 18.35 Hz .note_DS = {120, N_256}, // 19.45 Hz .note_E = {113, N_256}, // 20.60 Hz .note_F = {106, N_256}, // 21.83 Hz .note_FS = {100, N_256}, // 23.12 Hz .note_G = {95, N_256}, // 24.50 Hz .note_GS = {89, N_256}, // 25.96 Hz .note_A = {84, N_256}, // 27.50 Hz .note_AS = {79, N_256}, // 29.14 Hz .note_B = {75, N_256} // 30.87 Hz }, { // octave 1 .note_C = {71, N_256}, // 32.70 Hz .note_CS = {67, N_256}, // 34.65 Hz .note_D = {63, N_256}, // 36.71 Hz .note_DS = {59, N_256}, // 38.89 Hz .note_E = {56, N_256}, // 41.20 Hz .note_F = {53, N_256}, // 43.65 Hz .note_FS = {50, N_256}, // 46.25 Hz .note_G = {47, N_256}, // 49.00 Hz .note_GS = {44, N_256}, // 51.91 Hz .note_A = {42, N_256}, // 55.00 Hz .note_AS = {39, N_256}, // 58.27 Hz .note_B = {37, N_256} // 61.74 Hz }, { // octave 2 .note_C = {142, N_64}, // 65.41 Hz .note_CS = {134, N_64}, // 69.30 Hz .note_D = {127, N_64}, // 73.42 Hz .note_DS = {120, N_64}, // 77.78 Hz .note_E = {113, N_64}, // 82.41 Hz .note_F = {106, N_64}, // 87.31 Hz .note_FS = {100, N_64}, // 92.50 Hz .note_G = {95, N_64}, // 98.00 Hz .note_GS = {89, N_64}, // 103.83 Hz .note_A = {84, N_64}, // 110.00 Hz .note_AS = {79, N_64}, // 116.54 Hz .note_B = {75, N_64} // 123.47 Hz }, { // octave 3 .note_C = {71, N_64}, // 130.81 Hz .note_CS = {67, N_64}, // 138.59 Hz .note_D = {63, N_64}, // 146.83 Hz .note_DS = {59, N_64}, // 155.56 Hz .note_E = {56, N_64}, // 164.81 Hz .note_F = {53, N_64}, // 174.61 Hz .note_FS = {50, N_64}, // 185.00 Hz .note_G = {47, N_64}, // 196.00 Hz .note_GS = {44, N_64}, // 207.65 Hz .note_A = {42, N_64}, // 220.00 Hz .note_AS = {39, N_64}, // 233.08 Hz .note_B = {37, N_64} // 246.94 Hz }, { // octave 4 .note_C = {35, N_64}, // 261.63 Hz .note_CS = {33, N_64}, // 277.18 Hz .note_D = {31, N_64}, // 293.66 Hz .note_DS = {29, N_64}, // 311.13 Hz .note_E = {27, N_64}, // 329.63 Hz .note_F = {26, N_64}, // 349.23 Hz .note_FS = {24, N_64}, // 369.99 Hz .note_G = {23, N_64}, // 392.00 Hz .note_GS = {22, N_64}, // 415.30 Hz .note_A = {20, N_64}, // 440.00 Hz .note_AS = {19, N_64}, // 466.16 Hz .note_B = {18, N_64} // 493.88 Hz }, { // octave 5 .note_C = {142, N_8}, // 523.25 Hz .note_CS = {134, N_8}, // 554.37 Hz .note_D = {127, N_8}, // 587.33 Hz .note_DS = {120, N_8}, // 622.25 Hz .note_E = {113, N_8}, // 659.25 Hz .note_F = {106, N_8}, // 349.23 Hz .note_FS = {100, N_8}, // 369.99 Hz .note_G = {95, N_8}, // 392.00 Hz .note_GS = {89, N_8}, // 415.30 Hz .note_A = {84, N_8}, // 440.00 Hz .note_AS = {79, N_8}, // 466.16 Hz .note_B = {75, N_8} // 493.88 Hz }, { // octave 6 .note_C = {71, N_8}, // 1046.50 Hz .note_CS = {67, N_8}, // 1108.73 Hz .note_D = {63, N_8}, // 1174.66 Hz .note_DS = {59, N_8}, // 1244.51 Hz .note_E = {56, N_8}, // 1318.51 Hz .note_F = {53, N_8}, // 1396.91 Hz .note_FS = {50, N_8}, // 1479.98 Hz .note_G = {47, N_8}, // 1567.98 Hz .note_GS = {44, N_8}, // 1661.22 Hz .note_A = {42, N_8}, // 1760.00 Hz .note_AS = {39, N_8}, // 1864.66 Hz .note_B = {37, N_8} // 1975.53 Hz }, { // octave 7 .note_C = {35, N_8}, // 2093.00 Hz .note_CS = {33, N_8}, // 2217.46 Hz .note_D = {31, N_8}, // 2349.32 Hz .note_DS = {29, N_8}, // 2489.02 Hz .note_E = {27, N_8}, // 2637.02 Hz .note_F = {26, N_8}, // 2793.83 Hz .note_FS = {24, N_8}, // 2959.96 Hz .note_G = {23, N_8}, // 3135.96 Hz .note_GS = {22, N_8}, // 3322.44 Hz .note_A = {20, N_8}, // 3520.00 Hz .note_AS = {19, N_8}, // 3729.31 Hz .note_B = {18, N_8} // 3951.07 Hz } }; static void tone(uint8_t octave, uint8_t note) { uint32_t ret; note_t *val; ret = pgm_read_word_near((uint8_t *)&octaves + sizeof(octave_t) * octave + sizeof(note_t) * note); val = (note_t *)&ret; TCCR0B = (TCCR0B & ~((1<<CS02)|(1<<CS01)|(1<<CS00))) | val->N; OCR0A = val->OCRxn - 1; // set the OCRnx } static void stop(void) { TCCR0B &= ~((1<<CS02)|(1<<CS01)|(1<<CS00)); // stop the timer } int main(void) { uint8_t i, j; /* setup */ DDRB = 0b00000001; // set BUZZER pin as OUTPUT PORTB = 0b00000000; // set all pins to LOW TCCR0A |= (1<<WGM01); // set timer mode to Fast PWM TCCR0A |= (1<<COM0A0); // connect PWM pin to Channel A of Timer0 /* Walk throwgh all octaves */ for (i = 0; i < 8; ++i) { for (j = 0; j < 12; ++j) { tone(i, j); _delay_ms(80); } } stop(); _delay_ms(1500); /* loop */ while (1) { /* Polish song "Wlazł kotek na płotek" in loop */ tone(4, 7); // G _delay_ms(500); tone(4, 4); // E _delay_ms(500); tone(4, 4); // E _delay_ms(500); tone(4, 5); // F _delay_ms(500); tone(4, 2); // D _delay_ms(500); tone(4, 2); // D _delay_ms(500); tone(4, 0); // C _delay_ms(200); tone(4, 4); // E _delay_ms(300); tone(4, 7); // G _delay_ms(1000); stop(); _delay_ms(2000); tone(4, 7); // G _delay_ms(500); tone(4, 4); // E _delay_ms(500); tone(4, 4); // E _delay_ms(500); tone(4, 5); // F _delay_ms(500); tone(4, 2); // D _delay_ms(500); tone(4, 2); // D _delay_ms(500); tone(4, 0); // C _delay_ms(200); tone(4, 4); // E _delay_ms(300); tone(4, 0); // C _delay_ms(1000); stop(); _delay_ms(5000); } }
the code isnt working for me it shows unrecognized symbol for the notes
Thanks – I re-programmed the fuses for CPU/8 and it works nicely 🙂
I managed to squeeze in code for the ADC and set up a basic 0-5V scaling for tuned VCO with 1V/oct using the bottom 5 octaves.
This is great – thanks for the code.
🙂
Good to read that! I’m glad you like it.
/LP
Hi – thanks for this project.
I have copied your code to a ATtiny13a and it very nearly works for me.
The tones play and the Polish song plays.
However the lowest note I can achieve is C in Octave 3 (approx 131 Hz)
I have checked that the ATtiny is set for 1.2 MHz.
If I hard code tone (0, 0); then I get approx 131 Hz, not the expected 16.35 Hz.
In the comments, you have FUSE_L=0x6A and FUSE_H=0xFF
I’m not sure what to do with this information – can you please advise?
Maybe the problem is caused because I haven’t set these?
I am using Arduino IDE for programming and to flash the ATtiny.
Hi Sean,
In this example I’am using a 1.2MHz. It means that I use default fuse bits (L=0x6A, H=0xFF, http://eleccelerator.com/fusecalc/fusecalc.php?chip=attiny13a).
It looks like your Attiny13 is working with higher clock (9.6MHz).
16.35 * 8 => ~131Hz
/L
Yep – you were right.
Re-fused to 1.2 MHz- all good.
🙂
I’m having trouble getting this circuit to work properly . It produces a repeating series of “clicks” in the speaker instead of tones.
I am using WinAVR with avr-gcc to program the Attiny13a PU. I have set the fuses to l = 0x6a and h = 0xff using avrdude and verified them. Everything compiles without error and programs to the Attiny13a. I have tried a different mini speaker with the same result. It seems like the Attiny is running very slow, but I can’t figure out how to change it. I use an F_CPU = 1200000 in my Makefile.
Can you tell me how to troubleshoot this. Thank you
Hi, I guess it’s something wrong with CPU clock. Attiny13a runs with default clock F_CPU = 1200000. Mayby the chip is broken, or the chip is running with very slow clock – like 128kHz? From that point it happens to be not trivial to restore fuse bits.
/LP
Hi thank you for sharing this method to play tones in the tiny mcu. I used a buzzer instead of a piezo on my setup and found out the stop() method is not complete. For a piezo it is ok to stop the timer only since it has a very high impedance. However for a buzzer or inductive device (or if using the code to drive some other low impedance device, like an LED) only stopping the timer can leave the output high and running a steady current through the device because some combinations of the pwm waveform will stop on the high portion of the duty cycle. The device will get hot. So the stop() function should stop the timer and disconnect the OCxn output to the pin:
TCCR0A &= ~(1<<COM0A0); // disconnect PWM pin to Channel A of Timer0
This also requires that the tone() function reconnect the OCxn output to the pin on every call. Just add this above the TCCR0B line:
TCCR0A |= (1<<COM0A0); // connect PWM pin to Channel A of Timer0
Hi,
I’m sure your code lines will help other readers to handle this situation. The comments like yours are very helpfull. Thank you for sharing this!
/L
I really want to write this project using BASCOM AVR… could u help me?
you said “TCCR0A |= (1<<WGM01); // set timer mode to Fast PWM"
but its wrong, the only "WGM01" does not enable fast pwm, insted put timer in CTC mode,
hi! what compiler do you use? I can not compile in any way! produces an error on the line progmem! thx!
Hi! I’m using avr-gcc. Take a look here – http://blog.podkalicki.com/how-to-compile-and-burn-the-code-to-avr-chip-on-linuxmacosxwindows/
Hi, good job!
You can operate OC0B (PB1) in a push-pull manner to OC0A. This increases the volume. If you want to use a low resistance speaker, remove R1. AVRs have an internal current limit at about 40 mA. Because of the internal inductance of these speakers, the waveform is significantly more sinusoidal.
Kind Regards
Good hint. Thanks.
Nice project! Just wondering how you did the wavelength computation? Shouldn’t the wavelength of 440Hz be something around 681km (and the 78cm you computed rather map to a frequency of 382MHz)?
I was planning to run a dozen of these at a time, so for pitch stability, it’d probably be better to use an external XTAL. Do you have any experiences on this (i.e., do I have to find one at 1.2MHz)?
Hi, thanks! Computation is described in ATtiny13 manual. It’s good you pointed that missing thing. I added this info to a source code. All calculations has been prepared for ATtiny13 default internal clock source (1.2MHz)
F = F_CPU / (2 * N * (1 + OCRnx))
Where:
– F is a calculated PWM frequency
– F_CPU is a clock source (1.2MHz)
– the N variable represents the prescaler factor (1, 8, 64, 256, or 1024).
– the OCRnx is a counter limit.
If you need high precision, then I recommend to use other AVR chip, that accept external crystal input directly.
Unfortunately, AVRs internal oscillator has a low precision. What’s worst, ATtiny13 doesn’t accept a crystal input directly. The simplest approach to a stable external clock for that chip is to use a crystal clock generator IC. These are IC-sized metal boxes that put out a precision clock signal. They are available in a wide range of frequencies.