Measuring temperatures with TMP03/04 (Part 6)

Part 12345, 6, 7, 8

This post could be nammed “Ooops of the day”…

I recently added the PlainTMP0x library in an application which is running over very long period of times. In its previous version, the code would periodically send an error instead of a valid temperature. As this periodicity was very long (days), it was not so simple to find the origin of this bug. After a careful look at the main function code, I realized that I used the timeout testing in an inappropriate manner. The original intention was good and consisted in having the least lasting code for checking time in order to perform the most accurate timing of the cycles. But this type of code would fail in case of micros() encounters a rollover. Rollover happens when the counter of micros() (same for millis()) overflows and resets to 0 after counting 2^32 – 1  micros(). If the rollover happens in between the cycle recognition loop, the function will timeout and return an error…

The convenient way to handle this situation is presented in the next lines of generic code.

void loop(void) 

{	
	do {
		_now = millis();
	} while ((_now - _lastTime) < _interval);
	_lastTime = _now;
	/* Your code starts here */
	/* Your code ends here */
}

where _now and _lastime are global variables, 32 bits unsigned integers and _interval is a constant, 32 bits unsigned integer.

The way unsigned integers are subtracted gives the answer to this problem. For sake of better understanding, I will use an unsigned integer of 8 bits instead of a 32 bits unsigned integer.

If:

  • LastTime: 200 in decimal, is 11001000 in binary
  • Now: 210 in decimal,  is 11010010 in bianry

Then: Now – LastTime = 10 in both cases (signed and unsigned decimal)

If:

  • LastTime: 250 in decimal, is 11111010 in binary
  • Now: 3 in decimal,  is 11 in bianry

Then : Now – LastTime = -247 in signed decimal and 1010 in binary which turns to 10 in unsigned integer!

The real correct code for the PlainTMP0x library is as follows:

float PlainTMP0x::Temperature(uint8_t sensorModel, volatile uint8_t *port, uint8_t pin, uint8_t unit, uint8_t averages, float offset, float gain, uint32_t timeOut) 
/* 
Get the actual temperature expressed in the specified unit
offset and gain are the correction factors
timeOut is expressed in milliseconds
Function returns -1000.0 (TMP_ERR_TEMPERATURE) in case of error
*/
{
	DDR(*port) &= ~(1 << pin); /* Make the pin an input pin */
	PORT(*port) |= ~(1 << pin); /* Bias the input pin */
	if ((sensorModel == TMP_MOD_TMP03) || (sensorModel == TMP_MOD_TMP06)) {
		PORT(*port) |= (1 << pin); /* Bias the input pin */
	}
	if ((sensorModel == TMP_MOD_TMP03) || (sensorModel == TMP_MOD_TMP04)) {
		if (timeOut < 122) {
			timeOut = 122; /* Twice (tHigh + tLow) = 56 ms in the worst case */
		}
	} else if ((sensorModel == TMP_MOD_TMP05) || (sensorModel == TMP_MOD_TMP06)) {
		if (timeOut < 322) {
			timeOut = 322; /* Twice (tHigh + tLow) = 151 ms in the worst case */
		}
	}
	delay(1);
	uint32_t timeoutMicros = (timeOut * 1000);
	uint32_t tHigh = 0;
	uint32_t tLow = 0;
	for (uint8_t i = 0; i < averages; i++) {
		uint32_t now = micros();
		uint32_t startTime = now;
		do { /* Wait for low state on sensing pin */
			now = micros();
			if ((now - startTime) >= timeoutMicros) {
				return(TMP_ERR_STD_HIGH); /* Timeout detected */
			}
		} while ((PIN(*port) >> pin) & 0x01);
		do { /* Wait for high state on sensing pin */
			now = micros();
			if ((now - startTime) >= timeoutMicros) {
				return(TMP_ERR_STD_LOW); /* Timeout detected */
			}
		} while ((~PIN(*port) >> pin) & 0x01);  
		uint32_t tHighStart = now;  /* tHigh starts now */
		do { /* While sensing pin is high */ 
			now = micros();
			if ((now - startTime) >= timeoutMicros) {
				return(TMP_ERR_WRG_TIMING); /* Timeout detected */
			}
		} while ((PIN(*port) >> pin) & 0x01);
		uint32_t tLowStart = now; /* tLow starts now */
		do { /* While sensing pin is low */
			now = micros();
			if ((now - startTime) >= timeoutMicros) {
				return(TMP_ERR_WRG_TIMING); /* Timeout detected */
			}
		} while ((~PIN(*port) >> pin) & 0x01);
		uint32_t tLowStop = now; /* tLow stops now */
		/* Compute and cumulate high and low times */
		tHigh += (tLowStart - tHighStart); /* Cumulate measurements */
		tLow += (tLowStop - tLowStart); /* Cumulate measurements */
	}
	float result = 0.0;
	/* Compute temperature in degrees */
	if ((sensorModel == TMP_MOD_TMP03) || (sensorModel == TMP_MOD_TMP04)) {	
		result = (235.0 - ((400.0 * tHigh) / tLow));		
	} else if ((sensorModel == TMP_MOD_TMP05) || (sensorModel == TMP_MOD_TMP06)) {
		result = (421.0 - ((751.0 * tHigh) / tLow));
	}
	/* Reject wrong readings (Out of specs) */
	if ((result < -40.0) || (result > 150.0)) {
		return(TMP_ERR_OUT_OF_SPEC);
	}
	/* Convert in the appropriate unit as necessary */
	if (unit == TMP_UNI_KELVIN) { 
		result = (result + 273.15); 
	} else if (unit == TMP_UNI_FAHRENHEIT) { 
		result = (((result * 9.0) / 5.0) + 32.0); 
	}
	/* Apply correction factors */
	result *= gain;
	result += offset;
	return(result);
}

HTH

Leave a Reply

You must be logged in to post a comment.