Teensy 3.5 - Instrumentation Amp for Single-Ended Analog Recording

Status
Not open for further replies.

StefanM

Member
Hello Everybody,

I'm trying to assemble a two-stage instrumentation amplifier for differential analog signals to be recorded single-endedly on the teensy 3.5
My colleague Avner Wallach has created this circuit design for this purpose (the resistors and capacitors for filtering are just a suggestion):
two stage instrumental amp wallach.jpg

These are the core components:
  1. AD8225: first stage differential amplification
  2. OP2177: second stage OpAmp
  3. OP1177: level shifter (raises GND to midpoint between VSS and VPP)
  4. Passive RC highpass filter on input stage
  5. Sallen-Key lowpass filter on output stage

For recording the signals on the Teensy 3.5, I use a sketch that uses DMA to save incoming analog data to a buffer which is then flushed regularly to the SD card:
Code:
//*************************************************************************
// Two channel EOD logger programm for testing purposes
// Barebone: no RTC
//*************************************************************************

#include <ADC.h>              //library for easier ADC implementation
#include <DMAChannel.h>       //library for easier DMA implementation
#include <array>              //use C++ array structs
#include <SdFat.h>            // work with SD card


/*----------------------------------------------------------------*/
const uint32_t pdbfreq = 100000;  // sampling speed [Hz] max ~300MHz - actually the trigger frequency of the programmable delay block
uint32_t duration = 10;           // duration of each measure-cycle [s]
String Name = "Log";              // file name prefix
unsigned long debug_start;
/*----------------------------------------------------------------*/

/*DECLARATIONS adc and pins---------------------------------------*/
ADC adc;            // used to declare the adc.### object

/*PINS------------------------------------------------------------*/
const uint8_t adc_pin0 = A2;                    // A2 is connected to ADC0
const uint8_t adc_pin1 = A22;                   // A22 is connected to ADC1
/*----------------------------------------------------------------*/

/*Buffer declarations----------------------------------------------*/
std::array<volatile uint16_t, (uint32_t)64 * 512> buffer __attribute__ ((aligned (16 * 1024)));      // size of buffer is limited due to the Teensy's program memory
std::array<volatile uint16_t, (uint32_t)64 * 512> buffer1 __attribute__ ((aligned (16 * 1024)));

uint32_t      BUF_DIM       = 32768/2;                                          //size of buffer that holds data from ADC
uint32_t      FILE_SIZE     = 0;                                                //initial variables for filesize and pointer to...
uint32_t      last          = 0;     
uint32_t      last1         = 0;     
uint32_t      bytes         = 0;
float         preceil       = 0;
float         scale         = 0;
/*----------------------------------------------------------------*/

/*DECLARATIONS dma and tcd----------------------------------------*/
DMAChannel dma;                                 // used to declare the dma.### object for the first channel
DMAChannel dma1;                                // used to declare the dma.### object for the second channel

typeof(*dma.TCD)  tcd_mem[4] __attribute__ ((aligned (32))) ;   // alignment of four 32-byte blocks; needed for four different TCDs
typeof(*dma1.TCD)  tcd1_mem[4] __attribute__ ((aligned (32))) ;
/*----------------------------------------------------------------*/


/*DECLARATIONS microSD card files---------------------------------*/
SdFatSdioEX sd;                                  // used to declare the sd.### object (Sdfat);

uint16_t fileNr = 0;                             // after a given duration a new file is created; fileNr is an index used for the filename
uint16_t fileNr1 = 0;

File file;                                       // file object for logging data
File file1;                                      // file object for logging data
/*----------------------------------------------------------------*/


// function creates new files for data logging
/*----------------------------------------------------------------*/
void filestuff() {
  fileNr++;
  String filename = Name + "_dma0_" + fileNr + ".bin";
  char fname[30];
  filename.toCharArray(fname, 30);
  Serial.print("filename: ");
  Serial.println(filename);

  if (!file.open(fname, O_RDWR | O_CREAT)) {
    sd.errorHalt("open dma0 file failed");
  }
}

void filestuff1() {
  fileNr1++;
  String filename1 = Name + "_dma1_" + fileNr1 + ".bin";
  char fname1[30];
  filename1.toCharArray(fname1, 30);
  Serial.print("filename: ");
  Serial.println(filename1);
  if (!file1.open(fname1, O_RDWR | O_CREAT)) {
    sd.errorHalt("open dma1 file failed");
  }
}

void setup() 
{
  /*Serial monitor--------------------------------------------------*/
  debug_start = millis();
  Serial.begin(115200);
  while ((millis() - debug_start) <= 5000);
  //while (!Serial && ((millis() - debug_start) <= 5000));
  Serial.println("Begin Setup\n");
  /*----------------------------------------------------------------*/

  /*FileSetup-------------------------------------------------------*/
  String filename = Name + "_dma0_" + fileNr + ".bin";                        // create filenames
  char fname[30];
  filename.toCharArray(fname, 30);

  String filename1 = Name + "_dma1_" + fileNr + ".bin";
  char fname1[30];
  filename1.toCharArray(fname1, 30);

  Serial.println(filename);
  Serial.println(filename1);
       
  if (!  sd.begin()) {                                                        // start sdio interface to SD card
    sd.initErrorHalt("SdFatSdio   begin() failed");
  }    sd.chvol();
  if (!file.open(fname, O_RDWR | O_CREAT)) {                                  // create SD card files
    sd.errorHalt("open dma0 failed");
  }
  if (!file1.open(fname1, O_RDWR | O_CREAT)) {
    sd.errorHalt("open dma1 failed");
  }

  delay(100);
  /*----------------------------------------------------------------*/

  /*DurationSetup---------------------------------------------------*/
  bytes = ((duration*1000000)/(1000000/pdbfreq))* 2; 
  preceil = bytes/BUF_DIM;
  scale = ceil(preceil);                                                    // round up preceil value
  FILE_SIZE = (scale+2) * BUF_DIM;                                          // after writing FILE_SIZE uint16_t values to a file a new file is created
  /*----------------------------------------------------------------*/

  /*Mode Setup------------------------------------------------------*/
  pinMode(13, OUTPUT);                                                     // built-in LED is at PIN 13 in Teensy 3.5
  pinMode(adc_pin0, INPUT);                                                // configure as analog input pins
  pinMode(adc_pin1, INPUT);
  /*----------------------------------------------------------------*/

  /*ADC Setup-------------------------------------------------------*/
  adc.startSingleRead(adc_pin0, ADC_0);                                    // start ADC conversion
  adc.startSingleRead(adc_pin1, ADC_1);

  adc.setAveraging       (                              1  );              // ADC configuration
  adc.setResolution      (                           16, 0  );
  adc.setConversionSpeed ( ADC_CONVERSION_SPEED::HIGH_SPEED);
  adc.setSamplingSpeed   ( ADC_SAMPLING_SPEED::HIGH_SPEED  );

  adc.setAveraging (1, ADC_1);
  adc.setResolution (16, ADC_1);
  adc.setConversionSpeed ( ADC_CONVERSION_SPEED::HIGH_SPEED, ADC_1);
  adc.setSamplingSpeed   ( ADC_SAMPLING_SPEED::HIGH_SPEED, ADC_1  );

  adc.setReference(ADC_REFERENCE::REF_3V3, ADC_0);                         // set analog reference
  adc.setReference(ADC_REFERENCE::REF_3V3, ADC_1);
  /*----------------------------------------------------------------*/

  /* DMA ----------------------------------------------------------*/
  dma.source                 (           ADC0_RA);                         // source is the ADC result register
  dma.transferSize           (                 2);                         // set 2, one uint16_t value are two bytes
  dma.triggerAtHardwareEvent (DMAMUX_SOURCE_ADC0);                         // DMAMUX alignes source to DMA channel

  dma1.source                 (           ADC1_RA);
  dma1.transferSize           (                 2);
  dma1.triggerAtHardwareEvent (DMAMUX_SOURCE_ADC1);
  /*----------------------------------------------------------------*/
  
  /*TCD-------------------------------------------------------------*/

  // configure TCD for first dma
  dma.TCD->CITER    =           16 * 512;
  dma.TCD->BITER    =           16 * 512;
  dma.TCD->DOFF     =                  2;                                  // set 2, one uint16_t value are two bytes
  dma.TCD->CSR      =               0x10;

  dma.TCD->DADDR        = (volatile void*) &buffer [ 0 * 512]  ;
  dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[       1]  ;           // points to a 32-byte block that is loaded into the TCD memory of the DMA after major loop completion
  memcpy ( &tcd_mem[0], dma.TCD , 32 ) ;                                   // 32-byte block is transferred to &tcd_mem[0]

  dma.TCD->DADDR        = (volatile void*) &buffer [16 * 512]  ;
  dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[       2]  ;
  memcpy ( &tcd_mem[1], dma.TCD , 32 ) ;

  dma.TCD->DADDR        = (volatile void*) &buffer [32 * 512]  ;
  dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[       3]  ;
  memcpy ( &tcd_mem[2], dma.TCD , 32 ) ;

  dma.TCD->DADDR        = (volatile void*) &buffer [48 * 512]  ;
  dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[       0]  ;
  memcpy ( &tcd_mem[3], dma.TCD , 32 )  ;

  memcpy ( dma.TCD ,  &tcd_mem[0], 32 ) ;                                  // 16-byte block that is transferred into the TCD memory of the DMA

  // equal configuration for TCD of  second dma1

  dma1.TCD->CITER    =           16 * 512;
  dma1.TCD->BITER    =           16 * 512;
  dma1.TCD->DOFF     =                  2;
  dma1.TCD->CSR      =               0x10;

  dma1.TCD->DADDR        = (volatile void*) &buffer1 [ 0 * 512]    ;
  dma1.TCD->DLASTSGA     = (   int32_t    ) &tcd1_mem[       1]    ;
  memcpy ( &tcd1_mem[0], dma1.TCD , 32 ) ;

  dma1.TCD->DADDR        = (volatile void*) &buffer1 [16 * 512]    ;
  dma1.TCD->DLASTSGA     = (   int32_t    ) &tcd1_mem[       2]    ;
  memcpy ( &tcd1_mem[1], dma1.TCD , 32 ) ;

  dma1.TCD->DADDR        = (volatile void*) &buffer1 [32 * 512]    ;
  dma1.TCD->DLASTSGA     = (   int32_t    ) &tcd1_mem[       3]    ;
  memcpy ( &tcd1_mem[2], dma1.TCD , 32 ) ;

  dma1.TCD->DADDR        = (volatile void*) &buffer1 [48 * 512]    ;
  dma1.TCD->DLASTSGA     = (   int32_t    ) &tcd1_mem[       0]    ;
  memcpy ( &tcd1_mem[3], dma1.TCD , 32 )  ;

  memcpy ( dma1.TCD ,  &tcd1_mem[0], 32 ) ;
  /*----------------------------------------------------------------*/

  /*Start DMA and ADC-----------------------------------------------*/
  dma.enable();                                                             // enable DMA
  dma1.enable();

  adc.enableDMA(ADC_0);                                                     // connect DMA and ADC
  adc.enableDMA(ADC_1);

  adc.adc0->stopPDB();                                                      // start PDB conversion trigger
  adc.adc0->startPDB(pdbfreq);

  adc.adc1->stopPDB();
  adc.adc1->startPDB(pdbfreq);

  NVIC_DISABLE_IRQ(IRQ_PDB);                                               // we don't want or need the PDB interrupt

  adc.adc0->printError();                                                  // print ADC configuration errors
  adc.adc1->printError();
  /*----------------------------------------------------------------*/


  /*Debug-----------------------------------------------------------*/
  Serial.println(BUF_DIM);
  Serial.println(FILE_SIZE);
  Serial.print("bytes: ");
  Serial.println(bytes);
  Serial.println((uint32_t)&buffer[ 0], HEX);                               // debug: print memory location of buffer
  Serial.println((uint32_t)&buffer[ 16 * 512], HEX);
  Serial.println((uint32_t)&buffer[ 32 * 512], HEX);
  Serial.println((uint32_t)&buffer[ 48 * 512], HEX);
  Serial.println((uint32_t)&buffer1[ 0], HEX);
  Serial.println((uint32_t)&buffer1[ 16 * 512], HEX);
  Serial.println((uint32_t)&buffer1[ 32 * 512], HEX);
  Serial.println((uint32_t)&buffer1[ 48 * 512], HEX);
  Serial.println("----------------------------------");
  /*----------------------------------------------------------------*/

  /*Signal end of Setup method--------------------------------------*/
  for (int i = 0; i < 5; i++){                                             // visual feedback, blink 5 times if the setup was completed
    digitalWrite(13, HIGH);
    delay(300);
    digitalWrite(13, LOW);
    delay(300);
  }
}


void loop() {  
  while ( ((64*1024-1) & ( (int)dma.TCD->DADDR - last )) > BUF_DIM ){  
    if (BUF_DIM != (uint32_t)file.write( (char*)&buffer[((last/2)&(32*1024-1))], BUF_DIM) ){ 
      sd.errorHalt("write dma0 failed");    
      }
    last += BUF_DIM ;  
    
    if (BUF_DIM != (uint32_t)file1.write( (char*)&buffer1[((last1/2)&(32*1024-1))], BUF_DIM) ){ 
      sd.errorHalt("write dma1 failed");
      }
    last1 += BUF_DIM ;
  } 
  /*----------------------------------------------------------------*/
  if ( last >= FILE_SIZE ) {                                              // check if end of file is reached
    file.close();
    last = 0;                                                             // reset last
    filestuff();                                                          // create new files for data logging
  }
  if ( last1 >= FILE_SIZE ) {                                              // check if end of file is reached
    file1.close();
    last1 = 0;                                                             // reset last
    filestuff1();                                                          // create new files for data logging
    
    // blink LED to signal end of recording
    digitalWrite(13, HIGH);
    delay(5000);
    digitalWrite(13, LOW);
  }
  /*----------------------------------------------------------------*/
}

While the amp circuit performs well when I read its output with an oscilloscope, I haven't been successful in recording the signal on the teensy.
We tried the following configuration:
  1. Power supply: 4.5V (VPP = 4.5V, VSS = 0V, GND = 2.25V)
  2. Input signal: 1kHz sine wave, ~ 100mV peak-to-peak amplitude
  3. Amplifier settings: 5x gain, 300Hz highpass and 20kHz lowpass filter on ADC0; 10x gain, 300Hz highpass and 50kHz lowpass filter on ADC1
  4. Teensy was powered by VPP (on VIN) and VSS (on AGND)
  5. Recording settings: 100 kS/s, 3.3V internal reference

The results are completely shifted sine waves that are clipping/jumping from the minimum to the maximum:
ADC0:
dma0.png
ADC1:
dma1.png

I guess, I must have made a mistake in wiring or shifting the input level but at the moment, I can't find where I'm going wrong here. Can anybody help me out?
One thing that I noticed is that I should maybe connect the level-shifted GND (2.25V) to the Teensy, to set the analog zero? Only, if I e.g. connect it to the Teensy GND, I get weird results:
ADC0:
dma0.png
ADC1:
dma1.png

If I feed one input channel to the differential input for ADC0 (A11) and use the 2.25V GND as second input (A10) and use a teensy sketch for differential recordings, I measure an okay sine-wave with occasional clippings in the negative phase:
diff_clipping.png
Obviously, this defeats the purpose of having a differential amplifier at all... Does anybody have a suggestion?
 
Your first graph has a scale that goes negative. But in single ended mode, the output of the ADC is unsigned. Mix up signed and unsigned and you get strange jumps at 32767.
 
When I look for a data sheet on the AD8225 IA, what I find looks nothing like your schematic. The AD8225 is a single device in an 8 pin package with fixed gain and no Rg.

What are you really using?
 
Your first graph has a scale that goes negative. But in single ended mode, the output of the ADC is unsigned. Mix up signed and unsigned and you get strange jumps at 32767.

AH!! Damn, you're right, I used a python script that I originally wrote for the differential output *facepalm*
Changed datatype to unsigned integer and get this beautiful sine-wave
ADC0:
uint_dma0.png

ADC1:
uint_dma1.png

Seems like either my input is not a true 1kHz wave or my sampling frequency is off - I'll look into that

When I look for a data sheet on the AD8225 IA, what I find looks nothing like your schematic. The AD8225 is a single device in an 8 pin package with fixed gain and no Rg.

What are you really using?

Sorry, typo, it's the AD8224. We're currently looking for a different first stage though because the minimum operating voltage of the AD8224 is about 4.2V and we'd like to make the whole circuit battery-compatible. So if anybody knows a comparable 3.3V alternative, I'm open for suggestions (but will look around myself as well).

Thank you!!

*edit: can't edit my initial post to correct the model number to AD8224 - hope everybody who's interested sees this here
 
One of these might solve your battery problem. Battery powered by 18650 cell, outputs 5v and 3.3v. Use the 5v and power the teensy as well, or 3.3v to teensy and 5v to ADC.
Battery charger and holder.JPG
 
You could also add an inverting charge pump - the AD8224 will run on 3.3V and -3.3V.
 
A couple of things -

adding a resistor of a few k between the opamp output and Teensy pin will protect the teensy pin should it
not be powered up when the opamp is.

The virtual ground circuit places a heavy capacitive load on the OP1177R, probably causing it to oscillate. Opamps
aren't designed to drive capacitive loads.

Virtual grounds don't need heavy decoupling like this, just have decoupling between Vpp and Vss (where it matters
for an opamp), and add a capacitor to the resistor divider to short out all the Johnson noise going into the OP1177R
so its not putting that onto the virtual ground. I think you have Vss and digital ground at the same potential,
so add the capacitor between Vss and the resistor divider node.

If you feed virtual ground (via a resistor) to another analog pin on the teensy you can compensate for the
virtual ground voltage when taking measurements.

Oh, there's no need for a precision opamp to derive virtual ground, but good bandwidth and output current
are useful to keep the virtual ground impedance nice and low across the frequencies of interest.
 
One of these might solve your battery problem. Battery powered by 18650 cell, outputs 5v and 3.3v. Use the 5v and power the teensy as well, or 3.3v to teensy and 5v to ADC.
View attachment 22664

You could also add an inverting charge pump - the AD8224 will run on 3.3V and -3.3V.

Thank you for those suggestions! I believe that a lower voltage first stage might be the easier solution but in case this proves difficult, these are great fallbacks!
About the inverting charge pump - would that affect the way we shift the signal for single ended recording (i.e. could we work with a virtual GND of 1.65V and power the AD8224 with +-3.3V)?

A couple of things -

adding a resistor of a few k between the opamp output and Teensy pin will protect the teensy pin should it
not be powered up when the opamp is.

The virtual ground circuit places a heavy capacitive load on the OP1177R, probably causing it to oscillate. Opamps
aren't designed to drive capacitive loads.

Virtual grounds don't need heavy decoupling like this, just have decoupling between Vpp and Vss (where it matters
for an opamp), and add a capacitor to the resistor divider to short out all the Johnson noise going into the OP1177R
so its not putting that onto the virtual ground. I think you have Vss and digital ground at the same potential,
so add the capacitor between Vss and the resistor divider node.

If you feed virtual ground (via a resistor) to another analog pin on the teensy you can compensate for the
virtual ground voltage when taking measurements.

Oh, there's no need for a precision opamp to derive virtual ground, but good bandwidth and output current
are useful to keep the virtual ground impedance nice and low across the frequencies of interest.

That's a good tip for protecting the teensy - especially if we'd employ separate power sources which might lead to the teensy being unpowered while the amp circuit is running.

Do I read you correctly in that you're suggesting to change the virt. GND circuit from this (original):
virtual ground circuit.png

To this (?):
virtual ground circuit_decoupled.png
 
Last edited:
> a virtual GND of 1.65V and power the AD8224 with +-3.3V)?

You would want the AD8224 to operate without a virtual ground. And the 2nd stage needs power too. Probably not worth the changes, I'd go with one of your other options.
 
aye, true, the capacitor would render the voltage divider useless the way I drew it. Rather like this then (sorry, I'm taking small steps here):
virtual ground circuit_decoupled.png

I tried reading up more about suggested capacity for such a capacitor but I guess that varies a lot with the application and voltage invovled. With a voltage of 4.5V between VSS and VPP, would you say that 1uF is sufficient?
 
Here is some very general advice. Learn the basics of LTSpice and use it, even for simple circuits. Never perfect, but it's a great way to learn things like "what would happen if I injected a little noise here".
 
Hi everyone,
thank you for the advice! We have now learned some LTSpice basics and have included the suggested decoupling capacitors in our voltage divider circuit. We have troubleshot and developed the amp a bit and have now arrived at a configuration that yields robust amplification and bandpass filtering (see attached picture).
In this configuration, the Teensy 3.5 and amplifier are supplied with voltage from a 5V powerbank. Voltage input is amplified differentially, bandpass filtered and "lifted" to oscillate around a virtual ground of 1.66V for single-ended sampling on the teensy. The virtual ground is created by a LD1117V33 voltage regulator and a simple voltage divider circuit that is stabilized by an OP1177.
In the next step, we want to add surge protection for the ADCs - I read that clamping diodes are the suggested solution here. I couldn't find anything about the tolerance of the ADC above and below the 0-3.3V input limits. Does anybody know where to find these specifications or have suggestions as to which diodes would be a good fit?

Inst amp circuit.png

Any advice/ideas/criticism for the amplifier design are greatly appreciated! :)
 
It may not be possible in your case, but in general, use differential all the way to two teensy inputs.
 
It may not be possible in your case, but in general, use differential all the way to two teensy inputs.

We aim to maximize the number of channels that we can record with one teensy - going single-ended enables us to record on two channels very easily (and my colleague Sebastian Volkmer even successfully recorded on eight channels using only one teensy: https://github.com/muchaste/EOD-Logger).

Are there more benefits to differential recording on the teensy than just increasing the input voltage range?

Cheers!
 
Hello Everybody,

I'm trying to assemble a two-stage instrumentation amplifier for differential analog signals to be recorded single-endedly on the teensy 3.5

My observations on this circuit.

Firstly the AD8225 and OP2177 aren't rail-to-rail, which can be very limiting operating from a single 5V.
Have a look at devices like the OPA1692 or AD8586 for rail-to-rail audio opamps (the latter is 5V only
in fact).

Secondly the AD8225 is noisy, much noisier than opamps designed for audio at 45nV/√Hz. It does seem
suited from bandwidth and distortion perspective though. <= 5nV/√Hz is commonly achieved in audio
devices (ie a full 19dB quieter). 45nV/√Hz translates to 6µV rms refered to the input, so after a gain
of x55, that's 350µV rms on the output, pretty audible....

Thirdly you have full gain down to DC - fortunately the devices you've chosen have very low input offsets
so they (although multiplied by x55 gain) don't lead to excessive output offset voltage.
(low input offsets are not normally needed for audio as you simply block DC in enough places to keep the
DC gain low - audio amps should not be amplifying DC in the first place, note).

Fourthly the inputs hove no bleeder resistors so if the input caps are charged when connecting a loud crack
will be heard. 1M to analog ground on the inputs would be good.

Fifthly the virtual ground circuit has several issues. The decoupling should only be to 0V, not the supply as
well (that will inject rail noise directly into the system). The opamp is loaded with large amounts of capacitance
(the 1177 can handle 1nF or so of capacitive load, but 1uF will be destabilizing it, it may be oscillating). Perhaps
add 10--30 ohms on the opamp output to separate it from the harsh capacitive load - definitely want to check for
oscillations at this node.
The voltage divider needs decoupling to 0V too, otherwise the (divided) rail noise will be present on the output of the
opamp and fighting the decoupling there unnecessarily.

Sixthly the high values of resistance in the filter will be adding noise - large value resistors in series add voltage
noise - reducing all the values of R and scaling the C's up to match will simply reduce the noise (and input offset
error too). As a general rule in low-noise opamp design aim to keep at or below 10k impedances if possible. Most
opamps are happy to drive down to 2k loads, some handle 500 ohm loads well.

The feedback network for the filter stage can be 1k/10k, not 10k/100k, and you'll reduce noise at no cost there too.

If you've never done low-noise design a few simple rules can help - as little series resistance as possible (10k
maximum), and only use opamps with a voltage and current noise spectral density specification. For audio
5nV/√Hz and 0.5pA/√Hz or better are great to have, you'll be unlikely to have issues with noise then.

[ edit: I see you actually meant the AD8224 which is a completely different chip... Well that's quieter at least,
but its set to a gain of 50 so noise could still be an issue and the input offset could be a problem, and its has
no distortion figure quoted (meaning it could be a poor performer, especially at a high gain setting) ]
 
Last edited:
Thanks for the detailed review!

My observations on this circuit.

Firstly the AD8225 and OP2177 aren't rail-to-rail, which can be very limiting operating from a single 5V.
Have a look at devices like the OPA1692 or AD8586 for rail-to-rail audio opamps (the latter is 5V only
in fact).

Secondly the AD8225 is noisy, much noisier than opamps designed for audio at 45nV/√Hz. It does seem
suited from bandwidth and distortion perspective though. <= 5nV/√Hz is commonly achieved in audio
devices (ie a full 19dB quieter). 45nV/√Hz translates to 6µV rms refered to the input, so after a gain
of x55, that's 350µV rms on the output, pretty audible....

[Edit: yes, it's the AD8224 ^^]
I couldn't find anything about the AD8586 that you mentioned but the OPA1692 seems like a good replacement for the OP2177.

Thirdly you have full gain down to DC - fortunately the devices you've chosen have very low input offsets
so they (although multiplied by x55 gain) don't lead to excessive output offset voltage.
(low input offsets are not normally needed for audio as you simply block DC in enough places to keep the
DC gain low - audio amps should not be amplifying DC in the first place, note).

Is this a property of the amps or should we add more caps to block DC? We have the Highpass filter at the inputs to reduce DC noise there but where else would we place them?

Fourthly the inputs hove no bleeder resistors so if the input caps are charged when connecting a loud crack
will be heard. 1M to analog ground on the inputs would be good.

Thanks, I'll check that!

Fifthly the virtual ground circuit has several issues. The decoupling should only be to 0V, not the supply as
well (that will inject rail noise directly into the system). The opamp is loaded with large amounts of capacitance
(the 1177 can handle 1nF or so of capacitive load, but 1uF will be destabilizing it, it may be oscillating). Perhaps
add 10--30 ohms on the opamp output to separate it from the harsh capacitive load - definitely want to check for
oscillations at this node.
The voltage divider needs decoupling to 0V too, otherwise the (divided) rail noise will be present on the output of the
opamp and fighting the decoupling there unnecessarily.

You might refer to the older version of the vgnd-circuit but this probably also still apply to the newer design. I have implemented your former tip of a decoupling cap at the voltage divider, however, I should add the resistors to the opamp output. But would you say that the latest version of the vgnd circuit is sufficiently decoupled?

Sixthly the high values of resistance in the filter will be adding noise - large value resistors in series add voltage
noise - reducing all the values of R and scaling the C's up to match will simply reduce the noise (and input offset
error too). As a general rule in low-noise opamp design aim to keep at or below 10k impedances if possible. Most
opamps are happy to drive down to 2k loads, some handle 500 ohm loads well.

The feedback network for the filter stage can be 1k/10k, not 10k/100k, and you'll reduce noise at no cost there too.

If you've never done low-noise design a few simple rules can help - as little series resistance as possible (10k
maximum), and only use opamps with a voltage and current noise spectral density specification. For audio
5nV/√Hz and 0.5pA/√Hz or better are great to have, you'll be unlikely to have issues with noise then.

I learned that high resistance should keep the energy consumption of the amp low (which is one important factor as we want to deploy these on battery-driven dataloggers). But of course, we want to keep the noise low (we're not using it for audio purposes but rather to amplify electric signals from fish :) ). I guess, I'll just test different configurations and measure the energy consumption in action, as soon as I can return to my lab.

Thank you for the advice, much appreciated!
 
I think I erroneously assumed from "recorded" that this was audio (been doing a lot of this recently) -
if this a data-logger style application then the 1/f noise knee would be more important than general
noise (many datasheets show total noise in the 0.1--10Hz range, this is a good guide). But you are
blocking DC at the inputs so I guess that also made me think audio rather than data logging!

If you are needing micro-power design then other performance figures will be compromized - so its
great to actually list what performance and power consumption you want, in case you have to compromize
between the two.
 
> Are there more benefits to differential recording on the teensy than just increasing the input voltage range?

Yes, differential into the teensy reduces common mode noise. But whether it is significant depends on the details.

I haven't looked closely (ie, could be wrong), but you may get better performance and a much simpler design using the INA1650.

Edit: also consider the TLE2426 as a rail splitter.
 
Last edited:
Hello everybody,

it's been a while. Thanks to your technical support and the collaboration of some colleagues, I've built my first functional amp for the teensy 3.5.
It is designed to differentially amplify and bandpass filter analog inputs, delivering two single-ended outputs to the teensy ADCs. The baseline of the output is at 1.66V, allowing the signals to oscillate between 0-3.3V. Higher voltages are shunted through diodes to protect the analog in. What else.... there are switches to change filter settings (e.g. 1,100,300 Hz high-pass and 7,30kHz low-pass) and gain (5,30,180x).
Here's the schematic and the hand-soldered PCB :)

teensy amp.jpg

P7200836_downsampled.jpg
 
Status
Not open for further replies.
Back
Top