Path: Home => AVR overview => Applications => Thermometer with an ATtiny24   Diese Seite in Deutsch:
 Applications of AVR single chip controllers AT90S, ATtiny, ATmega and ATxmega A thermometer with an 1-by-8 LCD and an ATtiny24

# ATtiny24 thermometer

Many of the newer AVR types have a built-in thermometer. This measures the temperature of the chip and the result of the measurement can be read as a 10-bit ADC result. This here shows how to convert the measurement results to temperatures, either in Kelvin or in degrees Celsius or in degrees Fahrenheit, whatever you prefer to see on your LCD.

• how the measurement goes on, and
• as the temperature-to-ADC relationship is not exactly linear, how to approach the iteration in a square function in assembler, and
• a sophisticated hardware solution for doing all that, and
• an assembler source code for all this.
1. to multiply 16-by-16 bit numbers by software (the ATtiny24 does not have a hardware multiplicator),
2. to use fixed decimals to avoid floating point math in assembler (to increase the degrees with an additional tenth of degrees),
3. to use a square function y = a*x2 + b*x + c to interpolate non-linear device data,
4. to increase the accuracy of calculations by applying factors of 256n, or 2,048 here, to get enough bits for rounding,
5. to round binary values from 0.45 upwards instead of 0.5, and how
6. to adjust a three-factor function either by measuring potentiometer positions or by measuring and calculation.
As always: the drawings on this page are availabe in the LibreOffice-Draw file here, all calculations are in the LibreOffice-Calc file here.

## 1 The ATtiny24's built-in thermometer

If you set the ATtiny24's internal ADC port register ADMUX to the following channel:

ldi R16,0b100010|(1<<REFS1) ; Temp measurement, ref voltage = 1.1 V

and you start the ADC and to convert this channel with

If you then wait until the ADC has finished its work and clears the ADSC bit, you can read the ADC result with

to the register pair R1:R0.

### 1.1 The ADC's temperature characteristic

This is what ATMEL/Microchip writes in the handbook of the device. Note that it says "Typical Case" and does not provide any specification ranges. The text says that every increase or decrease of the temperature by one degree causes the ADC to increase or decrease by "roughly" one unit. So this makes clear that the temperature measurement is, at best, has a resolution of plus/minus one degree and that a calibration is necessary to achieve that result.

#### 1.1.1 Linear function

If you go deeper into the details, you see from those numbers that the slope below 25°C, which is (25 - (-40)) / (300 - 230) = 0.92857 is different from the slope above 25°C, which is (85 - 25) / (370 - 300) = 0.85714. So, if you want to increase the accuracy, you'll have to consider these two different slopes.

You can now approach the iteration linearly. Either you set the 25°C number to 300 and use this as the constant slope all over the curve or you determine a best-fit linear function.

First of all: to get rid of the Celsius- (and Fahrenheit-) negative numbers we use the absolute temperature in Kelvin (K). °C are converted to Kelvin by adding 273.15, the Fahrenheit-conversion to K is to subtract 32°F, multiply this by 5 / 9 and to add 273.15. That eases all calculations, and we'll get a slope of
ADC = (25 + 273.15) / 300 = 0.9938 per K

If you consider the other two points given additionally you can calculate a best-fit linear function with the following formula:
ADC = 0.1395 * T (K) + 286.2619
If you use two different linear functions for below 25°C and above, your fomulas are
Below 25°C: ADC = 0.14509 * T (K) + 195.786
Above 25°C: ADC = 0.13393 * T (K) + 410.071

#### 1.1.2 A square function

It is obvious from those very different results that the ADC-vs-temperature characteristic is anything else than a simple linear function, a linear function therefore produces errors and inaccuracies. So let us try a squared function. The square function is as follows:
ADC = a * T2 + b * T + c

As we are more interested in a temperature calculation from an ADC value, we formulate the function as follows:

This is the square function that we use. In fact it starts at temperatures slightly below 0K, which is physical nonsense. It ends beyond 1,023, where it falls down again (which is also physical nonsense). As the ADC only delivers values between 230 and 370, we keep far away from those physical nonsense areas: only the red part of the curve is used here.

As the handbook provides three ADC/t points, and as the square function has three constants, we can determine those constants from those three points. The picture to the right demonstrates, how the three constants a, b and c can be determined by applying 3-by-3 determinants. You'll find that calculation in the LibreOffice-Calc file here in the spreadsheet "Squared".

The values for the constants in the equation T = a * ADC2 + b * ADC + c are:
a = -0.000510
b = 1.198980
c = -15.625510
Use those if you want to convert a single ADC measurement to Kelvin.

As the practical measurements always involves summing up 64 single results, the term 64*ADC, instead of ADC, is used here. The three parameters then are:
a = -1.2456E-06
b = 0.18734
c = -156.2551
Note that a and c are both negative, only the linear term with b is positive.

The table from A68 to H97 demonstrates the temperature calculation from the ADC values in details. The line with ADC=300 (the 25°C value) comes to the following contributions:
• Squared portion: -45.9184,
• Linear portion: 359.6939,
• Constant portion: -15.6255.
That means that all three portions of the equation contribute significantly, none of those can be disregarded. The linear portion contributes the highest portion, the squared portion reduces this considerably and the constant portion reduces it slightly.
This is how this function works: while the linear part, b * ADC, increases linearly, the squared function a * ADC2 decreases with the square (a is negative). The squared part therefore reduces the linear contribution significantly.

#### 1.1.3 Logarithmic function

Most of natural phenomena have logarithmic functions. So let us try to apply this to our data.

The result is a formula like shown in red in the diagram. It shows a slightly higher T on the mid range around 25°C and slightly lower on both the hot and the cold side.

But: if we use the formula to calculate the temperature on our three points, we end with -1.45° at -40°C and with -1.83° at +85°C, while at +25°C the calculated temperature is by 1.83° too high.

Hence: The logaritmic function is obviously NOT a good approach to interpolate temperatures, if you want to increase your accuracy down to 0.1 K/°C. This is a luck for assembler programmers, that do not like logarithmic functions.

#### 1.1.4 Comparing the square function with linear functions

This here compares the square function with the linear-best-fit function. Given is the reverse calculation: calculating the temperature in Kelvin and in °C from a given ADC reading (as it later on comes in practice).

As you can see, the linear function underestimates the temperature around 25°C and overestimates the temperature below and above the two extreme points. We will later on see in detail how much difference that makes.

So, a short resumee: we'd rather use the square function here to get more accuracy.

## 2 Practical considerations when measuring

We have to measure the analog voltages of the three potentiometers and convert those to vary a, b and c of the square function. And we'll have to measure the temperature as described above and convert those from ADC values to temperatures.

### 2.1 Averaging

First of all: it would not be a good idea to measure voltages and temperatures only once, to convert the four ADC measurements to one single temperature and to display it on the LCD. Why? Just because that would flood the LCD with unnecessary information and would flatter the last digit. To see how often this would happen, we'll calculate the measurement frequency. First with an ADC clock prescaler of 2, second with 128. These are the results.

ParameterFastestSlowest
Clock frequency1 MHz
Overall clock divider1046,656
Measurement frequency9.62 kHz150 Hz

The conclusion is, that even with the highest ADC clock prescaler, the measurements are too fast, so that the last digit of the displayed temperature would flicker.

To avoid flickering we can decide to only send the result to the LCD on every one-hundred's measurement. But the second alternative is even better: to sum up the ADC results over a given time and to divide the sum by the number of summed-up measurements. If we use 64 measurements to average those, the measurement frequency comes down to 9.64 kHz / 64 = 150 Hz (ADC clock prescaler = 2) or down to 2.34 Hz (with the prescaler of 128). The lower of the two frequencies is convenient for the human eye, so that the last digit can be read and recognized.

Averaging the ADC results over 64 measurements also increases the accuracy a little bit. It is not a single measurement, in which the last digit flickers. but that flickering of the last digit is summed up. That is nearly as good as a 13th ADC converter bit, so we can undertake to add one decimal digit to the displayed temperature (25.0 instead of 25°C only).

Of course, we do not have to divide the result by 64, which would require six logical shifts to the right as well as six rotates to the right: we can use the 64*ADC result directly to calculate whatever we need to calculate. I'll name that in the following as 64*ADC or shorter as 64ADC.

Of course we do the adding in an interrupt service routine instead of waiting for the cleared ADSC bit. Here is the code for that:

in rSreg,SREG ; Save SREG
reti ; Return from interrupt
reti

At the end, when the bAdc flag has been set, all 64 measurements have been summed up and the result of 64*ADC is in rAdcH:rAdcL. The following calculations are outside the ISR, as a reaction to this flag.

The calculation outside the ISR has additional things to do:
2. to set the counter in rAdcCnt to 64, and
3. to write the next mux channel to ADMUX, and
4. to start the first conversion by writing ADCSRA.

### 2.2 The temperature as fixed-dot number

The decision to display another decimal digit behind the dot can cause C progammers to include the floating math library - and their hex output does not fit into the small flash of the ATtiny24 any more. So they change to an ATtiny44 or even an 84. Or they change to their beloved and favoured ATmega328 - just to add some unused pins. Assembler programmers are not that stupid and just use 10 * T (or 10 * t) instead and place the dot before the last decimal digit. All calculations now use simple and fast integers, and the float lib is a superfluous piece of software and a serious waste of controller time then - and it fits very well into the flash of an ATtiny24 - intelligence replaces unneeded scrap.

So, if we convert the 64ADC values to temperatures we use the following formula:
10 * T (K) = a * 64ADC² + b * 64ADC + c

To convert the Kelvin into °C, we just have to subtract 2731.5 (instead of 273.15) from that. The conversion of K to °F is a little bit more complicated, so see below for the details.

### 2.3 Determining a, b and c in the square function

Determining a, b and c for the above formula is just like the above shown determinant method, but our ADC values now are 64ADC's and the T values are 10T values now.

With the numbers of the datasheet, we'll end at

a = -1.24561543367347E-06
b = 0.18734056122449
c = -156.255102040816

Now, the second trick that the assembler programmer often uses to get rid of those float numbers is to multiply those with multiples of 256, which is a simple method (just use one or more bytes at the right side of the result for rounding the higher bytes, by that dividing the result by 256 or higher multiples of 256). The table demonstrates that.

MultValue256nMultiplicatorMultiplied valueRounded
a-1.24561543367347E-0625644,294,967,296-5349.87755102041-5350
b0.18734056122449256265,53612277.551020408212278
c-156.2551020408162561256-40001.306122449-40001

### 2.4 Comparing the square function with the two linear functions

Now we'll get back to the decision to use a squared function instead of a linear interpolation.

Here you see the calculated curve with the above parameters a, b and c in blue: it looks rather straight, but it isn't. In red color the difference between the squared function and the linear function for the best-fit is displayed on the y axis on the right. As the result of the function is 10*T, the differences of up to +10 at low and high temperatures correspond to 1 K too high, the -16 around the 25°C stands for 1.6 K too low.

From that you see that a linear function is associated with an inaccuracy of +1 and -1.6 degrees. And you won't get it much better.
Slightly better is it to split the curve into two sections, one below and one above 25°C. Now the differences between the squared function and the splitted linear function are by 0.6° different. The difference is relevant at 40 to 50°C and at 0 to -30°C. So, if you want to measure those with a splitted-linear function: do not expect that to be much better than 0.5°. Forget the 0.1° target or use the square function.

### 2.5 Adjusting the square curve with potentiometers

If you want to adjust the parameters a, b and c by varying the fixed numbers derived above, you'll need to set a percentage margin for that variation. If you decide to set that to +/- 5%, the 64ADC values have to vary those parameters over that range.

For parameter a this means the multiplier value of 5,350 has to go down by 5%, which is 5,083, as well as up to 5,618. So the whole pot variation spans over 5,618 - 5,083 = 535. If we multiply the 64*ADC by 535 and skip the two lower bytes of the result, we'll have the adder, to be added to 5,083.

In Assembler this would look like this:

.equ cPercent = 5 ; Percentage range +/- 5%
; Default middle values for a, b and c from spreadsheet calculation
.equ cMidA = 5350 ; Mid from calculation
.equ cMidB = 12278 ; Mid from calculation
.equ cMidC = 40001 ; Mid from calculation
; Deriving the ranges from that
.equ cLowA = (cMidA * (100 - cPercent) + 50) / 100 ; Low value
.equ cHighA = (cMidA * (100 + cPercent) + 50) / 100 ; High value
.equ cRangeA = cHighA-cLowA ; Range over which A varies
.equ cLowB = (cMidB * (100 - cPercent) + 50) / 100 ; Low value
.equ cHighB = (cMidB * (100 + cPercent) + 50) / 100 ; High value
.equ cRangeB = cHighB-cLowB ; Range over which B varies
.equ cLowC = (cMidC * (100 - cPercent) + 50) / 100 ; Low value
.equ cHighC = (cMidC * (100 + cPercent) + 50) / 100 ; High value
.equ cRangeC = cHighC-cLowC ; Range over which C varies

The structure for those values is in the SRAM:

.dseg
.org SRAM_START
sParamStruct:
.byte 2 ; cRangeA
.byte 2 ; cLowA
sCa:
.byte 2 ; cA
.byte 2 ; Dummy
.byte 2 ; cRangeB
.byte 2 ; cLowB
sCb:
.byte 2 ; cB
.byte 2 ; Dummy
.byte 2 ; cRangeC
.byte 2 ; cLowC
sCc:
.byte 2 ; cC
.byte 2 ; Dummy
;
.equ ndal = sCa - sParamStruct
.equ ndah = sCa - sParamStruct + 1
.equ ndbl = sCb - sParamStruct
.equ ndbh = sCb - sParamStruct + 1
.equ ndcl = sCc - sParamStruct
.equ ndch = sCc - sParamStruct + 1

This structure is written to the flash as a table:

ParamTable:
.dw cRangeA, cLowA, cMidA, 0
.dw cRangeB, cLowB, cMidB, 0
.dw cRangeC, cLowC, cMidC, 0

By reading the ParamTable with LPM, these 24 bytes are first copied to the SRAM ParamStruct at startup to init the SRAM space. The register pair XH:XL serves as channel pointer, the pair YH:YL serves for quick accesses to this structure. Both are set to ParamStruct at the beginning.

So whenever a channel's measurement is over, he has
1. to transfer the ADC result to the multiplicator M2, and
2. to read the next two bytes from SRAM (with the range parameter) to the multiplicator M1 (LSB and MSB), and
3. to multiply M1 and M2 as 16-by-16 bit multiplication, and
4. to read the next two bytes (with the low value, LSB and MSB) and to add those to bytes 2 and 3 of the multiplication result, and
5. to round bytes 2 and 3 of the multiplication result (using byte 0 and 1), and
6. to write bytes 2 and 3 of the added and rounded multiplication results to the next two bytes in SRAM (for later use in the temperature calculation).
From here, the values of a/b/c can be accessed with the following (if Y points to the beginning of the ParamTable):

.equ dal = 4
.equ dah = 5
.equ dbl = 12
.equ dbh = 13
.equ dcl = 20
.equ dch = 21
; Accessing a
ldd rM20,Y+dal
ldd rM21,Y+dah
;
; etc.

The variations that can be achieved when adjusting are very different. The table shows the degree changes when the parameters at the lowest and the highest range, as compared to the default medium values.

VaryingLowHighRange
a2.3-2.34.6
b-18.018.035.9
c0.8-0.81.6
abc-14.914.929.8

Note that varying the a and c potentiometers to lower values increases the displayed temperature.

Of course the highest variation comes with the b trim potentiometer. Here 5 degrees of the 270 mean 0.67 K, so be careful with this. If you do not to use a 10-spin for that you can reduce the variation down to 2%. The variation of the others is less significant.

## 3 The hardware

### 3.1 Schematic of the thermometer

This is the schematic of what we need:
1. The ATtiny24 measures the temperature by comparison with its internal 1.1V reference voltage, which is coupled to an external capacitor on the AREF pin.
2. For the later calculation it first measures the potentiometers on ADC1, ADC2 and ADC3. Those vary the parameters a, b and c by plus/minus 5% of their nominal value. These three potentiometers get their 1.1V over a 11k resistor divider.
3. After measuring the three potentiometers the temperature is measured. The ADC result is then multiplied with b, the result is rounded to 24 bits and copied to rT2:rT1:rT0. Additionally the ADC result is multiplied by itself and with a. This result is rounded to 24 bits and is subtracted from rT2:rT1:rT0. After rounding this to 16 bit, 16 bit constant c is subtracted. This is ten-fold of Kelvin now. From that the °C and/or the °F are calculated, if so configured.
4. The temperature results are converted to decimal, are formatted and then are displayed on the single-line LCD with eight characters. The LCD uses the upper nibble of the port A as bi-directional data bus and is controlled by the RS, RW and E signals from Port B.
5. The ISP6 interface allows to program the device in the system.

### 3.2 Mounting the thermometer

This is the thermometer mounted on a breadboard.

After mounting the 100nF cap onto the AREF pin I realized that this reduced the noise from +/-0.3°C down to less than +/-0.1°C.

Here, the complete mounting can be seen. The ISP6 connector programs the controller and serves as operating voltage supplier.

Here are the adjustment pots. Note that the adjustment directions of the a and c constants are in reverse direction to those of the b constant.

### 3.3 A PCB layout for the hardware

This displayed here is the small version of the PCB as gif. Right-click on that and select "Save as" to download a higher resolution gif.

Note that the LCD is mounted onto the soldering side of the PCB: the two 7-pin sockets have to be soldered so they point downwards. This allows to access the ATtiny24 thermally and increases heat flow from and to the ATtiny24.

Note that from the ISP6 pins only GND and Vop are attached. If you need the ISP6 as programmer interface, you'll have to solder cables to the RESET-, the USCK-, the MISO- and the MOSI-pins from the ISP6 to the respective pins of the ATtiny24 manually.

This is how the components sit on the PCB. The ISP6 interface here uses a straight box connector. If you prefer an angled one, you can use that as well.

And don't forget to connect the two 7-pin females to the downside of the PCB.

If you want to increase the PCB's height, e. g. to the standard 40 mm, go ahead. You'll have enough space then to add three or four M2.5-by-20 mm screws on the four sides to attach the PCB to whatever plastic casing.

### 3.4 Parts list

This here is the complete parts list for all. If you want to operate the thermometer with three 1.5V batteries, you can reduce the 11k resistor down to 10k to get the full range of adjustment.

The operation with rechargeable batteries is associated with higher costs, but after changing empty batteries a few times, you'll arrive at the same cost level.

## 4 The algorithms for calculating potentiometers and the temperature

In the following some basic algorithms used in the software are described in detail. This might be helpful if you want to apply changes.

### 4.1 16-bit multiplication

For a lot of different calculations we need a 16-by-16 bit multiplication. As the ATtiny24 has no hardware multiplyer, we'll formulate that as a subroutine.

The source code needs
1. two registers for the multiplicator M1,
2. two registers for the multiplicator M2 plus two additional registers to multiply M2 by two (at max 16 times),
3. four registers for the result of the multiplication.
The source code is as follows:

; Registers
.def rM1L = R0 ; M1, LSB
.def rM1H = R1 ; M1, MSB
.def rM2L = R2 ; M2, LSB
.def rM2H = R3 ; M2, MSB
.def rM22L = R4 ; M2 2 multiplicator, LSB
.def rM22H = R5 ; M2 2 multiplicator, MSB
.def rMR0 = R6 ; Multiplication result, byte 0
.def rMR1 = R7 ; dto., byte 1
.def rMR2 = R8 ; dto., byte 2
.def rMR3 = R9 ; dto., byte 3
; Multiplication 16-by-16 bit
MultM1M2:
clr rM22L ; Clear the upper 16 bits of M2
clr rM22H
clr rMR0 ; Clear result, byte 0
clr rMR1 ; dto., byte 1
clr rMR2 ; dto., byte 2
clr rMR3 ; dto., byte 3
MultM1M2a:
lsr rM1H ; Shift one bit to carry, MSB
ror rM1L ; dto., LSB
brcc MultM1M2b
MultM1M2b:
tst rM1L ; Check if M1 is zero, LSB
brne MultM1M2c
tst rM1H ; dto., MSB
breq MultM1M2d
MultM1M2c:
lsl rM2L ; Multiply M2 by 2
rol rM2H
rol rM22L
rol rM22H
rjmp MultM1M2a
MultM1M2d:
ret

The results need rounding. Either the complete 4-byte result is rounded to a 16-bit integer or the rounding uses the last byte only and leaves 24 bits of the result. Note that rounding up takes place if the lower byte is larger than 0.45 (exact in binary math: 0.453125) and not at 0.50.

RoundM16:
ldi rmp,0x4D
ldi rmp,0x8C
ldi rmp,0
ret
RoundM24:
ldi rmp,0x8C
ldi rmp,0
ret

### 4.2 Calculating the three parameters a, b and c from the potentiometers

Calculation of the three parameters happens each time following the 64 ADC measurements of one of the three trim potentiometer channels, when the bAdc flag is set.

In order to not having to step through all three measuring channels individually, all necessary input data and the space for the output is organized in form of a record of 8 consecutive bytes (of which two are dummies). So each time such a calculation is performed, a pointer in X increases by eight and the next channel to be muxed is calculated from this pointer X: channel = (X - baseaddr) / 8 + 1. The X pointer is restarted to its baseaddress on any temperature measurement, so always starts with channel 1.

When calculating the parameters, the ADMUX port register has to be read. If that was on the temperature measurement channel (in ATtiny24 that is 0b100010), then the temperature calculation has to be performed (see below).

The handling of channel data for a, b and c starts by reading the range from SRAM. These are two bytes to which X points to, they are read to M1 (low and high). The Adc sum in rAdcH:rAdcL is copied to M2 and the multiplication routine is called, followed by a call to RoundM16. Then the low value is read from where X points to and is added to rMR2 and rMR3. Both these registers now hold the multiplicators and are written to the two SRAM locations where X now points to. At the end two is added to X and it points to the next channel's four record entries (all four are words).

The position of X is now tested if it is at the end of the SRAM structure. If so, the next channel to be measured is the temperature channel, and ADMUX is set accordingly. If not, X is converted to the ADMUX channel by
1. subtracting ParamStruct from it, and
2. dividing that by 8 (three right-shifts), and
4. setting the REFS1 bit with an ORI.
With that the ADMUX channel is set, the rAdcH:rAdcL registers are cleared and the rAdcCnt register is restarted with 64. At last, the first conversion is started by writing the ADSC bit to 1 in the ADCSRA port register.

Note that placing all relevant parameters in that row to SRAM relieves us from handling all potentiometer measurements individually.

### 4.3 Calculation of the temperature in K

All temperature calculations are based on the absolute temperature in Kelvin. °C and °F are derived from that.

To calculate the temperature in K, applying the formula

we first multiply rAdcH:rAdcL with the parameter b. We start with the second term, as this is positive (the a and c terms are negative). The 32-bit result of b * 64ADC is rounded down to 24 bits and the three result bytes are copied to the temperature registers rT2:rT1:rT0.

Then rAdcH:rAdcL is multiplied by itself to get 64ADC2, is rounded down to 16 bits and the two MSBs of the result are multiplied by parameter a. The result is rounded to 24 bits and is then subtracted from rT2:rT1:rT0.

At last the parameter c is subtracted from rT2:rT1.

If K is to be displayed (flag bCF = 0), then rT2:rT1 is rounded to 16 bit by using rT0. Please note that the 10-fold of the temperature has been calculated here, so that, at 25.0°C, we now see 2982 in rT2:rT1, to be displayed later on as T=298.2K.

### 4.4 Calculation of the temperature in °C

Setting the flags bCF to 1 and the flag bF to 0 calculates the temperature in °C.

To calculate the temperature in °C we subtract 2731.5 from rT2:rT1:rT0. The minus 0.5 is done by subtracting 0x80 from rT0, the LSB and the MSB of 2731 are then both subtracted with carry (SBCI).

If, after subtracting, the result in rT2:rT1 is positive, we'll clear the T flag, if negative we subtract rT2:rT1 from zero and set the T flag. rT2:rT1 is now the ten-fold of the temperature in °C, e. g. at 25.0°C it is 250 and the T flag is clear.

### 4.5 Calculation of the temperature in °F

If both the bCF as well as the bF flag are set, the temperature in °F is calculated.

The calculation of °F from K is a little more complicated. The original formula is:
°F = (K - 273.15) * 1.8 + 32

But, as we have 10*K and as we need 10*F, we'd rather arrive at this formula:
10*F = 10*K * 1.8 - 2731.5 * 1.8 + 320 = 10 * K * 1.8 - 4596.7

The 1.8 is a real show-stopper. If we multiply it with 256, we get 460.8 or, rounded up 461. If we calculate some temperatures with that, we find that the accuracy is roughly 0.3°F. As the next higher 65,536-fold would not fit into our 16-bit multiplication scheme, we have to use either 1,024 or 2,048 as multiplicator instead of 256. That means either two or three shift operations to divide the result at the end.

In fact we use the 1.8 * 2048 = 3686 or 0x0E66 to multiply the rounded 16-bit 10*K temperature and we use 4596.7 * 1024 = 9414042 or 0x8FA59A as subtractor. If we do that, and round the result down to 16 bits, the result is within rational accuracy.

Of course, the °F can be negative as well, so we do the same procedure as with negative °C (setting the T flag, conversion to a positive value).

### 4.6 Displaying temperatures on the LCD

The stored temperatures in rT2:rT1 have now to be converted to decimals. This is done by repeatedly subtracting 1,000s, 100s and 10s, then adding a dot and the last digit from the remaining rest of the number. Of cause we'll have to set the dimension character (K, C or F) and, in case of Celsius and Fahrenheit, the degree character in the string. The two flags bCF and bF select the three modes, so in larger controllers with two additional pins you can change the display mode on the run with two jumpers, that set or clear those two bits in PCINT interrupt service routines.

JumperOpenClosed
CF°C or °FK
F°F°C

Note that the conversion of the temperature from K to °C/°F is done above separately.

This is the source code of the conversion of rT2:rT1 to decimal:

; Convert temperature in rT2:rT1 to decimal
; X points to the SRAM buffer
ldi XH,High(sDisplay)
ldi XL,Low(sDisplay)
sbrc rFlags,bCF
rjmp Convert2CF
ldi rmp,'T'
st X+,rmp
ldi rmp,'='
st X+,rmp
rjmp ConvertDec
Convert2CF:
ldi rmp,'t'
st X+,rmp
ldi rmp,'='
st X,rmp
ConvertDec:
ldi ZH,High(1000)
ldi ZL,Low(1000)
ldi rmp,'0' - 1
ConvertDec1:
inc rmp
sub rT1,ZL
sbc rT2,ZH
brcc ConvertDec1
st X+,rmp
ldi ZH,High(100)
ldi ZL,Low(100)
ldi rmp,'0' - 1
ConvertDec2:
inc rmp
sub rT1,ZL
sbc rT2,ZH
brcc ConvertDec2
st X+,rmp
ldi rmp,'0' - 1
ldi ZL,10
ConvertDec3:
inc rmp
sub rT1,ZL
brcc ConvertDec3
st X+,rmp
ldi rmp,'.'
.if cEN == 0
ldi rmp,','
.endif
st X+,rmp
ldi rmp,'0'+10
st X+, rmp
sbrc rFlags,bCF
rjmp ConvertCF
ldi rmp,'K'
st X,rmp
rjmp ConvertEnd
ConvertCF:
ldi rmp,cDeg
st X+,rmp
ldi rmp,'C'
sbrc rFlags,bF
ldi rmp,'F'
st X,rmp
sbiw XL,6 ; Replace leading zeros
ld rmp,X
cpi rmp,'0'
brne Convert4
ldi rmp,'='
st X+,rmp
ld rmp,X
cpi rmp,'0'
brne Convert4
ldi rmp,' '
st X+,rmp
Convert4: ; Set sign character
brtc Convert5
ldi rmp,'-'
st -X,rmp
rjmp ConvertEnd
Convert5:
.if cPlus == 1
ldi rmp,'+'
st -X,rmp
.endif
ConvertEnd:

## 5 The software

### 5.1 The source code

The software is available here in assembler format.

Please note that the software uses the LCD include software for accesses to the LCD with the file lcd.inc here, in detail described here, which should be in the same folder when assembling. This include software configures the LCD (in 1-by-8 mode, writes the °-character to the LCD) and configures its pins (data bus and control signals), and writes the result strings to the LCD.

The software has a lot of different debug switches on top. Those have all to be zero, if you want to operate the thermometer.

The debug switches are:
• DebugNoLcd: this switches all LCD operations off, can be used for simulation,
• DebugTCalc: starts with a completed temperature measurement cycle, so that the calculation of the temperature can be performed without having to wait for 64 ADC conversions, uses the default cMidA, cMidB and cMidC for calculation, the constant DebugAdc can be set to a desired value of a single AD conversion, use the SRAM to see the result, ends in an endless loop,
• DebugAbcParam: simulates a single parameter measurement with selecting the a, b or c channel and the ADC value by which the parameter shall be modified, also ends in an endless loop.

### 5.2 Changing the software's properties

The following selections can be altered in the "Adjustable constants" section:
• cMode: setting this to zero, outputs Kelvin, setting it to one, outputs °C (by default), setting it to two outputs °F. This setting is taken over at start-up to rFlags, later changes to these two bits in rFlags come into effect whenever temperatures are calculated and displayed.
• cEN: setting this to one (by default) uses decimal dots in numbers, zero uses the German notation with a decimal komma.
• cPlus: by setting this to one, the display adds a plus character if the temperature is positive (only in cMode 1 or 2), the default is zero (no plus added).
• cAdcClkPresc: This allows to alter the prescaler of the ADC. Lower prescaler values increase the temperature measurement repetition rate linearly (default: roughly 2 per second).
• cManAdj: This allows manual adjustment of the temperature. The three pot's are not measured and not adjusted, their default mid value is used for temperature calculation. Only every fourth measurement cycle is factually displayed.
• cHex: When in cManAdj mode, this displays the temperature sensor's ADC measurement sum in hex and does not calculate temperatures from that. This mode can be used for adjustment and for calculating a, b and c for a given ATtiny24 from three different temperatures using determinants (see the respective LibreOffice-Calc spread-sheet and the chapter 5.3.2 below). Disable cHex to switch the temperature calculation with the default parameters for a, b and c on again.

Of course, every ATtin24 exemplar has its own parameters a, b and c. So you are to adjust those manually, if you want to come to a resolution of around 0.1°.

#### 5.3.1 Manual adjustment with the three pots

For manual adjustment you can simply use the three pots. Take three samples at three different temperatures. If all three are too low or too high, adjust the c trim at the lowest temperature so that the displayed temp is correct.

Now compare the display on the other two temperatures: if, at the lower of both temperatures, the temperature is too high, while the temperature of the higher of both is also too high, you'll need to increase a by decreasing the a pot to lower pot values.

#### 5.3.2 Manual adjustment without the pots

The second method does not involve the three pots, so you can just remove those.

Setting the constant cManAdj in the source code to one enters manual adjustment mode. Only the ADC channel of the thermometer is measured then. It is helpful to set cHex also to 1, so you'll get the 64*ADC sum displayed in hex.

Now measure the ADC sum at three different temperatures. The lowest and the highest should be as far as possible from ambient temperature.

You can enter your measurements (temperature in °C and the resulting 64ADC sum in hex) directly into the green backgrounded cells in the sheet "ManAdj" of the LibreOffice-Calc file here. The sheet then calculates a, b and c from those three points and provides the results as source code lines, to be replaced.

After replacing those lines, remove the 1 from cHex, while leaving cManAdj at one and you see the temperatures related to this adjustment.

### 5.4 Supply current and self-heating

The whole device consumes 2.3 mA at 5V. As the LCD alone consumes 2 mA the consumption of the controller is below 0.3 mA. Add a large capacitor to measure the consumption, because the controller's consumption increases when temperature calculation is performed (twice per second), and this can read errors, if your measuring device is fast enough.

To evaluate whether the controller's consumption itself has a considerable influence on the temperature, I packed a plastic cover on top of the DIL package (so heat losses from the chip only occur with the 14 pins of the device), I switched the power on and registered the displayed temperatures. The listed temperatures (see the respective spread-sheet) did not indicate a rising trend with time. Self-heating therefore contributes less than 0.4° to the measured temperature and is not a relevant factor here. It might play a role here that the device enters the sleep mode when not busy, the device is slightly more than by 99% in sleep mode.

## 6 Conclusions

Only 60% of the flash of an ATtiny24 is occupied by this software. No need to go to a larger ATtiny44 or even an ATtiny84. All remains within the given resources of this small controller.

Do not try this in C and with a floating point lib, this will only blow up your hardware needs. And it isn't simpler or more accurate than this here.