; Digitaler Funktionsgenerator für Sinus, Rechteck, Dreieck und Sägezahn ; ; Copyright (C) 2003 Andreas Schwarz ; ; This program is free software; you can redistribute it and/or ; modify it under the terms of the GNU General Public License ; as published by the Free Software Foundation; either version 2 ; of the License, or (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ; ; ; Änderungen: ; ; ??.05.02 - Anfang: einfache Ausgabe von Sinus-Funktionswerten mit fester Frequenz ; ??.??.02 - Phasenakkumulator ; - Einstellung der Frequenz über Taster ; - Anzeige von Frequenz und Signalform auf LCD ; 18.10.02 - Frequenzanzeige funktioniert korrekt ; 19.10.02 - Auswahl der Signalformen ; - Signalformen Dreieck und Sägezahn hinzugefügt ; 15.12.02 - Delay-Funktionen verbessert (jetzt teilweise mit Timer statt Warteschleife) ; 29.01.03 - in der Frequenzanzeige wird nun eine Nachkommastelle angezeigt ; - "Überlauf" der Frequenzeinstellungs-Variable nach unten hin ; tritt nicht mehr auf; nach oben hin immer noch (ist aber unkritisch) ; - Umstellung von AT90S2333 auf ATmega8 (theoretisch bessere Sicherheit für ; EEPROM-Inhalt wegen funktionierender Brown-Out-Protection) ; 31.01.03 - feinere Frequenzeinstellung (in 0.5 Hz-Schritten) .include "m8def.inc" .listmac ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Konstanten ;; ;; ========== ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .equ COMPAREVALUE = 24 ; Zählerstand bei dem der Interrupt ; ausgelöst wird .equ KEY_UPF = 3 .equ KEY_UP = 2 .equ KEY_DOWN = 1 .equ KEY_DOWNF = 0 .equ KEY_WAVE = 4 .equ STEPF = 256 ; starke Änderung der Phasenakk.-Schrittweite .equ STEP = 32 ; schwache Änderung der Phasenakk.-Schrittweite ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Registerdefinitionen ;; ;; ==================== ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .def null = R1 ; immer 0 .def full = R2 ; immer "voll" = 0xFF = 255 = 0b11111111 .def sregsave = R9 ; Zwischenspeicher für das Statusregister ; während der Interruptroutine .def temp1 = R16 ; Zwischenspeicher 1 .def temp2 = R17 ; Zwischenspeicher 2 .def temp3 = R18 ; Zwischenspeicher 3 .def change1 = R3 ; Schrittweite, die durch den Phasenakkumulator ; zur Phasenposition addiert wird ; (24 Bit, auf 3 Register a 8 Bit aufgeteilt) .def change2 = R4 .def change3 = R5 .def z1 = R28 ; Dieses Registertripel enthält die Phasenposition .def z2 = R29 ; zu der die Register changeX bei der Phasenakk. .def z3 = R30 ; addiert werden .def waveform = R20 ; eingestellte Wellenform ; (Sinus, Rechteck, Dreieck oder Sägezahn) ; Register für die Binär nach Dezimal-Wandlung .def fbin0 = R6 ; binary value byte 0 (LSB) .def fbin1 = R7 ; binary value byte 1 .def fbin2 = R21 ; binary value byte 2 .def fbin3 = R22 ; binary value byte 3 (MSB) .def tBCD0 = R21 ; BCD value digits 1 and 0 (same as fbin2) .def tBCD1 = R22 ; BCD value digits 3 and 2 (same as fbin3) .def tBCD2 = R23 ; BCD value digits 4 and 5 .def tBCD3 = R24 ; BCD value digits 6 and 7 .def tBCD4 = R25 ; BCD value digits 8 and 9 .def fbinL = R26 ; binary value Low byte .def fbinH = R27 ; binary value High byte ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Makros ;; ;; ====== ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Z-Pointer auf bestimmte Adresse setzen .MACRO SetZPtr ;(Adresse) ldi ZL, LOW(@0) ldi ZH, HIGH(@0) .ENDMACRO ;String aus dem EEPROM auf dem LCD anzeigen .MACRO print ;(String-Adresse) push temp1 ldi temp1,@0 rcall printstring pop temp1 .ENDMACRO ;LCD-Cursor an bestimmte Position setzen .MACRO locate ;(Zeile,Spalte) push temp1 ldi temp1, 0b10000000|(((@0)-1)<<6)|((@1)-1) rcall lcd_command pop temp1 .ENDMACRO ;bestimmte Anzahl ms warten .MACRO waitms push temp1 ldi temp1, @0*6 rcall wms pop temp1 .ENDMACRO .MACRO wait10ms push temp1 ldi temp1, @0*6 rcall w10ms pop temp1 .ENDMACRO ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Interruptvektoren ;; ;; ================= ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .org 0x000 ; kommt ganz an den Anfang des Speichers ; (hier fängt die Programmausführung an!) rjmp RESET ; der Timer-Interrupt und die Tabellen werden ; übersprungen -> zum Hauptprogramm .org OC1Aaddr ; Int.vektor für Timer 1 Compare Match ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Timer-Interrupt ;; ;; =============== ;; ;; ;; ;; Dieser Programmteil wird exakt 256000 Mal pro Sekunde ;; ;; durch den Timer aufgerufen. Das Hauptprogramm wird dazu ;; ;; unterbrochen und anschließend fortgesetzt. ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; timer: in sregsave, SREG ; Statusregister sichern ; Sägezahn und Rechteck bekommen eine Sonderbehandlung: cpi waveform, t_square ; ist Rechteck eingestellt? breq square ; wenn ja, springe zu square cpi waveform, t_sawtooth ; ist Sägezahn eingestellt? breq sawtooth ; wenn ja, springe zu sawtooth ;Bei Sinus oder Dreieck wird einfach der Wert aus dem Speicher gelesen lpm ; Funktionswert aus dem Speicher lesen add z1, change1 ; Phasenakkumulator adc z2, change2 adc z3, change3 out PORTD, R0 ; Wert an den Analog-Digital-Wandler ausgeben out SREG, sregsave ; Statusregister wiederherstellen reti ; Interrupt verlassen, zurück zum Hauptprogramm square: ; falls Rechteck: add z1, change1 ; Phasenakumulator adc z2, change2 adc z3, change3 cpi z3, 128 ; Wenn die Mitte einer Schwingungsperiode ; überschritten ist... brlo square_1 out PORTD, null ; ...dann minimale Ausgangsspannung (0) out SREG, sregsave ; Statusregister wiederherstellen reti ; Interrupt verlassen, zurück zum Hauptprogramm square_1: out PORTD, full ; ...andernfalls maximale Ausgangsspannung out SREG, sregsave ; (siehe oben) reti ; sawtooth: add z1, change1 ; Phasenakkumulator adc z2, change2 adc z3, change3 out PORTD, z3 ; Beim Sägezahn ist die Spannung proportional zur ; Position in der Phase, das höchstwertige Byte ; der Position kann also direkt an den AD-Wandler ; gegeben werden out SREG, sregsave reti ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Tabellen mit den Funktionswerten ;; ;; ================================ ;; ;; ;; ;; Für Sinus und Dreieck sind hier die vorausberechneten ;; ;; Funktionswerte abgespeichert ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .org 0x100 SINTABLE: .db 0x80,0x82,0x85,0x88,0x8C,0x8F,0x92,0x95 .db 0x98,0x9B,0x9E,0xA1,0xA4,0xA7,0xAA,0xAD .db 0xB0,0xB3,0xB6,0xB9,0xBB,0xBE,0xC1,0xC3 .db 0xC6,0xC9,0xCB,0xCE,0xD0,0xD3,0xD5,0xD7 .db 0xDA,0xDC,0xDE,0xE0,0xE2,0xE4,0xE6,0xE8 .db 0xE9,0xEB,0xED,0xEE,0xF0,0xF1,0xF3,0xF4 .db 0xF5,0xF6,0xF8,0xF9,0xF9,0xFA,0xFB,0xFC .db 0xFD,0xFD,0xFE,0xFE,0xFE,0xFF,0xFF,0xFF .db 0xFF,0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFD .db 0xFD,0xFC,0xFB,0xFA,0xF9,0xF9,0xF8,0xF6 .db 0xF5,0xF4,0xF3,0xF1,0xF0,0xEE,0xED,0xEB .db 0xE9,0xE8,0xE6,0xE4,0xE2,0xE0,0xDE,0xDC .db 0xDA,0xD7,0xD5,0xD3,0xD0,0xCE,0xCB,0xC9 .db 0xC6,0xC3,0xC1,0xBE,0xBB,0xB9,0xB6,0xB3 .db 0xB0,0xAD,0xAA,0xA7,0xA4,0xA1,0x9E,0x9B .db 0x98,0x95,0x92,0x8F,0x8C,0x88,0x85,0x82 .db 0x80,0x7D,0x7A,0x77,0x73,0x70,0x6D,0x6A .db 0x67,0x64,0x61,0x5E,0x5B,0x58,0x55,0x52 .db 0x4F,0x4C,0x49,0x46,0x44,0x41,0x3E,0x3C .db 0x39,0x36,0x34,0x31,0x2F,0x2C,0x2A,0x28 .db 0x25,0x23,0x21,0x1F,0x1D,0x1B,0x19,0x17 .db 0x16,0x14,0x12,0x11,0xF,0xE,0xC,0xB .db 0xA,0x9,0x7,0x6,0x6,0x5,0x4,0x3 .db 0x2,0x2,0x1,0x1,0x1,0x0,0x0,0x0 .db 0x0,0x0,0x0,0x0,0x1,0x1,0x1,0x2 .db 0x2,0x3,0x4,0x5,0x6,0x6,0x7,0x9 .db 0xA,0xB,0xC,0xE,0xF,0x11,0x12,0x14 .db 0x16,0x17,0x19,0x1B,0x1D,0x1F,0x21,0x23 .db 0x25,0x28,0x2A,0x2C,0x2F,0x31,0x34,0x36 .db 0x39,0x3C,0x3E,0x41,0x44,0x46,0x49,0x4C .db 0x4F,0x52,0x55,0x58,0x5B,0x5E,0x61,0x64 .db 0x67,0x6A,0x6D,0x70,0x73,0x77,0x7A,0x7D TRIANGLETABLE: .db 0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e .db 0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e .db 0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e .db 0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e .db 0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e .db 0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e .db 0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e .db 0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e .db 0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e .db 0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e .db 0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae .db 0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe .db 0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce .db 0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde .db 0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee .db 0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe .db 0xff,0xfd,0xfb,0xf9,0xf7,0xf5,0xf3,0xf1 .db 0xef,0xed,0xeb,0xe9,0xe7,0xe5,0xe3,0xe1 .db 0xdf,0xdd,0xdb,0xd9,0xd7,0xd5,0xd3,0xd1 .db 0xcf,0xcd,0xcb,0xc9,0xc7,0xc5,0xc3,0xc1 .db 0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1 .db 0xaf,0xad,0xab,0xa9,0xa7,0xa5,0xa3,0xa1 .db 0x9f,0x9d,0x9b,0x99,0x97,0x95,0x93,0x91 .db 0x8f,0x8d,0x8b,0x89,0x87,0x85,0x83,0x81 .db 0x7f,0x7d,0x7b,0x79,0x77,0x75,0x73,0x71 .db 0x6f,0x6d,0x6b,0x69,0x67,0x65,0x63,0x61 .db 0x5f,0x5d,0x5b,0x59,0x57,0x55,0x53,0x51 .db 0x4f,0x4d,0x4b,0x49,0x47,0x45,0x43,0x41 .db 0x3f,0x3d,0x3b,0x39,0x37,0x35,0x33,0x31 .db 0x2f,0x2d,0x2b,0x29,0x27,0x25,0x23,0x21 .db 0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x11 .db 0x0f,0x0d,0x0b,0x09,0x07,0x05,0x03,0x01 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Stringkonstanten (im EEPROM) ;; ;; ============================ ;; ;; ;; ;; Hier sind die Zeichenketten gespeichert, die auf dem LCD ;; ;; angezeigt werden sollen; die Speicheradressen der ;; ;; Strings dienen gleichzeitig als Konstanten zur ;; ;; Festlegung der eingestellten Signalform. ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; .eseg .org 0x01 t_sinus: .db "Sinus ",0 t_square: .db "Rechteck ",0 t_triangle: .db "Dreieck ",0 t_sawtooth: .db "Saegezahn",0 t_hz: .db " Hz",0 .cseg ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Initialisierung ;; ;; =============== ;; ;; ;; ;; Hier wird der Prozessor initialisiert: ;; ;; Stackpointer, Ein-Ausgänge, Timerfrequenzen, ... ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; RESET: ldi temp1, low(RAMEND) out SPL, temp1 ; Stackpointer initialisieren ldi temp1, high(RAMEND) out SPH, temp1 ldi temp1, 0x00 out EEARH, temp1 ldi temp1, 0xFF out DDRD, temp1 ; Port D = Ausgang ldi temp1, 0x00 out DDRC, temp1 ; Port C = Eingang ldi temp1, 0xFF out PORTC, temp1 ; interne Pullups für Port C aktivieren ldi temp1, 0xFF out DDRB, temp1 ; Port B = Ausgang ldi temp1, 0x00 out PORTB, temp1 ldi temp1, 0x00 ; Additionsregister mit 0x00FA00 vorbelegen mov change1, temp1 ; (entspricht einer Ausgangsfrequenz von 1000 Hz) ldi temp1, 0xFA mov change2, temp1 ldi temp1, 0x00 mov change3, temp1 clr z1 ; Phasenposition auf 0 setzen clr z2 clr z3 ldi waveform, t_sinus ; Voreinstellung für Ausgangssignal: Sinus clr null clr full com full SetZPtr(SINTABLE*2) ; Adresse der Sinus-Tabelle in den Z-Pointer laden ldi temp1, low(COMPAREVALUE) ; Compare-Wert laden; bei einem Zählerstand von ; COMPAREVALUE wird der Timer-Interrupt aktiv out OCR1AL, temp1 ldi temp1, high(COMPAREVALUE) out OCR1AH, temp1 ldi temp1, (1<