xmega DAC output using timer interrupts

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

Hi peeps & seasons greetings from London.
Haven't been on this site for a while but I'm back. griping & asking for help .:)
I have some code I have been using on the Arduino UNO that generates FM synthesis and is a very nice piece of code from GordonJCP on github. It uses a timer overflow interrupt to output a sinewave stored in progmem and has been very cleverly programmed to incorporate a second wave as the second FM operator. The Timer interrupt vector used is timer2:

ISR(TIMER2_OVF_vect)

and it outputs the wave through DDS methods of phase accumulator and increment. The whole ISR is here:

ISR(TIMER2_OVF_vect) {
  // internal timer
  if(icnt1++ > 31) { // slightly faster than 1ms
    do_update=1;
    icnt1=0;
   }   
  

    // operator 1
  phaccu1=phaccu1+tword_m1;
  icnt=phaccu1 >> 24;
  y1 = pgm_read_byte_near(sine256 + ((icnt+fb1) % 256));
  fb1 = fb*(y1 + fb1) >> 8;

    // operator 2
  phaccu2=phaccu2+tword_m2;
  icnt=phaccu2 >> 24;  
  //fb2 = gain1*(y1) >> 8-(gain1>>1);
  fb2 = ((gain1*y1)>>6)-(gain1>>1);
  y2 = pgm_read_byte_near(sine256 + ((icnt+fb2)%256));  

    // set the PWM

   out = ((gain2*y2)>>8)-(gain2>>1);

    // DC restore and clip output
    out += 127;
    out >>=1;
    if (out<0) out=0;
    if (out>0xff) out = 0xff;
    
    OCR2A = out;
}

So it uses Output comp register as the output.
NOW. I love this sketch and would like to put it through a DAC on an Xmega board. I have an XAduino board which is an Arduino mega shaped board with an ATXMega128A chip on it. There is a modified Arduino IDE for it but I have found I can also program the chip on board using a main() loop and access the registers directly. I have seen some examples of outputting waves on the Xmega (Arbitrary waveform generator) and would like to know if anyone could help me with how I would go about using timer interrupts on the xmega to trigger output on the DAC rather than using the PWM method. The reason I am asking is because I would like to make use of the Xmegas DAC and implement more FM operators which I believe would be possible with the XMega DAC. Could anyone help me with how to do timer interrupts on the Xmega for DAC output?
I would be verry verry grateful.
Thanks.
Steve.

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

Hello Steve,

First, I think you should split your task in two. Making a timer interrupt for using a DAC isn't really different from any other timer interrupt work. You need to select a DAC sample frequency (the equivalent to the PWM frequency), and let the timer interrupt run at that frequency. Then at every interrupt, you update the DAC register.

Or did I misunderstand your question?

You're absolutely right. This member is stupid. Please help.

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

Hi Erik! :)
Yep. Thats pretty much what I want to do. Ive had some success today with finding a good tutorial on how to implement a timer and overflow interrupt. I've put in some code to set the internal clock at 16MHz and set the counter to trigger at ~32KHz. I'm a bit unsure as to how to set up the DAC though. The page for the Xaduino board I'm using suggests using analogWrite to send to the DAC but this seems crude and sounds bad. The code I have so far is here:

#include
#include
#include 
#include 
#include "dac_driver.h"
// # # # # # # # # # # # # # # # # # Variable 
volatile  int timer_blink =  500 ;  // timer for real-time execution 
 // 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

};
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 byte icnt1;             // var inside interrupt
volatile byte c4ms;              // counter incremented all 4ms
volatile unsigned long phaccu;   // pahse accumulator
volatile unsigned long tword_m;  // dds tuning word m
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # initialization 
void initialization ( void ) 
{ 
 cli ();   // Disable interrupts 
 // # # # # # # # # # # # # # # # # #
 
 OSC_PLLCTRL = 0x08;  // PLL mult. factor ( 2MHz x8 ) and set clk source to PLL. 

OSC_CTRL = 0x10;    // Enable PLL 

while( !( OSC_STATUS & 0x10 ) );    // Is PLL clk rdy to go ? 

CCP = 0xD8; //Unlock seq. to access CLK_CTRL 
CLK_CTRL = 0x04;  // Select PLL as sys. clk. These 2 lines can ONLY go here to engage the PLL ( reverse of what manual A pg 81 says ) 
 // # # # # # # # # # # # # # # # # # Timer0 - 1Khz
  TCC0.CTRLA  = TC_CLKSEL_DIV256_gc ;  // Prescaler 256
  TCC0.CTRLB  =  0x00 ;  // select mode: Normal
 TCC0.PER  =  2 ;  // counter top value of 1000 Hz  // 16000000/256/2 for interrupt :32KHz
 TCC0.CNT  =  0x00 ;  // Reset counter
 TCC0.INTCTRLA  =  0b00000011 ;  // Interrupt High Level 
 
 // # # # # # # # # # # # # # # # Share # # interrupts high-, medium-and lowlevel
 PMIC.CTRL  |= PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm ;
 // Setting up the DAC Single Conversion mode.    
   
 sei ();  // interrupts enabled 
} 

// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Int TimerC0 1Khz
 ISR ( TCC0_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
                         // read value fron ROM sine table and send to PWM DAC
 
   // analogWrite( 7, pgm_read_byte_near(sine256 + icnt));   -BAD WAY OF DOING IT. 
  if(icnt1++ == 125) {  // increment variable c4ms all 4 milliseconds
    c4ms++;
    icnt1=0;
   }   
} 
// # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Main 
int main ( void )  
{ 
 initialization ( ) ; 
 
 while ( 1 )  
 {  
 dfreq=1000.0;                    // initial output frequency = 1000.o Hz
  if (c4ms > 2) {                 // timer / wait fou a full second
      c4ms=0;
     // dfreq=analogRead(0);             // read Poti on analog pin 0 to adjust output frequency from 0..1023 Hz

              // disble Timer2 Interrupt
      tword_m=pow(2,32)*dfreq/refclk;  // calulate DDS new tuning word
  }

 
 } 
 
 }  

Any advice on the DAC would be welcome.
Thanks again,
Steve.

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

I don't use Arduino, but using the DAC isn't difficult at all. However, since I don't know your hardware, I can't make a complete setup for you.

I've made a small setup example for you, using DACA, channel 0 only.

  DACA_CTRLB=0x00; // Single-channel operation on ch 0; no event stuff
  DACA_CTRLC=0x00; // Use internal 1 V as reference; right adjust data
  DACA_TIMCTRL=0;  // Ignore the conversion interval. Your timer interrupt takes care of that.
  DACA_CTRLA=0x05; // Enable DAC0 output

If you use a different reference, change DACA_CTRLC according to the Xmega A manual (chapter 26.10.3).

Then, when you want to update the output voltage, set a new DAC value in the register DACA_CH0DATA. This should be in the range 0..4095.

If you use only 8 bits, you can either left-shift the data 4 bits before you write it to the DACA_CH0DATA register, or you can set the DAC input value to be left-adjusted (DACA_CTRLC, bit 0), and just write to the channel 0 high byte (DACA_CH0DATAH).

Additionally, there are some calibration bytes for the DAC, but don't bother with those just yet.

Oh – and remember not to have DAC power disabled in PR_PRPA or PR_PRPB.

You're absolutely right. This member is stupid. Please help.

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

Erik! Thankyou SO much for the help! I've done it! The chip on the Xaduino board is an ATXmega128a3 so I changed DACA to DACB but everything else was the same. I had a look at chapter 26 of the manual. Actually very straightforward. I had to change a few things, namely the clock speed was half of what it should be and I had to do some playing around with bit shifting of the output and the DC restore and clip section in the interrupt. I've also got the output coming out of both DAC channels.I did away with using the standard Arduino void setup() and void loop() and put all of the initialisation in a seperate function to be called in main().
The code is below:

//Modification of GordonJCP's FMTOY to work on XMEGA128a3.
// Uses on board DAC as opposed to PWM.
//Controlled by Bluetooth through Serial port 0 from an Android tablet.
//Thanks to Erik.T (AVR_Freaks) for the advice on using the DAC.


#include
#include
#include 
#include 

#define SERIAL

// 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
};

// MIDI note pitches in Hz
PROGMEM float pitchtable[] = {
8.18,  8.66,  9.18,  9.72,  10.30,  10.91,  11.56,  12.25,  12.98,  13.75,  14.57,  15.43,  16.35,  17.32,  18.35,  19.45,  20.60,  21.83,  23.12,  24.50,  25.96,  27.50,  29.14,  30.87,  32.70,  34.65,  36.71,  38.89,  41.20,  43.65,  46.25,  49.00,  51.91,  55.00,  58.27,  61.74,  65.41,  69.30,  73.42,  77.78,  82.41,  87.31,  92.50,  98.00,  103.83,  110.00,  116.54,  123.47,  130.81,  138.59,  146.83,  155.56,  164.81,  174.61,  185.00,  196.00,  207.65,  220.00,  233.08,  246.94,  261.63,  277.18,  293.66,  311.13,  329.63,  349.23,  369.99,  392.00,  415.30,  440.00,  466.16,  493.88,  523.25,  554.37,  587.33,  622.25,  659.26,  698.46,  739.99,  783.99,  830.61,  880.00,  932.33,  987.77,  1046.50,  1108.73,  1174.66,  1244.51,  1318.51,  1396.91,  1479.98,  1567.98,  1661.22,  1760.00,  1864.66,  1975.53,  2093.00,  2217.46,  2349.32,  2489.02,  2637.02,  2793.83,  2959.96,  3135.96,  3322.44,  3520.00,  3729.31,  3951.07,  4186.01,  4434.92,  4698.64,  4978.03,  5274.04,  5587.65,  5919.91,  6271.93,  6644.88,  7040.00,  7458.62,  7902.13,  8372.02,  8869.84,  9397.27,  9956.06,  10548.08,  11175.30,  11839.82,  12543.85, 
};

typedef struct {
    // Patch structure
    // parameters stored as bytes, containing values from 0-127
    // exactly as received over MIDI

    // Operator 1
    byte op1_ratio;   // 0-127, only 0-7 useful
    byte op1_detune;  // -64 to 63, u64 = 0
    byte op1_lfo;     // 0-127, scaled
    byte op1_gain;    // 0-127, scaled
    byte op1_env;     // -64 to 63, u64 = 0
    byte op1_a, op1_d, op1_s, op1_r;

    // Operator 2
    byte op2_ratio;
    byte op2_detune;
    byte op2_lfo;
    byte op2_gain;
    byte op2_env;
    byte op2_a, op2_d, op2_s, op2_r;
    
    // lfo
    byte lfo_rate;
    byte lfo_shape;
    byte lfo_delay;
   
    // instrument
    byte portamento;
    byte fb; 
} patch;

// if you add more patches, don't forget to change the limit in set_patch()
PROGMEM patch patches[] = {
    {2, 64, 3, 5, 82, 0, 83, 0, 75, 2, 64, 3, 64, 127, 68, 80, 75, 64, 25, 0, 0, 0, 73, },
{1, 64, 0, 77, 127, 0, 75, 0, 75, 7, 64, 0, 64, 127, 0, 91, 0, 0, 64, 0, 0, 0, 38, },
    {6, 64, 0, 3, 98, 0, 79, 0, 75, 2, 64, 0, 64, 127, 0, 80, 75, 64, 24, 0, 0, 0, 0, },
    {1, 64, 5, 49, 127, 48, 101, 0, 75, 2, 64, 5, 64, 127, 0, 80, 75, 64, 19, 0, 0, 0, 56, },
    {7, 64, 0, 12, 79, 0, 75, 0, 75, 2, 64, 0, 64, 127, 0, 91, 0, 90, 64, 0, 0, 0, 0, },
    {7, 64, 0, 12, 79, 0, 75, 0, 75, 1, 64, 0, 64, 127, 0, 91, 0, 90, 64, 0, 0, 0, 0, },
    {1, 64, 0, 12, 79, 0, 75, 0, 75, 7, 64, 0, 64, 127, 0, 91, 0, 90, 64, 0, 0, 0, 0, },
    {4, 64, 0, 2, 94, 0, 66, 0, 75, 2, 64, 0, 64, 127, 0, 80, 75, 64, 24, 0, 0, 0, 37, },
    {5,56,2,23,100,12,76,3,23,11,12,23,42,76,34,22,86,45,11,21,61,29,56},
};
#define N_PATCH 8

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

// envelopes
byte eg1_phase, eg2_phase;
float eg1_rate[3], eg1_rate_level[3];
float eg2_rate[3], eg2_rate_level[3];
float eg1_s, eg2_s;
float eg1, eg2;

double dfreq;
double tfreq;


char note;
char velocity;
char modwheel;
char cutoff;

// const double refclk=31372.549;  // =16MHz / 510
const double refclk=31250.0;

// midi functions
#define NOTEQUEUE 12
char notes[NOTEQUEUE];

byte st, p1, p2;

// variables used inside interrupt service declared as voilatile
volatile byte icnt;              // var inside interrupt
volatile byte icnt1;             // var inside interrupt
volatile byte y1, fb1;           // feedback
volatile byte y2;
volatile unsigned int fb2;    // mod depth

volatile unsigned long phaccu1;   // phase accumulator
volatile unsigned long phaccu2;   // phase accumulator
volatile byte fb;

volatile unsigned int gain1;
volatile unsigned int gain2;
volatile unsigned long tword_m1;  // dds tuning word m
volatile unsigned long tword_m2;  // dds tuning word m

volatile byte do_update;

// lfo
unsigned int tword_lfo;
unsigned int phaccu_lfo;
float lfo;
byte lfo_icnt;

float portamento;

// controllers
float bend, mod=1;

int i; // random general purpose counter
patch current;

// Wiring gets in the way of using structs
// so we just do both in one handler
void set_env() {
    float k;
    eg1_s = current.op1_s/127.0;    
    k = exp(-4*(current.op1_a/127.0)*2.3);
    eg1_rate_level[0] = k * 0.99; // attack time
    eg1_rate[0] = 1 - k;
    k = exp(-4*(current.op1_d/127.0)*2.3);
    eg1_rate_level[1] = k * eg1_s * 0.99;
    eg1_rate[1] = 1 - k;
    k = exp(-4*(current.op1_r/127.0)*2.3);
    eg1_rate_level[2] = 0; // final level
    eg1_rate[2] = 1-k;
    
    eg2_s = current.op2_s/127.0;    
    k = exp(-4*(current.op2_a/127.0)*2.3);
    eg2_rate_level[0] = k * 0.99; // attack time
    eg2_rate[0] = 1 - k;
    k = exp(-4*(current.op2_d/127.0)*2.3);
    eg2_rate_level[1] = k * eg2_s * 0.99;
    eg2_rate[1] = 1 - k;
    k = exp(-4*(current.op2_r/127.0)*2.3);
    eg2_rate_level[2] = 0; // final level
    eg2_rate[2] = 1-k;
    
}

void set_patch(int p) {
    // Fetch a patch from ROM
    
    if (p > N_PATCH) p=N_PATCH;// only have one patch! update when we add more
    patch *ptr = &patches[p];
    memcpy_P(¤t, ptr, sizeof(patch));
    fb = current.fb;
    set_env();
    tword_lfo = pow(2,16)*current.lfo_rate/4000;
}
void initialization ( void ) 
{ 
 cli ();   // Disable interrupts 
 // # # # # # # # # # # # # # # # # #
 
 OSC_PLLCTRL = 16;  // PLL mult. factor ( 2MHz x8 ) and set clk source to PLL. 

OSC_CTRL = 0x10;    // Enable PLL 

while( !( OSC_STATUS & 0x10 ) );    // Is PLL clk rdy to go ? 

CCP = 0xD8; //Unlock seq. to access CLK_CTRL 
CLK_CTRL = 0x04;  // Select PLL as sys. clk. These 2 lines can ONLY go here to engage the PLL ( reverse of what manual A pg 81 says ) 
 // # # # # # # # # # # # # # # # # # Timer0 - 1Khz
  TCC0.CTRLA  = TC_CLKSEL_DIV256_gc ;  // Prescaler 256
  TCC0.CTRLB  =  0x00 ;  // select mode: Normal
 TCC0.PER  =  4 ;  // counter top value of 1000 Hz  // 16000000/256/2 for interrupt :32KHz
 TCC0.CNT  =  0x00 ;  // Reset counter
 TCC0.INTCTRLA  =  0b00000011 ;  // Interrupt High Level 
 
 // # # # # # # # # # # # # # # # Share # # interrupts high-, medium-and lowlevel
 PMIC.CTRL  |= PMIC_HILVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_LOLVLEN_bm ;
 // Setting up the DAC Single Conversion mode.    
   DACB.CTRLB=0b01000000;
   DACB.CTRLC=0x00;
   DACB.TIMCTRL=0;
   DACB.CTRLA=0b00001101;
 sei ();  // interrupts enabled 
} 

void setup2()
{
  
  Serial.begin(38400);        // connect to the Serial port
  Serial.println("FMToy");
 

  
  
  set_patch(5);
  
  //pinMode(6, OUTPUT);      // sets the digital pin as output
 // pinMode(7, OUTPUT);      // sets the digital pin as output
//  pinMode(11, OUTPUT);     // pin11= PWM  output / frequency output
 pinMode(2, INPUT);
 // pinMode(13, OUTPUT);
 // digitalWrite(2, HIGH);
 // digitalWrite(13, LOW);
 // digitalWrite(7, LOW);
 // pinMode(3, OUTPUT);

 // digitalWrite(6, LOW);
  analogWrite(3, 255);

  

  // disable interrupts to avoid timing distortion
  
}

int main(void)
{
 setup2();
  initialization();
  while(1){
   // st=0x90;p1=69;p2=125;
  byte note = 60;
  while(Serial.available()<4) {
     if (do_update) {
      do_update=0;
      st=Serial.read();

      if (st != 0xff) {

        // crufty, not all statuses expect two values
        if (st >= 0x80 ) { // note off
          do {
            p1 = Serial.read(); 
           
          } while (p1 == 0xff);
          if (st != 0xc0) do {  // don't wait for a second byte
            p2 = Serial.read(); 
            
          } while (p2 == 0xff);
        }

       int sw=digitalRead(2);
 
        if ((st == 0x90 && p2 == 0) || st == 0x80) {
         eg1_phase=2;
            eg2_phase=2;
             digitalWrite(13, LOW);  // LED off
        }
          
          int j, k;
          k = notes[0];  // keep current key
          // remove note from note queue
          for(i=0; i 0) {
          // scan for highest note
          int j, k;
          k = notes[0];
         for(i=0; i=notes[i]) 
              // nudge rest down
             // for(j=NOTEQUEUE-1; j>=i; j--) 
                notes[j] = notes[j];
              
              notes[i]=p1;
              Serial.println(k);
              break;
                    
          }
            
          if (notes[0]!=k) {
            // top note released
            note = notes[0];
             
            tfreq=pgm_read_float_near(pitchtable+note);
          }
          
            // note on, set target frequency
            if (k==0) {
                // new note;
                eg1_phase=0;
                eg2_phase=0;
                velocity = p2;
                phaccu1=0;
                phaccu2=0;
                
            }

            digitalWrite(13, HIGH);  // LED on
        }
       if (st == 0xB0) {
          switch(p1) {
            // based on Novation BassStation parameters
            case 28: current.op1_gain = p2; break;   // Op1 Level
            case 29: {
                current.op1_lfo = p2; // set LFO level for both
                current.op2_lfo = p2;
                break;
            }
            case 30: current.op1_env = p2; break;    // Op1 EG Depth
            case 108: {
                current.op2_a = p2;
                set_env();
                break; 
            }
            case 109: {
                current.op2_d = p2;
                set_env();
                break; 
            }
            case 110: {
                current.op2_s = p2;
                set_env();
                break; 
            }
            case 111: {
                current.op2_r = p2;
                set_env();
                break; 
            }
            case 114: {
                current.op1_a = p2;
                set_env();
                break; 
            }
            case 115: {
                current.op1_d = p2;
                set_env();
                break; 
            }
            case 116: {
                current.op1_s = p2;
                set_env();
                break; 
            }
            case 117: {
                current.op1_r = p2;
                set_env();
                break; 
            }
            
            case 41:    // osc1 ratio
                current.op1_ratio = p2;
                break;
            case 23:    // feedback
                current.fb = p2;
                fb = current.fb;
                break;
            case 16:    // lfo speed
                current.lfo_rate = p2;
                tword_lfo = pow(2,16)*p2/4000;
                break;
            case 17:    // lfo speed
                current.op1_detune=p2;
                
                break;
            
            case 105:    // "real" cutoff
                analogWrite(3, p2*2);
                break;
#ifdef Serial
            case 127:
                if (p2 == 127) {
                    int i;
                    char *x;
                    x = (char *)¤t;
                    Serial.print("{");
                    for (i=0; i8)
          pa=0;
      }
      
      // update the voice
     
      phaccu_lfo += tword_lfo;
      lfo_icnt = phaccu_lfo >> 8;
      lfo = (pgm_read_byte_near(sine256+lfo_icnt)-127)/128.0f;
   
      eg1 = eg1_rate_level[eg1_phase] + eg1_rate[eg1_phase] * eg1;
      if (!eg1_phase && eg1 > 0.98) eg1_phase=1;

      eg2 = eg2_rate_level[eg2_phase] + eg2_rate[eg2_phase] * eg2;
      if (!eg2_phase && eg2 > 0.98) eg2_phase=1;
      
      
      gain1 = (current.op1_gain<<1) + (eg1 * ((current.op1_env-64)<<1));// + keyfollow ;
      if (gain1 < 0) gain1 = 0;
      
      gain2 = (current.op2_env<<1)*eg2;
 
      //dfreq = portamento*tfreq+(1-portamento)*dfreq;
      dfreq=tfreq;
      tword_m1 = pow(2,32)*((dfreq* current.op1_ratio)/2 * (1+(lfo*current.op1_lfo)/256.0))/refclk;
      tword_m2 = pow(2,32)*((dfreq* current.op2_ratio)/2 * (1+(lfo*current.op2_lfo)/256.0))/refclk;
    }
  
   
  }
}
 }
//******************************************************************
// timer2 setup
// set prscaler to 1, PWM mode to phase correct PWM,  16000000/510 = 31372.55 Hz clock

// Serial timer interrupt
//******************************************************************
// 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)
volatile int out;
ISR ( TCC0_OVF_vect ) {
  // internal timer
  if(icnt1++ > 31) { // slightly faster than 1ms
    do_update=1;
    icnt1=0;
   }   
  
    // operator 1
  phaccu1=phaccu1+tword_m1;
  icnt=phaccu1 >> 24;
  y1 = pgm_read_byte_near(sine256 + ((icnt+fb1) % 256));
  fb1 = fb*(y1 + fb1) >> 8;

    // operator 2
  phaccu2=phaccu2+tword_m2;
  icnt=phaccu2 >> 24;  
  //fb2 = gain1*(y1) >> 8-(gain1>>1);
  fb2 = ((gain1*y1)>>6)-(gain1>>1);
  y2 = pgm_read_byte_near(sine256 + ((icnt+fb2)%256));  

    // set the PWM

   out = ((gain2*y2)>>8)-(gain2>>1);

    // DC restore and clip output
   out += 127;
    out >>=1;
    if (out<0) out=0;
    if (out>0xff) out = 0xff;
   
    DACB.CH0DATA=(out<<4);
    DACB.CH1DATA=(out<<4);
}

A very nice FM synthesis engine with 2 operators, ADSR for both operators, 2 LFOs , OP gains, envelope gains and a patch structure that enables you to make your own patches (very DX7 :p ). At the moment I am controlling it from an Android device via bluetooth using a cheap bluetooth module for the Arduino. The gui was programmed using Processing for Android and the Ketai library. At the moment it is just a screen containing buttons to trigger notes and sliders for control of most of the synthesis parameters. What I want to do next is add more operators which is now possible thanks to the DAC. Also I am thinking of a more interesting gui for control..Maybe using cellular automata or some other kind of evolving graphical interface...
Again, thanks so much for helping. I always feel like I've acheieved something when I have success using the registers directly. :D

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

You're very welcome, Steve.

I usually find accessing the registers directly the easiest way to do things. I mean, I have to know the microcontroller functions well anyway, so I see no reason to use some obfuscating functions that may or may not do what I expect them to.

You're absolutely right. This member is stupid. Please help.