Question about DDS and ugly sine wave on 8 bit Arduino

Hi!

I made a small experiment with a 8 bit resistor ladder after reading the following article www.arduinoos.com.

I use 8 resistors instead of 6 (and removed the debug code in the article). Also changed the interrupt to 44.1kHz.

My sine wave looks ok at 5kHz but realy ugly at 10kHz.
If I change the interrupt to 88.2Khz it looks good at 10kHz.

I thought 44.1kHz should be good enough for a 22kHz wave?
Have anyone tested DDS on a 8bit AVR, and what are your experience?

/Olof

Edit: attaching two screen captures from oscilloscope (wave frequency 10kHz).

My sine wave looks ok at 5kHz but realy ugly at 10kHz.
If I change the interrupt to 88.2Khz it looks good at 10kHz.

I thought 44.1kHz should be good enough for a 22kHz wave?

At the Nyquist limit (half the sample rate) you have one sample for the positive half-cycle and one sample for the negative half-cycle. It’s up to the “reconstruction filter” to “connect the dots” and make a smooth waveform from those few points.

You could put a low-pass filter after the ladders, that will help smooth it out more.
Tool here for calculating values for some of the different ways to make a low pass filter.
http://sim.okawa-denshi.jp/en/CRlowkeisan.htm

Thanks for your answers.
I already have simple low pass filter (resistor + capacitor) because the sine wave looked very "digital" without the filter.

I can increase the speed of the interrupt even more, maybe 160Khz with a 20Mhz crystal.
After looking at a couple of DDS examples on internet I think it's a bit strange that many use so low "sampling rate" (is the correct term for for the interrupt speed?).

I would get it working with six bits first (as in the original design), then try eight. Did you use 1% resistors or better? Did you change the global constant for the number of output bits?

After looking at a couple of DDS examples on internet I think it’s a bit strange that many use so low “sampling rate” (is the correct term for for the interrupt speed?).

CDs are 44.1kHz/16-bit. That’s better than human hearing.

Although the pure digital representation looks “rough”, the low-pass filter smooths things out, the speakers also act as low-pass filters, and you can’t hear harmonics above around 20kHz anyway.

At 8-bits you’ll normally hear quantization noise with regular audio. But with full-scale signals (full-loudness tones), the noise may not be audible. (I don’t play around with 8-bit audio that much, so I’m not sure.)

Looking good on a scope and having the information in the waveform are two completely different things.
At the Nyquist limit a sin wave looks nothing like a sin wave on a scope. You have to sample a lot faster to make it look good, as you have found out.

@lof_n

What are you actually trying to achieve with this?

e.g. a function generator (waveform generator)

What frequency of waveform do you require.

What shape of waveform i.e do you really want a sine wave or if this just your first test.

I suspect that what you are trying to achieve is not possible on a Uno, mainly because its probably not fast enough and won't have enough resolution.

BTW. You havent posted you code, unless you are doing direct access to the the GPIO and changing multiple bits in one write to the GPIO port, your waveform is also going to look terrible, because digitalWrite is comparatively slow and also you should not change the bits independantly without using an output latch

Just want to try different methods of generating sine waves (for a future synth build).
Both analog and digital solutions
I know I could use a dedicated chip like AD9850 but that’s no fun :slight_smile:

The code (44kHz version), most of the lines are copied from the site I linked to.
Increasing (tried 88kHz and 176kHz) the sampling rate makes a big difference.

#define maxDACValue  255
#define WAV_TYP_SINE 0
#define WAV_TYP_SQUARE 1
#define WAV_TYP_TRIANGLE 2
#define WAV_TYP_SAW_POS 3
#define WAV_TYP_SAW_NEG 4
#define WAV_TYP_RANDOM 5

uint16_t refFrequency = 44146; 
unsigned long phaseAccumulator;
volatile unsigned long tuningWordM;     

uint8_t vWaveData[256];

void setup() {
  // put your setup code here, to run once:
  generateWaveData(WAV_TYP_SINE);
  DDRD = B11111111;
  setupInterrupt();
  setFrequency(10000);
}

void loop() {
  
}

void setupInterrupt()
{
  cli();//stop interrupts
  TIMSK0 &= ~_BV(TOIE0); // disable timer0 overflow interrupt
  //set timer1 interrupt at ~44.1kHz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = 361;// = (16*10^6) / (44100*1) - 1 (must be <65536)

  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 bit for 1 prescaler
  TCCR1B |= (1 << CS10); 
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  sei();//enable interrupts 
}

ISR(TIMER1_COMPA_vect)
{
  // Update phase accumulator counter, the phase accumulator will automatically overflow at 2^32
  phaseAccumulator += tuningWordM; 
  // Use the 8 MSBs from phase accumulator as frequency information
  PORTD = vWaveData[(phaseAccumulator >> 24)];
  //Test
  //PORTD ^= B11111111;
}

// Generate wave data in global vector
void generateWaveData(int style)
{
  switch(style){
  case WAV_TYP_SINE: // Sine
    for (int i=0; i < 256; i++) vWaveData[i] = (maxDACValue*(sin((i*6.2831)/256)+1))/2;
    break;
  case WAV_TYP_SQUARE: // Square
    for (int i=0; i < 256/2; i++) vWaveData[i] = 0;
    for (int i=256/2; i < 256; i++) vWaveData[i] = maxDACValue;
    break;
  case WAV_TYP_TRIANGLE: // Triangle
    for (int i=0; i < 256/2; i++) vWaveData[i] = (i*maxDACValue*2)/256;
    for (int i=256/2; i < 256; i++) vWaveData[i] = ((256-i)*maxDACValue*2)/256;
    break;
  case WAV_TYP_SAW_POS: // Saw tooth positive slope
    for (int i=0; i < 256; i++) vWaveData[i] = (i*maxDACValue)/256;
    break;
  case WAV_TYP_SAW_NEG: // Saw tooth negative slope
    for (int i=0; i < 256; i++) vWaveData[i] = ((256-i)*maxDACValue)/256; 
    break;
  case WAV_TYP_RANDOM: // Random (Noise)
    // randomSeed(millis());
    for (int i=0; i < 256; i++) vWaveData[i] = random(0, maxDACValue); 
    break;
  }
}

// Set frequency; 
int setFrequency(int frequency){
  // Calculate tuning word value  
  tuningWordM = (unsigned long)((pow(2, 32) * (float)frequency / refFrequency)); 
}