Arduino DDS Sinewave Generator

See reply #53

I'd do this in fast hardware - FPGA's - brilliant widgets.
program in verilog or VHDL

not trivial.

not really an arduino job at high speed.

regards

Allan.

ps done it up to 100MHz for RF

Hi ,

I have a similar problem as you do the 90degree phase shifted second output.
I want a second output , with continously variable phase shifting (with an second poti).

I'm a beginner, can you help code ?

Thank You
nernoe

nernoe@gmail.com

Someone else was looking at a 3 phase AC motor controller. I am on the learning curve and there are MANY details in 3 phase AC motor control that I am just starting to understand. I think you get the best efficiency if you measure currents in the windings and do field oriented control. Second choice seems to be using a rotational position sensor and put the motor on a dynamometer to map out the phase needed to drive the motor at peak efficiency at different frequencies. Third choice is a constant V/f (Voltage over frequency) drive. There are tons of details to get those working so I won't go into that here.

So.... I was wondering if option Three (V/F) mode could be coded up and if an Arduino could be fast enough. I have NOT developed the high voltage drivers but I think the following code, distilled from various sources may be a starting point. It does not control V/F=Constant and only puts out a AC voltage that is centered around V/2 or 5V/2=2.5V with an amplitude of 2.5VAC. I think eventually, I will need SIX PWM circuits with two required for each phase: one to generate a POSITIVE drive and a separate PWM to drive the NEGATIVE side of a MOSFET/IGBT pair. Some hardware to prevent "Shoot-through" will be required and some of the dedicated three phase driver chips may be an option. It's probably best the keep everything digital all the way to the MOSFET drivers.

So, back to the Aurduino side. The following code seems to work. I set the frequency to a very 0.5Hz, triggered my scope on the TEST_BIT (pin 7) and watched the PWM period grow and shrink absolutely symmetrically around a point a few microseconds before the TEST_BIT went high. The three phases so this sequentially. For now, I am just using THREE PWM channel with TWO Timers. Importantly, the two timers are synchronized (See this page about synchronizing the timers: ATmega Timer Syncrhonization | Open Music Labs ). Please see the comments in the code for credit where credit is due. I did little to the code but a few tweaks were needed to sync the timers and make sure the interrupt routine completed before being interrupted again. Floating point math was a big NO-NO!

I ran it on an Duemilenove. Some thoughts: The interrupt rate is about 31.372kHz. and the maximum update rate is two samples of the sinusoid so the maximum frequency will be something like 16kHz. How close you get to 16kHz will depend on filtering you do on the PWM. However, Nyquist is a limit and it would be easier to run much lower in frequency. Consider that a 12000RPM motor is running at 12000RPM/60seconds or 200Hz. So I think we have more than enough sampling on the high end. We have 24 bits below the actual value passed to the lookup table. Any changes below the bits passed to the sin look-up table will result in the same values being output by the PWM. The lowest frequency without repeating points in the sine table will be 31.372kHz/256 (for a 256 point lookup table) or 122Hz. You will get quantization noise below 122Hz but that's probably okay. How clean a spectrum do you need to drive an AC inductive motor? It's a huge inductor so getting high frequencies into it will be hard. It's a natural low pass filter.

I added 4 oscilloscope plots. The interrupt service routine uses 5.691 us but there is some overhead (pushing stuff on the stack before and popping it back after the interrupt) adds another 3-4 usec. The green trace is high while the interrupt vector is executing the code. The yellow, blue and magenta traces show nice synced PWM pulses and vary around the center of the display as the Phase-correct PWM should.

Next Steps:
0) I want to drive some LEDs to visualize the output and add a potentiometer as a throttle.

  1. Add V/F amplitude control. This will depend on the motor specs so perhaps getting the high voltage/current section will higher on this list.
  2. Driving the High and Low Side with PWM: Two options. Maybe I add sign bit and use some AND gates to control either the high or low side but not both. Or I double the number of PWM channels to SIX and drive each phase with a separate but SYNCED PWM. However, this relies on software to prevent shoot-through by making sure both the high and low sides are NOT simultaneously, on!
  3. Interface to a MOSFET driver.
  4. Correct me if I'm wrong: The PWM generates a desired voltage when we really want to control current. Some feedback from current sensors is going to be needed to make sure we get a current proportional to this voltage. Then, is the Arduino going to be fast enough to do all the desired/ required signal processing?

ThreePhaseDDS1.ino (7.29 KB)

TEK00000.PNG

TEK00001.PNG

TEK00002.PNG

TEK00003.PNG

V/f control added.

You need to find the voltage and the frequency cutoff to use. The voltage will vary linearly from ZERO at zero frequency to full amplitude (100% city cycle PWM) at the cutoff frequency and above.

ThreePhase_V-f_DDS.ino (9.53 KB)

const uint64_t twoTo32 = pow(2, 32); // compute value at startup and use as constant

Isn't the following more intuitive ? const uint64_t twoTo32 = 0x100000000ULL;

Yup! Thanks.

Just ordered an IR2130 and some opto couplers from DigiKey!

I have a motor I want to see spin! Gotta work up to the appropriate voltages but with no load, I may be able to do this with 20-30 Volts.

I think I understand the code in loop() that disables interrupts. I believe the correct thing to do is the following:

I think resetting the TIMSK2 bit, TOIE2 will prevent an interrupt from occurring. What I actually want is for the interrupt to occur but let it be queued until the ISR() has completed. Therefore, I want to use noInterrupts() and interrupts() rather than the cbi() and sbi() to turn off the interrupts. It's a subtle but important issue:

If an interrupt is missed, the phase of the sinusoid will be delayed and the error is cumulative. The spectral purity of the sinusoid will be affected. If an interrupt occurs during the writes to the variables, the ISR may see invalid or incomplete values which will affect a single but whole PWM period of 32usec.

I still need to check if this is a problem...

I may not have measured this correctly but I measured on the scope that the Amplitude calculation took 12.62us and the tword_m calculation took 41.52us. this means that the interrupt could be delayed by as much as 54.14usec PLUS the overhead of the noInterrupt() and interrupt() calls.

Maybe Arduino is just not fast enough to run at 32us. Perhaps I need to switch to a pre-scale of 8...

void loop()
{
//       cbi (TIMSK2, TOIE2); // disble Timer2 Interrupt
         noInterrupts();      // Does this cause interrupts to be queued?
// These two variable ARE used by the ISR AND must be matched to each other
// therefore I am protecting them from interrupts during this phase of the code.  
         Amplitude = 256 * dAmplitude;        // This takes 12.62usec
         tword_m = twoTo32 * dfreq / refclk; // calculate new DDS tuning word  --> Takes 41.52usec
         interrupts();
//       sbi (TIMSK2, TOIE2); // enable Timer2 Interrupt

Hi All,

Okay, I now have it working as THREE phase sinusoidal DDS. I have a high and a low output for each sinusoidal channel. Let's call the three phases U, V, and W.

Pin # Channel
6 U-high
5 U-low

9 V-high
10 V-low

11 W-high
3 W-low

Trigger your scope on Pin 7
If you are debugging the interrupt routines pin 7 is high while you are in the interrupt routine (after all the stack pushes).

Use pin 13 (LED pin) to see what's happening in the loop() but you will need to uncomment the digitalWrite() statements surrounding the noInterrupts() and interrupts() calls.

The Interrupt service routine runs in just under the period of the interrupt of 32us. Any longer and we will be in trouble. The loop() section of the sketch disables interrupts and it too runs in under 32usec. However, this only happens once per second. If either of these two sections gets longer than 32us interrupt period, we will miss interrupts. I'm watching the output on a scope and haven't seen any glitches but your mileage may vary. Glitches may destroy your MOSFET or drivers so be careful. I haven't built that part of the circuit yet.

Hopefully I have commented the code sufficiently that you can figure out what I'm trying to do...

ThreePhaseBipolar_V-f_DDS2.ino (13.9 KB)

Hi, I'm an interested hobby electronics just learning Arduino, but I'm not very well versed in C-programming.
I would be glad if you could answer a few questions about the project that can be found under the link in the first post Arduino DDS Sinewave Generator by Martin Nawrath.
The program runs flawlessly on my arduino uno, but despite reading some passages in the user manual of the Atmega8 and tormenting google, I do not understand some parts of the program code.

1.)
To save myself the manual entry of 255 sine values into an array, I tried to do this with the following code:

#include <math.h>
#include <avr/pgmspace.h>

int sinusArray[] = {};

for (int i = 0; i < 255; i++)
{
const int sinusArray PROGMEM = {sin((2PIi)/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 the author makes some calculations that I can not understand:
    In line 33, the author names a "measured" PWM-clock of 31376.6 Hz, but how did he measure it?
    In line 114, the author gives a calculated value of 31472.55 Hz for the same measure, I assume he 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 117 the author calls a runtime of 8 milliseconds. Where did he get this value from?
    I did not find anything about runtime computation in the User Guide.
    3.)
    In line 73, the author uses an infinite loop (while (1) {}) in the "voidLoop () {}" method. I'm surprised, because I thought so far that the "voidLoop () {}" method itself is already an endless loop.
    4.)
    In lines 123 and 127 the author shifts the value of the variable "phacculow" and "phaccuhigh" by 24 digits to the right. I do not understand why he is doing this, because the variable gets a completely different value then.
    5.)
    From line 131 to 134, the author uses the following code:
    -----------------------------------------------------------------------------------------------
    if(icnt1++ == 125) { // increment variable c4ms all 4 milliseconds
    c4ms++;
    icnt1=0;
    }
    -----------------------------------------------------------------------------------------------
    I think I understand why this code exists, but I do not understand how the author comes to the values used. (125 and 4 milliseconds).
    Is the 4 milliseconds halfway through 8 milliseconds runtime from the prvious calculation and so rests half a period ?
    Are the 125 almost half the cycle time for the array with the 254 sine values so it resets after an half-sine-wave?
    I apologize for the long text and thank you, if you have read this far.
    Sincerely,
    Rabobsel

Rabobsel:
Hi, I'm an interested hobby electronics just learning Arduino, but I'm not very well versed in C-programming.
I would be glad if you could answer a few questions about the project that can be found under the link in the first post "Arduino DDS Sinewave Generator by Martin Nawrath".

Can you please quote the link here, rather than expect people to search for it... I found a slightly
different copy with different line numbers in places...

The program runs flawlessly on my arduino uno, but despite reading some passages in the user manual of the Atmega8 and tormenting google, I do not understand some parts of the program code.

1.)
To save myself the manual entry of 255 sine values into an array, I tried to do this with the following code:

#include <math.h>
#include <avr/pgmspace.h>

int sinusArray[] = {};

for (int i = 0; i < 255; i++)
{
const int sinusArray PROGMEM = {sin((2PIi)/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.
    [/quote]
    Indeed - you need to use an array initializer with precomputed values to set up PROGMEM
    > What are the advantages of saving the sine values in flash memory and not in the RAM or EEPROM ?
    Far more memory available in flash, Uno has 32k flash, 1k EEprom, 2k SRAM IIRC
    > Do you know a way to realize what I wanted to do, or the manual entry is the only possible way ?
    What manual entry? Just stick the data in a generated header file as is normally done
    for big tables.
    > 2.)
    > In the comments the author makes some calculations that I can not understand:
    >
    > In line 33, the author names a "measured" PWM-clock of 31376.6 Hz, but how did he measure it?
    With a frequency meter I guess, or something with one built in like a 'scope.
    > In line 114, the author gives a calculated value of 31472.55 Hz for the same measure, I assume he 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 phase correct mode the timer counts up then down 0, 1, 2, 3, ...., 0xFE, 0xFF, 0xFE, ..., 2, 1, 0, 1, ...
    > In line 117 the author calls a runtime of 8 milliseconds. Where did he get this value from?
    > I did not find anything about runtime computation in the User Guide.
    Misprint, obviously meant to be 8µs
    > 3.)
    > In line 73, the author uses an infinite loop (while (1) {}) in the "voidLoop () {}" method. I'm surprised, because I thought so far that the "voidLoop () {}" method itself is already an endless loop.
    Can only speculate that's to avoid the time penalty of all the serial checking that happens in the loop that
    calls loop(), but yes it seems pointless.
    > 4.)
    > In lines 123 and 127 the author shifts the value of the variable "phacculow" and "phaccuhigh" by 24 digits to the right. I do not understand why he is doing this, because the variable gets a completely different value then.
    You think a 2^32 entry sine table is practical?
    > 5.)
    > From line 131 to 134, the author uses the following code:
    > -----------------------------------------------------------------------------------------------
    > if(icnt1++ == 125) { // increment variable c4ms all 4 milliseconds
    > c4ms++;
    > icnt1=0;
    > }
    > -----------------------------------------------------------------------------------------------
    > I think I understand why this code exists, but I do not understand how the author comes to the values used. (125 and 4 milliseconds).
    > Is the 4 milliseconds halfway through 8 milliseconds runtime from the prvious calculation and so rests half a period ?
    > Are the 125 almost half the cycle time for the array with the 254 sine values so it resets after an half-sine-wave?
    4ms is 125 cycles of the PWM waveform (approximately). This is so c4ms variable can be used for timing in loop(). The normal timer0 interrupt is disabled to prevent jitter in running the timer2 ISR.
    > I apologize for the long text and thank you, if you have read this far.
    >
    > Sincerely,
    > Rabobsel

Can someone explian to me from it comes 4ms for c4ms, 250 in void loop and 125 in ISR?

The timer is in phase-correct mode, so counts up and down, cycling every 510 system clocks.
At 16MHz system clock thats a cycle rate of 31.37kHz approx, divide that by 125 and you get
251Hz roughly, ie a period of 4ms.

Is there a means to ramp the resulting output waveform? After low-pass filtering, I’m looking for a sinewave that when switched on/off has a 5 ms rise and tail time. Possible?

Paul

Yes, just code the amplitude changes needed.

Hi
I am a total newbe in arduino world, only compiled one program, made by someone else.
Now I would like to make a DSD sine generator as described here: http://interface.khm.de/index.php/lab/interfaces-advanced/arduino-dds-sinewave-generator/

I have tried to make these changes:
Change
PROGMEM prog_uchar sine256[] = {
to
PROGMEM byte sine256[] = { ...or
PROGMEM char sine256[] = {

and even this:
PROGMEM prog_uchar sine256[] = {

but I still get an error message when trying to compile it:
Arduino: 1.6.10 (Windows 10), Board: "Arduino Nano, ATmega328"

DSDsinegenerator1:16: error: expected initializer before numeric constant

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,

^

DSDsinegenerator1:22: error: expected declaration before '}' token

};

^

exit status 1
expected initializer before numeric constant

What am I doing wrong?

koldby:
What am I doing wrong?

You missed-out "const" - check the documentation

I think I solved the issue.

the line:

PROGMEM prog_uchar sine256[] = {

Should be:

PROGMEM prog_uchar sine256[] =
{

Now it compiles without errors.
Now I have problems uploading to the Nano board:
Arduino: 1.6.10 (Windows 10), Board: "Arduino Nano, ATmega328"

Sketch uses 4.096 bytes (13%) of program storage space. Maximum is 30.720 bytes.
Global variables use 227 bytes (11%) of dynamic memory, leaving 1.821 bytes for local variables. Maximum is 2.048 bytes.
An error occurred while uploading the sketch
An error occurred while uploading the sketch
Exception in thread "Thread-45" java.util.ConcurrentModificationException
at java.util.LinkedList$LLSpliterator.forEachRemaining(LinkedList.java:1239)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at cc.arduino.contributions.libraries.LibrariesIndexer.rescanLibraries(LibrariesIndexer.java:123)
at processing.app.BaseNoGui.onBoardOrPortChange(BaseNoGui.java:838)
at processing.app.Base.onBoardOrPortChange(Base.java:1247)
at processing.app.Editor$DefaultExportHandler.run(Editor.java:2439)
at java.lang.Thread.run(Thread.java:745)

I have no idea what this means???

By the way, how can I get an e-mail notification, when there are responses to this topic?

I guess I also solved that myself.
Changed the com port from com1 to com5 and now it uploads...
Thanks

:wink:

Got everything up and working. I am going to use this as sine generator for a Lenco L75 gramophone motor to set the speed precisely and change speed between 33,3 45 and 78 RPM.
So I need a frequency range of about 45 Hz to 150 Hz.
I have observed, that even if I use a 10x potmeter, the sine generator does not have a stable output frequency when set to 50 Hz. I guess it is because the frequency range is too big for precise and stable operation in the lower part of the spectrum.
I have been looking at the code to see if I could find where the frequency range is set, but without luck so far.

Can someone help me out on this pls?