Die wahrscheinlich simpelste ICW-Sinus-Sensortaste der Welt

von Ralf Beesner                

Elektronik-Labor  Mikrocontroller




Ziele

Der grossspurige Titel ist selbstverständlich nur eine ironische Anspielung auf eine alte Schoko-Riegel-Werbung ;)

Meine erste ICW-Morsetaste war nur ein "schneller Hack" aus bereits vorhandenen Codeschnipseln. Die Aufteilung auf zwei Controller war einfach, preiswert, aber unelegant.

Ich habe daher versucht, Sinusgenerator und Morsezeichen-Erzeugung in einem Controller zusammenzufassen. Da der Controller mit der Sinuserzeugung ausgelastet ist, kann er nicht gleichzeitig mit Wait-Befehlen das Keyer-Timing per Software erledigen. Naheliegend wäre, einen zweiten Timer zu verwenden, der z.B. 1ms-Interrupts erzeugt. Jedoch besteht bei der Verwendung von Interrupts die Gefahr, dass die Interrupts und ihre Abarbeitung die Sinuserzeugung so ungünstig unterbrechen, dass der Sinus unsauber klingt.

Da der ATtiny 13A nur einen Timer hat, wäre eigentlich ein ATtiny 24 oder 25 erforderlich.

Es geht aber auch ohne zweiten Timer, denn man kann während der Sinuserzeugung eine Variable inkrementieren und diese "Timer-Ticks" in der Keyer-Routine auswerten. Nachteil ist, dass Tonhöhe und Timer-Ticks (also letztlich die Morsezeichendauer) nicht voneinander unabhängig sind und dass die Timerticks bei einem 600-Hz-Ton nur alle 1,7 ms erfolgen (das wird aber nur bei sehr hohen Gebegeschwindigkeiten relevant).

Das nächste "Thema" war, die Squeeze-Keyer-Mechanik durch eine Sensor-Lösung zu ersetzen. Burkhard Kainka hat einige Artikelzu ATmega- und ATtiny-Berührungssensoren verfasst, ich habe aber eine noch ressourcensparendere Variante von Tim Boeschke, Nickname "cpldcpu", gefunden. Sie nutzt statt Digitaleingängen ADC-Eingänge, basiert auf einer Atmel-Appnote und ist als C-Bibliothek realsiert. Den Kern seiner Library habe ich in Bascom nachempfunden.

Schliesslich war die Stromversorgung über einen USB-Anschluss nicht ideal - schöner wäre Batteriebetrieb. Bei Verwendung eines mechanischen Paddles kann man den Controller zwischen den Zeichenelementen in Powerdown schicken und mit "PinChange Interrupts" für jedes neue Zeichenelement aufwecken. Sensorpaddles müssen jedoch ständig abgefragt werden. Die Lösung war, den Watchdog auf 8-Sekunden-Interruptbetrieb umzukonfigurieren, die Interrupts zu zählen und bei Überschreiten von 250 Interrupts den Controller in Powerdown zu schicken (also nach etwa 30 Minuten, wenn zwischendurch das Paddle nicht berührt wird). Das Aufwecken erfolgt durch einen Reset, indem man das Geschwindigkeitspoti auf Minimum dreht.

Leider passte der Code, der alles Vorgenannte realisierte, zunächst nicht in den knappen ATtiny13A-Programmspeicher. Nach einigen Umbauten kam das Ziel etwas näher, aber ein paar Bytes war der Code immer noch zu gross. Der rettende Einfall war, die unscheinbare Zeile "Dit = 3 * Dah" durch zwei Additionen zu ersetzen; das brachte stolze 78 Bytes Ersparnis, denn der ATtiny hat keinen Hardware-Multiplikator und muss Multiplikationen relativ aufwendig per Software vornehmen.


Hardware

Die Minimalversion kommt mit nur 7 Bauteilen aus. PB2 muss für die Sensor-Routine frei bleiben. Das Geschwindigkeitspoti musste daher an den Reset-Pin umziehen. Der Nachteil ist, dass man an diesen ADC-Eingang nur Spannungen anlegen darf, die grösser als die halbe Betriebsspannung sind, damit kein Reset ausgelöst wird. Ausserdem ist am Reset-Pin ständig ein interner Pullup-Widerstand aktiv.

Ich habe aus der Not eine (bauteilsparende) Tugend gemacht: der Pullup-Winderstand linearisiert den Einstellbereich etwas, und das Poti erspart den ohnehin erforderlichen Reset-Taster, wenn man es auf weniger als etwa 20% dreht ;)

Tief- und Hochpass zur Glättung des PWM-Signals bzw. Reduktion der Tastclicks sind so bemessen, dass sie den NF-Pegel auf das Niveau eines Line-In-Eingangs absenken. An einem Mikrofoneingang ist der Pegel zu hoch, man kann aber den Einstellbereich des Trimmers verbessern, indem man zu R2 etwa 1 kOhm parallel schaltet. Mikrofoneingänge stellen meist eine kleine Spannung zum Betrieb eines Elektret-Mikrofones bereit. Der Stromfluss durch R2 sollte unschädlich sein.

Hinter einer vierpoligen Einzelbuchse für Headsets wird oft geprüft, ob in die vierpolige Buchse ein dreipoliger Kopfhörer-Stecker eingesteckt ist. Der löst auf dem 4. Ring einen Kurzschluss aus, das Gerät schaltet dann nicht vom internen auf das externe Mikrofon um. Steht R2 ganz am Anfang des Einstellbereichs, könnte das einen eingesteckten Kopfhörer vortäuschen.




Schaltplan der Minimalversion



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



Da PB1 ungenutzt ist, wird er in einer "KW-Test-Version" als Sender-Tastausgang verwendet. Sie ist um einen Schalttransistor für die Tastleitung eines KW-Transceivers und zwei Widerstände in den Sensor-Leitungen ergänzt, die laut Atmel die Festigkeit gegen HF-Einstrahlung verbessert sollen. Für KW-Betrieb ist der Keyer aber nicht wirklich geeignet, da er z.B. keinen Speicher für CQ-Rufe aufweist.







Schaltplan und Layout der Version mit Tx-Tastausgang



Der Schaltung läuft ab etwa 2,5V und verbraucht (je nach Betriebsspannung) in Ruhe zwischen 2 und 4 mA. Im Powerdown sind es ca. 25µA.


Weitere Erläuterungen zur Software

Die Ansprechschwelle der Sensoren wird bei Programmstart festgelegt. Der ADC-Wert am Punktsensor wird ein Mal gemessen und der Messwert um 96 erhöht. Der Punktsensor darf in dieser Zeit nicht berührt werden! Falls das doch mal versehentlich passieren sollte, muss man einen Reset auslösen, indem man das Geschwindigkeitspoti kurz auf Minimum dreht.


So arbeitet die Sensor-Routine:

Der ATtiny hat nur einen ADC, aber vier ADC-Pins. Sie werden per Registerbefehl ("AdMux") mittels interner FET-Schalter einzeln auf den ADC durchgeschaltet. Am eigentlichen ADC-Eingang hängt ein kleiner interner "Sample&Hold"-Kondensator (da eine Messung in mehreren Teilschritten erfolgt, soll er die Messspannung jeweils kurz zwischenspeichern).

Der "Sample&Hold"-Kondensator wird zunächst entladen, indem der ADC mit dem Befehl "AdMux" auf PB2 durchgeschaltet wird. PB2 wurde am Anfang des Programms als Ausgang mit Pegel "low" konfiguriert.

Dann wird einer der beiden Sensorpins als Ausgang umgeschaltet und auf "high" gelegt. Der Pin und die externe Sensorfläche bilden einen Kondensator, der so auf Betriebsspannung geladen wird. Der Pin wird nach kurzer Wartezeit als hochohmiger Eingang umkonfiguriert und "floatet" dann. Die Parasitärkapazität ist aber noch auf Betriebsspannung geladen.

Nun wird der floatende Pin per GetADC-Befehl an den ADC-Eingang durchgeschaltet und lädt den Sample&Hold-Kondensator des ADC auf, bis sich die Spannungen angeglichen haben. Da sich die Ladung nun auf zwei Kondensatoren verteilt, sinkt die Spannung. Sie wird dann gemessen.

Je nach Parasitärkapazität des Sensorkontaktes fliesst eine unterschiedliche Ladungsmenge auf den Sample&Hold-Kondensator ab, die Spannungen und damit die gemessenen ADC-Werte unterscheiden sich.

Im Leerlauf liegen sie z.B. um 600. Bei Berührung des Sensorkontaktes steigt die resultierende Kapazität aus Sensorpins, Sensorfläche und menschlichem Finger an. Es wird mehr Ladung gespeichert und an den "Sample&Hold"-Kondensator abgegeben, sie hebt die ADC-Werte gegenüber dem Leerlauf um z.B. 150 Schritte an.


Abfrage des Geschwindigkeitspotis:

Der ADC-Wert wird von einer Konstanten abgezogen, um die Drehrichtung des Potis umzukehren; höhere ADC-Werte führen so zu einer Verkürzung der Punktdauer. Anschliessend wird der Wert durch 16 geteilt. Die Division erfolgt ressourcensparend per Schiebe-Befehl. Mit den beiden Operationen ergibt sich ein Einstellbereich von ca. 17 - 50 WpM bzw. 85 -250 ZpM.


Sinuserzeugung:

Um Programmspeicher zu sparen, wird der Sinus aus nur 64 PWM-Schritten gebildet. Mit "Waitus 16" ergibt sich eine Frequenz von ca. 600 Hz. Nach jedem Durchlauf wird die Variable "Sndlen" dekrementiert (also etwa alle 1,7 ms) bis sie Null ist; dann wird in die Keyer-Routine zurückgesprungen. Der Wert für "Sndlen" hängt von der eingestellten Punktlänge "Dit" bzw. dem daraus abgeleiteten Wert "Dah" ab.

Auch die Pausen zwischen den Zeichenelementen werden über das Sinus-Unterprogramm erzeugt. Damit die Zeiten für Punkte und (punktlange) Pausen etwa gleich sind, durchläuft das Unterprogramm die gleiche For-Next-Schleife, schreibt aber jeweils den Wert Null ins PWM-Register.


Watchdog:

Normalerweise soll ein Watchdog einen Reset auslösen, wenn er nicht schnell genug periodisch durch ein Programm zurückgesetzt wird. Dadurch soll gewährleistet werden, dass sich ein Programm nicht unbemerkt für längere Zeit "aufhängen" kann.

Man kann den Watchdog aber auch über Registerbefehle zu einem Timer umkonfigurieren (und nebenbei auf die maximale Ansprechzeit von 8 sec setzen).

Durch Ausprobieren stellte sich heraus, dass Bascom den (undokumentierten?) Befehl "On Watchdog" versteht, mit dem man sehr einfach die Interrupt-Routine anspringen kann (wie bei einem normalen Timer-Interrupt). In der Bascom-Duku hatte ich ihn nicht gefunden.

Die Variable "offtimer" wird in der Interrupt-Routine ca. alle 8 Sekunden inkrementiert. Nach 251 Durchläufen wird der ADC abgeschaltet (sonst verbleibt ein Ruhestrom), alle Ausgänge auf Eingang geschaltet und der Controller in Powerdown geschickt.

Damit die Taste sich nicht ungewollt während der Benutzung abschaltet, wird "offtimer" in der Keyer-Punkt-Routine nach jedem gesendeten Punkt auf Null zurückgesetzt.


Quelltext mit Kommentaren:

' T13A-Sensorkeyer mit Sinusausgang
'
' Pinbelegung ATtiny13A:
' PB0 PWM-Ausgang,  PB1 optionaler Schaltausgang fuer TRX-Tastung,  PB2 frei (Sensor-ADC-Referenz),
' PB3 Punktsensor, PB4 Strichsensor
'
' Speed-Poti liegt an Adc0 / Reset. Werte unter halber Betriebsspannung loesen also Reset aus. Ist kein Bug,
' sondern Feature; dient zum Neukalibrieren bzw. zum Einschalten des Keyers.

' Ausgeschaltet wird ueber zweckentfremdeten Watchdog nach etwa 30 min Untaetigkeit

' Stromverbrauch mit 5V: 4mA / 6mA
' Stromverbrauch mit 2,5V: 1,7mA / 2,5 mA  
'
' Lizenz: WTFPL - http://www.wtfpl.net/about/
' verwendete Programmierumgebung: BASCOM 2.0.7.9 Freeware-Version


$Regfile = "ATtiny13.dat"
$Crystal = 9600000                                          ' Taktfrequenz: 9,6 MHz
$Hwstack = 8                                                ' Platz fuer nur 4 Unterprogramme reserviert, spart SRAM
$Swstack = 0                                                ' wird nicht gebraucht
$Framesize = 0                                              ' dito

Clkpr = 128                                                 ' Taktumschaltung auf 9,6 MHz
Clkpr = 0
Clkpr = 0

Dim Dit As Word                                             ' Laenge Punkt
Dim Dah As Word                                             ' Laenge Strich
Dim Sndlen As Word                                          ' Zaehlvariable fuer Zeichen-/Pausendauer
Dim P As Byte                                               ' "Punktspeicher"
Dim N As Byte                                               ' Schleifenvariable fuer Sinus-Erzeugung
Dim Anadah As Word                                          ' ADC-Wert fuer Erkennung Dah-Sensor
Dim Anadit As Word                                          ' ADC-Wert fuer Erkennung Dit-Sensor
Dim Sa As Byte                                              ' Amplitudenwert fuer PWM
Dim Refana As Word                                          ' speichert nach Reset den Sensor-Leerlauf-Kalibrierwert 
Dim Pause As Byte                                           ' wenn 1, wird in Sinus-Unterprogramm PWM = 0 gesetzt
Dim Offtimer As Byte                                        ' Zaehler fuer Abschaltautomatik

Config Timer0 = Pwm , Prescale = 1 , Compare A Pwm = Clear_up
' ' "Fast PWM" (37,5 kHz) per Registerbits konfigurieren: Tccr0a.0 = 1 ' wgm00 Tccr0a.1 = 1 ' wgm01 Tccr0b.3 = 0 ' wgm02 Start Timer0

Acsr.acd = 1 ' Analog-Komparator ausschalten, spart etwas Strom Config Adc = Single , Prescaler = Auto ' AD-Converter konfigurieren und starten Start Adc

Ddrb = &B0000_0111 ' DDRB.0 ist Tonausgang; DDRB.2 ist der Pin der Sensor-ADC-Referenz Portb = &B1110_0000 ' koennte man weglassen; ist eh Auslieferungszustand Didr0 = &B00111111 ' Digitaleingaenge abschalten; spart Strom On Watchdog Onwdt ' Interrupt-Routine fuer Watchdog-interrupt ' Watchdog als Interrupt-Quelle konfigurieren - WDCR: 7=WDTIF 6=WDIE 5=WDP3 4=WDCE 3=WDE 2=WDP2 1=WDP1 0=WDP0 Wdtcr = &B0001_1111 ' Schreiben auf WDTCR Vorbereiten Wdtcr = &B0110_0001 ' Interrupt statt Reset, Watchdog- Timer auf 8 sec setzen Sreg.7 = 1 ' Interrupts global freigeben Waitms 200 ' Vorlaufzeit fuer den ADC Gosub Ditsensor ' einmal ADC-Leerlaufwert am Punktsensor messen (Anpassung an Sensor) Refana = Anadit + 96 ' Referenz 96 hoeher setzen Do Gosub Adclesen
Gosub Ditsensor
If Anadit > Refana Or P = 1 Then ' Punktsensor gedrueckt (PB3) oder Punkt gespeichert P = 0 ' Punktspeicher loeschen Pause = 0 Sndlen = Dit ' Zeichenelement mit Laenge "Dit" senden Portb.1 = 1 ' Schaltausgang ein Gosub Sinus
Portb.1 = 0 ' Schaltausgang aus Pause = 1 Sndlen = Dit ' Pause nach dem Punkt Gosub Sinus
Offtimer = 0 ' Watchdog-Zaehler bei Aktivitaet zuruecksetzen End If Gosub Dahsensor
If Anadah > Refana Then ' Strichsensor gedrueckt (PB4) Pause = 0 Sndlen = Dah
PortB.1 = 1 Gosub Sinus
PortB.1 = 0 Gosub Ditsensor
If Anadit > Refana Then ' registrieren, wenn am Strichende bereits Punkt gedrueckt ist P = 1 ' Punkt speichern End If Pause = 1 Sndlen = Dit ' Pause nach Strich Gosub Sinus
End If Loop Sinus: Do Restore Sinusdata ' Zeiger auf Anfang der Data-Tabelle setzen For N = 1 To 64 If Pause = 0 Then Read Sa ' Tabellenwert lesen Pwm0a = Sa ' der Wert in PWM0A bestimmt das Puls- / Pausenverhältnis Else Pwm0a = 0 ' statt Nachschlagen in Tabelle konstant 0 Waitus 3 ' Ausgleich dafuer, dass Nachschlagen etwas laenger dauert End If Waitus 16 ' Frequenz des Tones senken; 16µs -> 600 Hz Next N
Decr Sndlen ' Software-Timer fuer Punkt- / Strichlaenge Loop Until Sndlen = 0 ' Zeichenelement fertig Return Ditsensor: Admux = 1 ' ADC1 (Referenz) an ADC-Eingang legen ; der muss auf Low liegen (bereits zu Anfang fest konfiguriert) Portb.3 = 1 ' Pullup an Punktsensor ein Ddrb.3 = 1 ' Punktsensor nun Ausgang --> Sensorpin laden Waitus 32 Ddrb.3 = 0 ' Punktsensor wieder Eingang Portb.3 = 0 ' Pullup an Punktsensor aus --> Sensorpin floatet Anadit = Getadc(3) ' Punktsensor auslesen Return Dahsensor: Admux = 1 ' ADC1 (Referenz) an ADC-Eingang legen Portb.4 = 1 ' Pullup an Strichsensor ein Ddrb.4 = 1 ' Strichsensor ist nun Ausgang Waitus 32 Ddrb.4 = 0 ' Strichsensor wieder Eingang Portb.4 = 0 ' Pullup an Strichsensor aus Anadah = Getadc(2) ' Strichsensor auslesen Return Adclesen: ' Dit = 20: ~40 WpM Dit = 24: 25 WpM Dit = 64 11 WpM ' Mit 1280 und Rechts-Shift um 4: 17 WPM bis 50 WpM Dit = Getadc(0) Dit = 1280 - Dit ' 1280 = 1024 + 256 Shift Dit , Right , 4 ' entspricht Teilung durch 16, spart Flash-Speicher Dah = Dit + Dit ' statt Multiplikation; spart Flash (78 bytes!) Dah = Dah + Dit
Return Onwdt: Incr Offtimer ' alle 8 sec wird Offtimer incrementiert If Offtimer > 250 Then ' nach 251 * 8 sec. erfolgt Powerdown Stop Adc
DDRB = 0 ' Powerdown Mcucr = &B0011_0000 ' 4=1, 3=0: Powerdown 5=1 :Sleep enable !Sleep
End If Return End Sinusdata: Data 0 , 0 , 2 , 5 , 9 , 14 , 21 , 28 , 36 , 46 , 56 , 66 , 78 , 89 , 101 , 114, Data 127 , 139 , 151 , 163 , 175 , 186 , 197 , 207 , 216 , 225 , 232 , 238 , 244 , 248 , 251 , 253, Data 253 , 253 , 251 , 248 , 244 , 239 , 232 , 225 , 216 , 207 , 197 , 187 , 175 , 164 , 151 , 139, Data 127 , 114 , 102 , 90 , 78 , 67 , 56 , 46 , 37 , 28 , 21 , 15 , 9 , 5 , 2 , 0,


Da das Programm sehr gross ist, sollte beim Kompilieren des Codes die Codeoptimierung von BASCOM eingeschaltet sein. Notfalls kann man das beigefügte Hex-File verwenden, statt den Quelltext selber zu kompilieren.


Betriebserfahrungen

Die Schaltung wurde etwa ein halbes Dutzend mal nachgebaut und getestet. Die HF-Eigenschaften der "KW-Test-Version" scheinen überraschend gut zu sein. Ein Tester verwendet eine endgespeiste Antenne, deren "heisses" Ende direkt ins Shack geführt ist. Er konnte bei 100W Sendeleistung keine Beeinflussungen durch HF feststellen.

Das Geben mit Sensorpaddles ist zunächst etwas ungewohnt, weil schon eine kleine drucklose Berührung reicht, um einen Sensor auszulösen. Es gibt unterschiedliche Gebestile: bei manchen "fliegen die Finger" beim Geben (die haben kaum Probleme), andere halten beim Geben dichten Abstand zu den Paddles und lösen hier und da unbeabsichtigte Zeichen aus (ich gehöre zu den letzeren). Das Überkleben mit einer dünnen selbstklebenden Kunststoff-Folie kann die Empfindlichkeit etwas reduzieren.

Bestechend finde ich jedoch, dass Sensorpaddles so robust und einfach aufgebaut sind (und so gut wie nichts kosten). Ich hatte mir zuvor einen Nachbau des Bencher BY2 Paddles von MFJ beim DARC-Verlag gekauft. Den MFJ-Nachbau fand ich ziemlich "wacklig" und schwer einzustellen (allein schon die Inbus-Schrauben empfand ich als Zumutung), aber ich habe keine Erfahrung mit Squeeze-Paddles anderer Hersteller. Möglicherweise ist auch die Qualität des Bencher-Originals besser, denn viele Nutzer "schwören drauf".

Einigen Testern war das Sensorpaddle zu dünn, sie haben das Paddle mit allerlei kreativen Ideen "aufgepolstert", von Sessel-Teppichgleitern bis zu alten Armbanduhrendeckeln ;)



Dieters provisorischer Aufbau



Claudios provisorischer Aufbau


Durchaus verwirrend ist, dass der Keyer sich nach etwa einer halben Stunde Nichtbenutzung "schlafen legt". Wenn die Schaltung nicht reagiert: erst mal das Geschwindigkeitspoti kurz auf Minimum drehen!


Download des Quellcodes:  0220-sinus-sensorkeyer.zip