Xmega wavtables and ADSR

Go To Last Post
9 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all. I am experimenting with wavetables and the xmega DAC on a 128a4u breakout board.

I know how to initiate a sine wave with a tuning word and phase incrementer but could anyone help me with how to implement code to add an amplitude envelope (ADSR) to the waveform?

Thanks.

Steve.S

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need an amplitude variable. On each cycle, you need to multiply the value from the table by the amplitude. Helps if the amplitude is a power of 2 so it becomes just a shift. 

The largest known prime number: 282589933-1

It's easy to stop breaking the 10th commandment! Break the 8th instead. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Although in practice you might not want to adjust the amplitude on each sample of a DDS signal generator.

 

So you might set up another Timer/Counter for a periodic interrupt, and every time it fires you adjust the amplitude scaler variable.

 

The envelope changes very slowly compared to the actual audio signal, which changes very slowly compared to the actual DDS update frequency.

 

JC

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK. Thanks for the help. One problem I'm having though is getting 2 timer interrupts to function in the same program. Is it possible to have two timer interrupts working next to each other?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Of course you can do that. Just follow usual interrupt practices and keep your ISRs short. In xMega, you can assign interrupt priorities so one can interrupt another.

The largest known prime number: 282589933-1

It's easy to stop breaking the 10th commandment! Break the 8th instead. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK. So I've got two interrupts working.

One is outputting a wavetable for the wave. (I'm using 2 256 value triangle wave with maximum value of 4095 for 12 bit.)

This interrupt is triggering at (32,000,000/(8*128)=31250 Hz.

The second interrupt is running at 3 Hz and is now triggering and i've tried incrementing a 256 sawtooth wave (from 4095 down to 0 in 256 values) and multiplying the tri wave with this.

SOmething is happening but its like a wobbly noisy wavy sound...

Should the multiplication be a simple as this? 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, I've not seen your code, and perhaps you are already doing this, but I'll share a thought or two.

 

Are you using floating point multiplication, or integer multiplication?

The floating point math takes a lot more of the processor's time.

If you have an O'scope, Set a bit high every time you enter the ISR and set it low every time you exit the ISR and you can easily see how much time the processor is spending on the ISR, vs in the Main Loop.

 

Sometimes it is possible to use scaled integers for faster calculations.

Using 100 times the value lets you pick up a couple of "decimal places" without the floating point overhead.

 

Next, are you performing calculations inside the ISR's?

The ISR that is outputting the signal value should enter the ISR, output the  next value, set a flag, and exit back to Main.

In the Main Loop watch for the flag and when you see it, increment the table value, multiple it by your modulator value, and have it all ready for the next time the interrupt comes along.

If you do it this way, the time to update the output signal is fixed, it doesn't depend upon any (variable) calculation time).

 

In practice whether the ISR sets a flag, or actually reads and stores the next table value is rather arbitrary.

 

With a dual channel O'scope one channel can track the calculations in the Main Loop to prepare the next value, while the other channel tracks the ISR firing and updating the DAC output.

One can easily see if the ISR is expecting the next value to be reading, and outputting it, before the Main Loop has finished calculating it, by watching the two channels.

The Main Loop calculations should run shortly after the DAC output ISR fires, and should be done before the Output ISR fires again.

 

Lastly, Gotta love the Xmega.

The Xmega gives you 32 MHz, without overclocking it.

This gives you a LOT more clock cycles for your calculations that a uC running at 16 or even at 20 MHz.

 

Also, it has priority interrupts.  One would typically make the DAC output ISR a high priority.

One wants it to occur right on schedule.

 

The much slower modulation ISR won't matter if it is off a few uSec's.

 

JC 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok. my ISR' slook like this:

ISR(TCC0_OVF_vect)
{ 
 
 accumulator +=accumulatorSteps;
  waveStep=accumulator>>8;
 mixer= tri[waveStep]*env;
dac.write((tri[waveStep]) *mixer);
//TCC0.INTFLAGS = (TC0_OVFIF_bm << 1);


}

ISR(TCC1_OVF_vect)
{
  //

envaccum+=envaccumSteps; // envelope accumulator
envstep=envaccum>>8;
env=decay[envstep]; // 256 point decay wavetable

 PORTA.OUTTGL = PIN3_bm; /* PA3 togglw */ //speed test

}

I'm using fixed points not floats. 

 

Also, because I'm using a modified Arduino IDE, the main() is actually loop().

I haven't got anything in the loop ATM....

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What I generally do is output a value that was calculated in the previous interrupt, that way you can minimize variations in latency that might otherwise occur. I does mean that the first value sent to the DAC has to be guesswork, but usually you'll want it to be zero or half full-scale, depending on how you're doing things.

Four legs good, two legs bad, three legs stable.