sine wave generation using DAC

I'm very new to microprocessor programming so please bare with me.

I'm working on a research project at school. I need to generate different analog signals (the frequency can be hard coded) ranging from 1 HZ to 100 KHZ.

I found someones project on the internet and have tried to modify it to meet my needs. The biggest problems I have had have involved the timers.

In my code, a timer interrupt is set which increments a counter flag. In the loop, every time the counter flag is updated, parts of the signal are generated (using a DAC not PWM). At 1 HZ, the signal looks great. However, once I try and get up to 1KHZ, the signal is really ugly. If anyone could hint me in the right direction about how to fix it, that would be great.

Specifically, how can I disable PWM mode in the Timer2?

Thanks,
Chris

/*
 *
 * 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"
#include <avr/interrupt.h>
#include <avr/io.h>
#include <Wire.h>




byte Program = 64; // this sets the DAC register to receive new data
byte Device = 96; // This hardwired into the IC and the BoB, in other words, it is a given.

int sineVal = 0;




// table of 256 sine values / one sine period / stored in flash memory
PROGMEM  prog_uchar 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))


double dfreq;
// const double refclk=31372.549;  // =16MHz / 510
const double refclk=31376.6;      // measured

// variables used inside interrupt service declared as voilatile
volatile byte icnt;              // var inside interrupt


volatile unsigned long phaccu;   // pahse accumulator
volatile unsigned long tword_m;  // dds tuning word m
volatile int timerTick = 0;

void setup()
{
  Wire.begin();
  
  Serial.begin(115200);        // connect to the serial port
  Serial.println("DDS Test");


  Setup_timer2();

  // disable interrupts to avoid timing distortion
  cbi (TIMSK0,TOIE0);              // disable Timer0 !!! delay() is now not available
  sbi (TIMSK2,TOIE2);              // enable Timer2 Interrupt

  dfreq=.5;                    // initial output frequency = 1000.o Hz
  tword_m=pow(2,32)*dfreq/refclk;  // calulate DDS new tuning word 

}

void loop()
{
    if (timerTick = 1)
      {         
           
           
            Wire.beginTransmission(Device);
            Wire.send(Program);
            Wire.send(sineVal);
            Wire.send(sineVal); // Needed twice, since the 4 lowest bits (of 12) are in the fourth byte
            Wire.endTransmission();
            Serial.print(sineVal);
            Serial.print(", ");
            timerTick = 0;
  
          }
    // if (c4ms > 250) 
      // { }                // timer / wait fou a full second  
 }
//******************************************************************
// 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
  cbi (TCCR2A, COM2A1);

  cbi (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)
 // 125 ticks = 4 milliseconds
ISR(TIMER2_OVF_vect) 
  {

     phaccu=phaccu+tword_m; // soft DDS, phase accu with 32 bits
          icnt=phaccu >> 24;     // use upper 8 bits for phase accu as frequency information
  
           sineVal = pgm_read_byte_near(sine256 + icnt); 
      timerTick = 1;
     
     
  }

did u test how long that Wire.begin/send stuff takes?
there seems to be a buffer...

why dont u just use delayMircoseconds?

r u allowed to use more than a DAC?
http://www.falstad.com/circuit/e-sine.html
http://www.falstad.com/circuit/e-vco.html

u can turn off the PWM stuff on timer2 by setting register TCCR2A to 0
(see datasheet doc2545 page 154pp)...
i like to set TIMSK2 to zero, 2... that turns off all interrupts on Timer/Counter2...
the prescaler can be conf-ed via TCCR2B (0 <=> no clock, 1 <=> no prescaler, ...)

what about COM2B0 and COM2B1 in ur sketch?

r u sure u can ever do 100kHz sine waves (with 256 points?) with a 16MHz clock?

-arne

I've read someplace (can't place it) on using a resistance ladder across eight ports, giving you a variable voltage in 256 steps.. and storing the waveform in a data table. I believe the project in question was based on a PIC not Atmel AVR (like Arduino), but I could be wrong.

Either way the idea is sound, simply shift the data out the eight ports, "mapping" out the desired waveform. I think the project was capable of nearly 200khz on a 20Mhz clock..

yup

that r2r ladder sounds faster, than this Wire lib...
it's easy to solder one...

although 160 cycles should be enough to tell that DAC its new orders... :slight_smile:

if the frequency may be hardcoded, u dont even need delayMicroseconds...
a loop around an «asm("nop\n");» would suffice...

-arne

Righto. Also, since the waveform is hard-coded in a data table (calculating would be slower), you could build some pretty complex waveforms, if you wanted.. not just sine, but square and sawtooth waves, as well as all sorts of weird ones with faux harmonics and such..

Thank you for all of your replies and help. I'm in the process of looking to your suggestions. In the mean time, I have modified my code and am still having problems.

I'm having a timing problem and I feel that if I'm able to find the root of the timing problem, I should be able to figure the rest of it out. I've commented out some of the code that is specific to the project but doesn't concern the problem.I have highlighted the portions of code that I feel directly relate to my problem.

Inside the never ending loop is a print statement that prints the number of times the counter interrupt occurs. What I want to do is make the timer interrupt trigger say 3,000 times a second. I'm doing the calculations thusly 16000000/64/(OCR1B/A) = 10 times a second. That works as I would expect. However, whenever I change the prescaler TCCR1B |= ((0 << CS10) | (1 << CS11) | ( 0 << CS12)); to something lower than 64. Or, when I change OCR1B/A, my calculations are no longer valid.

I'm new to all of this and have been searching for a solution for quite some time and am at a complete lost. I sure appreciate everyone who helps.

Thanks,
Chris

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

// global variables
//**
volatile int timerTick = 0;            // flag which indicates a timer interrupt occurs
volatile int ADCDone = 0;            // flag to check if ADC has completed
volatile int buttonPresses = 0;      // counts button presses
volatile int datasWritten = 0;      // counts number of data points written

double dfreq;                              // const double refclk=31372.549;  // =16MHz / 510
const double refclk=100;    // measured 31376.6
volatile unsigned long phaccu = 0;   // Phase accumulator
volatile unsigned long tword_m = 0;  // dds tuning word m
int icnt = 0;


// table of 256 sine values / one sine period / stored in flash memory
PROGMEM  prog_uchar 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

};
int test = 0;
// ADC Interrupt triggered when ADC conversion complete
ISR(ADC_vect)
{
      ADCDone = 1;
}


[glow]// Interrupt for output compare "B"
ISR(TIMER1_COMPB_vect)
{
      timerTick = 1;  // indicate that timer event has occured
      test++;
}[/glow]

ISR(INT0_vect)
{
      // do nothing
}

unsigned char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}

int main (void)
{
      int potVal = 0;
      int sineVal;

      // initialize the ports used and their direction
      init();

      // loop forever
      while (1)
    {
            // first button press
          printf("Waiting for button Press\n");

          dfreq=1.0;                    // initial output frequency = 1000.o Hz
          //tword_m=pow(2,32)*dfreq/refclk;  // calulate DDS new tuning word
          tword_m = 136902;

          //sleep_enable();
         EICRA |= (0<<ISC01)|(0<<ISC00);      // low level of INT0 causes interrupt
         EIMSK |= (1<<INT0);             // enable External Interrupt 0

          _delay_ms(100);
          sleep_mode();                        // this is where the CPU is put to sleep
          EIMSK = 0;                               // disable External Interrupt 0

                  printf("ms, data\n");

                  // start ADC conversions
                  ADCSRA |= (1 << ADSC);

                  // reset timer
                  TCNT1 = 0x0000;

                  // start timer at Fcpu/64  see pg 132 for clock prescalers
                  [glow]TCCR1B |= ((0 << CS10) | (1 << CS11) | ( 0 << CS12));[/glow]
                  int test = 1;
                  // loop until the we have a certain number of data points
                  while(1){

                        // wait for a timer interrupt
                        if(timerTick == 1){
                              // set flag back once we have entered loop
                              [glow]timerTick = 0;
                              printf("%d\n" , test );
                              test++;[/glow]

                              //phaccu = phaccu+tword_m; // soft DDS, phase accu with 32 bits
                              //icnt = phaccu >> 24;     // use upper 8 bits for phase accu as frequency information
                              //sineVal = pgm_read_byte_near(sine256 + icnt);

                              //printf("%d\n", sineVal);

                              // spin until ADC done
                              //while(ADCDone == 0){}

                              // set the flag back low when we determine ADC is done
                              //ADCDone = 0;

                              // store the result
                              //potVal = ADCW;
                              //printf("%d\n", sineVal);
                              //voltageOut(sineVal);
                        }
//printf("%d\n", test);

                  }

                  voltageZero();

                  TCCR1B = (1 << WGM12);  // turn off timer

                  // reset ADC
                  ADCSRA = 0x06 | (1 << ADATE) | (1 << ADEN) | (1 << ADIE);

                  datasWritten = 0;

            _delay_ms(500);
            _delay_ms(500);
            _delay_ms(500);
            _delay_ms(500);
    }

  return (0);
}


void init (void){

      // IO Init Data Direction Registers
    // 1 = output, 0 = input
      // set as output unless input desired
      DDRB = 0b11101111;            // enable bit 3 port B (for MOSI) as input (DIGITAL 12)
      DDRC = 0b11111110;            // enable bit 0 port C as input (ANALOG INPUT 0)
      DDRD = 0b11111011;             // enable bit 2 port D as input (DIGITAL PIN 2)      // enable bit 2 port D as input (DIGITAL PIN 2)


      //USART
          UBRR0H = UBRR >> 8;
          UBRR0L = UBRR;
          UCSR0B = (1<<TXEN0);

            // DAC I2c Init
            TWCR = 0;
            TWBR = 16;
            TWCR |= (1 << TWEN);

            voltageZero();

          stdout = &mystdout;            // change the std out to our uart_putchar function


          // Button Interrupt
            printf("Initializing \n");

          set_sleep_mode(SLEEP_MODE_PWR_DOWN);



      // ADC Init


//see page 244
    ADCSRA |= 0x06;                  // ADC clock prescale = 64  pg 256
      ADMUX  |= (1 << REFS0) | (1 << REFS1); // "use ADC pin 0", ref 1.1v, AVCC
      ADCSRA |= (1 << ADATE); // Enable Auto Trigger Mode
      ADCSRA |= (1 << ADEN);  // Enable ADC by writing 1 to ADEN
      ADCSRA |= (1 << ADIE);  // Enable ADC Interrupt
      ADCSRB |= 0x05;              // Set ADC to trigger on Timer/Counter1 Overflow pg 258

      [glow]// Timer Init
      TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode PG. 131
[/glow]
      //pg 134
      [glow]OCR1B = 25000;[/glow]                  // previously 975, 25000 will give us an output compare every 10ms      changed from 25000
      [glow]OCR1A = 25000;[/glow]                  //value at which the timer (TCNT1) will be cleared. trigger it 10 times in a second


      TIMSK1 |= (1 << OCIE1B);// enable interrupts on output compare B

      sei();
}


static int uart_putchar(char c, FILE *stream)
{
    if (c == '\n'){
          // add a carriage return for crappy hyperterminals
          uart_putchar('\r', stream);
    }
    // wait for USART to be ready
    loop_until_bit_is_set(UCSR0A, UDRE0);
    // send the data
    UDR0 = c;

    return 0;
}

void i2cSendStart(void)
{
      // send start condition   pg 225
      TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
}

void i2cSendStop(void)
{
      // transmit stop condition   pg 225
      TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
}

void i2cWaitForComplete(void)
{
      // wait for i2c interface to complete operation pg 218?
        while (!(TWCR & (1<<TWINT)));
}

void i2cSendByte(unsigned char data)
{
      // save data to the TWDR
      TWDR = data;
      // begin send
      TWCR = (1<<TWINT)|(1<<TWEN);
}

void voltageOut(int voltage){

      char hVout;
      char lVout;


      int vOut = voltage;
      //lVout = 287;
      lVout = vOut;
      hVout = (vOut >> 8)& 0x0F;

      //Send start condition
      i2cSendStart();
    i2cWaitForComplete();

      // send slave device address with write
      i2cSendByte(SLA_W);
      i2cWaitForComplete();

      // send first byte to DAC
      TWDR = hVout;
      TWCR = (1<<TWINT)|(1<<TWEN);
      i2cWaitForComplete();

      // send second byte to DAC
      TWDR = lVout;
      TWCR = (1<<TWINT)|(1<<TWEN);
      i2cWaitForComplete();

      //send stop condition
      i2cSendStop();

      TWCR = 0x00;//stop I2C
}

void voltageZero(void){

      //Send start condition
      i2cSendStart();
    i2cWaitForComplete();

      // send slave device address with write
      i2cSendByte(SLA_W);
      i2cWaitForComplete();

      // send first byte to DAC
      TWDR = 0;
      TWCR = (1<<TWINT)|(1<<TWEN);
      i2cWaitForComplete();

      // send second byte to DAC
      TWDR = 0;
      TWCR = (1<<TWINT)|(1<<TWEN);
      i2cWaitForComplete();

      //send stop condition
      i2cSendStop();

      TWCR = 0x00;//stop I2C
}

If you're willing to restart your project with a fresh approach to generating sine waves, I've had good success with the strategy and code I found here:

http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/

Thank you for your reply. My goal, is to implement exactly what you have done. However, I thought I could get better resolution by using a DAC instead of the ladder. I've actually used the DDS method you describe, however, I have been unable to accurately calculate the frequency of the sine wave because I believe something is messed up with my timing.

I'm sure I'm way mixed up somewhere, I'm just not sure where.

I've tried to take your project, change the timing from PWM to CTC to use a DAC to do the actual signal generation. Will this work?

Thanks again,
Chris

i think the Serial.print() call might disturb ur timing...

maybe this works better (some lines r taken from ur initial post)?

PROGMEM  prog_uchar wave[]  = {
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
};

void setup() { Wire.setup(); }

void loop() {
  noInterrupts();
  for (uint8_t i=0; ; i++) {
    const byte val = pgm_read_byte_near(wave+i);
    const byte prog = 64;
    const byte dev = 96;
    Wire.beginTransmission(dev);
    Wire.send(prog);
    Wire.send(val);
    Wire.send(val);
    Wire.endTransmission();

    for (uint8_t t=1; t; t++)
      asm("nop\n");
  }
  interrupts();
}

-arne

Thanks for the reply, I'll try it tomorrow morning and let you know how it works.

I feel dumb but what does

asm("nop\n");

do?

Thanks,
Chris

«asm("...");» tells the compiler,
that it can just quote the ...-part,
because it is already assembler code...

«nop» is an assembler instruction,
that means "no operation" in all assembler languages that i know
(Z80, x86, atmega)... :slight_smile:
it is like "Gesundheit", which means the same in english and german...
and it makes the CPU to spend a cpu cycle with nothing special
(no math and no fetch/store... just wagging tail...)...

here the «asm("nop\n");» hinders the compiler to optimize the loop away...
usually the compiler sees that the loop doesnt change any outer variable, so that it removes it...

btw: the «\n» seems to be important here...
if u omit it, the compiler doesnt fall for the trick... :stuck_out_tongue:

-arne

One more quick question. Do you have any advice as to the best method of changing the DC offset and amplitude of the sine wave?

Thanks,
Chris McBride

digitally?
the original value should be cast to a 16 bit signed integer (like int16_t)...

preferably the amplification factor X should be a power of 2 (2,4,8, ... or 1/2, 1/4, 1/8, ...)...
then it is easy:
(original value - 128) << X
or
(original value - 128) >> X

if X is not a power of 2 (but a float value), then the formula is
(original value - 128) * X
it might be better then to approximate X as A/B, because integer arithmetics might be faster than float arithmetics...
((original value - 128) * A) / B

oh - u might want to express X as A/B where B is a power of 2... :slight_smile:
like B = 256 =2^8
((original value - 128) * A) >> 8

the DC offset Y is just an addition (1 cycle):
(original value - 128) * X + Y
with Y=128 u would have the same DC offset as before...

analogous?
a low frequency amplifier should do it...
they use a tiny capacitor between their input and the output of the arduino...
and they might have knobs for amplification and DC offset...

-arne

I can't tell you how much help you have been. I'll be working on this all day tomorrow and I'll let you know how it goes.

Thanks again,
Chris

I have a numerically controlled oscillator sketch for my NB1A board
at Redirecting...
It generates four independent sine wave outputs using a
quad SPI DAC.

As the frequency increases you are getting less samples per
period output and the waveform is less sinusoidal (total harmonic
distortion THD). You can reduce the THD by adding more
entries in your wavetable and decreasing your timer count.
I doubt you will get to 100KHz on an ATmega328 but it
is easy to try. Adding a low-pass filter on the DAC
output will remove the higher frequency harmonics
and decrease the THD.

At the bottom of my NCO page are a couple of
references to articles describing building NCOs.

(* jcl *)

http://www.wiblocks.com

Thank you everyone for your comments and suggestions, they have been of great help. I still have some issues to sort out, but your help has been invaluable.

Thanks,
Chris