ATtiny85-Morse-Keyboard

von Ralf Beesner, DK5BU                

Elektronik-Labor  Mikrocontroller




Motivation

Ich gehöre nicht zu den "wirklich schnellen" Telegrafisten, kann aber exakt gegebene Morsetexte bis zu etwa 45 WpM (Worte pro Minute, also 225 ZpM) hören, komme jedoch beim Geben mit einem ElBug nur auf etwa 33-35 WpM.
Daraus entstand die Idee, es mal mit einem Morse-Keyboard zu versuchen. Mit dem bin ich auch nicht sooo viel schneller, weil ich nicht mit 10 Fingern schreiben kann, sondern nur mit 2 Fingern auf der Tastatur "herum hacke", komme aber immerhin auf etwa 40 WpM. Viele Telegrafisten halten ein Morsekeyboard zwar für ein "Sakrileg", aber auch ein ElBug ist eine elektronische "Prothese" - daher sehe ich das pragmatisch. Es gibt zwar PC-Programme für so etwas, aber die benötigen ein Interface, um den Sender zu tasten, man muss sie konfigurieren, starten und man muss sie auch im Vordergrund halten, damit die Tastatureingaben nicht in einem anderen Programm landen. Nebenbei bei QRZ.com herum zu clicken wird dann nervig. Einfacher ist es, eine Tastatur an einen Mikrocontroller zu klemmen und den die Details machen zu lassen.


Software

Mit BASCOM kann man PS/2-Tastaturen decodieren; es gibt in den "Bascom-Samples" ein Beispiel-Programm, das die Tastatur-Scancodes in ein serielles Signal umwandelt. Jedoch ist die Tastaturbelegung nicht deutsch; z.B. sind y und z vertauscht, es fehlen die Umlaute und einige Sondertasten sind anders belegt.

Kleiner Exkurs: Ich hatte vor Jahren einen Seriell-Morse-Wandler mit einem ATTiny13 beschrieben: http://www.elektronik-labor.de/AVR/SeriellCW.html . Die Tonerzeugung erfolgt dort mit dem einzigen Timer des Attiny13, die Formung der Morsezeichen mit Wait-Befehlen. Obwohl der ATtiny13 keinen Hardware-UART hat und der Controller meist in den Wait-Befehlen der Morse-Ausgabe "herum trödelt", lässt sich per Pin Change Interrupts erreichen, dass er sich trotzdem vorrangig um die seriellen Eingangsdaten kümmert. Eigentlich arbeitet der serielle Lesebefehl nur im Abfrage-(Polling)-Betrieb, aber so funktioniert er auch im Interrupt-Betrieb: bei einem Flankenwechsel am seriellen Pin springt der Mikrocontroller in die Interrupt-Routine, holt das Zeichen an dem seriellen Eingangspin ab und hängt es an des Ende eines Puffer-Strings, während die Morseausgabe des Haupt-Programms das jeweils älteste Zeichen aus dem Puffer-String abarbeitet. Da der Ton aus dem Timer kommt, treten nur winzig kleine Verzögerungen der Ton- bzw. Pausendauern auf, aber der Ton bleibt sauber.

Das funktioniert recht gut und lässt sich genau so im Keyboard-Betrieb nutzen: Der BASCOM-PS/2-Lesebefehl ist ebenfalls für Polling-Betrieb gedacht, aber wenn man die Clock-Leitung des Keyboards mit PinChange Interrupts überwacht, verzweigt er schnell genug in die Interrupt-Routine mit der Keybord-Abfrage. Sie empfängt den ScanCode, wandelt ihn mit Hilfe einer Tabelle in ein ASCII-Zeichen und hängt das ASCII-Zeichen an einen Puffer-String an. Die Morse-Routine des Hauptprogramms liest das älteste Zeichen des Pufferstrings, schlägt in einer zweiten Tabelle die Abfolge von Punkten und Strichen nach, gibt das Morse-Zeichen aus und entfernt das Zeichen aus dem Puffer. Die beiden Tabellen sind modifiziert; die Keyboard-Tabelle ist für die in Frage kommenden Zeichen "eingedeutscht" und irrelevante Tasten sind "tot gelegt", die Tabelle für die Umwandlung von ASCII in Morse ist um Umlaute ergänzt. Der Controller wird zur Laufzeit auf 8 MHz Takt umgeschaltet - die Fuse-Bits müssen also nicht auf 8 MHz umgeflasht werden.


Hardware

Da das Programm nicht mehr in einen ATtiny13 passte, musste ein ATtiny85 herhalten (ein ATtiny45 würde aber auch reichen).



Schaltplan



Streifenleiterplatinen-Layout, Blick von oben. Rotbraun: Drahtbrücken, blau: Leiterbahnen auf der Unterseite





Die Tastatur "hängt" an den Pins PB3 und PB4. Die verwendete Buchse ist keine Mini-DIN-Buchse, sondern eine USB-Buchse, weil meine USB-Tastatur auch PS/2 "kann" - es lag ein PS/2-Adapter bei, den ich weg gelassen habe. Zu beachten ist jedoch, dass nicht jede USB-Tastatur PS/2-fähig ist! Wer eine Tastatur mit PS/2 Stecker hat, müsste ihn mutig abschneiden oder das Board-Layout anpassen. Auf einem alten Screenshot ist die Belegung einer PS/2-Buchse ersichtlich:




Die Stromversorgung der Tastatur wird über PB1 geschaltet. So ist kein Ausschalter erforderlich. Der Controller wird mit einem Reset-Taster geweckt, legt sich nach etwa 20 Minuten Inaktivität selbst schlafen und schaltet vorher die Stromversorung der Tastatur ab. Meine Tastatur nebötigt nur 6 mA, deshalb konnte ich die Stromversorgung einfach per Attiny-Pin schalten. Falls die Tastatur mehr als 20 mA benötigt,sollte man einen Schalttransistor einfügen. An PB0 wird ein Rechteck-Tonsignal ausgegeben und an PB2 das invertierte CW-Tastsignal für einen KW-Transceiver.


Quelltext mit Kommentaren:

' Programm wandelt Ausgangssignal einer PS/2-Tastatur in Morsezeichen
' Manche USB-Tastaturen "können" auch PS/2
' Compiler: kostenfreie Bascom-Schnupper-Version (www.mcselec.com)
'
'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
'
' Grundeinstelllungen:
$Regfile = "ATtiny85.dat"
$Crystal = 8000000
$Hwstack = 32                                               ' wegen Interrupt-Routine
$Swstack = 4
$Framesize = 4

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

' Variablen und Konstanten dimensionieren:
'

Dim I As Byte                                               ' Fuer temporären Gebrauch als Schleifenzaehler
Dim M As Byte                                               ' Index des aktuellen Morsebuchstabens in Morsetabelle
Dim L As Byte                                               ' fuer niederwertigstes Bit beim bitweisen Erzeugen des Morsezeichens

Dim Pufferstring As String * 128
Dim Puffer(128) As Byte At Pufferstring Overlay             ' so kann man wahlweise String-Befehle oder Array-Befehle nutzen
Dim Position As Byte
Dim Recbyte As Byte
Dim Recchar As String * 1 Dim N As Byte

Dim Wpm As Word ' words per minute Dim Punktlaenge As Word ' Punktlaenge Dim Strichlaenge As Word ' Strichlaenge Dim Wortpause As Word ' Laenge Wortpause Dim Offtimer As Word


Const Tonhoehe = 200 ' Tonhoehe Rechteck-Mithoerton ' einige Speichertexte: ' am Ende sollte immer ein Leerzeichen stehen, damit ohne zus. <Return> los gesendet wird Const speichertext4 = "vvv " Const speichertext5 = "test test test " Const speichertext6 = "F6 " Const speichertext7 = "F7 " Const speichertext8 = "F8 " Const speichertext9 = "F9 " Clkpr = 128 ' Taktumschaltung auf 8 MHz Clkpr = 0 Clkpr = 0 ' Configure the pins to use for the clock and data ' Can be any pin that can serve as an input ' Keydata is the label of the key translation table ' Config Keyboard = Pinb.4 , Data = Pinb.3 , Keydata = Keydata

' Rechteck-Mithoerton: ' Timer0 zaehlt 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 = 64 , Compare A = Toggle , Clear Timer = 1 Ocr0a = Tonhoehe


Acsr.acd = 1 ' Analog-Komparator ausschalten, spart etwas Strom Pcmsk.4 = 1 ' PCINT für Port B.4 (Keyboard-Clock) freigeben Gimsk.pcie = 1 ' PCints aktivieren Sreg.7 = 1 ' Interrupts generell freigeben DDRB = &B00000110 ' DDRB.2 ist Tastausgang, DDRB.1 Stromvers. Keyboard Portb = &B00000011

On Pcint0 Kbd_lesen ' Interrupt-Routine definieren '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ' Main: Wpm = 30 ' Standard-Geschwindigkeit Gosub Timing ' in ms umrechnen Wait 1 ' Boot-Zeit fuer die Tastatur Pufferstring = "" ' Tastatur sendete beim Starten unerwuenschte Zeichen ' Hauptschleife Do Do ' Leerlauf-Warteschleife bis zum Timeout Incr Offtimer
If Offtimer > 64000 Then ' Schleife erzeugt einige min. Wartezeit vor Powerdown Offtimer = 0 PortB = 0 DDRB = 0 Gimsk.pcie = 0 Powerdown End If Waitms 10 ' 10ms und 64000: 10 min Leerlauf bis Powerdown Loop until Pufferstring <> "" ' erst wenn Zeichen im Puffer, gehts weiter Do ' Puffer auf Leerzeichen oder Return durchsuchen ("word mode") For N = 1 To Len(Pufferstring) If Puffer(N) = 32 Or Puffer(N) = 13 Then ' Wenn gefunden, ist Wort komplett und Morsezeichen-Erzeugung startet M = Asc(puffer(1)) ' Ascii- Wert des untersten Zeichens im Puffer ermitteln If M > 95 Then M = M - 32 ' Ascii-Kleinbuchstaben in ASCII-Grossbuchstaben wandeln End If M = Lookup(m , Morsetabelle) ' Morsezeichen aus Tabelle holen Gosub Morse
Pufferstring = Mid(pufferstring , 2) ' das unterste Zeichen im Pufferstring entfernen Offtimer = 0 End If Next Loop Loop ' Morsezeichenerzeugung: Morse: If M = 0 Then ' Sonderfall: Leerzeichen; Waitms Wortpause
Goto Zeichenende
End If For I = 1 To 8 If M = 1 Then ' Das Byte hat nur noch den Wert 1; Zeichenende! Goto Zeichenende
End If L = M And &B00000001 ' niederwertigstes Bit lesen Ddrb.0 = 1 PortB.2 = 1 If L = 1 Then Waitms Strichlaenge ' ist das Bit 1 -> dah Else Waitms Punktlaenge ' ist das Bit 0 -> dit End If Ddrb.0 = 0 PortB.2 = 0 Waitms Punktlaenge ' Pause innerhalb des Morsezeichens Shift M , Right , 1 ' Bits um eine Stelle nach rechts shiften Next I
Zeichenende: Waitms Strichlaenge ' Pause zwischen Morsezeichen Return Timing: ' Funktion fuer Geschwindigkeit, Werte in msec ' Bei Verwendung von Ganzzahl-Variablen wird Punktlaenge immer abgerundet; Rundungsfehler aber unter 1 WpM Punktlaenge = 1200 / Wpm : Strichlaenge = Punktlaenge * 3 : Wortpause = Punktlaenge * 5 Return End ' Interrupt-Routine Kbd_lesen: Pcmsk.4 = 0 ' Solange Bits reinkommen, PCINT sperren Recbyte = Getatkbd() Select Case Recbyte
Case 8: ' Backspace-Taste Position = Len(Pufferstring) If Puffer(Position) = 32 or Puffer(Position) = 13 Then Delchar Pufferstring , Position - 1 End If Delchar Pufferstring , Position
Case 20: Pufferstring = "" ' F12 Case 21: Wpm = Wpm - 2 : Gosub Timing ' F1 Case 22: Wpm = Wpm + 2 : Gosub Timing ' F2 Case 23: Pufferstring = Pufferstring + "wpm: " + Str(Wpm) + " " ' F3 Case 24: Pufferstring = Pufferstring + speichertext4 ' F4 Case 25: Pufferstring = Pufferstring + speichertext5 ' F5 Case 26: Pufferstring = Pufferstring + speichertext6 ' F6 Case 27: Pufferstring = Pufferstring + speichertext7 ' F7 Case 28: Pufferstring = Pufferstring + speichertext8 ' F8 Case 29: Pufferstring = Pufferstring + speichertext9 ' F9 Case Else: Recchar = Chr(recbyte) Pufferstring = Pufferstring + Recchar
End Select Pcmsk.4 = 1 ' PCINT wieder freigeben Return '- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Morsetabelle: 'Ascii 0 - 32 ; hier wird nur Linefeed in <kn> umgesetzt Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , &B00101101 , 0 , 0, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 Data 0 , 0 , 0 , 0 , 0 , 0 , 0 ' Ascii 32 - 47 -> _!"#$%&'()*+,-./ Data 0 , 0 , &B01010010 , 0 , 0 , 0 , &B00101010 , 0 , &B00101101 , &B01101101 , 0 , &B00101010 , &B01110011 , &B01100001
Data &B01101010 , &B00101001

'Ascii 48 - 57 -> 0123456789 Data &B00111111 , &B00111110 , &B00111100 , &B00111000 , &B00110000 , &B00100000 , &B00100001 , &B00100011 , &B00100111 , &B00101111

'Ascii 58 - 64 -> :;<=>?@ Data &B01000111 , &B01110011 , &B00101101 , &B00110001 , &B01101101 , &B01001100 , 0 'Ascii 65 - 90 ABCDEFGHIJKLMNOPQRSTUVWXYZ Data &B00000110 , &B00010001 , &B00010101 , &B00001001 , &B00000010 , &B00010100 , &B00001011 , &B00010000 , &B00000100
Data &B00011110 , &B00001101 , &B00010010 , &B00000111 , &B00000101 , &B00001111 , &B00010110 , &B00011011 , &B00001010
Data &B00001000 , &B00000011 , &B00001100 , &B00011000 , &B00001110 , &B00011001 , &B00011101 , &B00010011

'Umlaute willkürlich zugeordnet: 'Ascii 91-93 ÄÖÜ Ascii 94 - 96 -> unbelegt Data &B00011010 , &B00010111 , &B00011100 , 0 , 0 , 0 Keydata: ' normal keys lower case '------ ' Aenderungen an Original-Tabelle: ' Y,Z Korrektur: Dez-Zahlen 121 und 122 getauscht ' Fragezeichen auf sz gelegt (man muss also nicht die Shift-Teste druecken) ' weitere Zuordnungen: ' Taste hex dez pseudo-ascii ' ö 4C 76 124 ' ä 52 82 123 ' ü 54 84 125 ' F1 05 05 21 ' F2 06 06 22 ' F3 04 04 23 ' F4 0C 12 24 ' F5 03 03 25 ' F6 0B 11 26 ' F7 83 131 27 ' F8 0A 10 28 ' F9 01 01 29 ' F10 09 09 30 ' F11 78 120 31 ' F12 07 07 20 ' bksp 66 102 08 ' ab dez 96 alles (ausser backspace) auf 0, damit werden folgende ' unnuetze tasten tot gelegt: einf entf cursors pos1 ende bild, pause Data 0 , 29 , 0 , 25 , 23 , 21 , 22 , 20 , 0 , 30 , 28 , 26 , 24 , 0 , &H5E , 0 Data 0 , 0 , 0 , 0 , 0 , 113 , 49 , 0 , 0 , 0 , 121 , 115 , 97 , 119 , 50 , 0 Data 0 , 99 , 120 , 100 , 101 , 52 , 51 , 0 , 0 , 32 , 118 , 102 , 116 , 114 , 53 , 0 Data 0 , 110 , 98 , 104 , 103 , 122 , 54 , 7 , 8 , 44 , 109 , 106 , 117 , 55 , 56 , 0 Data 0 , 44 , 107 , 105 , 111 , 48 , 57 , 0 , 0 , 46 , 45 , 108 , 124 , 112 , 63 , 0 Data 0 , 0 , 123 , 0 , 125 , 0 , 0 , 0 , 0 , 0 , 13 , 43 , 0 , 0 , 0 , 0 Data 0 , 0 , 0 , 0 , 0 , 0 , 8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 31 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ' ' > 127: shifted keys UPPER case ' ------- ' Aenderungen an Original-Tabelle: ' Y,Z Korrektur: Dez-Zahlen 89 und 90 getauscht ' an Platz 204, 210, 212 Ascii-Zeichen für Umlaute eingetragen 92 91 93 ' auch hier alles ueber dez 224 auf Null: Data 0 , 0 , 0 , 27 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 Data 0 , 0 , 0 , 0 , 0 , 81 , 33 , 0 , 0 , 0 , 89 , 83 , 65 , 87 , 34 , 0 Data 0 , 67 , 88 , 68 , 69 , 0 , 35 , 0 , 0 , 32 , 86 , 70 , 84 , 82 , 37 , 0 Data 0 , 78 , 66 , 72 , 71 , 90 , 38 , 0 , 0 , 76 , 77 , 74 , 85 , 47 , 40 , 0 Data 0 , 59 , 75 , 73 , 79 , 61 , 41 , 0 , 0 , 58 , 95 , 76 , 92 , 80 , 63 , 0 Data 0 , 0 , 91 , 0 , 93 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 Data 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0


Bedienung

Der ATtiny wird mit dem Reset-Taster geweckt, versorgt die Tastatur mit Strom und wartet auf Eingaben. Sie werden allerdings erst ausgesendet, nachdem ein Leerzeichen oder ein "Return" eingegeben wurde ("word mode").
Mit der F1- und der F2-Taste wird die Geschwindigkeit um 2 WpM vermindert oder erhöht. Der initiale Wert ist 30 Wpm. Mit der F3-Taste kann man eine Ausgabe der Geschwindigkeit in WpM anstoßen. Mit F4 bis F9 kann man einen von sechs Speichertexten aussenden und mit F12 den Vorschreibspeicher komplett löschen. Die Speichertexte müssen mit Const-Befehlen fest in die Firmware einkompoliert werden.
Falls man sich mal vertippt hat und es rechtzeitig merkt, kann man mit der "Backspace-Taste" das jeweils letzte Zeichen aus dem Vorschreib-Puffer löschen.
Nach etwa 20 Minuten Nichtbenutzung legen sich Attiny und Tastatur schlafen.


PS/2- Tastaturen

Wer keine alte PS/2-Tastatur herumliegen hat: Bei Reichelt sind einige USB-Tastaturen gelistet, die noch PS/2 "sprechen" können.
Der Nachfolger meines Exemplars heißt "KEYSONIC ACK595P". Daneben gibt es noch einige flexible, aufrollbare Tastaturen, die sehr preiswert sind, aber hinsichtlich der Bedienung gewöhnungsbedürftig sein dürften, z.B. "LOGILINK ID0018A" und teure Cherry-Tastaturen.


Neue Version der Software

Die Software enthält einen "blöden" Flüchtigkeitsfehler: sie geht nie in den energiesparenden Powerdown. Die zweite innere Schleife, in der die Erzeugung der Morsezeichen erfolgt, wird nie verlassen, weil sie mit "Loop" und nicht mit "Loop until Pufferstring = "" " endet.

Ausserdem wurde der Wunch geäußert, einige zusammengezogene Verkehrszeichen einzubauen: Die Verkehrszeichen "kn" "ka" "as" "ve" und "sk" liegen nun auf den Tasten #, ^, Esc, Tab und Strg.


Download des (aktualisierten) Quellcodes:  1122-morse-keyboard.zip