Logik-Logger

  Konzeption   Datenstruktur   Programm   Auswertung
Auf dieser Seite wird beschrieben, wie man mit einem Controller und einem MAX232 einen kleinen Datenaufzeichner bastelt, der seine Ergebnisse per serieller Schnittstelle auf den PC überträgt. Bei mir ist dieses Programm schon eine ganze Weile im Einsatz und hat sich als recht nützlich erwiesen.

Konzeption

Das Problem besteht oft: eine serielle Übertragung oder irgendeine Signalfolge scheint nicht richtig übertragen zu werden. Wenn das Signal in schneller Folge wiederholt werden kann und kurz genug ist, kann man es noch per Oszilloskop und ein wenig Bastelei an den Trigger-Einstellungen als stehenden Verlauf auf den Schirm bringen und so dekodieren. Aber sobald die Pulsfolgen auf den vielleicht 10cm des Schirms nicht mehr zu unterscheiden sind oder einer tiefergehenden Analyse bedürfen, ist diese Lösung komplett überfordert.

Die Anschaffung eines digitalen Oszilloskops mit Speicher und Auslesemöglichkeit ist der naheliegenderweise Schritt - der mangels genügend Kleingeld in der Portokasse oft genug unerschwinglich ist. Aber darum auf eine digitale Aufzeichnungsmöglichkeit verzichten? Nein, das kommt nicht in Frage!

Die Hardware-Anforderungen für die hier besprochene kleine Aufzeichnungslösung sind denkbar gering: ein Experimentierboard mit einem 89C2051 und ein MAX232 für die serielle Schnittstelle genügen vollauf. Ein Schaltplan wäre dafür übertrieben: es genügt, TxD des Controllers mit einem Eingang des MAX zu verbinden, dessen Ausgang mit RxD der seriellen Schnittstelle des PC zu verschalten, und das übliche 'Gemüse' anzuschließen, das man für einen MAX232 und einen 89C2051 braucht. Man kann also im Bedarfsfalle auch schnell eine kleine Schaltung 'fliegend' zusammenklemmen.

Hardware-Aufbau

Der 8051-kompatible AT89C2051 verfügt nur über 128 Bytes Speicher. Das hört sich kleiner an als es ist: mit einer geeigneten Speicherstruktur lassen sich eine ganze Anzahl Daten aufzeichnen.

zurück zum Anfang

Die Datenstruktur

Ein Datensatz dieser Datenstruktur besteht aus zwei Teilen: zum einen aus den Daten, in diesem Programm sind das 2 Bit. Zum anderen enthält jeder Datensatz einen Zähler, der angibt, wie lange die Datenbits unverändert an den Inputs anlagen. Um möglichst wenig Platz zu verschwenden, gibt es diesen Zähler in den Längen von 5 und 13 Bit. Hatte also das Muster an den Inputs nur 31 Abfragezyklen Bestand, so wird der gesamte Datensatz nur 1 Byte lang, anderenfalls werden 2 Byte benötigt. Ein Bit im ersten Byte erlaubt eine Unterscheidung zwischen den beiden Datensatztypen.

Der Abfragetakt beträgt 20us. Bei einem Speicherverbrauch des Programms von geschätzten 20 Bytes, von denen der größte Teil für den Stack reserviert ist, bleibt somit Platz für 54 Muster, die nicht länger sind als 163ms, mit einer maximalen Aufnahmedauer von 8.8s.

Alternativ finden 108 Muster mit einer maximalen Länge von 620us und einer Gesamtaufnahmedauer von 66ms Platz. Da die Datensatzlänge nicht feststeht, ist eine optimale Speicherauslastung sichergestellt. Steht ein Muster länger als 163ms an den Inputs, so wird ein neuer Datensatz angelegt.

  Bit     0-4     5     6-7     8-15  
5 Bit-Zähler DPL 1 Daten
13 Bit-Zähler DPH 0 Daten DPL

zurück zum Anfang

Das Programm

Das Programm selbst läuft folgendermaßen ab: zunächst wird in einer Endlosschleife gewartet, bis sich an den Eingängen etwas tut. Wurde eine Veränderung festgestellt, beginnt die eigentliche Aufzeichnung damit, dass der Timer 0 aktiviert wird.

Der Timer 0-Interrupt fragt nun periodisch die Eingänge ab. Dabei ergeben sich zwei Möglichkeiten. Haben sich die Zustände der beiden Eingänge P1.7 und P1.6 bzw. des Voltage Comparators an P3.6 nicht geändert, so wird ein Zähler (DPTR) heraufgesetzt.

Hat sich hingegen der Zustand eines oder beider Eingänge verändert oder ist der Zähler übergelaufen - in den Datensätzen war ja nur Platz für 13 Bit - so wird der Zähler in der oben beschriebenen Weise im Speicher abgelegt und der Speicherzähler R0 heraufgesetzt.

Ist der Speicher vollständig gefüllt, so bricht die Aufzeichnung ab, und die gesammelten Daten werden im ASCII-Format lfnd. Nr., Sig.0, Sig.1, Dauer über die serielle Schnittstelle mit 9600 Baud ausgegeben, wo sie von einem Programm am PC im Empfang genommen und ausgewertet werden können.

zurück zum Anfang


;Autor     : Erik Buchmann
;Projekt   : Logik-Logger
; Quarz    : 24.000 MHz
; Generator: CodeGen Stable Version 1.0, Erik Buchmann '01
; Assembler: AS bzw. ASL
; Datum    : 25.2.01
;---------------------------------------------------------------
 CPU 8051
 INCLUDE stddef51

; Wenn der Voltage Comparator anstelle P1.7/P1.6 aufgenommen
; werden soll, USE_VLTMP SET "true"

USE_VLTMP SET "true"

; Konstanten-, Speicher- und Portbelegung
;---------------------------------------------------------------

TL0PRE          EQU  215        ; 20us
TH0PRE          EQU  215

RAM_START       EQU   20
RAM_END         EQU  128

STOP_KEY        EQU P1.0

; Programmbeginn
;---------------------------------------------------------------
 SEGMENT code
 ORG 0h
        jmp start

; Interruptroutinen
;---------------------------------------------------------------

; Interruptbehandlungsroutine Timer 0
 ORG 0Bh

; braucht 30 MSC

; R0    Zeiger auf nächsten Datensatz
; R1    Daten
; R2    Zwischenspeicher
; DPTR    Zähler 0 bis 8191

; Datensatz:
;    Bits 6, 7 enthalten die Daten
;   Bit   5   wenn 1 dann Bits 8-15 nicht belegt, nur 1 Byte
;    Bits 0- 4 enthalten DPH oder DPL wenn Bit 5=1
;   Bits 8-15 enthalten DPL

; Datenpins: P1.7, P1.6, wenn USE_VLTMP = "true" P3.6

        jnb STOP_KEY, gotStopSig

 IF USE_VLTMP = "true"
ANLVAL EQU 01000000b
        mov R2,P3
 ELSEIF
ANLVAL EQU 11000000b
        mov R2,P1            ; haben sich die Daten geändert?
 ENDIF

        mov a,R2
        xrl a,R1
        anl a,#ANLVAL
        jz nochange          ; Block 11 MSC incl CALL und RETI

        mov a,DPL
        anl a,#11100000b
        orl a,DPH
        jz onlyonebyte

        mov a,R1             ; Daten haben sich geändert,
        orl a,DPH
        mov @R0,a
        inc R0
        mov @R0,DPL
        inc R0               ; Block 10 MSC

        mov a,R2             ; Zähler zurücksetzen
        anl a,#ANLVAL        ; Datensatz abspeichern
        mov R1,a
        mov DPL,#1
        mov DPH,#0
                             ; Speicher voll?
        cjne R0,#RAM_END,timer0_ram1_end    ; Block 9 MSC bis RETI

        ; Puffer ist voll, Aufzeichnung abbrechen
gotStopSig:
        clr TR0
        setb c
        reti

onlyonebyte:
        mov a,R1             ; Daten haben sich geändert,
        orl a,DPL
        setb Acc.5
        mov @R0,a
        inc R0

        mov a,R2             ; Zähler zurücksetzen
        anl a,#ANLVAL        ; Datensatz abspeichern
        mov R1,a
        mov DPL,#1
                             ; Speicher voll?
        cjne R0,#RAM_END,timer0_ram1_end    ; Block 9 MSC bis RETI

        ; Puffer ist voll, Aufzeichnung abbrechen
        clr TR0
        setb c
        reti


nochange:                    ; Daten haben sich nicht geändert
        inc DPTR             ; Zähler erhöhen

        mov a,DPL            ; Überlauf des Zählers?
        cjne a,#0,timer0_end
        mov a,DPH
        cjne a,#31,timer0_end    ; Block 8 MSC

        ; Zähler hat 8191 erreicht, neuen Datensatz anlegen
        mov DPL,#1
        mov DPH,#0

        mov a,R1             ; Datensatz abspeichern
        orl a,#00011111b     ; und Zeiger erhöhen
        mov @R0,a
        inc R0
        mov @R0,#11111111b
        inc R0
                             ; Speicher voll?
        cjne R0,#RAM_END,timer0_ram1_end    ; Block bis reti 13 MSC

        ; Puffer ist voll, Aufzeichnung abbrechen
        clr TR0
        setb c
        reti

timer0_ram1_end:
        cjne R0,#RAM_END-1,timer0_end    ; Block bis reti 13 MSC

        ; Puffer ist voll, Aufzeichnung abbrechen
        clr TR0
        setb c
        reti


timer0_end:                  ; Aufzeichnung fortsetzen
        clr c
        reti
; Funktionen
;---------------------------------------------------------------
 INCLUDE div10.inc

; ein Byte aus ACC ausgeben
serial_out:
;  auf Abschluß des letzten Sendevorganges warten
        jnb SCON.1,serial_out
        clr SCON.1
;  senden
        mov SBUF,a
        ret
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

; Initialisierung
;---------------------------------------------------------------
start:
        mov P1,#255
        mov P3,#255

; serielle Schnittstelle aktivieren
;  9600 Baud, Modus 1
        mov TL1, #243
        mov TH1, #243

; Timer 0 aktivieren
;  Software-Kontrolle
        mov TL0, #TL0PRE
        mov TH0, #TH0PRE

; die SFR's initialisieren
        mov SP,  #3
        mov SCON,#82
        mov PCON,#128
        mov TMOD,#34
        mov TCON,#64
        mov IE,  #130

; Hauptprogramm
;---------------------------------------------------------------
main:
        clr P3.3

        mov R0,#RAM_START    ; Zähler zurücksetzen
        mov DPL,#1
        mov DPH,#0

 IF USE_VLTMP = "true"
        mov a,P3
 ELSEIF
        mov a,P1
 ENDIF
        anl a,#ANLVAL
        mov R1,a

main_trigger:                ; auf Start warten
 IF USE_VLTMP = "true"
        mov a,P3
 ELSEIF
        mov a,P1
 ENDIF
        anl a,#ANLVAL
        xch a,R1
        xrl a,R1
        jz main_trigger

        clr P3.4

        setb TR0             ; Timer anstellen
        clr c

main_wait:                   ; auf das Ende der Aufzeichnung warten
        jnc main_wait

        clr P3.5

        ; Daten auswerfen, von Byte RAM_START bis RAM_END
        mov R0,#RAM_START
        mov R2,#0
main_output:
        mov a,R2             ; RAM-Position ausgeben
        mov b,#10
        div ab
        push b
        mov b,#10
        div ab
        add a,#48
        call serial_out
        mov a,b
        add a,#48
        call serial_out
        pop Acc
        add a,#48
        call serial_out
        inc R2

        mov a,#','           ; Komma
        call serial_out

        mov a,@R0            ; P1.7 ausgeben
        anl a,#10000000b
        rl a
        add a,#48
        call serial_out

        mov a,#','           ; Komma
        call serial_out

        mov a,@R0            ; P1.6 ausgeben
        anl a,#01000000b
        rl a
        rl a
        add a,#48
        call serial_out

        mov a,#','           ; Komma
        call serial_out

        mov a,@R0            ; Anzahl der Durchläufe
        anl a,#00111111b     ; ermitteln

        jnb Acc.5, gottwobyte
        clr Acc.5
        mov DPH,#0
        mov DPL,a
        inc R0
        jmp ptrisset

gottwobyte:
        mov DPH,a
        inc R0
        mov DPL,@R0
        inc R0

ptrisset:
        ; Anzahl der Durchläufe ausgeben
        push 0
        push 1

        mov R0,DPL
        mov R1,DPH

        call F_div10
        push Acc
        call F_div10
        push Acc
        call F_div10
        push Acc
        call F_div10
        push Acc
        call F_div10

        add a,#48
        call serial_out
        pop Acc
        add a,#48
        call serial_out
        pop Acc
        add a,#48
        call serial_out
        pop Acc
        add a,#48
        call serial_out
        pop Acc
        add a,#48
        call serial_out

        pop 1
        pop 0

        mov a,#'\n'          ; Wagenrücklauf
        call serial_out

        cjne R0,#RAM_END,main_loop
main_end:
        mov a,#'<'
        call serial_out
        mov a,#'E'
        call serial_out
        mov a,#'O'
        call serial_out
        mov a,#'F'
        call serial_out
        mov a,#'>'
        call serial_out
        mov a,#'\n'
        call serial_out

        clr P3.7

        jmp ende

main_loop:
        cjne R0,#RAM_END-1,main_loop2
        jmp main_end

main_loop2:
        jmp main_output

; Programmende
;---------------------------------------------------------------
ende:
        jmp ende
END

Auswertung der Daten

Zur Auswertung der Daten genügt ein kleines Progrämmchen, das die Daten per serieller Schnittstelle einliest und irgendwie grafisch präsentiert. Weil die Daten im ASCI-Format übertragen werden, ist dafür nicht viel Aufwand nötig.

Da das Auswertungsprogramm in TCL/TK geschrieben ist, läuft es unter allen Plattformen, für die ein entsprechender Interpreter vorhanden ist, ohne Änderungen am Code. Also unter Linux und Windows ebensogut wie unter Solaris - sofern mir jemand verrät, wie bei den UltraSparc's die Schnittstellen verschaltet sind :-)

Den Interpreter gibt's bei http://dev.scriptics.com. Das Auswertungsprogramm kann hier heruntergeladen werden.

Auswerteprogramm
zurück zum Anfang

Seite zurück Startseite
Erik Buchmann
EMail an: Owner@ErikBuchmann.de