; **************************************************************
; * Angeschlossener Taster startet Morseausgabe eines Texts    *
; * (C)2005 by info@avr-asm-tutorial.net                       *
; **************************************************************
;
.INCLUDE "tn13def.inc"
;
; Schaltbild:
;                   ATMEL ATtiny13
;                     ___   ____
;            ___    1/   |_|    |8
;+5 Volt O--|___|----|Res    Vcc|----O + 5 Volt
;                    |          |   ___
;                    |PB3    PB2|--|___|--|<|--O + 5 Volt   
;                /  3|          |6   _    LED
;     0 Volt O--O O--|PB4    PB1|---|_|--O + 5 Volt
;                   4|          |   / \
;        0 Volt O----|Gnd    PB0|   ---
;                    |__________|   LSP
;
; Waehlbare Konstanten
;
.EQU cSpeed = 90 ; Gebegeschwindigkeit in bpm, 5..500 bpm
.EQU cTone = 1000 ; Tonhoehe in Hz, 300..8000 Hz
;
; Festgelegte Konstanten
;
.EQU fClock = 1200000
.EQU cTc0Div = 8
;
; Berechnete Konstanten
;
.EQU cTc0CmpA = fClock/cTc0Div/cTone/2
.EQU cTc0CmpB = cTc0CmpA/2
.EQU cTc0N = cTone*16/cSpeed
.EQU cTc0N2 = 2*cTc0N ; zweifach lange Zeit
.EQU cTc0N3 = 3*cTc0N ; dreifach lange Zeit
;
; Ueberpruefen der eingestellten Werte
;
.IF cTc0CmpA > 255
	.ERROR "Ton zu niedrig!"
	.ENDIF
.IF cTc0CmpA < 8
	.ERROR "Ton zu hoch!"
	.ENDIF
.IF cTc0N3 > 65535
	.ERROR "Frequenz zu niedrig!"
	.ENDIF
.IF cTc0N < 8
	.ERROR "Frequenz zu hoch!"
	.ENDIF
;
; Register Definitionen
;
.DEF rSreg = R15 ; Sicherungsregister fuer SREG in Ints
.DEF rmp = R16 ; Multipurpose register
.DEF rimp = R17 ; Interrupt multipurpose register
.DEF rFlg = R18 ; Flaggen
	.EQU bAktiv = 0 ; Ausgabe ist aktiv
	.EQU bPKurz = 1 ; Zeichenausgabe
	.EQU bPLang = 2 ; Pause zwischen Zeichen
.DEF rSig = R19 ; Auszugebendes Morsezeichen
.DEF rLen = R20 ; Anzahl Kurz und Lang
; frei R21..R23
.DEF rCntL = R24 ; Zaehler fuer Anzahl Halbwellen NF, LSB
.DEF rCntH = R25 ; dto., MSB
;
; Reset- and interrupt vectors
;
.CSEG ; Assembliere in den Programm-Flash-Speicher
.ORG $0000 ; beginne mit Adresse 0
;
; Sprungvektoren fuer Reset und Interrupts
;
	rjmp main ; Reset vector
	reti ; Int0 interrupt vector
	rjmp IntPcInt ; PCINT0 vector
	reti ; TC0 overflow vector
	reti ; Eeprom ready vector
	reti ; Analog comparator int vector
	rjmp IntTcCmpA ; TC0 CompA vector
	reti ; TC0 CompB vector
	reti ; WDT vector
	reti ; ADC conversion complete vector
;
; PCINT0 Service Routine
;   wird jedes Mal ausgefuehrt, wenn der Pegel am
;   Pin 3 (=PB4) wechselt (Taste wurde betaetigt)
;
IntPcInt:
	in rSreg,SREG ; sichere Flags
	sbic PINB,4 ; Ueberspringe naechsten Befehl wenn PB4 Null
	rjmp IntPcInt1 ; springe weil PB4 = Eins
	sbrc rFlg,bAktiv ; wenn schon aktiv: ignorieren
	rjmp IntPcInt1 ; ist schon aktiv
	ldi XH,HIGH(2*MorseText) ; Zeiger auf auszugebenden Text
	ldi XL,LOW(2*MorseText)
	ldi rCntH,0 ; Verzoegerung definieren
	ldi rCntL,1
	cbi PORTB,2 ; LED anschalten
	sbr rFlg,(1<<bAktiv)|(1<<bPLang) ; 
	ldi rimp,cTc0CmpA ; Dauer Halbwelle in Compare A
	out OCR0A,rimp
	ldi rimp,cTc0CmpB ; Halbe Dauer Halbwelle in Compare B
	out OCR0B,rimp
	ldi rimp,0b00110010 ; CTC, Ausgang B bei Compare Match auf Stumm
	out TCCR0A,rimp
	ldi rimp,0b00000010 ; Timer prescaler auf 8
	out TCCR0B,rimp
	ldi rimp,0b00000100 ; OCIE0A Interrupt einschalten
	out TIMSK0,rimp
IntPcInt1:
	out SREG,rSreg ; stelle Flags wieder her
	reti ; Kehre vom Interrupt zurueck
;
; Interrupt bei Timer-Ablauf
;
IntTcCmpA:
	in rSreg,SREG ; Flags retten
	sbiw rCntL,1 ; noch ein Durchlauf?
	brne IntTcCmpARet
	ldi rCntH,HIGH(cTc0N) ; Zaehler nachladen
	ldi rCntL,LOW(cTc0N)
	sbrs rFlg,bPKurz ; Kurzpausen-Bit gesetzt?
	rjmp IntTcCmpANoKu ; Kurz-Bit nicht gesetzt
	cbr rFlg,1<<bPKurz ; Kurz-Bit ruecksetzen
	dec rLen ; Anzahl Kurz/Lang in Zeichen vermindern
	brne IntTcCmpAStumm ; noch nicht Null, stumme Pause
	sbr rFlg,1<<bPLang ;lange Pause zwischen Zeichen
	ldi rCntH,HIGH(cTc0N2) ; Zaehler auf doppelte Dauer
	ldi rCntL,LOW(cTc0N2)
IntTcCmpAStumm:
	ldi rimp,0b00110010 ; Ausgang B bei Compare Match auf Stumm
	out TCCR0A,rimp
	rjmp IntTcCmpARet
IntTcCmpANoKu:
	sbrs rFlg,bPLang ; Langpausen-Bit gesetzt?
	rjmp IntTcCmpAAusgabe ; nei, weiter Zeichen ausgeben
	cbr rFlg,1<<bPLang ; setze Langpausen-Bit zurueck
	mov ZH,XH ; Z auf zu lesendes Zeichen setzen
	mov ZL,XL
	lpm ; Zeichen aus Flash in R0 einlesen
	mov rimp,R0 ; Zeichen nach rimp kopieren
	subi rimp,0x20 ; Ziehe Kontrollzeichen ab
	brcs IntTcCmpAEnde ; Zeichen ist Kontrollzeichen, beende Ausgabe
	adiw XL,1 ; X zeigt auf naechstes Zeichen
	cpi rimp,$40 ; Kleinbuchstabe?
	brcs IntTcCmpANoKlein
	subi rimp,$20 ; Kleinbuchstabe in Grossbuchstabe
IntTcCmpANoKlein:
	lsl rimp ; Tabellen-Offset mal zwei
	ldi ZH,HIGH(2*MorseTabelle) ; Z zeigt auf Tabelle
	ldi ZL,LOW(2*MorseTabelle)
	add ZL,rimp ; Offset zum Tabellenanfang addieren
	ldi rimp,0 ; Ueberlauf addieren
	adc ZH,rimp
	lpm ; Morsecode lesen
	mov rSig,R0 ; in Signalspeicher kopieren
	adiw ZL,1 ; zeige auf Zeichenlaenge
	lpm ; Zeichenlaenge lesen
	mov rLen,R0 ; in Zeichenlaengen-Speicher
	sbrc rLen,7 ; Bit 7 signalisiert Leerzeichen
	rjmp IntTcCmpALeer
IntTcCmpAAusgabe:
	sbr rFlg,1<<bPKurz ; Kurzpausen-Bit setzen
	lsl rSig ; naechstes Signal in Carry schieben
	brcc IntTcCmpAPieps ; kurzes Signal
	ldi rCntH,HIGH(cTc0N3) ; langes Signal
	ldi rCntL,LOW(cTc0N3)
IntTcCmpAPieps:
	ldi rmp,0b00010010 ; CTC, Ausgang B bei Compare Match auf Toggle
	out TCCR0A,rmp
	rjmp IntTcCmpARet
IntTcCmpALeer:
	sbr rFlg,(1<<bPKurz)|(1<<bPLang) ; setze Kurz- und Langpausen-Bit
	ldi rCntH,HIGH(cTc0N2) ; lange Pause
	ldi rCntL,LOW(cTc0N2)
	rjmp IntTcCmpAStumm
IntTcCmpAEnde:
	sbi PORTB,2 ; LED ausschalten
	ldi rimp,0b00000000
	out TCCR0B,rimp ; Timer stoppen
	out TIMSK0,rimp ; Timer-Interrupts abschalten
	sbi PORTB,1 ; Ausgang auf ins schalten
	cbr rFlg,1<<bAktiv ; Aktiv-Flag abschalten
IntTcCmpARet:
	out SREG,rSreg ; Flag register wieder herstellen
	reti
;
; Hauptprogramm nach dem Start
;
main:
;
; Stapelzeiger setzen fuer Rueckkehr-Adressen vom Interrupt
;
	ldi rmp,LOW(RAMEND) ; Stapelzeiger auf Ende SRAM
	out SPL,rmp
;
; Angeschlossene Hardware initiieren
;
	sbi DDRB,1 ; Lautsprecher-Ausgang als Ausgang definieren
	sbi PORTB,1 ; Lautsprecher-Ausgang auf Eins setzen
	sbi DDRB,2 ; LED-Ausgang als Ausgang definieren
	sbi PORTB,2 ; LED-Ausgang abschalten
	cbi DDRB,4 ; Taster-Eingang als Eingang definieren
	sbi PORTB,4 ; Internen Pull-Up-Widerstand einschalten
;
; Pin-Change-Interrupt fuer Taste aktivieren
;
	ldi rmp,0b00010000 ; Maskieren der aktiven Eingaenge
	out PCMSK,rmp
	ldi rmp,0b00100000 ; PCINT0-Interrupts ermoeglichen
	out GIMSK,rmp
;
; Interrupts generell einschalten
;
	sei ; Setze Interrupt Flagge
;
; Schlafmodus der CPU einstellen
;
	ldi rmp,0b00100000 ; Schlafen ermoeglichen, Modus Idle
	out MCUCR,rmp
;
; Loop mit Interrupt
;
loop:
	sleep ; Prozessor schlafen legen
	nop ; Tue nichts nach dem Aufwachen

	rjmp loop ; Prozessor wieder schlafen legen
;
; Morsecode der ASCII-Zeichen 0x20 bis 0x5F
; unteres Byte = Code (Ausgabe von links nach rechts, 0=Kurz, 1=Lang)
; oberes Byte  = Anzahl Punkte/Striche
;                Bit 7 = 1: Leerzeichen
;
Morsetabelle:
; Zeichen 20 .. 2F
.DB 0b00000000,0b10000000 ; Blank
.DB 0b01000000,5 ; ! = Warten
.DB 0b01001000,6 ; "
.DB 0b11011000,5 ; # = ~n
.DB 0b01101000,5 ; $ = á, °a
.DB 0b01000000,5 ; % = é
.DB 0b00000000,0b10000000 ; & = nicht benutzt, leer
.DB 0b01111000,6 ; '
.DB 0b10110000,5 ; (
.DB 0b10110100,6 ; )
.DB 0b00000000,0b10000000 ; * = nicht benutzt, leer
.DB 0b00010100,6 ; + = Spruchende
.DB 0b11001100,6 ; ,
.DB 0b10000100,6 ; -
.DB 0b01010100,6 ; .
.DB 0b10010000,5 ; /
;Zeichen 30 .. 3F
.DB 0b11111000,5 ; 0
.DB 0b01111000,5 ; 1
.DB 0b00111000,5 ; 2
.DB 0b00011000,5 ; 3
.DB 0b00001000,5 ; 4
.DB 0b00000000,5 ; 5
.DB 0b10000000,5 ; 6
.DB 0b11000000,5 ; 7
.DB 0b11100000,5 ; 8
.DB 0b11110000,5 ; 9
.DB 0b11100000,6 ; :
.DB 0b10101000,6 ; ;
.DB 0b10101000,5 ; < = Verkehrsanfang
.DB 0b10001000,5 ; =
.DB 0b01010000,5 ; > = Verkehrsende
.DB 0b00110000,6 ; ?
;Zeichen 40 .. 4F
.DB 0b11110000,4 ; @ = ch
.DB 0b01000000,2 ; A
.DB 0b10000000,4 ; B
.DB 0b10100000,4 ; C
.DB 0b10000000,3 ; D
.DB 0b00000000,1 ; E
.DB 0b00100000,4 ; F
.DB 0b11000000,3 ; G
.DB 0b00000000,4 ; H
.DB 0b00000000,2 ; I
.DB 0b01110000,4 ; J
.DB 0b10100000,3 ; K
.DB 0b01000000,4 ; L
.DB 0b11000000,2 ; M
.DB 0b10000000,2 ; N
.DB 0b11100000,3 ; O
;Zeichen 50 .. 5F
.DB 0b01100000,4 ; P
.DB 0b11010000,4 ; Q
.DB 0b01000000,3 ; R
.DB 0b00000000,3 ; S
.DB 0b10000000,1 ; T
.DB 0b00100000,3 ; U
.DB 0b00010000,4 ; V
.DB 0b01100000,3 ; W
.DB 0b10010000,4 ; X
.DB 0b10110000,4 ; Y
.DB 0b11000000,4 ; Z
.DB 0b01010000,4 ; [ = Ä
.DB 0b11100000,4 ; \ = Ö
.DB 0b00110000,4 ; ] = Ü
.DB 0b00000000,8 ; ^ = Irrung
.DB 0b00110100,6 ; _
;
; Text fuer Morseausgabe
;
MorseText:
;.DB "<test test test>",0
; .DB "<HALLO! HIER IST EIN ATMEL TINY13 BEI DER ARBEIT!>",0,0
; .DB "paris paris paris paris paris paris paris paris paris paris ",0,0
.DB "<Die kalte Hand am Arsch des Sheriffs.>",0
;
; Ende Quellcode
;
