I2C DAC not working within Timer ISR and other weirdness

I am posting this up because I’ve really hit a wall here, and I’m hoping to find a direct solution, otherwise I’ll have to switch to a Wire library that doesnt use interrupts. The program below is a simple Sinewave Generator, wherein I am supposed to get the analog Sine output from an I2C DAC (MCP4725). I borrowed most of the code from Amanda’s Waveform Generator here: http://www.instructables.com/id/Arduino-Waveform-Generator/
But I modified her code and stripped it down to its bare essentials… and replaced her Parallel Port outputs with I2C instead, focusing on outputting only a Sine wave from the I2C DAC and nothing more.

I included the option for the ISR to generate DAC output either via Adafruit’s I2C DAC library OR via my own function. Am not sure if this would help anyone later, but have a look at the simple I2C function I wrote below, ‘SendToI2cDAC’, that’s called by Timer1’s ISR. The problem here is that I can get an analog output if this I2C function is called from the main loop() but not when it is called from within the ISR. I have taken the usual precautions within the ISR function, such as avoiding Serial prints and using a volatile variable ‘flgTimer’ in order prevent the ISR from calling itself when it is already executing. But I still get absolutely NO output from MCP4725 and it’s quite frustrating to say the least.

So here’s part of my code (full program is attached below)… does anyone have any ideas as to how to get I2C working within the ISR?? And before anyone suggests moving the I2C function back to loop(), I want to make it clear that the very reason that I’m calling it from within the ISR is to ensure that the output is sent out at a fixed sampling rate. And yes, I have already tried enabling interrupts() within the ISR and disabling them as well, but none of these combinations help the I2C commands to go through successfully.

void SendToI2cDAC(unsigned int value){ //earlier, this function returned a combined byte value instead of void, but see comment at end of this function
  I2Cbuffer[0] = 0b01000000;       //Sets the buffer0 with control byte (010-Sets in Write mode)
  I2Cbuffer[1] = value >> 4;        //Puts the most significant bit values
  I2Cbuffer[2] = value << 4;        //Puts the Least significant bit values

  Wire.beginTransmission(MCP4725);  //Joins I2C bus with MCP4725 at 0x62 address
  Wire.write(I2Cbuffer[0]);         //Sends the control byte to I2C 
  Wire.write(I2Cbuffer[1]);         //Sends the MSB to I2C 
  Wire.write(I2Cbuffer[2]);         //Sends the LSB to I2C
  Wire.endTransmission();           //This line starts the actual transmission

  //return(I2Cbuffer[1] | I2Cbuffer[2]); //what WEIRDness! if this return is commented out and function changed to void, then Serial WORKS in loop but SLOWLY!! Hurray?? :/
}

//------------------------------------------------------------------------------------

ISR(TIMER1_COMPA_vect){ //timer 1 interrupt
  //UNFORTUNATE NOTE: ANY ATTEMPT TO CALL A SERIAL OR I2C FUNCTION WITHIN THIS TIMER INTERRUPT SEEMS TO FAIL MISERABLY
  if(!flgTimer) return;  //existing call has been interrupted while already processing an interrupt, so exit! Without this line, Arduino will hang and you'll need to Reset and/or upload an empty sketch just to get it back to normal
  flgTimer=false;
  
  //increment wave's t and reset each time it reaches period
  t++;
  if(t>=period) t=0;
  wavNum = t*wavInc; //calculate the offset you need to read the required sine value from the Lookup array
  wave = pgm_read_word(&(SineLookupForDAC[wavNum]));

  //Depending on the interrupt source, you may need to clear the interrupt flag here. Most Arduino interrupts are self-clearing.
  interrupts();//by default, interrupts are disabled in a timer interrupt, so re-enable them for I2C or SPI to work (EDIT: Doesn't seem to help either way)
  //Now send the final Output to the DAC within the Timer Interrupt itself -> supposed to give accurate output
  if(UseAdaI2cDACfunc) dac.setVoltage(wave, false);
  else SendToI2cDAC(wave); //previously => combinedValue = SendToI2cDAC(wave)

  //noInterrupts(); //turn off interrupts so we can't be interrupted while resetting our special variable
  flgTimer=true;
}

//------------------------------------------------------------------------------------

Also, here’s something weird that happened. Earlier, I had included Serial prints in the ISR, which I soon realized was a big no-no. So, I moved them to the main loop() instead. Initially, I had no luck with receiving any serial prints even from the loop function. But it’s the weirdest thing, for SOME reason, if I comment out the return value in the SendToI2cDAC function and change it to void, I do see the prints on the Serial Monitor but they come in verrryyy SLOWWLYYY, lol. I still dont get any analog output from the MCP DAC, so the Wire calls are clearly failing. As for the Serial output, I assume the Timer interrupt is happening so fast, that the Serial is only able to print out what’s left in its buffer? (not sure if that’s the right terminology for Arduino). But has anyone experienced this kinda weirdness before?

I2cDACwithinTimerISR.ino (10.5 KB)

Ignore this, brain fart.

Probably not related but should

 I2Cbuffer[0] = 0b01000000;       //Sets the buffer0 with control byte (010-Sets in Write mode)
  I2Cbuffer[1] = value >> 4;        //Puts the most significant bit values
  I2Cbuffer[2] = value << 4;        //Puts the Least significant bit values

be

 I2Cbuffer[0] = 0b01000000;       //Sets the buffer0 with control byte (010-Sets in Write mode)
  I2Cbuffer[1] = value >> 8;        //Puts the most significant bit values
  I2Cbuffer[2] = value << 4;        //Puts the Least significant bit values

and should the return value be

  return((I2Cbuffer[1] << 8) | I2Cbuffer[2]);

Try the following codes:

void SendToI2cDAC(unsigned int value)
{ //earlier, this function returned a combined byte value instead of void, but see comment at end of this function
  I2Cbuffer[0] = 0b01000000;       //Sets the buffer0 with control byte (010-Sets in Write mode)
  I2Cbuffer[1] = highByte(value);//value >> 4;        //Puts the most significant bit values
  I2Cbuffer[2] = lowByte(value);//value << 4;        //Puts the Least significant bit values

I'm not certain that your expectations are reasonable.

How can you expect to drive a bus which runs at 100KHz(or possibly 400KHz) with a 100KHz timer interrupt?

Is there a threshold lower timer interrupt frequency where you can see dac output?

cattledog:
I’m not certain that your expectations are reasonable.

How can you expect to drive a bus which runs at 100KHz(or possibly 400KHz) with a 100KHz timer interrupt?

Is there a threshold lower timer interrupt frequency where you can see dac output?

@cattledog has pin-pointed the actual problem why the data is not being written into the DAC. It is because, the MCU is not getting enough time (350 us, Fig-1) to finish the transaction of 35-bit (8 + 9 + 9 + 9) data over the I2C Bus within 10 us (1/100khz) timetick of update interval.


Figure-1:

1. The default speed of I2C Bus is 100 kHz, and it is this speed at which the OP has designed his sketch. So, the time required to transfer 35-bit data (8-bit slaveAddress, 8-bit ControlByte + 1-bit ACK, 8-bit HighByte Data + 1-bit ACK, 8-bit LowByte data + 1-bit ACK) is: 35x10 = 350 us; whereas, the available time is only 10 us being determined by the interrupt of TC1.

2. I2C Bus works on interrupt basis. When MCU has entered into ISR due to TC1 interrupt, the interrupt is globally disabled; however, the OP has enabled it in the ISR to allow the operation of I2C Bus.

3. I2C bus will take about 350 us time to finish data transaction; but, within this time there would be 34 more TC1 interrupts which would be honored as TC1 COMPA interrupt has the higher priority than the TWI interrupt. Moreover, each I2C Bus cycle would require from 80us - 90us to complete; whereas, there are higher priority interrupts at every 10 us interval. As a result, the I2C could never finish its bus cycle; the DAC never gets its input data; the output of DAC would naturally be NIL.

Solution:
Not possible using MCP4725 chip and 100 kHz (10 us) TC1 interrupt even using 400 kHz I2C Bus speed. At 400 kHz speed, the time required to transfer 35-bit data is: 87.5 us. If OP still want to use this chip, he has to lower down the sampling rate as suggested by @cattledog.

Alternately, the OP may to switch over to MCP4921 which is the same DAC as MCP4725 but SPI Port driven that supports data transfer rate as high as 8 MHz.

dac4725.png

@cattledog: Thank you for pointing that out, but the issue persisted even when I brought the sampling rate down to only 1KHz instead of the various values I tried between 10KHz to 50Khz. This was tested on a Pro Mini as well as a Duemilanova. And the only reason I tried such high sampling rates in the first place was because Amanda's original code allowed a frequency range upto 50kHz (half of the max 100kHz), but then I realized that was possible due to direct port manipulation with the DAC, but cannot work with the I2C DAC instead.

@GolamMostafa: Thank you for that detailed explanation!

If anyone's interested, this issue was solved with minimal change to the same code just by reducing the sampling rate to less than 10kHz and I also shifted the entire circuit to use a Mega 2560 instead, which worked flawlessly with my modified code. The Mega's faster processor obviously offers an advantage here, especially with its dedicated SDA & SCL pins for I2C.. and I am now able to switch between using either the I2C DAC or the port DAC directly to send out an analog output!