Go Down

Topic: Frequency modulation with arduino uno (Read 585 times) previous topic - next topic

jortband

Hey guys,

Currently I am busy making my own arduino digital/hybrid synth (digital; oscillators, LFO and ADSR and an analog filter and amplification section). The synth uses a 256 byte wavetable for its oscillator, which acts as input for an 8-bit pwm with a refresh rate of 31250 Hz.  The phase of the oscillator is tracked by means of a long and is incremented each time in an intterupt routine, by a phasedelta long variable.
Everything works perfectly excepts when I try to add frequency modulation. When frequency modulation is added the frequency of the oscillator drops.
The way I calculate the frequency modulation is the following: first I use the value of my modulation source (in this case the lfo) as an index in a fixedpoint lookup table for frequency modulation (unsigned int 6.10 format (10 bytes for the decimal value))  and multiply the phasedelta with this value trough a special function which deals with this specific fixed point math multiplication. The fixedmathmultiply function.
To counter the frequency drop I tried to do the calculation outside my main interrupt routine and do it in a different interrupt with a lower refresh rate (about 4000hz), but the oscillator still exhibits an unintended drop in frequency, probably because the interrupt routines still influence each other (have to wait before one is completed).

So my question is, does anybody know to optimize the multiplication process or knows how i can prioritize the interrupt routines in such a way that the do not influence each other?

Here is the increddibly slimmed down code [note: LFOwavetable is not included, because the code would other wise be to large for this post]:
Code: [Select]
#include <avr/interrupt.h>   //add interrupts
#include <avr/io.h>          //no idea
#include <avr/pgmspace.h>    //to store values in progmem
#include "Arduino.h"


//Array filled with values used for modulating the oscillator number with as conversion base 1024, so fixed point math can be used. contains 256 unsigned ints
prog_uint16_t freqModValArray[] PROGMEM = {
1024,1027,1030,1032,1035,1038,1041,1044,1047,1049,1052,1055,1058,1061,1064,1067,1070,1072,1075,1078,1081,1084,1087,1090,1093,1096,1099,1102,1105,1108,1111,1114,1117,1120,1123,1126,1129,1132,1135,1139,1142,1145,1148,1151,1154,1157,1160,1164,1167,1170,1173,1176,1179,1183,1186,1189,1192,1196,1199,1202,1205,1209,1212,1215,1219,1222,1225,1229,1232,1235,1239,1242,1245,1249,1252,1256,1259,1262,1266,1269,1273,1276,1280,1283,1287,1290,1294,1297,1301,1304,1308,1311,1315,1319,1322,1326,1329,1333,1337,1340,1344,1348,1351,1355,1359,1362,1366,1370,1373,1377,1381,1385,1388,1392,1396,1400,1404,1407,1411,1415,1419,1423,1427,1431,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1515,1519,1523,1527,1531,1535,1539,1544,1548,1552,1556,1561,1565,1569,1573,1578,1582,1586,1591,1595,1599,1604,1608,1612,1617,1621,1625,1630,1634,1639,1643,1648,1652,1657,1661,1666,1670,1675,1679,1684,1689,1693,1698,1702,1707,1712,1716,1721,1726,1730,1735,1740,1745,1749,1754,1759,1764,1768,1773,1778,1783,1788,1793,1797,1802,1807,1812,1817,1822,1827,1832,1837,1842,1847,1852,1857,1862,1867,1872,1877,1882,1888,1893,1898,1903,1908,1913,1919,1924,1929,1934,1940,1945,1950,1956,1961,1966,1972,1977,1982,1988,1993,1999,2004,2009,2015,2020,2026,2031,2037,2042,2048
};


//byte oscwaveform[2][256];
byte osc1waveform[256];
volatile unsigned long accumulatorosc1;
volatile unsigned long accumulatorLFO;
volatile int LFOval;
volatile unsigned long osc1count;
volatile unsigned long osc1phasedelta;
volatile unsigned long LFOphasedelta;

unsigned long standardclkfreq = 16000000L;
unsigned long samplingfreq = 31250L;
float conversionunit = (pow(2,32)/samplingfreq);
float a = 1.05946309;  //number used for calculating frequency of a note from a specific value
float frequency = 110; //setting the standard frequency for now

void setup(){
  noInterrupts();
  setuposc();
  setuppins();
  setuptimers();
  interrupts();
}

ISR(TIMER0_COMPA_vect){
  //routine that fires at a rate of 4000hz
  osc1count = fixedmathmultiply(osc1phasedelta,pgm_read_word(&(freqModValArray[LFOval/2])));
}

ISR(TIMER1_OVF_vect){
  OCR1B = updateLFO();
  OCR2A = updateosc1();
}

void setuposc(){
  accumulatorosc1 = 0;
  accumulatorLFO = 0;
  osc1count = 0;
  LFOval = 0;
 
  //preload a 50 percent square wave for osc1
  generateSquarewaveosc(50);
  osc1count = conversionunit*frequency;
  osc1phasedelta = osc1count;
  LFOphasedelta = 1 * conversionunit;  //operates at 1hz;
}

void setuppins(){
for(int i = 2; i<14;i++){
   pinMode(i,OUTPUT);
}
}

void setuptimers(){
  //to sync the timers
  //GTCCR = (1<<TSM)|(1<<PSRASY)|(1<<PSRSYNC); // halt all timers
   
  //set timer0 interrupt at 4kHz
  TCCR0A = 0;// set entire TCCR2A register to 0
  TCCR0B = 0;// same for TCCR2B
  TCNT0  = 0;//initialize counter value to 0
  // set compare match register for 2khz increments
  OCR0A = 61;// = (16*10^6) / (4000*64) - 1 (must be <256)
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  // Set CS01  bit for 64 prescaler
  TCCR0B |= (1 << CS01) | (1 << CS00);   
  // enable timer compare interrupt
  TIMSK0 |= (1 << OCIE0A);

 
 
  //setting up timer 1 with 9-bit fast pwm mode
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM12) | _BV(WGM11);  //setting the timer to 9-bit fast pwm mode with OCR1A/B as top
  //TCCR2B = _BV(CS20); //setting the prescaler to 1 (for future purposes might want to divide it by 8, so i can make more of a bass synthesizer, or even lower, to open up more calculation space.
  TCCR1B = _BV(WGM12)|_BV(CS10); //setting the prescaler to 1
  TIMSK1 = 0x01;        //Timer1 INT Reg: Timer1 Overflow Interrupt Enable
  OCR1A = 500;        //set overflow compare register to a random value, this one will be used for the LFO generation
  OCR1B = 50;
 
  //setting up timer 2 with 8 bit fast pwm mode
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20 );    //setting the timer to 8-bit fast pwm mode with OCR2A/B as top
  TCCR2B = _BV(CS20); //setting the prescaler to
  OCR2A = 127;        //set overflow compare register to a random value, this one will be used for the LFO generation
  OCR2B = 127;
 
  //syncing the timers
  // set all timers to the same value
  TCNT0 = 0; // set timer0 to 0
  TCNT1H = 0; // set timer1 high byte to 0
  TCNT1L = 0; // set timer1 low byte to 0
  TCNT2 = 0; // set timer2 to 0
 
  GTCCR = 0; // release all timers
 
}




void loop(){

}



void generateSquarewaveosc(int pw){
  //pw is a value between 1 and 99, best to limit this to 10 to 90
  byte switchpoint = 255*pw/100;
     for(int i =0; i<256 ; i++){
      if(i>switchpoint){
        osc1waveform[i] = 0;
      }else{
       osc1waveform[i] = 255;
      }

}




int updateLFO(){
  //function for updating the LFO
  //should have selectable waveshapes
  //has a 9-bit waveshape
  //should be able to cope with slow oscillations
  int pos;
  accumulatorLFO += LFOphasedelta;    //increment the LFO
  pos= accumulatorLFO>>23;            //position is a position between 0 and 511 (a 9-bit values)
  LFOval = pgm_read_word(&(LFOWavetables[pos]));
  return LFOval;
}

byte updateosc1(){
   //function for updating oscillator 1.
   byte val;          //value for storing the value of the oscillator
   byte pos;          //stores the phase position for the lookup table

   accumulatorosc1 += osc1count;      //increment the phase

   pos = accumulatorosc1>>24;          //get the phase position for the lookup table
   val = osc1waveform[pos];            //store the value of the lookup table at the pos position and return it
   return val;
}



unsigned long fixedmathmultiply(unsigned long number, int multiplier){
  unsigned long numberlsb = number & 65535ul; //get the lsb 16-bits of the number variable
  unsigned long numbermsb = number>>16 ;     // get the msb 16-bits of the number variable
  numberlsb = numberlsb*multiplier;
  numbermsb = numbermsb*multiplier;
  numberlsb = numberlsb>>10;
  numbermsb = numbermsb<<(6);
  unsigned long result = numbermsb+numberlsb;
  return result;
 
}

tmd3

The posted code doesn't compile for me.  It seems to be missing a '}' in the function generateSquarewaveosc().  That makes me suspect that this isn't the code you were actually testing, so I'm not willing to spend too much time on it.

This
Code: [Select]
ISR(TIMER0_COMPA_vect){
  //routine that fires at a rate of 4000hz
  osc1count = fixedmathmultiply(osc1phasedelta,pgm_read_word(&(freqModValArray[LFOval/2])));
}
definitely takes too long.  Testing its timing with this code
Code: [Select]
#include <avr/interrupt.h>   //add interrupts
#include <avr/io.h>          //no idea
#include <avr/pgmspace.h>    //to store values in progmem
#include "Arduino.h"

//Array filled with values used for modulating the oscillator number with as conversion base 1024, so fixed point math can be used. contains 256 unsigned ints
prog_uint16_t freqModValArray[] PROGMEM = {
  1024, 1027, 1030, 1032, 1035, 1038, 1041, 1044, 1047, 1049, 1052, 1055, 1058, 1061, 1064, 1067, 1070, 1072, 1075, 1078, 1081, 1084, 1087, 1090, 1093, 1096, 1099, 1102, 1105, 1108, 1111, 1114, 1117, 1120, 1123, 1126, 1129, 1132, 1135, 1139, 1142, 1145, 1148, 1151, 1154, 1157, 1160, 1164, 1167, 1170, 1173, 1176, 1179, 1183, 1186, 1189, 1192, 1196, 1199, 1202, 1205, 1209, 1212, 1215, 1219, 1222, 1225, 1229, 1232, 1235, 1239, 1242, 1245, 1249, 1252, 1256, 1259, 1262, 1266, 1269, 1273, 1276, 1280, 1283, 1287, 1290, 1294, 1297, 1301, 1304, 1308, 1311, 1315, 1319, 1322, 1326, 1329, 1333, 1337, 1340, 1344, 1348, 1351, 1355, 1359, 1362, 1366, 1370, 1373, 1377, 1381, 1385, 1388, 1392, 1396, 1400, 1404, 1407, 1411, 1415, 1419, 1423, 1427, 1431, 1434, 1438, 1442, 1446, 1450, 1454, 1458, 1462, 1466, 1470, 1474, 1478, 1482, 1486, 1490, 1494, 1498, 1502, 1506, 1510, 1515, 1519, 1523, 1527, 1531, 1535, 1539, 1544, 1548, 1552, 1556, 1561, 1565, 1569, 1573, 1578, 1582, 1586, 1591, 1595, 1599, 1604, 1608, 1612, 1617, 1621, 1625, 1630, 1634, 1639, 1643, 1648, 1652, 1657, 1661, 1666, 1670, 1675, 1679, 1684, 1689, 1693, 1698, 1702, 1707, 1712, 1716, 1721, 1726, 1730, 1735, 1740, 1745, 1749, 1754, 1759, 1764, 1768, 1773, 1778, 1783, 1788, 1793, 1797, 1802, 1807, 1812, 1817, 1822, 1827, 1832, 1837, 1842, 1847, 1852, 1857, 1862, 1867, 1872, 1877, 1882, 1888, 1893, 1898, 1903, 1908, 1913, 1919, 1924, 1929, 1934, 1940, 1945, 1950, 1956, 1961, 1966, 1972, 1977, 1982, 1988, 1993, 1999, 2004, 2009, 2015, 2020, 2026, 2031, 2037, 2042, 2048
};

volatile int LFOval;
volatile unsigned long osc1count;
volatile unsigned long osc1phasedelta;

void setup() {
  Serial.begin(115200);
  Serial.println("OK");
}

void loop() {
  const uint16_t iter = 31250;
  uint32_t st, fin, dur;
  st = micros();
  for (uint16_t i = 0; i < iter; i++) {
    osc1count = fixedmathmultiply(osc1phasedelta, pgm_read_word(&(freqModValArray[LFOval / 2])));
    LFOval++;
    LFOval &= 0xFF;
    osc1phasedelta += 0x123456U;
  }
  fin = micros();
  dur = fin - st;
  Serial.print(iter);
  Serial.print(" ");
  Serial.print(dur);
  Serial.print(" ");
  Serial.print((float)iter * 1000000.0 / (float)dur);
  Serial.println();
}

unsigned long fixedmathmultiply(unsigned long number, int multiplier) {
  unsigned long numberlsb = number & 65535ul; //get the lsb 16-bits of the number variable
  unsigned long numbermsb = number >> 16 ;   // get the msb 16-bits of the number variable
  numberlsb = numberlsb * multiplier;
  numbermsb = numbermsb * multiplier;
  numberlsb = numberlsb >> 10;
  numbermsb = numbermsb << (6);
  unsigned long result = numbermsb + numberlsb;
  return result;
}
yields this output:
Code: [Select]
OK
31250 1081680 28890.25
31250 1081720 28889.18
31250 1081716 28889.28
which is mostly your code, it takes about 1/29K =~ 34.4 us to run. There's only 32.0 us between Timer1 interrupts.  This code takes longer to run than the interval between Timer1 interrupts.  You'll miss a Timer1 interrupt from time to time, and your PWM frequency will appear to slow down.  It won't really slow down - it'll just keep the same value every now and then, and appear to slow down.  There's loop overhead in the test code, sure - but there's interrupt overhead in the ISR, too, what with all the pushing and popping.  I'm guessing that they're roughly the same, or, if they're not, that the test code runs a bit faster than the ISR.  That's an intuitive guess.

One way to alleviate this would be to only set a flag and maybe bump a counter in the Timer0 interrupt, and let loop() manage the calculations.  The Timer0 ISR won't overrun the time between Timer1 interrupts, and loop should have enough time, at 4 kHz, to keep up.

You may hope to keep loop() busy with other activities, so it won't be able to keep in step with the update task.  If that's so, you could try reenabling interrupts inside the Timer0 interrupt.  That way, though, is fraught with peril: it's hard enough to troubleshoot interrupts when they happen one at a time.  When they start interrupting each other, it can be really hard to tell what's going on.  This might work if you don't anticipate using other interrupts in your project.

You might want to consider using Timer2 instead of Timer0.  If you leave Timer0 alone, you'll have access to the Arduino timing functions.  Alternatively, if you keep a running count of Timer0 interrupts, you'll have a way to determine how long things take.

jortband

Hi,

Thanks for the reply. This was indeed and adapted version of my code.
I have implemented the interrupt in the timer 0 before, but as you stated, it does not have enough time to do the calculation within at interrupting at 4000hz, which as it turned out made the whole arduino freeze up. When I let the timer 0 interrupt fire at 2000 to 3000hz  it works like it should work. However the fidelity of the frequency modulation is not that great, it will suffice with an LFO with a fairly low frequency, but past the point of 50hz to 100hz  the changes do not change the modulation in a big way.
As i have the idea of making a bass synthesizer I am thinking of doing all the wave calculation, frequency modulation into the timer0 interrupt, freeing up more calculation time. Hopefully this will yield higher quality results. As soon as I have tested this I will post my findings here.

Thanks for the help!

tmd3

I like seeing abbreviated code.  It keeps me from having to pore through hundreds of irrelevant lines.  When you abbreviate code, though, I'll suggest that you test it before posting, and determine that it compiles, runs, and demonstrates the problem that you're trying to resolve.  If it doesn't compile or run, or it's overly complex, a lot of potential thread participants will skip it and move on to the next thing.  And, obviously, if it doesn't demonstrate the problem, posting the code won't help solve it.  Here's a snippet from "How to use this forum," found here - http://forum.arduino.cc/index.php/topic,148850.0.html - Item #11, "Tips for getting the most out of your post:"
Quote
Post a complete sketch (program code)! If you don't you waste time while people ask you to do that. However, with coding problems, if possible post a "minimal" sketch that demonstrates the problem - not hundreds of lines of code. If the problem goes away in the minimal sketch, it wasn't where you thought it was.


From what I see, running the timer at 2 kHz won't resolve the issue - it'll just make it appear less frequently.  The problem is that the TIMER0_COMPA ISR takes longer to execute than the time between TIMER1_OVF interrupts.  Depending on where in the TIMER1 cycle the TIMER0 interrupt occurs, it will execute through two TIMER1 interrupts, and you'll miss one.  The appearance, from an audio viewpoint, will be that the TIMER1 interrupt slows down; in fact, it will just be missing some updates, thus stretching out the output waveform in time. 

Another alternative might be to disable the Timer0 interrupt, poll the timer status in the in the Timer1 interrupt, and execute the Timer0 ISR code when Timer0 has fired.  The Timer0 code takes only a little longer than a Timer1 interrupt.  The fast PWM mode updates the OCR at rollover no matter when the OCR is changed, so, in theory, you have nearly twice the Timer1 duration to manage that calculation if you start early.  That might work, if the rest of the sketch can tolerate having the Timer1 ISR take control for more than its cycle time. 

Note that these notions are strictly intuitive - I haven't tested any of these theories, or tried any proposed solutions.

Go Up