Go Down

Topic: Arduino DDS Sinewave Generator (Read 68813 times) previous topic - next topic

nernoe

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


eightbitguy

#61
Sep 20, 2016, 05:17 am Last Edit: Sep 20, 2016, 11:45 pm by eightbitguy
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: http://www.openmusiclabs.com/learning/digital/synchronizing-timers/ ). 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?


eightbitguy

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.


AWOL

Code: [Select]
const uint64_t twoTo32 = pow(2, 32); // compute value at startup and use as constant
Isn't the following more intuitive ?
Code: [Select]
const uint64_t twoTo32 = 0x100000000ULL;

eightbitguy

#64
Sep 21, 2016, 08:53 pm Last Edit: Sep 21, 2016, 08:58 pm by eightbitguy
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.

eightbitguy

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

eightbitguy

#66
Sep 27, 2016, 11:36 pm Last Edit: Sep 28, 2016, 04:43 pm by eightbitguy Reason: Bug fix: sinusoid is 512 bytes not 256 therefore my offsets for 120 degrees was
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...



Rabobsel

#67
Nov 22, 2018, 04:02 pm Last Edit: Nov 22, 2018, 10:02 pm by 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.
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((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 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

MarkT

#68
Nov 22, 2018, 07:44 pm Last Edit: Nov 22, 2018, 07:45 pm by MarkT
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...
Quote
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((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.
Indeed - you need to use an array initializer with precomputed values to set up PROGMEM
Quote
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
Quote
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.
Quote
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.
Quote
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, ...
Quote
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
Quote
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.
Quote
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?
Quote
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.
Quote
I apologize for the long text and thank you, if you have read this far.

Sincerely,
Rabobsel
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

bulyshor

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

MarkT

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.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

RFEngineer

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

MarkT

Yes, just code the amplitude changes needed.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

Go Up