temp sensor I2C + USART bug

Go To Last Post
18 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,
I'm successfully reading data from a TMP275 temp sensor over I2C. I'm visualizing data in a terminal and on some lines a '0' appears giving a false reading. I can't find the cause of this problem but I suspect it has something to do with the conversion to char.

terminal log where the temp is actually 26 degrees:

Quote:
26,0
06,0
06,0
26,0
06,0
06,0
06,0
26,0
06,0

here is my code:

int main(void)
 {
	i2c_init();							// initialize I2C library
	i2c_start_wait(sensor+I2C_WRITE);	// set device address and write mode
    i2c_write(0x1);						// write pointer register 00000001 to select config register
	i2c_rep_start(sensor+I2C_READ);		// set device address and read mode
	config = i2c_readNak();				// read config register
	config |= _BV(6); 					// set config register for 12 bit resolution
	config |= _BV(5);					// Table 8, Page 7 from Datasheet
	i2c_rep_start(sensor+I2C_WRITE);	// set device address and write mode
	i2c_write(0x1);						// write pointer register 00000001 to select config register
	i2c_write(config);					// write config back to config register
	i2c_stop();							// stop

	while(1)
	{
	 i2c_start_wait(sensor+I2C_WRITE);	// set device address and write mode
     i2c_write(0x0);					// write pointer register 00000000 to select temp register
	 i2c_rep_start(sensor+I2C_READ);	// set device address and read mode
	 temp_high=i2c_readAck(); 			// Read high byte of temperature
	 temp_low=i2c_readNak(); 			// Read low byte of temperature
	 i2c_stop();						// stop

	usart_init();						// init usart
	_delay_ms(100);						// the Atmega8 will take a few seconds to get initialized and get stable clock pulses
	temp_low=(temp_low>>4)*625/1000;	// we adjuts the LSB temperature byte to get the fraction part of the temperature

	//send temp over USART
	if (temp_high<0) {					// first case if temp is < 0
	temp_high = -temp_high-1;			// negate temp_high and substract 1
	temp_low = -temp_low-1;			// negate temp_low and substract 1
	sprintf(buf, "-percent d, percent d\r\n", temp_high, temp_low); //for some reason the forum prevents me from posting with percent sign
	usart_puts(buf);       				// and write the whole formatted string. 
	_delay_ms(1000);
	}
	else if (temp_high>0) {				// second case if temp is > 0
	sprintf(buf, "percent d, percent d\r\n", temp_high, temp_low); //for some reason the forum prevents me from posting with percent sign
	usart_puts(buf);       				// and write the whole formatted string. 
	_delay_ms(1000);			
	}

}
}

Thanks
Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Put your 'usart_init()' in your initialisation code.

I will have a look at the data sheet. I am sure there is a more straightforward method of displaying the results.

David.

signed char tmp_hi;
signed char tmp_lo;
...
if (tmp_hi < 0) {
    tmp_lo = -tmp_lo;
}
printf("temperature is %3d.%04d",
        tmp_hi, ((tmp_lo>>4)&15)*625);

Untested.

Last Edited: Wed. Feb 3, 2010 - 03:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
Put your 'usart_init()' in your initialisation code.

I will have a look at the data sheet. I am sure there is a more straightforward method of displaying the results.

Thanks David,
here is the datasheet http://focus.ti.com/lit/ds/symlink/tmp275.pdf

Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

David,

I tried your code

	if (tmp_hi < 0) { tmp_lo = -tmp_lo; }
	printf("temperature is %3d.%04d", tmp_hi, ((tmp_lo>>4)&15)*625);

but I'm getting nothing on my terminal. Is the printf function suppose to send to USART ?

Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

printf() would write directly to the UART if you had registered the UART functions with fdevopen()

Since you have not, just sprintf(buf, "format") and lcd_puts(buf).

By the look of the data sheet, the temp sensor sends a signed 16 bit result. So if you had floating point printf(), you just would have said

int temp = (temp_hi << 8) | (temp_lo & 0xFF); 
sprintf(buf, "%08.4lf", temp / 256.0)

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

the temp is actually represented on 12 bits.

I tried:

int temp = (temp_high << 8) | (temp_low & 0xFF);
sprintf(buf, "%08.4lf", temp / 256.0); 

All I get is question marks

Quote:
? ? ? ? ? ? ?

I can't find a good resource explaining the sprintf function, and I don't understand what %08.41 does.

I also don't know why you're dividing temp to 256.0

Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

All I get is question marks

Then you haven't linked to the correct sprintf lib in your LDFLAGs and vfprintf over-ride:

http://www.nongnu.org/avr-libc/u...

If this isn't GCC then I believe both CV and ICC have checkboxes in their project config to select the skinny/heavy version of the printf lib

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Then you haven't linked to the correct sprintf lib in your LDFLAGs and vfprintf over-ride:

I never had to do that before so I don't know how I could do it and from the resource you linked I don't get it.

I'm writing in avr studio so it's gcc and in project configuration I can't find anything related to a skinny/heavy version of the printf lib. I only include stdio.h in my code.

It's confusing for me because I went from a working code that had a bug to a not working one which has functions that I don't understand. Sorry

Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry. I gave you an example with integer arithmetic and the regular 'stripped down' printf().

I gave you another example with the full-featured printf() that formats widths and floats. This latter example is neat and straightforward. But it does require you to link with the libprintf_flt.a which means altering the default configuration files (and uses a lot of flash).

But at the end of the day, a mega16 and upwards have plenty enough flash space. And if sensible use of library functions means that your programs are easier for you to understand and maintain, why not use the debugged functions?

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To use printf for floats when using Studio+WinAVR go to Project-configuration_options

Click the "libraries" icon.

In the list at the left click libprintf_flt.a then the [Add library->] button.

(while there do the same for libm.a)

Now click the "custom options" icon and in the left list "[linker options]"

In the box to the left of [Add] type in "-Wl,-u,vfprintf" then click [Add].

Now you should be able to use % f in printf()

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is some sample working code with printf that I use regularly:


#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((8000000/ (USART_BAUDRATE * 16UL))) - 1) //value to load into UBRR

static int UART_putchar(char c, FILE *stream);

static FILE mystdout = FDEV_SETUP_STREAM(UART_putchar, NULL, _FDEV_SETUP_WRITE);

void USART0_init()
{


   UCSR0B |= (1 << RXEN0) | (1 << TXEN0); // Turn on the transmission and reception circuitry
   UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00); //Set to 8 bit no parity, 1 stop bit
   UBRR0L = BAUD_PRESCALE; // Load lower 8-bits of the baud rate value into the low byte of the UBRR register
   UBRR0H = (BAUD_PRESCALE >> 8); // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
   stdout = &mystdout; //Required for printf init

}


static int UART_putchar(char c, FILE *stream)
{
    if (c == '\n') UART_putchar('\r', stream);
 
    while ((UCSR0A & (1 << UDRE0)) == 0) {}; // Do nothing until UDR is ready for more data to be written to it

    UDR0 = c;
   
    return 0;
}



/************MAIN**************/
int main (void)
    {

       
      USART0_init();
      //other init functions

          for(;;)
            {
 
                 //set flag in ISR and check flag in infinite while loop to print (for.e.g. ADC value)
                {

                   printf("#d.#d\n",a,b); //replace # by percent sign; percent sign replaced due to posting bug
               //reset flag
                  

                 }
              }
      } 


I am using a baud rate of 9600 bps and 8MHz as my crystal frequency. Also this example is for the Atmega168. As David has suggested try to use an AVR with more memory if possible; it will make your life much easier in the long run. I was using the Atmega48 and then moved to the Atmega168; same pinout but more memory and some other minor differences which did not affect my application. Also as Cliff mentions include the libm.a; it reduced my memory size by almost 30% - 50% (depending on your code).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@clawson, I did as you've said and it works now.
@david.prentice Thanks, it works, but I moved away from what I wanted to do.

now the results looks like this, it's correct no bug like in my code:

Quote:
025.8750
025.8750
025.8750
025.8750
025.8750

and I would like to see only 2 digits for the integer part and only 1 digit for the fraction part of the temperature
Quote:
example 25.8

int temp = (temp_high << 8) | (temp_low & 0xFF); 
sprintf(buf, "%08.4lf", temp / 256.0);

and from this code so far I understand that
temp_high << 8 shift left 8 bits
| OR , why ?
temp_low & 0xff : AND , why ?
08.4lf , don't understand & can't find a good reference for it
temp / 256.0 divide temp by 256.0 , why ?

thanks,
Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you want to print 25.9 instead of "025.8750", you just alter the format.

// this is just a standard way to form a 16 bit integer.
// it should work whether you have signed or unsigned char.
int temp = (temp_high << 8) | (temp_low & 0xFF);
// this should display "25.9" or "08.5" or "15.0"
sprintf(buf, "%04.1lf", temp / 256.0);

Any standard C textbook will explain how to use the printf() family. Remember that the full-featured version is always available on a PC or larger microcontrollers. It is too big for the smaller AVRs. And it will not come as default with AVR compiles.

To explain the temp/256.0

The 256.0 is a floating point constant, so any math will be done in floating point. And give a floating point result even though 'temp' is an integer.

If you look at the data sheet examples, you see that temp_high is just the signed integer temperature. The temp_low holds the # of 256ths. A LM75 just used bit7. So it is either 0 or 128 256ths. Your TMP275 uses bit7,6,5,4. So it is 0, 16, 32 ... 240 256ths.

When another chip is produced, it may use a higher resolution fraction.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@david , when using "%04.1lf" i get the same effect as with using my original code.

Quote:

05.9
05.9
25.9
05.9
05.9
25.9
25.9

So it must be a similar problem that if found/fixed could make my original code usable :-)

Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you do not want the zero padding, you remove the 0 from the format specifier. I only put in the zero cos it gets the format past the forum police.

printf() will round the result. So 25.75 will say 25.8 and 25.875 will say 25.9 and 25.50 will say 25.5

You seem to have some strange temperature readings. Do you not get any normal values like 15.0 or 18.2 ?

If you always get a .9 then it seems as if your temp_low is never varying properly. And why do you jump from 5 to 25 degrees?

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@david , ok I understand it rounds the result, but my readings are not strange it's just my room temperature, and it doesn't jump from 5 to 25, that is the bug I'm talking about. The bug only appears when I try to keep only 2 digits for the integer part of temp by lowering %06 (buf, "%06.2lf\r\n", temp / 256.0);

I believe a "0" is taking the place of the real number "2",

Florin

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Have you read the standard documentation for printf()?

The %06.2 means a width of six and a precision of 2. e.g. 012.34 or 123.45

As far as I can see the AVR printf() works just fine. In the simulator you see the floating point value, and the 'buf' displays the formatted ascii string representation.

If you say 4.2lf then this will show 1.23 (width 4)
But 12.34 will take 5 chars and 123.45 will take 6 chars. And your LCD display will look horrible when the digits move about.

Just practice on your PC using printf() to the console.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@david thanks for taking the time to making it clear :-)

I got what I wanted with :
sprintf(buf, "#6.1lf\r\n", temp / 256.0);

Quote:
27.0
27.0
26.9
26.9

Florin