Using Differential ADC atmega32U or at90usb1286 (Teensy or Teensy++) w/Thermocouple

Status
Not open for further replies.

Dave X

Well-known member
I've been fiddling with the x1 X10 and X200 gain differential ADC on the Teensy++ and hooked it up to a Type K thermocouple. Here's my code so far:

Code:
/* SPICE-ish Connections:

USBcable Teensy2.0++  Computer # direct connection to computer  
Vth A0 A1  # type K thermocouple, 41 uV/C

*/
// Teensy ++ at90usb1286-specific ADMUX settings per http://www.pjrc.com/teensy/at90usb1286.pdf p.328  
#define ADMUX_MASK    0b11111  // 
#define DIFF_1_0_x1   0b10000
#define DIFF_1_0_x10  0b01001
#define DIFF_1_0_x200 0b01011
#define DIFF_3_2_x1   0b11011
#define DIFF_3_2_x10  0b01101
#define DIFF_3_2_x200 0b01111

#define ADC0x10       0b01000
#define ADC0x200      0b01010
#define ADC2x200      0b01110

void setup()
{                
  Serial.begin(38400);
  analogReference(INTERNAL); // 2.56V
  DIDR0 |= (1 <<0);
  DIDR0 |= (1 <<1);
}

int val,temp;

char buffer[64];

int analogReadAdmux(int admux){
  // Read ADC based on an ADMUX setting 
  // teensy++ or at90usb1286-specific 
   int low,result;  
   ADMUX = (ADMUX & ~ ADMUX_MASK) | admux;
   ADCSRA |= (1<<ADSC);
   while (ADCSRA & (1<<ADSC)) ;
   low = ADCL;
   result =(ADCH<<8)|low;
   if (result >= 0x200) result |= ~(~0U >>8);
   return result;  
}


int fracpart(float val,int prec){
   if (val >= 0 )
      return int((val-int(val))*prec);
   else
      return int((int(val)-val)*prec);
}


void loop()                     
{
  float v,c;
  int iv,ic;
  val = analogReadAdmux(DIFF_1_0_x200 | 0b11000000 ); // 2.56V
  Serial.print("analog 1-0 x200 reads: ");
  v=val*2.56/200/512;
  
  c=v/41e-6;   // Type : ~ 41u/C
  sprintf(buffer,"%d %d.%03d mV %d.%03d C\n",val,int(v*1000),fracpart(v*1000,1000),int(c),
                      fracpart(c,1000));
  Serial.print(buffer);
  delay(250);
}


It does jump around a bit, but it is still pretty cool:

Code:
analog 1-0 x200 reads: 5 0.124 mV 3.048 C
analog 1-0 x200 reads: 0 0.000 mV 0.000 C
analog 1-0 x200 reads: 0 0.000 mV 0.000 C
analog 1-0 x200 reads: 2 0.049 mV 1.219 C
analog 1-0 x200 reads: -3 0.074 mV -1.829 C
analog 1-0 x200 reads: 3 0.074 mV 1.829 C
analog 1-0 x200 reads: 5 0.124 mV 3.048 C
analog 1-0 x200 reads: 5 0.124 mV 3.048 C
analog 1-0 x200 reads: 9 0.225 mV 5.487 C
... in oven:

analog 1-0 x200 reads: 232 5.799 mV 141.463 C
analog 1-0 x200 reads: 231 5.775 mV 140.853 C
analog 1-0 x200 reads: 232 5.799 mV 141.463 C
analog 1-0 x200 reads: 235 5.875 mV 143.292 C
analog 1-0 x200 reads: 232 5.799 mV 141.463 C
analog 1-0 x200 reads: 238 5.949 mV 145.121 C
analog 1-0 x200 reads: 236 5.899 mV 143.902 C
analog 1-0 x200 reads: 230 5.750 mV 140.243 C
analog 1-0 x200 reads: 239 5.974 mV 145.731 C
... in freezer:
analog 1-0 x200 reads: -28 0.700 mV -17.073 C
analog 1-0 x200 reads: -24 0.599 mV -14.634 C
analog 1-0 x200 reads: -26 0.650 mV -15.853 C
analog 1-0 x200 reads: -21 0.525 mV -12.804 C
analog 1-0 x200 reads: -19 0.475 mV -11.585 C
analog 1-0 x200 reads: -28 0.700 mV -17.073 C
.... in boiling water
analog 1-0 x200 reads: 130 3.249 mV 79.268 C
analog 1-0 x200 reads: 135 3.375 mV 82.317 C
analog 1-0 x200 reads: 136 3.400 mV 82.926 C
analog 1-0 x200 reads: 147 3.674 mV 89.634 C
analog 1-0 x200 reads: 144 3.599 mV 87.804 C

Wtih a Teensy 2.0's internal temp sensor, I could correct for the cold junction temperature and have a large-range temp logger for pretty cheap.

For increased thermal resolution, I guess I could use a much smaller Vref and not do the x200 gain. If I divide 5VDC with a 47K:100 to make a 10.25mV AREF, I wouldn't need the internal gain, and maybe I could measure +/-250C with about 1/2C resolution and accuracy within a few degrees.

It doesn't look like Teensy 3.0 has programmable gain on the ADCs, so the small AREF might be a super-cheap option for reading a thermocouple.

Anyone using the gain functions? The differential ADCs? Or tiny AREFs?
 
Last edited:
I would expect there is going to be some front-end noise that is the same level in millivolts, regardless of Vref, so at some point reducing Vref will not increase resolution, because the electronic noise will exceed the quantization noise. But it is fun to find out where that point is! The true analog thermocouple amps, eg. from Analog Devices, are pretty expensive.
 
From the measurements above, it looks like my alligator-clip-breadboard circuit has about 8 counts of noise. At x200 gain and 2.56V AREF, 1 count is 2.56/512/200=25uV, so that's already sub-millivolt at <200uV.

It would be interesting to see what is in the external circuit and what is internal amp/quantization error.

I guess one could test the internals with a couple voltage dividers, one for AREF, and one for the ADC0-ADC1 difference. Or maybe a multi-tap divider between VCC, AREF, ADC1, ADC0, and GND, then watch the ADC0 x1, x10, and x200 or ADC1-ADC0 x1, x10, and x200 gains.
 
Last edited:
Here's another differential ADC code for the Teensy and Teensy++:

Code:
#define MUX_1V1 0b011110  // works for both
#define ADMUX_MASK 0b11111  // MUX4..0 in ADMUX works for both
#define ADSCRB_MUXMASK (1<MUX5)  // MUX5 in
#define AREF_VCC (1<<REFS0)
#define AREF_2V56 ((1<<REFS0) | (1<<REFS1))
#define AREF_AREF 0

#define TENBIT_1V1_MV ((long)(1023L * 1000 * 1.1))


int analogReadMux(int aref,int mux,int twosComp=0){
  // Read ADC based on an ADMUX setting 
  // teensy++ or at90usb1286-specific 
   int low,result;  
   #if defined(__AVR_AT90USB1286__)  // Teensy ++ 2.0

   ADMUX = aref | (mux & ADMUX_MASK);
      
   #elif defined (__AVR_ATmega32U4__) // Teensy 2.0

   ADMUX = aref | (mux & ADMUX_MASK);
   ADCSRB = (ADCSRB & ~(1<<MUX5)) | (mux & (1<<MUX5));
        
   #else
   #error "analogReadMux defined only for Teensy 2.0 or Teensy++ 2.0"
   #endif
   delay(2);
   ADCSRA |= (1<<ADSC);
   while (ADCSRA & (1<<ADSC)) ;
   low = ADCL;
   result =(ADCH<<8)|low;
   if (twosComp and result >= 0x200) result -= 0x3ff;
   return result;  
}




long readVcc() {
  long result;
  
  result= analogReadMux(AREF_VCC, MUX_1V1,0);
 
  result = TENBIT_1V1_MV / result; // Back-calculate AVcc in mV
  return result;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.print("Vcc: ");
  Serial.print( 1.0*readVcc()/1000, 3 );

#if defined (__AVR_ATmega32U4__)
  Serial.print("V Temp: ");
  Serial.print((analogReadMux(AREF_2V56,0b100111,0)-273),DEC);
  Serial.print("C ");
#endif
  Serial.print(" 1.1/2.56: ");
  Serial.print(2.56*(analogReadMux(AREF_2V56,MUX_1V1,0)/1023.),3);
  Serial.print(" 0V(GND)/2.56: ");
  Serial.print(2.56*(analogReadMux(AREF_2V56,0b011111,0)/1023.),3);
  Serial.print("V"); 
 
  Serial.print(" 0V(GND)/2.56: ");
  Serial.print(2.56*(analogReadMux(AREF_2V56,0b011111,0)/1023.),3);
  Serial.print("V"); 
 
  Serial.print("  ADC1-ADC0 x1: ");
  Serial.print(2.56*(analogReadMux(AREF_2V56,0b010000,1)/512.0),3);
  Serial.print("V"); 

  Serial.print("  ADC1-ADC0 x10: ");
  Serial.print(2.56*(analogReadMux(AREF_2V56,0b001001,1)/512.0/10),6);
  Serial.print("V"); 

 #if defined (__AVR_ATmega32U4__)
  Serial.print("  ADC1-ADC0 x40: ");
  Serial.print(2.56*(analogReadMux(AREF_2V56,0b100110,1)/512.0/40),6);
  Serial.print("V"); 
#endif

  Serial.print("  ADC0-ADC1 x200: ");
  Serial.print(2.56*(analogReadMux(AREF_2V56,0b001011,1)/512.0/200),6);
  Serial.print("V"); 
 
  Serial.println("");
   
  delay(1000);
}

Some output from my Teensy 2.0, with a jumper between A0 and A1:

Code:
Vcc: 5.046V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002500V  ADC1-ADC0 x40: 0.000500V  ADC0-ADC1 x200: 0.000050V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000375V  ADC0-ADC1 x200: 0.000000V
Vcc: 5.068V Temp: 42C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.003000V  ADC1-ADC0 x40: 0.000375V  ADC0-ADC1 x200: 0.000050V
Vcc: 5.068V Temp: 42C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.005V  ADC1-ADC0 x1: 0.025V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000375V  ADC0-ADC1 x200: 0.000150V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.001500V  ADC1-ADC0 x40: 0.000500V  ADC0-ADC1 x200: 0.000100V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002500V  ADC1-ADC0 x40: 0.000625V  ADC0-ADC1 x200: 0.000050V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000500V  ADC0-ADC1 x200: 0.000000V
Vcc: 5.068V Temp: 42C  1.1/2.56: 1.111 0V(GND)/2.56: 0.005V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.015V  ADC1-ADC0 x10: 0.002500V  ADC1-ADC0 x40: 0.000500V  ADC0-ADC1 x200: 0.000000V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.005V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000500V  ADC0-ADC1 x200: 0.000000V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.025V  ADC1-ADC0 x10: 0.002500V  ADC1-ADC0 x40: 0.000625V  ADC0-ADC1 x200: 0.000000V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.114 0V(GND)/2.56: 0.005V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.025V  ADC1-ADC0 x10: 0.002500V  ADC1-ADC0 x40: 0.000375V  ADC0-ADC1 x200: 0.000175V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.114 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000500V  ADC0-ADC1 x200: -0.000050V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000375V  ADC0-ADC1 x200: -0.000025V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.114 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000625V  ADC0-ADC1 x200: 0.000075V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.025V  ADC1-ADC0 x10: 0.002500V  ADC1-ADC0 x40: 0.000500V  ADC0-ADC1 x200: 0.000000V
Vcc: 5.068V Temp: 43C  1.1/2.56: 1.111 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.020V  ADC1-ADC0 x10: 0.002000V  ADC1-ADC0 x40: 0.000375V  ADC0-ADC1 x200: 0.000075V

Looks like there's some calibration issues between the different amplifications.

With a Teensy++'s at90usb1286 chip, the 1V1 bandgap, and differential A0:A1 x1, x10 and x200 MUX[4..0] codes are the same, but there isn't a temperature sensor nor the x40 gain. I've added some #ifdefs, and tested it on a Teensy++ 2.0:

Code:
Vcc: 4.957 1.1/2.56: 1.096 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.045V  ADC1-ADC0 x10: 0.003000V  ADC0-ADC1 x200: 0.000125V
Vcc: 4.979 1.1/2.56: 1.096 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.035V  ADC1-ADC0 x10: 0.004000V  ADC0-ADC1 x200: 0.000125V
Vcc: 4.979 1.1/2.56: 1.096 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.040V  ADC1-ADC0 x10: 0.003500V  ADC0-ADC1 x200: 0.000075V
Vcc: 4.979 1.1/2.56: 1.096 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.035V  ADC1-ADC0 x10: 0.003000V  ADC0-ADC1 x200: 0.000000V
Vcc: 4.957 1.1/2.56: 1.096 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.000V  ADC1-ADC0 x1: 0.035V  ADC1-ADC0 x10: 0.003500V  ADC0-ADC1 x200: 0.000075V
Vcc: 4.979 1.1/2.56: 1.096 0V(GND)/2.56: 0.003V 0V(GND)/2.56: 0.003V  ADC1-ADC0 x1: 0.035V  ADC1-ADC0 x10: 0.004000V  ADC0-ADC1 x200: 0.000100V
 
Last edited:
Status
Not open for further replies.
Back
Top