Saturday, October 11, 2014

Trinket / ATtiny85 Internal Temperature and Voltage Sensing


Here is some info that was considered for Getting Started with Adafruit Trinket but was left on the cutting room floor.


The ATtiny85 on Trinket has the ability to sense the voltage applied to it.  This can be useful to determine if a battery is getting a bit too low.  The ATtiny85 also has a built-in temperature sensor.  Some say they don't use it due to a perceived lack of sensitivity.  But the sensor is fairly accurate if calibrated and if a bit of decimal math is used to adjust the temperature values read.

The temperature calibration is discussed in the Atmel document AVR122 - Calibration of the AVR's internal temperature reference at http://www.atmel.com/Images/doc8108.pdf You may need to take some readings and adjust some values in the code to dial-in the most accurate values over a temperature span.

The reading of voltage and temperature requires reading two values using the ATtiny85 analog to digital converter.  Unlike using the analogRead function on external pins, the voltage and temperature are in special areas that are read in similar method to analogRead.  ATtiny85 internal registers are polled to get the values.

Floating point arithmetic is used which adds considerable code overhead to the compiled program.

The sketch below reads the voltage and temperature and outputs the values over a software serial link.  An FTDI cable or FTDI friend connected to Trinket Pin #0 will output the values to a USB port which may be viewed by a terminal emulator.

/* 
 * Trinket / ATtiny85 Temperature and Voltage Measurement
 * Adapted from
 * http://forum.arduino.cc/index.php/topic,26299.0.html,
 * http://www.mikrocontroller.net/topic/315667, and
 * http://goetzes.gmxhome.de/FOSDEM-85.pdf
*/
// Values will be output to Trinket pin #0 via software serial for this example.  
// The library SendOnlySoftwareSerial by Nick Gammon is used to save a pin over
// using the SoftwareSerial library 
// http://gammon.com.au/Arduino/SendOnlySoftwareSerial.zip

#include <SendOnlySoftwareSerial.h>
SendOnlySoftwareSerial Serial(0);       // Output values via serial out on Pin #0

void setup() {
  // Setup the Analog to Digital Converter -  one ADC conversion
  //   is read and discarded

  ADCSRA &= ~(_BV(ADATE) |_BV(ADIE)); // Clear auto trigger and interrupt enable
  ADCSRA |= _BV(ADEN);                // Enable AD and start conversion
  ADMUX = 0xF | _BV( REFS1 );         // ADC4 (Temp Sensor) and Ref voltage = 1.1V;
  delay(100);                         // Settling time min 1 ms, take 100 ms
  getADC();

  Serial.begin(9600);                 // set up serial output
}

void loop() {
  int i;
  int t_celsius; 
  uint8_t vccIndex;
  float rawTemp, rawVcc;
  
  // Measure temperature
  ADCSRA |= _BV(ADEN);           // Enable AD and start conversion
  ADMUX = 0xF | _BV( REFS1 );    // ADC4 (Temp Sensor) and Ref voltage = 1.1V;
  delay(100);                    // Settling time min 1 ms, wait 100 ms

  rawTemp = (float)getADC();     // use next sample as initial average
  for (int i=2; i<2000; i++) {   // calculate running average for 2000 measurements
    rawTemp += ((float)getADC() - rawTemp) / float(i); 
  }  
  ADCSRA &= ~(_BV(ADEN));        // disable ADC  

  // Measure chip voltage (Vcc)
  ADCSRA |= _BV(ADEN);  // Enable ADC
  ADMUX  = 0x0c | _BV(REFS2);    // Use Vcc as voltage reference,
                                 //    bandgap reference as ADC input

  delay(100);                    // Settling time min 1 ms, there is
                                 //    time so wait 100 ms

  rawVcc = (float)getADC();      // use next sample as initial average
  for (int i=2; i<2000; i++) {   // calculate running average for 2000 measurements
    rawVcc += ((float)getADC() - rawVcc) / float(i);
  }
  ADCSRA &= ~(_BV(ADEN));        // disable ADC
  
  rawVcc = 1024 * 1.1f / rawVcc;
  //index 0..13 for vcc 1.7 ... 3.0
  vccIndex = min(max(17,(uint8_t)(rawVcc * 10)),30) - 17;   

  // Temperature compensation using the chip voltage 
  // with 3.0 V VCC is 1 lower than measured with 1.7 V VCC 
  t_celsius = (int)(chipTemp(rawTemp) + (float)vccIndex / 13);  
                                                                                   
  Serial.print("Temp: ");
  Serial.println(t_celsius);
  Serial.print("Vcc: ");
  Serial.println(rawVcc);
}

// Calibration of the temperature sensor has to be changed for your own ATtiny85
// per tech note: http://www.atmel.com/Images/doc8108.pdf
float chipTemp(float raw) {
  const float chipTempOffset = 272.9;           // Your value here, it may vary 
  const float chipTempCoeff = 1.075;            // Your value here, it may vary
  return((raw - chipTempOffset) / chipTempCoeff);
}

// Common code for both sources of an ADC conversion
int getADC() {
  ADCSRA  |=_BV(ADSC);          // Start conversion
  while((ADCSRA & _BV(ADSC)));    // Wait until conversion is finished
  return ADC;
}

The calibration methods used – voltage compensation, along with user testing for values placed in the chipTemp function, can get the accuracy of measurements to within one degree Celsius.

If you wish to keep parts and pin usage to a minimum, reading the temperature with the onboard sensor may provide the capability you need.

In addition, there are several benefits to being able to measure the supply voltage.  If the project is run on battery, Trinket or connected devices could be turned off if power reaches a predetermined lower limit.