I'm trying to do some Delta Sigma/DDS to put out more decent signal than a square wave to my speaker. I haven't fully understood the theory behind it (my math is quite rusty), but the Wiki page has an image explaining the principle very well and that's what I tried to emulate:
I'm using a 32 byte look-up table for my sine function and the timer0. I get what I expect from my function:
The whole purpose was to make a cleaner signal than a square wave, but the sound is horrible. I get very annoying distortions, probably because I'm commutating at still audible frequencies. Does anybody know a solution or similar projects?
Here's my code, it runs on Arduino Leonardo:
#include <util/delay.h>
#include <avr/pgmspace.h>
// waveform look-up table
PROGMEM uint8_t sine32[] = {128,153,177,199,218,234,245,253,255,253,245,234,218,199,177,153,128,103,79,57,38,22,11,3,1,3,11,22,38,57,79,103};
volatile struct Data
{
uint8_t counter: 8; // counts from 0 to 255
uint8_t pwm: 4; // how many times the pwm value must be kept at 1, from 0 to 8
uint8_t* waveform; // pointer to waveform table
} sound;
volatile uint32_t countDown;
void play(uint16_t frequency, uint16_t time, uint8_t* waveform)
{
sound.counter = 0;
sound.pwm = 0;
sound.waveform = waveform;
// set pin 7 as output
DDRE |= (1 << 6);
// disable timer0 overflow interrupt used for Arduino delay() functions
TIMSK0 = 0;
TIFR0 = 0;
// set mode to PWM, Phase Correct, TOP defined by OCR0A
TCCR0A = (1 << WGM00);
TCCR0B = (1 << WGM02);
// timer0 prescalers
const uint16_t prescalers[] = {1, 8, 64, 256, 1024};
// scan through prescalars to find the best fit (ocr <= 255)
uint16_t prescalerIndex, ocr;
// commutation speed it 256 times the frequency
frequency = frequency << 8;
// 16MHz / freq / presaler / ocr = commutation frequency
for(prescalerIndex = 0; prescalerIndex < 5; prescalerIndex++)
{
ocr = F_CPU / frequency / prescalers[prescalerIndex];
if(ocr <= 255)
{
// the prescaler and oscillator combination has been found
break;
}
}
// set the frequency
OCR0A = ocr;
// calculare the commutations to reach the playing time
countDown = frequency * time;
// reset timer0 counter
TCNT0 = 0;
// set the prescaler (enable timer)
TCCR0B = (TCCR0B & 0xf8) | prescalerIndex;
// enalble compare interrupt vector
TIMSK0 |= (1 << OCIE0A);
// wait here until sound is played
while(countDown);
// restore timer0 for Arduino time functions
TCCR0A = (1 << WGM01) | (1 << WGM00);
TCCR0B = (1 << CS01) | (1 << CS00);
TIFR0 = (1 << TOV0);
}
ISR(TIMER0_COMPA_vect)
{
// slow pwm changes every (frequecy * 32) times
if((sound.counter % 8) == 0)
{
// divide value (0 to 255) by 64 to get an index between 0 and 8
// adding 16 before shifting makes the singal less "biased" as it is rounded down
sound.pwm = (uint16_t) (pgm_read_byte_near(sound.waveform + (sound.counter >> 3)) + 16) >> 5;
}
// fast pwm changes every (frequency * 255) times, which is the frequency of this interrupt
if(sound.pwm > 0)
{
// keep the signal at logic high for the 1/256th of the period
PORTE |= (1 << 6);
sound.pwm--;
}
else
{
PORTE &= ~(1 << 6);
}
// count repeatedly from 0 to 255
sound.counter = (sound.counter++) % 255;
if(--countDown == 0)
{
// disable compare interrupt and enable overflow interrupt
TIMSK0 = (1 << TOIE0);
PORTE &= ~(1 << 6);
}
}
void setup()
{
}
void loop()
{
// play 100Hz sine wave for 10 seconds
play(100, 10000, sine32);
}