First thing, you must find a way to output the samples at a steady rate.
Your best bet is to use a timer. Ideally, you want to use the same timer
used for the PWM output, in order to have them synchronized. See for
example how it is done in the Mozzi sound synthesis
Then you can lower the frequency by repeatedly outputting the same
value: if you output every value once, you get the “base” frequency
(sample frequency ÷ 1024). If you output each value n times you get
the base frequency divided by n. Conversely, you can get higher
frequencies by skipping samples. For example, if you output only every
other sample, you get twice the base frequency.
These two approaches can be conveniently combined in a very simple code:
you keep a
phase variable and, on every sample, you increment it by an
amount proportional to the desired frequency. You then use the higher
bits of the phase as an index into your array. The lower bits then serve
only to increase the frequency resolution and allow frequencies lower
than the base one. Here is a simple implementation:
const uint8_t pin = ...;
// Call periodically.
static inline void output_a_sample()
static uint16_t phase;
phase += frequency;
This lets you set the frequency in units of the sampling frequency ÷
216, i.e. the base frequency ÷ 26.
Note that the phase here is 16-bits: 10 bits for the array index and 6
bits for the fractional part. Since all the bits are used, you do not
need to handle the rollover: a
uint16_t automatically rolls over
modulo 216, which is just what you want here. You can use a
32-bit variable if you need more frequency resolution.