;******************************************************************* ; Steuer-Firmware für das Elektor-232-485-Board. Version 2.0 ; Prozessor 16F84 mit Quarz 4 MHz. [GETESTET UND IM EINSATZ] ; (Es sollte sich aber auch zurück auf den 16C54 portieren lassen.) ; Written by (Fügen Sie hier Ihren Namen ein! - Und übersetzen!) ;******************************************************************* LIST p=16F84, r=dec INCLUDE "P16F84.inc" __IDLOCS 0804h ;Monat und Jahr __CONFIG _XT_OSC & _CP_OFF & _WDT_OFF & _PWRTE_ON CLOCK equ 1000000 ;Taktfrequenz/4 ;******************************************************************* ; Ein/Ausgabe-Pinzuordnung: ; ; (17) RA0 = Out, LED1 (Senden 232->485). ; (18) RA1 = Out, LED2 (Empfang 485->232). ; ( 1) RA2 = Out, rote LED "Rahmenfehler" (fehlendes Stoppbit) ; ( 2) RA3 = Out, enabling transmission on 485 bus (active low). ; ( 3) RA4/T0CKI= In, not used. ; ( 6) RB0 = In, 232 receiver monitoring. ; ( 7) RB1 = In, 485 reveiver monitoring. ; ( 8) RB2 = In, cfg jumper #1 (F). (Stoppbits0) ; ( 9) RB3 = In, cfg jumper #0 (E). (Stoppbits1) ; (10) RB4 = In, cfg jumper #2 (D). (Baudrate0) ; (11) RB5 = In, cfg jumper #3 (C). (Baudrate1) ; (12) RB6 = In, cfg jumper #4 (B). (Baudrate2) ; (13) RB7 = In, cfg jumper #5 (A). (Baudrate3) ; ( 4) MCLR = In, Rücksetzeingang, verbunden mit Vcc ; (15) OSC2 = Quarz ; (16) OSC1 = Quarz ; ; Die drei Leuchtdioden leuchten jeweils 65 ms auf bei folgenden ; Ereignissen: ; D1 an RA0, wenn vom RS232 ein Startbit kommt, ; D2 an RA1, wenn vom RS485 ein Startbit kommt, ; D3 an RA2, wenn (zz. nur vom RS232) ein Rahmenfehler erkannt wird, ; d.h. wenn zu einer Zeit, wenn das Stopp-Bit kommen müsste, ; ein LOW-Signal detektiert wird. ; Die LEDs werden, wie üblich, mit je einem Widerstand an Vcc angeschlossen, ; die Ausgänge sind LOW-aktiv. Sie leuchten bei _jeder_ Baudrate. ; ; Die Baudrate und Anzahl der Datenbits werden mit den Jumpern an B7~B4 ; eingestellt, siehe (Tabelle kann vom Anwender geändert werden) ; Die chaotische Zuordnung vom Elektor-Original wurde hier über Bord geworfen, ; aber das lässt sich leicht rückgängig machen. ; ; Die Stoppbits sind _nicht_ so gemeint, dass diese exakt mit dem Computer ; stimmen müssen (wie bei der Vorgängerversion), vielmehr wird hiermit ; die _Rückschaltverzögerung_ des RS485-Transmitters festgelegt. ; Und zwar in _halben_ Bitzeiten, Jumper B3~B2 haben diese Belegung: ; 00 = 1 Stoppbit, 01 = 1½ Stoppbit, 10 = 2 Stoppbit, 11 = 2½ Stoppbit. ; (0 = gesteckt, 1 = gezogen, weil low-aktiv und PIC-interne Pullups) ; In der Regel kann man diese Jumper ungesteckt lassen! ; ;P.S. Mit Invertierung der Signale an B0 und B1 kann man den MAX232 ; weglassen; die Leitung zum PC sollte dann aber möglichst kurz sein, ; TxD des Computers über einen Serienwiderstand von 1 kOhm und ; mit 2 Klemmdioden nach Vcc und GND oder 1 Z-Diode 4,7 V absichern! ; ; Eine automatische Detektierung der Baudrate (wie bei Infrarot-Umsetzern) ; ist bei der gewünschten hohen Baudrate, zumindest für eine lahme PIC, ; so gut wie unmöglich. Deshalb das Rudel Jumper oder DIL-Schalter. ; (Bitte beim Leiterplattenentwurf Beschriftung nicht vergessen!) ; Ein Hex-Dreh-Kodierschalter für die Baudrate sieht auch an einer ; Frontplatte einigermaßen ordentlich aus. ;******************************************************************* CBLOCK 0x0C ;Ab hier freie Speicherzellen im RAM dbl,dbh ;Zeit-Wert Stopp-Bit(s) Low und High d9l,d9h ;Zeit-Wert Daten-Bits Low und High tl,th ;Temporäres dll,dlh ;Zähler Low und High ENDC ;******************************************************************** org 0 goto main org 4 ;Timer-ISR zur Verzögerung des Ausschaltens der LEDs (=Software-Monoflop) bcf INTCON,T0IF ;Interrupt-Anforderung löschen bsf PORTA,0 ;LED "TxD" aus bsf PORTA,1 ;LED "RxD" aus bsf PORTA,2 ;LED "Rahmenfehler" aus retfie ;fertig (8 Takte) ;Ist die Nachleuchtdauer mit 65 ms zu kurz, hilft nur der Ausweich auf ;einen Softwarezähler, einen 16-bit-Zähler (wenn vorhanden) ;oder den Watchdog (der dann mal zu etwas _nütze_ wäre!). ;*** "Fehlende" Standard-Makros *** MOV MACRO dest,src movfw src movwf dest ENDM MOVE MACRO src,dest ;PIC-mäßig "verkehrtherum" movfw src movwf dest ENDM LD MACRO dest,imm movlw imm movwf dest ENDM LOAD MACRO imm,dest ;PIC-mäßig "verkehrtherum" movlw imm movwf dest ENDM DJNZ MACRO reg,dest ;wie 8051; ähnlich Z80; 80x86 "LOOP" decfsz reg,f goto dest ENDM ;**************************************** ;** Tabelle, die die Bitzeiten enthält ** ;**************************************** BitTime MACRO baudrate,datenbits ;Makro erzeugt vier retlw-Anweisungen ;Die ersten beiden RETLWs sind für das Abwarten des Bytes. variable i ;Die Wartezeit für das Datenpaket ist ideal 9½ (7 bit: 8½) Bitzeiten, damit ;ab der Mitte des Stoppbits abgetastet wird. i=(CLOCK*(datenbits*2+3))/baudrate/2 ;Konstante für einmaligen Durchlauf der Warteschleife und Befehle subtrahieren i-=20 ;Siehe Angaben in geschweiften Klammern {} unten ;Die entsprechende Schleife braucht 3 Takte pro Runde i/=3 ;Nun i = Anzahl Schleifenrücksprünge, wäre nicht die Lücke! ;Das "Lücken-Problem" (Lücke 255*3 - 257*3, es gibt kein 256*3!) lösen i-=high(i) ;Die Lücke als solche bleibt allerdings; nur der sich IF i<0 ;ergebende, kumulierende Fehler wird hier kompensiert ERROR "Baudrate zu hoch!" ENDIF IF i>65535 ERROR "Baudrate zu niedrig!" ENDIF ;Nun noch Low-Teil und High-Teil der Verwendung entsprechend inkrementieren retlw low(i)+1 retlw high(i)+1 ;Die zweiten RETLWs sind für das Abwarten des Abschaltens des RS485-Treibers: ;Es sollte eigentlich die Rundenzahl für die halbe Bitzeit gepeichert werden. ;Da die Zahl aber recht klein ist und multipliziert werden soll, ;wird hier eine 16fache (rechtsschiebbare) Rundenzahl gespeichert. i=CLOCK*16/baudrate/2 ;Die entsprechende Schleife braucht 5 Takte pro Runde. i=i/5 ;nun i = Anzahl Schleifenrücksprünge *16 für ½ Bitzeit IF i>16383 ;wir müssen ja noch 4x addieren können ERROR "Baudrate zu niedrig (Rechenfaktor 16 ändern)!" ENDIF ;Das Abziehen einer Konstanten [6] wie oben ist erst nach Verrechnung mit ;der Stoppbitzahl möglich. retlw low(i) retlw high(i) ENDM DlyTabNext: incf dll,f DlyTab: movfw dll ;Jumper: 0 = gesteckt, 1 = gezogen addwf PCL,f ; B7 B6 B5 B4 BitTime 110,8; 0 0 0 0 BitTime 300,8; 0 0 0 1 BitTime 600,8; 0 0 1 0 BitTime 1200,8; 0 0 1 1 BitTime 2400,8; 0 1 0 0 BitTime 4800,8; 0 1 0 1 BitTime 9600,8; 0 1 1 0 BitTime 19200,8; 0 1 1 1 BitTime 38400,8; 1 0 0 0 BitTime 57600,8; 1 0 0 1 BitTime 115200,8; 1 0 1 0 BitTime 600,7; 1 0 1 1 Hier kann der User BitTime 1200,7; 1 1 0 0 schalten und walten BitTime 2400,7; 1 1 0 1 wie er will: BitTime 4800,7; 1 1 1 0 Einfach die Zahlen BitTime 9600,7; 1 1 1 1 ändern! ;**************************************************** ;** Unterprogramme zum Setzen/Berechnen der Zeiten ** ;**************************************************** ;MAKRO Bitzeit ermitteln (je nach Jumper) ;PE: dll=Jumper-Bits für Baudrate um 2 nach links geschoben ;PA: dbh:dbl=Bit-Zeit in µs (16-bit-Wert) ;VR: w,dbh:dbl GetBitTime MACRO movlw 3Ch andwf dll,f ;/4 teilbarer Offset call DlyTab ;Erstes Byte movwf d9l ;Fertiges Zähl-Word, Low-Teil call DlyTabNext ;Nächstes Byte movwf d9h ;Fertiges Zähl-Word, High-Teil call DlyTabNext movwf dbl ;Halbe Bitlänge, Low-Teil call DlyTabNext ; (noch mit Stoppbits zu verrechnen!) movwf dbh ;Halbe Bitlänge, High-Teil ENDM ;UP Verzögerungszeiten setzen ;PE: dll=Jumper-Bits für Baudrate um 2 nach links geschoben ; dlh=Jumper-Bits für Stoppbits ;PA: dbh:dbl, d9h:d9l gesetzt ;VR: w,th:tl,dlh:dll SetDelay: GetBitTime ;Die Wartezeit, bis das Treiber-Signal entfernt wird, ;beträgt Stoppbits (vom Jumper) * Bitzeit/2 ;ergibt eine Multiplikation mit 26 ((Stoppbits+1)/2/5*256) TSTF dlh BZ nomul MOVE dbl,tl ;Multiplikation durch Addition MOVE dbh,th minimul: ;für bis zu 3 Runden movwf tl addwf dbl,f ADDCF dbh,f movwf th addwf dbh,f DJNZ dlh,minimul nomul: ;Jetzt müsste man 6*16/5 abziehen, aber es ist besser, ein bisschen länger ;zu warten, als Jitter-Ausgleich. Die Resynchronisierung ist davon unabhängig. ;Nun rechtsschieben (die lästige Division hat der Assembler schon gemacht) LOAD 4,tl shift: CLRC rrf dbh,f rrf dbl,f DJNZ tl,shift ;und Low und High inkrementieren (das Lücken-Problem hier ignorierend) incf dbl,f incf dbh,f return ;UP Empfangs-LED einschalten ;VR: Z=0 ;TA: 4 (ohne CALL) RxOn: clrf TMR0 ;Monoflop rücksetzen bcf PORTA,1 ;LED einschalten return ; (unbedingt in dieser Reihenfolge!) ;UP Fehler-LED einschalten, sonst wie RxOn ErrOn: clrf TMR0 ;Monoflop rücksetzen bcf PORTA,2 ;LED einschalten return ;******************* ;** Hauptprogramm ** ;******************* ;(1.) Ports, Zeitgeber und Interrupts initialisieren main: LOAD 00001000b,PORTA ;Alle LEDs einschalten (zur Kontrolle) clrw TRIS PORTA ;PortA: Alles Ausgänge ; movlw 11111111b ; TRIS PORTB ;PortB: Alles Eingänge (Ist schon so) movlw 00000111b ;Vorteiler 256 an Zeitgeber TMR0 OPTION bsf INTCON,T0IE ;Zeitgeber Überlaufinterrupt (Monoflop) bsf INTCON,GIE ;Globale Interrupts EIN ;(2.) Jumper einlesen und Zeiten ermitteln MOVE PORTB,dll ;Jumper einlesen rrf dll,f ;Die vier Baudraten-Bits will SetDelay rrf dll,f ;...in der Mitte haben movfw dll andlw 3 ;Untere 3 Bits = Verzögerung des Rückschaltens movwf dlh ;merken call SetDelay ;Setzen von dbl:dbh und d9l:d9h ;(3.) Hauptschleife: Warten auf Daten und RA3 schalten ; (Mehr macht das ganze Programm ja gar nicht!) mainloop: MOVE dbl,dll ;Bit-Warte-Zeit kopieren {16+} MOVE dbh,dlh ; {18+} btfss PORTB,0 call ErrOn ;sollte nie passieren; meist: falsche Baudrate ;Warten bis Stop-Bit-Zeit zu Ende, zur Re-Synchronisation aufs nächste Start-Bit. ;Dann unendlich warten aufs nächste Start-Bit, nebenbei Rx-LED bedienen ;PS: Falls beim _Eintritt_ in diese Fkt. ein 0-Bit vorliegt, liegt ein Rahmen-Fehler ; (fehlendes Stop-Bit) vor! w1: btfss PORTB,0 ;Nächstes Startbit (0) erscheint? {1} [2] goto w3 ;ja, Warteschleife beenden {3} DJNZ dll,w1 ; [4] DJNZ dlh,w1 ; [6] bsf PORTA,3 ;nach Ablauf der Stopp-Bit-Zeit Sender ausschalten w2: btfss PORTB,1 ;Ein 0-Bit auf Empfangsleitung? {2-4} call RxOn ;ja, LED einschalten btfsc PORTB,0 ;Ein 0-Bit auf Sendeleitung? {1-5} goto w2 ;nein, endlos warten { 2} w3: bcf PORTA,3 ;Sender einschalten {2-6} clrf TMR0 ; {3+} bcf PORTA,0 ;Tx-LED einschalten {4+} ;Neun Bit-Zeiten abwarten (dann kommt ja immer ein Stopp-Bit!) MOVE d9l,dll ; {6+} MOVE d9h,dlh ; {8+} ;Einfachste 16-Bit-Warteschleife (ohne extra Abbruchbedingung) ;Für jedes 3 Takte, für jedes 3*255+6 Takte, zuzüglich 4 Takte w4: DJNZ dll,w4 ; {10+} DJNZ dlh,w4 ; {12+} goto mainloop ; {14+} END ;********************** ;** Ende mit Allende ** ;********************** Wieso zum Geier war das Originalprogramm soo lang? Und unflexibel, unlogisch und fehlerhaft?