Home ==> Micro beginner ==> 13. Frequency counter, inductivity meter
ATtiny24

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

  1. Introduction to frequency measuring
  2. Introduction to decimal conversion
  3. Digital signal measuring with PCINT
  4. Analog signal measuring with analog comparer
  5. Inductivity measuring with PCINT

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.

Analog comparator 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 ",").

Division limits 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

8 bit division 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.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.

Home Top Frequencies Decimal conversion Digital Analog Inductivity


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.



Home Top Frequencies Decimal conversion Digital Analog Inductivity


13.3 Measuring digital signals with the PCINT

13.3.1 Task

As first task we measure the frequency of digital signals.

13.3.2 Hardware, mounting

Digital frequency meter The hardware needed for this task is simply nil: the signal source is simply attached to input pin PA3.

Mounting digital 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:

Signalgenerator Measured result

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).

Home Top Frequencies Decimal conversion Digital Analog Inductivity


13.4 Frequency measurement with the analog comparator

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

Scheme analog frequency meter 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

Elelectrolytic capacitor 1 µF 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.

Capacitor 100 nF This is a possible form of the film capacitor of 100 nF.

Resistor 100 k 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

Mounting analog measuring input 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.

Measure analog 2 Measure analog 1



Home Top Frequencies Decimal conversion Digital Analog Inductivity


13.5 Measuring inductivity with PCINT

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

Scheme F-L-Meter 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

4011 CMOS gate This is the quad NAND gate. Any other inverting gates will also work.

Mounting

Mounting F-L-Meter 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 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.

Coil 3.58H Result 3.58H

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.

Speaker Measuring the speaker

The inductivity of the speaker's coil is rather small and around 800 µH.

Home Top Frequencies Decimal conversion Digital Analog Inductivity


©2017 by http://www.avr-asm-tutorial.net