HSG |
|
Folgende Teilprobleme sind zu lösen:
import time import serial s = serial.Serial('/dev/ttyS1') bitzeit = 0.2 print('Empfang gestartet') while True: bm = '' # 01-Flanke abwarten alt = s.getCTS() neu = s.getCTS() while alt or not neu: time.sleep(0.005) alt = neu neu = s.getCTS() # Anfang Startbit time.sleep(1.5*bitzeit) # mitten im ersten Datenbit # Datenbits abtasten for i in range(8): if s.getCTS(): bm = bm + '1' else: bm = bm + '0' time.sleep(bitzeit) # mitten im Stoppbit b = int(bm,2) print('empfangen:',bm,chr(b),b)
Der Empfänger empf0.py muss zunächst auf richtige Schnittstelle und richtige Bitzeit gestellt werden. Wie man sieht, läuft er einmal gestartet ohne Ende. Er kann aber unter idle mit Ctrl-C abgebrochen werden. Nach einem Abbruch ist aber die Schnittstelle noch in Benutzung, sie kann mit 's.close()' geschlossen werden.
>>> Empfang gestartet empfangen: 01001000 H 72 empfangen: 01100001 a 97 empfangen: 01101100 l 108 empfangen: 01101100 l 108 empfangen: 01101111 o 111 empfangen: 00100000 32 empfangen: 01010111 W 87 empfangen: 01100101 e 101 empfangen: 01101100 l 108 empfangen: 01110100 t 116 empfangen: 00100001 ! 33 Traceback (most recent call last): File "/home/mk/Desktop/empf0.py", line 14, in <module> time.sleep(0.005) KeyboardInterrupt >>> s.close() >>>
Im Experiment war ein einwandfreier Empfang erst bei einer Bitzeit von 0,2s möglich.
Mit Hilfe des Programms Sender 0 wurde mit einer Bitzeit von 0,1s der Text 'Hallo' gesendet. Ein 'Roh-Empfang' mit dem Pegel-Transceiver sah vielversprechend aus.
Ohne große Mühe kann man nach dem Startbit das Bitmuster ' 0100 1000' gefolgt vom Stoppbit erkennen. Das Bitmuster gehört auch zum Buchstaben 'H', prima. Das folgende Bitmuster ist '0110 0001', das kann man z.B. in der Shell
>>> chr(int('01100001',2)) 'a' >>>
als zu einem 'a' gehörig identifizieren. Es scheint problemlos möglich auf diese Weise die ganze Nachricht zu 'entziffern', so gehört z.B. zu dem 'o' das Bitmuster '0110 1111', was sich am Ende des Oszillogramms wiederfinden lässt.
Im nächsten Schritt soll nun obiges Empfangsprogramm getestet werden.
>>> Empfang gestartet empfangen: 01001000 H 72 empfangen: 01100001 a 97 empfangen: 10110001 ± 177 empfangen: 10110001 ± 177 empfangen: 11100000 à 224 empfangen: 01001000 H 72 empfangen: 10000101 133 empfangen: 10110001 ± 177 empfangen: 10001011 139 empfangen: 11100000 à 224 empfangen: 01001000 H 72 empfangen: 01100001 a 97 empfangen: 10110001 ± 177 empfangen: 10110001 ± 177 empfangen: 11100000 à 224 empfangen: 01001000 H 72 empfangen: 10000101 133 empfangen: 10110001 ± 177 empfangen: 10001011 139 empfangen: 11100000 à 224 empfangen: 01001000 H 72 empfangen: 10000101 133 empfangen: 10001011 139 empfangen: 10001011 139 Traceback (most recent call last): File "/home/mk/Desktop/empf0.py", line 16, in <module> time.sleep(0.005) KeyboardInterrupt >>> s.close() >>>
Es fällt auf, dass zwar das erste Zeichen (einmal auch das zweite) richtig empfangen wird, die folgenden aber alle gestört sind. Vergleicht man das gesendete Bitmuster '01100001' mit dem z.B. empfangenen '10000101', so bemerkt man die Übereinstimmung im Muster '100001', der Teil '01' hingegen fehlt? Was könnte da passiert sein? Man könnte die Hypothese aufstellen, dass die steigende Flanke des Startbits erst bei '01' entdeckt wird und vorher 'überlesen' wurde. Die 0-1-Flankenentdeckung aus obigem Programm bleibt inaktiv, wenn man im Startbit ist. Sie bleibt auch inaktiv, wenn ein 0-Bit folgt. Das Empfangsprogramm-Programm tastet in Bitmitte ab und wartet dann eine ganze Bitzeit, dh. es ist sicher bis zur Mitte des Stoppbits nicht bereit, die 0-1-Flanke zu entdecken. Die zur Verfügung stehende Zeit beträgt eine halbe Bitzeit, im vorliegenden Fall also 0,05s. 'Verplembert' der Empfänger möglicherweise hier entscheidende Zeit? Die beiden Zeilen
b = int(bm,2) print('empfangen:',bm,chr(b),b)
sollen nun vermessen werden.
>>> import time >>> def t(): bm = '01000001' t1 = time.time() b = int(bm,2) print('empfangen:',bm,chr(b),b) t2 = time.time() return t2-t1 >>> t() empfangen: 01000001 A 65 0.03802609443664551 >>> t() empfangen: 01000001 A 65 0.049224853515625 >>> t() empfangen: 01000001 A 65 0.060156822204589844 >>> t() empfangen: 01000001 A 65 0.06677603721618652 >>> t() empfangen: 01000001 A 65 0.07325196266174316 >>> t() empfangen: 01000001 A 65 0.07481098175048828 >>> t() empfangen: 01000001 A 65 0.07638692855834961 >>> t() empfangen: 01000001 A 65 0.048609018325805664 >>> t() empfangen: 01000001 A 65 0.059754133224487305 >>> t() empfangen: 01000001 A 65 0.06090712547302246 >>> t() empfangen: 01000001 A 65 0.06513810157775879 >>> t() empfangen: 01000001 A 65 0.0659639835357666 >>>
Das erste Messergebnis ergibt eine ausreichend kurze Zeit. Wiederholungen der Messung, um die Reproduzierbarkeit zu untersuchen, zeigen zunächst eine kontinuierliche Zunahme der Zeit, die sich dann aber auf einen Wert von ca. 0.065s einpendelt. Das ist eindeutig zu viel, der auftretende Fehler ließe sich zwanglos damit erklären. Als einen ersten Versuch, den Fehler zu vermeiden, könnte man das Stoppbit verlängern. Tatsächlich lässt sich im 'richtigen' RS232-Protokoll das Stoppbit auf 1, 1,5 oder 2 Bitlängen einstellen. Mit Stoppbitlänge 0,2s ergibt sich folgender Empfang
>>> Empfang gestartet empfangen: 01001000 H 72 empfangen: 01100001 a 97 empfangen: 01101100 l 108 empfangen: 01101100 l 108 empfangen: 01101111 o 111 empfangen: 01001000 H 72 empfangen: 01100001 a 97 empfangen: 01101100 l 108 empfangen: 01101100 l 108 empfangen: 01101111 o 111 empfangen: 01001000 H 72 empfangen: 01100001 a 97 empfangen: 01101100 l 108 empfangen: 01101100 l 108 empfangen: 01101111 o 111 empfangen: 01001000 H 72 empfangen: 01100001 a 97 empfangen: 01101100 l 108 empfangen: 01101100 l 108 empfangen: 01101111 o 111 Traceback (most recent call last): File "/home/mk/Desktop/empf0.py", line 14, in <module> time.sleep(0.005) KeyboardInterrupt >>> s.close() >>>
Der Fehler scheint beseitigt, aber war die Lösung gut? Warum ist klar, dass bei einem Fehler der beschriebenen Art alle folgenden Zeichen gestört sind? Wie könnte man das Protokoll oder auch den Empfänger verbessern?
Grundsätzlich ist anzustreben, den Empfang von der grafischen Anzeige zu trennen. So kann man prinzipiell den eigentlichen Empfänger über eine Callback-Routine die Aktualisierung der Anzeige in der GUI auslösen lassen. Läuft der Empfänger im gleichen Thread wie die GUI, so mag das funktionieren, wenn auch übliche GUI-Funktionen während des Empfangs blockiert sind. Man kann beispielsweise den Empfang nicht ordentlich beenden. Eine bessere Lösung ist es, den Empfänger in einem eigenen Thread laufen zu lassen. Jetzt könnte aber ein asynchroner Zugriff des Empfängers auf die GUI zu einem Absturz führen. Es ist besser, die GUI in einem periodischen Polling den Zustand des Empfangspuffers abfragen und anzeigen zu lassen. Verwendet man dazu den GUI-Timer, so wird der schon für die nötige Synchronisierung sorgen. Es bleibt der konkurrierende Zugriff von Empfänger und GUI auf den Empfangspuffer. Hier kann mit einem geeigneten Lock ein überlappender Zugriff verhindert werden.
import time import threading stopflag = threading.Event() # Ende mit stopflag.set() def warteFlanke01(getLeitung): """ die Funktion blockiert bis über 'getLeitung' ein 0-1-Flankenwechsel entdeckt wurde """ alt = getLeitung() neu = getLeitung() while (alt or not neu) and (not stopflag.is_set()): time.sleep(0.0005) alt = neu neu = getLeitung() def receiveByte(bitzeit,getLeitung): """ im miniRS232-Protokoll wird mit der Bitzeit bitzeit in s und der Funktion getLeitung zum Abtasten der Leitung ein Bitmuster bm zurueckgegeben getLeitung hat den Typ bool """ bm = '' # Anfang Startbit time.sleep(1.5*bitzeit) # mitten im ersten Datenbit # Datenbits abtasten for i in range(8): if getLeitung(): bm = bm + '1' else: bm = bm + '0' time.sleep(bitzeit) # mitten im Stoppbit return bm def receive(bitzeit,getLeitung,puffer,enabled): """ die Funktion haengt empfangene Bytes dem bytearray puffer an, dabei wird das miniRS232-Protokoll mit der Bitzeit bitzeit in s und die Funktion getLeitung (Rückgabe bool) zum Abtasten der Leitung verwendet, die Funktion arbeitet solange bis das stopflag gesetzt wird """ print('receive gestartet') # DEBUG while not stopflag.is_set(): warteFlanke01(getLeitung,stopflag) if not stopflag.is_set(): bm = receiveByte(bitzeit,getLeitung) b = int(bm,2) print('empfangen:',bm,chr(b),b) # DEBUG puffer.append(b) print('receive gestoppt') # DEBUG if __name__ == "__main__": import serial s = serial.Serial('/dev/ttyS0') enabled = True puffer = bytearray([]) receive(0.02,s.getCTS,puffer,enabled) s.close()
Ein Programm zum Empfang von Nachrichten nach unserem miniRS232-Protokoll könnte
folgenden Aufbau haben:
Die Bitverschiebungsbefehle shl (shift left) und shr (shift right) haben sich als sehr nützlich zum
Zerlegen und Zusammenfügen von Bytes ('Integers') erwiesen. So wird z.B. aus dem Byte b = 148 = '1001 0100'
durch die Anweisung b := b shl 2; das Byte b = 80 = '0101 0000'.
Schreibe ein Programm, das einen einfachen Empfänger zu unserem Protokoll realisiert.
Teste das Programm indem auf einer Schnittstelle empfangen wird, was auf einer anderen Schnittstelle gesendet wird.
Die Schnittstellen werden dabei mit einem Nullmodemkabel (RTS und CTS gekreuzt) verbunden. Die Schnittstellen können dabei
auf demselben oder auf verschiedenen Computern sein.
Möglicherweise wird es nötig sein, den Fenster-Refresh explizit z.B. mit Form1.refresh; aufzurufen.
Versuche beim Testen des Programms das Fenster zu verschieben. Was passiert, wenn man das Programm beenden will?
empfaenger0.zip, Empf0_exe.zip
Zumindest auf einigen Rechnern kann man wesentlich höhere Baud-Raten erreichen, wenn man am Anfang des Programms die Zeitauflösung von sleep mit
... uses ...., mmsystem, ... ... TimeBeginPeriod(1);
auf eine große Granularität schaltet und am Schluss mit
TimeEndPeriod(1);
wieder ausschaltet.
Man kann Windows zu einem entscheidend - für unsere Zwecke - besseren Zeitverhalten veranlassen, wenn man mit dem Taskmanager (Strg-Alt-Entf) dem Prozess mit der rechten Maustaste eine höhere Priorität zuweist. Die Priorität 'Echtzeit' erreicht man nur mit Administratorrechten.