I'm generating a sine wave using a table, a PWM pin and an RC filter on the output. I would like to be able to control the frequency of the sine wave, how can I do that easily?
By the way, which is the maximum frequency achievable and why? As far as my understanding goes, the limit is given by the time arduino needs in order to run the loop() function.
This is the code I am using on arduino UNO. Please point it out if this is a horrible way to generate a sine wave with arduino, and if it is, feel free to provide a better solution.
const int8_t SIN1024_DATA [] = {... data table values (1024 values) ...};
int i = 0;
void setup()
{
pinMode(9, OUTPUT);
}
void loop()
{
digitalWrite(9, SIN1024_DATA[i]);
i++;
if(i == 1024){i = 0;}
}
1 Answer 1
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 library.
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 = ...;
uint8_t frequency;
// Call periodically.
static inline void output_a_sample()
{
static uint16_t phase;
analogWrite(pin, SIN1024_DATA[phase>>6]);
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.
while(true){ ... }
. Making it a few cycles faster. Secondly, don't use digitalWrite, as it's quite slow. Either use direct port manipulation, or use something like digitalWriteFast.