AVR overview =>
Applications => Thermometer with an ATtiny24
Diese Seite in Deutsch:
AVR single chip controllers AT90S, ATtiny, ATmega and ATxmega
A thermometer with an 1-by-8 LCD and an ATtiny24
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.
This page includes
This page also demonstrates, how
- 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.
As always: the drawings on this page are availabe in the LibreOffice-Draw file
here, all calculations are in the LibreOffice-Calc file
- to multiply 16-by-16 bit numbers by software (the ATtiny24 does not have a
- to use fixed decimals to avoid floating point math in assembler (to increase the
degrees with an additional tenth of degrees),
- to use a square function y = a*x2 + b*x + c to interpolate non-linear
- to increase the accuracy of calculations by applying factors of 256n,
or 2,048 here, to get enough bits for rounding,
- to round binary values from 0.45 upwards instead of 0.5, and how
- to adjust a three-factor function either by measuring potentiometer positions or
by measuring and calculation.
If you set the ATtiny24's internal ADC port register ADMUX to the following channel:
and you start the ADC and to convert this channel with
ldi R16,0b100010|(1<<REFS1) ; Temp measurement, ref voltage = 1.1 V
If you then wait until the ADC has finished its work and clears the ADSC bit, you can
read the ADC result with
.equ cAdcClkDiv = (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
ldi R16,(1<<ADEN)|(1<<ADSC)|cAdcClkDiv ; Start ADC and first measurement
to the register pair R1:R0.
sbic ADCSRA,ADSC ; Check ADSC bit
rjmp WaitAdc ; Conversion not yet ready, wait on
in R0,ADCL ; Read LSB of result
in R1,ADCH ; dto., MSB
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
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
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:
T = a * ADC2 + b * ADC + c
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
The values for the constants in the equation T = a * ADC2 + b * ADC +
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:
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.
- Squared portion: -45.9184,
- Linear portion: 359.6939,
- Constant portion: -15.6255.
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
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.
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.
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.
|Clock frequency||1 MHz|
|ADC clock prescaler||2||128|
|ADC cycles per conversion||13|
|ADC channels to measure||4|
|Overall clock divider||104||6,656|
|Measurement frequency||9.62 kHz||150 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:
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.
; ADCC interrupt service routine
in rSreg,SREG ; Save SREG
in rimp,ADCL ; Read LSB from ADC
add rAdcL,rimp ; Add to sum, LSB
in rimp,ADCH ; dto., read MSB
adc rAdcH,rimp ; dto., add MSB to sum
dec rAdcCnt ; Decrease ADC counter
sbr rFlags,1<<bAdc ; Set ADC flag
reti ; Return from interrupt
ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|cAdcClkPrsc ; Restart ADC
The calculation outside the ISR has additional things to do:
- to clear rAdcH:rAdcL, and
- to set the counter in rAdcCnt to 64, and
- to write the next mux channel to ADMUX, and
- 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.
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
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:
The structure for those values is in the SRAM:
.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
This structure is written to the flash as a table:
.byte 2 ; cRangeA
.byte 2 ; cLowA
.byte 2 ; cA
.byte 2 ; Dummy
.byte 2 ; cRangeB
.byte 2 ; cLowB
.byte 2 ; cB
.byte 2 ; Dummy
.byte 2 ; cRangeC
.byte 2 ; cLowC
.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
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
.dw cRangeA, cLowA, cMidA, 0
.dw cRangeB, cLowB, cMidB, 0
.dw cRangeC, cLowC, cMidC, 0
So whenever a channel's measurement is over, he has
From here, the values of a/b/c can be accessed with the following
(if Y points to the beginning of the ParamTable):
- to transfer the ADC result to the multiplicator M2, and
- to read the next two bytes from SRAM (with the range
parameter) to the multiplicator M1 (LSB and MSB), and
- to multiply M1 and M2 as 16-by-16 bit multiplication, and
- 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,
- to round bytes 2 and 3 of the multiplication result (using
byte 0 and 1), and
- 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
.equ dal = 4
.equ dah = 5
.equ dbl = 12
.equ dbh = 13
.equ dcl = 20
.equ dch = 21
; Accessing a
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.
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.1 Schematic of the thermometer
This is the schematic of what we need:
- 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.
- 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
- 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.
- 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.
- 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
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
The source code is as follows:
- two registers for the multiplicator M1,
- two registers for the multiplicator M2 plus two additional registers
to multiply M2 by two (at max 16 times),
- four registers for the result of the multiplication.
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.
.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
clr rM22L ; Clear the upper 16 bits of M2
clr rMR0 ; Clear result, byte 0
clr rMR1 ; dto., byte 1
clr rMR2 ; dto., byte 2
clr rMR3 ; dto., byte 3
lsr rM1H ; Shift one bit to carry, MSB
ror rM1L ; dto., LSB
add rMR0,rM2L ; Add to result
tst rM1L ; Check if M1 is zero, LSB
tst rM1H ; dto., MSB
lsl rM2L ; Multiply M2 by 2
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
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.
- subtracting ParamStruct from it, and
- dividing that by 8 (three right-shifts), and
- adding one, and
- setting the REFS1 bit with an ORI.
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
10*T = a * 64ADC2 + b * 64ADC + c
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
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
The calculation of °F from K is a little more complicated. The original
°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
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.
|CF||°C or °F||K|
Note that the conversion of the temperature from K to °C/°F is done
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 rmp,'0' - 1
ldi rmp,'0' - 1
ldi rmp,'0' - 1
.if cEN == 0
st X+, rmp
sbiw XL,6 ; Replace leading zeros
ldi rmp,' '
Convert4: ; Set sign character
.if cPlus == 1
5.1 The source code
The software is available here in assembler
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
- 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"
- 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
- 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.
5.3 Manual adjustment
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.
Repeat these measurements several times to approach your optimal adjustment.
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.
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.
©2023 by http://www.avr-asm-tutorial.net