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.1 Introduction to frequency measuring

### 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
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.HexResult hexPost subtr.SubtrPost rollingRollSRAM, HexSRAM, Dec
40280000000000000000000000000000000075F410D77F506,605,918,079
392700000000000000000001000000000011EBE821AEFE1,013,211,836,158
382600000000000000000003000000000031D7D0435DFC926,912,044,540
372500000000000000000007000000000071AFA086BBF8754,312,461,304
36240000000000000000000E0000000000E05F410D77F0409,113,294,832
35230000000000000000001D0000000001D1BE821AEFE0818,226,589,664
34220000000000000000003A0000000003A07D0435DFC0536,941,551,552
332100000000000000000075000000000751FA086BBF801,073,883,103,104
3220000000000000000000EB000000000EB1F410D77F001,048,254,578,432
311F000000000000000001D7000000001D71E821AEFE00996,997,529,088
301E000000000000000003AF000000003AF1D0435DFC00894,483,430,400
291D0000000000000000075F0000000075F1A086BBF800689,455,233,024
281C00000000000000000EBE00000000EBE0410D77F000279,398,838,272
271B00000000000000001D7D00000001D7D1821AEFE000558,797,676,544
261A00000000000000003AFA00000003AFA00435DFC00018,083,725,312
2519000000000000000075F4000000075F40086BBF800036,167,450,624
24180000000000000000EBE80000000EBE8010D77F000072,334,901,248
23170000000000000001D7D00000001D7D0021AEFE0000144,669,802,496
22160000000000000003AFA00000003AFA00435DFC0000289,339,604,992
211500000000000000075F4100000075F41186BBF80000578,679,209,984
2014000000000000000EBE82000000EBE8200D77F0000057,846,792,192
1913000000000100000E3AC4100001D7D0401AEFE00000115,693,584,384
1812000000000300000D3348100001C7588035DFC00000231,387,168,768
1711000000000700000B2450100001A669006BBF800000462,774,337,536
1610000000000F0000070661100001648A11D77F000000925,548,675,072
150F000000001E00000E0CC3000000E0CC31AEFE000000751,585,722,368
140E000000003D00000CD746100001C198605DFC000000403,659,816,960
130D000000007B00000A6C4D1000019AE8D1BBF8000000807,319,633,920
120C00000000F7000005965A1000014D89A077F0000000515,127,640,064
110B00000001EE00000B2CB5000000B2CB51EFE00000001,030,255,280,128
100A00000003DD000007172B1000016596B1DFC0000000960,998,932,480
90900000007BA00000E2E57000000E2E571BF80000000822,486,237,184
8080000000F7500000D1A6E100001C5CAE07F00000000545,460,846,592
7070000001EEB00000AF29D100001A34DD1FE000000001,090,921,693,184
6060000003DD7000006A2FB1000015E53B1FC000000001,082,331,758,592
5050000007BAE00000D45F7000000D45F71F8000000001,065,151,889,408
404000000F75D00000B49AF100001A8BEF1F0000000001,030,792,151,040
303000001EEBB000007511F1000016935F1E000000000962,072,674,304
202000003DD7600000EA23F000000EA23F1C000000000824,633,720,832
101000007BAED00000E023F100001D447F18000000000549,755,813,888
0Rdg000007BAEE00000CC23E100001C047E000000000000
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.

## 13.2 Introduction to decimal conversion (24 and 32 bit)

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 Measuring digital signals with the PCINT

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
;			 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 ; 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
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:
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)
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
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
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 Frequency measurement with the analog comparator

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 ; 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
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
ldi ZL,Low(sMDEnd)
ldi XL,Low(sMDEnd-3)
rjmp EvaluateShift ; Shift values in and divide
EvaluateAnalog:
ldi ZL,Low(sMAEnd)
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:
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)
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
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
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 Measuring inductivity with PCINT

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 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 ; 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
out DIDR0,rmp ; to pin disable port
; Activate PCINT3
ldi rmp,1<<PCINT3 ; Pin change on PA3
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:
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)
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
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
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
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
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
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:
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)
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
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:
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
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.