NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Support Forum » temp sensor project

November 17, 2010
by tkopriva
tkopriva's Avatar

I am a novice to this and have started with the tempsensor project. I wanted to slow down the rate of temperature readings (ideally to 1 second per reading). I tried modifying the following:

while(1) { // take 100 samples and average them! temp_avg = 0.0; for(i=0; i<100; i++) { last_sample = adc_read(); this_temp = sampleToFahrenheit(last_sample);

  // add this contribution to the average
  temp_avg = temp_avg + this_temp/100.0;
}

to:

while(1) { // take 115200 samples and average them! temp_avg = 0.0; for(i=0; i<115200; i++) { last_sample = adc_read(); this_temp = sampleToFahrenheit(last_sample);

  // add this contribution to the average
  temp_avg = temp_avg + this_temp/115200.0;
}

The program downloaded fine, but did not do anything once running (blank LCD). I re-downloaded the original and it worked again. I also tried 500, 1000, etc. and only the original code seems to work.

What am I missing here?

November 17, 2010
by bretm
bretm's Avatar

The variable "i" in the "for" loop is type uint8_t. A variable of that type can only take on values from 0 to 255, so it will always be less than 500, or 1000, or 115200, and the "for" loop will never complete. Once it reaches 255 and you try in increment it, it will wrap around back to 0.

You can change the type to increase the limit. Some types you could choose:

Type      Limit

int       32767
int16_t   32767
uint16_t  65535
int32_t   over 2 billion
uint32_t  over 4 billion

The reason you wouldn't want to use one of the 32-bit types is that on an 8-bit processor manipulating 32-bit values takes more time and more program space. But since you're already doing 32-bit floating-point calculations it shouldn't hurt.

November 17, 2010
by tkopriva
tkopriva's Avatar

thank you, that got it to work! I was able to get readings and see it update in hyperterminal.

one thing that seems strange is using 115200 it updates about every 20 seconds. reading the code I would think it should update every second:

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

Thanks Tom

November 17, 2010
by tkopriva
tkopriva's Avatar

just confirmed it by dividing 115200/20 = 5760, don't know why this is. I realize there are probably better ways of doing this, but would like to understand what is happening here.

Thanks Tom

November 17, 2010
by mrobbins
(NerdKits Staff)

mrobbins's Avatar

Hi Tom,

The ADC clock rate is 115200 Hz, but if you take a look at pages 249 and 250 of the ATmega168 datasheet, you'll see that it takes 13 ADC clock cycles to do a conversion, so that's about 8861 per second. (This is a "successive approximation analog to digital converter", so it needs one ADC clock cycle for each of the 10 bits of its output, plus a few extra for setting things up etc.)

If you're now counting up to average over 115200 samples, that should take roughly 13 seconds.

Mike

November 18, 2010
by tkopriva
tkopriva's Avatar

Thank You that helps a lot!

From what I understand interrupts are the better way to keep time. You have a good example--realtimeclock1.c which I have been learning from. What I do not understand is why I can not change OCR0A – Output Compare Register A to 1439 or 14399 and get a .1 or 1 second clock. I just seem to get the same frequency. See code below where I try to get readings every .1 second and still get a frequecy of .01 second:

// realtimeclock1.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

#include <stdio.h>
#include <stdlib.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:

void realtimeclock_setup() {
  // setup Timer0:
  // CTC (Clear Timer on Compare Match mode)
  // TOP set by OCR0A register
  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 1439;
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);
}

volatile int32_t the_time;

SIGNAL(SIG_OUTPUT_COMPARE0A) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
}

int main() {
  realtimeclock_setup();

  // init lcd
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // turn on interrupt handler
  sei();

  while(1) {
    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) the_time / 10.0);
  }

  return 0;
}
November 18, 2010
by mrobbins
(NerdKits Staff)

mrobbins's Avatar

Hi Tom,

Timer0 is a 8-bit timer, so just like a uint8_t variable can only count from 0 to 255, the 8-bit timer module can only count to 255 as well. That's why changing OCRA to 1439 or 14399 won't do what you expect.

However, Timer1 is a 16-bit timer which can count from 0 to 65535, so you might be able to make that do what you want!

Mike

November 20, 2010
by tkopriva
tkopriva's Avatar

Thanks for all the help!

I have now successfully created a program to output temperature readings on second intervals. The issue I have is since I am using a mod operator in the while loop, I am rolling the dice whether on when I get a match (see code). What I end up getting is read outs at 9, 21, 35 seconds for example. I realize the issue, but do not know how to fix it.

I tried putting the if statement inside:

SIGNAL(SIG_OUTPUT_COMPARE0A) 
    {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
    }

However, I get errors since the printf statement contains variables that are not yet defined.

if(the_time % 100 == 0) 
        {
            printf_P(PSTR("%.1f %7.1f\n"), temp_avg, (int32_t) the_time / 100.0);
        }

Can someone give me some pointers on how to properly use the interrupts to output temp readings on second intervals?

Here is the all the code:

// tempsensor.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>
#include <string.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PC0 -- temperature sensor analog input

void realtimeclock_setup() {
  // setup Timer0:
  // CTC (Clear Timer on Compare Match mode)
  // TOP set by OCR0A register
  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 143;
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);
}

volatile int32_t the_time;

SIGNAL(SIG_OUTPUT_COMPARE0A) 
    {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
    }

void adc_init() {
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}

uint16_t adc_read() {
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);

  return result;
}

double sampleToFahrenheit(uint16_t sample) {
  // conversion ratio in DEGREES/STEP:
  // (5000 mV / 1024 steps) * (1 degree / 10mV)
  //    ^^^^^^^^^^^      ^^^^^^^^^^
  //     from ADC         from LM34
  return sample * (5000.0 / 1024.0 / 10.0);  
}

int main() {
  realtimeclock_setup();

  // start up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // start up the Analog to Digital Converter
  adc_init();

  // start up the serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // turn on interrupt handler
  sei();

  // holder variables for temperature data
  uint16_t last_sample = 0;
  double this_temp;
  double temp_avg;
  int32_t i;
  int time_compare = 0;

  while(1) {
    // average samples over ~1 second (341692)
    temp_avg = 0.0;
    for(i=0; i<100; i++) {
      last_sample = adc_read();
      this_temp = sampleToFahrenheit(last_sample);

      // add this contribution to the average
      temp_avg = temp_avg + this_temp/100.0;
    }

    // write message to LCD
    lcd_home();
    lcd_write_string(PSTR("ADC: "));
    lcd_write_int16(last_sample);
    lcd_write_string(PSTR(" of 1024   "));
    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("Temperature: %.2f"), temp_avg);
    lcd_write_data(0xdf);
    lcd_write_string(PSTR("F      "));

    // write message to serial port
    if(the_time % 100 == 0) 
        {
            printf_P(PSTR("%.1f %7.1f\n"), temp_avg, (int32_t) the_time / 100.0);
        }       
  } 
    return 0;
}
November 21, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi tkopriva,

A solution for your mod time problem is to just keep a separate counter on your code that gets updated at the same time the_time does, however you just reset this one to 0 every time you write out to the serial port. Then do something like:

if(time_count > 99) 
  {
     printf_P(PSTR("%.1f %7.1f\n"), temp_avg, (int32_t) the_time / 100.0);
     time_count=0;      
  }

This way you won't miss your time target by much,

Is there a specific reason you are trying to only output the temperature once per second? If all you are doing is trying to have a time stamped temperature reading, I would suggest just sending out the temperature down the serial port as fast you can. Then on the python side you can average those readings and just write them out to the file once per second. This would also allow you to write the time out using your python would would let you get day month year and an accurate time. I'm not trying to discourage your approach if you wanted to do it so you could know how, but it might be easier to do it on the PC side.

Humberto

November 21, 2010
by tkopriva
tkopriva's Avatar

I am not particularly attached to doing it on the C side. It sounds like doing it on the PC side is more the better approach.

I am brand new to python programming. I have a basic script that writes the serial input to file. Is there a module/function in python that time stamps the serial inputs? or is it better to send in the time with the temperature reading.

#!/usr/bin/env python

#import module
import serial

#communicate with serial port
s = serial.Serial('COM3', 115200)

#write to text file
f = open('/Python27/temperature Readings/temperature.txt', 'w')
x = 1
while x == 1:
    f.write(s.read(13))

#cancel out of exe to escape infinite while loop and close text file

The comment I am most intrigued in is averaging the temperature readings over each second. Would this be figured out by some sort of module that works off the PC clock? Or are you thinking along the lines of an algorithm that takes all temperature readings for a given second identified by the serial input (how ever many that may be) and averaging them?

My input currently is:

printf_P(PSTR("%.1f %7.1f\n"), temp_avg, (int32_t) the_time / 100.0);

Thanks!

Tom

November 21, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi tkopriva,

The datetime module in python that does an awful lot of the time and date manipulations for you.

Here are a few examples of how you might use it:

from datetime import datetime,timedelta

#get the current date and time
current = datetime.now()
print current

#you have access to the different parts of the datetime object
print current.hour
print current.minute

#timedelta represents an inerval of time
one_hour = timedelta(hours=1)
one_hour_ten_min = timedelta(hours=1,minutes=10)

#you can add a datetime object with a timedelta object to get a new datetime object
new_time = current + one_hour
print new_time

#you can compare datetime objects just like numbers

if datetime.now() > current+one_hour:
   print "yay"

There are also ways to format a time to your liking, check out the strftime() section at the bottom of the documentation page I linked you to.

print datetime.now().strftime("%Y %m %d")

Would print out the year month day. You can format a time the way you want it, then print it out to the file right before you print out your temperature reading.

Hope that helps.

Humberto

November 21, 2010
by tkopriva
tkopriva's Avatar

That gives me plenty to work with, thanks you for all the examples!

Tom

December 06, 2010
by tkopriva
tkopriva's Avatar

Hi Humberto,

I went ahead and created some code to average the temperature readings on the PC side using python. Everything seems to work well for awhile until I get the following traceback error: line 19, could not convert string to float. It is perplexing since it works for awhile before crashing. Can you give me a type how to make my python code more stable (I am using v2.7)? See below for all the related code and some successful output before the inevitable crash:

#!/usr/bin/env python

#import modules
import serial
from datetime import datetime, date, time, timedelta

#communicate with serial port
s = serial.Serial('COM3', 115200)

#write to text file
f = open('/Python27/temperature Readings/temperature.txt', 'w')
x = 1
y = 1
total = 0.0
while x == 1:
    trigger = datetime.now() + timedelta(seconds = 1)
    while datetime.now() < trigger:
        temp = s.readline()
        total = total + float(temp)
        y = y + 1
    average_temp = total/(y-1)
    string = "%.1f " % average_temp + "%d " % y + datetime.now().strftime("%H:%M:%S") + "\n"
    f.write(string)
    total = 0.0
    y = 1

#cancel out of exe to escape infinite while loop and close text file

and the pertinent microcontroller code is:

  while(1) {

    // take 1000 samples and average them!
    temp_avg = 0.0;
    for(i=0; i<100; i++) {
      last_sample = adc_read();
      this_temp = sampleToFahrenheit(last_sample);

      // add this contribution to the average
      temp_avg = temp_avg + this_temp/100.0;
    }

    printf_P(PSTR("%.1f\n"), temp_avg);

 }

text file output:

65.8 64 20:17:28
65.8 62 20:17:29
65.8 63 20:17:30
December 06, 2010
by tkopriva
tkopriva's Avatar

Update:

I read that readline() has some issues with pySerial. I used read() instead and am having better success. However, still wary that I may set up an eight hour test just to find out it has crashed several hours into it!

In python I changed readline to:

temp = s.read(4)

and in my microcontroller program changed the output to:

printf_P(PSTR("%.1f"), temp_avg);

Tom

December 07, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi tkopriva,

I think your best bet in this situation is to use try/except (exception handling) blocks to just ignore any data that doesn't make sense.

while datetime.now() < trigger:
  try:
    temp = s.readline()
    total = total + float(temp)
    y = y + 1
  except:
    print "Error happened, ignoring value"

Strictly speaking this is not fantastic programming practice. If you wanted to be good about it you would catch the particular exception you were expecting. For more about exceptions in Python you can check out the official python exception documentation. Hope that helps.

Humberto

December 07, 2010
by tkopriva
tkopriva's Avatar

Thanks Humberto!

If I didn't want to print or do anything upon an exception, can I leave the except blank?

while datetime.now() < trigger:
  try:
    temp = s.readline()
    total = total + float(temp)
    y = y + 1
  except:
    //ignore value

or will this cause issues?

Thanks Tom

December 07, 2010
by tkopriva
tkopriva's Avatar

never mind the last question, I can't think of a case were I wouldn't want feedback when except executes

Thanks Tom

December 08, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi tkopriva,

It is generally good practice to print something out somewhere upon exception. You could have a different log file to log errors only. But in case anyone else wanders on this thread with the same question there is a way to have an empty except block in Python with the keyword pass.

while datetime.now() < trigger:
  try:
    temp = s.readline()
    total = total + float(temp)
    y = y + 1
  except:
    pass
January 15, 2011
by SirHobbes3
SirHobbes3's Avatar

Ok, so im doing the tempsensor project and i need some help. Take a look at the screenshot below. I recieve the error shown in the command prompt window, HELP!

January 15, 2011
by Rick_S
Rick_S's Avatar

Nerdkits Guide Pg 42 Step 10b.

If you are on Windows you need to check what COM port the USB cable loaded
as. This might change every time you connect it. Go to the Device Manger from
Control Panel. Expand the Ports (COM & LPT) section and see what is next to
the “Prolific USB-to-Serial” line. It should be COM5, COM6, or some other
number. You need to open the Makefile in a text editor and edit the line that
begins with AVRDUDEFLAGS. At the end you need to change “/dev/ttyUSB0” to
“COM5” or whatever you saw in the previous steps.
January 17, 2011
by SirHobbes3
SirHobbes3's Avatar

ok, i changed the com port in the makefile and it works beautfully!

thanks guys! SirHobbes3

Post a Reply

Please log in to post a reply.

Did you know that NerdKits make a great parent/child activity? Learn more...