The NerdKit Temperature Sensor

Temperature sensorOne of the first NerdKits projects involves programming a temperature sensor, not unlike the one that comes built in to the Sprite. The temperature sensor in the NerdKit looks very much like a transistor, but in this case the left pin connects to the voltage and the right pin connects to ground. The middle pin outputs analogue measurements corresponding to the amount of heat in its environment. In order to use this data, the microcontroller unit must first convert it into digital information.

The analogue-to-digital conversion executes according to the C program that I write, with help from the NerdKits libraries. At the highest level, the MCU executes the following main function:

int main() {

  // start up the LCD
  // ...
  // (code omitted)

  // initialize the analogue-to-digital converter
  adc_init();

  // start up the serial port
  // ...
  // (code omitted)

  // define some variables to keep track of the sensor info
  uint16_t prev_sample = 0;
  double curr_temp = 0.0;
  double avg_temp = 0.0;
  uint8_t i;

  // continued...

At this point in the main function, everything has been initialized. The LCD and serial ports are activated, as is the analogue-to-digital converter. The function adc_init() handles the job of initializing the ADC; it is defined as follows:

void adc_init() {

  // sets the analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0;

  // enable the analogue to digital converter
  // with its clock scaled to 1/128 speed
  // so that the ADC clock runs at 115.2 kHz
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // perform a conversion just to get the ADC up and running
  ADCSRA |= (1<<ADSC);
}

Notice that there are only three lines of code in the entire function. Unfortunately, these three lines of code are quite cryptic! The capitalized strings of letters have been predefined to correspond to particular aspects the the MCU; in this case, they are all related to the analogue-to-digital conversion (“ADC”). The rest of the characters – the pipe (“|“) and the shift operator (“<<“) – are bitwise operators, or mathematical operations on binary numbers. Manipulating binary numbers is something rarely done outside of embedded systems. In the object-oriented programming environments with which I’m far more experienced, a thick layer of abstraction separates the coder from the zeroes and ones of the machine.

Returning to the main function:

  // ... continued

  // this while statement will loop until the power is turned off
  while(1) {

    // average 100 data readings
    avg_temp = 0.0;
    for(i = 0; i < 100; i++) {
      prev_sample = adc_read();
      curr_temp = sampleToFahrenheit(prev_sample);
      // add the resulting data to the average
      avg_temp = avg_temp + curr_temp/100.0;
    }

    // display the avg_temp on the LCD
    // ...
    // (code omitted)

    // send a similar message to the serial port
    // ...
    // (code omitted)
  }

  // this closing line of code will never execute,
  // but it is necessary for syntax reasons
  return 0;
}

The while statement is where the heavy lifting takes place. Thousands of times each second, the digital information collected by the ADC is read and averaged over a group of 100 values. The adc_read() function is responsible for returning the temperature data:

uint16_t adc_read() {

  // read from the ADC and, assuming a conversion has been requested,
  // wait for the conversion to finish
  while(ADCSRA & (1<<ADSC)) {
    // do not do anything until conversion is cleared
  }
  // at this point the bit has been cleared, meaning
  // that a result has been returned

  // reads from the ADCL/ADCH registers and combines the results
  // this is done b/c the value is too large to be obtained all in one go
  uint16_t resultPart1 = ADCL;
  uint16_t resultPart2 = ADCH;
  uint16_t result = resultPart1 + (resultPart2<<8);

  // set the ADSC bit so that the next conversion begins
  ADCSRA |= (1<<ADSC);

  return result;
}

Essentially, this function takes a value that has been split over two registers, shifts one of them as needed, and then sticks them together so that the data may be returned to whoever is asking for it. The sampleToFahrenheit function is even simpler:

double sampleToFahrenheit(uint16_t sample) {
  // conversion into degrees Fahrenheit
  // (5000 mV / 1024 steps) * (1 degree / 10mV)
  // (from ADC) * (from LM34 temperature sensor)
  return sample * (5000.0 / 1024.0 / 10.0);
}

All of this code is located inside of a single .c file. After being compiled and uploaded onto the ATmega168, the system can be powered up and used as a thermometer:

Temperature Sensor Output

Note: the code in this post is an abbreviated and slightly modified version of Michael Robbins’s tempsensor.c file described in The NerdKits Guide from NerdKits.com.

Leave a Reply