In my recent post about using Bluemix and Arduinos,  I had to port the Bluemix sample temperature sensor reading code which was written for the Arduino Uno to the Yun.

Both the Uno and the Yun use microprocessors from Atmel, however, the Uno uses the ATmega328P and the Yun uses the ATmega32U4. Each of these chips supports an internal temperature sensor that can be read using an internal analog-to-digital convertor (ADC), however, the calibration, setup and process of reading are not the same.

The original Bluemix sample code uses the following to read the Uno temperature sensor.

double getTemp(void) {
  unsigned int wADC;
  double t;
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN); // enable the ADC
  delay(20); // wait for voltages to become stable.
  ADCSRA |= _BV(ADSC); // Start the ADC
  // Detect end-of-conversion
  while (bit_is_set(ADCSRA,ADSC));
  // Reading register "ADCW" takes care of how to read ADCL and ADCH.
  wADC = ADCW;
  // The offset of 324.31 could be wrong. It is just an indication.
  t = (wADC - 324.31 ) / 1.22;
  // The returned temperature is in degrees Celcius.
  return (t);
}

 
This is using two registers — ADMUX and ADCSTA — to control the temperature conversion process. Various bits are set in the registers to configure the process the the ADSC bit is set in ADCSRA to start the conversion and reset when the conversion ends. Note that the Arduino IDE supports the names of the registers and the bits within the registers which is a nice feature.

Yun schematic
ATmega32U4-ADC-Schematic-500

The Yun, in comparison, uses three control registers — ADMUX, ADCSTA and ADCSTB — to first configure the temperature conversion process. It does however use the ADSC bit in the ADCSRA to start the conversion and test for completion.

32u4ADCRegisters
The Atmel docs for the Yun’s processor recommend that we throw away the first reading, so the first call to getTemp() should be discarded.

void setupADC(){
 
  //ADC Multiplexer Selection Register
  ADMUX = 0;
  ADMUX |= (1 << REFS1);  //Internal 2.56V Voltage Reference with external capacitor on AREF pin
  ADMUX |= (1 << REFS0);  //Internal 2.56V Voltage Reference with external capacitor on AREF pin
  ADMUX |= (0 << MUX4);  //Temperature Sensor - 100111
  ADMUX |= (0 << MUX3);  //Temperature Sensor - 100111
  ADMUX |= (1 << MUX2);  //Temperature Sensor - 100111
  ADMUX |= (1 << MUX1);  //Temperature Sensor - 100111
  ADMUX |= (1 << MUX0);  //Temperature Sensor - 100111
 
  //ADC Control and Status Register A 
  ADCSRA = 0;
  ADCSRA |= (1 << ADEN);  //Enable the ADC
  ADCSRA |= (1 << ADPS2);  //ADC Prescaler - 16 (16MHz -> 1MHz)
 
  //ADC Control and Status Register B 
  ADCSRB = 0;
  ADCSRB |= (1 << MUX5);  //Temperature Sensor - 100111
}

double getTemp() {

  ADCSRA |= (1 << ADSC);  //Start temperature conversion
  while (bit_is_set(ADCSRA, ADSC));  //Wait for conversion to finish

  // ADCW combines ADCL and ADCH into single 16 bit number
  double temperature = ADCW;
  return temperature - 273.4;
}

 

An Honest Temperature

I had a physics prof at school who was a stickler for reporting the results of experiments. He insisted that we write up experiments in pen as opposed to pencil (otherwise he would rant that we “didn’t have confidence in our results”) and demanded error bars on the tolerance of the presumed accuracy of the result — along with error estimate calculations. And more importantly to a young student, he would deduct marks for being too precise. This has stuck with me: An inappropriate precision is misleading and, to my way of thinking, not an honest representation of information.

The original Uno code uses floating point precision to report the temperature. However, even when calibrated the chip temperature sensors in typical Atmel microprocessors are accurate to +-2% and uncalibrated can be +/10%. The sensor readings are ballpark temperature numbers, so we should also report the data with less precision — so I’ve chosen to use integers for the temperature as opposed to the floating point and to use a a rounded number for the Kelvin to Celsius conversion.

This gives the final version of the code: A realistic (if not too accurate) temperature value.

void setupADC(){
 
  //ADC Multiplexer Selection Register
  ADMUX = 0;
  ADMUX |= (1 << REFS1);  //Internal 2.56V Voltage Reference with external capacitor on AREF pin
  ADMUX |= (1 << REFS0);  //Internal 2.56V Voltage Reference with external capacitor on AREF pin
  ADMUX |= (0 << MUX4);  //Temperature Sensor - 100111
  ADMUX |= (0 << MUX3);  //Temperature Sensor - 100111
  ADMUX |= (1 << MUX2);  //Temperature Sensor - 100111
  ADMUX |= (1 << MUX1);  //Temperature Sensor - 100111
  ADMUX |= (1 << MUX0);  //Temperature Sensor - 100111
 
  //ADC Control and Status Register A 
  ADCSRA = 0;
  ADCSRA |= (1 << ADEN);  //Enable the ADC
  ADCSRA |= (1 << ADPS2);  //ADC Prescaler - 16 (16MHz -> 1MHz)
 
  //ADC Control and Status Register B 
  ADCSRB = 0;
  ADCSRB |= (1 << MUX5);  //Temperature Sensor - 100111
}

double getTemp(){

  ADCSRA |= (1 << ADSC);  //Start temperature conversion
  while (bit_is_set(ADCSRA, ADSC));  //Wait for conversion to finish

  // We could report an precise number but accuracy is only +/-2% at best.  
  // take an honest approach to reporting the temp as we know it */
  byte low  = ADCL;
  byte high = ADCH;
  int temperature = (high << 8) | low;  //Result is in kelvin
  return temperature - 273;
}

Post Navigation