Generating Sine Waves
Who thought this would be hard?
I have spent a week trying to work this out. Here is the pseudo code so far:
Calculate the Mark and Space ratios:
- A= int(Fsample/Fmark+0.5)
- B= int(Fsample/Fpace+0.5)
Find the lowest common multiple N of A and B:
- N = LCM(A,B)
Where N is the sine (iSin[]) lookup array size.
To generated the sine wave step through the array using Phase:
- Phase=Phase +N/A, for the mark frequency
Note: B=N/A
So:
- Phase=Phase +B
or
- Phase=Phase +A, for the space frequency
The Phase needs to roll back to the start on overflow:
- if (Phase>=N) then Phase=Phase-N
The sine wave is:
- Sine=iSin[Phase]
To reduce the sine look up array size by a constant (C) we can divide the Phase by the constant (C):
- Sine=iSin[Phase/C]
or
- Sine=iSin[Phase>>M], as the maths is much faster
The sample frequency is restricted and depends on the Arduino clock (16 MHz), the pre-scaler (timer dependent) and the PWM type.
So I have spent a day trying different combinations of Fsample and M to minimise the array size and the error. Nothing great found!
---
I thinks I need help (at least my partner thinks so)!
I found a paper (http://www.analog.com/static/imported-files/tutorials/MT-085.pdf) that although a bit complicated, shows a way out of this mess:
Set N to a power of 2, say 65536 (an unsigned int):
- B=65536*Fmark/Fsample
- A=65536*Fspace/Fsample
Then phase is:
- Phase=Phase+B
or
- Phase=Phase+A
Choose a sine array size that is a power of 2 , say 64.
- Sine=iSin[Phase>>(16-6)]
or
- Sine=iSin[Phase>>10]
Phase rollover is implicit with an unsigned int.
Here is the code for the ISR:
// TRANSMIT:
// Phase Correct PWM frequency 31250 Hz
// Originate modem Mark frequency 1270 Hz (=2244)
// Originate modem Space frequency 1070 Hz (=2663)
// Answer modem Mark frequency 2225 Hz (=4666)
// Answer modem Space frequency 2025 Hz (=4247)
const byte iSin[64] = {
128,140,153,165,177,188,199,209,
218,226,234,240,245,250,253,254,
255,254,253,250,245,240,234,226,
218,209,199,188,177,165,153,140,
128,116,103, 91, 79, 68, 57, 47,
38, 30, 22, 16, 11, 6, 3, 2,
1, 2, 3, 6, 11, 16, 22, 30,
38, 47, 57, 68, 79, 91,103,116
};
volatile byte DataOut=0;
ISR(TIMER2_OVF_vect) {
static unsigned int phase=0;
OCR2A = iSin[(phase>>10)]; // Output on D11
if (true) { // Set true for Originate Modem
if (DataOut==0) {
phase+=2244; // Fspace (Originate modem)
} else {
phase+=2663; // Fmark (Originate modem)
}
} else {
if (DataOut==0) {
phase+=4247; // Fspace (Answer modem)
} else {
phase+=4666; // Fmark (Answer modem)
}
}
}
Now is that not sweet code!
---
I used Phase Correct PWM instead of Fast PWM as it generates less interrupts rather than the phase correct feature.
Final note, don't put the iSin array inside the ISR like I did! It took ages to workout why the PWM was off frequency!
AlanX
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.