Program an ATtiny13 as an audio oscillator with variable frequency and pulse-width

I want to create a simple square wave oscillator similar to what one would do with a 555, but I want to use interrupt based PWM support to control pulse width and frequency.

I have been studying the datasheet, AVR APIs and whatever PWM examples I can find, but I haven't quite been able to put it all together.

Is it possible to create such an oscillator with the built in AVR PWM functionality and, if so, how? I friend of mine did something similar with an 8 pin PIC.

My reasoning is that I will get interesting sounds by changing the pulse-width and therefore the wave form at a given frequency. Similar to how the Atari Punk Console works but hopefully in a more stable way, i.e. changing the pulse width, but leaving the frequency constant or vise versa.

• could you please clarify what your question is? – Jason S Dec 29 '09 at 22:38
• I am not sure how interesting they will be but it is easy to try and you get to judge. As the pulse narrows the amplitudes of the harmonics increases. Keep narrowing the pulse and you get white noise. I think you would have more interesting options doing a numerically controlled oscillator. You can change the waveforms loaded into the wavetable. – jluciani Dec 30 '09 at 2:57

These should get you pretty far and the rest you can do with the datasheet. Start building piece by piece, from blinky to waveform to waveform that changes over time to tones. Some source might help with filtering and driving audio outputs (active LPF might do both neatly).

I suggest coming back with more specific questions.

The period of the PWM is determined by the rate of overflow of your timer. There are lots of settings on the Modes of Operation section to think about. If all you want to do is generate a constant period square wave, with variable duty cycle, I think you're going to want to use the CTC (Clear Timer on Compare Match) mode. Basic idea is to set OCR0A to the number of timer ticks until you want the pin to toggle next, and use the Compare Match interrupt to change that value for the next time around. So in avr-gcc it would look something like:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>

// global variables defining number of ticks on and off
uint8_t on_time_ticks, off_time_ticks, csxx_bits=0;

void setup_timer(double p_ms, double duty){
TCCR0A = _BV(COM0A0) // toggle OC0A on Compare Match
TCCR0B = _BV(WGM02); // set CTC mode WGM0[2,1,0] = 0b100

// ... do some stuff based on your CPU frequency
// to define the csxx_bits of TCCR0B when the timer is running
// and consequently, to set on_time_ticks and off_time_ticks
OCR0A = on_time_ticks;
TCCR0B |= your_settings_here;
}

void start_timer(){
//start the timer running at the desired rate
TCCR0B |= csxx_bits;
}

int main(int argc, char **argv){
double period_ms, duty_cycle;
setup_timer(period_ms, duty cycle);
start_timer();
for(;;){
//spin or sleep or whatever
}
}

ISR(TIM0_COMPA_vect){
if(OCR0A == on_time_ticks){
OCR0A = off_time_ticks;
}
else{
OCR0A = on_time_ticks;
}
}


Warning, this is untested code but I think the idea is right. By no means is this the only way to do it either.

There is one thing you should know about the ATTiny13, by the way. The internal RC Oscillator is only guaranteed to be accurate to within 10% off the factory floor. There's a user-calibration process you can go through (described by an atmel appnote) that will get you to 2% accuracy for the ATTiny13. If you want to do better than that you're probably going to need to use a chip that accomodates an external crystal...

• Your answer helped to clarify something similar which I was having problems with - thanks. Just a point (for anyone reading this long after the original thread): Your choice of 0b100 for WGM0[2,1,0] won't set CTC mode. (In fact it'll set a mode that's reserved by Atmel.) The ATtiny13 datasheet says CTC mode needs value 2; you've accidentally given it bit_number 2 instead (i.e. value 4). Because of that it's also necessary not only to change (i.e. clear) WGM02 in TCCR0B but also to set bits WGM01 and WGM00 to 1 and 0 respectively. Those bits are in TCCR0A, so it's not sufficient to set TCCR – lloydb Oct 10 '10 at 4:36

Not a direct answer to your question but this may be apropos and provide some hints --

I just created a numerically controlled oscillator (NCO) using an ATmega uC and a DAC. An array of integers is used to store one cycle of a waveform (wavetable). A phase accumulator (long int) is used to determine the address of the output data in the wavetable. Each timer interrupt increments the phase accumulator by a fixed value. The phase increment determines the frequency.

In my application I used a 64 byte wavetable that contained one cycle of a sinewave. It is easy to extend the wavetable and to add more resolution to the samples. My application note is at http://wiblocks.com/docs/app-notes/nb1a-nco.html

Basically a PWM doesn't change the frequency. PWM are mostly used to control the "intensity" of a signal.

To generate an interrupt for different frequencies I'd suggest that you use the timer in CTC mode.

It will run to your compare value, toggle an interrupt, clear and restart itself - until it runs to your compare value again...

Upon every interrupt you can switch one or more ports and the rest of the time (the timer still runs automatically) you may watch your inputs of any kind...

You may still "modulate" your square wave with a PWM to control the "gain". But one of them has to be done "by hand" because the ATtiny13 has only one hardware timer...

• most hardware PWM peripherals let you select frequency; if you keep duty cycle constant then PWM can still give you variable-frequency control – Jason S Dec 29 '09 at 23:29
• Changing the pulse width doesn't change the fundamental frequency but it does change the harmonics. – jluciani Dec 29 '09 at 23:40