So a bunch of blog posts back I posted how easy it was to get a square wave out of an arduino to do things with like generate your 600hz tone for CW etc. But we can go 1 further than that and use the arduino to generate sine waves. Of course you can filter the crap out of a 600hz square wave and make a sine, but to be honest, who really wants to wind inductors in the mH range or for that matter build a suitable LC filter to cut out the harmonics sufficiently to make a sine wave. But alas poor Yorick, there is a way and its called PWM.
So above we have a 31khz pwm signal that is being used to generate a sine wave. Through the wonders of mathematics and other nerd endeavours that PWM signal can be used to make sine waves, in my case a 600hz sine wave. Now obviously you still need to filter off the crap to see the sine wave, but its there and below is the filtered output. Now the good thing is you can filter with RC filters and make life simple. In my case i am using a 2 pole RC filter using 100nf caps and 2K resistors, and as you can see it looks rather sinusoidal.
So the following software is not mine, its something i found on the net and did some simple modifications to make work with an arduino UNO. Output is from PIN 11. Take a read, you should be able to work out how it works, using interrupts and timers to get some semblance of accuracy. Anyway, enjoy and have fun.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
/* * DDS Sine Generator mit ATMEGS 168 * Timer2 generates the 31250 KHz Clock Interrupt * * KHM 2009 / Martin Nawrath * Kunsthochschule fuer Medien Koeln * Academy of Media Arts Cologne */ #include "avr/pgmspace.h" // table of 256 sine values / one sine period / stored in flash memory PROGMEM const int sine256[] = { 127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240, 242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223, 221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78, 76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31, 33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124 }; #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #define OutputPin 11 int dfreq = 600; const double refclk=31376.6; // measured // variables used inside interrupt service declared as voilatile volatile byte icnt; // var inside interrupt volatile byte icnt1; // var inside interrupt volatile byte c4ms; // counter incremented all 4ms volatile unsigned long phaccu; // pahse accumulator volatile unsigned long tword_m; // dds tuning word m void setup() { pinMode(OutputPin, OUTPUT); // pin11= PWM output / frequency output Setup_timer2(); // disable interrupts to avoid timing distortion cbi (TIMSK0,TOIE0); // disable Timer0 !!! delay() is now not available sbi (TIMSK2,TOIE2); // enable Timer2 Interrupt tword_m=pow(2,32)*dfreq/refclk; // calulate DDS new tuning word } void loop() { } //****************************************************************** // timer2 setup // set prscaler to 1, PWM mode to phase correct PWM, 16000000/510 = 31372.55 Hz clock void Setup_timer2() { // Timer2 Clock Prescaler to : 1 sbi (TCCR2B, CS20); cbi (TCCR2B, CS21); cbi (TCCR2B, CS22); // Timer2 PWM Mode set to Phase Correct PWM cbi (TCCR2A, COM2A0); // clear Compare Match sbi (TCCR2A, COM2A1); sbi (TCCR2A, WGM20); // Mode 1 / Phase Correct PWM cbi (TCCR2A, WGM21); cbi (TCCR2B, WGM22); } //****************************************************************** // Timer2 Interrupt Service at 31372,550 KHz = 32uSec // this is the timebase REFCLOCK for the DDS generator // FOUT = (M (REFCLK)) / (2 exp 32) // runtime : 8 microseconds ( inclusive push and pop) ISR(TIMER2_OVF_vect) { sbi(PORTD,7); // Test / set PORTD,7 high to observe timing with a oscope phaccu=phaccu+tword_m; // soft DDS, phase accu with 32 bits icnt=phaccu >> 24; // use upper 8 bits for phase accu as frequency information // read value fron ROM sine table and send to PWM DAC OCR2A=pgm_read_byte_near(sine256 + icnt); if(icnt1++ == 125) { // increment variable c4ms all 4 milliseconds c4ms++; icnt1=0; } cbi(PORTD,7); // reset PORTD,7 } |
So I have cleaned up the original code to make it easier to read and to remove the stuff in there that was really doing nothing. And here is a link back to the original article. http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-dds-sinewave-generator/





Hello Mr. Powell,
I would be glad if you could answer a few questions about your project, because I wanted to test it on my Arduino once.
1.)
To save myself the manual entry of 255 sine values into an array, I tried to do this with the following code:
———————————————————————————————–#include
#include
int sinusArray[] = {};
for (int i = 0; i < 255; i++)
{
const int sinusArray[i] PROGMEM = {sin((2*PI*i)/255)};
}
———————————————————————————————–
As you expect, this does not work for several reasons:
– You can not change const variables, but variables in flash memory must be const type.
– You can not release or write to flash memory from a running program, but a for loop can only be used in a running program.
What are the advantages of saving the sine values in flash memory and not in the RAM or EEPROM ?
Do you know a way to realize what I wanted to do, or the manual entry is the only possible way ?
2.)
In the comments you make some calculations that I can not understand:
In line 26, you name a "measured" PWM-clock of 31376.6 Hz, but how did you measure it?
In line 69, you give a calculated value of 31472.55 Hz for the same measure, I assume you calculated it with the formula f (PWM) = f (clk) / (N * 510) from the Atmega8 user manual.
I have read the whole section in the user manual about the phase correct PWM mode, but there is no reference to the value 510 or where it came from. How does this value of 510 come about?
In line 72 you call a runtime of 8 milliseconds. Where did you get this value from?
I did not find anything about runtime computation in the User Guide.
3.)
From line 82 to 85, use the following code:
———————————————————————————————–
if(icnt1++ == 125) { // increment variable c4ms all 4 milliseconds
c4ms++;
icnt1=0;
}
———————————————————————————————–
I do not quite understand what this code is for.
At first I thought it was needed to reset the timer or interrupt, but I actually thought that these would be reset automatically after reaching the count value in the timer register, since the timer is in overflow mode.
Furthermore, I do not understand how you have calculated the values used (125 and 4 milliseconds).
Is the 4 milliseconds halfway through 8 milliseconds?
Are the 125 almost half the cycle time for the array with the 254 sine values?
Furthermore, I am irritated that the varialbe "icnt1" appears here only once if it is incremented and set to 0, but nowhere else in the program code.
I apologize for the long text, it would help me if you answered only one of the questions.
Sincerely,
Peter Bears
#include
#include
int sinusArray[] = {};
for (int i = 0; i < 255; i++) { int sinusArray[i] = {sin((2*PI*i)/255)}; }
Take out the const and remove it from progmem and it will compile and run. But you are doing math computations very tick almost and it will put load on the processor. Its easier to just lookup a value from a table and use it.