Edit: Here are some comments on the updated version of your code.
The expression 1024 * 64 * 65536 / 62500
overflows on both
multiplications. I suggest computing this as a float and assigning then
to an integer.
The line
TIFR2 |= bit(TOV2); // clear the interrupt
is only useful when you poll the interrupt flag with interrupts disabled. As you are now using the interrupt, you do not need this: the flag is automatically cleared when the ISR starts executing.
If you want to toggle an output pin, you can do
PIND |= bit(4);
which is a single machine instruction and more efficient than
PORTD ^= bit(4);
(a read-modify-write sequence). Then, for monitoring
the execution of the ISR on the scope, I would rather set and clear the
pin on every ISR execution.
I tested your program with the ISR modified as this:
ISR(TIMER2_OVF_vect) {
static uint32_t wave;
const uint32_t incr = 1.0 * 64 * 256 * 65536 / F_CPU;
PORTD |= bit(4);
wave += incr;
OCR2B = sinLUT[(wave >> 16) & 0x3f];
PORTD &= ~bit(4);
}
Note that I lowered the frequency to 1 Hz, in order to better see the behavior on the scope. The output looks really clean to me. There are some glitches on the pin 4 pulses, which occasionally are late by about 6.2 μs. This is due to the Timer 0 overflow interrupt, which is used by the Arduino core for timekeeping. If you don't need the Arduino timekeeping functions, you can avoid these glitches by disabling that interrupt. However, since the ISR is never late by more than one PWM cycle, the glitches should have no effect on the PWM signal generation.
Edit: Here are some comments on the updated version of your code.
The expression 1024 * 64 * 65536 / 62500
overflows on both
multiplications. I suggest computing this as a float and assigning then
to an integer.
The line
TIFR2 |= bit(TOV2); // clear the interrupt
is only useful when you poll the interrupt flag with interrupts disabled. As you are now using the interrupt, you do not need this: the flag is automatically cleared when the ISR starts executing.
If you want to toggle an output pin, you can do
PIND |= bit(4);
which is a single machine instruction and more efficient than
PORTD ^= bit(4);
(a read-modify-write sequence). Then, for monitoring
the execution of the ISR on the scope, I would rather set and clear the
pin on every ISR execution.
I tested your program with the ISR modified as this:
ISR(TIMER2_OVF_vect) {
static uint32_t wave;
const uint32_t incr = 1.0 * 64 * 256 * 65536 / F_CPU;
PORTD |= bit(4);
wave += incr;
OCR2B = sinLUT[(wave >> 16) & 0x3f];
PORTD &= ~bit(4);
}
Note that I lowered the frequency to 1 Hz, in order to better see the behavior on the scope. The output looks really clean to me. There are some glitches on the pin 4 pulses, which occasionally are late by about 6.2 μs. This is due to the Timer 0 overflow interrupt, which is used by the Arduino core for timekeeping. If you don't need the Arduino timekeeping functions, you can avoid these glitches by disabling that interrupt. However, since the ISR is never late by more than one PWM cycle, the glitches should have no effect on the PWM signal generation.
There are quite a few issues here.
First, you have to decide what channel you are going to use. You wrote that pin 11 is the B channel of Timer 2, but this is incorrect:
- pin 11 = PB3 = OC2A = A channel of Timer 2
- pin 3 = PD3 = OC2B = B channel of Timer 2
Next, you have to set the pin as output:
// either:
DDRB |= bit(PB3); // set pin 11 = PB3 = OC2A as output
// or:
DDRD |= bit(PD3); // set pin 3 = PD3 = OC2B as output
For enabling non-inverting PWM, you have to set either bit COM2A1
or
COM2B1
, depending on the channel you want. There is no reason to touch
the bit COM2A0
, which is for for the "toggle on compare match" mode
(not what you want).
Regarding the timer mode, you have chosen mode 7, (fast PWM with
TOP = OCR2A
). The ability to set the TOP value is meant to enable
frequency modulation, at the cost of loosing PWM capability on
channel A. Again, not what you want. Instead, you want the timer to
count all the way from 0x00
to 0xff
, which is what mode 3 gives you.
Then, there is the code that waits for the compare match in order to
update the PWM value at the right time. You are using the wrong bit:
OCIE2B
is an "interrupt enable" bit, which is used to enable the
corresponding interrupt. If you enable the interrupt and have not
defined a suitable interrupt service routine, then your program will
crash (restart). You do not need interrupts here. Instead, you can
simply test the "interrupt flag" OCF2A
or OCF2B
(depending on the
channel) from register TIFR2
.
There could be a race here: if the PWM value is very high, the interrupt
flag will raise just before the start of the next PWM cycle, and you may
end up updating the PWM value a bit too late. I would recommend instead
monitoring the TOV2
(overflow) interrupt flag, which raises at the
very beginning of a PWM cycle. Then you have a full cycle to update the
PWM value.