Path:
Home ==>
AVR-EN ==>
Micro beginner ==> 14. Voltage, current and temperature meter
Diese Seite in Deutsch (extern):
Lecture 14: Voltage, current and temperature meter
14.0 Overview
- Measuring, calculate and display of voltages
- Measuring, calculation and display of currents
- Measuring, calculation and display of temperatures
14.1.1 Hardware
Scheme
The components for measuring voltages are minimal: a voltage divider that reduces the
input voltage and feeds the divided voltage to the ADC3 input pin. With this configuration
voltages of up to 20.5 V can be measured. The resistor 56k2 is selected to ensure
a short sampling time of the ADC: if the feed resistance is too large, the sampling
time is prolonged from 1.5 to two clock cycles of the ADC.
Of course, the experimental board here
can also be used here without modification of the source code.
Components
To the left is the resistor of 56.2 kΩ, to the right the 1 MΩ.
If you use other values, you will have to change the constants on top of the source
code. With that change you can use any resistor combination.
14.1.2 Measuring range, measure/calculation/display issues
Measuring range
The ADC is programmed to use the internally generated reference voltage of 1.1 V,
to be independant from the operating voltage of the ATtiny13. The voltage on the
ADC3 pin is UADC3 = UInput * 56.2 / (1000 + 56.2) = 1.1 [V].
The maximum measurable voltage therefore is
UInput = 1.1 * (1000 + 56.2) / 56.2 = 20.673 [V]. Per ADC bit the resolution
is 0.02 V.
Measuring conditions
The software is built on the following conditions:
- The source pin for the ADC is ADC3, which is written to the MUX port of the
ATtiny24. As no other channels have to be measured in this version, this does not
change.
- As reference for the ADC the internal 1.1 V is used, the results are
not depending from the operating voltage.
- The measurements are averaged over 64 single measurements. This results in
a measurement/display frequency of 119&nbsl;cs/s.
Calculations
In the conversion of the ADC results to the displayed voltage the following parameters
play a role:
- the relation of the two resistors, with Rel = (1M+56k2)/56k2,
- at a reference voltage of 1.1 V the 10 bit ADC delivers 1,023, so the
measurement result is NADC = Upin * 1024 / 1.1,
- by summing up 64 measurements the ADC yields 64 * NADC as sum value,
- to display the voltage a resolution of 0.01 V makes sense because this is
the the ADC resolution of the 10 bit ADC.
From that the following formula for the display applies:
U (0,01V) = (1M+56k2)/56k2 * 1.1 / 1024 * 100 / 64 * NSum-ADC
The first parameters, by which the measured sum has to be muliplied is
U (0.01V) = 0.03154442 * NSum-ADC
To be independant from a floating point math lib, we multiply 0.0315442 with 65,536 and
skip the last two bytes of the result (dividing by 65,536). So we come to
U (0.01V) = 2067 * NSum-ADC / 65536
The calculation therefore is a 16-by-16 bit multiplication, with a 32 bit result.
By changing this constant we can accomodate to any other divider relation.
Display
The result of the multiplication and the dividing by 65,536 yields values at maximum
slightly above 2,000. For converting the binary to ASCII it is sufficient to start with
thousands. Suppressing leading zeros shall only apply to 1,000s, to make sure that at
least one digit before the descimal point is displayed. The decimal point
is to be displayed after the hundreds.
14.1.3 Program
The program is rather straight forward, the source code is here.
To assemble requires the LCD include file.
;
; ***************************************
; * Measuring voltages with an ATtiny24 *
; * (C)2017 by www.avr-asm-tutorial.net *
; ***************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; -------- Switches --------------------
.equ debug = 0 ; Debug switch for Studio simulation
.equ debugN = 1000 ; Parameter for Studio simulation
;
; -------- Hardware --------------------
; ATtiny24 _______
; 1 / |14
; VCC o--|VCC GND|--o GND
; 2| |13
; LCD-RS o--|PB0 |--o NC
; 3| |12
; LCD-RW o--|PB1 |--o NC
; 4| |11
; VCC-10k o--|RES |--o NC
; 5| |10 1 M to Input
; LCD-E o--|PB2 ADC3|--o 56k2 to GND
; 6| |9
; LCD-D7 o--|PA7 PA4|--o LCD-D4
; 7| |8
; LCD-D6 o--|PA6 PA5|--o LCD-D5
; |________|
;
; -------- Timing ----------------------
; Controller clock 1.000.000 cs/s
; ADC prescaler 128
; ADC clock frequency 7.812.5 cs/s
; ADC clocks per convers. 14.5
; Measure frequency 538.8 cs/s
; Measure cycle (64) 8.42 cs/s
;
; -------- Constants ------------------
.equ cRIn = 1000000 ; Ohms
.equ cRGnd = 56200 ; Ohms
.equ cMult = ((cRIn+cRGnd)*110+cRGnd/2)/cRGnd
;
; -------- Registers --------------------
; Used: R0 by LCD
; free: R1 .. R4
.def rAL= R5 ; ADC result adder, LSB
.def rAH = R6 ; dto., MSB
.def rM0 = R7 ; 32 bit multiplicator
.def rM1 = R8
.def rM2 = R9
.def rM3 = R10
.def rR0 = R11 ; 32 bit result
.def rR1 = R12
.def rR2 = R13
.def rR3 = R14
.def rSreg = R15 ; Save/restore SREG
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside ints
.def rFlag = R18 ; Flag register
.equ bRdy = 0 ; Ready flag
.def rCtr = R19 ; Counter measurements
.def rRead = R20 ; LCD read register
.def rLine = R21 ; LCD line counter
.def rmo = R22 ; LCD multi purpose
; free: R23 .. R29
; Used: Z = R31:R30 for LCD
;
; -------- Reset and interrupt vectors
rjmp Start ; RESET vector
reti ; INT0 External Int Request 0
reti ; PCINT0 Pin Change Int Request 0
reti ; PCINT1 Pin Change Int Request 1
reti ; WDT Watchdog Time-out
reti ; TIM1_CAPT TC1 Capture Event
reti ; TIM1_COMPA TC1 Compare Match A
reti ; TIM1_COMPB TC1 Compare Match B
reti ; TIM1_OVF TC1 Overflow
reti ; TIM0_COMPA TC0 Compare Match A
reti ; TIM0_COMPB TC0 Compare Match B
reti ; TIM0_OVF TC0 Overflow
reti ; ANA_COMP Analog Comparator
rjmp AdcIsr ; ADC ADC Conv Complete
reti ; EE_RDY EEPROM Ready
reti ; USI_STR USI START
reti ; USI_OVF USI Overflow
;
; -------- Interrupt Service Routines --
AdcIsr: ; ADC conversion complete
in rSreg,SREG ; Save SREG
in rimp,ADCL ; Read LSB
add rAL,rimp ; Add to sum
in rimp,ADCH ; Read MSB
adc rAH,rimp ; Add to result plus carry
dec rCtr ; Counter cycles
brne AdcIsrRet ; Not zero
sbr rFlag,1<<bRdy ; Set flag
ldi rCtr,64 ; Restart counter
mov rM0,rAL ; Copy sum
mov rM1,rAH
clr rAL ; Clear sum
clr rAH
AdcIsrRet:
; Start next conversion
ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rimp ; to ADC control port
out SREG,rSreg ; Restore SREG
reti
;
; --------- Reset vector, program start
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; Point to RAMEND
out SPL,rmp ; Write to stack pointer
.if debug == 1 ; Debugging session
ldi rmp,Low(debugN*64) ; Simulate N
mov rM0,rmp
ldi rmp,High(debugN*64)
mov rM1,rmp
rjmp Display
.else
; Init LCD
; Init LCD control port
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; to LCD control port
clr rmp ; Outputs clear
out pLcdCO,rmp ; to LCD control port
ldi rmp,mLcdDRW ; LCD data port mask write
out pLcdDR,rmp ; to LCD direction port
; Init LCD
rcall LcdInit ; Start LCD
ldi ZH,High(2*LcdTextOut) ; Z to text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Display text
ldi rmp,0x0C ; Cursor and blink off
rcall LcdC4Byte
; Start ADC
ldi rmp,(1<<REFS1)|(1<<MUX1)|(1<<MUX0) ; Int.Ref, channel 3
out ADMUX,rmp ; to ADC mux port
clr rAL ; Clear result sum
clr rAH
ldi rCtr,64 ; 64 measure cycles
ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rmp ; Start ADC
.endif
; Sleep mode
ldi rmp,1<<SE ; Sleep Mode Idle
out MCUCR,rmp
; Enable interrupts
sei ; Set int flag
;
; Main program loop
Loop:
sleep ; Go to sleep
nop ; Wake up
sbrc rFlag,bRdy ; Skip next if ready flag clear
rcall Display ; Process ready flag
rjmp Loop
;
; Calculate result and display
Display:
cbr rFlag,1<<bRdy ; Clear ready flag
; Multiply with constant cMult
clr rM2 ; Clear bytes 3 and 4
clr rM3
clr rR0 ; Clear result
clr rR1
clr rR2
clr rR3
ldi ZH,High(cMult) ; Load constant
ldi ZL,Low(cMult)
Display1:
lsr ZH ; Shift bit from MSB to LSB
ror ZL ; Shift bit from LSB to carry
brcc Display2 ; Bit was clear
add rR0,rM0 ; Add multiplicator to result
adc rR1,rM1
adc rR2,rM2
adc rR3,rM3
Display2:
mov rmp,ZL ; Copy LSB
or rmp,ZH ; Or MSB
breq Display3 ; No more bits to multiply
lsl rM0 ; Multiplicator * 2
rol rM1
rol rM2
rol rM3
rjmp Display1 ; Multiply further
Display3:
; Round result in rR3:rR2
ldi rmp,0x7F ; Add 0x7F
add rR0,rmp ; to lowest byte
adc rR1,rmp ; and to second byte with carry
ldi rmp,0 ; Carry adder
adc rR2,rmp ; Add carry to third byte
adc rR3,rmp ; Add carry to fourth byte
; Display on LCD
ldi ZH,1 ; LCD position
ldi ZL,4
rcall LcdPos
clt ; Suppress leading zeros
ldi ZH,High(2*DecimalTab)
ldi ZL,Low(2*DecimalTab)
Display4:
lpm rR0,Z+ ; Read decimal value
lpm rR1,Z+
tst rR0 ; LSB zero?
breq Display8 ; Finished
clr rmp ; rmp is digit counter
Display5:
; Subtract decimal
sub rR2,rR0
sbc rR3,rR1
brcs Display6 ; Carry, end of subtraction
inc rmp ; Increase digit count
rjmp Display5 ; Go on subtracting
Display6:
add rR2,rR0 ; Take back last subtraction
adc rR3,rR1
tst rmp ; Digit=0?
brne Display7 ; No, display
brts Display7 ; No suppression of leading zeros
ldi rmp,' ' ; Replace leading zero by blank
rcall LcdD4Byte
set ; Do not suppress leading zeros any more
rjmp Display4 ; Go on displaying digits
Display7:
set ; Do not suppress leading zeros any more
subi rmp,-'0' ; Convert to ASCII
rcall LcdD4Byte
cpi ZL,LOW(2*DecimalTab10) ; Decimal point?
brne Display4 ; No
ldi rmp,'.' ; Display decimal point
rcall LcdD4Byte
rjmp Display4
Display8:
ldi rmp,'0' ; Last digit
add rmp,rR3 ; Add ASCII-0
rjmp LcdD4Byte
;
; Decimal table thousands and below
DecimalTab:
.dw 1000
.dw 100
DecimalTab10:
.dw 10
.dw 0
;
; --------- LCD routines ---------------
LcdTextOut:
.db " Voltage measuring ",0x0D,0xFF
.db "U = xx.xx V",0xFE
; 4
; Include LCD routines
.include "Lcd4Busy.inc"
;
;
; End of source code
;
New in this code is SUB register,register. This subtracts the content
of the second register from the first one and stores the result in the first register.
14.1.4 Example
The internal 1.1 V reference makes it possible: the ATtiny24 measures its own
operating voltage.
When measuring currents it is crucial to have a as-low-as-possible input resistance to
avoid distortions by the measurement. We here use a very special feature of the ADC in
newer tiny devices that helps us with that.
14.2.1 Hardware
Scheme
The current is measured via the voltage drop over the resistor of 0.1 Ω. The
fuse that is in series with that resistor limits it to 2 A and protects the
resistor and the controller input pin ADC2 against short circuitting. The input ADC1
serves as differential input and is grounded. Measured is the differential voltage
between ADC2 and ADC1, and this difference is gained by a factor of 20.
Components
This here is a usual 0.1 Ω resistor. This wire resistor is specified for
5 Watts thermal power, while we would only need P = U * I = (I*R)*I = 4*0.1 =
0.4 W or the next higher rated (0.5 W). Unfortunately those resistors are
not sold, so we take what we get.
That is how the 2 A fuse looks like.
And this is a respective fuse holder. To fit to our breadboard we need to solder two
short wires to it.
To a fuse holder belongs a fuse holder cap. This is not necessary here because we only
measure low voltage DC. So see it rather as dust preventer.
14.2.2 Measuring range, measure/calculate/display issues
Measuring range
At full swing the ADC input reaches the reference voltage of 1.1 V, divided by
the differential gain of 20, which is 0.055 V. With a resistor of 0.1 Ω
this corresponds to a current of I = U / R = 0.055 / 0.1 = 0.55 A or 550 mA.
Per ADC bit this is a resolution of 0.53 mA. A display resolution of 0.1 mA
would be sufficient.
If we would not select a gain of 20 but of 1 (normal ADC input), our full range covers
currents of up to I = U / R = 1.1 / 0.1 = 11 A. To cover that whole range our
resistor should then have a power of P = I2 * R = 11 * 11 * 0.1 = 12.1 W.
Per ADC digit a resolution of around 11 mA would apply. The display should then
have a resolution of 0.01 A. And, of course, we need a higher-rated fuse.
Measuring
Measuring differential voltages involves just sending a different bit combination to
the ADMUX port. The device databook for the ATtiny24 says which MUX bits can be used
and lists all those combinations.
When measuring currents, we sum up 64 single values, just like in the previous chapter.
Calculation
The measured voltage on the ADC2 input is Umeas [V] = R [Ω] * I [A].
From that I [A] = Umeas [V] / R [Ω]. With a differential gain of 20
I [A] = 20 * Umeas [V] / R [Ω]. With a reference voltage of 1.1 V
we see Nmeas = 20 * Umeas * 1024 / 1.1. Also is 20 *
Umeas = Nmeas * 1.1 / 1024 and therefore I [A] = Nmess
* 1.1 / 1024 / R. For 64 summed up measurements I [A] = NSum-meas * 1.1 /
1024 / R / 64 / 20. For a resolution of 0.0001 A the integer value would be
I [0.0001 A] = 10000 * NSum-meas * 1.1 / 1024 / R / 64 / 20 or 0,0083923 *
NSum-meas / R. Multiplied by 65,536 a multiplication faktor of 550 results.
With 0.1 Ω a multiplication factor of 5,500 results. The whole formula then
is I [0.0001 A] = 5,500 * NSum-meas / 65536.
Display
The measured sum is a 16 bit binary, which is to be multiplied with the 16 bit binary
5,500. The lower two bytes of the result can be used to round the result in the upper
two bytes (division by 65,536). For conversion into the display format of 123.4 mA
first the thousands, then the hundreds and the tens are calculated. Following the hundreds
no suppression of leading zeros is needed any more, prior to the ones a decimal point
is to be displayed.
An alternative display format would be 1.234(5) A. That would require some changes
to the source code, but is possible and simple.
14.2.3 Program
The program is listed here, the source code is here. For
assembling the LCD include routines are required.
;
; **********************************************
; * To measure and display voltage and current *
; * (C)2017 by www.avr-asm-tutorial.net *
; **********************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; -------- Hardware --------------------
; ATtiny24 _______
; 1 / |14
; VCC o--|VCC GND|--o GND
; 2| |13
; LCD-RS o--|PB0 |--o NC
; 3| |12
; LCD-RW o--|PB1 ADC1|--o GND
; 4| |11
; VCC-10k o--|RES ADC2|--o 0.1 Ohms
; 5| |10 1 M to input
; LCD-E o--|PB2 ADC3|--o 56k2 to GND
; 6| |9
; LCD-D7 o--|PA7 PA4|--o LCD-D4
; 7| |8
; LCD-D6 o--|PA6 PA5|--o LCD-D5
; |________|
;
; -------- Timing ----------------------
; Processor clock 1,000,000 cs/s
; ADC prescaler 128
; ADC clock 7,812.5 cs/s
; ADC clocks per conv. 14.5
; Measuring frequency 538.8 cs/s
; Measure cycle, 64 m. 8.42 cs/s
; Two measure cycles 4.21 cs/s
;
; -------- Constants ------------------
; Voltage measurement
.equ cRInp = 1000000 ; Ohm
.equ cRGnd = 56200 ; Ohm
.equ cMultU = ((cRInp+cRGnd)*110+cRGnd/2)/cRGnd
; Current measurement
.equ cRI = 100 ; Resistor in milli ohm
.equ cMultI = (550000+cRI/2) / cRI
; ADC-MUX-Constants
.equ cMuxU = (1<<REFS1)|(1<<MUX1)|(1<<MUX0) ; Voltage input
.equ cMuxI = (1<<REFS1)|0b101101 ; Current input, diff.gain=20
;
; -------- Registers --------------------
; Used: R0 by LCD
; free: R1 .. R4
.def rAL= R5 ; ADC sum LSB
.def rAH = R6 ; ADC sum MSB
.def rM0 = R7 ; 32 bit multiplier
.def rM1 = R8
.def rM2 = R9
.def rM3 = R10
.def rR0 = R11 ; 32 bit result
.def rR1 = R12
.def rR2 = R13
.def rR3 = R14
.def rSreg = R15 ; Save/restore SREG
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside interrupts
.def rFlag = R18 ; Flag register
.equ bRdyV = 0 ; Ready flag voltage
.equ bRdyC = 1 ; Ready flag current
.def rCtr = R19 ; Counter measurements
.def rRead = R20 ; LCD read register
.def rLine = R21 ; LCD line counter
.def rmo = R22 ; LCD multi purpose
; free: R23 .. R29
; Used: Z = R31:R30 for LCD etc.
;
; -------- Reset and interrupt vectors
rjmp Start ; RESET vector
reti ; INT0 External Int Request 0
reti ; PCINT0 Pin Change Int Request 0
reti ; PCINT1 Pin Change Int Request 1
reti ; WDT Watchdog Time-out
reti ; TIM1_CAPT TC1 Capture Event
reti ; TIM1_COMPA TC1 Compare Match A
reti ; TIM1_COMPB TC1 Compare Match B
reti ; TIM1_OVF TC1 Overflow
reti ; TIM0_COMPA TC0 Compare Match A
reti ; TIM0_COMPB TC0 Compare Match B
reti ; TIM0_OVF TC0 Overflow
reti ; ANA_COMP Analog Comparator
rjmp AdcIsr ; ADC ADC Conv Complete
reti ; EE_RDY EEPROM Ready
reti ; USI_STR USI START
reti ; USI_OVF USI Overflow
;
; -------- Interrupt Service Routines --
AdcIsr: ; ADC conversion complete
in rSreg,SREG ; Save SREG
in rimp,ADCL ; Read LSB ADC
add rAL,rimp ; Add to sum
in rimp,ADCH ; Read MSB ADC
adc rAH,rimp ; Add to sum with carry
dec rCtr ; Counter measure
brne AdcIsrRet ; Not zero
in rimp,ADMUX ; Read Mux port
cpi rimp,cMuxU ; Was voltage measured?
breq AdcIsrI ; Yes, start I measuring
sbr rFlag,1<<bRdyC ; Set ready flag current
ldi rimp,cMuxU ; Start voltage measuring
out ADMUX,rimp ; in ADMUX port
rjmp AdcIsrCont ; End of routine
AdcIsrI:
sbr rFlag,1<<bRdyV ; Set voltage measurement
ldi rimp,cMuxI ; Start current measurement
out ADMUX,rimp ; in ADMUX port
AdcIsrCont:
ldi rCtr,64 ; Restart counter
mov rM0,rAL ; Transfer measured sum
mov rM1,rAH
clr rAL ; Clear sum
clr rAH
AdcIsrRet:
; Restart ADC conversion
ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rimp ; in ADC control port
out SREG,rSreg ; Restore SREG
reti
;
; --------- Reset vector, program start
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; Point to RAMEND
out SPL,rmp ; to stack pointer
; Init LCD control port
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; to LCD control port
clr rmp ; Control outputs off
out pLcdCO,rmp ; to LCD control port
ldi rmp,mLcdDRW ; LCD data port write
out pLcdDR,rmp ; to LCD data direction port
; Init LCD
rcall LcdInit ; Call init in include
ldi ZH,High(2*LcdTextOut) ; Z to text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Display text
ldi rmp,0x0C ; Cursor and blink off
rcall LcdC4Byte
; Start ADC
ldi rmp,cMuxU ; Start voltage measuring
out ADMUX,rmp ; in ADMUX
clr rAL ; Clear sum
clr rAH
ldi rCtr,64 ; 64 measures
ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rmp ; Start first ADC conversion
; Sleep mode
ldi rmp,1<<SE ; Sleep Mode Idle
out MCUCR,rmp
; Enable interrupts
sei ; Set I flag
;
; Main program loop
Loop:
sleep ; Go to sleep
nop ; Wake up
sbrc rFlag,bRdyV ; Skip next if voltage ready flag not set
rcall DisplayV ; Display voltage
sbrc rFlag,bRdyC ; Skip next if current ready flag not set
rcall DisplayC ; Display current
rjmp Loop ; Go back to sleep
;
; Calculate result voltage
DisplayV:
cbr rFlag,1<<bRdyV ; Clear flag
; Multiply with constant cMult
ldi ZH,High(cMultU) ; Constant to Z
ldi ZL,Low(cMultU)
rcall Multiplication ; rM1:rM2 * ZH:ZL
; Round result in rR3:rR2
ldi rmp,0x7F ; Adder
add rR0,rmp ; Round up
adc rR1,rmp ; Round up with carry
ldi rmp,0 ; Adder
adc rR2,rmp ; Round up with carry
adc rR3,rmp ; Round up with carry
; Display on LCD
ldi ZH,1 ; Set position on LCD
ldi ZL,4
rcall LcdPos
clt ; Suppress leading zeros
ldi ZH,High(2*DecimalTab)
ldi ZL,Low(2*DecimalTab)
DisplayV1:
lpm rR0,Z+ ; Read decimal number
lpm rR1,Z+
tst rR0 ; LSB zero?
breq DisplayV5 ; Yes, done
clr rmp ; Digit count
DisplayV2:
sub rR2,rR0 ; Subtract decimal
sbc rR3,rR1
brcs DisplayV3 ; Digit complete
inc rmp ; Increase digit count
rjmp DisplayV2 ; Go on subtracting
DisplayV3:
add rR2,rR0 ; Restore last subtraction
adc rR3,rR1
tst rmp ; Digit zero?
brne DisplayV4 ; No
brts DisplayV4 ; No suppression of leading zeros
ldi rmp,' ' ; Suppress leading zero
rcall LcdD4Byte
set ; No suppression of leading zeros any more
rjmp DisplayV1 ; Go on
DisplayV4:
set ; No suppression of leading zeros any more
subi rmp,-'0' ; Add ASCII-0
rcall LcdD4Byte
cpi ZL,LOW(2*DecimalTab10) ; Decimal point?
brne DisplayV1 ; No, go on
ldi rmp,'.' ; Decimal point
rcall LcdD4Byte
rjmp DisplayV1 ; Go on
DisplayV5:
ldi rmp,'0' ; Last digit
add rmp,rR3 ; Add ASCII-0
rjmp LcdD4Byte
;
; Decimal table thousends
DecimalTab:
.dw 1000
.dw 100
DecimalTab10:
.dw 10
.dw 0
;
; Calculate and display current
DisplayC:
cbr rFlag,1<<bRdyC ; Clear current flag
ldi ZH,2 ; Set LCD position
ldi ZL,4
rcall LcdPos
; Multiply with constant cMultI
ldi ZH,High(cMultI) ; Constant to Z
ldi ZL,Low(cMultI)
rcall Multiplication ; rM1:rM2 * ZH:ZL
; Round result in rR3:rR2:rR1:rR0
ldi rmp,0x7F ; Adder
add rR0,rmp ; Add last byte
adc rR1,rmp ; Add pre last byte
ldi rmp,0 ; Adder
adc rR2,rmp ; Add carry
adc rR3,rmp ; Add carry
; Display on LCD
clt ; Suppress leading zeros
ldi ZH,High(2*DecimalTab) ; Point to decimal table
ldi ZL,Low(2*DecimalTab)
DisplayC1:
lpm rM2,Z+ ; Read decimal
lpm rM3,Z+
tst rM2 ; LSB zero?
breq DisplayC7 ; Yes, done
clr rmp ; Digit counter
DisplayC2:
sub rR2,rM2 ; Subtract decimal
sbc rR3,rM3
brcs DisplayC3 ; Carry: end of subtraction
inc rmp ; Increase digit
rjmp DisplayC2 ; Go on subtracting
DisplayC3:
add rR2,rM2 ; Add again
adc rR3,rM3
tst rmp ; Digit zero?
brne DisplayC4 ; No
brts DisplayC5 ; Suppression of leading zeros off
cpi ZL,Low(2*DecimalTab10) ; Decimal point?
breq DisplayC4 ; No
ldi rmp,' ' ; Replace 0 by blank
rcall LcdD4Byte
rjmp DisplayC1 ; Go on
DisplayC4:
set ; Set flag no zero suppression any more
DisplayC5:
subi rmp,-'0' ; Convert to ASCII
rcall LcdD4Byte
rjmp DisplayC1
DisplayC7:
ldi rmp,'.' ; Display decimal point
rcall LcdD4Byte
ldi rmp,'0' ; Display last digit
add rmp,rR2
rjmp LcdD4Byte
;
; 16-by-16 bit multiplication
Multiplication: ; rM1:rM2 * ZH:ZL
; Result in rR3:rR2:rR1:rR0
clr rM2 ; Clear bytes 3 and 4
clr rM3
clr rR0 ; Clear result
clr rR1
clr rR2
clr rR3
Multiplication1:
lsr ZH ; Shift bit from MSB to LSB
ror ZL ; Shift lowest bit to carry
brcc Multiplication2 ; Do not add
add rR0,rM0 ; Add multiplicator to result
adc rR1,rM1
adc rR2,rM2
adc rR3,rM3
Multiplication2:
mov rmp,ZL ; All ones shifted out?
or rmp,ZH
breq Multiplication3 ; Yes, done
lsl rM0 ; Multipliy multiplicator by two
rol rM1
rol rM2
rol rM3
rjmp Multiplication1 ; Continue multiplication
Multiplication3:
ret
;
; --------- LCD routines ---------------
LcdTextOut:
.db "Voltage and current",0x0D
.db "U = xx,xx V",0x0D
.db "I = xxx,x mA",0xFE,0xFF
; 4
;
; LCD include routines
.include "Lcd4Busy.inc"
;
; End of source code
;
14.2.4 Example
That is such an example (the German version).
The ATtiny24 has an internal temperature sensor. This is utilized here.
14.3.1 Hardware
For measuring the temperature no external components are necessary.
14.3.2 Measuring range, measuring, calculation and display
Measuring range
ATMEL provides the following typical measurement results for temperatures:
t [°C] | ADC |
-40 | 230 |
25 | 300 |
85 | 370 |
Temperatures below -40 and above +85 °C are unpractical and beyond the
operating range of the controller.
Measuring temperature
The temperature measurement is initiated by setting the ADC multiplexer to 8. This
is documented in the device databook and used in the source code.
Calculation
From the above listed values the ADC result is Nmeas = 1.1194 * t [°C]
+ 273.88. From that the temperature calculation is t [°C] = 0.89286 * Nmeas -
244.52. For 64 measurements Messungen and multiplied by 65,536 the following results:
t [°C] = 65536 / 64 * 0.89286 * NSum-meas / 65536 - 245 = 914 *
NSum-meas / 65536 - 245.
Factually the parameter 245 is inaccurate and has to be adjusted. To determine this
parameter practically, the display of the measured temperature is in hex. This resulted
at 21°C in 0x013A, hence 35.8. The temperature was by 15°C too high, the parameter
245 had to be increased by 15.
Who wants it even more accurate has to determine the slope, too, by determining the
ADC result at two different temperatures and change the parameter cMultT in the source
code accordingly.
The current source code displays the temperature with a resolution of 1°C. For
displaying a resolution of 0.1°C has to change the math, e.g. the multiplicator
should be 9,143 and the subtractor 2.445 to arrive at tenth of degrees. As the physical
accuracy is only 0.89°C, the displayed tenth of degrees pretend a higher accuracy
than really is.
Display
The displayed temperature is an integer value. It can be positive or negative.
14.3.3 Program
The program is listed as follows, the source code is here
and requires the LCD include routines to assemble.
The degree character has to be programmed actively because it is not available with the
standard character set of the LCD.
;
; ************************************************
; * Voltage, current and temperature measurement *
; * (C)2017 by www.avr-asm-tutorial.net *
; ************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; -------- Switch --------------------
.equ debugDisplay = 0 ; Displays temperature in hex
;
; -------- Hardware --------------------
; ATtiny24 _______
; 1 / |14
; VCC o--|VCC GND|--o GND
; 2| |13
; LCD-RS o--|PB0 |--o NC
; 3| |12
; LCD-RW o--|PB1 ADC1|--o GND
; 4| |11
; VCC-10k o--|RES ADC2|--o 0.1 Ohms
; 5| |10 1 M to input
; LCD-E o--|PB2 ADC3|--o 56k2 to GND
; 6| |9
; LCD-D7 o--|PA7 PA4|--o LCD-D4
; 7| |8
; LCD-D6 o--|PA6 PA5|--o LCD-D5
; |________|
;
; -------- Timing ----------------------
; Clock frequency 1,000,000 cs/s
; ADC prescaler 128
; ADC clock frequency 7,812.5 Hz
; ADC clocks per measure 14.5
; Measuring frequency 538.8 cs/s
; Measuring cycle (64) 8.42 cs/s
; Three measuring cycles 2.81 cs/s
;
; -------- Constants ------------------
; Voltage
.equ cRInp = 1000000 ; Ohm
.equ cRGnd = 56200 ; Ohm
.equ cMultU = ((cRInp+cRGnd)*110+cRGnd/2)/cRGnd
; Current
.equ cRI = 100 ; Milliohm
.equ cMultI = (550000+cRI/2) / cRI
; Temperature
.equ cMultT = 914
.equ cMinusT = 245+15 ; Temperature plus correction
; ADMUX constants
.equ cMuxV = (1<<REFS1)|(1<<MUX1)|(1<<MUX0)
.equ cMuxC = (1<<REFS1)|0b101101
.equ cMuxT = (1<<REFS1)|0b100010
;
; -------- Register --------------------
; Used: R0 by LCD
; free: R1 .. R4
.def rAL= R5 ; LSB ADC sum
.def rAH = R6 ; dto., MSB
.def rM0 = R7 ; 32 bit multiplicator
.def rM1 = R8
.def rM2 = R9
.def rM3 = R10
.def rR0 = R11 ; 32 bit result
.def rR1 = R12
.def rR2 = R13
.def rR3 = R14
.def rSreg = R15 ; Save/restore SREG
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside interrupts
.def rFlag = R18 ; Flag register
.equ bRdyV = 0 ; Ready flag voltage
.equ bRdyC = 1 ; Ready flag current
.equ bRdyT = 2 ; Ready flag temperature
.def rCtr = R19 ; Counter for measurements
.def rRead = R20 ; LCD read register
.def rLine = R21 ; LCD line counter
.def rmo = R22 ; LCD additional multi purpose
; free: R23 .. R29
; Used: Z = R31:R30 for LCD etc.
;
; -------- Reset and interrupt vectors
rjmp Start ; RESET vector
reti ; INT0 External Int Request 0
reti ; PCINT0 Pin Change Int Request 0
reti ; PCINT1 Pin Change Int Request 1
reti ; WDT Watchdog Time-out
reti ; TIM1_CAPT TC1 Capture Event
reti ; TIM1_COMPA TC1 Compare Match A
reti ; TIM1_COMPB TC1 Compare Match B
reti ; TIM1_OVF TC1 Overflow
reti ; TIM0_COMPA TC0 Compare Match A
reti ; TIM0_COMPB TC0 Compare Match B
reti ; TIM0_OVF TC0 Overflow
reti ; ANA_COMP Analog Comparator
rjmp AdcIsr ; ADC ADC Conv Complete
reti ; EE_RDY EEPROM Ready
reti ; USI_STR USI START
reti ; USI_OVF USI Overflow
;
; -------- Interrupt Service Routines --
AdcIsr:
in rSreg,SREG ; Save SREG
in rimp,ADCL ; Read ADC LSB
add rAL,rimp ; Add to sum
in rimp,ADCH ; Read ADC MSB
adc rAH,rimp ; Add to sum plus carry
dec rCtr ; Count measurements
brne AdcIsrRet ; Not yet zero
in rimp,ADMUX ; Read ADMUX
cpi rimp,cMuxV ; Voltage measured?
breq AdcIsrC ; Yes, start current measurement
cpi rimp,cMuxC ; Current measured?
breq AdcIsrT ; Yes, start temperature measurement
sbr rFlag,1<<bRdyT ; Set T flag, start voltage
ldi rimp,cMuxV ; Measure voltage
out ADMUX,rimp ; in ADMUX
rjmp AdcIsrCont
AdcIsrC:
sbr rFlag,1<<bRdyV ; Set voltage flag
ldi rimp,cMuxC ; Start current measurement
out ADMUX,rimp ; in ADMUX
rjmp AdcIsrCont
AdcIsrT:
sbr rFlag,1<<bRdyC ; Set current flag
ldi rimp,cMuxT ; Start temperature measurement
out ADMUX,rimp ; in ADMUX
AdcIsrCont:
ldi rCtr,64 ; Restart counter
mov rM0,rAL ; Transfer sum
mov rM1,rAH
clr rAL ; Clear sum
clr rAH
AdcIsrRet:
ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rimp ; Restart ADC measuring
out SREG,rSreg ; Restore SREG
reti
;
; --------- Reset vector, program start
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; Point to RAMEND
out SPL,rmp ; to stack pointer
; Init LCD control port
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; to LCD control port
clr rmp ; Control outputs off
out pLcdCO,rmp ; to LCD control port
ldi rmp,mLcdDRW ; LCD data port write
out pLcdDR,rmp ; to LCD data direction port
; Init LCD
rcall LcdInit ; Call init in include
ldi ZH,High(2*LcdDefChar) ; Define degree char on LCD
ldi ZL,Low(2*LcdDefChar)
rcall LcdChars ; Define chars
ldi ZH,High(2*LcdTextOut) ; Z to text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Display text
ldi rmp,0x0C ; Cursor and blink off
rcall LcdC4Byte
; Start ADC
ldi rmp,cMuxV ; Start voltage measuring
out ADMUX,rmp ; in ADMUX
clr rAL ; Clear sum
clr rAH
ldi rCtr,64 ; 64 measurements
ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rmp ; Start first ADC conversion
; Sleep mode
ldi rmp,1<<SE ; Sleep Mode Idle
out MCUCR,rmp
; Enable interrupts
sei ; Set I flag
;
; Main program loop
Loop:
sleep ; Go to sleep
nop ; Wake up
sbrc rFlag,bRdyV ; Skip next if voltage ready flag not set
rcall DisplayV ; Display voltage
sbrc rFlag,bRdyC ; Skip next if current ready flag not set
rcall DisplayC ; Display current
sbrc rFlag,bRdyT ; Skip next if temperature ready flag not set
rcall DisplayT
rjmp Loop ; Go back to sleep
;
; Calculate result voltage
DisplayV:
cbr rFlag,1<<bRdyV ; Clear flag
; Multiply with constant cMult
ldi ZH,High(cMultU) ; Constant to Z
ldi ZL,Low(cMultU)
rcall Multiplication ; rM1:rM2 * ZH:ZL
; Round result in rR3:rR2
ldi rmp,0x7F ; Adder
add rR0,rmp ; Round up
adc rR1,rmp ; Round up with carry
ldi rmp,0 ; Adder
adc rR2,rmp ; Round up with carry
adc rR3,rmp ; Round up with carry
; Display on LCD
ldi ZH,1 ; Set position on LCD
ldi ZL,4
rcall LcdPos
clt ; Suppress leading zeros
ldi ZH,High(2*DecimalTab)
ldi ZL,Low(2*DecimalTab)
DisplayV1:
lpm rR0,Z+ ; Read decimal number
lpm rR1,Z+
tst rR0 ; LSB zero?
breq DisplayV5 ; Yes, done
clr rmp ; Digit count
DisplayV2:
sub rR2,rR0 ; Subtract decimal
sbc rR3,rR1
brcs DisplayV3 ; Digit complete
inc rmp ; Increase digit count
rjmp DisplayV2 ; Go on subtracting
DisplayV3:
add rR2,rR0 ; Restore last subtraction
adc rR3,rR1
tst rmp ; Digit zero?
brne DisplayV4 ; No
brts DisplayV4 ; No suppression of leading zeros
ldi rmp,' ' ; Suppress leading zero
rcall LcdD4Byte
set ; No suppression of leading zeros any more
rjmp DisplayV1 ; Go on
DisplayV4:
set ; No suppression of leading zeros any more
subi rmp,-'0' ; Add ASCII-0
rcall LcdD4Byte
cpi ZL,LOW(2*DecimalTab10) ; Decimal point?
brne DisplayV1 ; No, go on
ldi rmp,'.' ; Decimal point
rcall LcdD4Byte
rjmp DisplayV1 ; Go on
DisplayV5:
ldi rmp,'0' ; Last digit
add rmp,rR3 ; Add ASCII-0
rjmp LcdD4Byte
;
; Decimal table thousands
DecimalTab:
.dw 1000
.dw 100
DecimalTab10:
.dw 10
.dw 0
;
; Calculate and display current
DisplayC:
cbr rFlag,1<<bRdyC ; Clear current flag
ldi ZH,2 ; Set LCD position
ldi ZL,4
rcall LcdPos
; Multiply with constant cMultI
ldi ZH,High(cMultI) ; Constant to Z
ldi ZL,Low(cMultI)
rcall Multiplication ; rM1:rM2 * ZH:ZL
; Round result in rR3:rR2:rR1:rR0
ldi rmp,0x7F ; Adder
add rR0,rmp ; Add last byte
adc rR1,rmp ; Add pre last byte
ldi rmp,0 ; Adder
adc rR2,rmp ; Add carry
adc rR3,rmp ; Add carry
; Display on LCD
clt ; Suppress leading zeros
ldi ZH,High(2*DecimalTab) ; Point to decimal table
ldi ZL,Low(2*DecimalTab)
DisplayC1:
lpm rM2,Z+ ; Read decimal
lpm rM3,Z+
tst rM2 ; LSB zero?
breq DisplayC7 ; Yes, done
clr rmp ; Digit counter
DisplayC2:
sub rR2,rM2 ; Subtract decimal
sbc rR3,rM3
brcs DisplayC3 ; Carry: end of subtraction
inc rmp ; Increase digit
rjmp DisplayC2 ; Go on subtracting
DisplayC3:
add rR2,rM2 ; Add again
adc rR3,rM3
tst rmp ; Digit zero?
brne DisplayC4 ; No
brts DisplayC5 ; Suppression of leading zeros off
cpi ZL,Low(2*DecimalTab10) ; Decimal point?
breq DisplayC4 ; No
ldi rmp,' ' ; Replace 0 by blank
rcall LcdD4Byte
rjmp DisplayC1 ; Go on
DisplayC4:
set ; Set flag no zero suppression any more
DisplayC5:
subi rmp,-'0' ; Convert to ASCII
rcall LcdD4Byte
rjmp DisplayC1
DisplayC7:
ldi rmp,'.' ; Display decimal point
rcall LcdD4Byte
ldi rmp,'0' ; Display last digit
add rmp,rR2
rjmp LcdD4Byte
;
; Calculate temperature
DisplayT:
cbr rFlag,1<<bRdyT ; Clear T flag
.if debugDisplay == 1 ; Debug, display in hex
lsr rM1 ; / 2, divide sum by 64
ror rM0
lsr rM1 ; / 4
ror rM0
lsr rM1 ; / 8
ror rM0
lsr rM1 ; / 16
ror rM0
lsr rM1 ; / 32
ror rM0
lsr rM1 ; / 64
ror rM0
ldi ZH,3 ; Set LCD position
ldi ZL,11
rcall LcdPos
mov rmp,rM1 ; MSB to rmp
rcall LcdHex ; Display in hex
mov rmp,rM0 ; LSB to rmp
rjmp LcdHex ; Display in hex
LcdHex: ; Display rmp in hex
push rmp ; Save rmp
swap rmp ; Upper nibble first
rcall LcdHexN ; Display nibble
pop rmp ; Restore rmp
LcdHexN: ; Display nibble
andi rmp,0x0F ; Mask lower nibble
subi rmp,-'0' ; Add ASCII-0
cpi rmp,'9'+1 ; A to F?
brcs LcdHexN1 ; No
subi rmp,-7 ; Add 7 to adjust to A to F
LcdHexN1:
rjmp LcdD4Byte ; Display nibble on LCD
.endif
; Multiply with constant cMultT
ldi ZH,High(cMultT) ; Load constant to Z
ldi ZL,Low(cMultT)
rcall Multiplication ; rM1:rM0 * ZH:ZL
; Round result in rR3:rR2
ldi rmp,0x7F ; Adder
add rR0,rmp ; Add to LSB result
adc rR1,rmp ; Add to MSB result
ldi rmp,0 ; Adder = 0
adc rR2,rmp ; Add carry flag to byte 3
adc rR3,rmp ; Add carry flag to byte 4
; Subtract constant cMinusT
ldi rmp,Low(cMinusT)
sub rR2,rmp ; Subtract LSB
ldi rmp,High(cMinusT)
sbc rR3,rmp ; Subtract MSB and carry
; Display LSB on LCD
ldi ZH,3 ; Position on LCD
ldi ZL,4
rcall LcdPos
ldi rmp,'+' ; Positive
tst rR2 ; Test if negative
brpl DisplayT1 ; No, continue
ldi rmp,'-' ; Set negative
neg rR2 ; Subtract from 0xFF
DisplayT1:
rcall LcdD4Byte ; Display sign
ldi ZL,10 ; Decimal 10
clr rmp ; Digit counter
DisplayT2:
sub rR2,ZL ; Subtract decimal
brcs DisplayT3 ; Carry, to end
inc rmp ; Increase digit counter
rjmp DisplayT2 ; Go on subtracting
DisplayT3:
add rR2,ZL ; Take back last subtraction
tst rmp ; Digit = zero?
brne DisplayT4 ; no
ldi rmp,' ' ; Blank instead leading zero
rjmp DisplayT5 ; Display
DisplayT4:
subi rmp,-'0' ; To ASCII
DisplayT5:
rcall LcdD4Byte ; Display digit
ldi rmp,'0' ; ASCII-0
add rmp,rR2 ; Convert digit to ASCII
rjmp LcdD4Byte ; Write last digit
;
;
; 16-by-16 bit multiplication
Multiplication: ; rM1:rM2 * ZH:ZL
; Result in rR3:rR2:rR1:rR0
clr rM2 ; Clear bytes 3 and 4
clr rM3
clr rR0 ; Clear result
clr rR1
clr rR2
clr rR3
Multiplication1:
lsr ZH ; Shift bit from MSB to LSB
ror ZL ; Shift lowest bit to carry
brcc Multiplication2 ; Do not add
add rR0,rM0 ; Add multiplicator to result
adc rR1,rM1
adc rR2,rM2
adc rR3,rM3
Multiplication2:
mov rmp,ZL ; All ones shifted out?
or rmp,ZH
breq Multiplication3 ; Yes, done
lsl rM0 ; Multipliy multiplicator by two
rol rM1
rol rM2
rol rM3
rjmp Multiplication1 ; Continue multiplication
Multiplication3:
ret
;
; --------- LCD routines ---------------
LcdTextOut:
.db "Voltage/Current/Temp",0x0D,0xFF
.db "U = xx,xx V",0x0D
.db "I = xxx,x mA",0x0D,0xFF
.db "T = +xx ",0x00,"C",0xFE,0xFF
; 4
; Dregree character
LcdDefChar:
.db 64,0,14,10,14,0,0,0,0,0 ; Z = 0, Grad
.db 0,0 ; End of character table
; LCD include routines
.include "Lcd4Busy.inc"
;
; End of source code
;
Two new instructions are used here. NEG register subtracts the content
of the register from 256 and stores the result in the register. This inverts negative
values (bit 7 is set) to their positive value (bit 7 is clear).
The instruction BRPL label branches to the label if the sign during the
last operation is cleared (the value was positive).
14.3.4 Example
This is an example for temperature measurement.
©2017 by http://www.avr-asm-tutorial.net