I am well familiar with PWM generation in ATmega128 and its family microcontrollers. I have been using a prescaler and other registers for generating frequency. But I have to generate 20 kHz PWM signal. I tried, but I could not get the desired output. How can I do it?
As far as I know, in ATmega128, one instruction takes one cycle. Using a 16 MHz crystal, one instruction completes in 1/16M seconds.
I tried to generate 20 kHz signal (50 µs) with a 25 µs duty cycle. But I get a different frequency (277.78 Hz) on the oscilloscope, which is far less than 20 kHz.
My calculation was
16 MHz = 20000 Hz * 800.
for a 0-399 count. I made the port high. And a 399-799 count, and I made port low.
void frequency(void) { // 20 kHz frequency
if (cnt1 <= 399) {
PORTB |= (1<<7);
} else {
PORTB &= ~(1<<7);
}
cnt1++;
if (cnt1 >= 800)
cnt1 = 0;
}
I don't have access to the ATmega128, but I verified its 16-bit Timer 1 is similar to that in the ATmega328 and ATmega32U4, so the following should work with minor modification (the main sticking point is probably looking up what pin the overflow register is bound to):
#include <avr/io.h>
#include <util/delay.h>
struct CTC1
{
static void setup()
{
// CTC mode with TOP-OCR1A
TCCR1A = 0;
TCCR1B = _BV(WGM12);
// Toggle channel A on compare match
TCCR1A = (TCCR1A & ~(_BV(COM1A1) | _BV(COM1A0))) | _BV(COM1A0);
// Set channel A bound pin PB1 to output mode
#if defined(__AVR_ATmega32U4__)
DDRB |= _BV(5);
#else
DDRB |= _BV(1);
#endif
}
static void set_freq(float f)
{
static const float f1 = min_freq(1), f8 = min_freq(8), f64 = min_freq(64), f256 = min_freq(256);
uint16_t n;
if (f >= f1) n = 1;
else if (f >= f8) n = 8;
else if (f >= f64) n = 64;
else if (f >= f256) n = 256;
else n = 1024;
prescale(n);
OCR1A = static_cast<uint16_t>(round(F_CPU / (2 * n * f) - 1));
}
static void prescale(uint16_t n)
{
uint8_t bits = 0;
switch (n)
{
case 1: bits = _BV(CS10); break;
case 8: bits = _BV(CS11); break;
case 64: bits = _BV(CS11) | _BV(CS10); break;
case 256: bits = _BV(CS12); break;
case 1024: bits = _BV(CS12) | _BV(CS10); break;
default: bits = 0;
}
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11) | _BV(CS10))) | bits;
}
static inline float min_freq(uint16_t n)
{
return ceil(F_CPU / (2 * n * 65536));
}
};
void setup()
{
CTC1::setup();
CTC1::set_freq(20e3);
}
void loop()
{
// Do whatever
_delay_ms(1);
}
int main()
{
setup();
for (;;)
loop();
}
I tested on my scope and measure exactly 20 kHz off a ATmega328P running at 16 MHz. If 20 kHz is the only frequency you need then you can simplify this substantially. Translating the code to use one of the 8-bit timers is also straightforward though I haven't verified that it's possible to hit exactly 20 kHz with those.