DAC to Generate six Sine waves (individual analog O/p) with Arduino Uno

I am looking for some help/guidance to Generate six Sine waves (individual analog O/p) for one of my project

I want to generate six independent sine waves of 50Hz, 1 or 2V p-p (max) and that can be fully controlled by user input through serial port in terms of
it's amplitude and phase angle individually interfaced with my Arduino Uno board.

Specifications:

output Signal : pure sine wave output required continuously
o/p Voltage amplitude (pk-pk) : 1V or 2V max
signal Freq. : 50Hz
phase shift : 0 to 360 deg.

above parameters can be set independently at each channel

For instance.
ch1 = 0.7V having 0deg
ch2 = 1.5V having 30deg w.r.t. ch1
ch3 = 0.3V having 60deg w.r.t. ch1
ch4 = 1.0V having 240deg w.r.t. ch1
ch5 = 0.9V having 180deg w.r.t. ch1
ch6 = 0.6V having 90deg w.r.t. ch1

it is not mandatory that these 6 channels shall be on single board, we can use 2 DAC boards (4ch + 2ch) to make it 6 channel output

so for that I need support in feasibility and possible option which can fulfill above requirement.
Also it will be well appreciated if some codding idea can be provided.

i also reviewed some Arduino lib for MCP48xx, MCP4725.

Thanks in advance..!!

Maybe DAC8564? I've never used one, but it has 4 channels and 16 bits resolution.

Don't forget that if you use less than the maximum amplitude, you will lose some of that resolution (unless you implement some circuit to attenuate the signal under program control, which could get very complex).

The simplest solution might be to use an Arduino with a built-in DAC, such as any one based on SAMD21 chip (10-bit DAC) or SAMD51 (12-bit DAC), and a multi-channel sample-and-hold chip such as SMP08. Question is, will that be good enough for your use-case. If you don't know, maybe start with the simplest option and use that to learn more about what you actually need to build a second prototype and so on.

Specifications:

output Signal : pure sine wave output required continuously

No DAC can produce a "pure" sine wave, only an approximation. The closer the approximation, the more complex and expensive thing get.

The amplitude can be controlled by another DAC providing the reference voltage to the sine wave DAC.

DrDiettrich:
The amplitude can be controlled by another DAC providing the reference voltage to the sine wave DAC.

Great idea, but there is a "gotcha". A multi-channel DAC is likely to have only one Vref input, so if you want it's outputs to be different amplitudes, you still have to scale down the amplitudes of the lower amplitude channels digitally, loosing some resoution.

Its a challenge. Once you have your sine waves they can easily be amplitude modulated with a digital potentiometer for each channel.

The arduino Uno does not have an ADC. You CAN use PWM to produce an analog signal but not 6 at a time.

You have not defined how precise the sine wave must be - ie what harmonics would be allowed.

My page here gives more explanation.

Also consider the data rate for feeding the DACs. The higher the resolution the more samples have to be transferred during one wave (20ms).

12-bit resolution = 4096 steps. You will probably want at least some 2,000 steps per wave period to really use that resolution, at 50 Hz that's 100,000 steps per second. A lot of data to be transferred to an ADC.

As an Arduino can not calculate that fast, you need to use lookup tables. A 200,000 byte lookup table does not fit in a 32k memory...

Maybe you need something more powerful than an Arduino to do this.

The Arduino family includes more powerful controllers than only 8 bit AVR controllers.

DrDiettrich:
The Arduino family includes more powerful controllers than only 8 bit AVR controllers.

Yes, and, your relatively low frequency gives you a lot of flexibility in your approach. It's likely you could produce your 6 sines in real time using nothing but math. Basically anything with an ARM or ARM class processor, and there are many to choose from.

Hi,
Just trying to understand your requirements.
Do you need ADJUSTABLE phase difference between each channel.
If not and they are 60deg apart, then you only need 3ch and invert each of them to get the other 3,

What is the application?

Thanks.. Tom.. :slight_smile:

riyazshaikh26:
Please help to find out which one way I should go to do this, I mean which Arduino + DAC

We are trying to help. If you can tell us more, maybe we can narrow down our recommendations and eliminate some of the options. Can you tell us, for example, how far the output signal can be from that "pure" sine wave, in % or mV, at any instant in time? It might help if we understood the application: what are these sine waves for?

Get at least one DAC for the proper hardware setup.For the code you can run a dry test, with 6 times output to the same DAC.

Then report any problems for detailed analysis.

Analog Devices do a whole range of DDS generator modules. You can often find them on breakout boards for a few quid each.

  1. DAC of 10-12 bit is sufficient

If you use 10 or 12 bit DACs you will need a LOT of sample values per sinewave - consider whether an 8 bit DAC would suffice.

https://www.hobbytronics.co.uk/mcp4902-dac

For 10 mV on 5V total range (just "10 mV" doesn't make sense - it matters a lot whether your full range is 1V or 100V) you need at least 500 levels, that is not consistent with your 8-bit/256 level suggestion. A 9-bit DAC would be the minimum for that. So you'll end up with a 10-bit ADC as minimum.

Furthermore you'll need at least 500 sample points per period or around the zero crossing there would be more than 10 mV between steps. That's 25,000 samples per second at 50 Hz, or 50,000 bytes to transfer. Times six waves that is 300,000 bytes/s or 2.4 Mbit/s, not counting protocol overhead.

A single Arduino should be able to produce one such a sine wave. A seventh Arduino could be sending the moment the other Arduinos have to start producing the signal (by setting an I/O pin high or low) for the phase difference, and give a 50 Hz sync signal to keep them all in sync. That's how I think this could be done with Arduinos.

Maybe something faster like a Teensy can do it all on one board. You still need fast enough communication to your DACs to produce the output. I2C is basically out (even for a single DAC); a much faster protocol like SPI is needed.

o/p Voltage amplitude (pk-pk) : 1V or 2V max

For 1% or 10mV, 8-bit DAC would be enough.

Ah. So that's down to 100-200 steps overall. Makes me wonder why OP thinks the PWM method is not good enough... that's about the accuracy I expect (it gives 5V peak-peak but that's easily corrected with a voltage divider), especially if you crank up the PWM frequency (5-10 kHz should do) so you can use an RC filter with lower RC value.

That was a fun little experiment. I got a 6 sine waves out of an Arduino Nano, 50 Hz (or very close to that), phase adjustable with steps of 0.72 degrees, and with just two extra components per output: an RC filter of a 330Ω resistor and a 10 µF capacitor, values as calculated on this site. Easier than anticipated, to be honest.

The loop() function is empty. This is where e.g. a Serial input could be read for phase shifting, and the variable phaseShift adjusted accordingly. The values set in this code are just an example. As the whole thing is interrupt driven - at 25k Hz the phase is updated to the next, everything else is in hardware - there should be ample processing power left for this.

Code:

// Sine wave generator.
// 50 Hz; 500 steps per full sine wave; 25,000 total steps per second.
// 640 ticks per step.
// PWM frequency: at least 25 kHz.

// OC0A Arduino pin 6
// OC0B Arduino pin 5
// OC1A Arduino pin 9
// OC1B Arduino pin 10
// OC2A Arduino pin 11
// OC2B Arduino pin 3

const byte sine[500] = {127, 128, 130, 131, 133, 134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 150, 152, 153, 155, 157, 158, 160, 161, 163, 164, 166, 167, 169, 170, 172, 173, 175, 176, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 195, 196, 197, 199, 200, 201, 202, 204, 205, 206, 207, 209, 210, 211, 212, 213, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 235, 236, 237, 238, 239, 239, 240, 241, 241, 242, 243, 243, 244, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 250, 251, 251, 251, 252, 252, 252, 252, 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 254, 253, 253, 253, 253, 253, 253, 253, 253, 253, 252, 252, 252, 252, 252, 251, 251, 251, 250, 250, 250, 249, 249, 248, 248, 247, 247, 246, 246, 245, 245, 244, 243, 243, 242, 241, 241, 240, 239, 239, 238, 237, 236, 235, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, 213, 212, 211, 210, 209, 207, 206, 205, 204, 202, 201, 200, 199, 197, 196, 195, 193, 192, 190, 189, 188, 186, 185, 183, 182, 181, 179, 178, 176, 175, 173, 172, 170, 169, 167, 166, 164, 163, 161, 160, 158, 157, 155, 153, 152, 150, 149, 147, 146, 144, 142, 141, 139, 138, 136, 134, 133, 131, 130, 128, 127, 125, 123, 122, 120, 119, 117, 115, 114, 112, 111, 109, 107, 106, 104, 103, 101, 100, 98, 96, 95, 93, 92, 90, 89, 87, 86, 84, 83, 81, 80, 78, 77, 75, 74, 72, 71, 70, 68, 67, 65, 64, 63, 61, 60, 58, 57, 56, 54, 53, 52, 51, 49, 48, 47, 46, 44, 43, 42, 41, 40, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 18, 17, 16, 15, 14, 14, 13, 12, 12, 11, 10, 10, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 10, 11, 12, 12, 13, 14, 14, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 43, 44, 46, 47, 48, 49, 51, 52, 53, 54, 56, 57, 58, 60, 61, 63, 64, 65, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 93, 95, 96, 98, 100, 101, 103, 104, 106, 107, 109, 111, 112, 114, 115, 117, 119, 120, 122, 123, 125};

volatile uint16_t phaseShift[6];                            // Phase shift: 0 = 0 deg; 250 = 180 deg; 499 = almost 360 deg.

void setup() {

  pinMode(3, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);

  // Set up the timers for sufficiently high PWM output.
  // Timer0: fast PWM mode, non-inverting.
  // Prescaler = 1 so f(PWM) = 62.5 kHz (note: this messes up millis() which now runs 64x too fast).
  TCCR0A = bit(COM0A1) | bit(COM0B1) | bit(WGM01) | bit(WGM00);
  TCCR0B = bit(CS00);

  // Timer1: fast PWM mode, non-inverting, prescaler = 1, TOP = 639 (frequency: 25 kHz for update on timer overflow interrupt).
  // Use IRC1 as top, leaving OCR1A free for PWM. Sets ICF1 interrupt flag upon reaching TOP.
  TCCR1A = bit(COM1A1) | bit(COM1B1) | bit(WGM11);
  TCCR1B = bit(WGM13) | bit (WGM12) | bit(CS10);
  TCCR1C = 0;
  ICR1 = 639;

  // Enable the overflow interrupt.
  TIMSK1 = bit(TOIE1);

  // Timer2: fast PWM mode, non-inverting.
  // Prescaler = 1 so f(PWM) = 62.5 kHz.
  TCCR2A = bit(COM2A1) | bit(COM2B1) | bit(WGM21) | bit(WGM20);
  TCCR2B = bit(CS20);

  // Set some random values for the phase shift.
  phaseShift[0] = 0;
  phaseShift[1] = 100;
  phaseShift[2] = 120;
  phaseShift[3] = 30;
  phaseShift[4] = 90;
  phaseShift[5] = 450;
  


}

void loop() {
}

volatile uint16_t sineIndex;
ISR(TIMER1_OVF_vect) {
  uint16_t i = sineIndex;                                   // Local copy for increased efficiency.
  OCR0A = sine[i + phaseShift[0]];
  OCR0B = sine[i + phaseShift[1]];
  OCR1A = sine[i + phaseShift[2]];                          // Will produce a smaller wave amplitude as timer counts 0-639, not 0-255.
  OCR1B = sine[i + phaseShift[3]];                          // To get full swing, add second lookup table with values 0-638 instead.
  OCR2A = sine[i + phaseShift[4]];
  OCR2B = sine[i + phaseShift[5]];
  i++;
  if (i == 500) {
    i = 0;
  }
  sineIndex = i;
}

Schematic used (for each pin):
schematic.png

Note: depending on what you want to do with this signal you may want to pass it through an OpAmp to buffer it, add an offset, or decrease the amplitude with a voltage divider. I did not experiment with the RC values, the cap value I think is way bigger than needed.

schematic.png

Which external DAC? You picked one already? You know the requirements...

Code you have to write yourself. I'm not interested in doing that. The above was as I had some time to kill and it looked like an interesting challenge and kinda useful for a project of my own that I have in mind for a long time but never managed to get around to. It wasn't all that hard really; I wrote that in two hours (in between my two posts). Just a lot of carefully reading in the datasheet.

By the way, the ATtiny861 processor may also be interesting to you. It offers a 64 MHz timer with three 10-bit PWM outputs. With double RC output filters (I used only a single one) that should make for really nice waveforms. Two of them can make your six waveforms, you just have to find a way to sync the two of them.

@wvmarle thats a very neat solution. +Kudos. I agree the cap is a bit big, it needs to cut the step harmonics but not the fundamental so 1uF would I think be enough.

@riyazshaikh26 if you are considering using DACs you will need 6 external DACs plus likely some complicated electronics to generate a suitable count sequence for each, or some complicated code to send the right value to each DAC at the right time.

Using 1 external dac is easy - send @wvmarle's numbers to the dac at timed intervals.