Uhr mit Telegraphieausgabe


von Ralf Beesner
Elektronik-Labor   Projekte   AVR 


 

Im ELO-Beitrag zum Telegraphie-Thermometer schrieb ich zum Schluss etwas augenzwinkernd, dass das Gerät auch hilft, den Tag zu ordnen, weil es sich ähnlich wie eine Uhr mit Schlagwerk regelmäßig meldet. Noch besser könnte das eine exakte Uhr mit Morseausgabe. Und während es schon Milliarden Uhren auf der Welt gibt und sicherlich Millionen mit Big-Ben- Schlagwerken oder Kuckucksrufen, wäre das ein lohnendes Bastelprojekt, weil man so eine Uhr nicht für ein paar Euro im Laden kaufen kann.

In Roland Walters "AVR-Mikrocontroller-Lehrbuch" hatte ich über einen speziellen Uhrenmodus des Atmega8 gelesen: Wenn der Atmega8 mit dem internen RC-Oszillator als Taktgeber läuft, kann man den Timer2 des Chips in einen Modus versetzen, in dem er seinen Takt asynchron aus einem 32 kHz- Oszillator bezieht.

 

Der 32 kHz-Uhrenquarz wird an die gleichen Pins angeschlossen wie ein Standard- Taktquarz. Er kann wahlweise mit internen 36pF- Kapazitäten (durch Setzen der CKOPT-Fuse) oder externen Kondensatoren beschaltet werden. Da die Uhr mit den interen Kondensatoren zu langsam lief, habe ich externe verwendet (22 pF) - das erspart auch das Fusebit- Gefummel. Meine Uhr lief damit immer noch 1 sec / Tag zu langsam. Ich habe daher in der Software einen Feinabgleich eingebaut (siehe Kommentare im Programmlisting), der beim Tageswechsel einige Sekunden einfügt oder überspringt. Timer2 ist zwar nur ein 8 Bit- Timer, aber mit Vorteiler ist der Teilerfaktor so hoch, dass er Sekundenimpulse generieren kann. Der Atmega8 lässt sich für den Uhrenbetrieb in einen Energiesparmodus versetzen, in dem nur Timer2 läuft und der übrige Controller schlafen gelegt wird. In diesem Modus verbraucht der Atmega deutlich weniger als 10 µA. Timer 2 holt ihn einmal pro Sekunde aus dem Tiefschlaf.

Von diesem Sekundentakt ausgehend schrieb ich zunächst ein Programm, das die Uhrzeit auf einem LCD- Display anzeigt; anschließend ein Unterprogramm, das ein Cursor- Tastenkreuz (links-rechts-rauf-runter) und eine Menü- Taste abfragt, damit man die Zeit setzen und ggf. auch mehrere Weckzeiten einstellen kann.Unsinnige Eingaben sollte man auch noch programmtechnisch abfangen, und da habe ich dann die Lust verloren und mich an den klugen Spruch erinnert, dass eine Sache nicht dann perfekt ist, wenn man nichts mehr hinzufügen kann, sondern dann, wenn man nichts mehr weglassen kann ;-)

 

 

 

So sind eine sehr schlichte Schaltung und ein ziemlich kurzes Programm entstanden, das zur vollen Stunde "es ist x Uhr" in Morse ausgibt und dazwischen alle 15 Minuten die Minutenzahl (also 15, 30, 45). Die Zeitzählung erfolgt daher auch nicht in Minuten und Stunden, sondern in Viertelstunden und Stunden. Für das erstmalige Setzen der Zeit hatte ich mich an die Zeiten erinnert, als die Familie sich um 20.00 Uhr vor dem Fernseher versammelte, nach der Tagesschau- Uhr die Armbanduhr stellte und dann den regierungsamtlichen Verlautbarungen lauschte. Darum hatte ich zunächst nur vorgesehen, daß die Uhr nach einem Reset nicht um 0:00 Uhr, sondern um 20:00 Uhr startet, so daß man sie per Reset mit der Tagesschau- Uhr synchronisieren kann. Da das doch arg spartanisch war, habe ich noch ein "Mäuseklavier" drangestrickt, mit dem sich die vollen Stunden zwischen 8:00 und 23:00 Uhr als Startzeit wählen lassen.

 Wie bereits angedeutet: der ATmega8 ist für diesen Job völlig überdimensioniert. Ich hatte daher überlegt, das ganze mit einem ATtiny25 zu erschlagen. Der hat einen Quarzoszillator-Modus (auch für Low-Power-Uhrenquarze) und zwei Timer. Jedoch wird das Stromsparen schwieriger, weil der Oszillator durchlaufen muss. Außerdem ist ein 32kHz-Uhrenquarz zwar gut für die Energiebilanz, aber beim Programmieren heikel, weil die geringe Taktfrequenz für manche Programmiergeräte zu langsam ist. So bin ich dann doch beim ATmega8 geblieben - der ist mit seinen 28 Pins zwar etwas sperrig, aber bei Reichelt kostet er auch nicht mehr als ein ATtiny25. Man kommt sogar ohne Programmiergerät aus. In fabrikfrischem Zustand läuft der Atmega8 bereits mit dem 1-MHz-RC-Oszillator; das Ändern von Fusebits ist nicht erforderlich. Das Programmieren kann mit Burkhard Kainkas Mega8isp.exe erfolgen, das er für das Retro-Pingpong geschrieben hat, und als Programmierinterface reichen 2 Widerstände mit 22 kOhm (den Reset kann man von Hand mit dem Reset-Taster auslösen). Betrieben wird die Schaltung aus 3 Mignonzellen, die Platine ist auf die Unterseite eines Batteriehalters (3 Mignonzellen) geflanscht.

 Download: Mega8-Uhr

'Morseuhr
' gibt alle 15 min. die Minutenzahl aus und zur vollen Stunde die Stundenzahl
' ("es ist X Uhr")

' Einstellen der Uhrzeit geht nur zur vollen Stunde (Zeitsignal im Radio oder
' TV) per Reset
' Die Startzeit kann per 4-bit "Mäuseklavier" auf 8.00 Uhr bis 23.00 vorge-
' wählt werden.

' Der Tongeber (Buzzer) liegt an PB1, das Mäuseklavier an PB2 ... PB5

' Der Mega8 läuft in einem speziellen Uhrenbetrieb. Er wird mit 1 MHz getaktet
' (interner RC- Oszillator), Der Quarzoszillator ist mit einem 32768Hz-Uhren-
' quarz beschaltet, der den Timer 2 asynchron taktet. Timer 2 löst jede
' Sekunde einen Interrupt aus.

' Nach 900 Interrupts ist eine Viertelstunde um.

' Die Konfiguration des Timer2- Uhrenmodus und Erzeugung des Sekundenimpulses
' ist dem "AVR Mikrocontroller-Lehrbuch" von Roland Walter entnommen
'
' Version 4b: Feintuning Sekundentakt eingebaut (beim Tageswechsel Wartezeit oder Preset
' der Sekundenvariable auf Werte > 0 )
'----------------------------------------------------------
$regfile = "m8def.dat" 'ATmega8-Deklarationen
$crystal = 1000000 'Der AVR-Takt kommt vom RC-Oszillator
$baud = 9600

On Timer2 Ontimer2 'Interrupt-Routine für Timer2-Overflow


'Variablen

Dim S As Word ' Sekunden
Dim Q As Byte ' aufsummierte Viertelstunden
Dim V As Byte ' Viertelstunden für Morseausgabe
Dim H As Byte ' Stunden
Dim Z As Byte ' Zehnerstelle Stunden
Dim E As Byte ' Einerstelle Stunden

Dim J As Byte
Dim I As Byte ' wird in Zählschleife verwendet
Dim N As Word ' Aktueller Morsebuchstabe
Dim M As Byte ' wird fürs Auslesen der Morsebits verwendet
Dim L As Byte ' niederwertigstes Bit von M verwendet





' Konstanten
Const Dit = 60 ' Länge eines Punkts, daraus ergibt sich die Morsegeschwindigkeit
Const Dah = Dit * 3 ' Länge eines Strichs
Const Wort = Dit * 8 ' Länge der Pause zwischen Wörtern
Const Tonhoehe = 30 '20 ' Tonhöhe

'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Variablen dimensionieren:





Acsr.7 = 1 ' Analogen Komparator ausschalten
Assr.3 = 1 ' Timer2- Asynchron- Betrieb
Tccr2 = &B00000101 ' Vorteiler für Timer2 auf 128 setzen
Timsk.6 = 1 ' Timer2-Overflow-Interrupt an
Mcucr = &B00110000 ' Powerssave-Modus aktivieren
Sreg.7 = 1 ' Interrupts global einschalten
Ddrb = &B0000_0000 ' alle Pins sind Eingänge
Portb = &B1111_1111 ' Pullups, damit die Eingänge nicht floaten
Ddrc = &B0000_0000 ' alle Pins sind Eingänge
Portc = &B1111_1111 ' Pullups, damit die Eingänge nicht floaten
Ddrd = &B0000_0000 ' alle Pins sind Eingänge
Portd = &B1111_1111 ' Pullups, damit die Eingänge nicht floaten

'----------------------------------------------------------




' Tonsignal an OC2 / Port B 3 erzeugen; Signal wird unten durch Umschalten des DDRB im Takt der Zeichen geschaltet
'
Config Timer1 = Timer , Compare A = Toggle , Prescale = 64 , Clear Timer = 1
Ocr1a = Tonhoehe
'
Buzzer Alias Ddrb.1


' ' Startzeit aus den Dip- Schaltern auslesen

Q = Not Pinb ' invertieren, weil gesetzte Dipschalter 0 statt 1 ergeben
Q = Q And &B0011_1100 ' die Schalterbits filtern - > Q = 0,4,8 .... 28
Q = Q + 32 ' Q = 32,56 .... 80 -> Startzeit 8,9,10 ....,22,23 Uhr

Goto Einsprung:






Do 'Hauptschleife


' If S > 14 Then ' Testbetrieb ; 15 sec statt 15 min
If S > 899 Then ' Realbetrieb
S = 0
Q = Q + 1

If Q = 96 Then ' Tageswechsel; Uhr auf 00 stellen
' Waitms 500 ' Feinabgleich- Beispiel, falls die Uhr etwas zu schnell läuft
Q = 0
S = 2 ' Feinabgleich, falls die Uhr etwas zu langsam läuft
' Wait 1
End If

Einsprung:

H = Q / 4
V = Q Mod 4
Z = H / 10
E = H Mod 10


' auszugebende Morsezeichen:



If V = 1 Then ' 15. Minute
N = 1
Gosub Morse
N = 5
Gosub Morse
End If

If V = 2 Then ' 30. Minute
N = 3
Gosub Morse
N = 0
Gosub Morse
End If

If V > 2 Then ' 45. Minute
N = 4
Gosub Morse
N = 5
Gosub Morse
End If

If V = 0 Then ' volle Stunde
For N = 10 To 16 ' String "es ist "
Gosub Morse
Next
If Z > 0 Then
N = Z
Gosub Morse
End If
N = E
Gosub Morse
For N = 16 To 19 ' string " uhr"
Gosub Morse
Next
End If

End If

Mcucr.7 = 1 'Sleep-Modus erlauben
!Sleep 'In den Sleep-Modus gehen

Loop





' Morsezeichenerzeugung:

Morse:

M = Lookup(n , Tdata)

If M = 0 Then ' Sonderfall: Leerzeichen;
Waitms Wort
Goto Sign_end
End If

For I = 1 To 8
If M = 1 Then ' Das Byte hat nur noch den Wert 1; Zeichenende!
Goto Sign_end
End If
L = M And &B0000001 ' niederwertigstes Bit lesen
Buzzer = 1 ' Ausgänge einschalten
If L = 1 Then
Waitms Dah ' ist das Bit 1 -> dah
Else
Waitms Dit ' ist das Bit 0 -> dit
End If
Buzzer = 0 ' Ausgänge abschalten
Waitms Dit ' Pause innerhalb des Morsezeichens
Shift M , Right , 1 ' Bits um eine Stelle nach rechts shiften
Next I
Sign_end:
Waitms Dah ' Pause zwischen Morsezeichen
Return

End


'----------------------------------------------------------
Ontimer2: 'Timer2-Overflow-Interrupt-Routine

S = S + 1
Return



'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Codierung der Morsezeichen. Bei einem Prozessor mit viel SRAM würde man alle Zeichen
' in ein Array einlesen (Übersetzungstabelle), den eigentlichen Bakentext in einen String packen
' und diesen String zeichenenweise abarbeiten.
'
' Da der Attiny13 nur ca. 64 Byte SRAM hat, ist nur Platz für etwa 50 Bytes. Daher ist der Weg hier
' anders: Der Bakentext ist ein Array mit max. 52 Elementen, in dem die auszusendenden Zeichen stehen
' allerdings in einer leicht lesbaren schreibweise.
' Die Übersetzungstabelle ist mit Konstanten realisiert. Nur die wirklich benötigten
' Zeichen werden (beim kompilieren des Programmes) umgewandelt.


Const #a = &B00000110
Const #b = &B00010001
Const #c = &B00010101
Const #d = &B00001001
Const #e = &B00000010
Const #f = &B00010100
Const #g = &B00001011
Const #h = &B00010000
Const #i = &B00000100
Const #j = &B00011110
Const #k = &B00001101
Const #l = &B00010010
Const #m = &B00000111
Const #n = &B00000101
Const #o = &B00001111
Const #p = &B00010110
Const #q = &B00011011
Const #r = &B00001010
Const #s = &B00001000
Const #t = &B00000011
Const #u = &B00001100
Const #v = &B00011000
Const #w = &B00001110
Const #x = &B00011001
Const #y = &B00011101
Const #z = &B00010011
Const #ae = &B00011010
Const #oe = &B00010111
Const #ue = &B00011100
Const #0 = &B00111111
Const #1 = &B00111110
Const #2 = &B00111100
Const #3 = &B00111000
Const #4 = &B00110000
Const #5 = &B00100000
Const #6 = &B00100001
Const #7 = &B00100011
Const #8 = &B00100111
Const #9 = &B00101111

Const Leerz = 0
Const Anfuehrz = &B01010010
Const Kl_auf = &B00101101
Const Kl_zu = &B01101101
Const Plus = &B00101010
Const Komma = &B01110011
Const Minus = &B01100001
Const Punkt = &B01101010
Const Slash = &B00101001
Const Dopp_pkt = &B01000111
Const Gleich = &B00110001
Const Fragez = &B01001100
Const Ende = 255



Tdata:
Data #0 , #1 , #2 , #3 , #4 , #5 , #6 , #7 , #8 , #9
Data #e , #s , Leerz , #i , #s , #t , Leerz , #u , #h , #r




Tiny13-Uhr

Der im LP Microcontroller verwendete ATtiny13 ist leider recht ungeeignet - er hat nur einen Timer (der jedoch für die Tonerzeugung benötigt wird) und man kann keinen Quarz anschließen (lediglich einen kompletten externen Taktoszillator). Wenn man bereits einen quarzgenauen Sekundentakt hat (aus einer bereits vorhandenen Uhr oder aus einer Platine einer abgewrackten Quarz-Analoguhr), kann man jedoch diesen Sekundentakt auf den externen Interrupt- Eingang T0 des ATtiny13 geben und dann ähnlich verfahren wie bei dem internen Zähler-Interrupt des Atmega 8.

Ich habe daher das Programm auf den ATtiny13 portiert. Da analoge Quarzuhren meist mit 1,5 V betrieben werden und das Sekundensignal damit zu gering für einen mit 3 oder 5 V betriebenen ATtiny ist, habe ich einen Pegelwandler mit einem BC 547 vorgesehen. Da der ATtiny nur noch 3 Ports frei hat, lassen sich mit dem Mäuseklavier nur 3 bit vorwählen; der 4 Schalter liegt am Reset- Pin.

 

 

In der Junk-Box hatte ich noch die Oszillatorplatine aus einer fast 40 Jahre alten Quarz- Analoguhr. Die war zwar mit einer 1,5-V-Babyzelle bestückt, das IC lässt sich aber mit 5 V betreiben, ohne abzurauchen. Der 32 kHz- Quarz ist riesig groß; zu dem verbauten IC SAJ250 findet nicht einmal Tante Google ein Datenblatt. Aus dem IC kommen fallende Impulse von 100 msec Dauer, die ich über einen 1 nF- Kondensator auf Eingang T0 des Attiny13 gegeben habe (nicht näher untersuchter Workaraound; bei direkter Verbindung gab es pro Sekunde 2 Interrupts).

 

Download: Tiny-Uhr


' Hardware:
' Start der Uhr durch Reset- Taster
' externes 1sec- Signal aus Quarzuhr an PB1
' Buzzer an PB 0
' Stundenvorwahl (Startzeit) durch 3 Schalter an PB2, PB3, PB4, jeweils gegen Masse
'


'----------------------------------------------------------
$regfile = "attiny13.dat"
$crystal = 1200000
$hwstack = 8 ' nur max. 6 Unterprogramme, spart SRAM
$swstack = 0 ' wird nicht benötigt, spart SRAM
$framesize = 0

On Int0 Onint 'Interrupt-Routine


'Variablen

Dim S As Word ' Sekunden
Dim Q As Byte ' aufsummierte Viertelstunden
Dim V As Byte ' Viertelstunden M = Lookup(n , Ndata) für Morseausgabe
Dim H As Byte ' Stunden
Dim Z As Byte ' Zehnerstelle Stunden
Dim E As Byte ' Einerstelle Stunden

Dim J As Byte
Dim I As Byte ' wird in Zählschleife verwendet
Dim N As Word ' Aktueller Morsebuchstabe
Dim M As Byte ' wird fürs Auslesen der Morsebits verwendet
Dim L As Byte ' niederwertigstes Bit von M verwendet





' Konstanten
Const Dit = 60 ' Länge eines Punkts, daraus ergibt sich die Morsegeschwindigkeit
Const Dah = Dit * 3 ' Länge eines Strichs
Const Wort = Dit * 8 ' Länge der Pause zwischen Wörtern
Const Tonhoehe = 14 ' Tonhöhe

'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Variablen dimensionieren:



Gimsk.6 = 1 ' INT0 enable
Sreg.7 = 1 ' Global Interrupt enable
Mcucr.0 = 1 ' INT0 (wiederholt) bei steigender Flanke an PB1
Mcucr.1 = 1 ' INT0 zweites Bit
Acsr.acd = 1 'Analogen Komparator ausschalten

Ddrb = &B0000_0000 ' alle Pins Eingang
Portb = &B0001_1111 ' alle Pins Pullup-Widerstand



'----------------------------------------------------------
' Tonsignal an OC2 / Port B 3 erzeugen; Signal wird unten durch Umschalten des DDRB im Takt der Zeichen geschaltet
'
Config Timer0 = Timer , Compare A = Toggle , Prescale = 64 , Clear Timer = 1
Ocr0a = Tonhoehe


Buzzer Alias Ddrb.0


Buzzer = 0
'Q = 80 ' durch Reset Uhr auf 20:00 Uhr einstellen (Tagesschau- Zeitsignal ;-)

' ' Alternative: Startzeit mit Dip- Schalter vorwählen
Q = Not Pinb ' Invertieren, da gesetzter Schalter -> 0
Q = Q And &B0001_1100 ' Dipschalter-Bits rausfiltern -> Q = 0,4,8 .... 28
Q = Q + 52 ' Q = 52,56 .... 80 -> Startzeit 13, 14, 15, 16, 17,18,19,20 Uhr

Goto Einsprung:


Do 'Hauptschleife


If S > 14 Then 'Testbetrieb
' If S > 899 Then ' Realbetrieb Viertelstunde ist um
S = 0
Q = Q + 1 '

If Q = 96 Then ' Tageswechsel; Uhr auf 00 stellen
Q = 0
End If

Einsprung:

H = Q / 4 ' Stunden = aufsummierte Viertelstunden / 4
V = Q Mod 4 ' Rest Viertelstunden
Z = H / 10 ' Zehnerstelle Stunden
E = H Mod 10 ' Einerstelle Stunden


' auszugebende Morsezeichen:



If V = 1 Then ' 15. Minute
N = 1
Gosub Morse
N = 5
Gosub Morse
End If

If V = 2 Then ' 30. Minute
N = 3
Gosub Morse
N = 0
Gosub Morse
End If

If V > 2 Then ' 45. Minute
N = 4
Gosub Morse
N = 5
Gosub Morse
End If

If V = 0 Then ' volle Stunde
For N = 10 To 16 ' String "es ist "
Gosub Morse
Next
If Z > 0 Then ' Zehnerstelle ausgeben, wenn ungleich Null
N = Z
Gosub Morse
End If
N = E ' Einerstelle ausgeben
Gosub Morse
For N = 16 To 19 ' string " uhr"
Gosub Morse
Next
End If

End If


Mcucr.5 = 1 'Sleep-Modus erlauben
!Sleep 'In den Sleep-Modus gehen

Loop





' Morsezeichenerzeugung:


Morse:

M = Lookup(n , Tdata)


If M = 0 Then ' Sonderfall: Leerzeichen;
Waitms Wort
Goto Sign_end
End If

For I = 1 To 8
If M = 1 Then ' Das Byte hat nur noch den Wert 1; Zeichenende!
Goto Sign_end
End If
L = M And &B0000001 ' niederwertigstes Bit lesen
Buzzer = 1 ' Ausgänge einschalten
If L = 1 Then
Waitms Dah ' ist das Bit 1 -> dah
Else
Waitms Dit ' ist das Bit 0 -> dit
End If
Buzzer = 0 ' Ausgänge abschalten
Waitms Dit ' Pause innerhalb des Morsezeichens
Shift M , Right , 1 ' Bits um eine Stelle nach rechts shiften
Next I
Sign_end:
Waitms Dah ' Pause zwischen Morsezeichen
Return

End


'----------------------------------------------------------
Onint: 'Timer2-Overflow-Interrupt-Routine

S = S + 1 ' Sekunden inkrementieren

Return








'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Codierung der Morsezeichen. Bei einem Prozessor mit viel SRAM würde man alle Zeichen
' in ein Array einlesen (Übersetzungstabelle), den eigentlichen Bakentext in einen String packen
' und diesen String zeichenenweise abarbeiten.
'
' Da der Attiny13 nur ca. 64 Byte SRAM hat, ist nur Platz für etwa 50 Bytes. Daher ist der Weg hier
' anders: Der Bakentext ist ein Array mit max. 52 Elementen, in dem die auszusendenden Zeichen stehen
' allerdings in einer leicht lesbaren schreibweise.
' Die Übersetzungstabelle ist mit Konstanten realisiert. Nur die wirklich benötigten
' Zeichen werden (beim kompilieren des Programmes) umgewandelt.


Const #a = &B00000110
Const #b = &B00010001
Const #c = &B00010101
Const #d = &B00001001
Const #e = &B00000010
Const #f = &B00010100
Const #g = &B00001011
Const #h = &B00010000
Const #i = &B00000100
Const #j = &B00011110
Const #k = &B00001101
Const #l = &B00010010
Const #m = &B00000111
Const #n = &B00000101
Const #o = &B00001111
Const #p = &B00010110
Const #q = &B00011011
Const #r = &B00001010
Const #s = &B00001000
Const #t = &B00000011
Const #u = &B00001100
Const #v = &B00011000
Const #w = &B00001110
Const #x = &B00011001
Const #y = &B00011101
Const #z = &B00010011
Const #ae = &B00011010
Const #oe = &B00010111
Const #ue = &B00011100
Const #0 = &B00111111
Const #1 = &B00111110
Const #2 = &B00111100
Const #3 = &B00111000
Const #4 = &B00110000
Const #5 = &B00100000
Const #6 = &B00100001
Const #7 = &B00100011
Const #8 = &B00100111
Const #9 = &B00101111

Const Leerz = 0
Const Anfuehrz = &B01010010
Const Kl_auf = &B00101101
Const Kl_zu = &B01101101
Const Plus = &B00101010
Const Komma = &B01110011
Const Minus = &B01100001
Const Punkt = &B01101010
Const Slash = &B00101001
Const Dopp_pkt = &B01000111
Const Gleich = &B00110001
Const Fragez = &B01001100
Const Ende = 255



Tdata:
Data #0 , #1 , #2 , #3 , #4 , #5 , #6 , #7 , #8 , #9

Data #e , #s , Leerz , #i , #s , #t , Leerz , #u , #h , #r

Nachtrag von Hartmut Wynen:  Das Datenblatt zum SAJ250


Elektronik-Labor   Projekte   AVR