DDS-Generator 0,1Hz…100kHz


von Rudolf Drabek
Elektronik-Labor   Projekte   AVR 

 

 

Im diesem Artikel https://www.radiomuseum.org/forumdata/upload/rc_oscillator.pdf der klassische analoge RC-Generatoren beschreibt, habe ich schon darauf hingewiesen wie folgt: „Die modernste Art von RC-Generatoren ist zweifellos die Synthese mit einem digitalen Signalprozessor. Aber das ist ein sehr umfangreiches neues Thema wie man mit Software einen Signalgenerator erstellt.Vielleicht findet sich jemand der einen Artikel dazu schreibt.“

 

Bis jetzt hat für ELO noch niemand einen entsprechenden Artikel verfasst. Man kann narürlich so etwas von der Stange kaufen, auch mit höherer Performance. Der Lerneffekt ist aber gering. Ziel meiner Ausführungen ist die Realisierung dieses Prinzips mit handelsüblichen Bauelementen, die übrigens weniger als 20 Euro kosten, als auch ein wenig KnowHow zu vermitteln. Es gibt auch sehr viel Info im Internet z.B. http://de.wikipedia.org/wiki/Direct_Digital_Synthesis

 

1.    Funktionsweise in Kürze

 

Wesentlicher Bestandteil bei DDS ist das „Phasenrad“ um möglichst einfach das Funktionsprinzip zu erklären, hier mit nur 32 Zähnen.

 

 



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Geht jemand im Kreis um das Rad herum, von einem Zahn zum Nächsten und braucht dazu je 1 Sekunde, so ist er nach 32 s einmal um den Kreis herum. Jedes mal, wenn er beim Anfang vorbeikommt, läutet er eine Glocke. Nun, alle 32 s läutet es oder die Frequenz des Läutens beträgt 1/32 Hertz. Nimmt er 2 Zähne je Schritt so ist er nach 16 s herum = 1/16 Hz usw. Sie sehen also, dass die Frequenz in Stufen von 1/32 Hz erhöht werden kann. Das geht natürlich nicht grenzenlos. Nähme er 32 Schritte auf einmal  in einer Sekunde, so bewegte er sich nicht, da er immer wieder an derselben Stelle landet .

 

Herr Nyquist, Entdecker des Abtasttheorems, hat herausgefunden, dass man höchstens 16 Schritte bei 32 Zähnen ausführen darf. Also die Abtastrate darf höchstens die Hälfte der möglichen Zustände sein.

 

Im gezeigten Beispiel ist eine Taktfrequenz von 1 Hz gewählt, nämlich die Schrittdauer. Die max. mögliche Frequenz, unter Berücksichtigung  des Abtasttheorems, beträgt also 0,5 Hz ( 2 Schritte zu je 1 s Dauer über 16 Zähne). Mehr ist nicht dahinter. Was man aber erkennt ist die Tatsache, dass bei 3 Zähnen je Schritt schon etwas merkwürdiges auftritt. Nach 10 Schritten ist er fast herum, nach 11 Schritten aber schon einen  Schritt in der neuen Periode. Es geht sich nie genau aus! Bei diesem Beispiel.

 

Auch in realen Phasenrädern tritt dieser Effekt auf und hat eine Unsicherheit der Nulldurchgänge zur Folge, es tritt eine mehr oder weniger große Frequenzmodulation auf. Und das nur durch die Digitalisierung, die ja ein festes Raster  vorgibt. Man kann diesen Effekt nur sehr klein machen. Analoge Generatoren zeigen diesen Effekt natürlich nicht! 

 

Der vorgestellte DDS Generator ist mit einem 32 bit Phasenrad, jetzt Phasenaccumulator genannt, ausgestattet und die Schrittdauer beträgt exakt 0,6875000 Mikrosekunden. Die Schrittfrequenz betragt 1/0,6875 us = 1,45454545 Mhz periodisch. Die Zahnanzahl von 32 bit entspricht  4294967296,  also mehr als 4 Milliarden Zähnen.

 

 

Die Frequenz ergibt sich, wie schon erklärt,  zu:

 
            f  = Schrittweite . Schrittfrequenz / 32 bit              bei Schrittweite = 1 ergibt sich

 

            fmin = 1,45454545e6 / 2exp32  = 0,0003386627 Hz

            fmax= 1,45454545e6/2              = 727272           Hz     siehe nach bei Nyquist!

 

Ich habe die max. Frequenz auf 102727 Hz begrenzt, da dann der Spannungszug einer Schwingung noch 14 mal gesampelt wird, die Spannungsform also noch gut zu erkennen ist.

 

Für eine Fequenz von 1 Hz muss die Schrittweite also 2952,7900 sein. Das geht aber nicht, da man nur 2952 oder 2953 wählen kann. Es ergibt sich eine Abweichung  von ca. 71 ppm oder 0,007 %, was für Amatuerzwecke sicher ausreicht. Für z.B. 100 Hz also 295279 Schritte. Der Fehler ist praktisch NULL !

 

Wichtig sind die 2 Nullen hinter dem Komma, da damit ab 100 Hz der Frequenzfehler kleiner als 0,005 ppm wird. Im NF-Bereich ist der Fehler < 3 ppm. Schon der Quarz wird ungenauer sein. Ich habe sehr viel Zeit aufgewendet mit billigen Quarzen einen möglichst geringen Fehler zu erreichen. Es geht sich mit einem 16 MHz Quarz s.o. gut aus. Ist das Ergebnis individuell zu ungenau, so kann man ja eine krumme Quarzfrequenz wählen, z.B. 16255812 Hz. Dies ergibt dann einen Schritt  für 1 Hz von exakt 3000. Aber wie gesagt ab 10 Hz ist dier Frequenzfehler durch die Genauigkeit und Stabilität des Quarzes bestimmt.

 

 

 

 

2.    Realisierung

 

Die Anordnung besteht aus zwei Teilen mit jeweils einem Atmel ATtiny2313:

 

2.1 dem Kontrollteil mit Display und Drehencoder

 

Bis auf die Bedienung ist nichts besonders. Aus dem SW Listing kann man entnehmen wie ein Display angesteuert wird, wie man einen Drehencoder bedient und die Kommunikation mit dem eigentlichen DDS Generator via RS232 mit 500 kBaud.

 

Der Inhalt der Kommunikation ist die Information welche Spannungsform, Sinus, Dreieck, Sägezahn und Rechteck erzeugt werden soll und die Schrittweite um die der Phasenaccumulator incrementiert werden soll.

Es werden also 5 Bytes, 1 für die Spannungsform und 4 Bytes für den Stepwert, jedes mal, wenn der Drehencoder nach rechts = höhere Frequenz  oder links gedreht wird .

 

Die SW Entwicklung ich habe mit dem Kontrollteil begonnen und den DDS Teil emuliert. Das hatte im Nachhinein den Vorteil, dass die Frequenz schon im Kontrollteil berechnet und am Display dargestellt wird und der DDS Frequenzgenerator ein reiner Sklave ist, der das 5 Byte Kommando umsetzt und das sehr schnell, nämlich in 100 us.

Bei Frequenzen bis etwa 1000 Hz hört man keine Umschaltstörung.

Bei 10 kHz fehlt aber dann schon eine ganze Schwingung, was sich durch knacksen außert, da nicht im Nulldurchgang des Spannungszuges umgeschaltet wird.

 

2.1.1       Bedienung

 

Das einzige Bedienelement ist ein Encoder. Es wird der Pollin Panasonic Drehgeber verwendet, der u.a. auch einen Druckschalter beinhaltet.

 

Da der Frequenzbereich von 0,1 Hz bis 100 kHz geht muss es irgendeine Art von „Gangschaltung“ geben, da es zu mühsam ist in 1/10 Hz Schritten den ganzen Frequenzbereich im Verhältnis 1 : 1.000.000 durchzudrehen, da dreht man eher selber durch.

 

Solange der Schalter gedrückt ist, befindet man sich im Kommandomodus. Drehen entgegen dem Uhrzeiger ändert die Spannungsform, im Uhrzeigersinn wird der Frequenzstep geändert von 0,1 Hz auf 1 Hz, 10 Hz…….10kHz. Man darf auch beide Parameter bei gedrücktem Schalter ändern.

 

Lässt man den Schalter los, so wird das Display upgedated und die neuen Daten übernommen. Man könnte natürlich auch feststellen wie schnell einer dreht und ein „Automatikgetriebe“ vorsehen. Aber wie lange soll derselbe Gang eingelegt    bleiben ? usw.

 

Im Schaltbild ist ein TUX Display angegeben. Da ich aber in der Eagle Library das TC1602A Display nicht gefunden habe, soll dieser Hinweis genügen. Gleiches gilt für den Drehencoder.

 

2.1.2 Frequenzberechnung

 

Die Formel für die Frequenzberechnung ist ja schon bekannt. Ich verwende Fixkommaarithmetik. Da ein uC in Assembler keinen Dezimalpunkt kennt, der ja auf Grund  des kleinsten Steps mit 0,1 Hz aber vorkommen muss, habe ich die Formel wie folgt abgeändert.  Die Einerstelle also das LSB der Zahl repräsentiert die 1/10 Hz Stelle. Die Formel muss also nur mit dem Faktor 10 multipliziert werden und bei der Ausgabe im Display eben an der richtigen Stelle ein Dezimalpunkt ausgegeben werden. 

 

f = Schrittweite . (1454545.4  . 10)  / 2exp32

 

Die Frequenz  wird durch Multiplikation der Schrittweite mit einer Konstanten und nachfolgender Division durch 2exp32 ermittelt.

Auf die komische zahl 1.45 periodisch komme ich noch zu sprechen.

 

Die Division ist höchst einfach. Warum? 32 bit sind 4 Byte. So wie eine Verschiebung um 1 Bit eine Multiplikation oder Division um den Faktor / Divisor  2 bedeutet, so ist eine Verschiebung um 4 Byte nach links eine Division durch die Zahl 4294967296.

 

2.2  dem eigentlichen DDS Generator

 

Das Phasenrad = Phasenaccumulator ist nichts anderes als ein 32 Bit SW Zähler, der , wenn er überläuft, wie bei der Skizze, wieder von vielleicht null zu zählen beginnt. Dieser Zählvorgang, nämlich den Schrittwert zum Phasenaccu zu addieren dauert ja eine gewisse Zeit gemessen in Cycles der  ATtiny2313  Clockfrequenz von 16 MHz.

 

Im Listing sehen sie, dass es minimal 10 Zyklen braucht. Das ist ein Zeitbedarf von min. 0,625 uS. Ich habe aber ein „nop“ eingebaut also 11 Schritte geplant für die Addition und das holen der Daten aus einer der Spannungsformtabellen.

 

Daraus ergeben sich einerseits die Zahl 16e6 / 11 = 1,45e6 periodisch, anderseits  zusammen mit den 32 Bit eben die Zahl 2952,7900. Wichtig sind die 2 Nullen am Ende für die perfekte Genauigkeit weil ab 100 Hz kein bedeutender Zahlenrest  hinter dem Komma  vorkommt. Ich habe sehr viele Kombinationen untersucht, aber die gewählten Werte kommen mir optimal vor.

 

Der Phasenaccu muss für einen kontinuierlichen Spannungsverlauf ungestört laufen. Also kein Interrupt oder Sonstiges darf hier stören.

Leider ist eine Stördauer von 100 us aber unvermeidlich je Drehimpuls vom  Encoder.

 

Die Ausgabe der Spannung wurde mit einem R2R Widerstandsnetzwerk  realisiert. Ich hatte in meiner Schatzkiste eben 12,1 k Widerstände mit 0,1% Genauigkeit, was natürlich mehr als genug für einen 8 Bit DA Wandler ist. 

 

Die Störspannungen sind 1/256 der möglichen Ausgangsspannung von 5V. Das sind 48 dB. Ausreichend für Amateuranwendungen. Wie sie wissen ist MP3 nicht viel besser. Eine klassische Audio CD hat ja einen 16 Bit DA Wandler mit 96 dB Störabstand. Eine weitere Störquelle sind die Abstufungen in den Spannungstabellen.

 

Als ich das erste mal einen Kopfhörer mit dem Ausgang des DA Wandlers verband, war ich völlig hingerissen, dass ich nicht das kleinste Störgeräusch, sondern nur den reinsten Sinuston hörte.Die Sägezahntabelle werde ich möglicherweise noch ändern. Der Sägezahn klingt sehr scharf! Was natürlich auch heisst, dass sich jeder eine beliebige Spannungsform programmieren kann. Es ist zu erkennen wie, wenn man das Listing durchgeht.

 

Die Basis für den DDS Teil lieferte www.myplace.nu/avr/minidds/index.htm

 

 

3.    Ergebnisse

 

         Es folgen verschiedene kommentierte Bilder und Oszillogramme.

 

Sinus 400 Hz. Das Bild lässt sich noch vergrößern.

 

    


Dreieck mit 32 Samples je Periode 45 kHz                              Sägezahn mit 96 kHz


Die Verrundung der Steps ist auf die Zeitkonstante des Innenwiderstandes des  R2R Netzwerkes mit den 10 pF des Tastkopfes von 0,12 us bei einer Stepzeit von 0.6875 us zurückzuführen.

 

 

     

Die Fehler des DA Wandlers sind kleiner als 0,5 LSB

Man erkennt Fehler, die Stufung ist nicht perfekt

  

Ausschnitt auf dem man die Steps der Sinustabelle genau sieht. Ein AD Wandler mit 10 Bit würde hier deutliche Verbesserungen ergeben.

 

 

          

 

Das 10 / 11 Schritte Problem bei 32 Zähnen. Im Bild ist der Zeitunterschied genau 1 Schritt von 0.6875 us beim realen Phasenrad mit 32 Bit.

 

     


Frequenzumschaltung in 100 us nach einem Drehimpuls

 

 

Download:  Ddsgen.zip  (DDS_Generator.sch,  dds32.asm,  ddsgen.asm)


;********************************************************************************
;* dds32.asm
;* Control uC für den DDS Generator nach Jesper Hansen
;* http://www.myplace.nu/avr/minidds/index.htm
;* Bedienelement ist nur ein Drehgeber mit Schalter.
;* Der Schalter schaltet zwischen Kommando- und Ausführungsmodus um
;*
;* Um die Umschaltstörungen der DDS Signale kurz zu halten wird eine schnelle
;* Kommunikation mit 500 Kbaud via RS232 angewendet.
;* Im Steuer uC wird die Frequenz ausgerechnet und 5 Bytes an den DDS uC gesendet
;********************************************************************************

.include "tn2313def.inc" ;Prozessor ATTINY2313

; ATtiny2313 Display TC1602A 4 bit Mode
; +--+-+--+ 2 Zeilen zu je 16 Stellen
; !RESET | |_| | VCC Memoryadr.: Zeile1: 00 und Zeile2: 40h
; (RX)PD0 | | PB7(SCK) n.c.
; (TX)PD1 | | PB6(MISO) n.c.
; XTAL2 | | PB5(MOSI)----- Display pin 4 RS
; XTAL1 | | PB4 ----- Display pin 6 Enable
; (INT0)PD2 | | PB3(OC1) ----- Display pin 14
; (INT1)PD3 | | PB2 ----- Display pin 13
; (T0)PD4 | | PB1(AIN1)----- Display pin 12
; (T1)PD5 | | PB0(AIN0)----- Display pin 11
; GND | | PD6 Display pin 1 Masse
; +-------+ Display pin 2 VCC+
; Display pin 3 Kontrast
; PD0 I RX RS232 Display pin 5 R/W an Masse
; PD1 O TX RS232 Display pin 15 Beleuchtung +
; PD2 O Rotary Encoder phaseB Display pin 16 Display pin Masse
; PD3 O Rotary Encoder phaseA
; PD4 I Switch von Encodertaste
; PD5 I n.c.
; PD6 I n.c.

.equ XTAL = 8000000 ;systemclock [Hz] 16 Mhz im DDS_uC
.equ BAUD = 500000 ;Terminal Baudrate, double speed

; UBRR Wertfür RS232 ausrechnen
.equ UBRRL_VAL = (XTAL/(BAUD*8)-1) ;

;*******************************************************************
;* Fuse setting auf 0 (gesetzt):
;* Ext. Byte: Selfpgren enabled
;* Hi Byte: Default
;* Low Byte: "FF" = Clock undivided, startup long, >8MHz Quarz
;* ;*****************************************************************

;calculation variables
.def var_form = r2 ;Index auf Spannungsform
.def var_step = r3 ;Index auf Frequenzstepwert als auch Steptext

.def RY0 = r4 ;4 Byte Konstante für Frequenzberechnung
.def RY1 = r5 ;16e6/11*10
.def RY2 = r6 ; *10 weil ja 1/10 Hz Einheiten
.def RY3 = r7 ;im Display angezeigt werden

.def RR0 = r8 ;Adderwert für Phasenaccu 32 bit
.def RR1 = r9 ;max. 0x01c28f5c = 10 kHz Step
.def RR2 = r10
.def RR3 = r11 ;

.def RR4 = r12 ;der Phasenaccu 32 bit mit 0,6875 us je update
.def RR5 = r13 ;Max.Wert 0x12000000 entspricht 102272,7 Hz
.def RR6 = r14
.def RR7 = r15

.def RX0 = r16 ; Resultat der Frequenzberechnung ist auch
.def RX1 = r17 ; gleich der Input für die BCD Ausgabe
.def RX2 = r18 ;
.def RX3 = r19 ;
.def RX4 = r20 ; BCD value digits 0 and 1
.def RX5 = r21 ; BCD value digits 2 and 3
.def RX6 = r22 ; BCD value digits 4 and 5
.def RX7 = r23 ; BCD value digits 6 and 7 (MSD)

;allgemeine Variablen
.undef XL
.undef XH
.undef YL
.undef YH
.def A = r24 ;allg. Verwendung
.def B = r25
.def C = r29 ;allg Variable
.def neu = r25 ;doppelt belegt
.def var = r26 ;(XL)
.def old = r27 ;(XH)old value of the encoders
.def flag = r28 ;Flagregister für Encoder und Taste

.CSEG ; ab hier kommt Programmcode
.org 0x0000
rjmp init ; Reset Handler
.org 0x0006
rjmp Timer0 ; Timer0 ISR alle ~2ms
.org 0x0007
reti

.org 0x0013
_1cmd: .db "DDS Parameter",00,00 ;9 words
_1Zeile:.db "DDS +-",00,00; ;6 words
;=$0F words
;Strings für den Rest der 1. Zeile, bzw 2.Zeile Commandmode
_01: .db" 0,1Hz", 00,00 ;Strings padded auf gerade Zeichenanzahl
_1: .db" 1Hz", 00,00 ;je 4 Words
_10: .db" 10Hz", 00,00
_100: .db" 100Hz", 00,00
_1k: .db" 1kHz", 00,00 ;Initwert
_10k: .db" 10kHz", 00,00
sin: .db"Sinus ", 00 ;im DDS_uC sind die Tabellen in dieser
tria: .db"Dreieck", 00 ;Reihenfolge angelegt
saw: .db"Saegez.", 00
sq_r: .db"Recht_k" ,00 ;=$24 words

;Accuphasenstepwerte entsprechend den Strings oberhalb
;dem DDS-uC wird immer die Spannungsform und der Phasenaccuwert übergeben
;in Summe 5 Bytes

z104: .db $00,$00,00,$12 ;Maxwert DDSaccu entspricht 102272,7Hz
z555: .db $2e,$f2,$dd,00 ;Konstante für die Frequenzausgabe = 16e6/11*10
;Genauigkeit siehe ddsgen.asm
z01: .db $27,$01,$00,00 ;0 ppm
z1: .db $89,$0b,$00,00 ;0 ppm
z10: .db $58,$73,$00,00 ;0 ppm ;je 2 words/Zahl
z100: .db $6f,$81,$04,00 ;3 ppm
z1k: .db $56,$0e,$2d,00 ;57 ppm
z10k: .db $5c,$8f,$c2,01 ;0,09% ;=$10 words
.db"Tend" ;2 words
;In Summe $13 +$0F +$28 +$10 +$2 =$5C words = $B8 Bytes
;Adressierung der Werte über z hat noch immer ZH = 0

;************************************************************
init:
;init stack
ldi A, LOW(RAMEND) ;Stackpointer init
out SPL, A

;init Timer 0 interrupt alle 2,05 ms für Encoderabfrage
ldi A, 0b00000011 ;set CS00 : divisor: 64
;TCCR0A ist Default 0
out TCCR0B, A ;Seite 80
ldi A, 1 << TOIE0 ;Interrupt at Timer Overflow
out TIMSK, A ;Seite 82

;init PortD Encoder
ldi A, 0b00000010 ;nur pin PD1 (TX) ist output =1
out DDRD, A ;sonst PortD ist input
ldi A, 0xFF ;Pull-Up resistors on Port D
out PortD, A ;bzw PD1 = 1
clr old ;default "old value" Encoder = no action

;Init serial Port (UART) "polling", double speed
ldi C, HIGH(UBRRL_VAL) ;set Baudrate = 1 = 500 kBaud
out UBRRH, C
ldi C, LOW(UBRRL_VAL)
out UBRRL,C
ldi C,2 ;nur bei 8 MHz clock
out UCSRA,C ;double speed = bit1 Seite 134
ldi C,6 ;Frame format 8N1, Seite137
out UCSRC,C ;an sich der Defaultwert
ldi C,8
out UCSRB,C ;TX enable = bit3 Seite 135

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

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

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

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

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

;init Konstante z555 für Frq. Berechnung in RY[3:0] und RX[3:0]DDSaccu
clr ZH
ldi ZL,z555 * 2
lpm RY0,Z+ ;Faktor 16e6/11*10
lpm RY1,Z+
lpm RY2,Z+
lpm RY3,Z
mov RR0,ZH
mov RR1,ZH
mov RR2,ZH
inc RR2 ;set 0,08638 Hz, sonst wärs ja DC
mov RR3,ZH
mov RR4,ZH
mov RR5,ZH
mov RX3,ZH
mov RX4,ZH
mov RX5,ZH
mov RX6,ZH
mov RX7,ZH

sei ;enable all interrupts

ldi var,6 ;Pointer auf "Sinus"
mov var_form,var
ldi var,2 ;Pointer auf 10 Hz Stepweite per Encoderimpuls
mov var_step,var
rcall locval ;hole Wert entsprechend var_step
lpm RR0,z+ ;dieser Wert bleibt ungeändert
lpm RR1,z+ ;solange im cmd_mode kein neuer Wert vorliegt
lpm RR2,z+ ;diesen lt. je +- Drehimpuls zu RR[7:4] add/sub
lpm RR3,z
rjmp main
; so, init ist getan

;************************************************************************
;*Pollin Panasonic 16348 Encoder mit 2ms Timerinterruptabfrage
;* Autor: Hannes Lux (hannes) abgeändert von (rulixa)
;* http://www.mikrocontroller.net/topic/172127#1648272
;* OLD NEW Drehrichtung
;* 11 10 rechts
;* 10 00 rechts
;* 00 01 rechts
;* 01 11 rechts
;*
;* 00 10 links
;* 10 11 links
;* 11 01 links
;* 01 00 links = gegen Uhr
;* neu, old, flag, B, C ,A schon im Hauptprogramm definiert
;*************************************************************************
Timer0: push C ;Timer0 ISR ändert nur Register flag
push B ; "B" ist auch als "neu" definiert
push A
push ZL
push ZH
sbi pinD,6 ;toggle zum abhören ob Interrupt läuft, bzw wie
;genau die Clockfrequenz ist, z.B.bei RC_oscillator
in C,sreg ;save Statusreg
in neu,PinD;Drehgeber-Port holen PD2 und PD3, Phase A, B
andi neu,0b00001100 ;maskieren neu =PinD [3:2]
lsr old ;old[3:2] wird old[1:0], "urold" wird Nirvana
lsr old ;von links kommt nur 0 nach
or old,neu ;neue Drehgeberbits neu[3:2] nach old[3:2]
;Drehrichtung lt. old aus Tabelle holen
clr A
ldi ZL,low (enctab*2) ;Tabelle mit Drehgeber-Increment-Werten
ldi ZH,high (enctab*2)
add ZL,old ;Index aufaddieren
adc ZH,A
lpm A,Z ;Wert bei mir 0,1 oder 2
tst flag ;ist flag von main schon abgeholt worden ?
brne nnab
mov flag,A ;neuen Wert speichern, egal welcher Wert
nnab: out sreg,C ;restore Statusreg
pop ZH
pop ZL
pop A
pop B
pop C
reti
;wenn nicht gedreht wird, so ist neu = old, das kann sein 00 01 10 11
;bei jeder Kombi 0000 0101 1010 1111 steht 0 in der Tabelle = kein Auftrag
;flag 1 oder 2 : Auftrag liegt vor
;wird der Auftrag in main ausgeführt, so werden die Bits dort gelöscht.

;Tabelle mit Drehgeber-Werten (neuneu-oldold als Index)
enctab: ;nn oo, nn oo
.db 0, 0 ;00 00, 00 01 ;2 bytes je Zeile
.db 2, 0 ;00 10, 00 11 ;weil Assembler sowieso
.db 0, 0 ;01 00, 01 01 ;auf word auffüllen würde
.db 0, 1 ;01 10, 01 11 ;bisher verwendete Tabelle
.db 1, 0 ;10 00, 10 01 ; Adr_1=1 Adr_2=2
.db 0, 0 ;10 10, 10 11 ; Adr13=2 Adr14=1
.db 0, 2 ;11 00, 11 01 ;hatte bei "rechts" Hänger bzw. 2fach
.db 0, 0 ;11 10, 11 11


;.include "my_hlux.asm" ;Test welche Encodermethode
;.include "my_Timer0.asm" ;die wenigsten Fehler liefert
;.include "my_encoder_peda.asm" ;

;*************************************************************************
;* Jetzt folgt die Mainloop
;*************************************************************************
main: sbis PIND,4 ;Taste gedrückt ?
rjmp cmd_mode ;ja, Kommandomodus

fmode1: rcall display_freq ;1. und 2. Zeile
cpi flag,1 ;bit0 gesetzt= Frequ. increment
brne eminus ;
rcall add32 ;DDSaccu= DDSaccu + Frequenzdifferenz
rjmp disp_f

eminus: cpi flag,2 ;bit1 gesetzt= Frequ. decrement
brne disp_f ;nix gültiges, auf Auftrag warten
rcall sub32 ;DDSaccu= DDSaccu - Frequenzdifferenz
;bei flag = 0 passiert nix eben!

;nach encoder +- muss der DDS_controlteil den 32bit Phasenaccuwert senden
;der dann im DDS_uC die Frqeunz generiert

disp_f: rcall DDSaccu ;Accu holen, Frequenz ausrechnen,--> BCD
tst flag
BREQ nosend5 ;damit der DDS_uc nur bei neuen Daten
;angesteuert wird
rcall send5 ;sende Spannungsform und Wert an DDS-uC
nosend5: clr flag ;lösche tastenflag und warte auf Auftrag
rcall delay2ms
rcall delay2ms
rcall delay2ms
rcall delay2ms
rjmp main ;
;*******************************************************************
; Kommandomode.
; Wert in var einstellen durch Encoder, der 0...9 sein kann.
; var wird ausgehend vom alten var-Wert verändert
; 1. Der Text in Displayzeile 1 neu ausgegeben und
; 2. var wird de/incrementiert und der entsprechende Text im Display
; Zeile 2 ausgegeben und entspr. Wert nach RR[3:0] geschrieben
; 3.Nach Ende des cmd-mode hält var den Pointer um wieviel Hertz pro
; Impuls des Encoders die Frequenz geändert wird.
; Frequ.Step und Spannungsform also sin,säge,R-eck des DDS festlegen
; dazu werden die var Werte 0...9 ausgewertet, in Display Zeile 2 gezeigt
; ausgegeben und an den DDS_uC übermittelt
;********************************************************************
cmd_mode: sbic PIND,4 ;Taste gedrückt ?
rjmp update ;nein, Freq. mode

;Zeile1 und Zeile2 ausgeben
clr flag ;soll nichts unerledigtes vom Frq_mode übrig bleiben
clr ZH ;Text in Zeile1 schreiben
ldi ZL,_1cmd * 2
ldi C,0 ;Zeile1 Pos1
rcall lcd_flash_string;benützt den Befehl lpm
rcall clrline2 ;lösche 2. Zeile in cmd_mode
;Spannungsform in Zeile2 out
ldi ZL,_01 ;Wordadresse von Text = f (var)
ldi C,64 ;setze Cursor 2.Zeile:Pos 11
rcall locaddr2 ;wird var_form benützt
rcall lcd_flash_string
;Stepwerttext in Zeile2 out
ldi ZL,_01 ;Wordadresse von Text = f (var)
ldi C,74 ;setze Cursor 2.Zeile:Pos 11
rcall locaddr1 ;wird var_step benützt
rcall lcd_flash_string
ldi A,100
cmod: rcall delay2ms ;Display ist "ruhiger" mit 200ms delay
dec A
brne cmod


cmode1: cpi flag,1 ;rechts = Stepmode
breq incre ;Stepmode
cpi flag,2 ;links = Spgsformmode
breq incli ;Formmode
rjmp cmd_mode ;bleibe in cmd_mode, solange Taste gedrückt ist
;in der ISR kann ja "gedreht" erkannt werden
incre: clr flag ;lösche tastenflag und führe aus
mov var,var_step ;;Index für Stepwert
inc var ;var setzt beim alten Wert fort
cpi var,6
BRLO spgs_form
ldi var,0 ;Bereich 0....5
spgs_form:
mov var_step,var;Index für Frequenzstep
rcall locval ;pointer auf Freq_step
lpm RR0,z+ ;dieser Wert bleibt ungeändert
lpm RR1,z+ ;
lpm RR2,z+ ;solange im cmd_mode kein neuer Wert vorliegt
lpm RR3,z ;dieser wird je +- Drehimpuls zu RR[7:4] add/sub
rjmp cmd_mode

incli: clr flag ;lösche tastenflag und führe aus
mov var_form,var ;Index für Spannungsformtext
inc var ;var setzt beim alten Wert fort
cpi var,6
BRLO zuniedrig
cpi var,10 ;geht aber nur von 6...9
brlo u_form
zuniedrig: ldi var,6

u_form: mov var_form,var
rjmp cmd_mode

update: rcall send5 ; DDS_uC updaten
rjmp main
;Ende cmd_mode*********************************************

;Adresse rechen für Displaytext
;ZL=Source, offset in var
;locaddr:push B
; clr ZH
; mov B,var ;mal 4, da 4 words Text je String
; lsl B
; lsl B
; add ZL,B
; lsl ZL ; mal 2 für Byteadresse
; pop B
; ret

;Adresse rechen für Displaytext Zeile1
;ZL=Source, offset in var_step
locaddr1:push B
clr ZH
mov B,var_step ;mal 4, da 4 words Text je String
lsl B
lsl B
add ZL,B
lsl ZL ; mal 2 für Byteadresse
pop B
ret
;Adresse rechen für Displaytext Zeile2
;ZL=Source, offset in var_form
locaddr2:push B
clr ZH
mov B,var_form ;mal 4, da 4 words Text je String
lsl B
lsl B
add ZL,B
lsl ZL ; mal 2 für Byteadresse
pop B
ret

;Adresse der Stepweitenwertes ausrechnen. df = f (var) nach RR[2:0]
;Input ist nur var, ZL=Wertetabelle gibts ja nur eine
locval: push B
clr ZH
ldi ZL,z01 ;Basisadresse der Stepwerte
mov B,var_step ;mal 2, da 2 words value je Step
lsl B ;aber nur 24 bit sind relevant
add ZL,B
lsl ZL ; mal 2 für Byteadresse
pop B
ret ; Z=Pointer auf Stepvalue

;-----------------------------------------------------------------
;send 5 Bytes zum DDS_uC via RS232 mit 500 kBaud
;Die Umschaltzeit auf neue Werte beträgt also nur 100 us.
;-----------------------------------------------------------------
send5: cli
push A
mov A,var_form ;die Spannungsformtabelle im DDS_uC
subi A,5 ;beginnt an 100er Bytegrenzen im Code
rcall WrCom ;das Spannungsformbyte ist gesendet
mov A,RR4 ;byte1 des Phasenaccu
rcall WrCom ;out
mov A,RR5 ;byte2
rcall WrCom ;out
mov A,RR6 ;byte3
rcall WrCom ;out
mov A,RR7 ;byte4
rcall WrCom ;alle 32 bit des Phasenaccus sind gesendet
pop A
sei
ret

WrCom: sbis UCSRA,UDRE ;wait for UART ready
rjmp WrCom
out UDR,A
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; LCD-Routinen für TC1602A Display ;;
;; ================================= ;;
;; LCD 4-bit mode SPLC780D standard controller ;;
;; execution times INCLUDED in routines ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

.equ LCD_PORTB = PORTB ;define the Ports / IOs
.equ LCD_DDRB = DDRB
.equ PIN_RS =PD5 ;Config for display RS und Enable
.equ PIN_E =PD4


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


;Frequenz in Zeile 2 ab Pos. 10 darstellen
display_freq:
;erste Zeile
ldi ZL,_1Zeile * 2 ;flash in Words, hole aber Byte
clr ZH ;Display Zeile1 F-mode ausgeben
clr C ;set source/dest. of string
rcall lcd_flash_string ;benützt den Befehl lpm

ldi C,10 ;setze Cursor 1.Zeile:Pos 11 = DDRAM 10
ldi ZL,_01
rcall locaddr1
rcall lcd_flash_string

;zweite Zeile Spannungsform und Frequenz ausgeben
clr ZH
ldi ZL,_01
rcall locaddr2
ldi C,64 ;prereqs für nächste Programmzeile
rcall lcd_flash_string ;Spgsform

;6 digits, "." und 1 digit ausgeben
ldi C,72
rcall set_cur ;im Anschluß an "Frequ." diese numerisch out
clr zh
ldi zl,23 ;RX7 adressieren
ld C,z ;enthält hi und lo nibble, hi verwerfen
andi C,15 ;nur lo nibble ausgeben
ori C,$30
rcall lcd_data

ldi B,2 ;RX6 und RX5 ausgeben
clr ZH ;Z auf RX6setzen
ldi ZL,23 ;um 1 höher wegen "ld -Z" Befehl
bcdout: ld C,-Z ;hoechste Stelle =r23= BCD8 zuerst raus
swap C ;hi nibble holen
andi C, 15 ;ehemals hi nibble maskieren
ori C,$30 ;in ASCII
rcall lcd_data
ld C,Z ;
andi C, 15 ;lo nibble detto
ori C,$30 ;in ASCII
rcall lcd_data
dec B
brne bcdout ;

ld C,-z ;jetzt RX4_hi, "." und RX4_lo ausgeben
swap C
andi C, 15 ;ehemals hi nibble maskieren
ori C,$30 ;in ASCII
rcall lcd_data
ldi C,'.'
rcall lcd_data
ld C,z ;
andi C,15 ;lo nibble detto
ori C,$30 ;in ASCII
rcall lcd_data
ret

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

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

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

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

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

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

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

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

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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Ende der Displayroutinen, Beginn Mathematik ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;--------------------------------------------------------------------
;-- RR[7:4] = RR[7:4] - RR[3:0]
;--------------------------------------------------------------------
sub32: sub RR4,RR0
sbc RR5,RR1
sbc RR6,RR2
sbc RR7,RR3
mov A,RR7
cpi A,0x13 ;Frequenz < 0 geworden, auf max. setzen
brlo gut
clr RR4
clr RR5
clr RR6
ldi A, 0x12
mov RR7,A ;vom maxwert runterzählen
gut: ret

;--------------------------------------------------------------------
;-- RR[7:4] = RR[7:4] + RR[3:0]
;--------------------------------------------------------------------
add32: add RR4,RR0
adc RR5,RR1
adc RR6,RR2
adc RR7,RR3
mov A,RR7
cpi A,0x12 ;Frequenz zu hoch
brlo gut1
clr RR4
clr RR5
clr RR6
clr RR7
inc RR4 ;auf 0,0000338 Hz setzen
gut1: ret

;-----------------------------------------------------------------------
; die 4 Byte vom DDS Teil als 7 digit Ascii auf Display Zeile 2 out
; RX[7:0] = 64bit result of RX[3:0] * RY[3:0]
;-----------------------------------------------------------------------
;RR[7:4] ist der Phasenaccu
;RY[3:0] wird in der Init aktiviert

DDSaccu:mov RX0,RR4 ;transfer DDSaccu
mov RX1,RR5
mov RX2,RR6 ;um die
mov RX3,RR7
rcall mul32u ;die Frequenz zu berechnen und in
rcall Bin3BCD16 ;BCD ausgeben
ret

;*********************************************************************
;* Frequenz berechnen mittels 32 Bit Multiplikation und
;* anschließender Division durch 2^32. f = DDSaccu * clock/11 / 2^32
;* beim DDS mit 32 Bit Phasenaccu reichen an sich 24 Bits da max.
;* 1022727 1/10 Hz Schritte im Display angezeigt werden, von 1,67e6 mögl.
;**********************************************************************
; Call: RX[3:0] = 32bit multiplicand
; RX[7:4] = <don't care>
; RY[3:0] = 32bit multiplier (clock/11 *10) wegen 1/10 Hz units
; A = <don't care> (high register must be allocated)
; Result:RX[7:0] = 64bit result of RX[3:0] * RY[3:0]
; RY[3:0] = <not changed>
; A = unchanged
;nach der 2^32 Division steht Result in RX[3:0]
;
; Size = 21 words
; Clock = 436..532 cycles (+ret) 72 uS bei 8 MHz
; Stack = 0 byte

mul32u: push A
sub RX7,RX7
sub RX6,RX6
sub RX5,RX5
sub RX4,RX4 ;initialize variables
ldi A,33 ;
brcc PC+5 ; calculating loop
add RX4,RY0 ;
adc RX5,RY1 ;
adc RX6,RY2
adc RX7,RY3
ror RX7
ror RX6 ;
ror RX5 ;ins Ergebnis schieben
ror RX4 ;
ror RX3 ;
ror RX2 ;
ror RX1 ;
ror RX0 ;
dec A ;bis alle Bits durchgearbeitet sind
brne PC-14 ;continue loop;
;so, jetzt durch 2^32 dividieren
div2_32:mov RX3,RX7
mov RX2,RX6 ;divide by 2^32 ist ident mit Komma 4x nach links
mov RX1,RX5
mov RX0,RX4
pop A
ret ;das war's auch schon

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

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

.dseg





*******************************************************************
; Author = Jesper Hansen (C) GNU General Public License
; adaptiert für 32 bit Phasenaccumulator und debugged von
; Rudolf Drabek Nov. 2010. Für den Code keinerlei Garantie, nur für
; Privatverwendung und Ausbildungszwecke zu verwenden
;*******************************************************************
; PB0..7 = DA Wandler Data out
; PD0 = RXD
;*******************************************************************
;* Fuse setting auf 0 (gesetzt):
;* Ext. Byte: Selfpgren enabled
;* Hi Byte: Default
;* Low Byte: "FF" = Clock undivided, startup long, >8MHz Quarz
;********************************************************************
; Output frequency (using 32 bit accumulator) :
;
; f = deltaPhase * fClock/2^32
;
; fClock is in this case the CPU clock divided by the
; number of cycles to output the data ( 11 cycles )
;
; f = r24/r25/r26 * (16000000/11)*10/65536*65536
; f = r23/r24/r25/r26*0,000338662754
;
; fMax (theoretical) = 0.5 * fClock = 727272 Hz = 0,687500 us DDS-Takt
; 1 Durchlauf bei 1er Step benötigt also 2952,790016 Sekunden
; Wichtig für die Genauigkeit sind die 2 Nullen weiter hinter dem Komma.
; Fehler bei 0.1 Hz 900 ppm
; 1 HZ 71 ppm
; 10 HZ 3 ppm
; im NF-Bereich ist der Fehler bestimmt durch die Genauigkeit des Quarzes.
;******************************************************************************
;incrementwerte je Frequenzstep. Viel herumprobieren hilft für die Genauigkeit
; 0.1 Hz 295 0x0127 .
; 1 2953 0x0b89
; 10 29528 0x7358
; 100 295279 0x04816f
; 1000 2952790 0x2d0e56
; 10000 29527900 0x01c28f5c
; statt 29527900,16. Fehler s.o. 0,005 ppm ab 100 Hz
;******************************************************************************

.include "tn2313def.inc"
.org 0
rjmp reset
.org 7 ;RX complete interrupt
rjmp rx5

;******************************************************************************
; data tables
;******************************************************************************
;.cseg
; force table to begin at 256 byte boundary

.org 0x80 ;word address

sine: ; 256 step sinewave table
.db 0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae
.db 0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8
.db 0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5
.db 0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7
.db 0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc
.db 0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3
.db 0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83
.db 0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51
.db 0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27
.db 0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a
.db 0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08
.db 0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23
.db 0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c
.db 0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c

triangle: ; 256 step trianglewave table
.db 0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e
.db 0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e
.db 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e
.db 0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e
.db 0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e
.db 0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe
.db 0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde
.db 0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe
.db 0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1,0xef,0xef,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1
.db 0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1,0xcf,0xcf,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1
.db 0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xaf,0xab,0xa9,0xa7,0xa5,0xa3,0xa1
.db 0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91,0x8f,0x8f,0x8b,0x89,0x87,0x85,0x83,0x81
.db 0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71,0x6f,0x6f,0x6b,0x69,0x67,0x65,0x63,0x61
.db 0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51,0x4f,0x4f,0x4b,0x49,0x47,0x45,0x43,0x41
.db 0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31,0x2f,0x2f,0x2b,0x29,0x27,0x25,0x23,0x21
.db 0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11,0x0f,0x0f,0x0b,0x09,0x07,0x05,0x03,0x01

sawtooth: ; 256 step sawtoothwave table
.db 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
.db 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f
.db 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f
.db 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f
.db 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f
.db 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f
.db 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f
.db 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f
.db 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f
.db 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f
.db 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf
.db 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf
.db 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf
.db 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf
.db 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef
.db 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff


square: ; 256 step squarewave table
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
.db 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff


reset:
ldi r16, RAMEND
out SPL, r16 ; setup stack pointer
ldi r16,0x01 ; set UART speed to 500 kbps
out UBRRL,r16
; ldi r16,2 ;nur bei Control_uC da er 8 MHz clock hat
; out UCSRA,r16 ;double speed = bit1 Seite 134
ldi r16,6 ;Frame format 8N1, Seite137
out UCSRC,r16 ;an sich der Defaultwert
ldi r16,0x90 ; enable RXint and enable RXEN Seite 135
out UCSRB,r16 ; UCSRC default Seite 136

sei ; global enable interrupts
ser r16 ;
out DDRB,r16 ; set all PORTB bits as output

; set sinewave output as default
ldi r31,high(sine*2) ; setup Z pointer hi
ldi r30,low(sine*2) ; setup Z pointer lo

; clear accumulator
ldi r27,0x00 ; clear accumulator
ldi r28,0x00 ; clear accumulator
ldi r29,0x00 ; clear accumulator
;ldi r30,0x00 ; schon gesetzt mit Sinustabellenadresse

; setup adder registers ;max.Wert 12000000h = 301989888d = 102272,7Hz
ldi r22,byte1(295279*4+29528*4) ; setup adder value
ldi r23,byte2(295279*4+29528*4) ; to 440 Hz
ldi r24,byte3(295279*4+29528*4)
ldi r25,byte4(295279*4+29528*4)
;
; main loop
; r27,r28,r29,r30 is the phase accumulator
; r22,r23,r24,r25 is the adder value determining the frequency
;
; add value to accumulator
; load byte from current table in ROM
; output byte to port
; repeat
;
main: add r27,r22 ; 1
add r28,r23 ; 1
adc r29,r24 ; 1
adc r30,r25 ; 1
nop ; 1 ;das "nop" gibt höhere Genauigkeit s.o.
lpm ; 3 ;reduziert aber die max. Frequenz
out PortB,r0 ; 1 ;von 800 auf 727 kHz
rjmp main ; 2 ;ich begrenze sowieso auf 102 kHz
;in Summe 11 cycles

;**********************************************************************
; communication functionality mit 500 kBaud = 2us je bit
; ich übertrage 5 Bytes = 50 bits dauert 100 us
; gibt minimale Unterbrechung bei Frequenzumschaltung
; bei tiefen Frequenzen ist kein Umschaltgeräusch hörbar!
;**********************************************************************

; Nach dem 1. Byte werden die 4 restl. via polling geholt
; Damit ist besser sichergestellt, dass sich die Phase nicht verhaspelt

; ev. Kontrolle mit LED einbauen ob 5 Byte empfangen wurden
; war bisher nicht nötig. Wenn dann mit Hammerschlag = watchdog lösen.



rx5: cli
in r17,UDR ;das 1. Byte ist die Spannungsform
rcall get_char
mov r22,r16 ;der Phasenaccu Adderwert 32 bit
rcall get_char
mov r23,r16
rcall get_char
mov r24,r16
rcall get_char
mov r25,r16
cpi r17,5
BRLO joke
ldi r17,1 ;default SINUS
joke: mov r31,r17 ;das high Byte der Tabellen
sei
reti

get_char: ;7 ;incl. ret
sbis ucsra,rxc ;1/2 ;ready ?
rjmp get_char ;2 ;no, wait some more
in r16,UDR ;1 ;get the byte
ret ;min 10 cycles +3 je ev. waitcycle

;******************************************************************************
; end of file
;******************************************************************************



 Elektronik-Labor   Projekte   AVR