Das Morse-Thermometer


Wer morsen kann, ist klar im Vorteil
von Ralf Beesner, DK5BU
Elektronik-Labor   Projekte   AVR 



Die beschränkten Ressourcen des Attiny13 (nur 5 I/O-Ports) machen es umständlich, Messergebnisse oder Zustandsänderungen auszugeben. In vielen Programmbeispielen erfolgt die Ausgabe über eine Software- COM- Schnittstelle, aber für sie wird ein PC mit einem Terminalprogramm benötigt. 

Ideal ist die Signalisierung per Tonsignal. Man benötigt nur einen Piezo- Schallwandler, und es wird nur ein Ausgang des Attiny13 belegt.Gibt man Messwerte als Tonhöhe oder als Anzahl von Piepsern aus, ist noch nicht viel gewonnen.  Beherrscht man jedoch das Morsealphabet, ist eine elegante Signalisierung möglich, da sich in Morse alle Buchstaben, Zahlen und viele Sonderzeichen ausgeben lassen.

Aus dem alten 68er- Spruch "Wer lesen kann, ist klar im Vorteil", den ich als Schüler zu hören bekam, wird so "Wer Morsen kann, ist klar im Vorteil".

Die vorliegende Schaltung misst die Spannung an einem Spannungsteiler aus Festwiderstand und Kaltleiter, rechnet den Spannungswert in Grad Celsius um und gibt ihn in Morsezeichen aus.

Codierung von Morsezeichen

Ein effizientes Verfahren, Morsezeichen zu codieren, fand sich im WWW, es ist schon mehr als 10 Jahre alt und wurde von mehreren Autoren aufgegriffen; die Umsetzung erfolgte damals in Assembler auf  PIC- Microcontrollern. Eine kompakte Realisierung ist aber auch in Bascom auf einem AVR möglich.

Prinzip der Codierung:

 -  1 Byte pro Zeichen, niederwertiges Bit zuerst
 -  hat das Bit den Wert 0: Morsepunkt (dit)
 -  hat das Bit den Wert 1: Morsestrich (dah)
 -  da Morsezeichen unterschiedlich lang sind, ist ein Ende- Zeichen erforderlich; es ist ebenfalls ein 1 - Bit.
 -  Beispiel Morsezeichen "`a"' (dit dah): \verb &B00000110 \
 -  Beispiel Morsezeichen "`9"' (dah dah dah dah dit): \verb &B00101111 \
 -  Auslesen der codierten Zeichen: niederwertigstes Bit auslesen, alle Bits um 1 Stelle nach rechts schieben, niederwertigstes Bit auslesen. Ist der Wert des gesamten Byte nur noch 1, ist das Zeichen komplett.

 
Hardware

Der Spannungsteiler besteht aus einem Vorwiderstand und einem Kaltleiter KTY81. Dieser hat bei der Nominaltemperatur 25 Grad Celsius einen Widerstand von 1000 Ohm, bei 0 Grad 815 Ohm.

 

Der Widerstandsverlauf des KTY81 ist nicht linear, sondern deutlich ansteigend (vermutlich eine e-Funktion). In einer Spannungsteilerschaltung wird die Nichtlinearität jedoch in gewissen Grenzen kompensiert. Ich habe daher den Widerstands- und den Spannungsverlauf aus dem Datenblatt in eine OpenOffice-Kalkulationstabelle übertragen (siehe xls-Datei), den Vorwiderstand variiert und durch Ausprobieren einen günstigen Wert gesucht. Mit einem Vorwiderstand von 1420 Ohm (1200 Ohm und 220 Ohm in Reihe geschaltet) ergibt sich von -20 Grad bis +20 Grad recht genau eine Zunahme von 2 AD- Schritten pro Grad (siehe xls- Tabelle). Bei 0 Grad C  beträgt der AD- Wert 373.

Download: Kty-81-4.xls

 Über 20 Grad °C werden die Schritte etwas kleiner als 2; will man kompakt mit Bytes weiterrechnen, kann man nicht klassisch interpolieren, also die Kurve durch unterschiedlich steile Geradenstücke annähern, sondern nur den 0-Grad- Basiswert 373 etwas verschieben. Bei +30 Grad, +40 Grad und + 50 Grad wird der Basiswert je einen AD- Schritt angehoben (also um etwa ein halbes Grad). Damit ist die Nichtlinearität im Temperaturbereich -25 Grad C bis +55 Grad C recht ordentlich kompensiert.

Zu berücksichtigen ist die recht große Fertigungstoleranz der Kaltleiter. Der Hersteller wirft nicht große Teile der Produktion als Ausschuss weg, sondern verkauft sie mit unterschiedlicher Toleranzangabe. Ein KTY81-110 hat 1000 +/- 10 Ohm Widerstand bei 25 Grad, ein KTY81-120 hat 1000 +/- 20 Ohm bei 25 Grad. Außerdem tritt an dem Schaltausgang PB3 (siehe weiter unten) ein kleiner Spannungsabfall auf, der den Messwert ebenfalls etwas verschiebt.

Es empfiehlt sich daher, das fertige Thermometer bei bekannter Temperatur probelaufen lassen und den Basiswert in Zeile 74 etwas anpassen. Bei meiner Ausführung war der optimale Wert nicht 373, sondern 370. Die Korrekturwerte für die "Interpolation" werden aus dem Basiswert abgeleitet und brauchen daher nicht geändert zu werden.

Der Mittelabgriff des Spannungsteilers liegt an PB4 / ADC0 (Pin3). Das obere Ende des Spannungsteilers liegt nicht direkt an Plus, sondern wird zwecks Stromersparnis über PB3 (Pin2) nur für die Dauer der Messung geschaltet (auch der ADC wird nur für die Dauer der Messung aktiviert). Der Piezo-Schallwandler liegt an PB0/OC0A (Pin5).

Die Betriebsspannung stammt aus einer Lithiumzelle oder 3 Mignonakkus; sie ist mit 220 µF kurzzeitstabilisiert. Der Pluspol ist gut zu isolieren (auch auf der Platinenunterseite) und mit einer  Sicherung abzusichern; insbesondere bei Verwendung einer Lithiumzelle (Berstgefahr). An Pin1 liegt ein Resettaster.

PB1 und PB2 kann man über je 1 MOhm auf ein definiertes Potential legen, damit sie nicht floaten und im Powerdown Strom ziehen; eleganter ist es, nicht benötigten Digitaleingänge über das Register DIDR0 dauerhaft abzuschalten (Ausgangsports und die Zweitfunktionen der Pins wie z.B. ADC- Eingänge funktionieren trotzdem).

 

 

Software

Das Programmlistung ist ausführlich kommentiert, daher nur eine Übersicht: Zunächst die Kernbestandteile des Programms:

- Einlesen der benötigten Morsezeichen in ein Feld
- Timer0 als Tonquelle konfigurieren
- Auslesen des ADC, Umwandlung in Celsiusgrade, "Interpolieren" und Zerlegung in Einzelziffern
- Erzeugung der Morsezeichen

Das Hauptprogramm gibt zunächst die Buchstaben "temp " für "Temperatur" aus, verzweigt in die ADC- Routine, holt sich die Zehner- und Einerstelle der Temperatur, sendet sie in Morse und hängt noch ein "c" an. Anschließend geht der Prozessor in Powerdown.

 Um diesen Programmkern ist noch eine Zählschleife mit der Variablen "E" gelegt, die dafür sorgt, dass das Thermometer die Temperatur regelmäßig ausgibt (alle 15 - 20 Minuten).

Da der Attiny13 nur einen regulären Timer hat (der ja bereits benutzt wird), wird als Workaround der Watchdog als zweiter Timer genutzt. Er lässt sich lediglich auf maximal 8 sec Zeitdauer programmieren. Die Zählschleife verlängert die 8 Sekunden auf brauchbare Zeiten. In der Standard- Betriebsweise würde der Watchdog bei jedem Überlauf einen Reset auslösen; eine Zählvariable würde durch den Reset immer wieder auf den Anfangswert zurückgesetzt.

Der Watchdog muss daher in den Interrupt- Modus umgeschaltet werden, allerdings ist dieser nicht über den Bascom-Befehl "Config Watchdog" konfigurierbar, sondern nur durch direktes Beschreiben des Watchdog-Registers.

Ähnlich wie eine alte Uhr mit Schlagwerk meldet sich das Thermometer in regelmäßigen Zeitabständen (na ja, der Watchdog-Oszillator ist nicht sonderlich frequenzstabil). Wünscht man zwischendurch die Ausgabe der Temperatur, reicht ein Druck auf den Reset-Taster.

Download: Bascom-Quelltext und Hexfile

' Das Programm liest den ADC aus und gibt die Werte als Morse- Text per NF-Ton aus (ca alle 15 Minuten).
' Der ADC ist mit einem Spannungsteiler aus Kaltleiter KTY81-120 und Widerstand 1420 Ohm
' beschaltet; der Analogwert wird in Temperatur umgerechnet. Der Spannungsteiler liegt
' aus Stromspargründen nicht direkt an der Betriebsspannung, sondern wird über PB.3 geschaltet.

' Da der Attiny13 nur einen Timer hat und der für die Tonerzeugung genutzt wird, dient als weiterer
' Timer der Watchdog. Er läßt sich auf max. 8 sec Ansprechzeit konfigurieren. Da diese Zeit recht
' kurz ist, wird sie über eine Zählschleife verlängert; bei jedem Ansprechen wird die Variable E
' hochgezählt. Überschreitet sie einen (konfigurierbaren) Wert, startet die Temperaturmessung neu.

' Standardmäßig löst der Watchdog Resets aus. Die Variable E würde dadurch jeweils auf den
' Anfangswert gesetzt und die Schleife nicht funktionieren. Abhilfe: Der Watchdog läßt sich (etwas
' umständlich) auf Interruptbetrieb konfigurieren.

' Der Attiny13 ist auf 16 kHz geflasht (Stromersparnis).
' Bei anderem Takt muß $crystal, Timer0-Prescale und 'Const Tonhoehe' geändert werden



' Erläuterungen zum Morseteil:
'
' Codierung der Morsezeichen: 1 Byte pro Zeichen, niederwertiges Bit zuerst.
' Ist der Bit-Wert 0: Morsepunkt (dit)
' Ist der Bit-Wert 1: Moersestrich (dah)
' Da Morsezeichen unterschiedlich lang sind, ist das Ende- Zeichen ebenfalls ein 1-Bit.
'
' Beispiel Morsezeichen "a" (dit dah): &B00000110
' Beispiel Morsezeichen "9" (dah dah dah dah dit): &B00101111

' Auslesen der codierten Zeichen:
' niederwertigstes Bit auslesen, Byte um 1 Stelle nach rechts schieben, niederwertigstes Bit
' auslesen. Ist der Gesamtwert des Bytes nur noch 1, ist das Zeichen komplett.


' Timer0 erzeugt den Mithörton. Pin PB0 wird als Timer0-Ausgang OC0A verwendet.
' Pin PB3 schaltet den Spannungsteiler nur während der Messung an Spannung (Stromersparnis).
' Pin PB4 ist der Analogeingang.
'


' Änderungen Version 5 -> Version 6a:

' Spannungsteiler- Widerstand von 1 kOhm auf 1420 Ohm geändert
' Die Umrechnung AD- Wert -> Temperatur erfaßt jetzt auch Minusgrade.
' Da der Zusammenhang zwischen AD- Wert und Temperatur nicht ganz linear ist, wird in 3 Stufen interpoliert
' (der 0-Grad-Basiswert 373 wird um jeweils 1 erhöht)
'
' Die Pulldown- Widerstände an PB1 und PB2 wurden entbehrlich durch Setzen des Registers DIDR0
'
' Da der Flashspeicher knapp wurde, sind einige Befehle durch kompaktere Varianten ersetzt.
' - z.B. statt " x = x+1 " : " incr x "
' - Powerdown durch direktes Beschreiben des Registers und Assembler- Befehl "Sleep"
' Die Behandlung von Leerzeichen wurde in der Morseroutine weggelassen


'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Grundeinstelllungen:
$regfile = "ATtiny13.dat"
$crystal = 16000 ' Taktfrequenz: 128 kHz / 8
$hwstack = 8 ' nur max 4 geschachtelte Unterprogramme; spart SRAM
$swstack = 0 ' nicht benötigt; spart SRAM
$framesize = 0 ' dito
Baud = 9600


'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Konstanten

' vom Nutzer anzupassen:
Const Dit = 40 ' Länge eines Punkts, daraus ergibt sich die Morsegeschwindigkeit
Const Tonhoehe = 14 ' Tonhöhe
Const Msg_laenge = 20 ' Anzahl der DataBytes; maximal 47
Const Wdzaehler = 128 ' Multiplikator,um Watchdog- Zeit zu vervielfachen.
Const Basis = 370 ' AD- Wert bei 0 Grad C
' ' theoretisch 373, muß etwas angepaßt werden, wenn
' ' das Morsethermometer ungenau ist
'
' hier nichts ändern:
Const Dah = Dit * 3 ' Länge eines Strichs
Const Wort = Dit * 8 ' Länge der Pause zwischen Wörtern
Const Korr1 = Basis + 60 ' Korrektur bei > 30 Grad
Const Korr2 = Basis + 79 ' Korrektur bei > 40 Grad, gilt bis etwa 45 Grad
Const Korr3 = Basis + 98 ' Korrektur bei < 50 Grad

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

Dim I As Byte ' wird in Zählschleife verwendet
Dim N As Byte ' Aktueller Morsebuchstabe
Dim M As Word ' wird fürs Auslesen der Morsebits verwendet
Dim L As Byte ' niederwertigstes Bit von M verwendet
Dim Ana As Word ' ADC
Dim Zehner As Word ' Zehnerstelle des umgerechneten Analogwertes
Dim Einer As Word ' Einerstelle des umgerechneten Analogwertes
Dim Msg(msg_laenge) As Byte ' Feld für die Bakentext- Bytes
Dim E As Byte ' Multiplikator, um Watchdog- Timer zu verlängern
Dim Frost As Bit ' wird für die Ausgabe des Vorzeichens "-" benötigt

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

Restore Message ' Feld einlesen
For I = 1 To Msg_laenge
Read Msg(i)
Next



'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Initialisierung des Programms:
'
Acsr.acd = 0 ' Analog-Komparator ausschalten, spart etwas Strom
Ddrb = &B00001000 ' Pin PB3 als Ausgang konfigurieren (1) , Rest ist Eingang (0)
Portb = &B00000000 ' keine PullUp-Widerstände
Didr0 = &B00011111 ' Digital- Eingänge abschalten, da keiner benötigt wird
' ' spart Strom im Powerdown und verhindert, daß Eingänge floaten
' ' und dabei exzessiv Strom ziehen
'

' Morseton mit Timer0 erzeugen:

' Timer0 : Vorteilung(prescale) = 1 ; Bei Compare -match Toggle Pin Oc0a / Pb0
'
' Timer0 zählt das Register TCNT0 hoch. Es wird mit Register OCR0A verglichen.
' Wenn TCNT0 = OCR0A, wird Output OC0A/PB0 umgeschaltet (getoggled)
' Timer0 erzeugt so Dauerton; er wird jedoch nur an den Ausgangspin durchgeschaltet,
' wenn das Datenrichtungs- Register DDRB.0 = 1 ist. Bei 0 wird der Ton unterdrückt.

Config Timer0 = Counter , Prescale = 1 , Compare A = Toggle , Clear Timer = 1
Ocr0a = Tonhoehe

'
'-------------------------------------------------------------------------------
' Hauptprogramm:


E = Wdzaehler ' damit der Meßvorgang nach einem Reset sofort startet

Do
Incr E ' Watchdog- Verlängerer um 1 erhöhen
If E > Wdzaehler Then ' Watchdog- Verlängerer; Bedingung erfüllt
Wdtcr.6 = 0 ' stoppt Watchdog; Watchdog- Interrupts würden die Morseausgabe stören
For N = 11 To 14 ' wir senden "temp"
Gosub Morse
Next N
Gosub Messen ' wir fragen den AD- Wandler ab
If Frost = 1 Then ' unter 0 Grad
For N = 16 To 20 ' wir senden "minus"
Gosub Morse
Next N
Waitms Wort
End If
N = Zehner + 1 ' Zehnerstelle des Messwerts
Gosub Morse ' wir senden die Zehnerstelle
N = Einer + 1 ' Einerstelle des Messwerts
Gosub Morse ' wir senden die Einerstelle
Waitms Wort
N = 15 ' wir senden "c"
Gosub Morse
E = 0 ' Watchdog- Verlängerer zurücksetzen
End If

' direkte Registermanipulation, da der Bascom- Befehl "Config Watchdog" nur den Resetmodus kennt. Die Vorbereitungs-
' prozedur ist nötig, da einzelne Bits des Watchdogregisters gegen (unbeabsichtige) Veränderungen besonders geschützt sind.


Sreg.7 = 0 ' Interrupts sperren
Wdtcr = &B00111001 ' Schreiben auf WDTCR Vorbereiten
Mcusr.3 = 1 ' Schreiben auf WDTCR vorbereiten
Wdtcr = &B01100001 ' Interrupt statt Reset, Watchdog- Timer auf 8 sec setzen
Sreg.7 = 1 ' Interrupts wieder freigeben
' Powerdown; direkte Registermanipulation spart Flash
Mcucr = &B00110000
!Sleep

Loop



' Auslesen des ADC

Messen:

Portb.3 = 1 ' Spannung an Spannungsteiler legen
Config Adc = Single , Prescaler = Auto , Reference = Avcc
Waitms Wort ' Wortlänge warten, bis sich alles eingeschwungen hat
Start Adc
Ana = Getadc(2)

If Ana > Basis Then ' Plusgrade
Ana = Ana - Basis
If Ana > Korr1 Then ' Kennlinien- Korrektur
Incr Ana
End If
If Ana > Korr2 Then ' Kennlinien- Korrektur
Incr Ana
End If
If Ana > Korr3 Then ' Kennlinien- Korrektur
Incr Ana
End If
Else ' Minusgrade
Frost = 1
Ana = Basis - Ana
End If

Shift Ana , Right ' entspricht " Ana = Ana / 2 " spart aber Flash
Zehner = Ana / 10 ' Zerlegung in Einzelzahlen
Einer = Ana Mod 10
Stop Adc ' Strom sparen
Portb.3 = 0 ' Spannungsteiler stromlos machen; Strom sparen
Return



' Morsezeichenerzeugung:

Morse:

M = Msg(n) ' aktuelles Zeichen aus Kette rauspicken

' Wird in diesem Programm nicht benötigt:
' 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
Ddrb.0 = 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
Ddrb.0 = 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



'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
' Codierung der Morsezeichen. Bei einem Prozessor mit viel SRAM würde man die Zeichentabelle
' in ein Array einlesen, den in Morse zu codierenden Text in einen String packen und diesen
' String zeichenweise abarbeiten.
'
' Da der Attiny13 nur wenig SRAM hat, ist der Weg hier anders: Der zu codierende Text ist ein
' Array mit 16 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 #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



Message:

Data #0 , #1 , #2 , #3 , #4 , #5 , #6 , #7 , #8 , #9
Data #t , #e , #m , #p , #c , #m , #i , #n , #u , #s


Elektronik-Labor   Projekte   AVR