Pfad:
Home ==>
Mikrobeginner ==> 13. Frequenzzähler,
Induktivitätsmessgerät
This page in English (external):
Lektion 13: Frequenzzähler und Induktivitätsmessgerät
Und noch mal was richtig Praktisches: ein Frequenzzähler, mit Umwandlung von
24-Bit- und 40-Bit-Binärzahlen in ihre Dezimalentsprechung. Und obendrein ein praktisches
Induktivitätsmessgerät, mit Quadrieren und Dividieren in Assembler.
13.0 Übersicht
- Einführung in die Frequenzmessung
- Einführung in die Dezimalumwandlung
- Digitalsignalmessung mit PCINT
- Analogsignalmessung mit Analogvergleicher
- Induktivitätsmessung mit PCINT
13.1.1 Wahl der Torzeit
Die Messung von Frequenzen ist eigentlich trivial: man zählt einfach die Anzahl Sinuskurven
des Eingangssignals über eine Sekunde und zeigt diese dann auf der LCD-Anzeige an. Nun ist
eine Sekunde etwas lang.Wie wäre es mit 0,5 Sekunden oder gar 0,25 Sekunden?
Das Erkennen von Rechtecksignalen kann man mit dem Eingang INT0 oder, mit dem PCINT0 oder PCINT1,
an jedem anderen Eingang vornehmen. Verwendet man INT0, kann man die zu erkennende Flanke
(aufwärts, abwärts, beide) vorwählen. Beim PCINT tritt der Interrupt bei beiden
Flanken ein, also pro Schwingung zweimal.
Die großartige Aufgabe in Assembler ist dann die Multiplikation der gezählten Impulse
mit zwei oder vier. Der C-Programmierer wirft jetzt seine großartige
Multiplikationsbibliothek an (und steigt erst mal auf einen größeren AVR-Typ um, weil
ihm das Flash dadurch schon arg knapp wird), während der Assemblerkundige mit zweimaligem
Links-Shiften und viermaligem Links-Rotieren in ganzen sechs Takten schon fertig ist, also
z. B. so:
; Zaehlergebnis ueber 0,25 Sekunden in R3:R2:R1
lsl R1 ; mal zwei
rol R2
rol R3
lsl R1 ; mal vier
rol R2
rol R3
Wieso eigentlich drei Register? Mit denen lässt sich bis 256*256*256-1 = 16.777.215
zählen, also in einer Viertelsekunde bis 66,8 MHz. Also weit oberhalb dessen, was
normale Elektronikbastler so an Schwingungen machen und auch weit oberhalb dessen, was so ein
AVR normal zählen kann. Der ist mit 1 MHz Takt schon bei unter 100 kHz voll mit
Zählen ausgelastet und geht in die Sättigung. Mit der folgenden
Zähl-Interrupt-Serviceroutine kriegen wir nämlich folgende Ausführungszeiten:
; Interrupt starten: 4 Takte; Vektorsprung: 2 Takte
CntIsr:
in R15,SREG ; SREG sichern, +1 = 7
inc R1 ; Einer aufwaerts zaehlen, +1 = 8
brne CntIsrRet ; +1/2 = 9/10
inc R2 ; 256-er aufwaerts
brne CntIsrRet
inc R3 ; 65536-er aufwaerts
CntIsrRet: ; Minimum 10 Takte
out SREG,R15 ; SREG wieder herstellen, +1 = 11
reti ; +4 = 15
Mit 15 Takten sind bei 1 MHz Systemtakt 15 µs vergangen und bei
1.000.000 / 15 = 66.667 Hz ist es aus mit Zählen. Mit einem Takt von
8 MHz kommen wir immerhin noch bis 533 kHz Zählrate. Verwendet man zum
Zählen den eingebauten Timer, indem man statt des Vorteilers den externen Eingang
T1 (beim ATtiny24 PA4 am Pin 9) zählen lässt, kommt man etwa auf 250 kHz
(1 MHz Takt) oder 2 MHz (8 MHz Takt). Allerdings hängt an diesem Pin
in unserer Schaltung schon der Datenport D4 der LCD und wir müssten uns zusätzlich
was einfallen lassen, wie wir wechselweise das Messsignal und den LCD-Datenport, lesend und
schreibend, an diesen Pin bringen. Mit einem anderen AVR hätten wir dieses Problem
möglicherweise nicht.
Wer noch mehr zählen will, taktet den AVR mit 20 MHz (maximal 1,3 MHz
Zählrate) oder schaltet einen binären Vorteiler zwischen Messsignal und AVR,
allerdings unter Einbußen bei der Auflösung.
Umgekehrt sind bei ganz niedrigen Frequenzen von weniger als 10 Hz (kommt selten vor)
entweder längere Torzeiten angebracht. Oder man wechselt die Messmethode: man zählt
einfach die Zeit t zwischen zwei Signalen, die Frequenz ist dann 1 / t. Die wären
dann z. B. dadurch messbar, indem der Timer bei 1 MHz Takt mit einem Vorteiler von 1
zählt, wie lange es von Schwingung zu Schwingung braucht. Dann kriegt man direkt die
Mikrosekunden. Durch Teilen von 1.000.000 durch diese Zahl kriegt man die Frequenz in mHz.
Wie man solche großen Zahlen binär teilt (ohne wie der C-Programmierer die ganze
riesige Fließkommabibliothek zu bemühen und dann doch auf einen 96-pinnigen ATxmega
umzusteigen), kriegen wir etwas später in dieser Lektion.
13.1.2 Analogsignale auswerten
Oft tun uns zu messende Signale nicht den Gefallen, schon als steilflankige Rechteckschwingung
mit 5 V Amplitude vorzuliegen. Die Signale von einem Mikrofon, einem Lautsprecher oder aus
anderen Quellen sind Sinusse mit irgendeiner Amplitude von 5 mV (dynamisches Mikrofon) bis
2 V (Lautsprecher, 1 Watt an 4 Ohm). Der Elektronikbastler verstärkt jetzt
diese Signale so viel, dass da schon ein Rechteck rauskommt und schickt dieses schon steile
Signal noch mal durch einen Schmitt-Trigger. Jede Menge externe Elektronik jedenfalls.
Nicht so der AVR-Kundige. Der weiß, dass jeder uralte popelige AVR diese ganze Mimik schon
an Bord hat und nur darauf harrt, eingeschaltet zu werden. Die Mimik nennt sich
Analogvergleicher (Analog Comparator). Sie besteht aus einem Operationsverstärker, dessen
positiver Eingang am Pin AIN0 (PA1) und dessen negativer Eingang am Pin AIN1 (PA2) angeschlossen
ist. Das Vergleichsergebnis ist im Bit ACO im Analogstatus- und kontrollregister ACSR ablesbar.
Ist das Bit ACIE (Bit 3) in diesem Register gesetzt, dann wird bei jedem Wechsel des Bits der
Analogvergleicher-Interrupt ausgelöst und die Ausführung verzweigt an dessen
Vektor-Adresse (beim ATtiny24 0x000C). Das kann man sich auch zum Frequenzmessen zunutze
machen.
Damit ist fast alles schon vorhanden, um Analogsignale niedrigster Amplitude auf
Polaritätswechsel hin zu untersuchen: die ausgelösten Interrupts müssen einfach
nur gezählt werden. Es sind doppelt so viele wie die Frequenz, weil jeder Sinusdurchgang
zweimal die Polarität wechselt. Wir können uns bei einer Messzeit von 250 ms
also einmal Linksschieben und zweimal Linksrotieren des Zählergebnisses sparen.
13.1.3 Induktivitäten messen
Induktivitäten sind Spulen. Die Größe der Induktivität wird in Henry (H) angegeben.
Ihre Größe lässt sich anhand des (Schein-)Widerstands messen, den sie einer
Wechselspannung mit der Frequenz F entgegensetzen. Dieser Scheinwiderstand Z ergibt sich aus
der Formel
ZL (Ohm) = 2 * Π * F (Hz) * L (H)
Darin wird der Teil "2 * Π * F" auch als Kreisfrequenz bezeichnet und mit ω
abgekürzt.
Nun lässt sich der Widerstand nur etwas aufwändig messen, besonders, da es sich um
Wechselspannung handeln muss. Eleganter, und viel einfacher, ist es, die Spule mit einem
Kondensator zu einem Schwingkreis zu verschalten. Schwingkreise lassen sich bekanntlich zum
Schwingen anregen. Sie schwingen dann auf der Frequenz, bei der der Scheinwiderstand der Spule
gleich groß ist wie der des Kondensators. Dessen Scheinwiderstand ist umgekehrt zur
Frequenz (je größer die Frequenz, desto kleiner der Widerstand), also
ZC = 1 / (ω * C (Farad))
Die Gleichheit der beiden Scheinwiderstände ZL = ZC zeitigt die
Schwingungsgleichung
ZL = ZC oder ω * L = 1 / (ω * C) oder
ω2 = 1 / (L * C) oder ω = √(1/(L * C)) oder F = 1 / (2 * Π) *
√(1/(L * C))
Regt man den Schwingkreis mit der Spule und einem bekannten Kondensator zum Schwingen an, kann
man aus der gemessenen Frequenz mit der Formel
L(H) = 1 / (4 * Π2 * C(F) * F(Hz)2)
direkt die Induktivität ermitteln.
Da der Assemblerprogrammierer solche komplizierten Formeln etwas scheut (der C-Programmierer
nicht, der nimmt einfach seine Riesen-Fließkommabibliothek und steigt auf Mega um),
müsste sich das mit etwas Intelligenz umformulieren lassen. Da sowohl 4 als auch
Π2 als auch C sich nicht ändern, kann man 1 / (4 * Π2 * C)
einmal ausrechnen und dann nur durch F2 teilen. Nehmen wir das Ganze noch mit
1.000.000 mal, kriegen wir die Induktivität in µH. Für einen Kondensator von
50 nF resultiert daraus die Zahl 506.605.918.079, oder hexadezimal 75.F4.10.D7.7F, eine
40-bittige Zahl.
Diese ist durch F2 zu teilen. Statt nun wieder die großartige
Fließkomma-Bibliothek zu bemühen nimmt der Assemblerprogrammierer die gemessene
Frequenz, einfach mit sich selbst mal. Für die Frequenzmessung kommt eine 24-bittige Zahl
zum Einsatz (siehe oben), eigentlich liefert die beim Malnehmen ein 48-bittiges Ergebnis. Auf
die oberen 8 Bit können wir verzichten.
Begrenzen wir F2 auf 40 Bits, darf F maximal 741,455 kHz werden, was bei
1 MHz Taktfrequenz ohnehin nicht mehr messbar ist.
Die Untergrenze ergibt sich daraus, dass die Ausgabe von Induktivitäten von über
999 H unnötig ist. Es kommen daher nur Frequenzen von 23 und mehr Hz für die
Ausgabe in Frage.
13.1.3.1 Division 8-Bit durch 8-Bit
Die einfachste Division geht mit 8 Bits folgendermaßen. Zuerst wird das höchste Bit
des Dividenten in den noch leeren Divisionsbereich hineingeschoben. Von diesen 8 Bits wird der
Divisor abgezogen. Tritt dabei ein Überlauf auf, wird die Subtraktion wieder zurück
genommen und eine Null in das Ergebnisregister geschoben. Trat kein Überlauf auf, wird
eine Eins in das Ergebnis geschoben.
Diese Abfolge wird weitere sieben Male wiederholt und die Division ist fertig.
In Assembler sieht das so aus.
;
; Division 8 Bit durch 8 Bit
;
ldi R16,0xEE ; Dividend
ldi R17,10 ; Divisor
ldi R18,8 ; Anzahl Bits
clr R19 ; Dividend laufend
clr R20 ; Ergebnis
Shift:
lsl R16 ; Oberstes Bit Dividend
rol R20 ; in laufenden Dividend
sub R20,R17 ; Divisor abziehen
brcc Eins ; Carry clear = Eins
add R20,R17 ; Divisor wieder addieren
clc ; Ergebnisbit = 0
rjmp Resultat ; in Ergebnis
Eins:
sec ; loesche Ergebnisbit
Resultat:
rol R19 ; in Ergebnis rollen
dec R18 ; abwaerts zaehlen
brne Shift ; noch weiter
nop ; fertig
Das Studio sagt, dass die Division 92 µs benötigt.
Eigentlich ist binäre Division einfacher als dezimale, da es ja immer nur entweder Abziehen
(Eins) oder nicht Abziehen (Null) gibt.
13.1.3.2 Division 16-Bit durch 8-Bit
Bei 16 Bits kommen jeweils zwei Register zum Einsatz, bei denen Überträge mit addiert
und subtrahiert werden.
;
; Division 16 Bit durch 8 Bit
;
ldi R31,High(50000) ; Dividend
ldi R30,Low(50000)
ldi R16,75 ; Divisor, LSB
clr R17 ; MSB
clr R27 ; Ergebnis, MSB
clr R26 ; LSB
clr R18 ; Dividend, aktuell, LSB
clr R19 ; MSB
ldi R20,16 ; 16 Bits, Zaehler
Shift:
lsl R30 ; Dividend links schieben
rol R31 ; MSB
rol R18 ; in aktuellen rollen, LSB
rol R19 ; MSB
sub R18,R16 ; subtrahiere Divisor, LSB
sbc R19,R17 ; MSB
brcc Eins ; kein Carry, schiebe 1
add R18,R16 ; Carry, subtrahieren rueckgaengig
adc R19,R17 ; MSB
clc ; schiebe 0
rjmp Ergebnis ; in das Ergebnis
Eins:
sec ; schiebe 1
Ergebnis:
rol R26
rol R27
dec R20
brne Shift
nop
Auch das ist nicht gerade riesiger Aufwand. Das Ganze braucht nun 265 µs.
13.1.3.3 Division 40-Bit durch 40-Bit
Nachdem das Prinzip nunmehr verstanden ist, ist die 40-Bit-Division eigentlich kein Problem
mehr. Das Dividieren der 40-bittigen Zahl durch eine maximal 40-bittige Zahl (F2) mit
einem potenziell 40-bittigen Resultat erfordert aber insgesamt 160 Bits oder 20 Register, weil
für den Dividenten 80 Bits gebraucht werden. Bei 20 Registern zum Dividieren bleibt
nicht viel für Anderes übrig. Die Lösung für diesen Engpass ist die
Verlegung des Dividenten in das SRAM. Mit fünfmaligem
ld R16,Z ; Z zeigt auf Tabelle im SRAM
rol R16
st Z+,R16
wird dann das jeweils höchstwertige Bit des verbleibenden Dividenten in das Carry geschoben
und von da aus in den aktuellen Dividenden. Das braucht etwas länger, aber auch das ist
kein Riesendrum.
Die folgende Tabelle vollzieht den Ablauf im Programm für eine gemessene Frequenz von
1000 Hz (=0x0003E8, F2 = 1.000.000 = 0x0F4240).
Dez. | Hex | Ergebnis hex | Nach Subtr. | Subtr | Nach Rollen | Rollen | SRAM, Hex | SRAM, Dez |
40 | 28 | 0000000000 | 0000000000 | 0 | 0000000000 | 0 | 75F410D77F | 506.605.918.079 |
39 | 27 | 0000000000 | 0000000001 | 0 | 0000000001 | 1 | EBE821AEFE | 1.013.211.836.158 |
38 | 26 | 0000000000 | 0000000003 | 0 | 0000000003 | 1 | D7D0435DFC | 926.912.044.540 |
37 | 25 | 0000000000 | 0000000007 | 0 | 0000000007 | 1 | AFA086BBF8 | 754.312.461.304 |
36 | 24 | 0000000000 | 000000000E | 0 | 000000000E | 0 | 5F410D77F0 | 409.113.294.832 |
35 | 23 | 0000000000 | 000000001D | 0 | 000000001D | 1 | BE821AEFE0 | 818.226.589.664 |
34 | 22 | 0000000000 | 000000003A | 0 | 000000003A | 0 | 7D0435DFC0 | 536.941.551.552 |
33 | 21 | 0000000000 | 0000000075 | 0 | 0000000075 | 1 | FA086BBF80 | 1.073.883.103.104 |
32 | 20 | 0000000000 | 00000000EB | 0 | 00000000EB | 1 | F410D77F00 | 1.048.254.578.432 |
31 | 1F | 0000000000 | 00000001D7 | 0 | 00000001D7 | 1 | E821AEFE00 | 996.997.529.088 |
30 | 1E | 0000000000 | 00000003AF | 0 | 00000003AF | 1 | D0435DFC00 | 894.483.430.400 |
29 | 1D | 0000000000 | 000000075F | 0 | 000000075F | 1 | A086BBF800 | 689.455.233.024 |
28 | 1C | 0000000000 | 0000000EBE | 0 | 0000000EBE | 0 | 410D77F000 | 279.398.838.272 |
27 | 1B | 0000000000 | 0000001D7D | 0 | 0000001D7D | 1 | 821AEFE000 | 558.797.676.544 |
26 | 1A | 0000000000 | 0000003AFA | 0 | 0000003AFA | 0 | 0435DFC000 | 18.083.725.312 |
25 | 19 | 0000000000 | 00000075F4 | 0 | 00000075F4 | 0 | 086BBF8000 | 36.167.450.624 |
24 | 18 | 0000000000 | 000000EBE8 | 0 | 000000EBE8 | 0 | 10D77F0000 | 72.334.901.248 |
23 | 17 | 0000000000 | 000001D7D0 | 0 | 000001D7D0 | 0 | 21AEFE0000 | 144.669.802.496 |
22 | 16 | 0000000000 | 000003AFA0 | 0 | 000003AFA0 | 0 | 435DFC0000 | 289.339.604.992 |
21 | 15 | 0000000000 | 0000075F41 | 0 | 0000075F41 | 1 | 86BBF80000 | 578.679.209.984 |
20 | 14 | 0000000000 | 00000EBE82 | 0 | 00000EBE82 | 0 | 0D77F00000 | 57.846.792.192 |
19 | 13 | 0000000001 | 00000E3AC4 | 1 | 00001D7D04 | 0 | 1AEFE00000 | 115.693.584.384 |
18 | 12 | 0000000003 | 00000D3348 | 1 | 00001C7588 | 0 | 35DFC00000 | 231.387.168.768 |
17 | 11 | 0000000007 | 00000B2450 | 1 | 00001A6690 | 0 | 6BBF800000 | 462.774.337.536 |
16 | 10 | 000000000F | 0000070661 | 1 | 00001648A1 | 1 | D77F000000 | 925.548.675.072 |
15 | 0F | 000000001E | 00000E0CC3 | 0 | 00000E0CC3 | 1 | AEFE000000 | 751.585.722.368 |
14 | 0E | 000000003D | 00000CD746 | 1 | 00001C1986 | 0 | 5DFC000000 | 403.659.816.960 |
13 | 0D | 000000007B | 00000A6C4D | 1 | 000019AE8D | 1 | BBF8000000 | 807.319.633.920 |
12 | 0C | 00000000F7 | 000005965A | 1 | 000014D89A | 0 | 77F0000000 | 515.127.640.064 |
11 | 0B | 00000001EE | 00000B2CB5 | 0 | 00000B2CB5 | 1 | EFE0000000 | 1.030.255.280.128 |
10 | 0A | 00000003DD | 000007172B | 1 | 000016596B | 1 | DFC0000000 | 960.998.932.480 |
9 | 09 | 00000007BA | 00000E2E57 | 0 | 00000E2E57 | 1 | BF80000000 | 822.486.237.184 |
8 | 08 | 0000000F75 | 00000D1A6E | 1 | 00001C5CAE | 0 | 7F00000000 | 545.460.846.592 |
7 | 07 | 0000001EEB | 00000AF29D | 1 | 00001A34DD | 1 | FE00000000 | 1.090.921.693.184 |
6 | 06 | 0000003DD7 | 000006A2FB | 1 | 000015E53B | 1 | FC00000000 | 1.082.331.758.592 |
5 | 05 | 0000007BAE | 00000D45F7 | 0 | 00000D45F7 | 1 | F800000000 | 1.065.151.889.408 |
4 | 04 | 000000F75D | 00000B49AF | 1 | 00001A8BEF | 1 | F000000000 | 1.030.792.151.040 |
3 | 03 | 000001EEBB | 000007511F | 1 | 000016935F | 1 | E000000000 | 962.072.674.304 |
2 | 02 | 000003DD76 | 00000EA23F | 0 | 00000EA23F | 1 | C000000000 | 824.633.720.832 |
1 | 01 | 000007BAED | 00000E023F | 1 | 00001D447F | 1 | 8000000000 | 549.755.813.888 |
0 | Rdg | 000007BAEE | 00000CC23E | 1 | 00001C047E | 0 | 0000000000 | 0 |
Das Ergebnis der Division vor der Rundung, 506.606 µH, stimmt mit der Berechnung
überein. Die Berechnung dauert laut Studio 277 µs für die Multiplikation,
2.919 µs für die Division und 487 µs für die Dezimalumwandlung,
insgesamt 3.232 µs für alles. Nicht so arg lang für solche Riesenzahlen.
Damit steht der intelligenten Berechnung der Induktivität nichts mehr im Wege.
Schon in der Lektion 11 mit dem EEPROM-Einschaltzähler hatten wir 8- und 16-Bit-Zahlen
von Binär in Dezimalzahlen verwandelt und dabei führende Nullen unterdrückt.
Bei den Infrarot-Experimenten haben wir es uns einfach gemacht und die Zahlen aus gutem Grund
lieber hexadezimal ausgegeben. Nun haben wir es mit Monsterzahlen von 24 und 32 Bit Länge
zu tun. Ich bin mir nicht sicher, was der C-Programmierer jetzt macht, auf jeden Fall ist er
auf eine riesige Fließkomma-Bibliothek angewiesen und steigt mindestens auf einen Mega,
wenn nicht auf einen Xmega um. Was bloß das Gerücht, Assembler sei schwer zu lernen,
aus ansonsten ganz cleveren Menschen machen kann.
Dabei ist die Erweiterung von 16-Bit-Dezimalumwandlung auf 24 oder 32 Bit lange Zahlen
eigentlich ganz easy, wenn man das Prinzip mal verstanden hat: wiederholtes Abziehen der
Dezimalzahlen, von der größten angefangen immer zehnmal weniger und nacheinander bis
herunter zur 10. Entsprechend hatten wir bei 8 Bit mit dezimal 100 angefangen und bei 16 Bit mit
dezimal 10.000. Bei 24 Bit (256 hoch 3 minus 1 = 16.777.215) beginnen wir mit 10 Millionen,
bei 32 Bit (256 hoch 4 minus 1 = 4.294.967.295) eben einfach mit einer Milliarde.
Spätestens bei 40 Bit stoßen wir allerdings auf eine Grenze, die mit den Eigenheiten von
Assemblerprogrammen zu tun hat. Die verwalten nämlich Ganzzahlen manchmal als
vorzeichenbehaftete 32-Bit-Zahlen und können daher eigentlich nur 31 Bit Länge und
keinesfalls 40 Bit. Größere Zahlen muss man in Assembler auf andere Weise handhaben.
In dieser Lektion werden wir es mit einer 40-Bit-Zahl zu tun kriegen.
Mehr dazu später, wenn wir es brauchen.
13.3.1 Aufgabenstellung
In der ersten Aufgabe sind digitale Rechtecksignale zu messen.
13.3.2 Hardware, Aufbau
Der Hardwareaufwand bei dieser Aufgabe ist Null. Die Signalquelle wird einfach an den Eingang
PA3 angeschlossen.
So kann das Ganze aussehen.
Alternativer Aufbau
Wer dieses oder die nächsten Experimente lieber auf kompaktere Weise
aufbauen möchte und die vielen Zuleitungen zur LCD fest verdrahtet
sehen möchte, kann sich das
Board hier
als gedruckte Platine bauen. Alle Programmbeispiele dieser und der
nachfolgenden Lektionen laufen darauf ohne Änderungen.
13.3.3 Programm
Das Programm ist im Folgenden gelistet (der Quellcode ist hier).
;
; **************************************
; * Frequenzmessg Digital ATtiny24/LCD *
; * (C)2016 by www.gsc-elektronic.net *
; **************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Programmablauf -----------
;
; Misst die Anzahl Pegelwechsel an PA0 mittels
; PCINT ueber eine Viertelsekunde lang, nimmt
; diese mit Zwei mal, wandelt das Ergebnis in
; eine Dezimalzahl um und gibt sie auf der LCD
; aus.
;
; ----------- Hardware -----------------
; Digitaler Frequenzzaehler Eingang an
; PCINT3/PA3
;
; ----------- Timing -------------------
; Messzeit 250 ms
; Prozessor-Takt 8.000.000 Hz
; TC1-Prescaler 64
; TC1-Takt 125.000 Hz
; TC1-Takte in 250 ms 31.250
.equ cTc1CmpA = 31249
;
; ----------- Messwertmittelung --------
; Aktueller Messwert / 2 plus
; Vorheriger Messwert / 4 plus
; Vorvorheriger Messwert / 8 plus
; Vorvorvorheriger Messwert / 8 =
; Aktuelle Anzeige = F
;
; ----------- Ports, Pins --------------
.equ pOut = PORTA ; Ausgabeport
.equ pDir = DDRA ; Richtungsport
.equ bIO = PORTB3 ; Pin-Out Digital
.equ bID = DDA3 ; Pin-Richtung Digital
;
; ----------- Register -----------------
; benutzt: R0, R1 fuer LCD
.def rM0L = R2 ; aktueller Messwert, LSB
.def rM0M = R3 ; dto., MSB
.def rM0H = R4 ; dto., HSB
; frei: R5 .. R14
.def rSreg = R15 ; Statusregister
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp = R20 ; Vielzweck, Interrupts
.def rFlag = R21 ; Flaggen
.equ bTO = 0 ; Timeout vom Timer
.def rHilf = R22 ; Hilfsregister Dezimal
; frei: R22 .. R25
; benutzt: R27:R26 X ; fuer diverse Zwecke
; frei: R29:R28 Y
; benutzt: R31:R30 Z ; fuer LCD
;
; ----------- SRAM ---------------------
.DSEG
.ORG 0x0060
sM: ; Messwertspeicher, vier Werte:
; aktuell/2, letzter/4, vorletzter/8,
; vorvorletzter/8
; jeweils: L(+0), M(+1), H(+2)
.Byte 12
sMEnd:
;
; ---- Reset- und Int-Vektoren ---------
.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 Routinen -----
;
; PCINT Interrupt
; wird von jedem Pegelwechsel am PCINT3-
; Eingang ausgeloest
;
; Erhoeht den 24-Bit-Zaehler rM0H:rM0M:rM0L
;
Pci0Isr: ; Impulse am Digitaleingang zaehlen
in rSreg,SREG ; Status retten
inc rM0L ; zaehlen
brne Pci0IsrRet
inc rM0M ; MSB erhoehen
brne Pci0IsrRet
inc rM0H
Pci0IsrRet:
out SREG,rSreg ; Status herstellen
reti
;
; TC1 Compare Match A Interrupt
; wird nach Ablauf jedes CTC-Zyklusses
; ausgeloest
;
; Ausloesung nach 64 * (31.249 + 1) / 8 =
; 250 ms.
;
; Stoppt den PCINT-Interrupt und setzt
; die bTO-Flagge die die Ergebnisaus-
; gabe ausloest
;
Tc1Isr: ; Timeout Zaehler
in rSreg,SREG ; Status retten
ldi rimp,0 ; Zaehlen anhalten
out GIMSK,rimp ; Int-Disable
sbr rFlag,1<<bTO ; Timeout-Flagge
out SREG,rSreg ; Status herstellen
reti
;
; ----------- Hauptprogramm-Init ------
Start:
; Stapel einrichten
ldi rmp,LOW(RAMEND) ; RAM-Ende
out SPL,rmp ; in Stapelzeiger
; Auf 8 MHz Takt umstellen
ldi rmp,1<<CLKPCE ; Change Enable
out CLKPR,rmp ; in Clock Prescaler
ldi rmp,0 ; Precaler / 1
out CLKPR,rmp
; Port initialisieren
sbi pOut,bIO ; Eingabepin Pullup
cbi pDir,bID ; Eingabepin Input
; LCD-Port-Ausgaenge initiieren
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; Kontrollport-Ausgaenge
clr rmp ; Ausgaenge aus
out pLcdCO,rmp ; an Kontrollport
ldi rmp,mLcdDRW ; Datenport-Ausgabe, Schreiben
out pLcdDR,rmp ; auf Richtung Datenausgabeport
; LCD Init
rcall LcdInit ; starten LCD
ldi ZH,High(2*LcdTextOut) ; Z auf Text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Gib Text aus
; Timer Init
ldi rmp,High(cTc1CmpA) ; Compare auf
out OCR1AH,rmp ; Messzyklusdauer
ldi rmp,Low(cTc1CmpA)
out OCR1AL,rmp
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
; PCINT3 aktivieren
ldi rmp,1<<PCINT3 ; Pin change PA3
out PCMSK0,rmp ; maskieren
ldi rmp,1<<PCIE0 ; PCINT0 Interrupt
out GIMSK,rmp ; in Int-Maske
; Sleep Mode
ldi rmp,1<<SE ; Sleep enable
out MCUCR,rmp ; in Kontrollregister
; Interrupts enablen
sei ; Ints zulassen
Schleife:
sleep ; schlafen legen
nop ; nach Aufwachen
sbrc rFlag,bTO ; Timeout-Flagge?
rcall Auswerten ; gesetzt, Auswerten
rjmp Schleife
;
; Routine Auswerten der Zaehlergebnisse
; wird von der bTO-Flagge ausgeloest
;
; Stoppt den Compare-Match-Int von TC1,
; nimmt den Messwert mit zwei Mal, mit-
; telt die aktuelle und die letzten drei
; Messungen (Durchschnitt ueber eine Se-
; kunde), gibt den Durchschnitt dezimal
; auf der LCD aus und startet die naech-
; ste Messung.
;
Auswerten:
cbr rFlag,1<<bTO ; Flagge ruecksetzen
clr rmp ; Compare Match Int aus
out TIMSK1,rmp
; Messwerte im SRAM verschieben+dividieren
ldi ZH,High(sMEnd) ; Ziel
ldi ZL,Low(sMEnd)
ldi XH,High(sMEnd-3) ; Quelle
ldi XL,Low(sMEnd-3)
ld rmp,-X ; vorletzter nach vorvorletzter
st -Z,rmp ; kopieren
ld rmp,-X
st -Z,rmp
ld rmp,-X
st -Z,rmp
ld rmp,-X ; letzter nach vorletzter+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 ; neuester nach letzter+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 ; neuester ablegen
st -Z,rM0M
st -Z,rM0L
adiw ZL,3 ; Zeiger auf letzten
ldi rmp,4
mov R0,rmp ; R0 ist Zaehler
Auswerten1:
ld rmp,Z+ ; lese LSB
add rM0L,rmp ; Zu aktuellem Ergebnis addieren
ld rmp,Z+ ; lese MSB
adc rM0M,rmp ; addieren mit Uebertrag
ld rmp,Z+ ; lese HSB
adc rM0H,rmp ; addieren mit Uebertrag
dec R0
brne Auswerten1 ; Weitere Werte addieren
ldi ZH,1 ; LCD auf Ergebnisposition
ldi ZL,8
rcall LcdPos
rcall DezimalAus ; gib dezimal aus
Neustart:
clr rM0L ; Zaehler loeschen
clr rM0M
clr rM0H
ldi rmp,1<<PCIE0 ; PCINT0 Interrupt
out GIMSK,rmp ; in Int-Maske
clr rmp
out TCNT1H,rmp ; Zaehler ruecksetzen
out TCNT1L,rmp
ldi rmp,1<<OCIE1A ; Compare Match Int
out TIMSK1,rmp
ret
;
; 3 Byte-Zahl in rM0H:rM0M:rM0L in dezimal
; auf der LCD ausgeben
;
; Wird von der Auswerte-Routine aufgerufen
; Verwendet die 24-Bit-Dezimalwert-Tabelle
; bis 9,99 MHz (maximal bis 0x98.96.7F)
; Unterdrueckt fuehrende Nullen und Dezimal-
; trennzeichen
;
DezimalAus:
ldi ZH,High(2*DezimalTab)
ldi ZL,Low(2*DezimalTab)
clt ; Fuehrende Nullen
DezimalAus1:
lpm XL,Z+ ; lese Dezimalzahl
lpm XH,Z+
lpm rHilf,Z+
clr rmp ; Teilerzaehler
cp XL,rmp
brne DezimalAus2
cp XH,rmp
brne DezimalAus2
cp rHilf,rmp
breq DezimalAusEnd
DezimalAus2:
sub rM0L,XL ; abziehen
sbc rM0M,XH
sbc rM0H,rHilf
brcs DezimalAus3 ; Ueberlauf
inc rmp
rjmp DezimalAus2 ; weiter subtrahieren
DezimalAus3:
add rM0L,XL ; Ruecknahme Subtraktion
adc rM0M,XH
adc rM0H,rHilf
tst rmp ; Null?
brne DezimalAus4 ; Nicht Null
brts DezimalAus5 ; keine Nullen unterdr.
ldi rmp,' '
rcall LcdD4Byte
ldi rmp,' '
rjmp DezimalAusKomma
DezimalAus4:
set ; keine fuehrenden Nullen unterdr.
DezimalAus5:
subi rmp,-'0'
rcall LcdD4Byte
ldi rmp,'.'
DezimalAusKomma:
cpi XL,Byte1(1000000)
breq DezimalAusKomma1
cpi XL,Byte1(1000)
breq DezimalAusKomma1
rjmp DezimalAus1
DezimalAusKomma1:
rcall LcdD4Byte
rjmp DezimalAus1
DezimalAusEnd:
ldi rmp,'0' ; letzte Ziffer
add rmp,rM0L ; addieren
rjmp LcdD4Byte
;
DezimalTab:
.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 Starttext
LcdTextOut:
.db "Frequenzmesser tn24 ",0x0D,0xFF
.db "F(dig)= x,xxx,xxx Hz",0x0D,0xFF
; 8
.db " ",0x0D,0xFF
.db " ",0xFE,0xFE
;
; LCD-Include
.include "Lcd4Busy.inc"
;
; Ende Quellcode
;
13.3.4 Messbeispiel
Mit einem quarzgetriebenen Signalgenerator liefert die Messung folgendes Ergebnis:
Die Übereinstimmung ist nicht berauschend, was an dem recht ungenauen RC-Generator im
ATtiny24 liegt. Der ist bei 3 V Betriebsspannung kalibriert. Wer es genauer möchte,
kann das Oscillator Calibration Byte verändern. Wie das gemacht wird, steht im Handbuch.
Oder kann das Messergebnis per Multiplikation etwas genauer gestalten. Die entsprechenden
Grundlagen für die Multiplikation sind alle hier beschrieben.
13.4.1 Aufgabe
Die Frequenz von sinusförmigen Wechselspannungen ab 5 mV(eff) sollen gemessen und
angezeigt werden.
13.4.2 Hardware und Bauteile
Schaltbild
Das ist die nötige Hardware zum Messen. Sie besteht im Wesentlichen aus einem
Spannungsteiler, der für eine gewisse Ruhe sorgt, solange kein Signal angeschlossen ist.
Die zu messende Wechselspannung wird über einen Kondensator zugeführt.
Bauteile
Das ist der 1 µF-Elko, der die Referenzspannung konstant hält. Das längere
Bein ist wie immer der Pluspol.
Das ist eine mögliche Bauform des Folienkondensators von 100 nF.
Das sind die beiden Widerstände von 100 k, aus denen der Spannungsteiler aufgebaut
ist. Den 220 Ω hatten wir bereits früher.
Aufbau
So oder ähnlich sieht der Aufbau der Schaltung aus. Wer die Anschlussdrähte der
Widerstände etwas kürzt und die Schaltung kompakter aufbaut als hier gezeigt, kriegt
weniger störende Streusignale in den Eingang.
13.4.3 Programm
Das Programm ist im Folgenden gelistet (hier geht es zum Quellcode).
Eine Besonderheit und Unterschied zu allen bisherigen Formulierungen ist noch wichtig: der
Betrieb des Analogvergleichers im Interruptmodus ist mit dem Schlafmodus inkompatibel. Manchmal,
und unvorhersehbar, setzt im Schlafmodus das Aufwachen nach Interrupts völlig aus,
d. h. weder Analogvergleicher- noch Timer-Interrupts wecken die CPU auf. Der Chip
versinkt in den Dauerschlaf und die Messungen setzen aus. Nur ein Reset haucht der CPU wieder
Leben ein, aber nur für eine gewisse Zeit lang. ATMEL beschreibt diesen Fehler im
Handbuch korrekt. Diese Software arbeitet daher nicht im Schlafmodus.
Die Software misst nacheinander sowohl das Analogsignal wie auch das Signal am Digitaleingang.
Beide Ereignisse werden von der gleichen Interrupt-Service-Routine gezählt, nur sind
nacheinander der PCINT vom Digitaleingang und der Analogvergleicher-Int eingeschaltet. Der
Softwareteil zur Mittelung arbeitet daher mit zwei SRAM-Puffern.
;
; *************************************
; * Frequenzmessg Analog ATtiny24/LCD *
; * (C)2016 by www.gsc-elektronic.net *
; *************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Programmablauf -----------
;
; Nacheinander werden
; a) die Anzahl Pegelwechsel am PCINT3-
; Eingang,
; b) die Anzahl Pegelwechsel an den Analog-
; vergleicher-Eingaengen
; ueber 250 ms lang gezaehlt, gemittelt und
; in Hz auf der LCD angezeigt.
;
; ----------- Hardware -----------------
; Analoger Frequenzzaehler am Analog-
; vergleicher AIN0/PA1 und AIN1/PA2
;
; ----------- Timing -------------------
; Messzeit 250 ms
; Prozessor-Takt 8.000.000 Hz
; TC1-Prescaler 64
; TC1-Takt 125.000 Hz
; TC1-Takte in 250 ms 31.250
.equ cTc1CmpA = 31249
;
; ----------- Messwertmittelung --------
; Aktueller Messwert / 2 plus
; Vorheriger Messwert / 4 plus
; Vorvorheriger Messwert / 8 plus
; Vorvorvorheriger Messwert / 8 =
; Aktuelle Anzeige = F
;
; ----------- Ports, Pins --------------
.equ pOut = PORTA ; Ausgabeport
.equ pDir = DDRA ; Richtungsport
.equ bIO = PORTB3 ; Pin-Out Digital
.equ bID = DDA3 ; Pin-Richtung Digital
;
; ----------- Register -----------------
; benutzt: R0, R1 fuer LCD
.def rM0L = R2 ; aktueller Messwert, LSB
.def rM0M = R3 ; dto., MSB
.def rM0H = R4 ; dto., HSB
; frei: R5 .. R14
.def rSreg = R15 ; Statusregister
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp = R20 ; Vielzweck, Interrupts
.def rFlag = R21 ; Flaggen
.equ bTO = 0 ; Timeout vom Timer
.equ bAn = 1 ; Analogvergleicher aktiv
.def rHilf = R22 ; Hilfsregister Dezimal
; frei: R22 .. R25
; benutzt: R27:R26 X ; fuer diverse Zwecke
; frei: R29:R28 Y
; benutzt: R31:R30 Z ; fuer LCD
;
; ----------- SRAM ---------------------
.DSEG
.ORG 0x0060
sMD: ; Digitalwerte
.Byte 12
sMDEnd:
sMA: ; Analogwerte
.Byte 12
sMAEnd:
;
; ---- Reset- und Int-Vektoren ---------
.CSEG
.ORG 0x0000
rjmp Start ; Reset-Vektor, 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 Routinen -----
;
; CntIsr Interrupt
; wird wechselweise vom PCINT-Interrupt und
; vom Analogvergleicher-Interrupt bei jedem
; Pegelwechsel ausgeloest
;
; Zaehlt die Anzahl Pegelwechsel in rM0H:rM0M:rM0L.
;
CntIsr: ; Impulse am Analogvergleicher zaehlen
in rSreg,SREG ; Status retten
inc rM0L ; zaehlen
brne CntIsrRet
inc rM0M ; MSB erhoehen
brne CntIsrRet
inc rM0H
CntIsrRet:
out SREG,rSreg ; Status herstellen
reti
;
; TC1 Compare Match A Interrupt
; wird nach 250 ms vom Compare Match A
; ausgeloest
;
; Schaltet die Comparator- und PCINT-
; Interrupts ab und setzt die bTO-Flagge.
;
Tc1Isr: ; Timeout Zaehler
ldi rimp,0
out ACSR,rimp ; Disable Int Comparator
out GIMSK,rimp ; Disable PCInt
in rSreg,SREG ; Status retten
sbr rFlag,1<<bTO ; Timeout-Flagge
out SREG,rSreg ; Status herstellen
reti
;
; ----------- Hauptprogramm-Init ------
Start:
; Stapel einrichten
ldi rmp,LOW(RAMEND) ; RAM-Ende
out SPL,rmp ; in Stapelzeiger
; Auf 8 MHz Takt umstellen
ldi rmp,1<<CLKPCE ; Change Enable
out CLKPR,rmp ; in Clock Prescaler
ldi rmp,0 ; Precaler / 1
out CLKPR,rmp
; Port initialisieren
sbi pOut,bIO ; Eingabepin Pullup
cbi pDir,bID ; Eingabepin Input
; LCD-Port-Ausgaenge initiieren
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; Kontrollport-Ausgaenge
clr rmp ; Ausgaenge aus
out pLcdCO,rmp ; an Kontrollport
ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
out pLcdDR,rmp ; auf Richtungsregister Datenausgabeport
; LCD Init
rcall LcdInit ; starten LCD
ldi ZH,High(2*LcdTextOut) ; Z auf Text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Gib Text aus
ldi rmp,0x0C ; Cursor und Blink aus
rcall LcdC4Byte
; Timer Init
ldi rmp,High(cTc1CmpA) ; Compare auf
out OCR1AH,rmp ; Messzyklusdauer
ldi rmp,Low(cTc1CmpA)
out OCR1AL,rmp
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
; Analogvergleicher deaktivieren
ldi rmp,0 ; Disable Int Comparator
out ACSR,rmp
ldi rmp,(1<<ADC2D)|(1<<ADC1D) ; Input Pin disable
out DIDR0,rmp
; PCINT3 aktivieren
ldi rmp,1<<PCINT3 ; Pin change PA3
out PCMSK0,rmp ; maskieren
ldi rmp,1<<PCIE0 ; PCINT0 Interrupt
out GIMSK,rmp ; in Int-Maske
; Kein Sleep Mode wegen Analogcomparator!
ldi rmp,0 ; Sleep enable
out MCUCR,rmp ; in Kontrollregister
; Interrupts enablen
sei ; Ints zulassen
Schleife:
sbrc rFlag,bTO ; Timeout-Flagge?
rcall Auswerten ; gesetzt, Auswerten
rjmp Schleife
;
; Rountine Auswerten der Zaehlergebnisse
; wird von der bTO-Flagge ausgeloest
;
; Berechnet abhaengig von der Flagge bAn fuer
; PCINT- und Analogvergleicherergebnisse
; den Durchschnitt aus der aktuellen und
; den drei letzten Messwerten und gibt ihn
; dezimal auf der LCD aus.
;
Auswerten:
cbr rFlag,1<<bTO ; Flagge ruecksetzen
clr rmp ; Compare Match Int aus
out TIMSK1,rmp
; Messwerte im SRAM verschieben/Dividieren
sbrc rFlag,bAn ; Digitalwerte auswerten?
rjmp AuswertenAnalog
ldi ZH,High(sMDEnd) ; Ziel
ldi ZL,Low(sMDEnd)
ldi XH,High(sMDEnd-3) ; Quelle
ldi XL,Low(sMDEnd-3)
rjmp AuswertenShift
AuswertenAnalog:
ldi ZH,High(sMAEnd) ; Ziel
ldi ZL,Low(sMAEnd)
ldi XH,High(sMAEnd-3) ; Quelle
ldi XL,Low(sMAEnd-3)
AuswertenShift:
; Messwerte im SRAM verschieben+dividieren
ld rmp,-X ; vorletzter nach vorvorletzter
st -Z,rmp ; kopieren
ld rmp,-X
st -Z,rmp
ld rmp,-X
st -Z,rmp
ld rmp,-X ; letzter nach vorletzter+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 ; neuester nach letzter+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 ; neuester ablegen
st -Z,rM0M
st -Z,rM0L
adiw ZL,3 ; Zeiger auf letzten
ldi rmp,4
mov R0,rmp ; R0 ist Zaehler
Auswerten1:
ld rmp,Z+ ; lese LSB
add rM0L,rmp ; Zu aktuellem Ergebnis addieren
ld rmp,Z+ ; lese MSB
adc rM0M,rmp ; addieren mit Uebertrag
ld rmp,Z+ ; lese HSB
adc rM0H,rmp ; addieren mit Uebertrag
dec R0
brne Auswerten1 ; Weitere Werte addieren
ldi ZH,1 ; Digitalergebnis ausgeben
sbrc rFlag,bAn ; Analogflagge gesetzt?
ldi ZH,2 ; ja, Analogposition
ldi ZL,8
rcall LcdPos
rcall DezimalAus ; gib dezimal aus
Neustart:
clr rM0L ; Letzte Dezimale loeschen
ldi rmp,1<<bAn ; Analog-Flagge umkehren
eor rFlag,rmp ; Invertiert bAn-Flagge
sbrc rFlag,bAn ; Analogflagge gesetzt?
rjmp NeustartAnalog ; ja
; Digital messen, PCINT3 aktivieren
ldi rmp,1<<PCINT3 ; Pin change PA3
out PCMSK0,rmp ; maskieren
ldi rmp,1<<PCIE0 ; PCINT0 Interrupt
out GIMSK,rmp ; in Int-Maske
rjmp Neustart1
NeustartAnalog:
ldi rmp,1<<ACIE ; Enable Int Comparator
out ACSR,rmp
Neustart1:
clr rmp
out TCNT1H,rmp ; Zaehler ruecksetzen
out TCNT1L,rmp
ldi rmp,1<<OCIE1A ; Compare Match Int
out TIMSK1,rmp
ret
;
; 3 Byte-Zahl in rM0H:rM0M:rM0L in dezimal
; auf der LCD ausgeben
; wird von der Auswerten-Routine aufgerufen
;
; Wandelt rM0H:rM0M:rM0L in Dezimalzahl um
; und gibt sie an der aktuellen LCD-Position
; aus, unterdrueckt fuehrende Nullen und De-
; zimaltrennzeichen
;
DezimalAus:
ldi ZH,High(2*DezimalTab)
ldi ZL,Low(2*DezimalTab)
clt ; Fuehrende Nullen
DezimalAus1:
lpm XL,Z+ ; lese Dezimalzahl
lpm XH,Z+
lpm rHilf,Z+
clr rmp ; Teilerzaehler
cp XL,rmp
brne DezimalAus2
cp XH,rmp
brne DezimalAus2
cp rHilf,rmp
breq DezimalAusEnd
DezimalAus2:
sub rM0L,XL ; abziehen
sbc rM0M,XH
sbc rM0H,rHilf
brcs DezimalAus3 ; Ueberlauf
inc rmp
rjmp DezimalAus2 ; weiter subtrahieren
DezimalAus3:
add rM0L,XL ; Ruecknahme Subtraktion
adc rM0M,XH
adc rM0H,rHilf
tst rmp ; Null?
brne DezimalAus4 ; Nicht Null
brts DezimalAus5 ; keine Nullen unterdr.
ldi rmp,' '
rcall LcdD4Byte
ldi rmp,' '
rjmp DezimalAusKomma
DezimalAus4:
set ; keine fuehrenden Nullen unterdr.
DezimalAus5:
subi rmp,-'0'
rcall LcdD4Byte
ldi rmp,'.'
DezimalAusKomma:
cpi XL,Byte1(1000000)
breq DezimalAusKomma1
cpi XL,Byte1(1000)
breq DezimalAusKomma1
rjmp DezimalAus1
DezimalAusKomma1:
rcall LcdD4Byte
rjmp DezimalAus1
DezimalAusEnd:
ldi rmp,'0' ; letzte Ziffer
add rmp,rM0L ; addieren
rjmp LcdD4Byte
;
DezimalTab:
.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 Starttext
LcdTextOut:
.db "Frequenzmesser 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
.include "Lcd4Busy.inc"
;
; Ende Quellcode
;
13.4.4 Messbeispiele
Der analoge Messeingang ist sehr empfindlich, bei höheren Frequenzen reichen 2 mV(eff)
für eine stabile Messung aus. Bei niedrigen Frequenzen macht sich der hohe kapazitive
Scheinwiderstand des 100 nF-Kondensators etwas bemerkbar, so dass höhere Amplituden
erforderlich sind. Bei offenem Eingang machen sich Signale am Digitaleingang störend
bemerkbar.
Das gleiche Signal, einmal analog (mit kleiner Amplitude) und gleichzeitig digital eingespeist,
zeigt nicht immer das gleiche Ergebnis. Ursache dafür dürfte Übersprechen auf
den Analogeingang sein.
13.5.1 Aufgabe
Die Induktivität von Spulen ist zu messen. Sie soll einen weiten Messbereich von 1 mH
bis über 10 H umfassen.
13.5.2 Hardware und Bauteile
Schaltbild
Dies ist das komplette Schaltbild zur gleichzeitigen Messung von Digital- und Analogfrequenzen
sowie zur Bestimmung der Induktivität. Der induktive Teil ist mit einem CMOS-NAND-Gatter
realisiert, das die besten Schwingeigenschaften über den weiten Messbereich aufweist. Die
beiden Kondensatoren von 100 nF bilden mit der Induktivität den Schwingkreis, sie sind
in Serie geschaltet (wirksame Kapazität 50 nF). Die Rückkopplung ist über
100 k recht niedrig angekoppelt, reicht aber über den gesamten Messbereich gut aus.
Das zweite NAND-Gatter dient der Signalauskopplung, zwei weitere Gatter in der Packung sind
unbenutzt.
Bauteile
Das ist das Vierfach-NAND. Es eignen sich auch andere invertierende CMOS-Gatter.
Aufbau
Das ist der Aufbau. Die Spule ist mit Krokodilklemmen angekoppelt.
13.5.3 Programm
Das hier ist das Programm (zum Quellcode geht es hier). Es misst
nacheinander an allen drei Eingängen und stellt die Messergebnisse auf drei LCD-Zeilen
dar. Auf den Schlafmodus wurde wieder verzichtet, weil der Analogvergleicher das Aufwecken
blockiert.
;
; *************************************
; * Frequenz- und Induktivitaetsmessg *
; * (C)2016 by www.gsc-elektronic.net *
; *************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Programmablauf -----------
;
; Misst nacheinander Pegelwechsel
; a) am PCINT3-Eingang,
; b) an den Analogvergleichereingaengen,
; c) am LC-Oszillator
; ueber 250 ms lang, rechnet diese in
; Frequenzen (a, b) oder Induktivitaet (c)
; um und stellt das Ergebnis dezimal auf
; der LCD dar.
;
; ----------- Hardware -----------------
; Digitaler Frequenzzaehler am Input PA3
; und
; Analoger Frequenzzaehler am Analog-
; vergleicher AIN0/PA1 und AIN1/PA2
; und
; Induktivitaetsmessung aus Frequenz am
; Input PA0 (4011-LC-Oszillator)
;
; ----------- Timing -------------------
; Messzeit 250 ms
; Prozessor-Takt 1.000.000 Hz
; TC1-Prescaler 64
; TC1-Takt 15.625 Hz
; TC1-Takte in 250 ms 3.906
.equ cTc1CmpA = 3905
;
; ----------- Messwertmittelung --------
; Aktueller Messwert / 2 plus
; Vorheriger Messwert / 4 plus
; Vorvorheriger Messwert / 8 plus
; Vorvorvorheriger Messwert / 8 =
; Aktuelle Anzeige = F
;
; ----------- Ports, Pins --------------
.equ pOut = PORTA ; Ausgabeport
.equ pDir = DDRA ; Richtungsport
.equ bIO = PORTB3 ; Pin-Out Digital
.equ bID = DDA3 ; Pin-Richtung Digital
;
; ----------- Register -----------------
; benutzt: R0, R1 fuer LCD
.def rM0L = R2 ; aktueller Messwert, LSB
.def rM0M = R3 ; dto., MSB
.def rM0H = R4 ; dto., HSB
.def rMH0 = R5 ; 5-Byte Hilfsregister
.def rMH1 = R6 ; fuer Multiplikation
.def rMH2 = R7 ; und Division
.def rMH3 = R8
.def rMH4 = R9
.def rME0 = R10 ; 5-Byte Ergebnisregister
.def rME1 = R11 ; fuer Multiplikation
.def rME2 = R12 ; und Division
.def rME3 = R13
.def rME4 = R14
; frei: R5 .. R14
.def rSreg = R15 ; Statusregister
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp = R20 ; Vielzweck, Interrupts
.def rFlag = R21 ; Flaggen
.equ bAn = 0 ; Analogvergleicher aktiv
.equ bIp = 1 ; Induktivitaetsmessung
.equ bTO = 2 ; Timeout vom Timer
.def rHilf = R22 ; Hilfsregister Dezimal
; frei: R22 .. R25
; benutzt: R27:R26 X ; fuer diverse Zwecke
; frei: R29:R28 Y
; benutzt: R31:R30 Z ; fuer LCD
;
; ----------- SRAM ---------------------
.DSEG
.ORG 0x0060
sMD: ; Digitalwerte, 4*(HSB:MSB:LSB)
.Byte 12
sMDEnd:
sMA: ; Analogwerte
.Byte 12
sMAEnd:
sMI: ; Induktivitaetswerte
.Byte 12
sMIEnd:
sDividend: ; fuer Division
.Byte 5
sDividendEnde:
;
; ---- Reset- und Int-Vektoren ---------
.CSEG
.ORG 0x0000
rjmp Start ; Reset-Vektor, 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 Routinen -----
;
; CntIsr wird nacheinander von PCINT- und
; Analogvergleicher-Interrupts ausgeloest
;
; Zaehlt die Impulse in rM0H:rM0M:rM0L
;
CntIsr: ; Impulse zaehlen
in rSreg,SREG ; Status retten
inc rM0L ; zaehlen
brne CntIsrRet
inc rM0M ; MSB erhoehen
brne CntIsrRet
inc rM0H
CntIsrRet:
out SREG,rSreg ; Status herstellen
reti
;
; TC1 Compare Match A Interrupt
; wird nach 250 ms vom Compare Match aus-
; geloest
;
; Schaltet Analogvergleicher-, PCINT- und
; TC1-Interrupts aus und setzt die bTO-Flagge.
;
Tc1Isr: ; Timeout Zaehler
ldi rimp,0
out ACSR,rimp ; Disable Int Comparator
out GIMSK,rimp ; Disable PCInt
out TIMSK1,rimp ; Diable Timer-Int
in rSreg,SREG ; Status retten
sbr rFlag,1<<bTO ; Timeout-Flagge
out SREG,rSreg ; Status herstellen
reti
;
; ----------- Hauptprogramm-Init ------
Start:
; Stapel einrichten
ldi rmp,LOW(RAMEND) ; RAM-Ende
out SPL,rmp ; in Stapelzeiger
; Port initialisieren
sbi pOut,bIO ; Eingabepin Pullup
cbi pDir,bID ; Eingabepin Input
; LCD-Port-Ausgaenge initiieren
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; Kontrollport-Ausgaenge
clr rmp ; Ausgaenge aus
out pLcdCO,rmp ; an Kontrollport
ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
out pLcdDR,rmp ; auf Richtungsregister Datenausgabeport
; LCD Init
rcall LcdInit ; starten LCD
ldi ZH,High(2*LcdTextOut) ; Z auf Text
ldi ZL,Low(2*LcdTextOut)
rcall LcdText ; Gib Text aus
ldi rmp,0x0C ; Cursor und Blink aus
rcall LcdC4Byte
; Timer Init
ldi rmp,High(cTc1CmpA) ; Compare auf
out OCR1AH,rmp ; Messzyklusdauer
ldi rmp,Low(cTc1CmpA)
out OCR1AL,rmp
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
; Analogvergleicher deaktivieren
ldi rmp,0 ; Disable Int Comparator
out ACSR,rmp
ldi rmp,(1<<ADC2D)|(1<<ADC1D) ; Input Pin disable
out DIDR0,rmp
; PCINT3 aktivieren
ldi rmp,1<<PCINT3 ; Pin change PA3
out PCMSK0,rmp ; maskieren
ldi rmp,1<<PCIE0 ; PCINT0 Interrupt
out GIMSK,rmp ; in Int-Maske
; Sleep Mode, kein Sleep wegen Analogcomparator
clr rmp ; Sleep mode disable
out MCUCR,rmp ; in Kontrollregister
; Interrupts enablen
sei ; Ints zulassen
Schleife:
; kein Schlafen wegen Analog-Comparator
sbrc rFlag,bTO ; Timeout-Flagge?
rcall Auswerten ; gesetzt, Auswerten
rjmp Schleife
;
; Routine Auswerten der Zaehlergebnisse
; wird von gesetzter bTO-Flagge ausge-
; loest
;
; Wertet Digital-, Analog- und Induktions-
; messwerte aus (Flaggenbits 0 und 1:
; 0 = Digital, 1 = Analog, 2 = Indukt.)
; Mittelt den aktuellen und die drei
; vorherigen Messwert und gibt sie als
; Frequenz in Hz (0 und 1) oder als
; Induktivitaet in uH (2) aus.
;
Auswerten:
cbr rFlag,1<<bTO ; Flagge ruecksetzen
clr rmp ; Compare Match Int aus
out TIMSK1,rmp
; Messwerte im SRAM verschieben/Dividieren
cpi rFlag,0x01 ; Induktivitaetswerte?
breq AuswertenAnalog ; Analog
brcs AuswertenDigital ; Digital
; Induktivitaet auswerten
ldi ZH,High(sMIEnd) ; Ziel
ldi ZL,Low(sMIEnd)
ldi XH,High(sMIEnd-3) ; Quelle
ldi XL,Low(sMIEnd-3)
rjmp AuswertenShift
AuswertenDigital:
ldi ZH,High(sMDEnd) ; Ziel
ldi ZL,Low(sMDEnd)
ldi XH,High(sMDEnd-3) ; Quelle
ldi XL,Low(sMDEnd-3)
rjmp AuswertenShift
AuswertenAnalog:
ldi ZH,High(sMAEnd) ; Ziel
ldi ZL,Low(sMAEnd)
ldi XH,High(sMAEnd-3) ; Quelle
ldi XL,Low(sMAEnd-3)
AuswertenShift:
; Messwerte im SRAM verschieben+dividieren
ld rmp,-X ; vorletzter nach vorvorletzter
st -Z,rmp ; kopieren
ld rmp,-X
st -Z,rmp
ld rmp,-X
st -Z,rmp
ld rmp,-X ; letzter nach vorletzter+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 ; neuester nach letzter+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 ; neuester ablegen
st -Z,rM0M
st -Z,rM0L
adiw ZL,3 ; Zeiger auf letzten
ldi rmp,4
mov R0,rmp ; R0 ist Zaehler
Auswerten1:
ld rmp,Z+ ; lese LSB
add rM0L,rmp ; Zu aktuellem Ergebnis addieren
ld rmp,Z+ ; lese MSB
adc rM0M,rmp ; addieren mit Uebertrag
ld rmp,Z+ ; lese HSB
adc rM0H,rmp ; addieren mit Uebertrag
dec R0
brne Auswerten1 ; Weitere Werte addieren
cpi rFlag,0x02 ; Induktivitaet?
brcs Auswerten2
rcall Indukt ; Induktivitaet ausgeben
rjmp Neustart
Auswerten2:
mov ZH,rFlag ; Position Digital/Analog-Ausgabe
inc ZH
ldi ZL,8
rcall LcdPos
;
; 3 Byte-Zahl in rM0H:rM0M:rM0L in dezimal
; auf der LCD ausgeben
;
; Wandelt Zahl bis 9,99 Mio. in Dezimalformat um
; und gib sie auf LCD aus, mit Unterdrueckung
; fuehrender Nullen und Dezimaltrennzeichen
;
Dezimal3Aus:
ldi ZH,High(2*Dezimal3Tab)
ldi ZL,Low(2*Dezimal3Tab)
clt ; Fuehrende Nullen
Dezimal3Aus1:
lpm XL,Z+ ; lese Dezimalzahl
lpm XH,Z+
lpm rHilf,Z+
clr rmp ; Teilerzaehler
cp XL,rmp
brne Dezimal3Aus2
cp XH,rmp
brne Dezimal3Aus2
cp rHilf,rmp
breq Dezimal3AusEnd
Dezimal3Aus2:
sub rM0L,XL ; abziehen
sbc rM0M,XH
sbc rM0H,rHilf
brcs Dezimal3Aus3 ; Ueberlauf
inc rmp
rjmp Dezimal3Aus2 ; weiter subtrahieren
Dezimal3Aus3:
add rM0L,XL ; Ruecknahme Subtraktion
adc rM0M,XH
adc rM0H,rHilf
tst rmp ; Null?
brne Dezimal3Aus4 ; Nicht Null
brts Dezimal3Aus5 ; keine Nullen unterdr.
ldi rmp,' '
rcall LcdD4Byte
ldi rmp,' '
rjmp Dezimal3AusKomma
Dezimal3Aus4:
set ; keine fuehrenden Nullen unterdr.
Dezimal3Aus5:
subi rmp,-'0'
rcall LcdD4Byte
ldi rmp,'.'
Dezimal3AusKomma:
cpi XL,Byte1(1000000)
breq Dezimal3AusKomma1
cpi XL,Byte1(1000)
breq Dezimal3AusKomma1
rjmp Dezimal3Aus1
Dezimal3AusKomma1:
rcall LcdD4Byte
rjmp Dezimal3Aus1
Dezimal3AusEnd:
ldi rmp,'0' ; letzte Ziffer
add rmp,rM0L ; addieren
rcall LcdD4Byte
rjmp Neustart
;
Dezimal3Tab:
.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
;
; Routine Indukt
; wird von Auswerten aufgerufen wenn Mess-
; zyklus Induktivitaet gemessen hat
;
; Rechnet Induktivitaet aus und gibt sie aus.
; Prueft, ob Frequenz kleiner 23 oder groe-
; sser 0x0B5050 ist und gibt Fehlermeldung
; aus.
; Multipliziert F mit sich selbst und teilt
; Konstante in Dividendtabelle durch F hoch
; 2. Gibt das 32-Bit-Ergebnis auf der LCD
; aus.
;
Indukt:
ldi ZH,3
ldi ZL,6
rcall LcdPos
tst rM0M
brne InduktN2
tst rM0H
brne InduktN2
mov rmp,rM0L ; LSB Frequenz in rmp
cpi rmp,2 ; Frequenz 0 oder 1?
brcc InduktN1
; F = 0 oder 1, Zeile leeren und Null
ldi XL,10
InduktN0:
ldi rmp,' '
rcall LcdD4Byte
dec XL
brne InduktN0
ldi rmp,'0'
rjmp LcdD4Byte
;
InduktN1: ; F groesser Null
cpi rmp,23 ; F zwischen 2 und 22?
brcc InduktN2
ldi ZH,High(2*Underflow22)
ldi ZL,Low(2*Underflow22)
rjmp LcdTextC
Underflow22:
.db "(F < 23 Hz) ",0xFE,0xFF
InduktN2:
ldi rmp,0x50
cp rM0L,rmp
cpc rM0M,rmp
ldi rmp,0x0B
cpc rM0H,rmp
brcs InduktN3
ldi ZH,High(2*Overflow)
ldi ZL,Low(2*Overflow)
rjmp LcdTextC
Overflow:
.db " (F > Max) ",0xFE,0xFF
InduktN3:
; rM0H:rM0M:rM0L mit sich selbst multipliz
mov rMH0,rM0L ; Zahl kopieren
mov rMH1,rM0M
mov rMH2,rM0H
clr rMH3
clr rMH4
clr rME0 ; Ergebnis leeren
clr rME1
clr rME2
clr rME3
clr rME4
Indukt1:
lsr rM0H ; niedrigstes Bit herausschieben
ror rM0M
ror rM0L
brcc Indukt2
add rME0,rMH0 ; zum Ergebnis addieren
adc rME1,rMH1
adc rME2,rMH2
adc rME3,rMH3
adc rME4,rMH4
Indukt2:
tst rM0L
brne Indukt3
tst rM0M
brne Indukt3
tst rM0H
breq Indukt4
Indukt3:
lsl rMH0 ; mit 2 multiplizieren
rol rMH1
rol rMH2
rol rMH3
rol rMH4
rjmp Indukt1
Indukt4:
; Division, Dividend in SRAM laden
ldi ZH,High(2*Dividendtabelle)
ldi ZL,Low(2*Dividendtabelle)
ldi XH,High(sDividend)
ldi XL,Low(sDividend)
Indukt5:
lpm rmp,Z+ ; Dividend aus Tabelle
st X+,rmp ; in SRAM
cpi XL,Low(sDividendEnde)
brcs Indukt5
; Dividend leeren
clr rMH0
clr rMH1
clr rMH2
clr rMH3
clr rMH4
; Ergebnis leeren
clr rM0L
clr rM0M
clr rM0H
clr ZL
clr ZH
; Dividend in SRAM rechts schieben
ldi rmp,8*(sDividendEnde-sDividend)+1
mov R0,rmp
Indukt6:
; Dividieren
ldi XH,High(sDividend)
ldi XL,Low(sDividend)
ldi rmp,sDividendEnde-sDividend
mov R1,rmp
clc ; Carry loeschen
Indukt7:
ld rmp,X ; Byte aus SRAM laden
rol rmp ; hoechstes Bit in Carry
st X+,rmp ; Byte in SRAM speichern
dec R1
brne Indukt7
; Carry in Hilfsregister schieben
rol rMH0
rol rMH1
rol rMH2
rol rMH3
rol rMH4
sub rMH0,rME0 ; Abziehen
sbc rMH1,rME1
sbc rMH2,rME2
sbc rMH3,rME3
sbc rMH4,rME4
brcc Indukt8 ; Abziehen korrekt
add rMH0,rME0 ; wieder addieren
adc rMH1,rME1
adc rMH2,rME2
adc rMH3,rME3
adc rMH4,rME4
clc ; Carry clr
rjmp Indukt9
Indukt8:
sec ; Carry auf Eins
Indukt9:
dec R0
breq Indukt10 ; weiter dividieren
rol rM0L ; in Ergebnis rollen
rol rM0M
rol rM0H
rol ZL
rol ZH
rjmp Indukt6
; Ergebnis runden
Indukt10:
ldi rmp,0
adc rM0L,rmp
adc rM0M,rmp
adc rM0H,rmp
adc ZL,rmp
adc ZH,rmp
mov rMH0,ZL
mov rMH1,ZH
;
; 4 Byte-Zahl in rMH0:rM0H:rM0M:rM0L in dezimal
; auf der LCD ausgeben
;
; Wandelt in dezimal um, unterdrueckt fuehrende
; Nullen und Dezimaltrennzeichen
;
Dezimal4Aus:
ldi ZH,High(2*Dezimal4Tab)
ldi ZL,Low(2*Dezimal4Tab)
clt ; Fuehrende Nullen
Dezimal4Aus1:
lpm rME0,Z+ ; Lese Dezimalzahl
lpm rME1,Z+
lpm rME2,Z+
lpm rME3,Z+
clr rmp
or rmp,rME0 ; Ende der Tabelle?
or rmp,rME1
or rmp,rME2
or rmp,rME3
breq Dezimal4AusEnd
clr rmp
Dezimal4Aus2:
sub rM0L,rME0 ; abziehen
sbc rM0M,rME1
sbc rM0H,rME2
sbc rMH0,rME3
brcs Dezimal4Aus3
inc rmp
rjmp Dezimal4Aus2
Dezimal4Aus3:
add rM0L,rME0 ; abziehen rueckgaengig
adc rM0M,rME1
adc rM0H,rME2
adc rMH0,rME3
tst rmp
brne Dezimal4Aus4
brts Dezimal4Aus5
ldi rmp,' '
ldi rmp,' '
rjmp Dezimal4Aus6
Dezimal4Aus4:
set ; Kein fuehrenden Nullen
Dezimal4Aus5:
subi rmp,-'0'
ldi rmp,'.'
Dezimal4Aus6:
cpi ZL,Low(2*Dezimal4Tab1Mio)
breq Dezimal4Aus7
cpi ZL,Low(2*Dezimal4Tab1000)
brne Dezimal4Aus1
Dezimal4Aus7:
rjmp Dezimal4Aus1
Dezimal4AusEnd:
ldi rmp,'0'
add rmp,rM0L
rcall LcdD4Byte
rjmp Neustart
;
Dividendtabelle:
.db 0x7F,0xD7,0x10,0xF4,0x75,0x00
;
Dezimal4Tab:
.dw LWRD(100000000),HWRD(100000000)
.dw LWRD(10000000),HWRD(10000000)
.dw LWRD(1000000),HWRD(1000000)
Dezimal4Tab1Mio:
.dw LWRD(100000),HWRD(100000)
.dw LWRD(10000),HWRD(10000)
.dw LWRD(1000),HWRD(1000)
Dezimal4Tab1000:
.dw LWRD(100),HWRD(100)
.dw LWRD(10),HWRD(10)
.dw 0,0
;
Neustart:
clr rM0L ; Letztes Ergebnis loeschen
clr rM0M
clr rM0H
inc rFlag ; naechster Messmodus
cpi rFlag,3
brcs NeuStart1
clr rFlag ; erster Messmodus
NeuStart1:
; rFlag=0:digital, =1:analog, =2:L
cpi rFlag,0x01
brcs NeustartDigital
breq NeustartAnalog
; Neustart Induktivitaetsmessung
ldi rmp,1<<PCINT0 ; Pin Change PA0
rjmp NeuStartPcInt
; Digital messen, PCINT3 aktivieren
NeuStartDigital:
ldi rmp,1<<PCINT3 ; Pin change PA3
NeuStartPcInt:
out PCMSK0,rmp ; maskieren
ldi rmp,1<<PCIE0 ; PCINT0 Interrupt
out GIMSK,rmp ; in Int-Maske
rjmp Neustart2
NeustartAnalog:
clr rmp
out GIMSK,rmp
ldi rmp,1<<ACIE ; Enable Int Comparator
out ACSR,rmp
Neustart2:
ldi rmp,1<<TSM ; Prescaler Sync Mode
out GTCCR,rmp
ldi rmp,(1<<TSM)|(1<<PSR10) ; Reset Presc1
out GTCCR,rmp
clr rmp
out GTCCR,rmp ; Prescaler Count Mode
out TCNT1H,rmp ; 16-Bit-Zaehler ruecksetzen
out TCNT1L,rmp
ldi rmp,1<<OCIE1A
out TIMSK1,rmp ; in Timer-Int-Maske
ret
;
; LCD Starttext
LcdTextOut:
.db "Frequenzmesser 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
.include "Lcd4Busy.inc"
;
; Ende Quellcode
;
13.5.4 Simulation der Programmausführung
Die Simulation wird wieder mit
avr_sim
vorgenommen. Alle Aufrufe von LCD-Routinen werden dazu mit ";"
auskommentiert und durch Schreiboperationen in das SRAM ersetzt, um die
Ergebnisse anschauen zu können. Das Registerpaar Y kann für diese
Schreiboperationen verwendet werden.
Nützlich ist es, die 24-Bit-Dezimalumwandlung und die
Induktivitätsberechnung zu simulieren.
13.5.4.1 Simulation der 24-Bit-Binär-Dezimal-Umwandlung
Um die Dezimalumwandlung zu erproben, wird die Dezimalzahl 1.234.567
in binärem Format in die Register rM0H:rM0M:rM0L geladen und
damit die Umwandlungsroutine aufgerufen.
Das Umwandlungsergebnis, statt auf die LCD ins SRAM geschrieben, ist
korrekt.
Die Zeit, die für die Umwandlung benötigt wird, ist deutlich
unterhalb von einer Millisekunde. Das stört die parallel ablaufende
nächste Frequenzmessung nicht im Geringsten, selbst wenn wir noch
die Schreiboperationen zur LCD (ca. 40 µs pro Zeichen) dazu
zählen.
13.5.4.2 Simulation der Induktiviätsberechnung
Als Erstes simulieren wir die Induktivitätsberechnung mit einer
Frequenz von 1.000 Hz. Dieser Wert wird in die Register R4:R3:R2
geschrieben.
Das Resultat ist mit 506,606 mH korrekt (wie die obige
Beispielrechnung ausweist).
3,6 ms sind vergangen. Die Division dauert halt etwas länger
als die Multiplikation, insbsondere wenn man die zu dividierende
Zahl mühsam Bit für Bit aus dem SRAM in das Divisionsregister
schieben muss.
Das wäre das Ergebnis, wenn der LC-Oszillator 10.000 Hz
produziert hätte: 5,6 mH.
Simulation von 50 Hz Oszillatorfrequenz resultiert in einer
Induktivität von 202 H, was eine ziemlich große
Spule wäre.
Der Zeitbedarf bei der Berechnung von 50 Hz ist ein bisschen
länger, dauert aber nicht zu lang.
Simulation hilft dabei, komplizierte Berechnungen zu entwickeln und
zu überprüfen, ob sie unter allen realistischen Bedingungen
korrekt rechnen. Damit können Unterprogramme mit einer klar
definierten Aufgabenstellung mit Eingangszahlen (in Registern oder
im SRAM) gefüttert werden und es kann ziemlich einfach
gecheckt werden, ob sie zu korrekten Resultaten kommen.
13.5.5 Messbeispiele
Bei den beiden folgenden Messungen waren der Digital- und der Analog-Eingang unbeschaltet, die
dargestellten Messwerte in Zeile 2 und 3 der LCD sind Einstrahlungen aus den Messobjekten und
kapazitive Einstreuungen vom Aufbau.
13.5.5.1 Große Spule
Das ist die Messung mit einer relativ großen Spule.
Die Messung stimmt relativ gut mit der auf andere Weise ermittelten Induktivität
überein. Wieder ist die angezeigte Induktivität etwas zu groß, die gemessene
Frequenz also etwas zu niedrig.
13.5.5.2 Lautsprecherspule
Auch die Lautsprecherspule, die wir in einem anderen Experiment verwendet haben, hat eine
Induktivität:
Die Induktivität der Lautsprecherspule ist mit knapp 800 µH doch recht
bescheiden.
©2016-2018 by http://www.gsc-elektronic.net