Calculation Considerations in PIC Applications

copyright, Peter H. Anderson, Dept of Electrical Engineering,
Morgan State University, Baltimore, MD 21239, July 7, '97

Introduction.

In many instances, raw data is obtained from either an A/D or a DS1621 or similar and it may be desirable to perform mathematical calculations. In some cases, calculations may actually be unnecessary. In others, they may be necessary but are so complex that a look up table may be in order. Finally, there are those applications where calculations are required and can be most probably be reduced to rather trivial problems with a bit of fore thought.

In this discussion, numerous examples of calculations are illustrated which may be implemented using no more than an 8-bit by 8-bit multiply routine, a 16-bit divided by 8-bit divide routine, 16-bit add and subtraction routines and routines capable of converting either one or two bytes from natural binary to BCD.

Implementations of all of these routines are presented eleswhere.

But, first, let's examine whether caculations are really neccessary and if they are, whether a look up table is a better alternative.

The Why Calculate Cases?

Assume we are designing a data logger with is measuring temperature using a resistive divider consisting of a fixed resistor and an NTC thermistor. This is in turn read using an A/D such as the Philips 8591 4-channel A/D. Assume the data logger's only purpose is to continually log the temperature of a stream. The data logger will later be retrieved and the data serially uploaded to a PC for analysis using a spreadsheet program.

In this instance, why even bother with calculations. Why not simply store the raw A/D readings in serial EEPROM and upload these raw readings to the PC and let the PC do what it does marvelously well; perform the floating point calculations. Even if each individual data logger contains calibration data in the PIC's internal EEPROM, this too can be uploaded.

A question of human factors does arise. The human who is deploying this logger would like to know it is doing its thing. They might be given a serial LCD which they temporarily plug into the logger to verify it's operation while deploying or checking the logger. However, even then the raw data might be displayed on the LCD and the user given a brief table of how raw data translates into temperature. This need not be many points; perhaps 30, 40, 50, 60 and 70 degrees F, dpending on the application.

My point is that we may rush to incorporate sophisticated floating point routines only to find our program memory is quickly exhausted when in fact, no number crunching is even necessary.

The Difficult to Calculate Cases.

Assume we are designing a data logger to be packed in each crate of strawberries by a grower. The shipper guarantees a certain standard of refrigeration. The receiver then checks the logger in each crate as it is unloaded from the truck.

An unloading dock is a confusing place and it isn't realistic to expect there will be a PC. I don't like the idea of an LED to indicate "all went well on the journey" as it is lacking in imparting much confidence to the buyer and it lacks flexability. Strawberries going to 70 degrees over a period of two hours might be cause to penalize the shipper for noncompliance, but not cause to dump the entire load.

This, we might give the logger the ability for the user to actually observe the maximum and minimum temperatures during the journey as well as the hourly temperature readings on an LCD. Clearly, raw data will be unacceptable. Humans expect to see real numbers in base 10 and workers in the US expect to see temperatures in degrees F.

I used the example of a fixed resistor / thermistor divider as the temperature calculation from the raw data involves a number of natural log functions, the very type of thing that a PIC is not designed to do.

An alternative might be a lookup table. During the design process one might use a spreadsheet program to calculate the temperature corresponding to each of the 256 possible values the raw data may assume and program the result into a serial EEPROM such as a 93LC56 (512 byte) serial EEPROM which is dedicated for table lookup or even use a small 512 byte portion of the serial EEPROM being used to store the raw data such as a Microchip 24LC65 (8K bytes).

In fact, for 12-bit A/D applications, there are 4096 possible values of raw data. Note that a very inexpensive 24LC65 could be preprogammed to hold 4096 two byte values in a lookup table.

Thus, the raw data might be logged to serial EEPROM. However, in displaying the data, the lookup table would be used.

This takes a lot of overhead off the PIC. The process might be one of multiplying the raw data by 2 and then going into the lookup table at that address and fetching those two bytes and displaying them on the LCD.

An example of this is offered at http://www.access.digex.net/~pha/stamp. Under "Use of a 93LC56 for Table Lookup". Note that I used the parallel port to program the 93LC56. Although, the discussion is in the context of the Basic Stamp, the reader might easily map it over to the PIC.

The Trivial Calculation Cases.

Finally, we have those cases where calculation is both necessary and rather trivial. Or, it can be made trivial.

Note that trivial calculations might be used in conjunction with table lookup. In the above example, the raw data is mutiplied by two to form the address in the lookup table.

Another example. One problem with Farenheight is the extra digit associated with over 100 degrees. Thus, one could take advantage of the fact that under 20 degrees F is rare, particulalry in transporting strawberries and simply off-set the lookup table by 20 degrees. The PIC would then map the raw data into a lookup address, fetch the two bytes and add 20 to the high byte.

My intent in offering the following examples is to point out that with a bit of fore thought, you can probably handle most calculations with an 8X8 multiply routine, a 16 div 8 divide routine, two byte add and subtract routines and a BCD conversion routine capable of handling up to 9999 decimal. If you can't, consider table lookup.

DS1621 - Display in Degrees F.

Assume we are using a DS1621 where the raw data is in degrees C. Let's further assume we are dealing with only the highest 8-bits. Recall, there is a ninth bit which indicates whether the result is XX.0 or XX.5. But, for the moment, we are dealing with only the highest 8-bits.

Assume we desire to display the result in degrees F.

We can use the relation;

	T_F = T_C * 1.8 + 32.0

Multiplying by 10 gets rid of the decimal point.

	T_F_10 = T_c * 18 + 320

Note that the tools required to do this will be a multiply routine of one byte by another byte (MULT8X8). And, then a routine to add two 16 bit numbers which is quite simple. And also a routine to convert a 16 bit natural binary quantity to BCD. (BCD_2_BYTE).

Of course, we would have to remember to output the HI_BCD byte and then the high nibble of the LO_BCD byte. But, then we would have to output a decimal point and finally the low nibble of LO_BCD byte.

Perhaps multiplying by 100 offers an advantage.

	T_F_100 = 180 * T_C + 3200

Here again MULT8X8 will do the job without the result spilling beyond two bytes. The BCD_2_BYTE I wrote can handle up to 9999 decimal. Thus, depending on the value of T_C, this is an alternative. Then the HI_BCD byte is output followed by a decimal point, followed by the LO_BCD byte.

Now, lets examine how we can also use that ninth bit. If it is zero, there is no change in the expressions for T_F_10 or for T_F_100. But, if it is one, indicating a .5 value, the modification to T_F_10 is simply adding nine (0.5 * 18). Similarly, the modification to to T_F_100 is a matter of adding 90.

Thus, perform the calculation based on the high temperature byte. Then test the ninth bit and either add zero or 9 (or 90 if T_F_100).

Note that in observing that;

	Y = k * x + b 

is the same as

	Y= k * x1 + k * x2 + b

where x = x + x2

we avoided a multiplication of 9 bits times 8 bits and can use a simple 8-bit times 8-bit multiply routine.

LTC1392.

The LTC1392 is an amazingly inexpensive 10 bit A/D device capable of measuring its own temperature, V_CC and one differential voltage with a reference of either 1.0 or 0.5 Volts. (Examples of the use of this device with a PC Parallel Port and Basic Stamp 2 appear elsewhere).

For data logging I would tend toward storing the raw data in two bytes. Sure, only two bits in one of the bytes are actually used, but serial EEPROM is cheap; about $3.00 for a 24LC65 (8K bytes) and eight of these can be accommodated on the same I2C bus providing 64K bytes.

However, for displaying, we have some interesting expressions. Note that Code is the 10-bit result of the A/D conversion.

LTC1392 - T_C.

	T_C = Code / 4.0 - 130.0

I was first tempted to simply divide Code by 4, but note this is an integer divide and in doing so, two precious bits of accuracy are lost.

	T_C_10 = 2.5 * Code - 1300.0

	T_C_100 = 25 * Code - 13000

This appears workable except 25 * Code is an 8 X 10 multiply.

Let's try applying the distributive property as was done above. But, first recognize that Code is a 10 bit quantity which is equal to Code_8 * 4 + Code_2, where Code_8 is the highest eight bits and Code_2 is a byte consisting of only the two lowest bits.

	T_C_100 = 25 * (Code_8 * 4 + Code_2) - 13000

                = 100 * Code_8 + 25 * Code_2 - 13000

Note that we now have reduced the problem to two MULT8X8, an addition, a subtraction and a two byte conversion to BCD.

We should verify that the hihest value will not exceed the 9999 decimal capability of the two byte BCD routine. Uh oh! If T_C = 125.75, then T_C_100 = 12575. Thus, this will work only if the maximum temperature is 99.9 degrees C. Note that we are talking of 0.1 degrees short of boiling.

If there are values of T_C which do exceed this we might consider;

	T_C_10 = 100 * Code_8 + 2 * Code_2 - 1300

I believe the maximum loss of accuracy in rounding 2.5 to 2 is (0.5 * 3) / 10 degrees C or 0.15 degrees C. At temperatures over 100 degrees C, this may not be significant.

Of course, one could be elegant and test the value of Code to determine if the temperature is greater or less than 100 and fork to two different display routines.

LTC1392 - V_CC.

The expression for V_CC;

	V_CC = (7.26 - 2.42) * Code /1024 + 2.42

	     = 4.84 * Code / 1024 + 2.42

	V_CC_100 = 484 * Code / 1024 + 242

Applying the distributive property

	V_CC_100 = 484 * ((Code_8 * 4) + Code_2) /1024 + 242
	         = 1936 * Code_8/1024 + 484 * Code_2/1024 + 242

Fortunately, 1936 is equally divisible by 16 and 484 by 4.

	V_CC_100 = 121 * Code_8 / 64 + 121 * Code_2 / 256 + 242

This now involves two MULT8X8. The division can be done by right shifts. The addition is then a two byte addition.

The highest value of V_CC is 6.00. Thus V_CC_100 is well within the 9999 decimal limitation of BCD_2_BYTE.

DS1621 Extended Resolution.

Many of the Dallas digital thermometer devices (DS1620, DS1621, DS1820, DS1821, etc) provide for extending the resolution of the measurments. The techniques and the expressions for calculating the temperature vary from one device to another and the reader should obtain a copy of Application Note 105 from Dallas Semiconductor (http://www.dalsemi.com).

For the DS1621, obtain the high eight bits of the temperature reading. Let's term this T_C_WHOLE. Note that this is the whole number of degrees C.

There is then a procedure for fetching the counts per degree C (SLOPE_9) and the counts remaining (COUNTS_9). These are both 9-bit quantities.

The full expression for the temperature is then;

	T_C = T_C_WHOLE - 0.5 + (SLOPE_9 - COUNTS_9) / (SLOPE_9)

Note that the third term is between 0.0 and 1.0.

One might decrement T_C_WHOLE and rewrite this as;

	T_C = (T_C_WHOLE-1) + 0.5 + (SLOPE_9 - COUNTS_9) / SLOPE_9

Consider the second and third terms. This is the fractional part of the temperature reading.

	T_C_FRACT = 0.5 + (SLOPE_9 - COUNTS_9) / SLOPE_9
If we divide both numerator and denominator of the second term by two, we have lost at most 1/256 of a degree. This is desirable as we are then working with bytes as opposed to 9-bit quantities.

We might also multiply the entire equation by 100.

	T_C_FRACT_100 = 50 + 50 * (SLOPE_8 - COUNT_8) / (SLOPE_8)

The subtraction of (SLOPE_8 - COUNT_8) is trivial. This may then be multipled by 50 using MULT8X8. Dividing this by SLOPE_8 is a 16-bit quantity divided by an 8-bit quantity. Then adding 50 is easy enough.

If the result is greater than or equal to 100, T_C_WHOLE is incremented and 100 is subtracted from T_C_FRACT_100.

T_C_WHOLE may now be passed to a routine capable of converting a one byte BCD quantity (BCD_1_BYTE) and displayed followed by a decimal point. T_C_FRACT may similarly be passed to BCD_1_BYTE and displayed.

Conclusion.

The C language is a great tool. In fact, half of my time is spent teaching C to undergraduate electrical engineers.

However, I am not entirely convinced of its value when applied to very small processors having say 512 bytes of memory. C can actually can lull one into naivety. For example, the following C expression is very easy to write;

	T_F = T_C * 1.8 * 32.0;

But, the implementation of that seemingly trivial expression will take up a good deal of program memory, perhaps, leading one to progressively move to larger and larger PICs or perhaps conclude that the very inexpensive PIC family simply cannot handle the application. And, this may be very false.

Thus, my major point in the above discussion is, don't be too quick to jump right into floating point operations. Tinker a bit.

Consider whether the application even requires any calculations. If it does, consider whether the calculations might better be handled using a lookup table. Finally, if calculations are required, fool with the expressions. You might be pleasantly surprised to find they can be manipulated such that they may be evaluated using rather trivial integer routines.