Path:
Home ==>
AVR-EN ==>
Micro beginner ==> 13. Frequency counter,
inductivity meter
Diese Seite in Deutsch (extern):
Lecture 13: 13. Frequency counter and inductivity meter
And again something really practical: a frequency counter with conversion of
24 and 40 bit binaries to decimal ASCII. And further a practical device to
measure inductivities, with squaring and division in assembler.
13.0 Overview
- Introduction to frequency measuring
- Introduction to decimal conversion
- Digital signal measuring with PCINT
- Analog signal measuring with analog comparer
- Inductivity measuring with PCINT
13.1.1 Digital signals and counting limits
Measuring frequencies is rather trivial: one detects polarity changes, counts
those during one second and displays those on a LCD. Because one second is a bit
long, how about counting for 0.5 or 0.25 seconds?
If the signal is already a digital one we can use the INT0 or any PCINT to
detect polarity changes. If INT0 is selected as input, we can select the type
of level change that triggers the interrupt (rising, falling or both edges).
If both edges are selected or if PCINT is used, two signals trigger the interrupt
for each wave.
The complicated task in assembler is then the multiplication of the counted
signals by two or four. The C programmer now includes his large math library
(and changes to a larger AVR type because the flash is too small). People
familiar with assembler just do a double shift or rotate left on the counter
registers, such as:
; Count result during 0.25 seconds gate time in R3:R2:R1
lsl R1 ; Multiply by two
rol R2
rol R3
lsl R1 ; and by four
rol R2
rol R3
and are done.
But why three registers? With those one can count to 256*256*256-1 = 16,777,215
in a quarter second, so up to 66.8 Mcs/s. This is far above what the usual
audio generator can generate, and far beyond what an AVR with 1 Mcs/s
clock can count. The following interrupt service routine for counting has the
listed execution times:
; Count interrupt: 4 clock cycles for interrupt trigger plus 2 clock cycles for vector jump
CntIsr:
in R15,SREG ; Save SREG, +1 = 7
inc R1 ; Count LSB up, +1 = 8
brne CntIsrRet ; No overflow, +1/2 = 9/10
inc R2 ; MSB up, +1 = 10
brne CntIsrRet ; No overflow, +1/2 = 11/12
inc R3 ; HSB up, +1 = 12
CntIsrRet: ; 10/12/13 clock cycles
out SREG,R15 ; Restore SREG, +1 = 11/13/14
reti ; +4 = 15/17/18
With 15 clock cycles at 1 Mcs/s system clock 15 µs are elapsed
and at 1,000,000 / 15 = 66,667 cs/s counting is at its limit.
If we would increase the controller's clock rate to 8 Mcs/s, we would be at
533 kcs/s counting rate.
Another opportunity to count digital signals automatically is the timer/counter.
Instead of the controller clock prescaler that we previously used to clock the
timer we can select either the input pin T0 to clock the timer/counter TC0 or
the input pin T1 to clock the timer/counter TC1, either at rising or falling
edges. If we use this we can count up to 250 kcs/s (at 1 Mcs/s clock)
or 2 Mcs/s (at 8 Mcs/s clock).
As T1 is on pin 9, together with PA4, and as we use this pin already as data port
for the LCD, we run into a conflict in use. With a different type of AVR we can
possibly avoid this conflict.
Another method to increase the counting limit would be to increase the controller
clock to the maximum possible would be to clock the controller with an external
clock source of up to 20 Mcs/s (for which we would need a port B portpin)
or to prescale the digital signal with an external divider by two, four, eight,
ten, sixteen or 256 (by that reducing the resolution).
At the other end of the spectrum, at very low frequencies, we can measure the time
between two signals, e.g. in µs, and to calculate the frequency from that.
How division with large binaries works is shown later on in that lecture. The
good news is that we do not need the large C library for that.
13.1.2 Detecting analog signals
Very often frequency signals are not digital and 5 V level but sine waves
with small amplitudes. Signals from a dynamic microfone (5 mV) or a speaker
(2 V at 1 Watt power on 4 Ω) do not fit to a digital input.
The electronic hobbyist now amplifies those signals so that a steep rectangle
wave results and uses a Schmitt-trigger to increase its steepness further. Adding
that external electronics is disparate: a small 14 pin controller surrounded by
three of four times larger external electronics.
And it is completely unnecessary because the AVR has an analog comparator on board.
This comparator is an integrated op-amp. Its positive input is attached to pin AIN0
(PA1), the negative input is AIN1 (PA2). The result of the op-amp can be read from
the bit ACO in the analog status and control port ACSR. If the ACIE bit in this port
is set, any change in this result leads to a analog comparator interrupt and calls
the Analog Comparator Interrupt vector (in ATtiny24: vector 0x000C). This mechanism
can be used to measure analog frequencies with very small amplitudes by simply
counting those interrupts. As interrupts occur every time the comparision result
changes each sine wave triggers two interrupts.
13.1.3 Measuring inductivities
Inductivities are coils. The more turns the coil has, the higher its inductivity. It
is measured in Henry (H). Measuring can be based on the impedance ZL that
the coil shows when a sine wave of a certain frequency F is applied. The larger the
coil the larger is its resistance ZL against AC. The formula shows the
relation of the impedance ZL and the inductivity L.
ZL (Ohms) = 2 * Π * F (cs/s) * L (H)
The part "2 * Π * F" is called angular frequency and abbreviated as
ω.
To measure the resistance is a little bit complicated as it is AC. An AD converter
would have to measure the voltage many times and would have to determine the maximum
of the sine wave. For small inductivities at elevated frequencies this method is not
very reliable. More intelligent is it to add a capacity to the inductor, to
oscillate this combination and to measure the frequency with which it oscillates.
At this oscillation frequency the impedance of the inductor equals the impedance of
the capacitor, which is
ZC = 1 / (ω * C (Farad))
and therefore decreases with higher frequencies. If ZL = ZC
are combined the oscillation frequency is:
ZL = ZC or ω * L = 1 / (ω * C) or
ω2 = 1 / (L * C) oder ω = √(1/(L * C)) oder F = 1 / (2 * Π) *
√(1/(L * C))
Measuring the frequency F of the resonant circuit delivers the inductivity L of the
coil as
L(H) = 1 / (4 * Π2 * C(F) * F(cs/s)2)
and so yields the inductivy.
The assembler programmer hates such formulas (the C programmer doesn't, invokes the
powerful math lib and changes device type to xmega). Not so in assembler, applying
some intelligence. As 4 as well as Π2 as well as the capacity C do not
change, we have to calculate 1 / (4 * Π2 * C) once and divide this
constant by F2. If we multiply this constant by 1.000.000 the result is
in µH directly. For a capacity of 50 nF the constant is 506,605,918,079
or hexadecimal 75.F4.10.D7.7F, a 40 bit binary.
To divide this constant by F2 we first multiply F with itself. The frequency
F is a 24 bit long number, which yields a 48 bit long multiplication result. We can
skip the upper eight bits of that because that high frequencies cannot be measured.
The following table shows the maximum and minimum limits of the division (thousands
separator ".", decimal point ",").
If we limit F2 to 40 bits, F can be at maximum 741.455 kcs/s. This is
beyond what can be measured at 1 Mcs/s clock frequency.
The lower limit results from the fact that inductivities above 999 H are
unnecessary. Only frequencies equal or above 23 cs/s come into question, which is
low enough and fits all our needs.
Division 8 bit by 8 bit
For the inductivity display we will need a binary division. The simplest division
is 8 by 8 bit. And this goes as follows.
For division by 8 bits we need a further 8 bit register. In the first step the
highest bit of the divident is shifted into bit 0 of this register, by shifting
that into the carry and rolling it to the additional register. Now the divider
is subtracted from this extra register. If this subtraction yields the carry
flag set, the subtraction is taken back by adding the divider again. The reversed
carry bit from the subtraction is then shifted left into the result register.
These steps are repeated seven times. The division is complete. In assembler this
goes like that:
;
; Division 8 bit by 8 Bit
;
ldi R16,0xEE ; Divident
ldi R17,10 ; Divider
ldi R18,8 ; Number of bits
clr R19 ; Clear divident extra register
clr R20 ; Clear result
Shift:
lsl R16 ; Shift most significant divident bit to carry
rol R20 ; Roll into the MSB of the divident
sub R20,R17 ; Subtract divider
brcc One ; Carry is clear, shift a one into the result
add R20,R17 ; Restore previous MSB divident
clc ; Clear result bit in carry
rjmp Result ; Shift into result
One:
sec ; Set carry bit
Result:
rol R19 ; Roll carry bit into result
dec R18 ; Count down
brne Shift ; Go on dividing
; done
The simulation in the Studio says that the division lasts 92 µs.
That is rather simple and quick. And it is even easier than decimal dividing.
Division 16 bit by 8 bit
If a 16 bit long binary has to be divided two registers come into play for the
divident, the extra divident shifter and the result.
;
; Division 16 bit by 8 bit
;
ldi R31,High(50000) ; Divident
ldi R30,Low(50000)
ldi R16,75 ; Divider, LSB
clr R17 ; MSB divident
clr R27 ; Result, MSB
clr R26 ; dto., LSB
clr R18 ; Extra divident, LSB
clr R19 ; dto., MSB
ldi R20,16 ; 16 bits, counter
Shift:
lsl R30 ; Shift LSB divident left
rol R31 ; Roll into MSB, shift bit 7 MSB to carry
rol R18 ; Roll carry into extra divident, LSB
rol R19 ; Roll bit 7 LSB into MSB
sub R18,R16 ; Subtract Divider, LSB
sbc R19,R17 ; Subtract MSB with carry
brcc One ; No carry, shift a one to the result
add R18,R16 ; Carry set, restore original before subtract
adc R19,R17 ; Add MSB and carry
clc ; Shift a zero to the result
rjmp Result ; Roll into the result
One:
sec ; Shift a one to result
Result:
rol R26 ; Roll carry into result, LSB
rol R27 ; Roll carry into result, MSB
dec R20 ; Count down
brne Shift ; Go on dividing
; Done
Again this is not very complicated. It needs 265 µs.
Division 40 bit by 40 bit
After understanding the principle of division it seems easy to extend the division
to larger binaries. But dividing the 40 bit divident by a 40 bit divider using an
additional 40 bit long number and resulting in a 40 bit number requires 160 bits
or twenty 8 bit registers. Using resgisters would not leave much registers for other
puposes. The solution for this shortage is the SRAM: we place the 40 bit there and
by repeating
ld R16,Z ; Z points to number in SRAM
rol R16 ; roll into carry
st Z+,R16 ; and store rolled result
for five times we have the highest bit of the remaining divident in the carry flag and
can roll that into the extra division register. This needs a bit longer execution time
but works fine.
The following table demonstrates the stages of the division for a measured frequency
of 1000 cs/s (=0x0003E8, F2 = 1.000.000 = 0x0F4240).
Dec. | Hex | Result hex | Post subtr. | Subtr | Post rolling | Roll | SRAM, Hex | SRAM, Dec |
40 | 28 | 0000000000 | 0000000000 | 0 | 0000000000 | 0 | 75F410D77F | 506,605,918,079 |
39 | 27 | 0000000000 | 0000000001 | 0 | 0000000001 | 1 | EBE821AEFE | 1,013,211,836,158 |
38 | 26 | 0000000000 | 0000000003 | 0 | 0000000003 | 1 | D7D0435DFC | 926,912,044,540 |
37 | 25 | 0000000000 | 0000000007 | 0 | 0000000007 | 1 | AFA086BBF8 | 754,312,461,304 |
36 | 24 | 0000000000 | 000000000E | 0 | 000000000E | 0 | 5F410D77F0 | 409,113,294,832 |
35 | 23 | 0000000000 | 000000001D | 0 | 000000001D | 1 | BE821AEFE0 | 818,226,589,664 |
34 | 22 | 0000000000 | 000000003A | 0 | 000000003A | 0 | 7D0435DFC0 | 536,941,551,552 |
33 | 21 | 0000000000 | 0000000075 | 0 | 0000000075 | 1 | FA086BBF80 | 1,073,883,103,104 |
32 | 20 | 0000000000 | 00000000EB | 0 | 00000000EB | 1 | F410D77F00 | 1,048,254,578,432 |
31 | 1F | 0000000000 | 00000001D7 | 0 | 00000001D7 | 1 | E821AEFE00 | 996,997,529,088 |
30 | 1E | 0000000000 | 00000003AF | 0 | 00000003AF | 1 | D0435DFC00 | 894,483,430,400 |
29 | 1D | 0000000000 | 000000075F | 0 | 000000075F | 1 | A086BBF800 | 689,455,233,024 |
28 | 1C | 0000000000 | 0000000EBE | 0 | 0000000EBE | 0 | 410D77F000 | 279,398,838,272 |
27 | 1B | 0000000000 | 0000001D7D | 0 | 0000001D7D | 1 | 821AEFE000 | 558,797,676,544 |
26 | 1A | 0000000000 | 0000003AFA | 0 | 0000003AFA | 0 | 0435DFC000 | 18,083,725,312 |
25 | 19 | 0000000000 | 00000075F4 | 0 | 00000075F4 | 0 | 086BBF8000 | 36,167,450,624 |
24 | 18 | 0000000000 | 000000EBE8 | 0 | 000000EBE8 | 0 | 10D77F0000 | 72,334,901,248 |
23 | 17 | 0000000000 | 000001D7D0 | 0 | 000001D7D0 | 0 | 21AEFE0000 | 144,669,802,496 |
22 | 16 | 0000000000 | 000003AFA0 | 0 | 000003AFA0 | 0 | 435DFC0000 | 289,339,604,992 |
21 | 15 | 0000000000 | 0000075F41 | 0 | 0000075F41 | 1 | 86BBF80000 | 578,679,209,984 |
20 | 14 | 0000000000 | 00000EBE82 | 0 | 00000EBE82 | 0 | 0D77F00000 | 57,846,792,192 |
19 | 13 | 0000000001 | 00000E3AC4 | 1 | 00001D7D04 | 0 | 1AEFE00000 | 115,693,584,384 |
18 | 12 | 0000000003 | 00000D3348 | 1 | 00001C7588 | 0 | 35DFC00000 | 231,387,168,768 |
17 | 11 | 0000000007 | 00000B2450 | 1 | 00001A6690 | 0 | 6BBF800000 | 462,774,337,536 |
16 | 10 | 000000000F | 0000070661 | 1 | 00001648A1 | 1 | D77F000000 | 925,548,675,072 |
15 | 0F | 000000001E | 00000E0CC3 | 0 | 00000E0CC3 | 1 | AEFE000000 | 751,585,722,368 |
14 | 0E | 000000003D | 00000CD746 | 1 | 00001C1986 | 0 | 5DFC000000 | 403,659,816,960 |
13 | 0D | 000000007B | 00000A6C4D | 1 | 000019AE8D | 1 | BBF8000000 | 807,319,633,920 |
12 | 0C | 00000000F7 | 000005965A | 1 | 000014D89A | 0 | 77F0000000 | 515,127,640,064 |
11 | 0B | 00000001EE | 00000B2CB5 | 0 | 00000B2CB5 | 1 | EFE0000000 | 1,030,255,280,128 |
10 | 0A | 00000003DD | 000007172B | 1 | 000016596B | 1 | DFC0000000 | 960,998,932,480 |
9 | 09 | 00000007BA | 00000E2E57 | 0 | 00000E2E57 | 1 | BF80000000 | 822,486,237,184 |
8 | 08 | 0000000F75 | 00000D1A6E | 1 | 00001C5CAE | 0 | 7F00000000 | 545,460,846,592 |
7 | 07 | 0000001EEB | 00000AF29D | 1 | 00001A34DD | 1 | FE00000000 | 1,090,921,693,184 |
6 | 06 | 0000003DD7 | 000006A2FB | 1 | 000015E53B | 1 | FC00000000 | 1,082,331,758,592 |
5 | 05 | 0000007BAE | 00000D45F7 | 0 | 00000D45F7 | 1 | F800000000 | 1,065,151,889,408 |
4 | 04 | 000000F75D | 00000B49AF | 1 | 00001A8BEF | 1 | F000000000 | 1,030,792,151,040 |
3 | 03 | 000001EEBB | 000007511F | 1 | 000016935F | 1 | E000000000 | 962,072,674,304 |
2 | 02 | 000003DD76 | 00000EA23F | 0 | 00000EA23F | 1 | C000000000 | 824,633,720,832 |
1 | 01 | 000007BAED | 00000E023F | 1 | 00001D447F | 1 | 8000000000 | 549,755,813,888 |
0 | Rdg | 000007BAEE | 00000CC23E | 1 | 00001C047E | 0 | 0000000000 | 0 |
The result of the division before rounding, 506,606 µH, equals our expected
result. The calculation requires 277 µs for the multiplication of F with
itself, 2,919 µs for the division and 487 µs for the decimal
conversion, in total 3.232 µs for all. Not too long for such a lengthy
operation.
We can use this in the software for the inductivity meter, without a giant math lib
and all within a small and tiny 24.
Already back in lecture 11 we converted 8 and 16 bit binaries to ASCII and suppressed
leading zeros. During the infrared experiments in lecture 12 we made it easy and
displayed hexadecimal numbers. Now we have monster numbers with 24 or 32 bits to display.
I am not sure what the C programmer now does, in any case he is stuck to his mighty
floating point math lib and changes to an xmega type. What makes the rumor that assembler
is too complicated out of people that usually are intelligent enough to understand rather
complex issues.
The extension of the 16 bit decimal conversion to 24 or 32 long binaries is rather
simple if the algorithm is well understood: repeatedly subtracting the binary
representation of the decimal digits. Starting with the largest and down to the 10s. In
8 bit our largest was 100, in 16 bit 10,000. In 24 bit the largest is 256 power to
three minus 1 = 16.777.215, so we start with 10 millions, at 32 bit
(256 powered to 4 minus 1 = 4.294.967.295) simply with one billion. At 40 bit we would
come to a limitation of the assembler that can handle integers only as signed 32 bit
binaries. But this can be avoided by splitting the 40 bit binaries into two 24 bit
binaries and is demonstrated later on in the course.
13.3.1 Task
As first task we measure the frequency of digital signals.
13.3.2 Hardware, mounting
The hardware needed for this task is simply nil: the signal source is simply attached
to input pin PA3.
Of course, the experimental board here
can also be used for that (and the following experiments in this lecture).
The source in my case is a homebrew digital signal generator.
13.3.3 Program
The program is listed in the following, the source code is here.
;
; ***********************************************************
; * Frequency counting of digital signals with ATtiny24/LCD *
; * (C)2017 by www.avr-asm-tutorial.net *
; ***********************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Switches -----------------
.equ RawData = 0 ; 1: Display raw data
; 0: Average the last four
.equ debugAverages = 0 ; 1: Jump to average calc
; 0: normal
.equ New = 0x10203 ; Newester Messwert
.equ Last = New ; Laster Messwert
.equ PreLast = New/2 ; PreLaster Messwert/2
.equ PrePreLast = New/4 ; PrePreLaster Messwert/4
.equ PrePrePreLast=New/4 ; PrePrePreLaster Messwert/4
;
; ----------- Hardware -----------------
; Digital rrequency counter input on PCINT3/PA3
;
; ----------- Timing -------------------
; Gate time 250 ms
; Controller clock 8,000,000 cs/s
; TC1-Prescaler 64
; TC1 clock 125,000 cs/s
; TC1 clocks in 250 ms 31,250
.equ cTc1CmpA = 31249
;
; ----------- Value calculation --------
; Current value / 2 plus
; Last value / 4 plus
; Pre last value / 8 plus
; Pre pre last value / 8 =
; Current display value
;
; ----------- Ports, portpins --------------
.equ pOut = PORTA ; Output port for pullup
.equ pDir = DDRA ; Direction port for pullup
.equ bIO = PORTB3 ; Pin digital input
.equ bID = DDA3 ; Pin direction digital input
;
; ----------- Registers -----------------
; Used: R0, R1 for LCD
.def rM0L = R2 ; Current value, LSB
.def rM0M = R3 ; dto., MSB
.def rM0H = R4 ; dto., HSB
; free: R5 .. R14
.def rSreg = R15 ; Save/Restore SREG
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Additional multi purpose
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD-Register
.def rimp = R20 ; Multi purpose inside interrupts
.def rFlag = R21 ; Flags
.equ bTO = 0 ; Timeout from timer
.def rHelp = R22 ; Hilfsregister Dezimal
; free: R22 .. R25
; Used: R27:R26 X ; for diverse purposes
; free: R29:R28 Y
; Used: R31:R30 Z ; for LCD
;
; ----------- SRAM ---------------------
.DSEG
.ORG 0x0060
sM: ; Measure value storage, four values:
; current/2, last/4, pre-last/8,
; pre-pre-last/8
; each as: L(+0), M(+1), H(+2)
.Byte 12
sMEnd:
;
; ---- Reset and interrupt vectors ---------
.CSEG
.ORG 0x0000
rjmp Start ; Reset-Vektor, init
reti ; INT0 External Int 0
rjmp Pci0Isr ; PCI Request 0
reti ; PCINT1 PCI Request 1
reti ; WDT Watchdog Time-out
reti ; TIM1_CAPT TC1 Capture Event
rjmp Tc1Isr ; 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
reti ; ADC ADC Conversion Complete
reti ; EE_RDY EEPROM Ready
reti ; USI_STR USI START
reti ; USI_OVF USI Overflow
;
; ----- Interrupt Service Routines -----
Pci0Isr: ; PCINT0 ISR, count pulses on the digital input
in rSreg,SREG ; Save SREG
inc rM0L ; Count LSB
brne Pci0IsrRet ; No overflow
inc rM0M ; Increase MSB
brne Pci0IsrRet ; No overflow
inc rM0H ; Increase HSB
Pci0IsrRet:
out SREG,rSreg ; Restore SREG
reti
; TC1 Compare match A interrupt, end of gate time
Tc1Isr: ; Time out counter
in rSreg,SREG ; Save SREG
ldi rimp,0 ; Disable PCINT0 interrupt
out GIMSK,rimp ; in general interrupt mask
sbr rFlag,1<<bTO ; Set time out flag
out SREG,rSreg ; Restore SREG
reti
;
; ----------- Main program init ------
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; Point to RAMEND
out SPL,rmp ; to stack pointer
; Change to 8 Mcs/s clock
ldi rmp,1<<CLKPCE ; Set change enable bit
out CLKPR,rmp ; in clock prescaler port
ldi rmp,0 ; Precaler / 1
out CLKPR,rmp ; in clock prescaler port
; Init LCD port control outputs
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; to LCD control port
clr rmp ; Outputs off
out pLcdCO,rmp ; to Lcd control port
ldi rmp,mLcdDRW ; LCD data port write
out pLcdDR,rmp ; to LCD data port
; Init input port
sbi pOut,bIO ; Input pin pullup
cbi pDir,bID ; Input pin as input
.if debugAverages == 1 ; Debug-Code: calc average
ldi ZH,High(sM) ; Point to values in SRAM
ldi ZL,Low(sM)
ldi rmp,Byte3(Last) ; Last value/2
st Z+,rmp
ldi rmp,Byte2(Last)
st Z+,rmp
ldi rmp,Byte1(Last)
st Z+,rmp
ldi rmp,Byte3(PreLast) ; Pre-last value /4
st Z+,rmp
ldi rmp,Byte2(PreLast)
st Z+,rmp
ldi rmp,Byte1(PreLast)
st Z+,rmp
ldi rmp,Byte3(PrePreLast) ; Pre-pre-last value /8
st Z+,rmp ;
ldi rmp,Byte2(PrePreLast)
st Z+,rmp
ldi rmp,Byte1(PrePreLast)
st Z+,rmp
ldi rmp,Byte3(PrePrePreLast) ; Pre-pre-pre last value /8
st Z+,rmp
ldi rmp,Byte2(PrePrePreLast)
st Z+,rmp
ldi rmp,Byte1(PrePrePreLast)
st Z+,rmp
Repeat:
ldi rmp,Byte3(New) ; Latest value
mov rM0H,rmp
ldi rmp,Byte2(New)
mov rM0M,rmp
ldi rmp,Byte1(New)
mov rM0L,rmp
rcall Evaluate
rjmp Repeat
.endif
; Init LCD
rcall LcdInit ; Start LCD
ldi ZH,High(2*LcdTextOut) ; Point Z to text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Display text
; Init timer
ldi rmp,High(cTc1CmpA) ; Compare A value
out OCR1AH,rmp ; MSB to compare A port
ldi rmp,Low(cTc1CmpA)
out OCR1AL,rmp ; LSB to compare A port
clr rmp ; TC1 Normal operation
out TCCR1A,rmp
ldi rmp,(1<<CS11)|(1<<CS10) ; Presc 64
out TCCR1B,rmp
ldi rmp,1<<OCIE1A ; Compare Match Int
out TIMSK1,rmp ; Enable
; Activate PCINT3
ldi rmp,1<<PCINT3 ; Pin change PA3
out PCMSK0,rmp ; to mask
ldi rmp,1<<PCIE0 ; PCINT0 Interrupt
out GIMSK,rmp ; to int mask
; Sleep Mode
ldi rmp,1<<SE ; Sleep enable idle
out MCUCR,rmp ; to control port
; Enable interrupts
sei ; Set I bit in SREG
Loop:
sleep ; schlafen legen
nop ; nach Aufwachen
sbrc rFlag,bTO ; Skip next if time out flag clear
rcall Evaluate ; Process evaluate
rjmp Loop
;
; Evaluate counting results
Evaluate:
cbr rFlag,1<<bTO ; Clear time out flag
clr rmp ; Compare Match Int off
out TIMSK1,rmp
.if RawData == 1 ; Debug switch, display raw data
lsl rM0L ; Multiply value by 2
rol rM0M
rol rM0H
.else ; Calculate average
; Shift and divide values in SRAM
ldi ZH,High(sMEnd) ; Z is pointer to target
ldi ZL,Low(sMEnd)
ldi XH,High(sMEnd-3) ; X is pointer to source
ldi XL,Low(sMEnd-3)
ld rmp,-X ; Pre-last to pre-pre-last
st -Z,rmp ; Copy
ld rmp,-X
st -Z,rmp
ld rmp,-X
st -Z,rmp
ld rmp,-X ; Last to pre-last with division
lsr rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
ld rmp,-X ; Newest to last with division
lsr rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
st -Z,rM0H ; Store current value
st -Z,rM0M
st -Z,rM0L
adiw ZL,3 ; Point to last value
ldi rmp,4
mov R0,rmp ; R0 is counter
Evaluate1:
ld rmp,Z+ ; Read LSB
add rM0L,rmp ; Add to current
ld rmp,Z+ ; Read MSB
adc rM0M,rmp ; Add to current with overflow
ld rmp,Z+ ; Read HSB
adc rM0H,rmp ; Add to current with overflow
dec R0 ; Count down
brne Evaluate1 ; Add further values
.endif
.if debugAverages == 1 ; Debug switch
ret ; Skip LCD display, for debug breakpoint
.endif
ldi ZH,1 ; LCD display position
ldi ZL,8
rcall LcdPos
rcall DecimalDisplay ; Display decimal
Newstart:
clr rM0L ; Clear count
clr rM0M
clr rM0H
ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupt
out GIMSK,rmp ; to int mask
clr rmp ; Clear TC1
out TCNT1H,rmp ; MSB
out TCNT1L,rmp ; LSB
ldi rmp,1<<OCIE1A ; Enable compare match A int
out TIMSK1,rmp
ret
;
; 3 byte binary in rM0H:rM0M:rM0L to decimal
; on LCD
DecimalDisplay:
ldi ZH,High(2*DecimalTab)
ldi ZL,Low(2*DecimalTab)
clt ; Supprss leading zeros
DecimalDisplay1:
lpm XL,Z+ ; Read decimal, LSB
lpm XH,Z+ ; MSB
lpm rHelp,Z+ ; HSB
clr rmp ; Clear divider counter
cp XL,rmp ; Check table end
brne DecimalDisplay2 ; No
cp XH,rmp ; Check MSB
brne DecimalDisplay2 ; No
cp rHelp,rmp ; Check HSB
breq DecimalDisplayEnd ; Calculation end
DecimalDisplay2:
sub rM0L,XL ; Subtract decimal, LSB
sbc rM0M,XH ; dto., MSB
sbc rM0H,rHelp ; dto., HSB
brcs DecimalDisplay3 ; Overflow
inc rmp ; Next higher digit
rjmp DecimalDisplay2 ; Subtract further
DecimalDisplay3:
add rM0L,XL ; Take back last subtraction, LSB
adc rM0M,XH ; MSB
adc rM0H,rHelp ; HSB
tst rmp ; Zero?
brne DecimalDisplay4 ; Not zero
brts DecimalDisplay5 ; Do not suppress zero
ldi rmp,' ' ; Blank
rcall LcdD4Byte ; to LCD
ldi rmp,' ' ; Check decimal thousand
rjmp DecimalDisplayKomma
DecimalDisplay4:
set ; Do not suppress zeros any more
DecimalDisplay5:
subi rmp,-'0' ; Convert to ASCII
rcall LcdD4Byte ; To LCD
ldi rmp,',' ; Display a thousands separator
DecimalDisplayKomma:
cpi XL,Byte1(1000000) ; A million?
breq DecimalDisplayKomma1
cpi XL,Byte1(1000) ; One thousand?
breq DecimalDisplayKomma1
rjmp DecimalDisplay1 ; Continue
DecimalDisplayKomma1:
rcall LcdD4Byte ; Output rmp
rjmp DecimalDisplay1 ; Continue
DecimalDisplayEnd:
ldi rmp,'0' ; Last digit
add rmp,rM0L ; Add rest
rjmp LcdD4Byte ; Display and return
;
DecimalTab:
.db Byte1(1000000),Byte2(1000000)
.db Byte3(1000000),Byte1(100000)
.db Byte2(100000),Byte3(100000)
.db Byte1(10000),Byte2(10000)
.db Byte3(10000),Byte1(1000)
.db Byte2(1000),Byte3(1000)
.db Byte1(100),Byte2(100)
.db Byte3(100),Byte1(10)
.db Byte2(10),Byte3(10)
.db 0,0,0,0
;
; LCD Start text
LcdTextOut:
.db "Frequency meter tn24",0x0D,0xFF
.db "F(dig)= x,xxx,xxx Hz",0x0D,0xFF
; 8
.db " ",0x0D,0xFF
.db " ",0xFE,0xFE
;
; LCD include routines
.include "Lcd4Busy.inc"
;
; End of source code
;
13.3.4 Examples
With a xtal controlled signal generator the result is as follows:
The two results are not equal, the ATtiny24 result is too low by roughly 0.19%.
This might be tolerable for most of the applications. It seems that the
ATtiny24 runs with a slightly higher RC frequency. That is due to the rather rough
adjustment of the RC, which is adjusted at lower operating voltages and runs faster
at 4.8 V. To achieve a higher accuracy we can change the oscillator calibrator
byte, as described in the device's databook. Also we can adjust with a multiplication,
but 0.19% is not a large difference and we have to use 17 bit multiplication
(65661 / 65536).
13.4.1 Task
The frequency of sine wave AC with 5 mV (eff.) amplitude and above shall be
measured and displayed.
13.4.2 Hardware and components
Scheme
This is the necessary hardware to measure. The main components are a voltage divider.
This divider provides a voltage that is roughly half of the operating voltage, to
which the negative op-amp input is tied to and AC is blocked with the 1 µF
capacity. The small 220 Ω resistor provides a roughly 5 mV higher
voltage to which the other input is connected via a 100 k resistor. This design
blocks signal changes on the input line that have a very small amplitude (open input).
The AC to be measured comes via a 100 nF capacitor and modulates the voltage
divider's DC.
Components
This is the 1 µF capacitor. The longer of the two wires is the plus pole,
be sure to have it polarized in the right direction.
This is a possible form of the film capacitor of 100 nF.
This are the three 100 kΩ resistors, from which the voltage divider is
made of and that feed the DC to the sensing input. The 220 Ω resistor
was shown in an earlier lecture.
Mounting
The mounting looks like this. If you shorten the resistor wires and if you mount
the components in a compact manner you get less noise on the input and the measurement
is more stable.
13.4.3 Program
The program is listed in the following, the source code is here.
A very special condition occurs here: the operation of the analog comparator and
the sleep mode are conflicting. Sometimes, and inpredictable, the controller does
not wake up on interrupts. Neither comparator nor timer interrupts wake up the
CPU, the controller goes to deep sleep and is dead. Only a reset wakes up the
controller again. ATMEL confirms this error in the device handbook. This software
works without sleep mode, therefore.
Counting events from the PCINT on the digital input as well as those from the analog
comparator are measured in two time phases with only one of the two interrupts
enabled, one after the other. And the software uses the same interrupt service
routine, the software part to calculate averages uses two different value buffers
in the SRAM.
;
; ********************************************************
; * Frequency meter analog and digital with ATtiny24/LCD *
; * (C)2017 by www.avr-asm-tutorial.net *
; ********************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Hardware -----------------
; Analog frequency counter on analog-
; comparator AIN0/PA1 and AIN1/PA2
; Digital frequency counter on PCINT3/PA3
;
; ----------- Timing -------------------
; Gate time 250 ms
; Controller clock 8.000.000 cs/s
; TC1 prescaler 64
; TC1 clock 125.000 cs/s
; TC1 clock in 250 ms 31.250
.equ cTc1CmpA = 31249
;
; ----------- Value averaging --------
; Current value / 2 plus
; Last value / 4 plus
; Pre last value / 8 plus
; Pre pre last value / 8 =
; Displayed averaged value
;
; ----------- Ports, port pins --------
.equ pOut = PORTA ; Output port
.equ pDir = DDRA ; Direction port
.equ bIO = PORTB3 ; Port pin digital pullup
.equ bID = DDA3 ; Port pin direction pullup
;
; ----------- Registers -----------------
; Used: R0, R1 for LCD
.def rM0L = R2 ; Current value, LSB
.def rM0M = R3 ; dto., MSB
.def rM0H = R4 ; dto., HSB
; free: R5 .. R14
.def rSreg = R15 ; Save/restore SREG
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Another multi purpose register
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD-Register
.def rimp = R20 ; Multi purpose inside interrupts
.def rFlag = R21 ; Flags
.equ bTO = 0 ; Time out timer
.equ bAn = 1 ; Analog comparer active
.def rHelp = R22 ; Additional register decimal
; free: R23 .. R25
; Used: R27:R26 X ; for diverse purposes
; free: R29:R28 Y
; benutzt: R31:R30 Z ; for LCD
;
; ----------- SRAM ---------------------
.DSEG
.ORG 0x0060
sMD: ; Digital measured values
.Byte 12
sMDEnd:
sMA: ; Analog measured values
.Byte 12
sMAEnd:
;
; ---- Reset and interrupt vectors ---------
.CSEG
.ORG 0x0000
rjmp Start ; Reset vector, init
reti ; INT0 External Int 0
rjmp CntIsr ; PCI Request 0
reti ; PCINT1 PCI Request 1
reti ; WDT Watchdog Time-out
reti ; TIM1_CAPT TC1 Capture Event
rjmp Tc1Isr ; 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
rjmp CntIsr ; ANA_COMP Analog Comparator
reti ; ADC ADC Conversion Complete
reti ; EE_RDY EEPROM Ready
reti ; USI_STR USI START
reti ; USI_OVF USI Overflow
;
; ----- Interrupt Service Routines -----
CntIsr: ; PCINT0/ANA_COMP, count pulses
in rSreg,SREG ; Save SREG
inc rM0L ; Count up
brne CntIsrRet ; No overflow
inc rM0M ; Increase MSB
brne CntIsrRet ; No overflow
inc rM0H ; Increase HSB
CntIsrRet:
out SREG,rSreg ; Restore SREG
reti
;
Tc1Isr: ; TC1 time out interrupt
ldi rimp,0
out ACSR,rimp ; Disable Int Comparator
out GIMSK,rimp ; Disable PCInt
in rSreg,SREG ; Save SREG
sbr rFlag,1<<bTO ; Set time out flag
out SREG,rSreg ; Restore SREG
reti
;
; ----------- Main program init ------
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; Point to RAMEND
out SPL,rmp ; to stack pointer
; Set 8 Mcs/s controller clock source
ldi rmp,1<<CLKPCE ; Clock change enable
out CLKPR,rmp ; in clock prescaler port
ldi rmp,0 ; Precaler = 1
out CLKPR,rmp ; to clock prescaler port
; Init LCD control port outputs
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; control port outputs
clr rmp ; Outputs clear
out pLcdCO,rmp ; to Lcd control port
ldi rmp,mLcdDRW ; LCD data port mask write
out pLcdDR,rmp ; to LCD data direction port
; Init digital input port
sbi pOut,bIO ; Input port pullup
cbi pDir,bID ; Input port is input
; Init LCD
rcall LcdInit ; Call included init routine
ldi ZH,High(2*LcdTextOut) ; Point Z to text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Display text
ldi rmp,0x0C ; Cursor and blink off
rcall LcdC4Byte
; Init timer
ldi rmp,High(cTc1CmpA) ; Compare value MSB
out OCR1AH,rmp ; to MSB timer compare port
ldi rmp,Low(cTc1CmpA) ; dto., LSB
out OCR1AL,rmp ; to timer compare port
clr rmp ; TC1 normal operation
out TCCR1A,rmp
ldi rmp,(1<<CS11)|(1<<CS10) ; Prescaler 64
out TCCR1B,rmp
ldi rmp,1<<OCIE1A ; Enable compare match int
out TIMSK1,rmp
; Deactivate analog comparer
ldi rmp,0 ; Disable interrupt comparator
out ACSR,rmp ; to analog comparer control port
ldi rmp,(1<<ADC2D)|(1<<ADC1D) ; Input pin driver disable
out DIDR0,rmp ; to disable pin port
; Activate PCINT3 digital input
ldi rmp,1<<PCINT3 ; Pin change PA3
out PCMSK0,rmp ; to PCINT0 mask port
ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupt
out GIMSK,rmp ; to general int mask
; No sleep mode due to analog comparator error!
ldi rmp,0 ; Sleep disable
out MCUCR,rmp ; to master control port
; Enable interrupts
sei ; Set I flag in SREG
Loop:
sbrc rFlag,bTO ; Skip next if time out flag clear
rcall Evaluate ; Flag set, evaluate measured value
rjmp Loop
;
; Evaluate and display measured counting results
Evaluate:
cbr rFlag,1<<bTO ; Clear time out flag
clr rmp ; Disable compare match int
out TIMSK1,rmp ; of TC1
; Shift measured value to SRAM and divide
sbrc rFlag,bAn ; Skip next if digital value
rjmp EvaluateAnalog ; Jump to analog value
ldi ZH,High(sMDEnd) ; Target address
ldi ZL,Low(sMDEnd)
ldi XH,High(sMDEnd-3) ; Source address
ldi XL,Low(sMDEnd-3)
rjmp EvaluateShift ; Shift values in and divide
EvaluateAnalog:
ldi ZH,High(sMAEnd) ; Target address
ldi ZL,Low(sMAEnd)
ldi XH,High(sMAEnd-3) ; Source address
ldi XL,Low(sMAEnd-3)
EvaluateShift:
; Shift measured value to SRAM and divide
ld rmp,-X ; Shift pre-last to pre-pre-last
st -Z,rmp ; Write to target
ld rmp,-X ; Copy from source
st -Z,rmp ; Write to target
ld rmp,-X ; Copy from source
st -Z,rmp ; Write to target
ld rmp,-X ; Shift last to pre-last and divide
lsr rmp ; divide by 2
st -Z,rmp ; Write to target
ld rmp,-X ; Copy from source
ror rmp ; Divide by 2 with carry
st -Z,rmp ; Write to target
ld rmp,-X ; Copy from source
ror rmp ; Divide by 2 with carry
st -Z,rmp ; Write to target
ld rmp,-X ; Shift latest to last and divide
lsr rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
st -Z,rM0H ; Store current measurement
st -Z,rM0M
st -Z,rM0L
adiw ZL,3 ; Point to new last
ldi rmp,4 ; Four rounds of adding sum
mov R0,rmp ; R0 is counter
Evaluate1:
ld rmp,Z+ ; Read LSB
add rM0L,rmp ; Add to sum
ld rmp,Z+ ; Read MSB
adc rM0M,rmp ; Add to sum with carry
ld rmp,Z+ ; Read HSB
adc rM0H,rmp ; Add to sum with carry
dec R0 ; Next round
brne Evaluate1 ; Go on adding
ldi ZH,1 ; Point to line 2 in LCD
sbrc rFlag,bAn ; Skip next if analog flag clear
ldi ZH,2 ; Point to line 3 in LCD
ldi ZL,8 ; Point to column 9
rcall LcdPos ; Set LCD position
rcall DecimalOut ; Write decimal to LCD
Restart:
clr rM0L ; Clear last decimal digit
ldi rmp,1<<bAn ; Invert analog flag
eor rFlag,rmp
sbrc rFlag,bAn ; Skip next if analog flag clear
rjmp RestartAnalog ; Restart an analog comparer cycle
; Measure digital, activate PCINT3
ldi rmp,1<<PCINT3 ; Enable pin change int on PA3
out PCMSK0,rmp ; to mask port
ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupt
out GIMSK,rmp ; to int mask
rjmp Restart1 ; Start timer TC1
RestartAnalog:
ldi rmp,1<<ACIE ; Enable interrupt analog comparator
out ACSR,rmp ; in analog comparer control port
Restart1:
clr rmp ; Clear timer TC1
out TCNT1H,rmp ; Compare match A port MSB
out TCNT1L,rmp ; dto., LSB
ldi rmp,1<<OCIE1A ; Enable compare match interrupt
out TIMSK1,rmp ; in TC1 mask
ret
;
; Display 3 byte binary in rM0H:rM0M:rM0L in decimal on LCD
DecimalOut:
ldi ZH,High(2*DecimalTab) ; Point Z to decimal table
ldi ZL,Low(2*DecimalTab)
clt ; Suppress leading zeros
DecimalOut1:
lpm XL,Z+ ; Read decimal number to X and rHelp
lpm XH,Z+
lpm rHelp,Z+
clr rmp ; Subtraction counter
cp XL,rmp ; LSB = 0?
brne DecimalOut2 ; No, go on
cp XH,rmp ; MSB = 0?
brne DecimalOut2 ; No, go on
cp rHelp,rmp ; HSB = 0?
breq DecimalOutEnd ; Yes, finalize
DecimalOut2:
sub rM0L,XL ; Subtract decimal from value, LSB
sbc rM0M,XH ; dto., MSB and carry
sbc rM0H,rHelp ; dto., HSB and carry
brcs DecimalOut3 ; Underflow occurred
inc rmp ; Count subtractions
rjmp DecimalOut2 ; No carry, go on subtracting
DecimalOut3:
add rM0L,XL ; Take back last subtraction
adc rM0M,XH
adc rM0H,rHelp
tst rmp ; Is digit zero?
brne DecimalOut4 ; No, not zero
brts DecimalOut5 ; Zero suppression is off
ldi rmp,' ' ; Display a blank
rcall LcdD4Byte
ldi rmp,' ' ; Thousands separator is a blank
rjmp DecimalOutKomma ; Check thousands separator
DecimalOut4:
set ; Do not suppress leading zeros any more
DecimalOut5:
subi rmp,-'0' ; Convert to ASCII
rcall LcdD4Byte ; and display on LCD
ldi rmp,',' ; Thousands separator
DecimalOutKomma:
cpi XL,Byte1(1000000) ; Millions?
breq DecimalOutKomma1 ; Yes, display komma
cpi XL,Byte1(1000) ; Thousands?
breq DecimalOutKomma1 ; Yes, display komma
rjmp DecimalOut1 ; No, go on converting
DecimalOutKomma1:
rcall LcdD4Byte ; Komma or blank as thousands separator
rjmp DecimalOut1 ; Go on converting
DecimalOutEnd:
ldi rmp,'0' ; Last digit
add rmp,rM0L ; Add ASCII-0
rjmp LcdD4Byte ; and display on LCD
;
DecimalTab:
.db Byte1(1000000),Byte2(1000000)
.db Byte3(1000000),Byte1(100000)
.db Byte2(100000),Byte3(100000)
.db Byte1(10000),Byte2(10000)
.db Byte3(10000),Byte1(1000)
.db Byte2(1000),Byte3(1000)
.db Byte1(100),Byte2(100)
.db Byte3(100),Byte1(10)
.db Byte2(10),Byte3(10)
.db 0,0,0,0
;
; LCD Start text
LcdTextOut:
.db "Frequency meter tn24",0x0D,0xFF
.db "F(dig)= x.xxx.xxx Hz",0x0D,0xFF
; 8
.db "F(ana)= x.xxx.xxx Hz",0x0D,0xFF
; 8
.db " ",0xFE,0xFE
;
; LCD include routines
.include "Lcd4Busy.inc"
;
; End of source code
;
13.4.4 Experiences and example
The analog input is very sensible, at higher frequencies 2 mV amplitude are enough
for a stable count result. At low frequencies the high impedance of the capacitor of
100 nF plays a role so that higher AC voltages are required. If the analog input
is open, fast signals on the digital input can stray in.
The same signal, once fed in as well on the analog input (with small amplitude) and
on the digital input (with 5 V amplitude) does not always show the same result.
The reason for that might be the high noise on the analog input caused by harmonics
on the digital input.
13.5.1 Task
The inductivity of coils is to be determined. The measurement shall cover a wide range
from 1 mH up to 10 H.
13.5.2 Hardware and components
Scheme
This is the complete schematic to measure digital and analog frequencies as well as the
inductivity of coils. The inductivity measuring works with an oscillator build with a
CMOS NAND gate in a 4011, a second gate is used to increase edge slopes. Two gates
are not used. The oscillator works stable over a wide range of inductivities, much
better than a transistorized one or an FET stage. The two capacitors of 100 F
are in series, so that the effective capacity is 50 nF. The feedback is reduced
by a 100 kΩ resistor, but this is enough to keep the oscillator swinging
over the whole range.
Components
This is the quad NAND gate. Any other inverting gates will also work.
Mounting
This is the mounting. The coils are attached with crocodile clips.
13.5.3 Program
The following lists the program, the source code is here.
Similiar to the previous version, the three inputs are measured one after the other.
The sleep mode was again not selected because of the incompatibility of the analog
comparer with this mode.
;
; ****************************************************************
; * Digital/analog frequency counter and inductivity measurement *
; * (C)2017 by www.avr-asm-tutorial.net *
; ****************************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Hardware -----------------
; Digital frequency input on PA3
; and
; Analog frequency input on the analog-
; comparator AIN0/PA1 und AIN1/PA2
; and
; Inductivity measurement via frequency
; on input PA0 (4011 LC oscillator)
;
; ----------- Timing -------------------
; Gate time 250 ms
; Controller clock 1,000,000 cs/s (!)
; TC1 prescaler 64
; TC1 clock 15,625 cs/s
; TC1 ticks in 250 ms 3.906
.equ cTc1CmpA = 3905
;
; ----------- Value averaging --------
; Current value / 2 plus
; Last value / 4 plus
; Pre-last value / 8 plus
; Pre-pre-last value / 8 =
; displayed value
;
; ----------- Ports, portpins --------------
.equ pOut = PORTA ; Output port
.equ pDir = DDRA ; Direction port
.equ bIO = PORTB3 ; Portpin digital pullup
.equ bID = DDA3 ; Portpin digital direction
;
; ----------- Registers -----------------
; Used: R0, R1 for LCD
.def rM0L = R2 ; Current measuring value, LSB
.def rM0M = R3 ; dto., MSB
.def rM0H = R4 ; dto., HSB
.def rMH0 = R5 ; 40 bit help register
.def rMH1 = R6 ; for multiplication
.def rMH2 = R7 ; and division
.def rMH3 = R8 ;
.def rMH4 = R9 ;
.def rMR0 = R10 ; 40 bit result register
.def rMR1 = R11 ; for multiplication
.def rMR2 = R12 ; and division
.def rMR3 = R13 ;
.def rMR4 = R14 ;
.def rSreg = R15 ; Save/restore SREG
.def rmp = R16 ; Multi purpose register
.def rmo = R17 ; Another multi purpose register
.def rLine = R18 ; LCD line counter
.def rRead = R19 ; LCD read register
.def rimp = R20 ; Multi purpose inside interrupts
.def rFlag = R21 ; Flags
.equ bAn = 0 ; Analog comparer active
.equ bIp = 1 ; Inductivity meter active
.equ bTO = 2 ; Time out of timer
.def rHelp = R22 ; Help register decimal
; free: R23 .. R25
; Used: R27:R26 X ; for diverse purposes
; free: R29:R28 Y
; Used: R31:R30 Z ; for LCD etc.
;
; ----------- SRAM ---------------------
.DSEG
.ORG 0x0060
sMD: ; Digital values, 4*(HSB:MSB:LSB)
.Byte 12
sMDEnd:
sMA: ; Analog values
.Byte 12
sMAEnd:
sMI: ; Inductivity values
.Byte 12
sMIEnd:
sDivident: ; for division
.Byte 5
sDividentEnd:
;
; ---- Reset and interrupt vectors ---------
.CSEG
.ORG 0x0000
rjmp Start ; Reset vector, init
reti ; INT0 External Int 0
rjmp CntIsr ; PCI Request 0
reti ; PCINT1 PCI Request 1
reti ; WDT Watchdog Time-out
reti ; TIM1_CAPT TC1 Capture Event
rjmp Tc1Isr ; 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
rjmp CntIsr ; ANA_COMP Analog Comparator
reti ; ADC ADC Conversion Complete
reti ; EE_RDY EEPROM Ready
reti ; USI_STR USI START
reti ; USI_OVF USI Overflow
;
; ----- Interrupt Service Routines -----
CntIsr: ; Count pulses from PCINT0, PCINT3 and ANA_COMP
in rSreg,SREG ; Save SREG
inc rM0L ; Count LSB up
brne CntIsrRet ; No overflow
inc rM0M ; Increase MSB
brne CntIsrRet ; No overflow
inc rM0H ; Increase HSB
CntIsrRet:
out SREG,rSreg ; Restore SREG
reti
;
Tc1Isr: ; Time out timer TC1
ldi rimp,0
out ACSR,rimp ; Disable analog comparator int
out GIMSK,rimp ; Disable PCINT0
out TIMSK1,rimp ; Diable timer int
in rSreg,SREG ; Save SREG
sbr rFlag,1<<bTO ; Set time out flag
out SREG,rSreg ; Restore SREG
reti
;
; ----------- Main program init ------
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; Point to RAMEND
out SPL,rmp ; to stack pointer
; Init LCD control port outputs
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; to LCD control port
clr rmp ; Clear LCD control outputs
out pLcdCO,rmp ; to LCD control port
ldi rmp,mLcdDRW ; LCD data port output mask write
out pLcdDR,rmp ; to LCD data port direction
; Init digital port
sbi pOut,bIO ; Digital input pin pullup
cbi pDir,bID ; Digital input pin is input
; Init LCD
rcall LcdInit ; Call to included routine
ldi ZH,High(2*LcdTextOut) ; Point Z to text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Display text
ldi rmp,0x0C ; Cursor and blink off
rcall LcdC4Byte
; Init timer
ldi rmp,High(cTc1CmpA) ; Compare match A
out OCR1AH,rmp ; to MSB
ldi rmp,Low(cTc1CmpA)
out OCR1AL,rmp ; and to LSB
clr rmp ; TC1 normal operation
out TCCR1A,rmp
ldi rmp,(1<<CS11)|(1<<CS10) ; Prescaler 64
out TCCR1B,rmp
ldi rmp,1<<OCIE1A ; Enable compare match int
out TIMSK1,rmp
; Deactivate analog comparator
ldi rmp,0 ; Disable comparator interrupt
out ACSR,rmp
ldi rmp,(1<<ADC2D)|(1<<ADC1D) ; Input pin disable comparator
out DIDR0,rmp ; to pin disable port
; Activate PCINT3
ldi rmp,1<<PCINT3 ; Pin change on PA3
out PCMSK0,rmp ; to mask
ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupt
out GIMSK,rmp ; in int mask
; Sleep Mode, no sleep due to analog comparator error
clr rmp ; Sleep mode disable
out MCUCR,rmp ; in master control port
; Enable interrupts
sei ; by setting the I flag in SREG
Loop:
; Dont sleep
sbrc rFlag,bTO ; Skip next if time out flag clear
rcall Evaluate ; Time out flag set, evaluate
rjmp Loop
;
; Evaluate the measuring results
Evaluate:
cbr rFlag,1<<bTO ; Clear flag
clr rmp ; Disable compare match int
out TIMSK1,rmp ; in TC1
; Copy values to SRAM
cpi rFlag,0x01 ; Inductivity values?
breq EvaluateAnalog ; Go to analog
brcs EvaluateDigital ; Go to digital
; Induktivity value
ldi ZH,High(sMIEnd) ; Target
ldi ZL,Low(sMIEnd)
ldi XH,High(sMIEnd-3) ; Source
ldi XL,Low(sMIEnd-3)
rjmp EvaluateShift
EvaluateDigital:
ldi ZH,High(sMDEnd) ; Target
ldi ZL,Low(sMDEnd)
ldi XH,High(sMDEnd-3) ; Source
ldi XL,Low(sMDEnd-3)
rjmp EvaluateShift
EvaluateAnalog:
ldi ZH,High(sMAEnd) ; Target
ldi ZL,Low(sMAEnd)
ldi XH,High(sMAEnd-3) ; Source
ldi XL,Low(sMAEnd-3)
EvaluateShift:
; Shift measured value to SRAM and divide
ld rmp,-X ; Copy pre-last to pre-pre-last
st -Z,rmp
ld rmp,-X
st -Z,rmp
ld rmp,-X
st -Z,rmp
ld rmp,-X ; Copy last to pre-last with division
lsr rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
ld rmp,-X ; Copy latest to pre-last with division
lsr rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
ld rmp,-X
ror rmp
st -Z,rmp
st -Z,rM0H ; Copy current value
st -Z,rM0M
st -Z,rM0L
adiw ZL,3 ; Point to last value
ldi rmp,4
mov R0,rmp ; R0 is counter
Evaluate1:
ld rmp,Z+ ; Read LSB
add rM0L,rmp ; Add to current
ld rmp,Z+ ; Read MSB
adc rM0M,rmp ; Add to current with carry
ld rmp,Z+ ; Read HSB
adc rM0H,rmp ; Add to current with carry
dec R0 ; Next adder
brne Evaluate1 ; Go on adding
cpi rFlag,0x02 ; Inductivity?
brcs Evaluate2 ; No
rcall Induct ; Calculate and display inductivity
rjmp Restart ; Restart measuring
Evaluate2: ; Display digital or analog measure result
mov ZH,rFlag ; Position of the digital/analog display
inc ZH
ldi ZL,8
rcall LcdPos
; 24 bit binary in rM0H:rM0M:rM0L to dezimal on LCD
Decimal3Out:
ldi ZH,High(2*Decimal3Tab) ; Point Z to decimal table
ldi ZL,Low(2*Decimal3Tab)
clt ; Suppress leading zeros
Decimal3Out1:
lpm XL,Z+ ; Read decimal number
lpm XH,Z+
lpm rHelp,Z+
clr rmp ; Subtraction counter
cp XL,rmp ; LSB clear?
brne Decimal3Out2 ; no
cp XH,rmp ; MSB clear?
brne Decimal3Out2 ; No
cp rHelp,rmp ; HSB clear
breq Decimal3OutEnd ; Yes, to end of conversion
Decimal3Out2:
sub rM0L,XL ; Subtract decimal
sbc rM0M,XH
sbc rM0H,rHelp
brcs Decimal3Out3 ; Overflow, end sub
inc rmp ; Increase counter
rjmp Decimal3Out2 ; Go on subtracting
Decimal3Out3:
add rM0L,XL ; Take back last subtraction
adc rM0M,XH
adc rM0H,rHelp
tst rmp ; Is digit zero?
brne Decimal3Out4 ; No, not zero
brts Decimal3Out5 ; Do not suppress zeros
ldi rmp,' ' ; Suppress leading zero
rcall LcdD4Byte
ldi rmp,' ' ; Blank instead of komma
rjmp Decimal3OutKomma ; Check thousands separator
Decimal3Out4:
set ; Do not suppress zeros any more
Decimal3Out5:
subi rmp,-'0' ; Convert to ASCII
rcall LcdD4Byte ; Write to LCD
ldi rmp,',' ; Thousands separator
Decimal3OutKomma:
cpi XL,Byte1(1000000) ; Millions?
breq Decimal3OutKomma1 ; Yes
cpi XL,Byte1(1000) ; Thousands?
breq Decimal3OutKomma1 ; Yes
rjmp Decimal3Out1 ; No thousands separator
Decimal3OutKomma1:
rcall LcdD4Byte ; Write thousands separator
rjmp Decimal3Out1
Decimal3OutEnd:
ldi rmp,'0' ; Last digit
add rmp,rM0L ; Add ASCII-0
rcall LcdD4Byte ; To LCD
rjmp Restart
;
Decimal3Tab:
.db Byte1(1000000),Byte2(1000000)
.db Byte3(1000000),Byte1(100000)
.db Byte2(100000),Byte3(100000)
.db Byte1(10000),Byte2(10000)
.db Byte3(10000),Byte1(1000)
.db Byte2(1000),Byte3(1000)
.db Byte1(100),Byte2(100)
.db Byte3(100),Byte1(10)
.db Byte2(10),Byte3(10)
.db 0,0,0,0
;
; Calculate and display inductivity
Induct:
ldi ZH,3 ; Set LCD position
ldi ZL,6
rcall LcdPos
tst rM0M ; MSB zero?
brne InductN2 ; No
tst rM0H ; HSB zero?
brne InductN2 ; No
mov rmp,rM0L ; LSB frequency to rmp
cpi rmp,2 ; Frequency 0 or 1?
brcc InductN1 ; No
; F = 0 oder 1, clear line and display 0
ldi XL,10 ; 10 characters
InductN0:
ldi rmp,' ' ; Clear line
rcall LcdD4Byte
dec XL
brne InductN0 ; Characters left
ldi rmp,'0' ; Display 0
rjmp LcdD4Byte
;
InductN1: ; F larger than one
cpi rmp,23 ; F between 2 and 22?
brcc InductN2 ; No
ldi ZH,High(2*Underflow22) ; Display message
ldi ZL,Low(2*Underflow22)
rjmp LcdTextC
Underflow22:
.db "(F < 23 Hz) ",0xFE,0xFF
InductN2:
ldi rmp,0x50 ; Result larger than 0x0B5050?
cp rM0L,rmp
cpc rM0M,rmp
ldi rmp,0x0B
cpc rM0H,rmp
brcs InductN3 ; No
ldi ZH,High(2*Overflow) ; Display error message
ldi ZL,Low(2*Overflow)
rjmp LcdTextC
Overflow:
.db " (F > Max) ",0xFE,0xFF
InductN3: ; Value within correct range
; Multiply rM0H:rM0M:rM0L by itself (F*F)
mov rMH0,rM0L ; Copy value
mov rMH1,rM0M
mov rMH2,rM0H
clr rMH3 ; Clear upper bytes of value
clr rMH4
clr rMR0 ; Clear result registers
clr rMR1
clr rMR2
clr rMR3
clr rMR4
Induct1: ; Multiply
lsr rM0H ; Shift lowest bit to carry
ror rM0M
ror rM0L
brcc Induct2 ; Carry is clear, do not add
add rMR0,rMH0 ; Add to result
adc rMR1,rMH1
adc rMR2,rMH2
adc rMR3,rMH3
adc rMR4,rMH4
Induct2: ; All three bytes clear
tst rM0L
brne Induct3 ; No
tst rM0M
brne Induct3 ; No
tst rM0H
breq Induct4 ; Yes, done
Induct3:
lsl rMH0 ; Multiply by 2
rol rMH1
rol rMH2
rol rMH3
rol rMH4
rjmp Induct1 ; Continue multiplication
Induct4:
; Division, load divident to SRAM
ldi ZH,High(2*Dividenttable) ; Z points to divident table
ldi ZL,Low(2*Dividenttable)
ldi XH,High(sDivident) ; X points to SRAM divident
ldi XL,Low(sDivident)
Induct5:
lpm rmp,Z+ ; Read divident from table
st X+,rmp ; Store in SRAM
cpi XL,Low(sDividentEnd) ; All read?
brcs Induct5 ; No, go on
; Clear divident help registers
clr rMH0
clr rMH1
clr rMH2
clr rMH3
clr rMH4
; Clear result registers
clr rM0L
clr rM0M
clr rM0H
clr ZL
clr ZH
; Shift divident in SRAM right
ldi rmp,8*(sDividentEnd-sDivident)+1
mov R0,rmp ; R0 is outer counter
Induct6:
; Divide
ldi XH,High(sDivident) ; Point X to SRAM
ldi XL,Low(sDivident)
ldi rmp,sDividentEnd-sDivident ; Number of bytes
mov R1,rmp ; R1 is inner counter
clc ; Clear carry
Induct7:
ld rmp,X ; Read byte from SRAM
rol rmp ; Roll highest bit to carry
st X+,rmp ; Store multiplied byte in SRAM
dec R1 ; Count down inner loop
brne Induct7 ; Go on shifting
; Shift carry into help register
rol rMH0
rol rMH1
rol rMH2
rol rMH3
rol rMH4
sub rMH0,rMR0 ; Subtract divider
sbc rMH1,rMR1
sbc rMH2,rMR2
sbc rMH3,rMR3
sbc rMH4,rMR4
brcc Induct8 ; Subtraction no carry
add rMH0,rMR0 ; Carry, take back subtraction
adc rMH1,rMR1
adc rMH2,rMR2
adc rMH3,rMR3
adc rMH4,rMR4
clc ; Clear carry
rjmp Induct9 ; Shift carry into result
Induct8:
sec ; Set carry
Induct9:
dec R0 ; Decrease outer counter
breq Induct10 ; Division done
rol rM0L ; Roll carry into result
rol rM0M
rol rM0H
rol ZL
rol ZH
rjmp Induct6 ; Go on dividing
; Round result
Induct10:
ldi rmp,0 ; Add zero
adc rM0L,rmp ; with carry
adc rM0M,rmp ; with carry
adc rM0H,rmp ; with carry
adc ZL,rmp ; with carry
adc ZH,rmp ; with carry
mov rMH0,ZL ; Copy highest two byte to help register
mov rMH1,ZH
;
; 32 bit binary in rMH0:rM0H:rM0M:rM0L to decimal on LCD
Decimal4Out:
ldi ZH,High(2*Decimal4Tab) ; Point Z to decimal tab
ldi ZL,Low(2*Decimal4Tab)
clt ; Suppress leading zeros
Decimal4Out1:
lpm rMR0,Z+ ; Read decimal number
lpm rMR1,Z+
lpm rMR2,Z+
lpm rMR3,Z+
clr rmp
or rmp,rMR0 ; End of decimal table?
or rmp,rMR1
or rmp,rMR2
or rmp,rMR3
breq Decimal4OutEnd ; Yes, finish
clr rmp ; Subtraction counter
Decimal4Out2:
sub rM0L,rMR0 ; Subtract decimal
sbc rM0M,rMR1
sbc rM0H,rMR2
sbc rMH0,rMR3
brcs Decimal4Out3 ; Carry set, end of subtract
inc rmp ; Increment result
rjmp Decimal4Out2 ; Go on subtraction
Decimal4Out3:
add rM0L,rMR0 ; Take back subtraction
adc rM0M,rMR1
adc rM0H,rMR2
adc rMH0,rMR3
tst rmp ; Digit is zero?
brne Decimal4Out4 ; No
brts Decimal4Out5 ; Do not suppress zeros
ldi rmp,' ' ; Suppress leading zero
rcall LcdD4Byte
ldi rmp,' ' ; Blank instead of thousands separator
rjmp Decimal4Out6 ; Check thousands separator
Decimal4Out4:
set ; Do not suppress leading zeros any more
Decimal4Out5:
subi rmp,-'0' ; Add ASCII-0
rcall LcdD4Byte ; and display
ldi rmp,',' ; Thousands separator
Decimal4Out6:
cpi ZL,Low(2*Decimal4Tab1Mio) ; One million?
breq Decimal4Out7 ; Yes
cpi ZL,Low(2*Decimal4Tab1000) ; One thosand?
brne Decimal4Out1 ; No
Decimal4Out7:
rcall LcdD4Byte ; Display thousands separator
rjmp Decimal4Out1 ; Go on converting
Decimal4OutEnd:
ldi rmp,'0' ; Last digit
add rmp,rM0L ; Add ASCII-0
rcall LcdD4Byte ; to LCD
rjmp Restart ; Restart counting
;
Dividenttable:
.db 0x7F,0xD7,0x10,0xF4,0x75,0x00
;
Decimal4Tab:
.dw LWRD(100000000),HWRD(100000000)
.dw LWRD(10000000),HWRD(10000000)
.dw LWRD(1000000),HWRD(1000000)
Decimal4Tab1Mio:
.dw LWRD(100000),HWRD(100000)
.dw LWRD(10000),HWRD(10000)
.dw LWRD(1000),HWRD(1000)
Decimal4Tab1000:
.dw LWRD(100),HWRD(100)
.dw LWRD(10),HWRD(10)
.dw 0,0
;
Restart:
clr rM0L ; Clear last result
clr rM0M
clr rM0H
inc rFlag ; Next measure mode
cpi rFlag,3 ; Mode > 2?
brcs Restart1 ; No, go on
clr rFlag ; Start first mode
Restart1:
; rFlag=0:digital, =1:analog, =2:L
cpi rFlag,0x01 ; Mode 1?
brcs RestartDigital ; Mode 0, digital
breq RestartAnalog ; Mode 1, analog
; Restart inductivity
ldi rmp,1<<PCINT0 ; Pin Change PA0
rjmp RestartPcInt ; Start PCINT
; Digital, activate PCINT3
RestartDigital:
ldi rmp,1<<PCINT3 ; Pin change PA3
RestartPcInt:
out PCMSK0,rmp ; To PCINT0 mask
ldi rmp,1<<PCIE0 ; Enable PCINT0 interrupt
out GIMSK,rmp ; in int mask
rjmp Restart2
RestartAnalog:
clr rmp ; Disable PCINT int
out GIMSK,rmp
ldi rmp,1<<ACIE ; Enable analog comp int
out ACSR,rmp ; in analog comparator control port
Restart2:
ldi rmp,1<<TSM ; Prescaler Sync Mode
out GTCCR,rmp ; to control port
ldi rmp,(1<<TSM)|(1<<PSR10) ; Reset Prescaler 1
out GTCCR,rmp ; in control port
clr rmp ; Clear reset prescaler
out GTCCR,rmp ; in prescaler count mode port
out TCNT1H,rmp ; Clear 16 bit counter TC1
out TCNT1L,rmp
ldi rmp,1<<OCIE1A ; Enable TC1 compare A int
out TIMSK1,rmp ; in timer int mask
ret
;
; LCD Start text
LcdTextOut:
.db "Frequency meter tn24",0x0D,0xFF
.db "F(dig)= x.xxx.xxx Hz",0x0D,0xFF
; 8
.db "F(ana)= x.xxx.xxx Hz",0x0D,0xFF
; 8
.db "L = xxx.xxx.xxx ",0xE4,"H",0xFE,0xFE
; 6
; LCD include routines
.include "Lcd4Busy.inc"
;
; End of source code
;
13.5.4 Simulating program execution
Program simulation can be made with
avr_sim. All
calls to the LCD should be replaced by write operations to the SRAM to view
the results. Register pair Y can be used for those write operations.
Useful simulation can be made for the 24-bit-decimal conversion and for the
inductivity calculation.
13.5.4.1 Simulation of 24 bit binary to decimal conversion
For simulating the decimal conversion, the binary number 1.234.567
is written to rM0H:rM0M:rM0L. Then the conversion routine is called.
The conversion result, as it goes to the LCD, is correct.
The time required for this conversion is well below 1 ms.
13.5.4.2 Simulation of the inductivity calculation
First we simulate a measured frequency of 1,000 Hz. The value of
1,000 is loaded to the registers R4:R3:R2.
The result of 506.606 mH is correct, as has been shown in the
above example case.
3.6 ms have been elapsed. Division of large numbers is a little
bit more time consuming than conversion.
This is the result when 10,000 Hz would be generated by the LC
oscillator, 5.6 mH.
Simulation of 50 Hz oscillator frequency results in an inductivity of
202 H.
Calculation time at 50 Hz lasts a little bit longer, but not too
long.
Simulation helps in develloping and debugging by letting you check
even complex programs. It helps to clearly define subroutines for
which entry values (in registers, in SRAM) and resulting changes
can be clearly tracked.
13.5.4 Examples
During the following examples the digital and the analog input were open, the displayed
values in line 2 and 3 of the LCD are capacitive stray signals.
Large coil
This is the measurement of a relative large coil.
The measurement is near the inductivity that was determined with a different method.
Again the displayed inductivity is slightly too large, the measured frequency is a little
bit too low, caused by the limited accuracy of the internal RC oscillator.
Speaker coil
The coil in the speaker that we used in one of the previous lectures has an inductivity,
too.
The inductivity of the speaker's coil is rather small and around 800 µH.
©2017 by http://www.avr-asm-tutorial.net