Pfad:AVR-DE => Anwendungen => Signalgenerator M16 => Software
Signalgenerator AVR-Anwendungen

Sinus/Dreieck/Rechteck/Sägezahn-Signalgenerator mit ATmega16
Software für den Signalgenerator
Logo

Signalgenerator ATmega16 Software-Beschreibung

Auf dieser Seite wird erläutert, wie die Software aufgebaut ist und wie sie funktioniert.

Im Überblick funktioniert die Software so: Nachfolgend werden alle Software-Bestandteile im Detail beschrieben.

  1. Erzeugung der Kurvenformen mit dem TC1-CTC-Interrupt
  2. Frequenzeinstellung
  3. Die CTC-Berechnung
  4. Die RC-Filter-Einstellungen
  5. Die Transfer-Zusammenstellung zum Umschalten
  6. Die LCD-Anzeige
  7. Die Kurvenformerzeugung
  8. Simulation des Programms, Debug
  9. Listing der Quellcodes

1. Erzeugung der Kurvenformen mit dem TC1-CTC-Interrupt

1.1 Anforderung bei der Erzeugung der Kurvenform

Ablauf beim CTC-Int Request Das Besondere an der Software, und die zentrale Anforderung dabei ist, dass über den gesamten Frequenzbereich von 2 Hz bis 20 kHz hinweg der DA-Wandler mit dem R/2R-Netzwerk regelmäßig und zeitlich exakt mit den Tabellenwerten der betreffenden Kurvenform versorgt wird. Das wird erreicht, indem der 16-Bit-Counter/Timer TC1 im CTC-Modus (Clear Timer on Compare) betrieben wird. Der Compare-Wert bestimmt dabei über den Zeitraum, der bis zur nächsten Byte-Ausgabe an den R/2R-Port vergeht, und damit über die erzeugte Frequenz.

1.2 Verfahren der Erzeugung der Kurvenform

Dazu wurde folgendes Verfahren ausgewählt.
  1. Eine Tabelle, die im Flash-Speicher des ATmega16 abgelegt ist, enthält für jede der vier Kurvenformen jeweils 256 Einzelwerte, die die Kurvenform und deren Amplitude (Aussteuerung) angeben. Die 256 Einzelwerte bilden den Kurvenzug in einem Durchgang ab.
  2. Wechselt die Kurvenform, dann wird die jeweilige neue Flash-Tabelle in einen 256 Byte umfassenden Speicher im SRAM kopiert und von dort aus schrittweise mit dem CTC-Interrupt an den Digital-Analog-Wandler ausgegeben.
  3. Liegt die gewählte Frequenzeinstellung im Bereich 2 kHz - 20 kHz und die mit dem Potentiometer eingestellte Frequenz bei 2.500 Hz und darüber, dann wird die Frequenz dadurch verdoppelt, indem in den 256 Bytes der auszugebenden SRAM-Tabelle zwei Kurvenzüge kopiert werden, d. h. es wird nur jeder zweite Tabelleneintrag der Flash-Tabelle in die SRAM-Tabelle übernommen und bei den restlichen 128 Werten mit der Flash-Tabelle neu begonnen.
  4. Entsprechend wird ab 5.000 Hz eine vervierfachte Kopie der Flash-Tabelle abgelegt und nur jeder vierte Wert aus der Flash-Tabelle verwendet. Analog erfolgt ab 10.000 Hz eine Verachtfachung des Wellenzugs.

1.3 CTC-Compare-A-Werte

Bei den beiden Extremen, 2 Hz und 20 kHz, ergeben sich für den CTC-Wert von TC1 folgende Beziehungen. In beiden Fällen wird mit einem TC1-Vorteiler von 1 gearbeitet, so dass TC1 mit 16 MHz getaktet wird. Bei 2 Hz wird in den 256 Einzelstufen ein Kurvenzug ausgegeben, bei 20 kHz in den 256 Stufen aber acht Kurvenzüge. Die CTC-Vergleicherwerte für TC1 ergeben sich dann zu
FrequenzCTC-Compare-A-BerechnungCTC-WertAnmerkung
1 Hz16.000.000 Hz / 256 - 162.499beim Einschalten
2 Hz16.000.000 Hz / 256 / 2 Hz - 131.249niedrigste Frequenz
20 Hz16.000.000 Hz / 256 / 20 Hz - 13.124Beginn zweiter Bereich
200 Hz16.000.000 Hz / 256 / 200 Hz - 1311Beginn dritter Bereich
2 kHz16.000.000 Hz / 256 / 2000 Hz - 130Beginn vierter Bereich
2,5 kHz16.000.000 Hz / 256 * 2 / 2500 Hz - 149Beginn Verdoppelung
5 kHz16.000.000 Hz / 256 * 4 / 5000 Hz - 149Beginn Vervierfachung
10 kHz16.000.000 Hz / 256 * 8 / 10000 Hz - 149Beginn Verachtfachung
20 kHz16.000.000 Hz / 256 * 8 / 20000 Hz - 124Ende Einstellbereich

1.4 Anforderung bei der Erzeugung der Kurvenform mit dem CTC-Interrupt

Bei 16 MHz Takt und der maximal zu erzeugenden Frequenz von 20 kHz tritt daher alle (24 + 1) Takte ein Interrupt auf. Maximal darf daher die CTC-Interrupt-Routine bis zu 24 Takte benötigen, von der Auslösung der Interrupt-Anforderung durch den Timer bis zu und einschließlich der Rückkehr vom Interrupt. Würde sie länger benötigen, dann würde der CTC-Interrupt die Ausführung jeden anderen Codes blockieren und käme außer Tritt (unbehandelte Mehrfachanforderungen). Diese zentrale Anforderung ist auf jeden Fall von der Software einzuhalten, wenn sie über den gesamten Frequenzbereich bis 20 kHz korrekt funktionieren soll.

Wechsel der Kurvenformen treten daher immer dann auf, wenn In allen diesen Fällen muss die Ausgabetabelle im SRAM neu geschrieben werden.

Damit es in allen diesen Fällen des Neuschreibens nicht zu abrupten Pegeländerungen kommt (z. B. beim Umschalten von Sinus auf Rechteck oder bei den genannten Änderungen der Frequenzeinstellung), Das erstgenannte Kriterium wird dadurch erfüllt indem in der CTC-Interrupt-Routine eine Flagge bStart gesetzt wird. bStart signalisiert dabei den Nulldurchgang des Signals. Falls die Auswertung der AD-Wandlerwerte vom Potentiometer oder der Schaltereinstellungen ergab, dass Änderungen erfolgt sind, werden dann Das zweite Kriterium, dass alle Kurvenzüge mit einer Null beginnen sollen, hat nur Konsequenzen bei der Sinustabelle: sie wurde um 270° phasenverschoben in die Tabelle geschrieben, damit sie bei Null losgeht.

1.5 Kurvenform-Tabellen

Die vier Flash-Tabellen für die Kurvenformen sind in der Include-Datei Tabellen.inc abgelegt. Die Tabelle Sine: erzeugt den Sinus, die Tabelle Triangle: die Dreieckspannung, die Tabelle Sawtooth: den Sägezahn und Rectangle: die Rechteckspannung.

Alle Tabellen sind so angelegt, dass sie nach der Analogisierung im R/2R-Netzwerk die nachfolgende Elektronik (RC-Filter-Schalter, Operationsverstärker) mit maximal 3 V Amplitude aussteuern (dezimal 153 von 255 bei maximal möglicher Aussteuerung), damit insbesondere der CMOS-Operationsverstärker-Eingang nicht übersteuert wird. Sie wurden mit einem Open-Office Spreadsheet generiert und können daher für andere Kurvenformen abgewandelt werden. Der Export der Tabellenwerte in die Assembler-Include-Datei erfolgt durch Markieren der Tabelle in Spalte H, kopieren mit Strg-C und Pasten in die Include-Datei mit Strg-V.

Aussteuerung der DAC bei den vier Auflösungen

1.6 CTC-Interrupt-Service-Routine

Die Zeitbeziehungen in der Interrupt-Service-Routine (Anzahl benötigte Takte) sind also exakt zu erfassen und dürfen 24 Takte nicht überschreiten. Das ergibt folgende Zeitbeziehungen:

; CTC-compareA-Interrupt: Ausgabe naechster DAC-Wert
TC1CAIsr: ; 6 Taktzyklen fuer Int und Vektor-Jump
	in rSreg,SREG ; Status retten, +1 = 7
        ld rimp,Y+ ; Naechster Tabellenwert, +2 = 9
	out pDacOut,rimp ; in DAC schreiben, +1 = 10
        cpi YL,Low(sR2REnd) ; Ende Puffer? +1 = 11
        brne TC1CAIsrRet ; +1 = 12, +2 = 13
	ldi YH,High(sR2R) ; Neustart ; +1 = 13
	sbr rFlag,1<<bStart ; +1 = 14
TC1CAIsrRet: ; 13/14 Takte
	out SREG,rSreg ; Status wieder herstellen, +1 = 14/15
	reti ; + 4 = 18/19

In allen Fällen bleiben bei der höchsten Frequenz zwischen 6 und 7 Takte pro CTC-Interrupt für andere Aufgaben übrig. Das ist bei 20 kHz nicht gerade üppig, reicht aber für die anstehenden Aufgaben aus, insbesondere für das zügige Schreiben der Ausgabetabelle in das SRAM.



Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

2. Frequenzeinstellung

Damit die Software die Frequenz einstellen kann, ist regelmäßig der Potentiometerstand am Eingang ADC0 zu messen und in die korrekten CTC-Werte für den TC1-CTC-Interrupt umzurechnen.

2.1 Die Interrupt-Service-Routine der ADC-Messungen

Damit durch die ADC-Messungen keine relevante Störung der DAC-Ausgabe erfolgt, muss die ADC-Interrupt-Routine möglichst ganz kurz gehalten werden.

Das gelingt nur, wenn die ADC-Routine mit den folgenden Instruktionen auskommt:

; ADC Interrupt-Service-Routine
AdcInt: ; 6 Takte fuer Int und RJMP
  set ; Setzen der T-Flagge im SREG, +1 Takt = 7
  reti ; +4 Takte = 11

Die dadurch verursachten 11 Takte Verzögerung machen sich in einer zeitlichen Verschiebung einer einzigen DAC-Ausgabe um 0,69 Mikrosekunden bemerkbar. Bei 2 Hz entspricht das einer zeitlichen Verschiebung einer einzigen Ausgabe um 0,035%, bei 20 kHz um 44%. Da die 11 Takte Verzögerung bei 20 kHz schon nach zwei Einzelausgaben wieder aufgeholt sind (zwischen zwei CTC-Int bleiben in der Regel 7 Takte übrig), handelt es sich nicht um einen merklichen Einfluss. Außerdem spielen diese Störungen nur beim Dreieck und beim Sägezahn eine Rolle:

2.2 Taktung des ADC

Um die Störungen durch ADC-Interrupts weiter zu minimieren, werden nicht mehr als unbedingt nötige ADC-Wandlungen durchgeführt. Das wird erreicht, indem zum Anstoßen des Messvorganges der Timer TC0 verwendet wird: Um den Messvorgang anzustoßen, wird Alle weiteren Wandlungen erfolgen dann über den Timer, der damit auch über die weitere Auswertung zeitlich bestimmend ist.

2.3 Weitere Auswertung der ADC-Ergebnisse

Da pro ADC-Messung nur ca. 15,6 ms vergehen, werden sechzehn dieser Messergebnisse des ADC außerhalb der Interrupt-Service-Routine aufsummiert. Liegen acht Ergebnisse vor, werden diese wie im nächsten Kapitel beschrieben ausgewertet.

Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

3. Die Routine zur CTC-Berechnung

Die Berechnung erfolgt in zwei Stufen:
  1. der Berechnung der Soll-Frequenz aus dem Potentiometer-Mittelwert,
  2. der Berechnung des CTC-Wertes aus der Soll-Frequenz.

3.1 Berechnung der Soll-Frequenz aus der Potentiometerstellung

Damit die erzeugte Frequenz linear mit der Potentiometerstellung ansteigt, wird aus der Stellung des Potentiometers die Sollfrequenz errechnet. In den Grobbereichen zwischen 2 und 200 Hz wird diese als das 65.536-fache der Sollfrequenz berechnet, ab 200 Hz als das 256-fache. Dazu werden mit den folgenden Parametern a (Steigung) und b (Ordinatenabschnitt) in f = Multiplikator * (a * Poti + b) folgende Berechnungen ausgefürt:

EinstellbereichMultiplikatorabMin(Poti=0)Max(Poti=255)
DezimalHexDezimalHex
2 - 20 Hz65.536(20 - 2) / 2552131.07202.00.001.310.72014.00.00
20 - 200 Hz65.536(200 - 20) / 255201.310.72014.00.0013.107.200C8.00.00
200 - 2.000 Hz256(2.000 - 200) / 25520051.20000.C8.00512.00007.D0.00
2.000 - 20.000 Hz256(20.000 - 2.000) / 2552.000512.00007.D0.005.120.0004E.20.00


Die Berechnung erfolgt durch Multiplikation des 16-Bitwertes 65.536 bzw. 256 * a mit dem 8-bittigen ADC-Durchschnittswert zum 24-Bit-Ergebnis. Der Addierer 65.536 bzw. 256 * b wird vor der Multiplikation in die Ergebnisregister geladen und zusammen mit den einzelnen Multiplikationswerten aufaddiert (das spart das anschließende Hinzu-Addieren).

Das nachfolgende Schema zeigt den Ablauf der Berechnung der Sollfrequnz in den betreffenden Registern.

Berechnung der Sollfrequenz

Das Register rN2 ist zu Beginn der Berechnungen immer Null, es wird bei der 8-Bit-Multiplikation dazu verwendet, um den Multiplikator in jedem Schritt mit Zwei zu multiplizieren und, falls das aus rmp herausgeschobene Bit eine Eins ist, zum Ergebnis zu addieren.

Das Ergebnis in rN5:rN4:rN3 ist das 65.536- bzw. 256-fache der Sollfrequenz. Für die nachfolgende CTC-Berechnung wird rN3 zum Runden verwendet und mit dem 256-fachen bzw. dem 1-fachen Frequenzwert in rN5:rN4 weitergerechnet.

3.2 Berechnung der CTC-Werte aus der Sollfrequenz

Die Berechnung der CTC-Werte aus den Sollfrequenzen ist mit
nCTC = Takt / 256 / Sollfrequenz - 1
relativ einfach (Division 24 Bit / 16-Bit = 16-Bit. Da in den Bereichen von 2 bis 200 Hz das 256-fache der Sollfrequenz vorliegt, muss es für diese Frequenzen heißen:
nCTC = Takt / (256*Sollfrequenz) - 1
Bei der Berechnung der CTC-Werte aus den Sollfrequenzen spielen ab 2.500 Hz aber zusätzlich noch die Anzahl der in der DAC-Tabelle gespeicherten Kurvenzüge (2, 4 oder 8) eine Rolle, so dass sich folgende Beziehungen ergeben:
Fein-
bereich
#
Frequenzbereich
(Hz)
DAC-
Vielfache
Soll-Frequenz
Formel
Dividend
Formel
Dividend
Dezimal
Dividend
Hex
CTCmax
bei fmin
CTCmin
bei fmax
02 - 201(65536*f)/256(256*clock)/25616.000.000F4240031.2493.124
120 - 2003.124312
2200 - 2.000(256*f)/256clock/25662.50000F42431230
32.000 - 2.5003024
42.500 - 5.0002clock/128125.00001E8484924
55.000 - 10.0004clock/64250.00003D090
610.000 - 20.0008clock/32500.00007A120
Der Dividend kommt mit vier Bytes (=32 Bits) aus. Die zu dividierenden Frequenzen benötigen zwei Bytes (max. 200 * 256 = 51.200). Das Ergebnis passt in zwei Bytes.

Es ist noch zu beachten, dass das CTC-Ergebnis aus dem Dividenden und dem Divisor um Eins zu vermindern ist, da im CTC-Betrieb ein Takt mehr ausgeführt wird (das Rücksetzen des Zählers erfolgt erst nach dem Erreichen des Compare-A-Wertes und dem Eintreffen des nächsten Zählerimpulses, synchronisiert mit dem Zählertakt) und nicht bereits mit dem Eintreten der Gleichheit.

Als Besonderheit der Division wurde nicht mit dem Linksschieben des Dividenden begonnen, damit auch der Fall, dass der Divisor bereits kleiner ist als die beiden oberen Bytes des Dividenden. Dafür werden 17 und nicht 16 Divisionsschritte durchgeführt.

Das folgende Bild zeigt die Registerverwendung beim Teilen der Taktfrequenz durch die Sollfrequenz.

CTC-Berechnung

3.3 Zeitbedarf der Berechnungen

Die in der folgenden Tabelle angegebenen Zeiten wurden mit avr_sim gemessen und beziehen sich auf den Betrieb ohne die Frequenzausgabe durch TC1.
FeinbereichFrequenzZeitbedarf µsFrequenzZeitbedarf µs
0226,71932,4
12026,519931,9
220026,01.99932,4
32.00026,42.49928,3
42.50028,34.99930,2
55.00030,29.99931,0
610.00031,019.99932,0
Alle Zeiten liegen um die 30 µs, was in Anbetracht einer 8*24-Bit-Multiplikation und einer 32/24-Bit-Division relativ kurz ist. Bei der schnellsten Frequenzausgabe-Variante verlängert sich dies um das 25 / 6 -fache (19 Takte Ausgabe, 6 Takte zur freien Verfügung), also auf ca. 125 µs, da zwischendurch immer der TC1-Interrupt zuschlägt.

Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

4 Die RC-Filter-Einstellungen

Die Hardware ermöglicht das Zuschalten von Kondensatoren, um die Spannungsstufen des DAC-Rechtecksignals auszugleichen. Das macht aber nur beim Erzeugen von Sinussignalen Sinn, bei allen anderen Kurvenformen nicht.

Im Einzelnen können mit dem digitalen Schalter vier Kondensatoren von 470 pF, 1 nF, 10 nF und 100 nF einzeln oder parallel zugeschaltet werden. Für die Auswahl der Kondensatoren kann die hier beschriebene Software verwendet werden.

In der Tabelle sind die einzustellenden Kondensatoren in den verschiedenen Frequenzbereichen zusammengestellt.

FeinbereichFrequenzbereichPotentiometerKondensatorKapazitätSchalter
vonbisvonbis100nF10nF1nFn47FnFPB3:PB0
02200255XXXX111n471111
1202000255 XXX11n470111
2200500042 X  10n0100
5002.00043255  XX1n470011
32.0002.5000255  X 1n0010
4..62.50020.0000255   Xn470001
Die Eigenschaften der verschiedenen Filter sind im Folgenden näher beschrieben.

4.1 Frequenzbereich 2 bis 20 Hz

Im Frequenzbereich 2 bis 20 Hz werden alle vier Kondensatoren parallel eingeschaltet. Dadurch ergeben sich bei 2 Hz folgende Kurven:

Filter bei 2 Hz

Wie zu erkennen ist, ergeben sich dadurch saubere Sinussignale.

Bei 20 Hz weist das so geschaltete Filter bereits eine geringe Abschwächung der Amplitude auf:

Filter bei 20 Hz

Die Abschwächung ist aber gering und hinnehmbar, so dass eine weitere Differenzierung der zugeschalteten Kondensatoren nicht nötig ist.

Über den gesamten Frequenzbereich ergibt sich der hier gezeigte Verlauf.

Filterkurve 2 bis 20 Hz Die Filterkurve mit n(RC)=2 verläuft über den gesamten Frequenzbereich oberhalb des 0,9-fachen.

Oberwellenunterdrückung Bei 2 Hz und einem 8-Bit-DAC beträgt die digitale Frequenz 256 Hz. Die dritte und weitere ungeradzahligen Oberwellen, aus der sich das Rechtecksignal zusammensetzt, liegt daher bei über 750 Hz. Bei diesen Frequenzen weist das RC-Filter mit R=10k und C=111n47 die nebenstehenden Eigenschaften auf. Die Unterdrückung dieser Oberwellen ist sehr hoch (Amplitude kleiner 0,04).

Die Filterkurven wurden mit dem Software-Tool rc_sweep erstellt. Es ist Freeware und in der Windows-64-Version hier und als Lazarus-Win-Source-Code hier verfügbar. Achtung! Bei niedrigen Frequenzen, kleinen Widerständen und kleinen Kondensatoren kann die Ausführung länger dauern.

4.2 Frequenzbereich 20 bis 200 Hz

20 Hz mit 11,47 nF In diesem Frequenzbereich wird mit dem Kondensator 11n47 (10n+1n+0,47n) gearbeitet. So sieht die Filterkurve bei der niedrigsten Frequenz, 20 Hz, aus. Der Filtereffekt ist bei dieser niedrigen Frequenz noch recht bescheiden und wird am oberen Ende des Bereichs noch deutlich besser.

200 Hz mit 11,47 nF Und so sieht es bei 200 Hz aus.

Filterkurve 20 bis 200 Hz Die Abschwächung im gesamten Frequenzbereich liegt bei weniger als 10%.

4.3 Frequenzbereich 200 bis 500 Hz

Filterkurve 200 bis 2500 Hz Bei diesem Frequenzbereich kommt noch der 10 :nF-Kondensator zum Einsatz. Die Abschwächung bei 500 Hz hält sich in Grenzen. Der 1 nF-Kondensator hätte bei 200 Hz eine zu geringe Filterwirkung:

Filter mit 1n bei 200 Hz

4.4 Frequenzbereich 500 bis 2.000 Hz

In diesem Bereich werden die beiden Kondensatoren 1 nF und n47F eingeschaltet. Die Filterkurve sieht so aus.

Filterkurve 500 bis 2000 Hz Im untersten Frequenzbereich bei 500 Hz ergibt sich folgende Kurve:

Filter mit 1n47 bei 500 Hz

4.5 Frequenzbereich 2.000 bis 2.500 Hz

Filterkurve 2.000 bis 2.500 Hz Hier wird nur der 1 nF-Kondensator eingeschaltet. Er sorgt für eine gute Filterwirkung in diesem Bereich.

4.6 Frequenzbereich 2.500 bis 20.000 Hz

Filterkurve 2.500 bis 20.000 Hz In diesem gesamten Bereich kommt nur der 470 pF-Kondensator zum Einsatz. Er bringt über den gesamten Bereich gute Filterwirkung. Bei zweistufigem Betrieb wie in der vorliegenden Schaltung ist zwar die Abschwächung auf die halbe Ausgangsspannung schon merklich, aber für einen weiteren kleineren Kondensator von 100 pF steht kein weiterer Analogschalter zur Verfügung.

An der untersten Frequenzbereichsgrenze von 2.500 Hz und beim 7-Bit-DAC sieht die Kurve so aus:

Filter mit 470pF bei 2.500 Hz

Die Filterung bei dieser Frequenz zeigt einen sauberen Sinus ohne Oberwellen. Bei der höchsten Frequenz ist die Abschwächung noch hinnehmbar:

Filter mit 470pF bei 20.000 Hz


Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

5. Die Transfer-Zusammenstellung

5.1 Umschaltung bei Frequenzänderung und Kurvenformwechsel

Beim Wechsel des Frequenzbereiches, der Potistellung oder der Schalter für die Kurvenform ist es nötig, die Zählerhardware mit den neuen Werten zu versorgen. Das erfolgt erst dann, wenn die ausgegebene Kurve ihren Nulldurchgang hat und das Bit bStart im Register rFlag durch die TC1-CTC Interruptserviceroutine gesetzt ist. Bis zum Erreichen dieses Zustands sind die berechneten Werte in einem SRAM-Bereich abgelegt und werden noch nicht direkt in die Hardware geschrieben.

Es sind zwei Fälle zu unterscheiden:
  1. Ändert sich bei der Neuberechnung die Kurvenart (Rechteck, Sägezahn, Dreieck oder Sinus) oder die Anzahl der Durchgänge einer Kurve (1-fach, 2-fach, 4-fach oder 8-fach), dann müssen die SRAM-Inhalte aus der Flash-Tabelle neu erzeugt werden.
  2. Ist das nicht der Fall, dann ist nur der CTC-Wert neu einzustellen.

5.2 Wechsel der Kurvenform

Der erste Fall wird nach der Neuberechnung der Sollwerte durch eine gesetzte bSetR2R-Flagge signalisiert. Damit die Übernahme der neuen Werte auch in den Fällen mit kurzen CTC-Werten schneller erfolgt als die Ausgabe über das R/2R-Netzwerk, muss dies sofort nach dem Nulldurchgang beginnen und mit hoher Geschwindigkeit erfolgen. Ist das Schreiben der neuen Tabellenwerte langsamer als deren Ausgabe über den CTC-Interrupt, dann wird die neue Kurve um einen Kurvenduchgang verzögert ausgegeben. Ist das nicht der Fall, wird sofort die neue Kurvenform ausgegeben.

Um zu ermitteln, bei welchen Frequenzen diese Umschaltung sofort oder erst mit Verzögerung erfolgt, ist die Schreibroutine im Folgenden nach der Anzahl Takte analysiert:

Convert2Sram1: ; 12/22 Takte Instruktionen pro Byte
  lpm rmp,Z ; Lese Wert aus Sinustabelle, +2 = 2
  st X+,rmp ; Kopiere in SRAM-Puffer, +2 = 4
  add ZL,rAdd ; Addieren Tabellenwert, +1 = 5
  ldi rmp,0 ; Ueberlauf addieren ; +1 = 6
  adc ZH,rmp ; zu MSB ; +1 = 7
  inc rCnt ; Anzahl Bytes zaehlen, +1 = 8
  breq Convert2SramEnd ; Null, fertig, +1 = 9 (Sprung irrelevant)
  mov rmp,rCnt ; Kopiere Byte-Zaehler, +1 = 10 
  and rmp,rAnd ; Isoliere Bits, +1 = 11
  brne Convert2Sram1 ; +2 = 13, +1 = 12
  pop ZL ; Anfangsadresse setzen, LSB, +2 = 14
  pop ZH ; dto., MSB, +2 = 16
  push ZH ; Z wieder sichern, MSB, +2 = 18
  push ZL ; dto., LSB, +2 = 20
  rjmp Convert2Sram1 ; +2 = 22

Im Falle der einfachen Kurvenform erfolgt kein Durchlauf des 22-Takte-Teils und pro Byte werden 12 Takte benötigt. Sind zwei Kurventeile zu übertragen erhöht sich diese Taktzahl auf (12*255+22)/256 = 12,04 Takte pro Byte. Bei vier Kurven sind es 12,16 und bei acht 12,27 Takte. Der Übergang erfolgt also dann, wenn der CTC-Durchgang (12+18) = 30 Takte dauert, was im Frequenzbereich von 10 bis 20 kHz bei etwa 16,7 kHz läge. Da diese Frequenz aber wesentlich höher liegt als diejenige, bei der ein Neuschreiben der Tabelle erfolgen muss (9,999 nach 10 kHz), tritt dieser Fall praktisch nicht ein. Daher ist auch der Fall ausgeschlossen, dass Einzelwerte aus der neuen Tabelle sich mit Einzelwerten der alten Tabelle mischen und dadurch Störstellen entstehen könnten: entweder ist beim Neuschreiben der Tabelle die Kopierroutine langsamer als die Ausgaberoutine (z. B. beim Übergang von 9,999 kHz auf 10 kHz, alter CTC-Wert = 24) oder wesentlich schneller (beim umgekehrten Übergang, alter CTC-Wert = 49).

Nur beim Umschalten über einen Schalterwechsel kann dieser Fall bei der oben genannten Frequenz eintreten, dauert dann aber nur für einen einzigen Durchlauf lang an (1, 2, 4 oder 8 Kurvendurchgänge lang).

5.3 Wechsel der Frequenz

Der zweite Fall, dass nur der CTC-Wert sich ändert, kommt relativ häufiger vor, da der Poti-Wert um +/- 1 Bit klappern kann. In dem Fall, dass sich dadurch auch der CTC-Wert um eine Einheit nach unten ändern kann (insbesondere im Bereich zwischen 2 und 20 Hz) muss vermieden werden, dass der CTC-Wert um Eins kleiner wird und dadurch ein Compare-Match verpasst werden kann. Dies wäre der Fall, wenn der Timer in einem der beiden CTC-Modes betrieben würde (CTC durch Compare Match A im Timer Mode 4 oder durch ICR-Compare-Match im Timer Mode 12), da der neu geschriebene Compare-Wert sofort angewendet wird. Das könnte dazu führen, dass der Timer erst 65.535 Takte später wieder den Compare-Match erreicht, was bei dem hier verwendeten Takt vier Millisekunden lang dauert und bei hohen Frequenzen eine halbe Ewigkeit lang den Ablauf verzögern würde.

Um das zu vermeiden wird hier von einem Feature der AVR-Timer Gebrauch gemacht: der neu geschriebene Compare-Wert wird in den PMW-Modi erst dann angewendet, wenn der Timer Null erreicht (Update of OCR on Bottom). Dadurch kann es nicht zum Verpassen eines Compare-Match kommen, wenn häufig neue Werte in den Compare A geschrieben werden. Der Timer TC1 wird daher im Fast-PWM-Modus betrieben (Mode 15), obwohl gar keine anderen PWM-Features benötigt werden.

5.4 Datenzusammenstellung im SRAM

Um feststellen zu können, ob erforderlich ist, enthält die Software einen umfangreichen Soll/Ist-Speicher. In diesem Speicherbereich im SRAM sind beide Bereiche in identischer Abfolge gehalten, damit der Speicherbereich, in dem die Parameter der Neueinstellung zusammengestellt werden, nach der erfolgten Umschaltung über die alten Werte kopiert werden können und bei der nächsten Auswertung aller Einstellungen eindeutig festgestellt werden kann, welche der Parameter zu ändern sind.

Die Datenzusammenstellung ist im SRAM wie folgt gegliedert:

; Ablagepeicher fuer die aktuellen Einstellwerte
sR2RCurrL: ; 0x0060
  .Byte 1 ; Aktuelle R2R-Tabellen-Adresse, LSB
sR2RCurrH: ; 0x0061
  .Byte 1 ; dto., MSB
sR2RCurrAdd: ; 0x0062
  .Byte 1 ; Aktueller R2R-Tabellen-Addierer
sR2RCurrAnd: ; 0x0063
  .Byte 1 ; Aktueller R2R-Tabellen-Wiederholwert
sCmpACurrH: ; 0x0064
  .Byte 1 ; Aktueller TC1-CTC-Wert, MSB zuerst
sCmpACurrL: ; 0x0065
  .Byte 1 ; dto., LSB danach
sCondCurr: ; 0x0066
  .Byte 1 ; Aktueller Kondensator fuer RC-Netzwerk (0..15)
sFineTuneCurr: ; 0x0067
  .Byte 1 ; Aktueller Feinbereich Frequenzeinstellung (0..6)
sCourseTuneCurr: ; 0x0068
  .Byte 1 ; Aktueller Grobbbereich Frequenzeinstellung (0..3)
sPotCurr: ; 0x0069
  .Byte 1 ; Aktueller Potistand, (0..255)
sCurveCurr: ; 0x006A
  .Byte 1 ; Aktueller Modus, (0..3)
sCurrEnd: ; Ende aktueller Datenbereich

Der Bereich der Neueinstellung ist identisch gegliedert und benannt, nur fehlt in den Labeln der Teil "Curr".

5.5 Übernahme der neuen Kurvenform

Sind beide Flaggen bStart und bSetR2R gesetzt, dann erfolgt die Übernahme der neuen Werte folgendermaßen.

Zunächst wird die neu einzustellende Kurvenform geschrieben. Dazu wird
  1. das Registerpaar ZH:ZL auf die Adresse der ausgewählten Kurve in sR2RH:sR2RL gesetzt,
  2. das Register rAdd aus dem Wert in sR2RAdd geladen, dieses Register gibt an, um welchen Betrag die Flash-Adresse bei jedem geschriebenen zu erhöhen ist (1, 2, 4 oder 8), um die richtige Anzahl an Kurven in den Speicherbereich zu kopieren,
  3. das Register rAnd aus dem Wert in sR2RAnd geladen, dieses Register gibt an, bei welcher Anzahl Bytes wieder am Tabellenanfang der Flashtabelle begonnen werden muss (0 bei einem Kurvendurchgang mit 256 Werten, 0x7F bei zwei Kurven pro Durchgang, 0x7F bei dreien und 0x3F bei vieren).
Die Routine Convert2Sram: erledigt daraus das Kopieren der Werte aus der Flashtabelle in den Speicherbereich ab sR2R:. Dabei wird so vorgegangen:
  1. Aus der Flash-Tabelle, auf die Z zeigt, wird ein Byte ausgelesen und in den SRAM-Speicherbereich geschrieben. So sieht die Flash-Tabelle beim Sinus auszugsweise aus:

Sine: ; Sinustabelle 8 bits, 3V max
  .db 0,0,0,0,0,1,1,1 ; n=0 - 7
  .db 1,2,2,3,3,4,4,5 ; n=8 - 15
  ; ... bis n = 255

  1. Zu der Flash-Adresse wird der Wert des Registers rAdd addiert. Soll eine 1:1-Kopie der Flash-Tabelle angelegt werden (eine Kurve pro Durchgang) wird Eins addiert, sollen zur Frequenzerhöhung mehrere Kurven pro Durchgang erzeugt werden (2, 4 oder 8), dann wird nur jeder zweite, vierte oder achte Wert der Flash-Tabelle kopiert.
  2. Ein Bytezähler wird erhöht. Dieser wird je nach Anzahl der Kurven pro Durchgang mit 0xFF, 0x7F, 0x3F oder mit 0x1F ver-UND-et. Ergibt das Null, wird mit der Flash-Tabelle neu begonnen.
  3. Sind alle 256 Werte der Tabelle geschrieben, wird die Routine beendet.
Das ergibt bei acht Kurvenzügen pro Durchgang im SRAM dann folgende Belegung:

SRAM-Belegung mit acht Sinuskurven Beginnend ab Adresse sR2R: liegen acht entsprechend verkürzte Kurvenzüge mit jeweils 32 Bytes Länge im SRAM, alle sind identisch.

Nach der Änderung der Kurvenform wird die Übernahme mit der Einstellung der anderen Werte und mit der Aktualisierung der LCD-Ausgabe fortgesetzt.

5.6 Übernahme der neuen CTC-Werte

Nach der Bearbeitung nach 5.5 wird noch geprüft, ob die Flagge bSetCtc gesetzt ist. Falls ja, wird der unter sCmpAH:sCmpAL: abgelegte neue CTC-Wert in das Vergleichsregister OCR1A des TC1 geschrieben. Danach werden die Kondensatoren gemäß des übergebenen Wertes in sCond: eingestellt.

Man beachte, dass die Änderung des CTC-Wertes sich erst nach Beendigung des aktuell laufenden CTC-Zyklus auswirkt (ab Zyklus 1, bei vorheriger Neueinstellung der Kurvenform erst nach dem Abschluss des Kopiervorganges). Das kann sich durch eine Verzögerung minimal bemerkbar machen.

5.7 Aktualisierung der LCD-Anzeige

Wenn eine der Flaggen bSetR2R oder bSetCtc gesetzt war, erfolgt nun die Aktualisierung der LCD-Anzeige.

Dazu wird
  1. aus den Einstellungen in sFineTune: und der Potistellung in sPot zunächst die Displayeinstellung in sDisplayTune: ermittelt: sie gibt an, in welchem Format die Ausgabe der Frequenz erfolgt (0 = n,nnnn; 1 = nn,nnn; 2 = nnn,nn; 3 = n.nnn,n; 4 = nn.nnn),
  2. entsprechend dieser Einstellung in sDisplayTune: sowie aus der Einstellung in sFineTune: wird ein 32 Bit umfassender Dividend geladen, der durch den eingestellten 16-Bit-CTC-Wert (+1) geteilt wird,
  3. die resultierende 24-Bit-Ganzzahl in den SRAM-Pufferbereich ab sLcdBuf: in eine Dezimalziffernfolge umgewandelt (mit Unterdrückung führender Nullen),
  4. durch Einfügen von Tausender- und Dezimal-Trennzeichen an den geeigneten Stellen formatiert,
  5. durch ein führendes LCD-Spezialzeichen ergänzt, das die Kurvenform symbolisiert, und schließlich
  6. der Pufferinhalt in die LCD geschrieben.
Die einzelnen Schritte sind nachfolgend detaillierter beschrieben.

5.7.1 Ermittlung der Frequenz aus dem CTC-Wert

Die Frequenzberechnung aus den CTC-Werten erfolgt durch Teilen des Taktes durch den CTC-Wert von Teiler TC1 (+1). Dabei werden die anzuzeigenden Kommastellen als Quasi-Festkomma-Integer-Werte behandelt. So wird das Frequenzergebnis 2,0000 Hz als binärer Wert 20.000 aus der Division erhalten, das Einfügen des Kommas an der richtigen Stelle (2,0000) erfolgt erst nach der Umwandlung in die Dezimalzahl. Ebenso das Einfügen des Tausendertrenners an der richtigen Stelle (2,000.0).

5.7.2 Berechnung der Ist-Frequenz

Drei Parameter gehen in die Frequenzberechnung ein: Bedingt durch die Darstellungsweise der Ergebnisse ergeben sich die folgenden Darstellungsbereiche und deren Frequenzberechnung:

f (Hz)Grob-Fein-Display- Dezimal-DarstellungCTC+1 DividendMinimumMaximum
vonbisbereichstellen01234567vonbisFormelDezimalHexDezimalHexDezimalHex
29,99990 004 M 2,000031.2506.250Takt*10.000/256625.000.00025.40.BE.402,000000.4E.2010,000001.86.A0
1019,99913 M 10,0006.2493.125Takt*1.000/25662.500.00003.B9.AC.A010,00200.27.1220,00000.4E.20
2099,999113.12462520,00000.4E.20100,00001.86.A0
100199,9922M 100,00624313Takt*100/2566.250.00000.5F.5E.10100,0000.27.10199,6800.4E.40
200999,992231263199,6800.4E.80992,0601.83.86
1.0001.999,931M1.000,06231Takt*10/256625.00000.09.89.68992,100.26.C12.016,100.4E.C1
2.0002.499,933 30252.016,100.4E.C12.500,000.61.A8
2.5004.999,944 5025Takt*10/1281.250.00000.13.12.D02.500.000.61.A85.000,000.C3.50
5.0009.999,955 Takt*10/642.500.00000.26.25.A05.000,000.C3.5010.000,001.86.A0
10.00020.00066 0M 10.000Takt/32500.00000.07.A1.2010.00000.27.1020.00000.4E.20


Bei der Frequenzberechnung werden folgende Ressourcen benötigt: Die Division des Dividenden (4 Bytes, 32 Bit) durch den Divisor (2 Byte, 16 Bit) erfolgt wie üblich. Nur eine Besonderheit ist dabei zu beachten: wenn die beiden oberen Bytes des Dividenden von Beginn der Division an schon größer sind als der Divisor (dieser Fall tritt unter bestimmten Relationen auf - in den Fällen in der Tabelle, bei denen das Divisionsergebnis in der Spalte Maximum hex 01.00.00 erreicht), muss die Division nicht mit dem Linksschieben des Dividenden sondern mit dem Abziehen des Divisors beginnen. Dafür sind dann 17 Divisionsschritte auszuführen statt 16. Da das Divisionsergebnis in diesen Fällen tatsächlich 17 Bits hat, muss bei der Dezimalumwandlung das 17.te Bit mit verarbeitet werden (100.000-er).

5.7.3 Umwandlung der binären Frequenz in die dezimale Anzeige

Die binäre Frequenz ist dann in eine Dezimalzahl zu wandeln. Die größte Dezimalzahl ist dabei 100.000 (binär: 01.86.A0). Die einzelnen Dezimalziffern werden dazu jeweils schrittweise mit den dezimalen Vielfachen von 10 verglichen (von 100.000 abwärts über 10.000, 1.000, 100, 10 bis 1) und jeweils n-fach abgezogen, bis beim Abziehen ein Unterlauf eintritt.

Das Vorgehen ist im Folgenden am Beispiel der Zahl 234.567 ausgeführt.

Dezimalumwandlung Im ersten Schritt wird die Zahl mit der binären 100.000 (hex 01.86.A0) verglichen. Diese lässt sich von der Zahl zwei Mal abziehen, bevor ein Unterlauf auftritt. Durch das Abziehen verschwindet die erste Ziffer der Zahl (siehe die Zahl in der nächsten Zeile.

Die "2" wird in eine ASCII-Zahl umgewandelt, indem einfach 48 addiert wird. Sie wird ein einem Ausgabepuffer abgelegt und der Pufferzeiger um Eins erhöht.

Als Zweites sind die Zehntausender dran. Danach die Tausender, etc. Die letzte Ziffer braucht nicht mehrfach abgezogen werden, sie stellt als solche den Einer-Rest dar.

Dies hier ist der Quellcode der Dezimalumwandlung. Im SRAM steht in der Zelle sCurve das Zeichen für die Kurvenformausgabe der LCD (0 = Sägezahn Saegezahn, 1 = Sinus Sinus, 2 = Dreieck Dreieck, 3 = Rechteck)Rechteck, das als Erstes an die LCD ausgegeben wird. Danach wird der Umwandlungspuffer durch Überschreiben mit Leerzeichen geleert. Das Register rN3 dient zur Unterdrückung führender Nullen (1 = Unterdrücken, 0 = nicht mehr unterdrücken).

; Divisionsergebnis ist in rN6:rN5:rN4
DecimalConversion:
  ldi XH,High(sLcdBuf) ; X zeigt auf Puffer
  ldi XL,Low(sLcdBuf)
  lds rmp,sCurve
  st X+,rmp ; Kurvenform-zeichen in LCD-Ausgabepuffer
  ldi rmp,' '
DecConv1:
  st X+,rmp ; Leerzeichen in den Puffer
  cpi XL,Low(sLcdBuf+8)
  brne DecConv1
  ldi XH,High(sLcdBuf+2) ; Auf Anfang Ausgabe
  ldi XL,Low(sLcdBuf+2)
  clr rN3 ; Fuehrende Nullen unterdruecken
  inc rN3 ; auf Eins
  ldi ZH,High(2*Dec6Tab) ; Dezimaltabelle in Z
  ldi ZL,Low(2*Dec6Tab)
DecConv2:
  lpm rN0,Z+ ; Lese Dezimal-Digit-Binaerzahl
  lpm rN1,Z+
  lpm rN2,Z+
  tst rN0 ; Ende der Tabelle?
  breq DecConv6
  ldi rmp,0xFF ; Ergebnis in rmp
DecConv3:
  inc rmp ; Naechstes Ergebnis
  sub rN4,rN0 ; Dezimalzahl abziehen, Byte 1
  sbc rN5,rN1 ; dto., Byte 2
  sbc rN6,rN2 ; dto., Byte 3
  brcc DecConv3 ; Weiter abziehen
  add rN4,rN0 ; Letzte Subtraktion rueckgaengig, Byte 1
  adc rN5,rN1 ; dto., Byte 2
  adc rN6,rN2 ; dto., Byte 3
  tst rmp ; Ergebnis Null?
  breq DecConv5 ; Ist Null
  clr rN3 ; Fuehrende-Nullen-Flagge loeschen
DecConv4:
  ; Nicht unterdruecken
  subi rmp,-'0' ; Digit in ASCII-Ziffer wandeln
  st X+,rmp
  rjmp DecConv2
DecConv5:
  tst rN3 ; Fuehrende Nullen unterdruecken?
  breq DecConv4 ; Nein, als '0' ausgeben
  ldi rmp,' ' ; Fuehrende Null unterdruecken
  st X+,rmp ; in LCD-Puffer
  rjmp DecConv2 ; Weiter wandeln
DecConv6:
  ldi rmp,'0'
  add rmp,rN4 ; Letzte Ziffer
  st X,rmp ; in LCD-Puffer

Dazu gehört noch die Dezimaltabelle DecTab:, die die binärkodierten Dezimalvielfachen angibt.

;
; Dezimaltabelle 6 Ziffern = 24 Bit
Dec6Tab:
  .db Byte1(100000),Byte2(100000)
  .db Byte3(100000),Byte1(10000)
  .db Byte2(10000),Byte3(10000)
  .db Byte1(1000),Byte2(1000)
  .db Byte3(1000),Byte1(100)
  .db Byte2(100),Byte3(100)
  .db Byte1(10),Byte2(10)
  .db Byte3(10),0
  .db 0,0

5.8 Finalisierung der Datenübernahme

Abschließend werden noch
  1. der Neueinstellungsbereich im SRAM ab sTransfStart: bis sTransfEnd: über den Bereich ab sCurrStart: kopiert,
  2. die Flaggen bSetR2R: und gelöscht.

5.9 Zeitbedarf

Alle angegebenen Zeiten wurden mit avr_sim gemessen und beziehen sich auf den Betrieb ohne die Frequenzausgabe durch TC1.

5.9.1 Zeitbedarf beim Wechsel der Kurvenform

Wechselt bei der Neueinstellung die Kurvenform, dann dauert dies minimal 225,6 µs bei einer Kurve, bei acht zu erzeugenden Kurven 226,4 µs, also minimal länger.

5.9.2 Zeitbedarf der Übernahme

Der Zeitbedarf dieses Teilschrittes beläuft sich auf bescheidene 6,2 µs und ist unabhängig von anderen Parametern konstant.

5.9.3 Zeitbedarf der Frequenzanzeigeberechnung

FeinbereichFrequenzZeitbedarf µsFrequenzZeitbedarf µs
0244,61951,9
12045,119951,7
220046,41.99950,2
32.00049,62.49948,8
42.50048,84.99948,3
55.00048,39.99944,1
610.00044,119.99944,5
Alle Zeiten liegen um die 50 µs, mit geringer Variationsbreite.

5.9.4 Zeitbedarf der LCD-Ausgabe

Am Ende ist die Position der LCD an den Anfang zu stellen und die acht Zeichen aus dem SRAM-Puffer an die LCD auszugeben. Der Zeitbedarf für ein einzelnes Zeichen sowie die Positionseinstellung ist mit maximal 40 µs zu veranschlagen, mit allen Programmrahmenoperationen nicht mehr als 60 µs. Neun Schreiboperationen belaufen sich damit auf ca. 540 µs.

5.9.5 Gesamter Zeitbedarf

Der gesamte Zeitbedarf für die Datenübernahme (6,2 µs), für die Frequenzberechnung mit anschließender Dezimalwandlung und Dezimal- bzw. Tausender-Punktsetzung (50 µs) und dem Schreiben auf die LCD (540 µs) beläuft sich damit auf ca. 600 µs.

Ist zusätzlich eine neue Kurvenform einzustellen, dann sind weitere 226 µs zu veranschlagen, was zu 826 µs Gesamtdauer führt.

Verlängert sich die Bearbeitung durch zwischenzeitlich eintretende TC1-Interrupts zur Frequenzausgabe, dann sind bei der schnellsten Frequenzausgabe-Variante Verlängerungen auf bis zu 3,5 ms Ausführungsdauer zu veranschlagen.

Durch diese lange Dauer könnten Wechselwirkungen auftreten. Allerdings treten ADC-Messzyklen nur alle 15 ms ein, so dass die Ausgabe in jedem Fall kürzer bleibt als ein einziger Messzyklus. Der gesamte Messzyklus für 16 Messungen hat ca. 250 ms Dauer und endet mit der Neuberechnung aller Parameter. Auch dieser Zeitraum wird weit unterschritten. Nebenwirkungen treten daher keine auf.

Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

6. Die LCD-Anzeige

Die für die LCD-Anzeige erforderliche Assembler-Software ist in der Include-Datei lcd.inc zusammengestellt, die hier genauer beschrieben ist. Die in der Include-Datei zur Verfügung gestellten Programmteile sind mit den jeweiligen Aufrufparametern in einem Abschnitt in deren Kopf dokumentiert.

Alle zur Verfügung gestellten Routinen verwenden das Register R16 als Vielzweckregister. Alle weiteren verwendeten Register sind in der Routinenliste benannt. Die Register R0 und R1 sind bei einigen Routinen ebenfalls verwendet, ihre Inhalte sind aber mit Push und Pop gesichert.

6.1 Einstellungen der LCD-Anzeige

6.1.1 Parametersatz für lcd.inc

Nachfolgend ist ist der Parametersatz dargestellt. Er enthält alle Parameter, die die Include-Datei erwartet und die den konkreten Anwendungsfall charakterisieren.

; Parameter-Satz der Einstellungen
  .equ clock = 16000000 ; Taktfrequenz Prozessor in Hz
; LCD-Groesse:
  .equ LcdLines = 1 ; Anzahl Zeilen (1, 2, 4)
  .equ LcdCols = 8 ; Anzahl Zeichen pro Zeile (8..24)
; LCD-Ansteuerung
  .equ LcdBits = 8 ; Busbreite (4 oder 8)
  ; Wenn 4-Bit-Ansteuerung:
    ;.equ Lcd4High = 1 ; Busnibble (1=Oberes, 0=Unteres)
  .equ LcdWait = 0 ; Ansteuerung (0 mit Busy, 1 mit Warteschleifen)
; LCD-Datenports
  .equ pLcdDO = PORTC ; Daten-Ausgabe-Port
  .equ pLcdDD = DDRC ; Daten-Richtungs-Port
; LCD-Kontrollports und -pins
  .equ pLcdCEO = PORTA ; Control E Ausgabe-Port
  .equ bLcdCEO = PORTA7 ; Controll E Ausgabe-Portpin
  .equ pLcdCED = DDRA ; Control E Richtungs-Port
  .equ bLcdCED = DDA7 ; Control E Richtungs-Portpin
  .equ pLcdCRSO = PORTA ; Control RS Ausgabe-Port
  .equ bLcdCRSO = PORTA5 ; Controll RS Ausgabe-Portpin
  .equ pLcdCRSD = DDRA ; Control RS Richtungs-Port
  .equ bLcdCRSD = DDA5 ; Control RS Richtungs-Portpin
; Wenn LcdWait = 0:
  .equ pLcdDI = PINC ; Daten-Input-Port
  .equ pLcdCRWO = PORTA ; Control RW Ausgabe-Port
  .equ bLcdCRWO = PORTA6 ; Control RW Ausgabe-Portpin
  .equ pLcdCRWD = DDRA ; Control RW Richtungs-Port
  .equ bLcdCRWD = DDA6 ; Control RW Richtungs-Portpin
; Wenn Dezimalausgabe benoetigt wird:
  ;.equ LcdDecimal = 1 ; Wenn definiert: einbinden
; Wenn Hexadezimalausgabe benoetigt wird:
  ;.equ LcdHex = 1 ; Wenn definiert: einbinden
; Wenn nur Simulation im SRAM:
  ;.equ avr_sim = 1 ; 1=Simulieren, 0=Nicht simulieren

6.1.2 Spezialzeichen

Die Routine LcdChars bietet die Möglichkeit, eigene Zeichen zu definieren. Sie wird hier benutzt, um die aktuell eingestellte Kurvenform zu symbolisieren. Die Spezialzeichen sind mit dem Open-Office-Tool Zeichengenerator entworfen, das auf der Webseite hier näher beschrieben ist.

Spezialzeichen 0 Saegezahn Spezialzeichen 1 Sinus Spezialzeichen 2 Dreieck Spezialzeichen 3 Rechteck Im Einzelnen sind die Zeichen 0 bis 3 definiert und werden zu Beginn mit der Routine LcdChars in die LCD geschrieben. Die diesen Zeichen entsprechende Zeichentabelle liefert das OpenOffice-Tool, sie sieht folgendermaßen aus:

; Tabelle der Codezeichen
Charcodes:
.db 64,0,0,1,3,5,9,17,1,0 ; Z = 0, Saegezahn
.db 72,0,0,8,20,20,21,5,2,0 ; Z = 1, Sinus
.db 80,0,0,4,4,10,10,17,17,0 ; Z = 2, Dreieck
.db 88,0,0,14,10,10,10,10,27,0 ; Z = 3, Rechteck
.db 0,0 ; Ende der Tabelle

Die Routine LcdChars aus der lcd.inc sieht folgendermaßen aus:

; +-------+----------------+--------------+
; |Routine|Funktion        |Parameter     |
; +-------+----------------+--------------+
; |LcdSpec|Erzeugt Spezial-|Z: 2*Tabellen-|
; |       |zeichen         |   adresse    |
; +-------+----------------+--------------+
; ...
;
; LcdSpec erzeugt Spezialzeichen auf der LCD
;   Z zeigt auf 2*Tabellenadresse
;   Tabellenformat:
;     1.Byte: Adresse des Zeichens 0b01zzz000,
;             0: Ende der Tabelle
;     2.Byte: Dummy
;     3. bis 10.Byte: Daten der Zeilen 1 bis 8
LcdSpec:
  push R0 ; R0 ist Zaehler
LcdSpec1:
  lpm R16,Z+ ; Lese Adresse des Zeichens
  tst R16 ; Ende der Tabelle?
  breq LcdSpec3 ; Ende Tabelle
  rcall LcdCtrl ; Adresse schreiben
  adiw ZL,1 ; Ueberlese Dummy
  ldi R16,8 ; 8 Byte pro Zeichen
  mov R0,R16 ; R1 ist Zaehler
LcdSpec2:
  lpm R16,Z+ ; Datenbyte lesen
  rcall LcdData ; Datenbyte ausgeben
  dec R0 ; Abwaerts zaehlen
  brne LcdSpec2 ; Weiter Daten ausgeben
  rjmp LcdSpec1 ; Naechstes Zeichen
LcdSpec3:
  pop R0 ; R0 wieder herstellen
  ldi ZH,0 ; Cursor Home
  ldi ZL,0
  rjmp LcdPos

Im Registerpaar Z wird die Position der Zeichentabelle im Flash übergeben (wegen LPM mit 2*Codezeichen). Die Codes 0 bis 3 entsprechen den beiden Kurvenform-Bits, wie sie von den Eingabeschaltern her eingelesen werden. Sie werden in der ersten Spalte der LCD-Anzeige angezeigt.

6.2 Start der LCD-Anzeige

Die Initiierung der LCD-Anzeige beinhaltet
  1. die Konfigurierung der erforderlichen Ports und Pins der LCD,
  2. die Einstellung der LCD auf die nötigen Parameter (Portbreite, Anzeigeauflösung, etc.),
  3. die Erzeugung der vier Spezialzeichen zur symbolisierten Anzeige der Kurvenform, und
Die Routine LcdInit: erledigt diese Schritte.

6.3 Startanzeige

Die Ausgabe einer Startmeldung mit dem Programmnamen und der Version erfolgt im Hauptprogramm durch Aufruf der Funktion LcdText: mit der Flashtabelle in Z als Parameter.

Die Startanzeige zeigt, dass die LCD korrekt initiiert ist und funktioniert. Sie lautet:

LcdInitText:
.db "SigGenV1",0xFE,0xFE ; Textausgabe bei Init

Sie wird mit folgendem Code in die LCD geschrieben:

  clr ZH ; Position = 0/0, Zeile -1
  clr ZL : Spalte -1
  rcall LcdPos ; Setze Position
  ldi ZH,High(2*LcdInitText) ; Z auf Text
  ldi ZL,Low(2*LcdInitText)
  rcall LcdText

Alle weiteren Funktionen der LCD-Include, die verwendet werden, finden sich unter dem Suchbegriff call lcd im Quellcode, die Aufrufe sind in Kopf der Include-Datei näher erläutert.

Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

7. Die Kurvenform-Erzeugung

7.1 Kurvenformen

Kurvenformen 8-Bit-DAC Die Kurvenform-Tabellen für den 8-Bit DAC sind in der Include-Datei Tabellen.inc angegeben. Sie wurden mit dem Open-Office-Spreadsheet Wellentabellengenerator erzeugt.

Damit es beim Umschalten auf eine andere Kurvenform nicht zu steilen Pegeländerungen kommt, wurde dabei der Sinus um +270° phasenverschoben. Damit beginnen alle Kurven bei dem DAC-Wert Null und es kommt beim Umschalten der Kurvenform nicht zu steilen Sprüngen.

7.2 Der Digital-Analog-Wandler aus Widerständen

Der Digital-Analog-Wandler ist als R/2R-Netzwerk mit 1%-Widerständen aus Werten von 1k10 und 2k20 aufgebaut. 1%-Widerstände sind ein Muss weil das oberste Bit 128-mal so stark in das Ergebnis eingeht als das unterste Bit und sich höhere Differenzen des Bit7-Widerstandswerts entsprechend gravierend auf das Endergebnis auswirken. Außerdem gibt es 1k10-Widerstände ohnehin nur in der E24-Reihe und die ist sowieso fast immer 1% genau.

Die Auswahl der Kombination 1k10 und 2k20 wurde aus folgenden Gründen gewählt: Die Kombination 1k10 und 2k20 stellt daher einen guten Kompromiss dar.

7.3 Berechnung des R/2R-Netzwerks

7.3.1 Spannungen und Ströme im R/2R-Netzwerk

Spannungen und Stroeme in einem R/2R-Netzwerk Die Berechnung der Spannungen und Ströme in einem 8-Bit-R/2R-Netzwerk ist nicht ganz trivial, da alle Spannungen und Ströme vom Zustand von 8 Eingangsbits abhängen und sich alle gegenseitig beeinflussen. Das Schaltbild zeigt alle Ströme und Spannungen. Die angezeigten Stromrichtungen zeigen positive Ströme an, die Ströme können dabei auch negativ werden (entspricht einer Umkehr des Richtungspfeils).

7.3.2 Berechnung der Ausgangsspannung des Netzwerks

Ausgangsspannung des R/2R-Netzwerks Damit wir nicht die Ausgangsspannungen als Riesenformeln handhaben müssen, da immer alle acht Eingangsspannungen in das Ergebnis jeder Spannungsberechnung eingehen, behelfen wir uns damit, dass wir das Ergebnis der Ausgangsspannung des R/2R-Netzwerks schon kennen: alle acht Spannungen der Eingangspins Un gehen entsprechend ihrer binären Gewichtung (1, 2, 4, 8, 16, 32, 64, 128) in das Endergebnis ein, durch Teilen mit 256 erhält man das Ergebnis als Spannungswert am Ausgang (UR7).

7.3.3 Berechnung der Spannung an UR6

R/2R Berechnung der Spannung an UR6 Aus der bekannten Spannung an UR7 und der Eingangsspannung an U7 lässt sich die Spannung an UR6 berechnen, denn die beiden Ströme IR7 und IE7 sind identisch (sofern kein Strom an Uout entnommen wird) und betragen (U7 - UR7) / 2R. Die Formeln zur Berechnung von UR6 ergeben sich demnach wie folgt.

R2R Formeln fuer UR6 Durch Kombination der beiden Ströme und Umstellung der Formeln erhält man eine sehr einfache Formel für UR6 und kann diese Spannung aus UR7 und U7 berechnen.

Bemerkenswert ist hier schon, dass die Widerstandswerte R gänzlich aus der Formel herausfallen und sich nur noch die "2" in der Formel hält (in U7/2).

7.3.4 Berechnung der weiteren Spannungen URn

R2R Berechnung von UR6 Die nächstniedrige Spannung UR5 im Netzwerk kann folgendermaßen aus den beiden Strömen IR6 und IE7 errechnet werden, die sich zum Strom IE6 ergänzen.

R2R Formeln fuer UR5 Im Endergebnis ergibt sich wiederum eine recht einfache Formel für UR5, in die UR6, UR7 und die Spannung am Eingang U6 eingehen.

Analog dazu können alle weiteren Spannungen URn aus 2,5 * UR(n+1) - UR(n+2) und U(n+1)/2 berechnet werden und erhält so alle Spannungen an den Verbindungsknoten des Netzwerks für eine gegebene Kombination von Eingangsspannungen U0 bis U7.

7.3.5 Beispielberechnung für die Eingangskombination 10101010

R2R Spannungen und Stroeme bei 0xAA Dies hier zeigt die Spannungen und Ströme in dem Netzwerk bei einem Eingabewert von 0xAA oder 0b10101010, bei dem die Bits die Polarität wechseln.

Man erkennt, dass die Spannungen URn keinen einfachen Regeln folgen. Je nach der Polarität des Eingangsbits ergibt sich eine niedrigere oder höhere Spannung als die darunter oder darüber liegende.

Auch bei den Strömen IRn ergeben sich keine einfachen Regelmäßigkeiten außer dass diese bei High-Pegel am Eingang Un negativ sind und bei High-Pegeln positiv. Alle liegen aber um die ca. 1 mA, was einer Gesamtsumme von 7,85 mA aus allen acht Portausgängen entspricht (egal welche Polarität der Strom hat). Die einfache Summe liegt aber bei nur 0,51 mA, weil sich positive und negative Ströme bei der Kombination 0xAA fast ganz ausgleichen.

Auch die Ströme IEn folgen keinen einfachen Regelmäßigkeiten außer dass sie bei der Eingangskombination 0b10 positiv und bei 0b01 negativ sind. Sie sind aber nicht Null, wenn 0b00 oder 0b11 an zwei benachbarten Eingängen anliegt, auch in diesen Fällen fließt noch Strom aus oder in die darunter- oder darüberliegenden Netzwerk-Knoten.

7.3.6 Ströme aus den und in die Ports

Portströme im R/2R Netzwerk Dies hier sind alle Ströme aus den und in die Ports bei allen 256 Kombinationen. Aufsummiert sind die jeweiligen acht Absolutwerte der Portpin-Ströme.

Der höchste Gesamtstrom mit 8,08 mA ergibt sich bei der Kombination 0x55 = 0b01010101. Ansonsten sind kaum Regelmäßigkeiten zu erkennen.

7.3.7 Abweichungen durch die realen CMOS-Ausgangsspannungen

Hypergenaue Zeitgenossen setzen als Eingangsspannungen für Nullen eher 0,022 V ein, für Einsen 4,97 V, da die CMOS-Ausgänge bei 0,63 mA im Low-Zustand oder 0,82 mA Strom im High-Zustand (jeweils Mittelwerte aus allen 256 Eingangs-Kombinationen) schon nicht mehr bis zum Anschlag treiben, und erhalten ein etwas genaueres Ergebnis.

R2R Abweichungen vom Sollwert bei realen Ausgangsspannungen Um die Abweichungen zu quantifizieren wurden hier Die Berechnung zeigt, dass die Abweichungen in allen Fällen unter +/-1% und daher unter der Genauigkeit der Widerstände im Netzwerk bleiben.

Das würde sich nur dann ändern, wenn die Ströme im Netzwerk auf etwas mehr als das Doppelte (ca. 2,5 mA) ansteigen würden. Auch deshalb ist die Auswahl von 1k10 und 2k20 gerechtfertigt, weil die Ströme damit unter der Grenze bleiben, bei der die realen CMOS-Ausgangsspannungen sich auf das Ergebnis auswirken würden.

7.4 Tabellen für den Kurvenverlauf

Die Tabelle mit dem Kurvenverlauf ist im Kapitel 9 Listing dokumentiert.

Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

8 Die Signalgenerator-Software im Simulator

Bei der Software-Entwicklung wurden verschiedene Schalter eingebaut, die zur Überprüfung der Software im Simulator geeignete Einstellungen vornehmen.

8.1 Debug-Schalter

Die folgenden Debug-Schalter sind eingebaut:
  1. debug_CtcCalc: Dieser Schalter erlaubt es, eine Frequenz cFrequency einzustellen (2..20.000) und die Kurvenform cCurve einzustellen und sofort zur CTC-Berechnungsroutine zu springen. Im SRAM wird daraus der neue Parametersatz ausgegeben. Die weitere Ausführung des Programms erfolgt nicht.
  2. debug_Transfer: Ist dieser Schalter gesetzt, wird der neue Parametersatz unmittelbar übernommen und die Wellenform und CTC-Frequenz eingestellt. Danach erfolgt keine weitere Programmausführung.
  3. debug_Second: Nachdem die Software normal mit der eingestellten Frequenz fStart und der eingestellten Kurvenform in curvegestartet ist, wird bei der Ermittlung der nächsten Werte die in cFrequency2 und die Kurvenform in cCurve2 eingestellt. Der AD-Wandler wird durch ein schnelleres Timing in cTCCR02 beschleunigt, um die Wartezeit zu verkürzen.
  4. debug_TC1Off: Mit diesem Schalter kann die Einstellung der Wellenform im SRAM und die Ausführung des TC1-Interrupts zur DAC-Ansteuerung abgeschaltet werden, um davon unbeeinflusste Ausführungsgeschwindigkeiten messen zu können.
  5. debug_NoLcd: Dieser Schalter unterbindet die Initiierung der LCD sowie Ausgaben an die LCD.
  6. debug_insert: Dieser Schalter diente der simulierten Überprüfung des Einfügens von Dezimal- und Tausendertrennpunkten in die Zahlenausgabe im SRAM.
  7. debug_lcdonly: Dieser Schalter dient dem Debugging der LCD. Nach der Eröffnungsmeldung wird die weitere Programmausführung unterbunden.
Alle Debug-Schalter müssen auf Null gestellt werden, um eine endgültig laufende ausführbare Version zu erzeugen.

8.2 Simulationsergebnisse

Hier der Signalgenerator im Simulator, mit avr_sim simuliert.

Viel beschäftigt ist der Timer TC1, wenn man Frequenzen über 2.500 Hz erzeugen lässt:

TC1 beim Compare Match TC1 beim Compare Match Interrupt Links ist der Compare Match erreicht, der einen Takt später (rechts) den Interrupt-Vektor anstößt.

Schlafanteil bei 20 kHz Schlafanteil bei 10 kHz Entsprechend ist der Zeitanteil, die der Prozessor im Schlafmodus verbringt, von der eingestellten Frequenz abhängig: ca. 15% bei 20 kHz (links), ca. 71% bei 10 kHz bei 10 kHz.

Hier dann ein 2.000 Hz Sinussignal mit 256 Stufen, noch ohne RC-Filternetzwerk.

2 kHz Sinus-Signal Die Stufungen des DA-Wandlers sind praktisch nur zu erahnen. Die Frequenz ist wegen Rundungsfehlern der Ganzzahl-Mathematik nicht exakt.

Die Verringerung der Ausgangsspannung auf Operationsverstärker-Eingangs-freundliche 3 V Spitze wirkt sich hier erkennbar aus.

2 kHz Dreieck-Signal Eine saubere Dreieckspannung mit 2 kHz.

10 kHz Sinus-Signal Die Stufungen des hier nur 32-stufigen DA-Wandlers sind hier noch schön zu sehen, bevor es in das RC-Filter geht.

5 kHz Saegezahn-Signal Und hier mal ein Sägezahn mit 5 kHz. Die Stufungen des DA-Wandlers mit 64 Einzelwerten pro Durchgang sind gerade noch zu erkennen.

20 kHz Rechteck-Signal Ein Rechtecksignal mit 20 kHz.

Umschaltung von Sinus auf Rechteck Hier wird von einem 10-kHz-Sinus auf einen 10-kHz-Sägezahn und dann auf einen 20 kHz-Sägezahn umgeschaltet. Die Umschaltung von Sinus auf Sägezahn erfolgt beim Nulldurchgang der Kurve. Die Umstellung auf die höhere Frequenz erfolgt nicht beim Nulldurchgang.


Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

9. Programmlistings

9.1 Haupt-Sourcecode

Hier ist der gesamte Code für das Hauptprogramm, es ist hier als Assembler-Quellode downloadbar.

;
; ***********************************************
; * Signal generator ATmega16: Sawtooth/Sine/   *
; * Triangle/Rectangle, with R/2R-DAC+8-Bit-LCD *
; * Version 1, September 2018                   *
; * (C)2018 by http://www.avr-asm-tutorial.net  *
; ***********************************************
;
; Include file with type-specific constants
.NOLIST
.INCLUDE "m16def.inc" ; Header file for ATmega16
.LIST
;
; ============================================
;    S I M U L A T I O N - S W I T C H E S
; ============================================
;
; Final version: All debug flags = 0
; Debug version: Debugging with simulator, flags = 1
;
; Simulation der CTC-Werte zur Frequenzeinstellung
.equ debug_CtcCalc = 0 ; Debuggen der Berechnungsroutinen CTC
.if debug_CtcCalc == 1
  .equ cFrequency = 2 ; Zu simulierende Frequenz in Hz
  .equ cCurve = 1 ; Kurvenform (0..3)
    ; 00 = Saegezahn, 01 = Sinus, 10 = Dreieck, 11 = Rechteck
  .endif
;
; Simulation des Transfers der eingestellten Werte
;   (wird nur ausgefuehrt wenn auch debug_CtcCalc
;    eingeschaltet ist)
.equ debug_Transfer = 0 ; Debuggen des Wertetransfers
;
; Simulation des Uebergangs vom ersten zur zweiten Kurvenform
;   definiert Frequenz und Schalterstellungen der zweiten
;   Kurve
.equ debug_Second = 0 ; Schnelles Umschalten auf 2. Kurvenform
.if debug_Second == 0
  .equ cFrequency2 = 20000 ; Die naechste Frequenz
  .equ cCurve2 = 0 ; Die naechste Kurvenform
  .equ cTCCR02 = (1<<WGM01)|(1<<CS01) ; Prescaler = 8
  .endif
;
; TC1 abschalten
.equ debug_TC1off = 0 ; TC1 zum Simulieren abschalten
;
; Simulation ohne LCD-Ausgaben
;   (Dieser Schalter kann mit anderen Schaltern zusammen
;    gesetzt werden.)
.equ debug_NoLcd = 0 ; Debuggen ohne LCD-Ausgabeoperationen
;
.equ debug_insert = 0 ; Debuggen der Anzeigenverschiebung
  .if debug_insert == 1
  .equ InsertDecPos = 5
  .equ InsertDecChar = '.'
  .equ InsertThdPos = 1
  .equ InsertThdChar = ','
  .endif
;
; Inbetriebnahme-Tests Schalter
.equ debug_lcdonly = 0 ; Nur LCD testen (Init)
;
; ============================================
;   H A R D W A R E I N F O R M A T I O N E N 
; ============================================
;
; Device = ATmega16
;              ___________
;           1 /           |40
; S1A-S2A o--|PB0     ADC0|--o Frequenzpoti
; S1B-S2B o--|PB1      PA1|--o S1 00 = 2 - 20 Hz, 01 = 20 - 200 Hz
; S1C-S1C o--|PB2      PA2|--o S2 10 = 200 - 2000 Hz, 11 = 2 - 20 kHz
; S1D-S2D o--|PB3      PA3|--o S3 Kurve 0, 00 = Saegezahn, 01 = Sinus
;      NC o--|PB4      PA4|--o S4 Kurve 1, 10 = Dreieck, 11 = Rechteck
;    MOSI o--|PB5      PA5|--o LCD-Ctrl RS
;    MISO o--|PB6      PA6|--o LCD-Ctrl R/W
;     SCK o--|PB7      PA7|--o LCD-Ctrl E
;  10k+5V o--|RESET   AREF|--o C=100n
;     +5V o--|VCC      GND|--o 0V
;      0V o--|GND     AVCC|--o C=100n, L=22uH
;   Quarz o--|XTAL2    PC7|--o LCD-D7
;  16 MHz o--|XTAL1    PC6|--o LCD-D6
;  DAC-D0 o--|PD0      PC5|--o LCD-D5
;  DAC-D1 o--|PD1      PC4|--o LCD-D4
;  DAC-D2 o--|PD2      PC3|--o LCD-D3
;  DAC-D3 o--|PD3      PC2|--o LCD-D2
;  DAC-D4 o--|PD4      PC1|--o LCD-D1
;  DAC-D5 o--|PD5      PC0|--o LCD-D0
;  DAC-D6 o--|PD6      PD7|--o DAC-D7
;          20|____________|21
;
;
; ============================================
;      P O R T S   U N D   P I N S 
; ============================================
;
; Ports und Pins zur RC-Filterkontrolle
.equ pRcfOut = PORTB ; Ausgabeport RC-Filter
.equ pRcfDir = DDRB  ; Richtungsport RC-Filter
.equ bRcfA   = PORTB0 ; Pin RC-Filter A
.equ bRcfB   = PORTB1 ; Pin RC-Filter B
.equ bRcfC   = PORTB2 ; Pin RC-Filter C
.equ bRcfD   = PORTB3 ; Pin RC-Filter D
.equ mRcf    = (1<<bRcfA)|(1<<bRcfB)|(1<<bRcfC)|(1<<bRcfD) ; Maske
; Frequenzbereiche der Kondensatoren
.equ cFein0 = 0x0F ; 2..20 Hz: 111,47 nF
.equ cFein1 = 0x07 ; 20..200 Hz: 11,47 nF
.equ cFein2l = 0x04 ; 20..500 Hz: 10 nF
.equ cFein2h = 0x03 ; 500..2000 Hz: 1n47
.equ cFein3 = 0x02 ; 2000..2500 Hz: 1n
.equ cFein46 = 0x01 ; 2500..20000 Hz: n47
.equ cPot2h = 255*(500-200)/(2000-200)+1 ; 500 Hz-Schwelle
; Ports, Pins und Masken Digital-Analog-Konverter
.equ pDacOut = PORTD ; Ausgabeport DAC
.equ pDacDir = DDRD ; Richtungsport DAC
.equ mDac5   = 0b00011111 ; Maske 5-Bit-DAC
.equ mDac6   = 0b00111111 ; Maske 6-Bit-DAC
.equ mDac7   = 0b01111111 ; Maske 7-Bit-DAC
.equ mDac8   = 0b11111111 ; Maske 8-Bit-DAC
; Ports und Pins zur LCD-Kontrolle siehe LCD-Einstellungen
;   (Ports LCD-Datenbus siehe LCD-Einstellungen)
; Ports, Pins und Masken Schalter
.equ pSwOut  = PORTA ; Ausgabeport Schalter
.equ pSwDir  = DDRA  ; Richtungsport Schalter
.equ pSwIn   = PINA  ; Leseport Schalter
.equ bSw1    = PORTA1 ; Schalter 1
.equ bSw2    = PORTA2 ; Schalter 2
.equ bSw3    = PORTA3 ; Schalter 3
.equ bSw4    = PORTA4 ; Schalter 4
.equ mSwPu   = (1<<bSw1)|(1<<bSw2)|(1<<bSw3)|(1<<bSw4) ; Maske
;
; ============================================
;     L C D - E I N S T E L L U N G E N
; ============================================
;
; Parameter-Satz der Einstellungen
  .equ clock = 16000000 ; Taktfrequenz Prozessor in Hz
; LCD-Groesse:
  .equ LcdLines = 1 ; Anzahl Zeilen (1, 2, 4)
  .equ LcdCols = 8 ; Anzahl Zeichen pro Zeile (8..24)
; LCD-Ansteuerung
  .equ LcdBits = 8 ; Busbreite (4 oder 8)
  ; Wenn 4-Bit-Ansteuerung:
    ;.equ Lcd4High = 1 ; Busnibble (1=Oberes, 0=Unteres)
  .equ LcdWait = 0 ; Ansteuerung (0 mit Busy, 1 mit Warteschleifen)
; LCD-Datenports
  .equ pLcdDO = PORTC ; Daten-Ausgabe-Port
  .equ pLcdDD = DDRC ; Daten-Richtungs-Port
; LCD-Kontrollports und -pins
  .equ pLcdCEO = PORTA ; Control E Ausgabe-Port
  .equ bLcdCEO = PORTA7 ; Controll E Ausgabe-Portpin
  .equ pLcdCED = DDRA ; Control E Richtungs-Port
  .equ bLcdCED = DDA7 ; Control E Richtungs-Portpin
  .equ pLcdCRSO = PORTA ; Control RS Ausgabe-Port
  .equ bLcdCRSO = PORTA5 ; Controll RS Ausgabe-Portpin
  .equ pLcdCRSD = DDRA ; Control RS Richtungs-Port
  .equ bLcdCRSD = DDA5 ; Control RS Richtungs-Portpin
; Wenn LcdWait = 0:
  .equ pLcdDI = PINC ; Daten-Input-Port
  .equ pLcdCRWO = PORTA ; Control RW Ausgabe-Port
  .equ bLcdCRWO = PORTA6 ; Control RW Ausgabe-Portpin
  .equ pLcdCRWD = DDRA ; Control RW Richtungs-Port
  .equ bLcdCRWD = DDA6 ; Control RW Richtungs-Portpin
; Wenn Dezimalausgabe benoetigt wird:
  ;.equ LcdDecimal = 1 ; Wenn definiert: einbinden
; Wenn Hexadezimalausgabe benoetigt wird:
  ;.equ LcdHex = 1 ; Wenn definiert: einbinden
; Wenn nur Simulation im SRAM:
  ;.equ avr_sim = 1 ; 1=Simulieren, 0=Nicht simulieren
;
; ================================================
; K O N S T A N T E N   Z U M  E I N S T E L L E N
; ================================================
;
.equ fstart  = 10000 ; Startfrequenz in Hz
.equ curve = 1 ; Kurvenform, kann sein:
;                0=Saegezahn, 1=Sinus, 2=Dreieck, 3=Rechteck
;
; Sprachauswahl:
;   beeinflusst Dezimalpunkte und Tausender-Trennzeichen
.equ LangEn = 0 ; 0 = Deutsch, 1 = Englisch
;
; =======================================================
;  F E S T E + A B G E L E I T E T E  K O N S T A N T E N 
; =======================================================
;
; Umrechnung der Startfrequenz in SRAM-Tabellenparameter
.if fstart < 2500
  .equ cFStart = (clock / 256 + fstart/2) / fstart - 1
  .equ cAdd = 1
  .equ cAnd = 0xFF
  .endif
.if (fstart>=2500)&&(fstart<5000)
  .equ cFStart = (clock / 256 * 2 + fstart/2) / fstart - 1
  .equ cAdd = 2
  .equ cAnd = 0x7F
  .endif
.if (fstart>=5000)&&(fstart<10000)
  .equ cFStart = (clock / 256 * 4 + fstart/2) / fstart - 1
  .equ cAdd = 4
  .equ cAnd = 0x3F
  .endif
.if (fstart>=10000)&&(fstart<=20000)
  .equ cFStart = (clock / 256 * 8 + fstart/2) / fstart - 1
  .equ cAdd = 8
  .equ cAnd = 0x1F
  .endif
;
; Konstante cAdcAdd: Software-Zaehler fuer ADC-Messwerte
;
.equ cTc0Presc = 1024 ; TC0-Prescaler
.equ cTc0Comp  = 243 ; TC0-Compare Match
.equ cAdcAdd   = 16 ; 16 Compare Match ADC Zyklen
;
; Konstanten zur Sollfrequenzberechnung
;
; Steilheit fuer die Frequenzberechnung
.equ cM0 = (65536*(20-2)+127)/255 ; Bereich 2-20 Hz
.equ cM1 = (65536*(200-20)+127)/255 ; Bereich 20-200 Hz
.equ cM2 = (256*(2000-200)+127)/255 ; Bereich 200-2.000 Hz
.equ cM3 = (256*(20000-2000)+127)/255 ; Bereich 2-20 kHz
;
; Addierwert fuer Potistellung 0
.equ cA0 = 65536*2 ; Bereich 2-20 Hz
.equ cA1 = 65536*20 ; Bereich 20-200 Hz
.equ cA2 = 256*200 ; Bereich 200-2.000 Hz
.equ cA3 = 256*2000 ; Bereich 2-20 kHz
;
; Potentiometerwerte fuer Feinbereich
.equ cP10 = ((10-2)*255+(20-2)/2)/(20-2)+1 ; 10 Hz Schwelle
.equ cP100 = ((100-20)*255+(200-20)/2)/(200-20)+1 ; 100 Hz Schwelle
.equ cP1000 = ((1000-200)*255+(2000-200)/2)/(2000-200) ; 1000 Hz Schwelle
.equ cP2500 = ((2500-2000)*255+(20000-2000)/2)/(20000-2000) ; 2.500 Hz Schwelle
.equ cP5000 = ((5000-2000)*255+(20000-2000)/2)/(20000-2000) ; 5.000 Hz Schwelle
.equ cP10000 = ((10000-2000)*255+(20000-2000)/2)/(20000-2000) ; 10.000 Hz Schwelle
;
; Potentiometerwerte fuer Kondensatorzuschaltung
.equ cPC0 = 0 ; Gesamter Feinbereich 0
.equ cC0L = 0x0F ; Alle C zuschalten
.equ cC0H = 0x0F ; Alle C zuschalten
.equ cPC1 = 0 ; Gesamter Feinbereich 1
.equ cC1L = 0x07 ; Alle ausser der hoechste
.equ cC1H = 0x07 ; Alle ausser der hoechste
.equ cPC2 = 43 ; Feinbereich 2
.equ cC2L = 0x04 ; C bei kleiner als Schwelle
.equ cC2H = 0x03 ; C bei groesser/gleich Schwelle
.equ cPC3 = 0 ; Gesamter Feinbereich 3
.equ cC3L = 0x02 ; Nur 1 nF
.equ cC3H = 0x02 ; Nur 1 nF
.equ cPC46 = 0 ; Gesamte Feinbereiche 4 bis 6
.equ cC46L = 0x01 ; Nur 470 pF
.equ cC46H = 0x01 ; Nur 470 pF
;
; Divisor fuer CTC-Wertberechnung
.equ cDiv01 = clock  ; Divisor Clock/(256*Frequenz), 1 Kurvenzug
.equ cDiv23 = clock/256  ; Divisor Clock/Frequenz, 1 Kurvenzug
.equ cDiv4 = clock/128 ; Divisor Clock/Frequenz, 2 Kurvenzuege
.equ cDiv5 = clock/64 ; Divisor Clock/Frequenz, 4 Kurvenzuege
.equ cDiv6 = clock/32 ; Divisor Clock/Frequenz, 8 Kurvenzuege
;
; Divisionskonstanten fuer Anzeigedarstellung
.equ cND0 = ((10*clock+128)/256)*1000 ; 2..9,9999 Hz, 1 Kurvenzug, 4 Dezimalstellen
.equ cND1 = ((10*clock+128)/256)*100 ; 10..99,999 Hz, 1 Kurvenzug, 3 Dezimalstellen
.equ cND2 = ((10*clock+128)/256)*10 ; 100..999,99 Hz, 1 Kurvenzug, 2 Dezimalstellen
.equ cND3 = (10*clock+128)/256 ; 1.000..2.499,9 Hz, 1 Kurvenzug, 1 Dezimalstelle
.equ cND4 = (10*clock+64)/128 ; 2.500..4.999,9 100 Hz, 2 Kurvenzuege, 1 Dezimalstelle
.equ cND5 = (10*clock+32)/64 ; 5.000..9.999,9 Hz, 4 Kurvenzuege, 1 Dezimalstelle
.equ cND6 = (clock+16)/32 ; 10.000..20.000 Hz, 8 Kurvenzuege, 0 Dezimalstellen
;
; Debug-Einstellungen umrechnen
;
; Debug der Ctc-Berechnung
.if debug_CtcCalc == 1 ; CTC-Berechnung simulieren
  .if cFrequency < 20
    .equ cCourseTune = 0 ; Grobbereich Frequenz (0..3)
    .equ cPot = (65536*cFrequency - cA0) / cM0
    .endif
  .if (cFrequency>=20)&&(cFrequency<200)
    .equ cCourseTune = 1 ; Grobbereich Frequenz (0..3)
    .equ cPot = (65536*cFrequency - cA1) / cM1
    .endif
  .if (cFrequency>=200)&&(cFrequency<2000)
    .equ cCourseTune = 2 ; Grobbereich Frequenz (0..3)
    .equ cPot = (256*cFrequency - cA2) / cM2
    .endif
  .if (cFrequency>=2000)&&(cFrequency<20000)
    .equ cCourseTune = 3 ; Grobbereich Frequenz (0..3)
    .equ cPot = (256*cFrequency - cA3) / cM3
    .endif
  .endif
;
; Debug der Umschaltung auf die zweite Kurvenform/Frequenz
.if debug_Second == 1
  .if cFrequency2 < 20
    .equ cCourseTune2 = 0
    .equ cAdc2 = (1023*(cFrequency2-2))/18
    .else
    .if cFrequency2 < 200
      .equ cCourseTune2 = 1
      .equ cAdc2 = (1023*(cFrequency2-20))/180
      .else
      .if cFrequency2 < 2000
        .equ cCourseTune2 = 2
        .equ cAdc2 = (1023*(cFrequency2-200))/1800
       .else
        .equ cCourseTune2 = 3
        .equ cAdc2 = (1023*(cFrequency2-2000))/18000
        .endif
      .endif
    .endif
  .equ cPot2 = cAdc2/4
  .equ cTCCR0 = (1<<WGM01)|(1<<CS01)|(1<<CS00) ; Prescaler = 8
  .endif
;
; ============================================
;                T I M I N G
; ============================================
;
; Timer TC1 (16-Bit-PWM mit CompA-Interrupt):
;   erledigt die Ausgabe der Signale ueber R/2R-DAC
;
;   Taktfrequenz           = 16.000.000 Hz
;   Vorteiler              =          1
;
;   f = Taktfrequenz / 256 * Kurven / (CTC+1)
;   (Kurven = Anzahl Kurvenzuege in 256 Schritten)
;   (Feinbereiche: 0..6  in Klammern)
;
;   +--------------+----------+--------+---------+
;   | Feinbereich  | Frequenz | Kurven | TC1-CTC |
;   |--------------+----------+--------+---------+
;   | 2 - 20 Hz    | 2 Hz     |    1   |  31.249 |
;   | (0)          | 20 Hz    |        |   3.124 |
;   +--------------+----------+--------+---------+
;   | 20 - 200 Hz  | 20 Hz    |    1   |   3.124 |
;   | (1)          | 200 Hz   |        |     312 |
;   +--------------+----------+--------+---------+
;   | 200 - 2000 Hz| 200 Hz   |    1   |     312 |
;   | (2)          | 2000 Hz  |        |      30 |
;   +--------------+----------+--------+---------+
;   | 2 - 2,5 kHz  | 2 kHz    |    1   |      30 |
;   | (3)          | 2,5 kHz  |        |      24 |
;   +--------------+----------+--------+---------+
;   | 2,5 - 5 kHz  | 2,5 kHz  |    2   |      49 |
;   | (4)          | 5 kHz    |        |      24 |
;   +--------------+----------+--------+---------+
;   | 5 - 10 kHz   | 5 kHz    |    4   |      49 |
;   | (5)          | 10 kHz   |        |      24 |
;   +--------------+----------+--------+---------+
;   | 10 - 20 kHz  | 10 kHz   |    8   |      49 |
;   | (6)          | 20 kHz   |        |      24 |
;   +--------------+----------+--------+---------+
;
;   Extrema Frequenzbereich:
;     Niedrigste Frequenz: Takt / 256 / 65536  = 0,954 Hz
;     Hoechste Frequenz  : Takt / 256 * 8 / 21 = 23,8 kHz
;
; Timing TC0:
;   steuert das Einlesen des ADC-Wertes und
;   die Neujustierung der Ausgabefrequenz
;
;   Taktfrequenz TC0        = 16.000.000 Hz
;   Prescaler TC0           =      1.024
;   CTC-Wert TC0            =        243
;   Messzyklusfrequenz      =         64 Hz
;   Messzyklus alle         =         15,6 ms
;   Anzahl ADC-Messungen    =         16
;   Update-Zyklus           =        249,9 ms
;   Update-Frequenz         =          4,002 Hz
;
; ============================================
;   R E G I S T E R D E F I N I T I O N E N
; ============================================
;
.def rN0 = R0 ; Multi-Rechenregister, Byte 0
.def rN1 = R1 ; dto., Byte 1
.def rN2 = R2 ; dto., Byte 2
.def rN3 = R3 ; dto., Byte 3
.def rN4 = R4 ; dto., Byte 4
.def rN5 = R5 ; dto., Byte 5
.def rN6 = R6 ; dto., Byte 6
.def rCnt = R7 ; Byte-Zaehler fuer R/2R-Tabelle
.def rAdd = R8 ; Addierer fuer R/2R-Tabelle
.def rCntRst = R9 ; Byte-Zahler fuer Restart R/2R-Tabelle
.def rTabEL = R10 ; Tabellenende, nur LSB
.def rTabAL = R11 ; Tabellenanfang, LSB
.def rTabAH = R12 ; dto., MSB
.def rAdcL = R13 ; ADC-Ergebnissumme, LSB
.def rAdcH = R14 ; dto, MSB
.def rSreg = R15 ; Statusregister-Zwischenspeicher
.def rmp = R16 ; Vielzweckregister
.def rimp = R17 ; Vielzweckregister innerhalb Ints
.def rFlag = R18 ; Flaggenregister
  .equ bStart = 0 ; Wellenzug komplett, Null-Durchgang
  .equ bSetR2R = 1 ; Uebernahme-Flagge fuer neue Einstellung R2R
  .equ bSetCtc = 2 ; Uebernahme-Flagge fuer neue Einstellung CTC-Wert
.def rAdc  = R19 ; ADC-Werte-Zaehler
.def rAnd = R20 ; Und-Register fuer R/2R-Tabelle
.def rPot = R21 ; Poti-Stellungswert
; Frei: R22..R25
; Verwendet: R27:R26 = X fuer Kopieren R2R-Tabelle
; Verwendet: R29:R28 = Y SRAM-Adresse R/2R-Tabelle
; Verwendet: R31:R30 = Z fuer Tabellenlesen innerhalb Int
;
; ============================================
;       S R A M   D E F I N I T I O N E N
; ============================================
;
.DSEG
.ORG  SRAM_START
; Ablagepeicher fuer die aktuellen Einstellwerte
sCurrStart:
sR2RCurrL: ; 0x0060
  .Byte 1 ; Aktuelle R2R-Tabellen-Adresse, LSB
sR2RCurrH: ; 0x0061
  .Byte 1 ; dto., MSB
sR2RCurrAdd: ; 0x0062
  .Byte 1 ; Aktueller R2R-Tabellen-Addierer
sR2RCurrAnd: ; 0x0063
  .Byte 1 ; Aktueller R2R-Tabellen-Wiederholwert
sCmpACurrH: ; 0x0064
  .Byte 1 ; Aktueller TC1-CTC-Wert, MSB zuerst
sCmpACurrL: ; 0x0065
  .Byte 1 ; dto., LSB danach
sCondCurr: ; 0x0066
  .Byte 1 ; Aktueller Kondensator fuer RC-Netzwerk (0..15)
sFineTuneCurr: ; 0x0067
  .Byte 1 ; Aktueller Feinbereich Frequenzeinstellung (0..6)
sCourseTuneCurr: ; 0x0068
  .Byte 1 ; Aktueller Grobbbereich Frequenzeinstellung (0..3)
sPotCurr: ; 0x0069
  .Byte 1 ; Aktueller Potistand, (0..255)
sCurveCurr: ; 0x006A
  .Byte 1 ; Aktueller Kurvenform, (0..3)
sCurrEnd: ; Ende aktueller Datenbereich
sDummy1: ; 0x006B
  .Byte 0x0070-sCurrEnd ; Fuer Schoenheit
;
; Uebernahmepeicher fuer die Neueinstellung
sTransfStart:
sR2RL: ; 0x0070
  .Byte 1 ; R2R-Tabellen-Adresse, LSB
sR2RH: ; 0x0071
  .Byte 1 ; dto., MSB
sR2RAdd: ; 0x0072
  .Byte 1 ; R2R-Tabellen-Addierer
sR2RAnd: ; 0x0073
  .Byte 1 ; R2R-Tabellen-Wiederholwert
sCmpAH: ; 0x0074
  .Byte 1 ; TC1-CTC-Wert, MSB zuerst
sCmpAL: ; 0x0075
  .Byte 1 ; dto., LSB danach
sCond: ; 0x0076
  .Byte 1 ; Kondensator fuer RC-Netzwerk (0..15)
sFineTune: ; 0x0077
  .Byte 1 ; Feinbereich Frequenzeinstellung (0..6)
sCourseTune: ; 0x0078
  .Byte 1 ; Grobbbereich Frequenzeinstellung (0..3)
sPot: ; 0x0079
  .Byte 1 ; Potistand, (0..255)
sCurve: ; 0x007A
  .Byte 1 ; Kurvenform, (0..3)
    ; 00 = Saegezahn, 01 = Sinus, 10 = Dreieck, 11 = Rechteck
sTransfEnd: ; Ende Uebernahme-Datenbereich
;
sDummy2: ; 0x007B
  .Byte 0x007F-sTransfEnd ; Fuer Schoenheit

sDisplayTune: ; 0x007F
  .Byte 1 ; Displaybereiche, 0 (2-9,9999 Hz), 1 (10-99,999 Hz),
          ; 2 (100-999,99 Hz), 3 (1-2,4999 kHz),
	  ; 4 (2,5-4,9999 kHz), 5 (5-9,99 kHz), 6 (10-20 kHz)
;
; LCD-Ausgabepuffer
sLcdBuf: ; 0x0080 .. 0x0087
  .Byte 8 ; 8 Zeichen auf LCD
;
sDummy3: ; 0x0088
  .Byte 8
;
sLcdBufE: ; 0x0090
; R/2R Ausgabepuffer im SRAM
sR2R: ; 0x0090 .. 0x018F
  .byte 256
sR2REnd:
;
; ==============================================
;   R E S E T   U N D   I N T   V E K T O R E N
; ==============================================
;
.CSEG
.ORG $0000
  jmp Main ; Reset-Vektor
  reti ; INT0 Int Vektor 1
  nop
  reti ; INT1 Int Vektor 2
  nop
  reti ; TC2COMP Int Vektor 3
  nop
  reti ; TC2OVF Int Vektor 4
  nop
  reti ; TC1CAPT Int Vektor 5
  nop
  rjmp TC1CAIsr ; TC1COMPA Int Vektor 6
  nop
  reti ; TC1COMPB Int Vektor 7
  nop
  reti ; TC1OVF Int Vektor 8
  nop
  reti ; TC0OVF Int Vektor 9
  nop
  reti ; SPI_STC Int Vektor 10
  nop
  reti ; USART_RXC Int Vektor 11
  nop
  reti ; USART_UDRE Int Vektor 12
  nop
  reti ; USART_TXC Int Vektor 13
  nop
  rjmp AdcIsr ; ADC Int Vektor 14
  nop
  reti ; EE_RDY Int Vektor 15
  nop
  reti ; ANA_COMP Int Vektor 16
  nop
  reti ; TWI Int Vektor 17
  nop
  reti ; INT2 Int Vektor 18
  nop
  rjmp Tc0Isr ; TC0COMP Int Vektor 19
  nop
  reti ; SPM_RDY Int Vektor 20
  nop
;
; ==========================================
;    I N T E R R U P T   S E R V I C E
; ==========================================
;
; CTC-compareA-Interrupt: Ausgabe naechster DAC-Wert
TC1CAIsr: ; 6 Taktzyklen fuer Int und Vektor-RJMP
  in rSreg,SREG ; Status retten, +1 = 7
  ld rimp,Y+ ; Naechster Tabellenwert, +2 = 9
  out pDacOut,rimp ; in DAC schreiben, +1 = 10
  cpi YL,Low(sR2REnd) ; Ende Puffer? +1 = 11
  brne TC1CAIsrRet ; +1 = 12, +2 = 13
  ldi YH,High(sR2R) ; Neustart ; +1 = 13
  sbr rFlag,1<<bStart ; +1 = 14
TC1CAIsrRet: ; 13/14 Takte
  out SREG,rSreg ; Status wieder herstellen, +1 = 14/15
  reti ; + 4 = 18/19
;
; TC0 Compare Match Interrupt
Tc0Isr: ; 6 Taktzyklen fuer Int und Vektor-Sprung
  clt ; Loesche T-Flagge, +1 = 7
  reti ; +4 = 11
;
; ADC Conversion complete Interrupt
AdcIsr: ; 6 Taktzyklen fuer Int und Vektor-Sprung
  set ; Setze T-Flagge, +1 = 7
  reti ; +4 = 11
;
; ============================================
;    H A U P T P R O G R A M M    I N I T
; ============================================
;
Main:
; Initiiere Stapel
  ldi rmp, HIGH(RAMEND) ; Initiiere MSB Stapel
  out SPH,rmp
  ldi rmp, LOW(RAMEND) ; Initiiere LSB Stapel
  out SPL,rmp
.if debug_insert == 1 ; Debuggen der Verschiebung
  ldi ZH,High(sLcdBuf)
  ldi ZL,Low(sLcdBuf)
  ldi rmp,' '
  st Z+,rmp
  st Z+,rmp
  st Z+,rmp
  ldi rmp,'1'
  debug_insert1:
    st Z+,rmp
    inc rmp
    cpi rmp,'7'
    brne debug_insert1
  ldi rmp,InsertDecPos
  mov rN0,rmp
  ldi rmp,InsertDecChar
  rcall Insert
  ldi rmp,InsertThdPos
  mov rN0,rmp
  ldi rmp,InsertThdChar
  rcall Insert
  debug_insert_loop:
    rjmp debug_insert_loop
  .endif
; Initiiere Port A
  in rmp,PORTA ; Lese Ausgaberegister
  ori rmp,mSwPu ; Setze Pull-Up-Bits Schalter
  out PORTA,rmp
  in rmp,DDRA ; Lese Richtungsregister
  andi rmp,0xE0 ; Loesche Pull-Up Pins und ADC0
  out DDRA,rmp
; Init Port B
  in rmp,pRcfDir ; Lese Richtungsregister RC-Filter-Port
  ori rmp,mRcf ; Setze RC-Filter-Bits
  out pRcfDir,rmp ; Setze Richtungsbits RC-Filter
  in rmp,pRcfOut ; Lese Ausgangsport RC-Filter-Port
  andi rmp,0xFF-mRcf ; Maskiere RC-Filter-Bits
  out pRcfOut,rmp
; Init Port C
  ; (Erfolgt durch lcd.inc)
; Init Port D
  ldi rmp,mDac8 ; 8-Bit-DAC
  out pDacDir,rmp ; in Richtungsregister
  clr rmp ; auf Anfang Signal
  out pDacOut,rmp
;
.if (debug_CtcCalc == 0)&&(debug_NoLcd == 0)
; Init LCD
  rcall LcdInit ; LCD-Anzeige initiieren
  ldi ZH,High(2*Charcodes) ; Spezialzeichen
  ldi ZL,Low(2*Charcodes)
  rcall LcdSpec ; Spezialzeichen setzen
  clr ZH ; Cursor auf Anfang
  clr ZL
  rcall LcdPos
  ldi ZH,High(2*LcdInitText) ; Z auf Text
  ldi ZL,Low(2*LcdInitText)
  rcall LcdText
  ldi rmp,20 ; warte 1 s
LcdStart1:
  rcall LcdWait50ms ; Warte fuer 50 ms
  dec rmp
  brne LcdStart1
  .endif
.if debug_lcdonly ; Nur LCD testen
LcdOnlyLoop:
  rjmp LcdOnlyLoop
  .endif
; Init ADC
  ldi rAdc,cAdcAdd ; Anzahl ADC-Werte zum Addieren
  ldi rmp,(1<<ADTS1)|(1<<ADTS0) ; TC0 Compare match startet ADC
  out SFIOR,rmp ; Special Function I/O Port Register
  ldi rmp,1<<REFS0 ; AD-Kanal 0, REFS0=UB
  out ADMUX,rmp
  ; Erste Wandlung beginnen
  .set cADStart = (1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADATE)
  .set cADStart = cADStart+(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
  ldi rmp,cADStart
  out ADCSRA,rmp
; Kurvenausgabe starten
InitCurve:
  ldi rmp,Curve ; Eingestellte Kurvenform
  lsl rmp ; * 2
  ldi ZH,High(2*WaveTabAddr) ; Stelle initiale Kurvenform ein
  ldi ZL,Low(2*WaveTabAddr)
  add ZL,rmp ; Zur Tabellenadresse addieren
  ldi rmp,0
  adc ZH,rmp ; Und Carry addieren
  lpm rmp,Z+ ; LSB Wellenformadresse in Zwischenspeicher
  lpm ZH,Z ; und Adresse MSB lesen
  mov ZL,rmp ; LSB in ZL
  ldi rmp,cAdd ; Addierer (Kurvenvervielfacher
  mov rAdd,rmp
  sts sR2RAdd,rAdd
  ldi rAnd,cAnd ; Und fuer Kurvenneustart
  sts sR2RAnd,rAnd
.if (debug_CtcCalc == 0)&&(debug_TC1Off == 0)
  rcall Convert2Sram ; Schreibe Kurve in SRAM
  .endif
.if debug_CtcCalc == 1
  ; Debug-Intercept
  ldi rmp,2*cCourseTune+8*cCurve ; Stelle Schalter her
  out PORTA,rmp
  ldi rmp,0x1E ; Schalte Schalter als Outputs
  out DDRA,rmp
  ldi rmp,cPot ; Lege Potistellung fest
  sts sPot,rmp ; Potistellung speichern
  rcall debug_CtcCalc_start
  .if debug_Transfer == 1
    ; Uebernahme des Datensatzes
    ldi rFlag,(1<<bStart)|(1<<bSetR2R)
    rcall Transfer
    .endif
  Debug_CtcCalc_Loop:
    rjmp Debug_CtcCalc_Loop
  .endif
; Timer starten
  ; TC0 ist Update-Timer zum ADC lesen
  ;   Compare Match startet ADC-Wandlung
  ldi rmp,cTc0Comp ; Compare-Wert
  out OCR0,rmp
  .if debug_Second == 1
    ; Stelle vorgewaehlte Schalterstellungen ein
    ldi rmp,2*cCourseTune2+8*cCurve2 ; Stelle Schalter her
    out PORTA,rmp
    out PINA,rmp ; Geht bei ATmega16, kein Invertier-Feature
    ldi rmp,0x1E ; Schalte Schalter als Outputs
    out DDRA,rmp
    ; Fuer Simulation beschleunigt
    ldi rmp,cTCCR02 ; CTC, Prescaler = 64
    .else
    ; Normale Rate
    ldi rmp,(1<<CS02)|(1<<CS00)|(1<<WGM01) ; CTC, Presc=1024
    .endif
  out TCCR0,rmp
; TC1 als DA-Wandler-Treiber starten
  ldi YH,High(sR2R) ; Y als Zeiger auf DAC-Puffer im SRAM
  ldi YL,Low(sR2R)
  ldi rmp,High(cfstart) ; Startwert in Compare A
  out OCR1AH,rmp
  ldi rmp,Low(cfstart)
  out OCR1AL,rmp
  ldi rmp,(1<<WGM11)|(1<<WGM10) ; Kontrollregister A, Fast PWM OCR1A
  out TCCR1A,rmp
  ldi rmp,(1<<WGM13)|(1<<WGM12)|(1<<CS10) ; Fast PWM OCR1A, Presc = 1
  .if debug_TC1Off == 0
    out TCCR1B,rmp
    .endif
; Timer Interrupts
  ldi rmp,(1<<OCIE1A)|(1<<OCIE0) ; TC1 und TC0 Compare-A-Int
  out TIMSK,rmp
; Init Sleep und Interrupts
  ldi rmp,1<<SE ; Enable sleep
  out MCUCR,rmp
  sei
;
; ============================================
;     P R O G R A M M - S C H L E I F E
; ============================================
;
Loop:
  sleep ; Schlafen legen
  nop ; Dummy fuer Aufwecken, +1 = 1
  sbrc rFlag,bStart ; Start-Flagge gesetzt?, +1/2 = 2/3
  rcall Transfer ; ja, arbeite ab, +3 = 5/6
  brtc Loop ; ADC-Flagge gesetzt?
  rcall AdcCalc ; ja, arbeite ADC-Wert ab
  rjmp loop ; Zurueck nach Loop
;
; ==========================================
;     U P D A T E - B E H A N D L U N G
; ==========================================
;
; ADC-Flagge ist gesetzt (ADC-Wandlung komplett)
;
AdcCalc:
  clt ; Flagge loeschen
  .if debug_Second == 1
    ; Debug: Addiere voreingestellten Wert
    ldi rmp,Low(cAdc2) ; Addiere Einstellwert
    add rAdcL,rmp
    ldi rmp,High(cAdc2)
    adc rAdcH,rmp
    .else
    ; Normal: Addiere ADC-Wandler-Wert
    in rmp,ADCL ; AD-Wandler-Ergebnis lesen, LSB
    add rAdcL,rmp ; zu Ergebnis addieren
    in rmp,ADCH ; dto., MSB
    adc rAdcH,rmp
    .endif
  dec rAdc ; Zyklen abwaerts zaehlen
  breq AdcCalc1 ; 16 Zyklen fertig
  ret ; noch nicht
AdcCalc1:
  ldi rAdc,cAdcAdd ; Zyklen neu starten
  lsl rAdcL ; 1 mal links schieben
  rol rAdcH
  lsl rAdcL ; 2 mal links schieben
  rol rAdcH
  sts sPot,rAdcH ; Ergebnis in SRAM speichern
  mov rPot,rAdcH ; Und in Register
  clr rAdcL ; Summenspeicher loeschen
  clr rAdcH
;
; Ermittle den Frequenz-Sollwert aus dem Mittelwert der
; Potentiometer-Stellung
;
debug_CtcCalc_Start:
  ; Freqenzbereich aus Schalterstellung
.if debug_CtcCalc == 1
  ldi rmp,cCourseTune
  ldi rPot,cPot
  sts sPot,rPot
  .else
  .if debug_Second == 1
    ldi rmp,cCourseTune2
    ldi rPot,cPot2
    sts sPot,rPot
    .else
    clr rmp ; Hole Schalterstellung
    sbic pSwIn,bSw3 ; Schalter 3
    ori rmp,1 ; nach Bit 0
    sbic pSwIn,bSw4 ; Schalter 4
    ori rmp,2 ; nach Bit 1
    .endif
  .endif
  sts sCourseTune,rmp ; Speichere in SRAM
  clr rAdd ; rAdd fuer R2R-Tabelle auf 1
  inc rAdd
  ldi rAnd,0xFF ; rAnd fuer R2R-Tabelle auf alles
  ; Ermittle den Frequenz-Feinbereich
  cpi rmp,3 ; Bereich = 3?
  brcs SetFineTune ; Feinbereich = 0, 1 oder 2
  ; Grobbereich = 3, ermittle Feinbereich
  cpi rPot,cP2500 ; ab 2.500 Hz?
  brcs SetFineTune
  lsl rAdd ; Addiere 2 zu R2R-Tabelle
  lsr rAnd ; Neustart R2R-Tabelle bei 128
  inc rmp ; Feinbereich = 4
  cpi rPot,cP5000 ; ab 5.000 Hz?
  brcs SetFineTune
  lsl rAdd ; Addiere 4 zu R2R-Tabelle
  lsr rAnd ; Neustart R2R-Tabelle bei 64
  inc rmp ; Feinbereich = 5
  cpi rPot,cP10000 ; ab 10.000 Hz?
  brcs SetFineTune
  lsl rAdd ; Addiere 8 zu R2R-Tabelle
  lsr rAnd ; Neustart R2R-Tabelle bei 32
  inc rmp ; Feinbereich = 6
SetFineTune:
  sts sFineTune,rmp ; Feinbereich in SRAM ablegen
; Berechnung der Sollfrequenz
; Sollfrequenz = Poti*Steilheit+Basiswert
; Steilheit in rN2:rN1:rN0
; Addierer und Ergebnis in rN5:rN4:rN3
  lds R0,sCourseTune ; Grobbereich
  lsl R0 ; Mal zwei
  mov rmp,R0 ; Kopieren
  lsl rmp ; Mal vier
  add rmp,R0 ; Mal sechs
  ldi ZH,High(2*MultTab) ; Z auf Multiplikationstabelle
  ldi ZL,Low(2*MultTab)
  add ZL,rmp ; Zur Adresse addieren
  ldi rmp,0
  adc ZH,rmp ; Ueberlauf addieren
  lpm rN0,Z+ ; Steilheit, LSB
  lpm rN1,Z+ ; dto., MSB
  clr rN2 ; Multiplikator HSB leeren
  lpm rN3,Z+ ; Addierer in Ergebnis, Byte 1
  lpm rN4,Z+ ; dto., Byte 2
  lpm rN5,Z+ ; dto., Byte 3
  lpm rN6,Z+ ; dto., Byte 4
  lds rmp,sPot ; Potentiometerstellung
Multip:
  tst rmp ; Multiplikator = Null?
  breq MultipEnd
Multip1:
  lsr rmp ; naechstes Bit in Carry schieben
  brcc Multip2
  add rN3,rN0 ; Multiplikator zu Ergebnis addieren
  adc rN4,rN1
  adc rN5,rN2
Multip2:
  lsl rN0 ; Multiplikator mal zwei
  rol rN1
  rol rN2
  rjmp Multip
MultipEnd:
  ; Soll-Frequenz * 256/65536 ist in rN5:rN4:rN3
  ; Dividieren der Sollfrequenz / 256 und Runden
  ldi rmp,128
  cp rN3,rmp
  brcs ShiftUp
  ldi rmp,1 ; aufrunden
  add rN4,rmp
  ldi rmp,0
  adc rN5,rmp
ShiftUp:
  mov rN6,rN5 ; Um ein Register nach oben verschieben
  mov rN5,rN4
  lds rmp,sFineTune ; Feinbereich lesen
  lsl rmp ; Mal zwei
  lsl rmp ; Mal vier
  ldi ZH,High(2*DivCtcTab) ; CTC-Divisor-Tabelle
  ldi ZL,Low(2*DivCtcTab)
  add ZL,rmp ; Zu Tabelle addieren
  ldi rmp,0
  adc ZH,rmp ; Ueberlauf addieren
  lpm rN0,Z+ ; Lese Byte 1
  lpm rN1,Z+ ; dto., Byte 2
  lpm rN2,Z+ ; dto., Byte 3
  clr rN3 ; Byte 4 leeren
; Division 32 Bit durch 16 Bit
;   Dividend in rN3:rN2:rN1:rN0
;   Divisor in rN6:rN5
;   Ergebnis in YH:YL = R29:R28
Div32:
  clr ZL ; Ergebnis loeschen, LSB
  clr ZH ; dto., MSB
  ldi rmp,16 ; Anzahl Divisionsschritte
Div32a:
  lsl rN0 ; Dividend mal zwei, Byte 1
  rol rN1 ; dto., Byte 2
  rol rN2 ; dto., Byte 3
  rol rN3 ; dto., Byte 4
  brcs Div32Sub ; Ueberlauf in 33.tes Bit abfangen
  cp rN2,rN5 ; mit Divisor vergleichen, niedriges Byte
  cpc rN3,rN6 ; dto., hoeheres Byte und Carry
  brcs Div32b ; Null in Ergebnis schieben
Div32Sub:
  sub rN2,rN5 ; Divisor abziehen, niedriges Byte
  sbc rN3,rN6 ; dto., hoeheres Byte und Carry
  sec ; 1 in Ergebnis schieben
  rjmp Div32c
Div32b:
  clc ; Carry loeschen
Div32c:
  rol ZL ; Carry in Ergebnis rotieren
  rol ZH
  dec rmp ; weiter dividieren?
  brne Div32a ; ja
  sbiw ZL,1 ; CTC um Eins vermindern
  sts sCmpAH,ZH ; in Uebergabespeicher ablegen, MSB
  sts sCmpAL,ZL ; dto., LSB
.if debug_CtcCalc == 1
  ldi rmp,cCurve
  .else
  clr rmp ; Kurvenform ermitteln
  sbic pSwIn,bSw3 ; Schalter 3 lesen
  sbr rmp,1<<0 ; und bei Eins auswerten
  sbic pSwIn,bSw4 ; Schalter 4 lesen
  sbr rmp,1<<1 ; und bei Eins auswerten
  .endif
  sts sCurve,rmp ; Kurvenform in SRAM speichern
; Setze Wellenform
  ldi ZH,High(2*WaveTabAddr) ; Adressen Wellentabellen
  ldi ZL,Low(2*WaveTabAddr)
  lds rmp,sCurve
  lsl rmp
  add ZL,rmp ; Kurvenform addieren
  ldi rmp,0
  adc ZH,rmp
  lpm rmp,Z+ ; Lese Adresse Tabelle, LSB
  sts sR2RL,rmp
  lpm rmp,Z ; Adresse, MSB
  sts sR2RH,rmp
; Setze Wellenparameter
  ldi ZH,High(2*FineTab) ; Adresse Feintabelle
  ldi ZL,Low(2*FineTab)
  lds rmp,sFineTune ; Lese Feinbereich
  lsl rmp ; Mal zwei
  add ZL,rmp ; Zu Adresse addieren
  ldi rmp,0
  adc ZH,rmp
  lpm rmp,Z+ ; Lese Addierer
  sts sR2RAdd,rmp
  lpm rmp,Z ; Lese Restartmaske
  sts sR2RAnd,rmp
; Kondensatoren ermitteln
  lds rmp,sCurve ; Kurvenform lesen
  cpi rmp,1 ; Sinus?
  ldi rmp,0 ; Kondensatoren aus
  brne SetCond ; Bei allen Nicht-Sinus: Aus
  lds rmp,sCourseTune ; Grob
  lds rN0,sFineTune ; Feinbereich lesen
  mov rmp,rN0 ; Kopieren
  lsl rmp ; Mal zwei
  add rmp,rN0 ; Mal drei
  ldi ZH,High(2*CondTab) ; Z auf Kondensatortabelle, MSB
  ldi ZL,Low(2*CondTab) ; dto., LSB
  add ZL,rmp ; Zu Tabellenadresse addieren, LSB
  ldi rmp,0
  adc ZH,rmp ; Carry zu MSB
  lpm rN0,Z+ ; Lese Potentiometer-Schwelle
  lds rmp,sPot ; Lese Potentiometer-Stellung
  cp rmp,rN0 ; mit Schwelle vergleichen
  lpm rmp,Z+ ; Niedrigeren Kondensator lesen
  brcs SetCond ; niedriger als Schwelle
  lpm rmp,Z ; Hoeheren Kondensator lesen
SetCond:
  sts sCond,rmp ; Kondensatoren schreiben
; Ueberpruefen ob Kurvenform geaendert
  lds rN0,sR2RCurrH ; Bisherige Kurvenform
  lds rmp,sR2RH ; Neue Kurvenform
  cp rmp,rN0 ; Vergleiche
  brne SetR2RFlag ; Setze Kurvenform-Ausgabe-Flagge
  lds rN0,sR2RCurrAnd ; Bisheriges UND
  lds rmp,sR2RAnd ; Neues AND
  cp rmp,rN0 ; Vergleiche
  brne SetR2RFlag ; Setze Kurvenform-Ausgabe-Flagge
  lds rN0,sCmpACurrL ; Low-Byte CTC-Wert bisher
  lds rmp,sCmpAL ; Low-Byte CTC-Wert neu
  cp rmp,rN0 ; Vergleiche
  brne SetCtcFlag ; Ungleich
  lds rN0,sCmpACurrH ; High-Byte CTC-Wert bisher
  lds rmp,sCmpAH ; High-Byte CTC-Wert neu
  cp rmp,rN0 ; Vergleiche
  brne SetCtcFlag ; Ungleich
  ret ; Keine Flagge setzen
SetR2RFlag:
  sbr rFlag,1<<bSetR2R
  ret
SetCtcFlag:
  sbr rFlag,1<<bSetCtc
  ret
;
; Tabelle der Feinbereichseinstellungen
;   Erstes Byte: Addierer, 1 = alle Bytes,
;     2, 4, 8: Mehrere Kurven
;   Zweites Byte: UND-Wert, 0xFF = alle Bytes,
;     0x7F, 0x3F, 0x1F: Mehrere Kurven
FineTab:
  .db 1,0xFF ; Feinbereich 0
  .db 1,0xFF ; Feinbereich 1
  .db 1,0xFF ; Feinbereich 2
  .db 1,0xFF ; Feinbereich 3
  .db 2,0x7F ; Feinbereich 4
  .db 4,0x3F ; Feinbereich 5
  .db 8,0x1F ; Feinbereich 6
;
; Tabelle der Kondensatoren der Feinbereiche
;   Byte 1: Von sPot = 0 bis sPot = Byte 1
;   Byte 2: Kondensatorwert
;   Byte 3: Kondensatorwert oberhalb
CondTab:
  .db cPC0,cC0L,cC0H, cPC1,cC1L,cC1H ; Feinbereiche 0 und 1
  .db cPC2,cC2L,cC2H, cPC3,cC3L,cC3H ; Feinbereiche 2 und 3
  .db cPC46,cC46L,cC46H, cPC46,cC46L,cC46H ; Feinbereiche 4 und 5
  .db cPC46,cC46L,cC46H, 0 ; Feinbereich 6
;
; Steilheit und Addierer Sollfrequenzberechnung
; Multiplikationstabelle: y = a * x + b
;   Erstes Wort: Gradient a, 16 Bit
;   Zweites Wort: Achsenabschnitt b, LWRD
;   Drittes Wort: Achsenabschnitt b, HWRD
MultTab:
  .dw cM0,LWRD(cA0),HWRD(cA0) ; 2..20 Hz
  .dw cM1,LWRD(cA1),HWRD(cA1) ; 20..200 Hz
  .dw cM2,LWRD(cA2),HWRD(cA2) ; 200..2000 Hz
  .dw cM3,LWRD(cA3),HWRD(cA3) ; 2..20 kHz
;
; Dividenden der Feinbereiche um CTC zu errechnen
;   Erstes Wort: LWRD cDiv, Zweites Word HWRD cDiv
DivCtcTab:
  .dw LWRD(cDiv01),HWRD(cDiv01) ; 2..20 Hz
  .dw LWRD(cDiv01),HWRD(cDiv01) ; 20..200 Hz
  .dw LWRD(cDiv23),HWRD(cDiv23) ; 200..2000 Hz
  .dw LWRD(cDiv23),HWRD(cDiv23) ; 2..2,5 kHz
  .dw LWRD(cDiv4),HWRD(cDiv4) ; 2,5..5 kHz
  .dw LWRD(cDiv5),HWRD(cDiv5) ; 5..10 kHz
  .dw LWRD(cDiv6),HWRD(cDiv6) ; 10..20 kHz
;
; ==========================================
;   S T A R T ,   N U L L D U R C H G A N G
; ==========================================
;
; Start-Flagge gesetzt, Transfer der neuen Werte
Transfer: ; 5 Takte nach Sleep
  cbr rFlag,1<<bStart ; Setze Flagge zurueck, +1 = 6
  sbrc rFlag,bSetR2R ; Uebernahmeflagge Welle gesetzt?, +1/2 = 7/8
  rcall NewWave ; Neue Wennform einstellen, +3 = 10
  sbrc rFlag,bSetCtc ; Uebernahmeflagge CTC-Wert gesetzt?, +
  rcall NewCtc ; Neuen CTC-Wert einstellen
  ret ; Nein, ignoriere Neustart
;
; Neue Wellenform einstellen
NewWave: ; 10 Takte
; sR2RL: ; 0x0070
; sR2RH: ; 0x0071
; sR2RAdd: ; 0x0072
; sR2RAnd: ; 0x0073
  lds ZL,sR2RL ; Lade Wellenform-Adresse, LSB, +2 = 12
  lds ZH,sR2RH ; dto., Lade MSB, +2 = 14
  lds rAdd,sR2RAdd ; Lade Addierer, +2 = 16
  lds rAnd,sR2RAnd ; Lade Wiederholparameter, +2 = 18
;.if debug_Transfer == 0
  rcall Convert2Sram ; +3 = 21
;  .endif
NewCtc:
; sCmpAH: ; 0x0074
; sCmpAL: ; 0x0075
; sCond: ; 0x0076
; sFineTune: ; 0x0077
; sCourseTune: ; 0x0078
; sPot: ; 0x0079
; sCurve: ; 0x007A
  lds rmp,sCmpAH ; Lese neuen CTC-Wert, MSB
  out OCR1AH,rmp ; Schreibe neuen Wert, MSB
  lds rmp,sCmpAL ; dto., lese LSB
  out OCR1AL,rmp ; dto., schreibe LSB
  in rmp,pRcfOut ; Lese Kondesator-Port
  andi rmp,255-mRcf ; Loesche Kondesatoren-Bits
  push R0 ; Sichern R0
  mov R0,rmp ; Bits in R0
  lds rmp,sCond ; Lese Kondensatoren
  andi rmp,mRcf ; Isoliere Kondensatoren-Bits
  or rmp,R0 ; Vereine Bits
  out pRcfOut,rmp ; Schreibe in Kondensator-Port
  pop R0 ; R0 wieder herstellen
Copy2Curr:
  ; Kopiere neuen Stand ueber bisherigen Stand
  ldi XH,High(sCurrStart)
  ldi XL,Low(sCurrStart)
  ldi ZH,High(sTransfStart)
  ldi ZL,Low(sTransfStart)
Copy2Curr1:
  ld rmp,Z+ ; Lese neuen Wert
  st X+,rmp ; Ueberschreibe bisherigen Wert
  cpi ZL,Low(sTransfEnd) ; Ende erreicht?
  brne Copy2Curr1 ; Nein, weiter
  cbr rFlag,(1<<bSetR2R)|(1<<bSetCtc) ; Flaggen loeschen
; Ermittle den Displaybereich aus Feinbereich und Potistand
;   Displaybereiche:
;   0: 2..9,999 Hz, Fein 0 bis 10 Hz, Display = 0
;   1: 10..99,999 Hz, Fein 0 ab 10 Hz, Fein 1 bis 99,999 Hz
;   2: 100..999,999 Hz, Fein 1 ab 100 Hz, Fein 2 bis 999,99 Hz
;   3: 1000..2499,99 Hz, Fein 2 ab 1 kHz, Fein 3
;   4: 2500..4999,9 Hz, Fein 4
;   5: 5000..9999,9 Hz, Fein 5
;   6: 10000..20000 Hz, Fein 6
  lds rmp,sFineTuneCurr ; Lese aktuellen Feinbereich
  cpi rmp,3 ; Feinbereich ab 3?
  brcc SetDisplayTune ; Ab 3 Displaybereich = Feinbereich
  cpi rmp,1 ; Feinbereich 1?
  brcs DisplayTune0 ; Feinbereich 0
  breq DisplayTune1 ; Feinbereich 1
  lds rmp,sPotCurr ; Feinbereich=2, Lese Potistand
  cpi rmp,cP1000 ; Poti kleiner 1000?
  ldi rmp,2 ; Displaybereich = 2
  brcs SetDisplayTune
  inc rmp ; Displaybereich = 3
  rjmp SetDisplayTune
DisplayTune0: ; Feinbereich = 0
  lds rmp,sPotCurr ; Lese Potistand
  cpi rmp,cP10 ; Pruefe 10 Hz-Schwelle
  ldi rmp,0 ; Displaybereich 0, kleiner 10 Hz Schwelle
  brcs SetDisplayTune ; Displaybereich = 0
  inc rmp ; Displaybereich = 1
  rjmp SetDisplayTune
DisplayTune1: ; Feinbereich = 1
  lds rmp,sPotCurr ; Lese Potistand
  cpi rmp,cP100 ; 100 Hz Schwelle
  ldi rmp,1 ; Displaybereich = 1
  brcs SetDisplayTune
  inc rmp ; Displaybereich = 2
SetDisplayTune:
  sts sDisplayTune,rmp
; Lade Dividend des Displaybereichs
  ldi ZH,High(2*DivTab) ; 32-Bit-Dividendentabelle in Z, MSB
  ldi ZL,Low(2*DivTab) ; dto., LSB
  lds rmp,sDisplayTune ; Lese Displaybereich
  lsl rmp ; Mal zwei
  lsl rmp ; Mal vier
  add ZL,rmp ; Zur Tabellenadresse addieren
  ldi rmp,0
  adc ZH,rmp ; Ueberlauf addieren
  lpm rN0,Z+ ; Lese Dividend, Byte 1
  lpm rN1,Z+ ; dto., Byte 2
  lpm rN2,Z+ ; dto., Byte 3
  lpm rN3,Z+ ; dto., Byte 4
; Division
Div24:
  clr rN4 ; Ergebnis loeschen, Byte 1
  clr rN5 ; dto., Byte 2
  clr rN6 ; dto., Byte 3
  lds ZL,sCmpACurrL ; Lese Low-Byte CTC nach ZL
  lds ZH,sCmpACurrH ; dto., High-Byte nach ZH
  adiw ZL,1 ; erhoehe um 1
  ldi rmp,17 ; 17 Bits dividieren
  clc
Div24a:
  brcs Div24b ; Eins ist herausgeschoben, subtrahiere unmittelbar
  cp rN2,ZL ; vergleiche Low-Byte
  cpc rN3,ZH ; dito, High-Byte
  brcs Div24c ; Nicht subtrahieren
Div24b:
  sub rN2,ZL ; Low-Byte abziehen
  sbc rN3,ZH ; dito, High-Byte
  sec ; Carry-Flagge setzen
  rjmp Div24d
Div24c:
  clc ; Carry loeschen
Div24d:
  rol rN4 ; Carry in Ergebnis schieben
  rol rN5
  rol rN6
  lsl rN0 ; Dividend links schieben
  rol rN1
  rol rN2
  rol rN3
  dec rmp ; Zaehler um 1 vermindern
  brne Div24a ; weiter dividieren
  cp rN2,ZL
  cpc rN3,ZH
  brcs DecimalConversion ; nicht Aufrunden
  inc rN4 ; Aufrunden
  brne DecimalConversion
  inc rN5 ; Aufrunden
  brne DecimalConversion
  inc rN6
; Divisionsergebnis ist in rN6:rN5:rN4
DecimalConversion:
  ldi XH,High(sLcdBuf) ; X zeigt auf Puffer
  ldi XL,Low(sLcdBuf)
  lds rmp,sCurve
  st X+,rmp ; Modezeichen in LCD-Ausgabepuffer
  ldi rmp,' '
DecConv1:
  st X+,rmp ; Leerzeichen in den Puffer
  cpi XL,Low(sLcdBuf+8)
  brne DecConv1
  ldi XH,High(sLcdBuf+2) ; Auf Anfang Ausgabe
  ldi XL,Low(sLcdBuf+2)
  clr rN3 ; Fuehrende Nullen unterdruecken
  inc rN3 ; auf Eins
  ldi ZH,High(2*Dec6Tab) ; Dezimaltabelle in Z
  ldi ZL,Low(2*Dec6Tab)
DecConv2:
  lpm rN0,Z+ ; Lese Dezimal-Digit-Binaerzahl
  lpm rN1,Z+
  lpm rN2,Z+
  tst rN0 ; Ende der Tabelle?
  breq DecConv6
  ldi rmp,0xFF ; Ergebnis in rmp
DecConv3:
  inc rmp ; Naechstes Ergebnis
  sub rN4,rN0 ; Dezimalzahl abziehen, Byte 1
  sbc rN5,rN1 ; dto., Byte 2
  sbc rN6,rN2 ; dto., Byte 3
  brcc DecConv3 ; Weiter abziehen
  add rN4,rN0 ; Letzte Subtraktion rueckgaengig, Byte 1
  adc rN5,rN1 ; dto., Byte 2
  adc rN6,rN2 ; dto., Byte 3
  tst rmp ; Ergebnis Null?
  breq DecConv5 ; Ist Null
  clr rN3 ; Fuehrende-Nullen-Flagge loeschen
DecConv4:
  ; Nicht unterdruecken
  subi rmp,-'0' ; Digit in ASCII-Ziffer wandeln
  st X+,rmp
  rjmp DecConv2
DecConv5:
  tst rN3 ; Fuehrende Nullen unterdruecken?
  breq DecConv4 ; Nein, als '0' ausgeben
  ldi rmp,' ' ; Fuehrende Null unterdruecken
  st X+,rmp ; in LCD-Puffer
  rjmp DecConv2 ; Weiter wandeln
DecConv6:
  ldi rmp,'0'
  add rmp,rN4 ; Letzte Ziffer
  st X,rmp ; in LCD-Puffer
; Setze Dezimalpunkt
  ldi ZH,High(2*DecPtPos) ; Dezimalpunkt-Positionen
  ldi ZL,Low(2*DecPtPos)
  lds rmp,sDisplayTune ; Lade Anzeigebereich
  add ZL,rmp ; Addiere
  lpm rN0,Z ; Lese Dezimalpunkt-Position
  tst rN0 ; Position = 0?
  breq Thousand
  .if LangEn == 1
     ldi rmp,'.'
     .else
     ldi rmp,','
     .endif
  rcall Insert
Thousand:
  adiw ZL,8 ; Lese Tausendertrennung
  lpm rN0,Z
  tst rN0 ; Position = 0?
  breq DisplayBuffer ; Puffer ausgeben
  .if LangEn == 1
    ldi rmp,','
    .else
    ldi rmp,'.'
    .endif
  rcall Insert
DisplayBuffer:
  clr ZH ; Position auf LCD einstellen
  clr ZL
.if debug_NoLcd == 0
  rcall LcdPos
  .endif
  ldi ZH,High(sLcdBuf) ; Zeige auf LCD-Puffer-Anfang
  ldi ZL,Low(sLcdBuf)
  ldi rmp,8 ; Acht Zeichen
.if debug_NoLcd == 0
  rcall LcdSram ; Puffer ausgeben
  .endif
  ret
;
; Insert: Fuege Zeichen in rmp in den Puffer
;   an der Position rN0 ein
Insert:
  push ZH ; Sichern Z
  push ZL
  ldi ZH,High(sLcdBuf+1) ; An den Anfang des Zahlenbereichs
  ldi ZL,Low(sLcdBuf+1)
Insert1:
  ldd rN1,Z+1 ; Lese naechstes Zeichen im Puffer
  st Z+,rN1 ; Schreibe Zeichen
  dec rN0 ; Anzahl Verschiebungen vermindern
  brne Insert1 ; weiter
  st Z,rmp ; Setze Dezimal- oder Tausenderpunkt
  pop ZL
  pop ZH
  ret
;
; Divisortabelle
DivTab:
  .dw LWRD(cND0),HWRD(cND0)
  .dw LWRD(cND1),HWRD(cND1)
  .dw LWRD(cND2),HWRD(cND2)
  .dw LWRD(cND3),HWRD(cND3)
  .dw LWRD(cND4),HWRD(cND4)
  .dw LWRD(cND5),HWRD(cND5)
  .dw LWRD(cND6),HWRD(cND6)
;
; Dezimaltabelle 6 Ziffern = 24 Bit
Dec6Tab:
  .db Byte1(100000),Byte2(100000)
  .db Byte3(100000),Byte1(10000)
  .db Byte2(10000),Byte3(10000)
  .db Byte1(1000),Byte2(1000)
  .db Byte3(1000),Byte1(100)
  .db Byte2(100),Byte3(100)
  .db Byte1(10),Byte2(10)
  .db Byte3(10),0
  .db 0,0
;
; Dezimalpuunkt-Positionen der Displaybereiche
;   Displ. Anzeige  Dezimal 1000er
;     0    M 9,9999   +2      +0
;     1    M 99,999   +3      +0
;     2    M 999,99   +4      +0
;     3    M9.999,9   +5      +1
;     4    M9.999,9   +5      +1
;     5    M9.999,9   +5      +1
;     6    M 19.999   +0      +3
DecPtPos:
  .db 2,3,4,5,5,5,0,0
  .db 0,0,0,1,1,1,3,0 ; +8 Bytes Versatz
;
; =================================
;      S R A M - P U F F E R
; =================================
;
; SRAM-Puffer mit Tabellenwerten befuellen
;   Eingang: Z zeigt auf Tabellenanfang
;     rAdd enthaelt Addierer fuer R/2R-Tabellenwerte
;       1=8-Bit, 2=7-Bit, 4=6-Bit, 8=5-Bit
;     rAnd enthaelt Und-Register fuer R/2R-Neustart
;       8 Bit = 0; 7 Bit = 127, 6 Bit = 63, 5 Bit = 31
;   Benutzt: rCnt = R7 ; Zaehler fuer Anzahl R/2R-Tabelle
;     rCntRst: Byte-Zahler fuer Neustart R/2R-Tabelle
;
Convert2Sram: ; 21 Takte
  push XH ; Sichern X, MSB, +2 = 23
  push XL ; dto., LSB, +2 = 25
  ldi XH,High(sR2R) ; X zeigt auf SRAM-Puffer, MSB, +1 = 26
  ldi XL,Low(sR2R) ; dto., LSB, +1 = 27
  clr rCnt ; Byte-Zaehler auf Null, +1 = 28
  push ZH ; Z sichern, MSB, +2 = 30
  push ZL ; dto., LSB, +2 = 32
Convert2Sram1:
  lpm rmp,Z ; Lese Wert aus Sinustabelle, +3 = 35
  st X+,rmp ; Kopiere in SRAM-Puffer, +2 = 37
  add ZL,rAdd ; Addieren Tabellenwert
  ldi rmp,0 ; Ueberlauf addieren
  adc ZH,rmp ; zu MSB
  inc rCnt ; Anzahl Bytes zaehlen
  breq Convert2SramEnd ; Null, fertig
  mov rmp,rCnt ; Kopiere Byte-Zaehler
  and rmp,rAnd ; Isoliere Bits
  brne Convert2Sram1
  pop ZL ; Anfangsadresse setzen, LSB
  pop ZH ; dto., MSB
  push ZH ; Z wieder sichern, MSB
  push ZL ; dto., LSB
  rjmp Convert2Sram1
Convert2SramEnd:
  pop ZL
  pop ZH
  pop XL
  pop XH
  ret

; LCD-Routinen
;
; LCD-Codezeichen fuer Kurvenform-Darstellung
; Tabelle der Codezeichen
Charcodes:
.db 64,0,0,1,3,5,9,17,1,0 ; Z = 0, Saegezahn
.db 72,0,0,8,20,20,21,5,2,0 ; Z = 1, Sinus
.db 80,0,0,4,4,10,10,17,17,0 ; Z = 2, Dreieck
.db 88,0,0,14,10,10,10,10,27,0 ; Z = 3, Rechteck
.db 0,0 ; Ende der Tabelle
;
.include "lcd.inc" ; Import der LCD-Routinen
;
LcdInitText:
.db "SigGenV1",0xFE,0xFE ; Textausgabe bei Init
;
; Tabellen laden
.include "wavetab.inc"
;
; Tabellenanfangsadressen
;   00 = Saegezahn, 01 = Sinus, 10 = Dreieck, 11 = Rechteck
WaveTabAddr:
  .dw 2*SawTooth
  .dw 2*Sine
  .dw 2*Triangle
  .dw 2*Rectangle
;
; Ende Quellcode
; Copyright
.db "(C)2018 by Gerhard Schmidt  " ; menschenlesbar
.db "C(2)18 0ybG reahdrS hcimtd  " ; wortgerecht
;



Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

9.2 Tabelle der Wellenformen

Und hier die zugehörige Wellentabelle als Include-Datei (als Assembler-Include-Datei hier).

Die Tabellen werden in das SRAM kopiert, wo der CTC-Interrupt die Werte Byte-für-Byte abholt und in den DAC-Port schreibt.

Ab 2,5 kHz wird beim Kopieren ins SRAM nur jeder zweite Wert der Tabelle kopiert, dafür wird beim 128.ten Byte wieder von vorn begonnen. Dadurch wird eine Verdopplung der Frequenz erreicht (hier als 7-Bit-Ansteuerung bezeichnet). Ab 5 kHz wird nur jeder vierte Wert der Tabelle kopiert, ab 10 kHz jeder achte.

Alle Tabellen sind auf eine Amplitude von 3 Vpp ausgelegt, um den Eingang des FET-Operationsverstärkers nicht zu übersteuern. Da der CA3140 schon ab unter 0,0 V Eingangsspannung korrekt arbeitet, wurde keine Untergrenze addiert (wie dies bei einem 741-Opamp nötig gewesen wäre).

;
; Tabellen fuer Kurvenverlauf
;
; Sinus, 270 Grad phasenverschoben
;
Sine: ; Sinustabelle 8 bits, 3V max
	.db 0,0,0,0,0,1,1,1 ; n=0 - 7
	.db 1,2,2,3,3,4,4,5 ; n=8 - 15
	.db 6,7,7,8,9,10,11,12 ; n=16 - 23
	.db 13,14,15,16,17,19,20,21 ; n=24 - 31
	.db 22,24,25,27,28,29,31,32 ; n=32 - 39
	.db 34,36,37,39,40,42,44,45 ; n=40 - 47
	.db 47,49,51,53,54,56,58,60 ; n=48 - 55
	.db 62,63,65,67,69,71,73,75 ; n=56 - 63
	.db 77,78,80,82,84,86,88,90 ; n=64 - 71
	.db 91,93,95,97,99,100,102,104 ; n=72 - 79
	.db 106,108,109,111,113,114,116,117 ; n=80 - 87
	.db 119,121,122,124,125,126,128,129 ; n=88 - 95
	.db 131,132,133,134,136,137,138,139 ; n=96 - 103
	.db 140,141,142,143,144,145,146,146 ; n=104 - 111
	.db 147,148,149,149,150,150,151,151 ; n=112 - 119
	.db 152,152,152,152,153,153,153,153 ; n=120 - 127
	.db 153,153,153,153,153,152,152,152 ; n=128 - 135
	.db 152,151,151,150,150,149,149,148 ; n=136 - 143
	.db 147,146,146,145,144,143,142,141 ; n=144 - 151
	.db 140,139,138,137,136,134,133,132 ; n=152 - 159
	.db 131,129,128,126,125,124,122,121 ; n=160 - 167
	.db 119,117,116,114,113,111,109,108 ; n=168 - 175
	.db 106,104,102,100,99,97,95,93 ; n=176 - 183
	.db 91,90,88,86,84,82,80,78 ; n=184 - 191
	.db 76,75,73,71,69,67,65,63 ; n=192 - 199
	.db 62,60,58,56,54,53,51,49 ; n=200 - 207
	.db 47,45,44,42,40,39,37,36 ; n=208 - 215
	.db 34,32,31,29,28,27,25,24 ; n=216 - 223
	.db 22,21,20,19,17,16,15,14 ; n=224 - 231
	.db 13,12,11,10,9,8,7,7 ; n=232 - 239
	.db 6,5,4,4,3,3,2,2 ; n=240 - 247
	.db 1,1,1,1,0,0,0,0 ; n=248 - 255
;
; Dreiecktabelle
;
Triangle: ; Dreiecktabelle 8 bits, 3V max
	.db 0,1,2,4,5,6,7,8 ; n=0 - 7
	.db 10,11,12,13,14,16,17,18 ; n=8 - 15
	.db 19,20,22,23,24,25,26,27 ; n=16 - 23
	.db 29,30,31,32,33,35,36,37 ; n=24 - 31
	.db 38,39,41,42,43,44,45,47 ; n=32 - 39
	.db 48,49,50,51,53,54,55,56 ; n=40 - 47
	.db 57,59,60,61,62,63,65,66 ; n=48 - 55
	.db 67,68,69,71,72,73,74,75 ; n=56 - 63
	.db 77,78,79,80,81,82,84,85 ; n=64 - 71
	.db 86,87,88,90,91,92,93,94 ; n=72 - 79
	.db 96,97,98,99,100,102,103,104 ; n=80 - 87
	.db 105,106,108,109,110,111,112,114 ; n=88 - 95
	.db 115,116,117,118,120,121,122,123 ; n=96 - 103
	.db 124,126,127,128,129,130,131,133 ; n=104 - 111
	.db 134,135,136,137,139,140,141,142 ; n=112 - 119
	.db 143,145,146,147,148,149,151,152 ; n=120 - 127
	.db 153,152,151,149,148,147,146,145 ; n=128 - 135
	.db 143,142,141,140,139,137,136,135 ; n=136 - 143
	.db 134,133,131,130,129,128,127,126 ; n=144 - 151
	.db 124,123,122,121,120,118,117,116 ; n=152 - 159
	.db 115,114,112,111,110,109,108,106 ; n=160 - 167
	.db 105,104,103,102,100,99,98,97 ; n=168 - 175
	.db 96,94,93,92,91,90,88,87 ; n=176 - 183
	.db 86,85,84,82,81,80,79,78 ; n=184 - 191
	.db 77,75,74,73,72,71,69,68 ; n=192 - 199
	.db 67,66,65,63,62,61,60,59 ; n=200 - 207
	.db 57,56,55,54,53,51,50,49 ; n=208 - 215
	.db 48,47,45,44,43,42,41,39 ; n=216 - 223
	.db 38,37,36,35,33,32,31,30 ; n=224 - 231
	.db 29,27,26,25,24,23,22,20 ; n=232 - 239
	.db 19,18,17,16,14,13,12,11 ; n=240 - 247
	.db 10,8,7,6,5,4,2,1 ; n=248 - 255
;
; Saegezahn-Tabelle
;
Sawtooth: ; Saegezahntabelle 8 bits, 3V max
	.db 0,1,1,2,2,3,4,4 ; n=0 - 7
	.db 5,5,6,7,7,8,8,9 ; n=8 - 15
	.db 10,10,11,11,12,13,13,14 ; n=16 - 23
	.db 14,15,16,16,17,17,18,19 ; n=24 - 31
	.db 19,20,20,21,22,22,23,23 ; n=32 - 39
	.db 24,25,25,26,26,27,28,28 ; n=40 - 47
	.db 29,29,30,31,31,32,32,33 ; n=48 - 55
	.db 34,34,35,35,36,37,37,38 ; n=56 - 63
	.db 38,39,40,40,41,41,42,43 ; n=64 - 71
	.db 43,44,44,45,46,46,47,47 ; n=72 - 79
	.db 48,49,49,50,50,51,52,52 ; n=80 - 87
	.db 53,53,54,55,55,56,56,57 ; n=88 - 95
	.db 58,58,59,59,60,61,61,62 ; n=96 - 103
	.db 62,63,64,64,65,65,66,67 ; n=104 - 111
	.db 67,68,68,69,70,70,71,71 ; n=112 - 119
	.db 72,73,73,74,74,75,76,76 ; n=120 - 127
	.db 77,77,78,79,79,80,80,81 ; n=128 - 135
	.db 82,82,83,83,84,85,85,86 ; n=136 - 143
	.db 86,87,88,88,89,89,90,91 ; n=144 - 151
	.db 91,92,92,93,94,94,95,95 ; n=152 - 159
	.db 96,97,97,98,98,99,100,100 ; n=160 - 167
	.db 101,101,102,103,103,104,104,105 ; n=168 - 175
	.db 106,106,107,107,108,109,109,110 ; n=176 - 183
	.db 110,111,112,112,113,113,114,115 ; n=184 - 191
	.db 115,116,116,117,118,118,119,119 ; n=192 - 199
	.db 120,121,121,122,122,123,124,124 ; n=200 - 207
	.db 125,125,126,127,127,128,128,129 ; n=208 - 215
	.db 130,130,131,131,132,133,133,134 ; n=216 - 223
	.db 134,135,136,136,137,137,138,139 ; n=224 - 231
	.db 139,140,140,141,142,142,143,143 ; n=232 - 239
	.db 144,145,145,146,146,147,148,148 ; n=240 - 247
	.db 149,149,150,151,151,152,152,153 ; n=248 - 255
;
; Rechteck-Tabelle
;
Rectangle: ; Rechtecktabelle 8 bits, 3V max
	.db 0,0,0,0,0,0,0,0 ; n=0 - 7
	.db 0,0,0,0,0,0,0,0 ; n=8 - 15
	.db 0,0,0,0,0,0,0,0 ; n=16 - 23
	.db 0,0,0,0,0,0,0,0 ; n=24 - 31
	.db 0,0,0,0,0,0,0,0 ; n=32 - 39
	.db 0,0,0,0,0,0,0,0 ; n=40 - 47
	.db 0,0,0,0,0,0,0,0 ; n=48 - 55
	.db 0,0,0,0,0,0,0,0 ; n=56 - 63
	.db 0,0,0,0,0,0,0,0 ; n=64 - 71
	.db 0,0,0,0,0,0,0,0 ; n=72 - 79
	.db 0,0,0,0,0,0,0,0 ; n=80 - 87
	.db 0,0,0,0,0,0,0,0 ; n=88 - 95
	.db 0,0,0,0,0,0,0,0 ; n=96 - 103
	.db 0,0,0,0,0,0,0,0 ; n=104 - 111
	.db 0,0,0,0,0,0,0,0 ; n=112 - 119
	.db 0,0,0,0,0,0,0,0 ; n=120 - 127
	.db 153,153,153,153,153,153,153,153 ; n=128 - 135
	.db 153,153,153,153,153,153,153,153 ; n=136 - 143
	.db 153,153,153,153,153,153,153,153 ; n=144 - 151
	.db 153,153,153,153,153,153,153,153 ; n=152 - 159
	.db 153,153,153,153,153,153,153,153 ; n=160 - 167
	.db 153,153,153,153,153,153,153,153 ; n=168 - 175
	.db 153,153,153,153,153,153,153,153 ; n=176 - 183
	.db 153,153,153,153,153,153,153,153 ; n=184 - 191
	.db 153,153,153,153,153,153,153,153 ; n=192 - 199
	.db 153,153,153,153,153,153,153,153 ; n=200 - 207
	.db 153,153,153,153,153,153,153,153 ; n=208 - 215
	.db 153,153,153,153,153,153,153,153 ; n=216 - 223
	.db 153,153,153,153,153,153,153,153 ; n=224 - 231
	.db 153,153,153,153,153,153,153,153 ; n=232 - 239
	.db 153,153,153,153,153,153,153,153 ; n=240 - 247
	.db 153,153,153,153,153,153,153,153 ; n=248 - 255
;
; Ende Tabellen
;

Die Tabellen belegen 512 Speicherworte oder 1/16tel des verfügbaren Flash-Speichers.

Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing

9.3 Die LCD-Include-Routine

Dies ist die angepasste Include-Routine für die Ansteuerung der LCD im 8-Bit-Busy-Modus. Sie ist hier näher beschrieben und hier als Assembler-Quellcode downloadbar.

;
; *********************************
; * LCD include routines          *
; * (C)2018 avr-asm-tutorial.net  *
; *********************************
;
; ***********************************************
; *  L C D   I N T E R F A C E  R O U T I N E N *
; ***********************************************
;
; +-------+----------------+--------------+
; |Routine|Funktion        |Parameter     |
; +-------+----------------+--------------+
; |LcdInit|Initiiert die   |Ports, Pins   |
; |       |LCD im einge-   |              |
; |       |stellten Modus  |              |
; +-------+----------------+--------------+
; |LcdText|Gibt den Text im|Z=2*Tabellen- |
; |       |Flash ab Zeile 1|  adresse     |
; |       |aus             |0x0D: Naechste|
; |       |                |      Zeile   |
; |       |                |0xFF: Ignorie-|
; |       |                |      re      |
; |       |                |0xFE: Textende|
; +-------+----------------+--------------+
; |LcdSram|Gibt den Text im|Z=SRAM-Adresse|
; |       |SRAM aus        |R16: Anzahl   |
; +-------+----------------+--------------+
; |LcdChar|Gibt ein Zeichen|R16: Zeichen  |
; |       |aus             |              |
; +-------+----------------+--------------+
; |LcdCtrl|Gibt Kontrollbe-|R16: Befehl   |
; |       |fehl aus        |              |
; +-------+----------------+--------------+
; |LcdPos |Setzt Ausgabe-  |ZH: Zeile 0123|
; |       |position        |ZL: Spalte 0..|
; +-------+----------------+--------------+
; |LcdSpec|Erzeugt Spezial-|Z: 2*Tabellen-|
; |       |zeichen         |   adresse    |
; +-------+----------------+--------------+
; | S C H A L T E R   L C D D E C I M A L |
; +-------+----------------+--------------+
; |LcdDec2|Gibt zwei Dezi- |R16: Binaer-  |
; |       |malstellen aus  |     zahl     |
; +-------+----------------+--------------+
; |LcdDec3|Gibt drei Dezi- |R16: Binaer-  |
; |       |malstellen aus  |     zahl     |
; +-------+----------------+--------------+
; |LcdDec5|Gibt fuenf Dezi-|Z: Binaerzahl |
; |       |malstellen aus  |   16 Bit     |
; +-------+----------------+--------------+
; |    S C H A L T E R   L C D H E X      |
; +-------+----------------+--------------+
; |LcdHex2|Gibt zwei Hexa- |R16: Binaer-  |
; |       |dezimalen aus   |     zahl     |
; +-------+----------------+--------------+
; |LcdHex4|Gibt vier Hexa- |Z: Binaerzahl |
; |       |dezimalen aus   |   16 Bit     |
; +-------+----------------+--------------+
;
; ***********************************
;  P A R A M E T E R - V O R L A G E
; ***********************************
;
; Standard-Parameter-Satz der Einstellungen
;.equ clock = 1000000 ; Taktfrequenz Prozessor in Hz
; LCD-Groesse:
  ;.equ LcdLines = 1 ; Anzahl Zeilen (1, 2, 4)
  ;.equ LcdCols = 8 ; Anzahl Zeichen pro Zeile (8..24)
; LCD-Ansteuerung
  ;.equ LcdBits = 4 ; Busbreite (4 oder 8)
  ; Wenn 4-Bit-Ansteuerung:
    ;.equ Lcd4High = 1 ; Busnibble (1=Oberes, 0=Unteres)
  ;.equ LcdWait = 0 ; Ansteuerung (0 mit Busy, 1 mit Warteschleifen)
; LCD-Datenports
  ;.equ pLcdDO = PORTA ; Daten-Ausgabe-Port
  ;.equ pLcdDD = DDRA ; Daten-Richtungs-Port
; LCD-Kontrollports und -pins
  ;.equ pLcdCEO = PORTB ; Control E Ausgabe-Port
  ;.equ bLcdCEO = PORTB0 ; Controll E Ausgabe-Portpin
  ;.equ pLcdCED = DDRB ; Control E Richtungs-Port
  ;.equ bLcdCED = DDB0 ; Control E Richtungs-Portpin
  ;.equ pLcdCRSO = PORTB ; Control RS Ausgabe-Port
  ;.equ bLcdCRSO = PORTB1 ; Controll RS Ausgabe-Portpin
  ;.equ pLcdCRSD = DDRB ; Control RS Richtungs-Port
  ;.equ bLcdCRSD = DDB1 ; Control RS Richtungs-Portpin
; Wenn LcdWait = 0:
  ;.equ pLcdDI = PINA ; Daten-Input-Port
  ;.equ pLcdCRWO = PORTB ; Control RW Ausgabe-Port
  ;.equ bLcdCRWO = PORTB2 ; Control RW Ausgabe-Portpin
  ;.equ pLcdCRWD = DDRB ; Control RW Richtungs-Port
  ;.equ bLcdCRWD = DDB2 ; Control RW Richtungs-Portpin
; Wenn Dezimalausgabe benoetigt wird:
  ;.equ LcdDecimal = 1 ; Wenn definiert: einbinden
; Wenn Hexadezimalausgabe benoetigt wird:
  ;.equ LcdHex = 1 ; Wenn definiert: einbinden
; Wenn nur Simulation im SRAM:
  ;.equ avr_sim = 1 ; 1=Simulieren, 0=Nicht simulieren
;
; *****************************************
;  T E X T A U S G A B E - V O R L A G E N
; *****************************************
;
; Tabellen zum Kopieren fuer verschiedene Formate
;
; --------------------------
; Einzeilige LCD
;   8 Zeichen pro Zeile
; Text_1_8:
;   .db "        ",0xFE,0xFF
;        01234567
;
;   16 Zeichen pro Zeile
; Text_1_16:
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 Zeichen pro Zeile
; Text_1_20:
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 Zeichen pro Zeile
; Text_1_24:
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; --------------------------
; Zweizeilige LCD
;   16 Zeichen pro Zeile
; Text_2_16:
;   .db "                ",0x0D,0xFF
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 Zeichen pro Zeile
; Text_2_20:
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 Zeichen pro Zeile
; Text_2_24:
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; --------------------------
; Vierzeilige LCD
;   16 Zeichen pro Zeile
; Text_4_16:
;   .db "                ",0x0D,0xFF
;   .db "                ",0x0D,0xFF
;   .db "                ",0x0D,0xFF
;   .db "                ",0xFE,0xFF
;        0123456789012345
;
;   20 Zeichen pro Zeile
; Text_4_20:
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0x0D,0xFF
;   .db "                    ",0xFE,0xFF
;        01234567890123456789
;
;   24 Zeichen pro Zeile
; Text_4_24:
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0x0D,0xFF
;   .db "                        ",0xFE,0xFF
;        012345678901234567890123
;
; *********************************
;   P A R A M E T E R  C H E C K
; *********************************
;
; Sind alle Parameter korrekt angegeben?
;
; Groesse definiert?
.ifndef LcdLines
  .error "LCD line size (LcdLines) undefined!"
  .else
  .if (LcdLines!=1)&&(LcdLines!=2)&&(LcdLines!=4)
    .error "LCD illegal line size (LcdLines)!"
    .endif
  .endif
.ifndef LcdCols
  .error "LCD column size (LcdCols) undefined!"
  else
  .if (LcdCols<8)||(LcdCols>24)
    .error "LCD illegal column size (LcdCols)!"
    .endif
  .endif
;
; clock definiert?
.ifndef clock
  .error "Clock frequency (clock) undefined!"
  .endif
;
; 4- oder 8-Bit-Interface gewaehlt?
.ifndef LcdBits
  .error "LCD data bus bits (LcdBits) undefined!"
  .else
  .if (LcdBits != 4) && (LcdBits != 8)
    .error "LCD data bus bits (LcdBits) not 4 or 8!"
    .endif
  .if LcdBits == 4
    .ifndef Lcd4High
      .error "LCD 4 bit data bus nibble (Lcd4High) undefined!"
      .else
      .if (Lcd4High != 0) && (Lcd4High != 1)
        .error "LCD 4 bit data bus nibble (Lcd4High) not 0 or 1!"
        .endif
      .endif
    .endif
  .endif
;
; LCD data ports
.ifndef pLcdDO
  .error "LCD data output port (pLcdDO) undefined!"
  .endif
.ifndef pLcdDD
  .error "LCD data direction port (pLcdDD) undefined!"
  .endif
.if LcdWait == 0
  .ifndef pLcdDI
    .error "LCD data input port (pLcdDI) undefined!"
    .endif
  .endif
;
; LCD Control Ports und Pins
.ifndef pLcdCEO
  .error "LCD control E output port (pLcdCEO) undefined!"
  .endif
.ifndef pLcdCED
  .error "LCD control E direction port (pLcdCED) undefined!"
  .endif
.ifndef bLcdCEO
  .error "LCD control E output pin (bLcdCEO) undefined!"
  .endif
.ifndef bLcdCED
  .error "LCD control E direction pin (bLcdCED) undefined!"
  .endif
.ifndef pLcdCRSO
  .error "LCD control RS output port (pLcdCRSO) undefined!"
  .endif
.ifndef pLcdCRSD
  .error "LCD control RS direction port (pLcdCRSD) undefined!"
  .endif
.ifndef bLcdCRSO
  .error "LCD control RS output pin (bLcdCRSO) undefined!"
  .endif
.ifndef bLcdCRSD
  .error "LCD control RS direction pin (bLcdCRSD) undefined!"
  .endif
.ifndef LcdWait
  .error "LCD operating property (LcdWait) undefined!"
  .else
  .if LcdWait == 0
    .ifndef pLcdCRWO
      .error "LCD control RW output port (pLcdCRWO) undefined!"
      .endif
    .ifndef bLcdCRWO
      .error "LCD control RW output pin (bLcdCRWO) undefined!"
      .endif
    .ifndef pLcdCRWD
      .error "LCD control RW direction port (pLcdCRWD) undefined!"
      .endif
    .ifndef bLcdCRWD
      .error "LCD control RW direction pin (bLcdCRWD) undefined!"
      .endif
    .endif
  .endif
;
; *************************************
;  S I M U L A T I O N   A V R _ S I M
; *************************************
;
; Simulation ermitteln
.ifdef avr_sim
  .equ simulation = avr_sim
  .else
  .equ simulation = 0
  .endif
.if simulation == 1
  .dseg
  SimStart:
  SimDisplayPos:
    .byte 1
  SimCtrlClear:
    .byte 1
  SimCtrlReset:
    .byte 1
  SimCtrlInputmode:
    .byte 1
  SimCtrlDisplay:
    .byte 1
  SimCtrlCursorShift:
    .byte 1
  SimCtrlFunctionset:
    .byte 1
  SimCtrlCharGenRamAdr:
    .byte 1
  SimCtrlDisplayRamAdr:
    .byte 1
  SimDataDisplay:
    .byte LcdLines*LcdCols
  SimEnd:
  .cseg
  .endif
;
; *********************************
;      L C D - R O U T I N E N
; *********************************
;
; LcdInit: Ports und Pins initiieren
;          Einschalt-Wartezyklus
;          Funktionseinstellungen
;          LCD loeschen
LcdInit:
  ; Init der LCD Kontroll-Bits
  cbi pLcdCEO,bLcdCEO ; E-Pin low
  sbi pLcdCED,bLcdCED ; E-Pin output
  cbi pLcdCRSO,bLcdCRSO ; RS-Pin low
  sbi pLcdCRSD,bLcdCRSD ; RS-Pin output
  .ifdef pLcdCRWO
    .ifdef bLcdCRWO
      cbi pLcdCRWO,bLcdCRWO ; RW-Pin low
      .endif
    .endif
  .ifdef pLcdCRWD
    .ifdef pLcdCRWD
      sbi pLcdCRWD,bLcdCRWD ; RW-Pin output
      .endif
    .endif
  ; Init der LCD Datenbus-Ports
  .if LcdBits == 8
    clr R16 ; Datenbus auf Null
    .else
    in R16,pLcdDO ; Lese Outputbits Datenbus
    .if Lcd4High == 1
      andi R16,0x0F ; Oberes Nibble loeschen
      .else
      andi R16,0xF0 ; Unteres Nibble loeschen
      .endif
    .endif
  out pLcdDO,R16 ; Datenbus Output auf Null
  .if LcdBits == 8
    ldi R16,0xFF ; Setze alle Richtungsbits
    .else
    in R16,pLcdDD ; Lese Richtungsbits Datenbus
    .if Lcd4High == 1
      ori R16,0xF0 ; Oberes Nibble high
      .else
      ori R16,0x0F ; Unteres Nibble high
      .endif
    .endif
  out pLcdDD,R16 ; Richtungsbits Datenbus setzen
  ; LCD-Startphase
  .if simulation == 0
    rcall LcdWait50ms
    .endif
  ; LCD auf 8-Bit-Datenbusbreite einstellen
  ldi R16,0x30
  rcall Lcd8Ctrl ; im 8-Bit-Modus an LCD-Kontroll
  .if simulation == 0
    rcall LcdWait5ms ; 5 ms lang warten
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; im 8-Bit-Modus an LCD-Kontroll
  .if simulation == 0
    rcall LcdWait5ms ; 5 ms lang warten
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; im 8-Bit-Modus an LCD-Kontroll
  .if simulation == 0
    rcall LcdWait5ms ; 5 ms lang warten
    .endif
  ldi R16,0x30
  rcall Lcd8Ctrl ; im 8-Bit-Modus an LCD-Kontroll
  .if simulation == 0
    rcall LcdWait5ms ; 5 ms lang warten
    .endif
  ; Wenn 4-Bit-Interface: auf 4-Bit-Datenbus umschalten
  .if LcdBits == 4
    ldi R16,0x20 ; 4-Bit-Interface
    rcall Lcd8Ctrl
    .if simulation == 0
      rcall LcdWait5ms
      .endif
    .endif
  ; Function set
  .if LcdBits == 8
    ldi R16,0x30 ; 8-Bit-Datenbus
    .else
    ldi R16,0x20 ; 4-Bit-Datenbus
    .endif
  .if LcdLines > 1
     ori R16,0x08 ; Mehrzeiliges Display
    .endif
  rcall LcdCtrl
  ; Display mode
  ldi R16,0x0C ; Display an, Unterstrich aus, Cursorblink aus
  rcall LcdCtrl
  ; LCD Entry Mode setzen
  ldi R16,0x06 ; Cursor nach rechts, Cursor shift
  rcall LcdCtrl
  ; LCD loeschen
  ldi R16,0x01 ; Display loeschen
  rjmp LcdCtrl
;
; LcdText
;   Gibt den Text im Flash auf der LCD aus
;     Z zeigt auf Texttabelle
;     0x0D: Zeilenvorschub und Wagenruecklauf
;     0xFF: Ignoriere (Fuellzeichen)
;     0xFE: Ende des Texts
LcdText:
  push R0 ; R0 sichern
  ldi R16,LcdLines ; Max Zeilenanzahl
  mov R0,R16 ; in R0
LcdText1:
  lpm R16,Z+ ; Lese naechstes Zeichen
  cpi R16,0xFE ; Endezeichen?
  breq LcdText3
  brcc LcdText1 ; Ueberlese Fuellzeichen
  cpi R16,0x0D ; Zeilenvorschub+Wagenruecklauf
  brne LcdText2
  dec R0 ; Zeilenzaehler vermindern
  breq LcdText1
  .if simulation == 1
    push ZH
    push ZL
    push R16
    ldi ZH,High(SimDataDisplay) ; Wagenruecklauf und Zeilenvorschub
    ldi ZL,Low(SimDataDisplay)
    ldi R16,LcdLines
    LcdText1a:
      adiw ZL,LcdCols
      dec R16
      cp R16,R0
      brne LcdText1a
    ldi R16,Low(SimDataDisplay)
    sub ZL,R16
    ldi R16,High(SimDataDisplay)
    sbc ZH,R16
    sts SimDisplayPos,ZL
    pop R16
    pop ZL
    pop ZH
    .endif
  push ZH ; Z sichern
  push ZL
  ldi ZH,LcdLines ; Zeile berechnen
  sub ZH,R0
  clr ZL ; Zeilenanfang
  rcall LcdPos
  pop ZL ; Z wieder herstellen
  pop ZH
  rjmp LcdText1 ; nachstes Zeichen
LcdText2:
  rcall LcdChar ; Zeichen in R16 an LCD ausgeben
  rjmp LcdText1 ; Naechstes Zeichen
LcdText3:
  pop R0 ; R0 wieder herstellen
  ret
;
; LcdSRam gibt Text im SRAM an der aktuellen LCD-Position aus
;   Z zeigt auf SRAM-Adresse
;   R16: Anzahl Zeichen
LcdSRam:
  push R16 ; Rette R16
  ld R16,Z+ ; Lese Zeichen
  rcall LcdChar ; Zeichen in R16 an LCD ausgeben
  pop R16 ; R16 wieder herstellen
  dec R16 ; Anzahl Zeichen abwaerts
  brne LcdSRam ; weiter Zeichen ausgeben
  ret
;
; LcdChar gib Zeichen in R16 an der aktuellen Position aus
;   R16: Zeichen
LcdChar:
.if simulation == 1
  push ZH
  push ZL
  push R16
  ldi ZH,High(SimDataDisplay)
  ldi ZL,Low(SimDataDisplay)
  lds R16,SimDisplayPos
  inc R16
  sts SimDisplayPos,R16
  dec R16
  add ZL,R16
  ldi R16,0
  adc ZH,R16
  pop R16
  st Z,R16 ; Ausgabe in SRAM schreiben
  pop ZL
  pop ZH
  .endif
  rjmp LcdData
;
; LcdByte gibt Kontrollbefehl in R16 an die LCD aus
LcdCtrl:
.if simulation == 1
    cpi R16,0x80 ; Display RAM-Adresse?
    brcs LcdCtrlSim1
    sts SimCtrlDisplayRamAdr,R16
    push ZH
    push ZL
    mov ZH,R16
    andi R16,0x40
    brne LcdCtrlSim0a
    clr ZL ; Zeile 1 oder 3
    rjmp LcdCtrlSim0b
  LcdCtrlSim0a:
    ldi ZL,LcdCols ; Zeile 2 oder 4
  LcdCtrlSim0b:
    mov R16,ZH
    andi R16,0x3F
    subi R16,LcdCols
    brcc LcdCtrlSim0c ; Zeilen 3 oder 4
    subi R16,-LcdCols
    rjmp LcdCtrlSim0d
  LcdCtrlSim0c:
    sbrc ZH,6 ; Zeile 4?
    subi R16,-LcdCols ; Spaltenlaenge addieren
  LcdCtrlSim0d:
    add ZL,R16
    sts SimDisplayPos,ZL
    pop ZL
    pop ZH
    rjmp LcdCtrlSim9
  LcdCtrlSim1:
    cpi R16,0x40 ; Character Generator RAM-Adresse?
    brcs LcdCtrlSim2
    sts SimCtrlCharGenRamAdr, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim2:
    cpi R16,0x20 ; Functionset?
    brcs LcdCtrlSim3
    sts SimCtrlFunctionSet, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim3:
    cpi R16,0x10 ; Cursor shift
    brcs LcdCtrlSim4
    sts SimCtrlCursorShift, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim4:
    cpi R16,0x08 ; Display control?
    brcs LcdCtrlSim5
    sts SimCtrlDisplay, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim5:
    cpi R16,0x04 ; Display control?
    brcs LcdCtrlSim6
    sts SimCtrlInputmode, R16
    rjmp LcdCtrlSim9
  LcdCtrlSim6:
    cpi R16,0x02 ; Reset?
    brcs LcdCtrlSim7
    sts SimCtrlReset,R16
    rjmp LcdCtrlSim9
  LcdCtrlSim7:
    cpi R16,0x01 ; Clear?
    brcs LcdCtrlSim8
    sts SimCtrlClear,R16
    ; LCD clear display
    push ZH
    push ZL
    ldi ZH,High(SimEnd)
    ldi ZL,Low(SimEnd)
    clr R16
  LcdCtrl7a:
    st -Z,R16 ; Rueckwaerts mit Nullen fuellen
    cpi ZL,Low(SimStart)
    brne LcdCtrl7a
    cpi ZH,High(SimStart)
    brne LcdCtrl7a
    ldi R16,0x01
    pop ZL
    pop ZH
    rjmp LcdCtrlSim9
  LcdCtrlSim8: ; 00-Befehl
  LcdCtrlSim9:
  .endif
  .if LcdWait == 0
    rcall LcdBusy
    .endif
  cbi pLcdCRSO,bLcdCRSO ; RS-Bit low
  .if LcdBits == 4
    push ZL
    push R16
    in ZL,pLcdDO ; Daten Output Port lesen
    .if Lcd4High == 1
      andi ZL,0x0F ; Oberes Nibble loeschen
      andi R16,0xF0 ; Oberes Nibble Eingabezahl loeschen
      .else
      andi ZL,0xF0 ; Unteres Nibble Output loeschen
      swap R16 ; Oberes und unteres Nibble vertauschen
      andi R16,0x0F ; Oberes Nibble loeschen
      .endif
    or R16,ZL ; Kombinieren
    out pLcdDO,R16
    rcall LcdPulseE ; E aktivieren
    pop R16 ; R16 wieder herstellen
    push R16 ; und sichern
    in ZL,pLcdDO ; Daten Output Port lesen
    .if Lcd4High == 1
      andi ZL,0x0F ; Unteres Nibble erhalten
      swap R16 ; Unteres und oberes Nibble vertauschen
      andi R16,0xF0
      .else
      andi ZL,0xF0 ; Oberes Nibble erhalten
      andi R16,0x0F ; Unteres Nibble erhalten
      .endif
    out pLcdDO,R16 ; Auf Datenbus
    rcall LcdPulseE ; E aktivieren
    pop R16 ; Eingabezahl wieder herstellen
    pop ZL ; ZL wieder herstellen
    .else
    out pLcdDO,R16 ; Eingabezahl auf Datenbus
    rcall LcdPulseE ; E aktivieren
    .endif
  .if LcdWait == 1
    andi R16,0xFC ; Obere Bits Kontrollbefehl
    brne LcdCtrl1 ; Kurze Verzoegerung
    rjmp LcdWait1640us ; 1,64 ms warten
  LcdCtrl1:
    rjmp LcdWait40us ; 40 us warten
    .else
    ret
    .endif
;
; LcdPos setzt den LCD Cursor an die Position in Z
;   ZH: Zeile (0 bis Anzahl Zeilen - 1)
;   ZL: Spalte (0 bis Anzahl Spalten - 1)
LcdPos:
  cpi ZH,1 ; Zeile = 1?
  ldi R16,0x80 ; Zeile 0
  .if LcdLines < 2 ; LCD hat nur eine Zeile
    rjmp LcdPos1
    .endif
  brcs LcdPos1
  ldi R16,0xC0 ; Zeile 2 bei zweizeiliger LCD
  .if LcdLines == 2
    rjmp LcdPos1
    .endif
  breq LcdPos1
  ldi R16,0x80+LcdCols ; Zeile 3
  cpi ZH,2 ; Zeile = 3
  breq LcdPos1
  ldi R16,0xC0+LcdCols ; Zeile 4
LcdPos1:
  add R16,ZL ; Spaltenadresse addieren
  rjmp LcdCtrl
;
; LcdSpec erzeugt Spezialzeichen auf der LCD
;   Z zeigt auf 2*Tabellenadresse
;   Tabellenformat:
;     1.Byte: Adresse des Zeichens 0b01zzz000,
;             0: Ende der Tabelle
;     2.Byte: Dummy
;     3. bis 10.Byte: Daten der Zeilen 1 bis 8
LcdSpec:
  push R0 ; R0 ist Zaehler
LcdSpec1:
  lpm R16,Z+ ; Lese Adresse des Zeichens
  tst R16 ; Ende der Tabelle?
  breq LcdSpec3 ; Ende Tabelle
  rcall LcdCtrl ; Adresse schreiben
  adiw ZL,1 ; Ueberlese Dummy
  ldi R16,8 ; 8 Byte pro Zeichen
  mov R0,R16 ; R1 ist Zaehler
LcdSpec2:
  lpm R16,Z+ ; Datenbyte lesen
  rcall LcdData ; Datenbyte ausgeben
  dec R0 ; Abwaerts zaehlen
  brne LcdSpec2 ; Weiter Daten ausgeben
  rjmp LcdSpec1 ; Naechstes Zeichen
LcdSpec3:
  pop R0 ; R0 wieder herstellen
  ldi ZH,0 ; Cursor Home
  ldi ZL,0
  rjmp LcdPos
;
; **********************************
;   D E Z I M A L  A U S G E B E N
; **********************************
; Routinen zur Ausgabe von Dezimalzahlen
;
.ifdef LcdDecimal
  ;
  ; LcdDec2 gibt Binaerzahl in R16 dezimal mit zwei Stellen aus
  ;   ohne Unterdrueckung fuehrender Nullen
  LcdDec2:
    mov ZL,R16 ; Zahl kopieren
    ldi R16,'0'-1 ; Zehnerzaehler
    cpi ZL,100 ; Zweistellig?
    brcs LcdDec2a
    ldi ZL,99 ; dreistellig, auf Maximalwert setzen
  LcdDec2a:
    inc R16
    subi ZL,10
    brcc LcdDec2a
    rcall LcdData ; Zehner ausgeben
    ldi R16,10+'0'
    add R16,ZL
    rcall LcdData
    ret
  ;
  ; LcdDec3 gibt Binaerzahl in R16 dezimal mit drei Stellen aus
  ;   mit Unterdrueckung fuehrender Nullen
  LcdDec3:
    ldi ZH,1 ; Fuehrende Nullen unterdruecken
  LcdDec3null: ; behalte fuehrende Nullen-Unterdrueckung bei
    push R0
    mov ZL,R16 ; Zahl in ZL
    ldi R16,100 ; Hunderter
    rcall LcdDec3a
    rcall LcdData
    ldi R16,10 ; Zehner
    rcall LcdDec3a
    rcall LcdData
    ldi R16,'0'
    add R16,ZL
    rcall LcdData
    pop R0
    ret
  ;
  LcdDec3a: ; Dezimalziffer ermitteln
    clr R0 ; Zaehler
    dec R0 ; auf - 1
  LcdDec3b:
    inc R0 ; Erhoehe Zaehler
    sub ZL,R16 ; Subtrahiere 100 oder 10
    brcc LcdDec3b ; Kein Ueberlauf: weiter
    add ZL,R16 ; Letzte Subtraktion rueckgaengig
    tst R0 ; Ziffer Null?
    breq LcdDec3c ; Ja, pruefe Nullunterdrueckung
    clr ZH ; Keine Nullenunterdrueckung mehr
    ldi R16,'0' ; Ziffer in ASCII
    add R16,R0
    ret
  LcdDec3c:
    tst ZH ; Nullenunterdrueckung?
    breq LcdDec3d
    ldi R16,' '
    ret
  LcdDec3d:
    ldi R16,'0'
    ret
  ;
  ; LcdDec5 gibt Zahl in Z mit fuenf Dezimalstellen aus
  ;   mit Unterdrueckung fuehrender Nullen
  LcdDec5:
    push R2
    push R1
    push R0
    clr R2 ; Fuehrende Nullen
    inc R2 ; unterdruecken
    mov R1,ZH ; Kopiere Zahl nach R1:R0
    mov R0,ZL
    ldi ZH,High(10000) ; 1. Dezimalstelle
    ldi ZL,Low(10000)
    rcall LcdDec5a ; Konvertiere 1. Dezimalstelle
    ldi ZH,High(1000) ; 2.Dezimalstelle
    ldi ZL,Low(1000)
    rcall LcdDec5a ; Konvertiere 2. Dezimalstelle
    ldi ZH,High(100) ; 2.Dezimalstelle
    ldi ZL,Low(100)
    rcall LcdDec5a ; Konvertiere 3. Dezimalstelle
    ldi ZH,High(10) ; 2.Dezimalstelle
    ldi ZL,Low(10)
    rcall LcdDec5a ; Konvertiere 4. Dezimalstelle
    ldi R16,'0'
	add R16,R0
	rcall LcdChar ; Zeige 5. Dezimalstelle an
    pop R0
    pop R1
    pop R2
    ret ; Gib R16 als Dezimal mit drei Stellen aus
  ;
  LcdDec5a: ; Ermittle Dezimalstelle
    ldi R16,'0'-1
  LcdDec5b:
    inc R16 ; Erhoehe Zaehler
    sub R0,ZL ; Subtrahiere Dezimalstelle LSB
    sbc R1,ZH ; und MSB mit Carry
    brcc LcdDec5b
    add R0,ZL ; Letzte Subtraction rueckgaengig
    adc R1,ZH
    cpi R16,'0' ; Nullunterdrueckung?
    breq LcdDec5c ; Ja, pruefe
    clr R2 ; Nullunterdrueckung ausschalten
    rjmp LcdChar
  LcdDec5c:
    tst R2 ; Nullunterdrueckung aus?
    breq LcdDec5d ; Ja
    ldi R16,' ' ; Null unterdruecken
  LcdDec5d:
    rjmp LcdChar
  ;
  .endif
;
; ****************************************
;  H E X A D E Z I M A L  A U S G E B E N
; ****************************************
; Routinen zur Ausgabe von Dezimalzahlen
.ifdef LcdHex
  ;
  ; LcdHex2 gibt Zahl in R16 in Hexadezimalformat
  ;   auf der LCD aus
  LcdHex2:
    push R16 ; Wird noch gebraucht
    swap R16 ; Oberes Nibble in unteres Nibble
    rcall LcdNibble
    pop R16
  LcdNibble:
    andi R16,0x0F ; Unteres Nibble erhalten
    subi R16,-'0' ; ASCII-Null addieren
    cpi R16,'9'+1 ; A bis F?
    brcs LcdNibble1 ; Nein
    subi R16,-7 ; 7 addieren
  LcdNibble1:
    rjmp LcdChar ; Zeichen in R16 ausgeben
  ;
  ; LcdHex4
  LcdHex4:
    mov R16,ZH ; Oberes Byte in R16
    rcall LcdHex2
    mov R16,ZL ; Unteres Byte in R16
    rjmp LcdHex2
  ;
  .endif
;
; *********************************
;  L C D - A N S T E U E R U N G
; *********************************
;
; Warte bis LCD Busy-Flagge geloescht
;   wird nur benoetigt wenn Warten aus
.if LcdWait == 0
  LcdBusy:
    push R16 ; Inhalt von R16 sichern
    .if LcdBits == 8
      clr R16 ; Datenbus-Richtung auf Input
      .else
      in R16,pLcdDD ; Lese Richtungs-Bits
      .if Lcd4High == 1
        andi R16,0x0F ; Loesche oberes Nibble
        .else
        andi R16,0xF0 ; Loesche unteres Nibble
        .endif
      .endif
    out pLcdDD,R16 ; Richtungsregister setzen
    .if LcdBits == 8
      clr R16 ; Alle Ausgabepins low
      .else
      in R16,pLcdDO ; Lese Ausgabeport
      .if Lcd4High == 1
        andi R16,0x0F ; Loesche oberes Nibble
        .else
        andi R16,0xF0 ; Loesche unteres Nibble
        .endif
      .endif
    out pLcdDO,R16 ; Loesche Pull-Ups
    cbi pLcdCRSO,bLcdCRSO ; RS-Pin auf Low
    sbi pLcdCRWO,bLcdCRWO ; RW-Pin auf High
  LcdBusyWarte:
    rcall LcdIn ; Aktiviere E, lese Datenport, deaktiviere E
    .if LcdBits == 4
      rcall LcdPulseE ; Dummy fuer lower nibble
      .endif
    lsl R16 ; Busy-Flag in Carry schieben
    brcs LcdBusyWarte ; Flagge 1, weiter warten
    cbi pLcdCRWO,bLcdCRWO ; RW-Pin auf Low
    .if LcdBits == 8
      ldi R16,0xFF ;  Datenbus wieder auf Ausgang
      .else
      in R16,pLcdDD ; Lese Richtung Datenport
      .if Lcd4High == 1
        ori R16,0xF0 ; Oberes Nibble High
        .else
        ori R16,0x0F ; Unteres Nibble High
        .endif
      .endif
    out pLcdDD,R16 ; Richtungsport setzen
    pop R16 ; R16 wieder herstellen
    ret ; Fertig
  ; Lese Busy-Flagge in R16
  ;   wird nur benoetigt, wenn Warten ausgeschaltet
  LcdIn:
    cbi pLcdCRSO,bLcdCRSO ; LCD-RS-Pin auf Low
    sbi pLcdCEO,bLcdCEO ; Setze Bit bLcdCEO im LCD-Kontrollport
    nop ; Warte einen Takt
    .if clock>1000000
      nop
      .endif
    .if clock>2000000
      nop
      .endif
    .if clock>3000000
      nop
      .endif
    .if clock>3000000
      nop
      .endif
    .if clock>4000000
      nop
      .endif
    .if clock>5000000
      nop
      .endif
    .if clock>6000000
      nop
      .endif
    .if clock>7000000
      nop
      .endif
    .if clock>8000000
      nop
      .endif
    .if clock>9000000
      nop
      .endif
    .if clock>10000000
      nop
      .endif
    .if clock>11000000
      nop
      .endif
    .if clock>12000000
      nop
      .endif
    .if clock>13000000
      nop
      .endif
    .if clock>14000000
      nop
      .endif
    .if clock>15000000
      nop
      .endif
    .if clock>16000000
      nop
      .endif
    .if clock>17000000
      nop
      .endif
    .if clock>18000000
      nop
      .endif
    .if clock>19000000
      nop
      .endif
    in R16,pLcdDI ; Lese Datenbus
    cbi pLcdCEO,bLcdCEO ; Loesche Bit bLcdCEO
    ret
  .endif
;
; 1 us Impuls am LCD-E-Pin ausgeben
LcdPulseE:
  sbi pLcdCEO,bLcdCEO ; Setze Bit bLcdCEO im LCD-Kontrollport
  .if clock>1000000
    nop
    .endif
  .if clock>2000000
    nop
    .endif
  .if clock>3000000
    nop
    .endif
  .if clock>3000000
    nop
    .endif
  .if clock>4000000
    nop
    .endif
  .if clock>5000000
    nop
    .endif
  .if clock>6000000
    nop
    .endif
  .if clock>7000000
    nop
    .endif
  .if clock>8000000
    nop
    .endif
  .if clock>9000000
    nop
    .endif
  .if clock>10000000
    nop
    .endif
  .if clock>11000000
    nop
    .endif
  .if clock>12000000
    nop
    .endif
  .if clock>13000000
    nop
    .endif
  .if clock>14000000
    nop
    .endif
  .if clock>15000000
    nop
    .endif
  .if clock>16000000
    nop
    .endif
  .if clock>17000000
    nop
    .endif
  .if clock>18000000
    nop
    .endif
  .if clock>19000000
    nop
    .endif
  cbi pLcdCEO,bLcdCEO ; Loesche Bit bLcdCEO
  ret
;
; R16 im 8-Bit-Modus an LCD-Kontrolle ausgeben
Lcd8Ctrl:
  .if simulation == 1
    push R16
    clr R16
    sts SimDisplayPos,R16
    pop R16
    .endif
  .if LcdBits == 4
    push ZL ; ZL sichern
    in ZL,pLcdDO ; Daten Output Port lesen
    .if Lcd4High == 1
      andi ZL,0x0F ; Oberes Nibble Output loeschen
      andi R16,0xF0 ; Unteres Nibble Eingabezahl loeschen
      .else
      andi ZL,0xF0 ; Unteres Nibble Output loeschen
      swap R16 ; Oberes und unteres Nibble vertauschen
      andi R16,0x0F ; Unteres Nibble Daten isolieren
      .endif
    or R16,ZL ; Kombinieren
    pop ZL
    .endif
  out pLcdDO,R16 ; Daten auf LCD Output Port
  cbi pLcdCRSO,bLcdCRSO ; RS-Bit low
  rjmp LcdPulseE ; E-Puls ausloesen
;
; LcdData: R16 im eingestellten Modus Datenausgabe an LCD
;   R16: Zeichen
LcdData:
  .if LcdWait == 0
    rcall LcdBusy ; Warte auf Busy-Flag der LCD
    .endif
  sbi pLcdCRSO,bLcdCRSO ; Setze RS-Bit
  .if LcdBits == 4
    push ZL
    push R16
    in ZL,pLcdDO ; Daten Output Port lesen
    .if Lcd4High == 1
      andi ZL,0x0F ; Oberes Nibble loeschen
      andi R16,0xF0 ; Unteres Nibble Eingabezahl loeschen
      .else
      andi ZL,0xF0 ; Unteres Nibble Output loeschen
      swap R16 ; Oberes und unteres Nibble vertauschen
      andi R16,0x0F ; Oberes Nibble loeschen
      .endif
    or R16,ZL ; Kombinieren
    out pLcdDO,R16
    rcall LcdPulseE ; E aktivieren
    pop R16 ; R16 wieder herstellen
    push R16 ; und wieder sichern
    in ZL,pLcdDO ; Daten Output Port lesen
    .if Lcd4High == 1
      andi ZL,0x0F ; Unteres Nibble Output erhalten
      swap R16 ; Unteres zum oberen Nibble machen
      andi R16,0xF0 ; Oberes Nibble Eingabezahl erhalten
      .else
      andi ZL,0xF0 ; Oberes Nibble Output erhalten
      andi R16,0x0F ; Unteres Nibble Eingabezahl erhalten
      .endif
    or R16,ZL ; Kombinieren
    out pLcdDO,R16 ; Auf Datenbus
    rcall LcdPulseE ; E aktivieren
    pop R16 ; Eingabezahl wieder herstellen
    pop ZL ; ZL wieder herstellen
    .else
    out pLcdDO,R16 ; Eingabezahl auf Datenbus
    rcall LcdPulseE ; E aktivieren
    .endif
  .if LcdWait == 1
    rjmp LcdWait40us
    .endif
  ret
;
; ********************************
;    W A R T E R O U T I N E N
; ********************************
;
; 50 ms lang warten
;   Da bei Taktfrequenzen oberhalb von 3,1 MHz die
;   maximale Dauer der Z-Schleife ueberschritten
;   wuerde, wird 10 mal die 5ms-Routine aufgerufen
LcdWait50ms:
  push R16 ; 10 mal 5 ms aufrufen
  ldi R16,10
LcdWait50ms1:
  rcall LcdWait5ms
  dec R16
  brne LcdWait50ms1
  pop R16
  ret
;
LcdWait5ms:
  ; 5 ms warten, RCALL 3 Takte
  .equ cLcdZ5ms = (5*clock/1000 - 18 + 2) / 4
  push ZH ; Rette ZH, + 2 Takte
  push ZL ; Rette ZL, + 2 Takte
  ldi ZH,High(cLcdZ5ms) ; + 1 Takt
  ldi ZL,Low(cLcdZ5ms) ; + 1 Takt
  rjmp LcdWaitZ ; + 2 Takte
  ; Insgesamt: 11 Takte
;
.if LcdWait == 1 ; mit Wait-Zyklen
  ; 1,64 ms warten
  .equ cLcdZ1640us = (164*(clock/1000)/100 - 18 + 2) / 4
  LcdWait1640us:
  push ZH ; Rette ZH
  push ZL ; Rette ZL
  ldi ZH,High(cLcdZ1640us)
  ldi ZL,Low(cLcdZ1640us)
  rjmp LcdWaitZ
  ;
  ; 40 us warten
  .equ cLcdZ40us = (40*clock/1000000 - 18 + 2) / 4
  LcdWait40us:
  push ZH ; Rette ZH
  push ZL ; Rette ZL
  ldi ZH,High(cLcdZ40us)
  ldi ZL,Low(cLcdZ40us)
  rjmp LcdWaitZ
  .endif
;
; Warteroutine mit Z Zyklen
LcdWaitZ: ; 11 Takte
  sbiw ZL,1 ; Abwaerts zaehlen, 2 Takte
  brne LcdWaitZ ; Nicht Null, weiter, 2 Takte bei Sprung, 1 Takt am Ende
  pop ZL ; ZL wieder herstellen, 2 Takte
  pop ZH ; ZH wieder herstellen, 2 Takte
  ret ; Zurueck, 4 Takte
  ; Insgesamt: 11 + 4 * (Z - 1) + 3 + 8
;
  ; Anzahl Takte = 11 + 4 * (Z - 1) + 3 + 8
  ;                11: RCALL, PUSH, LDI, LDI, RJMP
  ;                4: 4 Takte pro Z-Zyklus (minus 1)
  ;                3: 3 Takte fuer letzten Zyklus
  ;                8: POP, RET
  ;              = 4 * Z + 11 - 4 + 3 + 8
  ;              = 4 * Z + 18
  ; Z = (Anzahl Takte - 18 + 2) / 4
  ;                +2: Aufrunden vor Teilen durch 4
;
; End of include file



Top Kurven Frequenzwahl CTC-Berechnung RC-Filter Transfer LCD Kurvenerzeugung Simulation Listing


Lob, Tadel, Fehlermeldungen, Genöle und Geschimpfe oder Spam bitte über das Kommentarformular an mich.

Zum Anfang dieser Seite

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