Das LC-Meter Projekt II     

von Rudolf Drabek                    
Elektronik-Labor   Projekte   AVR 



Vor Jahren, als uC’s für mich noch ein spanisches Dorf waren, habe ich auf der Aade.com Seite so einen Bausatz um 60 US$ entdeckt. Inzwischen kann man es auch in Deutschland auf funkamateur.de um 115 Euro bestellen. Man findet dort auch einen Testbericht von Klaus Raban DM2CQL. So ein Ding muss jeder haben. Ich habe nun eine nachbausichere Lösung entwickelt.
 
Als Referenz möchte ich noch die Seite http://home.ict.nl~fredkrom/pe0fko/LCMeter angeben, von der ich einen Teil der Mathematikroutinen verwendet habe, die in Assembler geschrieben sind. Ebenfalls dort sind viele Links über LCmeter eingetragen, sodass man ein Bild über die Szene bekommt.
 
Meine Ziele waren: Auflösung 1 pF   1nH ( ist 2 nH)
Messbereich : 1 pF bis theor. 99 uF wenns der Oszillator mitmacht.
                          2 nH bis 99 mH , mit der Oszillatoreinschränkung
Genauigkeit    1% oder besser +- 1 digit.
Auf die Genauigkeit gehe ich noch im Detail ein.
 
In der ersten Projektbeschreibung „Das LC-Meter Projekt“  mit Ausgabe der Ergebnisse am Displaymodul habe ich ein LC-Meter in der ELO (die Registrierung, nach der gefragt wird, verpflichtet zu nichts) veröffentlicht, mit der Bemerkung, dass noch genügend Portpins frei für ein LCD Display wären.  Ich habe es nun selbst realisiert mit dem Pollin Display HMC16223 SO. Die 5 wesentlichen Bauteile sind um weniger als 10 Euro erhältlich. Der Rest ist aus Elektroschrott zusammengesucht.
 
Der Vollständigkeit halber  noch ein Bild mit dem ursprünglichen Displayunit, das aber kaum jemand nachgebaut haben wird. Es steht und stand aber eine RS232 Schnittstelle mit 9600 Baud zur Verfügung, sodass die Ergebnisse auch am PC dargestellt werden konnten und können. Durch die spezielle Messmethode, wobei jede 2. Messung die Nullpunktdrift herausrechnet, kann man von der ersten Sekunde nach dem Einschalten an genaue Resultate erhalten.
Eine Drift des Oszillatores mit dem LM319 gibt es zwar auch, alle Bauteile zusammen, aber die bleibt unter 0,5%. Es gibt also keine Kalibrierroutine, da ja der Kalibrierbauteil von derselben Genauigkeitsklasse ist wie die verwendeten Lo und Co.  Das Bild mit dem ursprünglichen Displaymodul zusammen zeigt, dass mit der etwas eigentümlichen RS232 Anpassung die Daten auch an PC’s gesendet werden können.
 

 
Die Schaltung hat natürlich große Ähnlichkeit mit der ursprünglichen.


 
 

 
Das Gerät ist selbstkalibrierend, genauer gesagt die Nullpunktdrift wird eliminiert durch geeignete Software.  Eine Kalibrierung im Sinn des Wortes wird nicht durchgeführt. Man muss, wer es wünscht, einen genaue(n) Bauteil(e) besorgen und die Abweichung feststellen. Die Software ist aber leicht anzupassen für den der es kann. Mir reicht es so, es ist genau genug. Aber auch fast alle anderen LCmeter sind nicht „kalibrierend“ im Sinn des Wortes, sondern vergleichen nur 2 Bauteile gleicher Art.
 
Meine Lösung vermeidet jede Nullpunktdrift dadurch und das ist neu, soweit ich sehen kann, indem abwechselnd eine fo Messung und eine  fx Messung durchgeführt wird. Daraus errechnet sich dann der Cx bzw. Lx Wert. Erklärung folgt. Es gibt einen einzigen Schalter, der zwischen L und C umschaltet. 
Da das LCmeter ein SpinOff des 120 Mhz Frequenzzählers ist, sieht man noch den Platz für den 16er Teiler 74F161 mit der Brücke Eingang/Ausgang:




 
Viele der LCmeter verwenden ein Floating Point Mathepaket, was natürlich beeindruckend ist. Man muss aber überlegen wie denn der Input für die Mathe aussieht und besser wird das Ergebnis nicht durch FLT. Darüber muss man sich klar sein. Ich war deshalb froh zuerst bei Fred Krom ein Integermathepaket zu finden. Inzwischen habe ich es auch noch an anderen Stellen im Internet gefunden.
 
Basis für alle Berechnungen ist die Formel         f*2PI = 1/ Wurzel( LC )   (1)
 
  Oder C  = (Cx + Co) =  1/ ( Lo ( 2PI*fx)²)                                                (2)
 
Zuerst wollte ich  eine Konstante k = 1/ ( Lo*4*PI²) verwenden, weil dann die nötige Mathematik ein Minimum wird.
 
Die Formel wäre dazu     Cx =  k/fx² -Co                          aus (2) folgt               (3)
Nun, wäre schön, wenn k über die Zeit konstant bliebe. Aber stellen sie sich vor dass sie einen 1 pF Kondesator bestimmen wollen. Die Gleichung sieht dann wie folgt aus:
 
                                               1 = 1021 – 1020                                                      (4)
 
Sie sehen die Problematik. Bei kleinen Messwerten spielt  die Konstanz von k als auch Co eine maßgebende Rolle. Die Drift ist in etwa 3-5 pF, an sich nicht viel, aber meine Ziele erreiche ich damit nicht.  Also Strategie umstellen auf folgendes.
 
Messe zuerst fo, dass heißt ohne Cx, also nur Co. Dann die Messung Co+Cx .
Ich bekomme also 2 Frequenzmesswerte.
 
Die Formel, können sie selbst nachprüfen ist dann
 
            Cx =  Co*fo² / fx²  -Co        (5)      in FLT wäre es:  Cx = Co(fo²/fx²2 –1)  (6)
 
Sie muss, bei Integermathematik von links nach rechts in der gezeigten Schreibform durchgeführt werden. Bei FLT ist dies keine Anforderung, ich werde noch zeigen warum.
 
Ich muss jetzt etwas ins Detail gehen mit der Frequenzmessung. Verwendet wird Timer1, ein 16 bit Counter, der nur bis 65536 zählen kann, wenn man keinen Software Counter anhängt. Die höchste Frequenz des Bauteiloszillators habe ich mit 600 kHz festgelegt. Werte siehe Schaltbild. Damit liegt auch die Gatezeit des Frequenzzählers fest mit .1/10 sec.Das Zählergebnis ist also maximal 60000. Das heißt auch gleichzeitig: egal welche Grundfrequenz Sie planen, Sie müssen immer darauf achten innerhalb der 16 bit zu bleiben. Damit wird FLT ziemlich entzaubert. Messen Sie 1 pF dann sinkt der Zählerstand auf 59970.
 
Würden Sie (6) verwenden sieht das Ergebnis wie folgt aus:
Nun, 60000/59970 in Integermathe ist 1, ob Quadrat oder nicht. Das Rechenergenis für Cx   ist NULL. Thema verfehlt!
 
Nicht so wenn Sie (5) verwenden:
Wieder messen sie 60000 und 59970
 
Cx =  1020*60000²  /  59970²   -1020  = 1  nun, so einfach ist es.
Für Lx Messungen ergibt sich die min. Auflösung mit 2,3 nH = 2 nH.
 
Bei Integer oder Fixkommamathe muss man sehr genau aufpassen was man tut und so kommt man drauf, dass Auflösungen von gar 0,01 pF nur math. Werte sind die nicht der Realität entsprechen und nur durch FLT zusammenkommen. Man denkt einfach weniger nach mit FLT.
 
Weiter: Co ist angegeben mit 1020 pF. Bei kleinen Messwerten, wo eine Differenz von großen Werten den größten Einfluß hat ist die Toleranz ziemlich egal, ob sie    10 pF +- 1% messen.
 
Bei großen Werten z.B 100 nF ist die Toleranz so genau wie die Toleranz des      1020 pF Referenz C’s.  Auch ob sie von 100000 pF 1020 oder 1021 abziehen spielt keine Rolle. Die niedrigste fx Frequenz bei, sagen wir 1 uF wäre 19160 Hz, der Zählerstand 1916. Das ist eine Auflösung von 0,05 %. Bei 99 uF  0,5%. Das reicht.
 
Das wären die Überlegungen zu Auflösung und Genauigkeit. Die Bauform der Referenzinduktivität ist sehr wichtig. Ringkerne oder gewöhnlich Drosselspulen haben einen großen Temperaturkoeffizienten. Ich habe eine Toko ZF-Spule aus einem alten MW- Radio umgewickelt, die ist am stabilsten. Der Rest des Projektes, die Software finden sie im asm Listing.

Das Lcmeter ist nachbaufest und stellt keine besonderen Anforderungen. Im Displaymodul ist an der 1. Stelle ein „C“ oder „L“ zu sehen, damit man weiss wie der Schalter steht. Das war übrigens der Grund, warum ich das Display mit 7-Segment auf alfa-numerisch, „-„ und „@“ umgestellt habe.  Groß- und Kleinschreibung ist mir nicht gelungen, hi, J. Die führenden Nullen habe ich nicht unterdrückt, ebenso werden alle Stellen und nicht  nur die 4 wesentlichen angezeigt.
 
Download: Lcmeter.zip

;****************************************************************************
;*ATtiny2313 LC meter based on ideas from: *
;*frequency meter (C)2002 by Richard Cappels sap@cappels.org *
;*;http://projects.cappels.org/ *
;*Math routines from Fred W. Krom PE0FK0 see http://home.ict.nl/~fredkrom *
;*the rest changed a lot by Rudi Drabek (C)2009 *
;*especially NEW calibrating routine to eliminate warmup drift ~3pF *
;*when used keep copyrights mentioned! *
;****************************************************************************
.include "tn2313def.inc"
.equ clock = 5000000 ;clock frequency
.equ baudrate = 9600 ;choose a baudrate
.equ baudconstant = 31 ;(clock/(16*baudrate))-1
;Fuses High = $DB, LOW = $EE


; ATtiny2313
; +--+-+--+ 2 Reedrelais:
; !RESET |1 |_|20| VCC 1 Lreed
; (RX)PD0 | | PB7(SCK) 2 Creed
; (TX)PD1 | | PB6(MISO)
; XTAL2 | | PB5(MOSI)
; XTAL1 | | PB4
; (INT0)PD2 | | PB3(OC1)
; (INT1)PD3 | | PB2
; (T0)PD4 | | PB1
; (T1)PD5 | | PB0
; GND |10 11| PD6
; +-------+
;
; PD0 I RX RS232 nicht benützt
; PD1 O TX RS232
; PD2 O C-reed off=Co on=Cx, immer off bei Lx
; PD3 O L-reed off=Lx on=Lo umgekehrt!! immer on bei Cx
; PD4 I L/C Schalter
; PD5 I Frequence input von LM319
; PD6 O nc
; PB0 I Display pin 11 D4 4bit mode, Display HMC16223SO von Pollin
; PB1 I Display pin 12 D5
; PB2 I Display pin 13 D6
; PB3 I Display pin 14 D7
; PB4 O Enable pin 6
; PB5 O RS pin 4
; PB6 O Gate LED
; PB7 nc

.macro storeX ;SRAM intermediate store
ldi ZL,byte1(@0)
rcall stx
.endmacro
.macro storeY ; <memory>
ldi ZL,byte1(@0)
rcall stY
.endmacro

.macro loadX ; <memory>
ldi ZL,byte1(@0)
rcall ldX
.endmacro
.macro loadY ; <memory>
ldi ZL,byte1(@0)
rcall ldY
.endmacro

.macro movXtoY
mov RY0,RX0
mov RY1,RX1
mov RY2,RX2
mov RY3,RX3
.endmacro
.macro movYtoX
mov RX0,RY0
mov RX1,RY1
mov RX2,RY2
mov RX3,RY3
.endmacro

;counter gatetime variables
.def presetloopmultiplier =r0
.def presetdelaycounter =r1
.def presetdelaycounter1 =r2
.def delaycounter =r3
.def delaycounter1 =r4
.def loopmultiplier =r5

;*********************************
;calculation varables

.def RY0 = r6
.def RY1 = r7
.def RY2 = r8
.def RY3 = r9

.def RR0 = r10
.def RR1 = r11
.def RR2 = r12
.def RR3 = r13
.def RR4 = r14
.def lcswitch=r15 ;check ob L oder C Messung

.def RX0 = r16 ; timer1 lo und fx^2
.def RX1 = r17 ; timer1 hi und fx^2
.def RX2 = r18 ; fx^2
.def RX3 = r19 ; fx^2
.def RX4 = r20 ; BCD value digits 0 and 1
.def RX5 = r21 ; BCD value digits 2 and 3
.def RX6 = r22 ; BCD value digits 4 and 5
.def RX7 = r23 ; BCD value digits 6 and 7 (MSD)

;allgemeine Variablen ;general purpose register
.def A = r24
.def B = r25
.def C = r29
.def mux = r26

.org $00
rjmp main
.org $0007 ;RXCIE Interrupt, Wird nicht verwendet,nur zur Vollständigkeit
;may be zur Kalibrierung verwenden.
.org $13

;constanten für die Cx Lx Berechnung
k1: ;von http://netzreport.googlepages.com onlineumrechner k=1/(4*pi^2*Lo)
.db $80, $d0, $c0, $6b, $52, $03; 69,35uH, 1020pf k=3,65253e(14-2), 0,1s gate!!
;k1 wird nicht verwendet bei fo^2/fx^2 Methode ACHTUNG
Cref:
.db $93,$03,00,00 ;die Basiskapazität 915 pF .Ein zweiter Referenzkondensator
;zur Co Kalibrierung ist auch nicht besser als der verwendete Styroflex.
;also wird die fo Drift herausgemessen und damit eliminiert. (C) Rudi Drabek
Lref:
.db $40, $19,$01,00 ;die Basisinduktivität 72000 nH,

_C: .db " C-Messung (pF)",00,00
_L: .db " L-Messung (nHy)",00,00

main:
ldi A,low(ramend)
out spl,A ;set Stackpointer
clr ZH ;ist ZH und immer 0
ldi ZL,k1*2 ;adressiere Konstanten bytewise
clr YH ;und verschiebe nach
ldi YL,kc ;Beginn SRAM bzw. .dseg
ldi mux,14
tabletr:lpm A,Z+
st Y+,A
dec mux
brne tabletr ;fertig
;init Port B und D
ldi A,255 ;alles output bei LCmeter
out DDRB,A ;B0....B5 Display, B6="Gate open" LED
;B7 ist unbeschaltet
ldi A,0b01001110;PortD0=RX, PD1=TX, PD2=Creed, PD3=Lreed,PD4=L/C switch,
;PD5=In Counter1 ;PD6= unbeschaltet PD7 gibts nicht
out DDRD,A
;via RS232 über PD1 gehen die Ergebnisse zum VFD-Display bzw.RS232 Empfänger
;für den Fall der weiteren Nutzung am z.B. PC
;init counter1
ldi A,$00 ;set TCCR1A
out TCCR1A,A ;Seite 108 "normal count operation" von Counter1
ldi A,$06 ;enable input to counter 1 Seite 111
out TCCR1B,A ;clock on falling edge, so nötig für 74F160
ldi A,$00 ;clear 16 bit counter
out tcnt1h,A
out tcnt1l,A
mov RX2,A ;RX0...3 NULL setzen
mov RX3,A
ldi A,$70 ;setzt Bits 4,5 und 6 auf 1 : 74F160 und LED aktiv
out PortB,A ;jetzt
;RS232_init
cli ;disable Interrupt during setup of USART
clr A
out UBRRH,A ;baudrate 9600 bei 4 Mhz aus Tabelle
ldi A,baudconstant
out UBRRL,A
ldi A,0b0000110 ;8N1 setzen
out UCSRC,A
ldi A,0b10011000; RX und TX enable, RXCIE interrupt enable
out UCSRB,A ;
;sei ;enable Interrupt, bleibt gesperrt
;set 0,1s Messzeit sodass Timer 1 innerhalb 16 bit Ergebnis bleibt
ldi A,25 ;(19949+51 codecycles)*25 -1= 499999 cycles
mov presetloopmultiplier,A ; +2 cycles "cbi instr." in total 500001
ldi A,98 ;(4 cycles) * 98 -1=391 cycles
mov presetdelaycounter,A
ldi A,50 ;(391+8)*50 -1=19949
mov presetdelaycounter1,A

;**************LCD Init
lcd_init:
ldi C,$FF ;PortB alle auf Output Seite50
out DDRB,C
;clr C ;geht net wieso? WEGEN enable Puls
out PortB,C ;definierter Status fürs Display
ldi B,100 ;n x 2ms delay
wait_display: ;t2313 ist fertg, display auch
rcall delay2ms ;lass alles zur Ruhe kommen
dec B
brne wait_display

; Display DB [7:4] liegt auf PortB [3:0]
; sonst könnte man nicht RS = PB5, Enable = PB4
; im 4 bit mode vom PortB aus bedienen
ldi C,0b00000011 ;8bit mode,
out LCD_PORTB, C ;
rcall lcd_enable ;1,
rcall delay2ms
rcall delay2ms
rcall lcd_enable ;2
rcall delay2ms
rcall lcd_enable ;3
rcall delay2ms ;Display ist jetzt eindeutig im 8bit mode

;LCD: function set
ldi C,0b00000010;4bit-Mode, noch im 8 bit mode gesendet
out LCD_PORTB, C
rcall lcd_enable
rcall delay2ms ;Display ist jetzt im 4bit mode

; ab nun kann das Byte jeden Wert haben, da es in 2 nibbles aufgeteilt
; wird, die via PB[3:0] kommend, an das Display DB[7:4] gelangen.

funcset:ldi C, 0b00101000 ;4bit, 2 Zeilen, 5x7 dots + cursor
rcall lcd_cmd
disp_on:ldi C, 0b00001100 ;Display on, Cursor off, blink off
rcall lcd_cmd
dispclr:ldi C, 0b00000001 ;clear Display,Cursor home,
rcall lcd_cmd ;executiontime 1,52 ms
rcall delay2ms ;deshalb delay
entmode:ldi C, 0b00000110 ;increment, Cursor move
rcall lcd_cmd

;********************
;* MEASURE FREQUENCY*
;********************
measurefreq:
; L oder C-Messung?
in lcswitch,pinD ;wie steht der L/C Auswahlschalter
sbrs lcswitch,4
rjmp messC ;bit clear = false, also messe C
;L-messung gefragt
cbi portD,2 ;C-Reed immer offen --> Cx hat keinen Einfluss
rjmp gocount ;gehe zu Frequenz feststellen
messC: sbi portD,3 ;L-Reed immer geschlossen

;**Jetzt noch 1. Zeile Display ausgeben***************
gocount:clr ZH ;Text in Zeile1 schreiben
ldi ZL,_C * 2 ;byte adresse
in lcswitch,pinD ;wie steht der L/C Auswahlschalter
sbrc lcswitch,4
subi ZL, -18 ;L-Messung gefragt

ldi C,0 ; C-Messung Zeile1 Pos1
rcall lcd_flash_string ;benützt den Befehl lpm

ldi A,$00 ;clear 16 bit counter
out TCNT1H,A
out TCNT1L,A
mov RX2,A ;clear RX0...3
mov RX3,A
mov loopmultiplier,presetloopmultiplier ;grösste Loop laden
sbi PORTB,6 ;LED Counting indicator on
ldi A,6 ;select ext. clock source
out TCCR1B,A ;start counting
bigloop:mov delaycounter,presetdelaycounter ;1; innerste loop laden
mov delaycounter1,presetdelaycounter1 ;1 mittlere loop laden

dealylooproutine: ; 5 millisecond delay loop
dec delaycounter ;1
nop ;1
brne dealylooproutine ;2;1;in Summe 392-1
mov delaycounter,presetdelaycounter ;1;reload innerste loop
dec delaycounter1 ;1;werde später fractured loop
nop ;1;wie im Original machen
nop ;1
nop ;1
brne dealylooproutine ;2;1; in Summe 19950-1

;die LoCo Werte werden so eingestellt, dass timer1 auf max.62000 zählt
in A,TIFR ;check auf Überlauf ;1;
sbrs A,7 ;Seite 82 TOV1 = bit7 ;2;1;
rjmp nooverflow ;2;
inc RX2 ;ja, also SWcounter hinaufzählen ;1;
ori A,128 ;setze Bit 7, damit TOV1 Flag gelöscht wird.;1;
out TIFR,A ; ;1;
rjmp goon ;2;
nooverflow:nop ;;Zeitausgleich für overflowhandling ;1;
nop ;muss ja gleich lang sein, sonst ist die ;1;
nop ;1 sec Messzeit="gate open" verschieden lang;1;
goon: ldi A,9 ;extra Zeit damit die Messzeit 0,1 sec ist;1;in Summe 8
goon1: dec A ;1;jeder Zweig
nop ;1;
brne goon1 ;2;1;in Summe 36-1
nop ;1;
nop ;1;
dec loopmultiplier ;1;
brne bigloop ;2;1;in Summe 6
;jetzt ist die gatetime abgelaufen, bis hierher dauert es in Summe 499999 cycles
out TCCR1B,ZH ;stop counting Timer1 = no clock source set
cbi PortB,5 ;Rest von 120 MHz Zähler stört nicht
cbi PortB,6 ;set port d bit 6 low (turn LED off)


;hole die Ergenisse timer1
in RX0,TCNT1L;move counter contents to input for number conversion
in RX1,TCNT1H;RX2 und RX3 bleiben ja NULL;

;**************************************************************************
;* Cx = (Co*fo^2)/fx^2 - Co ;genau so ausführen wegen Auflösung
;* bei kleinen Cx Werten !!!
;**************************************************************************

in lcswitch,pinD ;wie steht der L/C Auswahlschalter
sbrs lcswitch,4
rjmp isC
isL: sbic portD,3 ;wie steht Lreed?
rjmp countfo ;Lreed "on" , Lo messen
sbi portD,3 ;Lreed ist "off", also "on" für nächste fo Messung
rjmp goLC
isC: sbis portD,2 ;wie steht Creed
rjmp countfo ;Creed "off", Co messen
cbi portD,2 ;Creed ist "on", for next measurecycle auf "off"

goLC: movXtoY ;copy counter from RXn to RYn for fx^2
rcall mul32 ;ergibt nur 32 bit fx^2 da ja timer1 in 16bit bleibt
storeX fx2 ;fx^2 speichern
loadY fo2 ;fo^2 holen ;könnte man ausbauen z.B. 20 bit
;L oder C Messung?
in lcswitch,pinD ;wie steht der L/C Auswahlschalter
sbrs lcswitch,4
rjmp getc
loadX Lo
rjmp gocalc
getc: loadX Co
gocalc:
rcall mul32 ;Co * fo^2
loadY fx2
rcall div48 ;so, C ist berechnet Ergebnis max 256e3= 16,7 mio pF
in lcswitch,pinD ;wie steht der L/C Auswahlschalter
sbrs lcswitch,4
rjmp cmess
loadY Lo ;jetzt noch das reale Lo der Schaltung abziehen
rjmp abziehen
cmess: loadY Co
abziehen:rcall sub32 ; ohne Cx sollte jetzt 00000000 da stehen, real 1pF


;Messwert ausgeben
; in BCD wandeln, aber vorher "L" oder "C" ausgeben für RS232 only
in lcswitch,pinD ;wie steht der L/C Auswahlschalter
sbrs lcswitch,4
rjmp is_C
ldi A,'L' ;bit 4 set also Spule
rjmp is_LC
is_C: ldi A,'C' ;bit 4 clear, also Kondensator
is_LC: rcall WrCOM
rcall millisec ;jetzt die 8 Stellen des Ergebnisses

;**Display Zeile 2 löschen und Cursor auf Position 6 = 64 + 6 =70
rcall clrline2
ldi C,70 ;Cursor auf Position 7 in Zeile 2
rcall set_cur

rcall Bin3BCD16 ;in ASCII ausgeben
ldi mux,4 ;RX7 bis RX4 ausgeben
clr ZH ;Z auf RX7 setzen
ldi ZL,24 ;um 1 höher wegen "ld -Z" Befehl
bcdout: ld A,-Z ;hoechste Stelle =r23= BCD8 und 7 zuerst raus
swap A
andi A, 15 ;hi nibble maskieren
ori A,$30 ;in ASCII
rcall WrCOM ; an RS232
;und ans Display senden
mov C,A ;Wert nach C weil lcd_data das will
rcall lcd_data

rcall millisec ;1 Byte braucht ja 1,04 ms, no handshake
ld A,Z ;also Wartezeit von ca 1,2 ms einführen
andi A, 15 ;lo nibble detto
ori A,$30 ;in ASCII
rcall WrCOM ; an RS232
;und ans Display senden
mov C,A ;Wert nach C weil lcd_data das will
rcall lcd_data

rcall millisec
dec mux
brne bcdout ;4 Bytes mit den 8 Stellen ausgegeben

premea: clr A ;extra delay ca 1/3 sec damit display
mov loopmultiplier,A;mit 0,1 sec gatetime nicht so flackert.
disp3: rcall millisec
dec loopmultiplier
brne disp3
rjmp measurefreq

;
;die Induktivität stabil zu bekommen war nicht einfach
;jedenfalls darf man keine "bead choke" verwenden.

countfo: ;bei offenem Reedrelais wird das eingebaute C ermittelt
in lcswitch,pinD ;wie steht der L/C Auswahlschalter
sbrs lcswitch,4
rjmp isCo
isLo: cbi portD,3 ;Lreed ist "on", für next cycle = fx "off"
rjmp countfo1
isCo: sbi portD,2 ;Creed ist "off", for next measurecycle auf "on"
countfo1:movXtoY ;copy counter from RXn to RYn for fo^2
rcall mul32 ;ergibt nur 32 bit fo^2 da ja timer1 in 16bit bleibt
storeX fo2 ;das ist jetzt das aktuelle fo^2
rjmp premea ;das reed soll ausgeprellt haben


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LCD-Routinen für HMC16223 Display ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.equ LCD_PORTB = PORTB ;define the Ports / IOs
.equ LCD_DDRB = DDRB
.equ PIN_RS =PB5 ;Config for display RS und Enable
.equ PIN_E =PB4

;send text pointed by source=Z und destination=C from flasch to LCD
lcd_flash_string:
rcall set_cur ;set display, DDRAM address
lcd_flash_1:
lpm C, Z+ ;pointed char nach C
cpi C, 00 ;Stringende = 0
breq lcd_flash_2
rcall lcd_data
rjmp lcd_flash_1
lcd_flash_2:ret

;send a data byte in C to the LCD, RS = high, changes C
lcd_data:
push C ;copy the data byte for later
swap C ;hi nibble zuerst senden
andi C, 0b00001111 ;clear ehemals low nibble
sbr C, 1<<PIN_RS ;es sind Daten RS=1
out LCD_PORTB, C ;prepare LCD port for write
rcall lcd_enable ;LCD enable writes now + 50us

pop C ;process now low nibble
andi C, 0b00001111
sbr C, 1<<PIN_RS ;es sind Daten RS=1
out LCD_PORTB, C ;
rcall lcd_enable
ret

;send a command in C to the LCD, RS = low changes C
;bis auf RS kein Unterschied zu lcd_data
lcd_cmd:push C ;copy the command byte for later
swap C ;hi nibble zuerst senden
andi C, 0b00001111 ;clear ehemals low nibble und RS=0
out LCD_PORTB, C ;prepare LCD port for write
rcall lcd_enable ;LCD enable writes now + 50us

pop C ;process now low nibble
andi C, 0b00001111 ;RS ist damit automatisch 0
out LCD_PORTB, C ;
rcall lcd_enable
ret

clrline2:ldi C,64 ;lösche Zeile2 des Displays
rcall set_cur
ldi A,15
clr1: ldi C,' '
rcall lcd_data
dec A
brne clr1
ret

;create enable pulse 5 cycles auf 1 macht 0,75 us ändert kein Register
;mit fallender Flanke werden Daten übernommen
lcd_enable:
sbi LCD_PORTB, PIN_E ;2; Enable high
nop
nop
nop
cbi LCD_PORTB, PIN_E ;2; Enable low
rcall delay50us ;ab H>L Enable braucht das Display 38 us
ret ;bis es wieder bereit ist

; set cursor to DDRAM position in C, changes C
; 1. line [C]= 0.....15d
; 2. line [C]= 64d...79d
set_cur:ori C, $80 ;Befehl selbst
rcall lcd_cmd
rcall delay50us ;50 us sollte an sich genügen
ret

; > 38 us delay, ändert kein Register
delay50us:push A
ldi A, 50
delay50us_:dec A ;(5*50-1+7)*0,2 = 51,2 us
nop
nop
brne delay50us_
pop A
ret

; > 1,52 ms delay for lcd_clr und lcd_home ändert kein Register
delay2ms:push C
push B
ldi C, 32
w2ms0: ldi B, 125
w2ms1: dec B ;delay 3*B -1 Takte ~75 uS
brne w2ms1
dec C ;+1
brne w2ms0 ;(375*32 +10)*0,2
pop B
pop C
ret ;= 2402 uS

;--------------------------------------------------------------------
;-- RX[64] = RX[32] * RY[32]
;-- bei LCcalc bleibt das Ergebnis aber innerhalb 32 bit.
;die Genauigkeit des LC-meters ist bei 16 bit timer 1, selbst bei
;Zählerstand 1000 innerhalb 1%o, also eine Messzeitanpassung, damit
;der Zähler immer nah am maxcount liegt ist eine Fleissaufgabe
;--------------------------------------------------------------------
mul32: clr RX7
clr RX6
clr RX5
sub RX4,RX4 ; RX4=0, C=0
ldi A,32+1
mnxtb: brcc mnoadd
add RX4,RY0
adc RX5,RY1
adc RX6,RY2
adc RX7,RY3
mnoadd: ror RX7
ror RX6
ror RX5
ror RX4
ror RX3
ror RX2
ror RX1
ror RX0
dec A
brne mnxtb
ret

;--------------------------------------------------------------------
;-- RX[48] = RX[48] / RY[32]
;--------------------------------------------------------------------
div48: ldi A,48+1 ; init loop counter
clr RR0 ; clear remainder Low byte
clr RR1
clr RR2
clr RR3
sub RR4,RR4 ; clear remainder High byte and carry
div1: rol RX0
rol RX1
rol RX2
rol RX3
rol RX4
rol RX5
dec A
brne div3
ret

div3: rol RR0
rol RR1
rol RR2
rol RR3
rol RR4
sub RR0,RY0 ;remainder = remainder - divisor
sbc RR1,RY1
sbc RR2,RY2
sbc RR3,RY3
sbc RR4,ZH
brcc div2 ;if result negative
add RR0,RY0 ; restore remainder
adc RR1,RY1
adc RR2,RY2
adc RR3,RY3
adc RR4,ZH ;
clc ; clear carry to be shifted into result
rjmp div1 ;else
div2: sec ; set carry to be shifted into result
rjmp div1

;--------------------------------------------------------------------
;-- RX[32] = RX[32] - RY[32]
;--------------------------------------------------------------------
sub32: sub RX0,RY0
sbc RX1,RY1
sbc RX2,RY2
sbc RX3,RY3
ret

;--------------------------------------------------------------------
;-- Store and load X & Y from SRAM
;--------------------------------------------------------------------
stx: st Z+,RX0
st Z+,RX1
st Z+,RX2
st Z+,RX3
ret

sty: st Z+,RY0
st Z+,RY1
st Z+,RY2
st Z+,RY3
ret

ldX: ld RX0,Z+ ;
ld RX1,Z+
ld RX2,Z+
ld RX3,Z+
ret

ldy: ld RY0,Z+
ld RY1,Z+
ld RY2,Z+
ld RY3,Z+
ret

;Delay für RS232. Da kein Handshake verwendet wird
;muss der Sender fürs timing sorgen

millisec:clr A ;(256*3*8 -1 +7)*0,20 uS=1,23 ms
ldi B,9 ;wenn A nicht mit 0 geladen wird, dann ist der
millis: dec A ;1. Durchlauf eben entsprechend. Der 2. aber mit
brne millis ;256 , so kann man "fractured loops" bauen
dec B ;und gut sehr genau auf die gewünschte Zeit kommen.
brne millis
ret

;-----> wird nicht verwendet
;Empfange Zeichen via Interruptsteuerung. Kopie der Routine vom VFD-Display

RdCom: clr YH
ldi YL,$60
ldi mux,8 ;jetzt das ganze Display um eine Stelle nach links
d_shift:ldd B,Y+1 ;hole Stelle+1, speichere 1 Stelle links davon
st Y+,B ;dann nächste Stelle rechts adressieren
dec mux ;8x das ganze
brne d_shift
in A,UDR ;hole USART empfangenes Byte ab
rcall WrCom ;Echo out
andi A,15 ;in Zahl wandeln, falls nötig
ldi YL,$68 ;und
st Y,A ;ins Display rechtsbündig schreiben
reti ;nicht vergessen "i"


;max 9 Zeichen dürfen gesendet werden, das Display kann nicht mehr darstellen
WrCOM: sbis UCSRA,UDRE ;wait for empty transmit buffer
rjmp WrCom
out UDR,A ;sende Byte in A
ret

;***************************************************************************
;* Bin3BCD == 24-bit Binary to BCD conversion
;*
;* RX0:RX1:RX2 >>> RX4:RX5:RX6:RX7
;* hex dec
;* r16r17r18 >>> r20r21r22r23
;* see AVR200 bis MATH32X application notes, http://avr-asm.tripod.com
;* sehr gute Seite ist auch http://elm-chan.org u.a. Wurzel ziehen
;***************************************************************************
;.def RX0 =r16 ; binary value byte 0 (LSB)
;.def RX1 =r17 ; binary value byte 1
;.def RX2 =r18 ; binary value byte 2 (MSB)
;.def RX3 =r19 ; ist für diese Routine nicht nötig
;.def RX4 =r20 ; BCD value digits 0 and 1
;.def RX5 =r21 ; BCD value digits 2 and 3
;.def RX6 =r22 ; BCD value digits 4 and 5
;.def RX7 =r23 ; BCD value digits 6 and 7 (MSD)

Bin3BCD16: ldi RX7,0xfa ;initialize digits 7 and 6
binbcd_107: subi RX7,-0x10 ;
subi RX0,byte1(10000*1000) ;subit fbin,10^7
sbci RX1,byte2(10000*1000) ;
sbci RX2,byte3(10000*1000) ;
brcc binbcd_107 ;
binbcd_106: dec RX7 ;
subi RX0,byte1(-10000*100) ;addit fbin,10^6
sbci RX1,byte2(-10000*100) ;
sbci RX2,byte3(-10000*100) ;
brcs binbcd_106 ;
ldi RX6,0xfa ;initialize digits 5 and 4
binbcd_105: subi RX6,-0x10 ;
subi RX0,byte1(10000*10) ;subit fbin,10^5
sbci RX1,byte2(10000*10) ;
sbci RX2,byte3(10000*10) ;
brcc binbcd_105 ;
binbcd_104: dec RX6 ;
subi RX0,byte1(-10000) ;addit fbin,10^4
sbci RX1,byte2(-10000) ;
sbci RX2,byte3(-10000) ;
brcs binbcd_104 ;
ldi RX5,0xfa ;initialize digits 3 and 2
binbcd_103: subi RX5,-0x10 ;
subi RX0,byte1(1000) ;subiw fbin,10^3
sbci RX1,byte2(1000) ;
brcc binbcd_103 ;
binbcd_102: dec RX5 ;
subi RX0,byte1(-100) ;addiw fbin,10^2
sbci RX1,byte2(-100) ;
brcs binbcd_102 ;
ldi RX4,0xfa ;initialize digits 1 and 0
binbcd_101: subi RX4,-0x10 ;
subi RX0,10 ;subi fbin,10^1
brcc binbcd_101 ;
add RX4,RX0 ;LSD
ret ;


;--------------------------------------------------------------------
;-- SRAM variablen
;--------------------------------------------------------------------
.dseg

kc: .byte 6 ; Konstante für C-Rechnung
Co: .byte 4 ; C reference
Lo: .byte 4 ; L reference
fo2: .byte 4 ; f0 ohne Cx
fx2: .byte 4 ; fx mit zu messendem Cx, Lx
kl: .byte 6 ; Constant L calculation

; ursprünglich wollte ich fo ersetzen durch kc bzw kl für die Cx und Lx
; Messung. Aber die aktuelle fo Messung und die Autocalibratroutine
; (jede 2. Messung stellt fo fest) ist einfacher, da fo nicht aus Lo
; bzw Co errechnet wird. Auch ist Lo und Co nicht ausreichend konstant über
; Temperatur. Das ist die einzige Quelle, neben dem LM319 für Unstabilität


Elektronik-Labor   Projekte   AVR