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.
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
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));
}