PIC-Interrupt-basierte Soft-UART-Timing-Probleme

Ich habe versucht, einen Software-UART auf einem PIC18F452 mit TIMER0-Interrupts zu implementieren, und ich kann das Timing nicht zum Laufen bringen.

Ich verwende MPLAB ASM zum Kompilieren und das PICkit2 zum Programmieren.

LIST P=18F452
include <P18F452.inc>
CONFIG WDT=OFF, LVP=OFF, OSC=HS, CP0=OFF

variable cyclesPerBaud=1            ; 1 cycle(s) per baud
variable cyclesInt=0xFFFF           ; counter in 16 bit timer
variable freq = 20000000            ; clock frequency 20Mhz
variable baud = 9600                    ; baud rate
variable cyclesMain=33              ; cycles in Main program branch (intr->checkBitCounter->startStopBit->transfer)
variable cyclesIdle=16              ; cycles in Idle branch (intr->Idle)
variable offset = (freq/(4*baud))/(cyclesPerBaud)
variable initOffset = cyclesInt - offset
variable durrOffset = cyclesInt - offset + cyclesMain
variable hurrOffset = cyclesInt - offset + cyclesIdle

cblock 0x20
    char                    ; character to send
    bitCounter      ; bits left to send
    startStopBit    ; checks to send start of stop bit
    buffer              ; buffer where char is rotated
    cycle                   ; the current cycle in cyclesPerBaud starting high
endc

org 0000h
    goto main
org 0008h
    goto intr

main
bcf OSCCON, SCS     ; use primary clock
bsf RCON, IPEN      ; enable priority levels

; interrupt config:
bsf INTCON, GIEH    ; enable high priority interrupts
bcf INTCON, GIEL    ;   disable low priority interrupts
bsf INTCON, TMR0IE; enable TMR0 interrupts
bcf INTCON, INT0IE; dis. ext. interrupts
bcf INTCON, RBIE    ; dis. rb port change int.
bcf INTCON, TMR0IF; clear the TMR0 intr. flag bit
bcf INTCON, INT0IF; 
bcf INTCON, RBIF    ;

bsf INTCON2, TMR0IP; set TMR0 high priority

; timer 0 config:   
bcf T0CON, TMR0ON   ; temp. disable timer
bcf T0CON, T08BIT   ; 16 bit counter
bcf T0CON, T0CS     ; T0CKI pin input as source
bcf T0CON, T0SE     ; switch on falling edge
bsf T0CON, PSA      ; disable prescaler
bcf T0CON, T0PS2    ; doesn't really matter
bcf T0CON, T0PS1    ; -||-
bcf T0CON, T0PS0    ; -||-

; init output registers and variables
clrf TRISD
clrf LATD
movlw 0xFF
movwf PORTD             ; our output port set to high (serial 0 - but not sure about that)
movlw b'01111111' ; symbol to send
movwf char
movlw cyclesPerBaud
movwf cycle             ; unused
clrf bitCounter     ; zeros
clrf startStopBit   ; zeros
clrf buffer             ; zeros
; eof init 
movlw LOW initOffset    ; dunno why but HIGH has to be reversed with LOW
movwf TMR0H             
movlw HIGH initOffset   
movwf TMR0L             
bsf T0CON, TMR0ON   ; turn on timer

loop 
goto loop                   ; waiting for the     interrupt

intr                ; main branch: 8 cycles
btfss INTCON, TMR0IF
    retfie
bcf T0CON, TMR0ON

; had to comment this section out - didn't want to work with it - dunno why
;   decf cycle, F
;   btfss STATUS, Z
;       goto idle
;   movlw cyclesPerBaud
;   movwf cycle

checkBitCounter             ; all branches until 'tmr_ret': 16 cycles
movf bitCounter, F
btfss STATUS, Z
    goto rotateBuffer

;startStopBit

movf startStopBit, F
btfss STATUS, Z
    goto stopCopy
movlw 0x8
movwf bitCounter
movf char, W
movwf buffer
rlncf buffer, F
movlw 0x00

transfer
movwf PORTD
decf bitCounter, F
btfsc STATUS, Z
    incf startStopBit, F

tmr_ret         ; 5 cycles (+8 from intr = 13 cycles)
movlw LOW durrOffset    ; dunno why but HIGH has to be reversed with LOW
movwf TMR0H             
movlw HIGH durrOffset   
movwf TMR0L 

bsf T0CON, TMR0ON
retfie

stopCopy
clrf startStopBit
movlw 0xFF
nop
goto transfer

rotateBuffer
rrncf buffer, F
movf buffer, W
nop
nop
nop
nop
goto transfer   

idle        ; 5 cycles (+7 from intr = 12 cycles)
movlw LOW hurrOffset    ; dunno why but HIGH has to be reversed with LOW
movwf TMR0H             
movlw HIGH hurrOffset   
movwf TMR0L 

bsf T0CON, TMR0ON
retfie

end

Der PIC sendet Sachen auf PORTD0, wie es soll, aber es sind nicht die Daten, die er senden soll. Wenn ich den Höchstwert für die Übertragung (mit Realterm) erreiche, ist das gesendete Byte 11011111 oder 10111111 und manchmal 11111111 anstelle von 01111111.

Seit ich angefangen habe, 'Variablen' im Code zu verwenden, ist mir aufgefallen, dass ich den unteren Teil der Timing-Offsets mit HIGH und High-Bytes mit LOW in die TMR0H:L-Register kopieren musste - weiß jemand, warum das so funktioniert? Vielleicht verwechsle ich die Bedeutung dieser Register: Ich habe sie zuvor so verwendet, als ob das TMR0H-Register die signifikanteren 8 Bits des 2-Byte-Zählers enthalten würde - ist das richtig?

Antworten (1)

EMPFANG

Der normale Ansatz zur Implementierung eines asynchronen Softwareempfängers besteht darin, einen Timer-Tick zu haben, der kontinuierlich mit der 3-fachen oder 5-fachen Baudrate läuft (Hinweis: Ungerade Zahlen sind besser als gerade Zahlen). Achten Sie darauf, dass der Eingang bei zwei aufeinanderfolgenden Ticks niedrig ist. Sobald dies beobachtet wurde, beginnen Sie mit dem Sampling der Eingabe bei jedem dritten Tick, bis Sie sie neun weitere Male gesampelt haben. Wenn die Eingabe beim neunten Mal hoch ist, haben Sie ein richtig gerahmtes Zeichen erhalten. Wenn es beim neunten Mal niedrig ist, liegt ein Rahmenfehler vor.

* * * S - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - S
-----______000000111111222222333333444444555555666666777777----
--______000000111111222222333333444444555555666666777777-------

Das obige Zeitdiagramm zeigt, wie die Dinge funktionieren sollten. Die oberste Zeile zeigt, was bei jedem Timer-Tick passiert (jedes Nicht-Leerzeichen ist ein Timer-Tick; das * ist ein Tick, das auf das Startbit wartet, ein S prüft, ob die Leitung immer noch niedrig ist, nachdem es empfangen hat, was wie ein aussah Startbit dar. Die Zahlen 0-7 stellen die Abtastung der Bits 0-7 dar und S ist das Stoppbit. Die unteren beiden Zeilen zeigen die eingehenden Daten in den Fällen, in denen man gerade noch den Anfang eines Startbits erwischt (auf der dritten Sternchen) oder wo man es bei einem Interrupt nur knapp verfehlt (also die Leitung für ein Drittel einer Bitzeit niedrig ist, wenn das Startbit bemerkt wird). Beachten Sie, dass selbst bei diesem Unsicherheitsgrad die Quelle garantiert ist Bit 0 zu senden, wenn es abgetastet wird, und ebenso für den Rest der Bits.

Ein Softwareansatz besteht darin, die Leitung an den angezeigten Unterbrechungen abzutasten und sie bei den mit Strichen markierten zu ignorieren. Eine Alternative besteht darin, die Zeile bei jedem Interrupt blind in ein 32-Bit-Schieberegister abzutasten, indem Sie Folgendes verwenden:

  rrf   headtail,w
  bcf   _tail,3
  btfsc _INPUTPORT,_INPUTBIT
   bsf  _STATUS,_tail,3
  rrf   buff2,w
  movff buff1,buff2
  movff buff0,buff1
  movwf buff0
  rrf   headtail,f

Die letzten vier vom Port empfangenen Bits befinden sich in den Headtail-Bits 3..0. Die 24 Bits davor befinden sich in buff0..buff2, wobei jedes jedes dritte Bit enthält. Die Bits davor befinden sich in den Headtail-Bits 7..4. Wenn man das tut, kann man prüfen, ob Headtail das Bitmuster 00xxxx1x enthält. Wenn dies der Fall ist, kopieren Sie buff1 dorthin, wo Sie die eingehenden Daten haben möchten, kopieren Sie buff1 an die Stelle, an der Sie die Daten haben möchten, ODER headtail mit 11000011 und full buff0..buff2 mit FF. Sonst nichts tun. Dieser Ansatz ist möglicherweise etwas langsamer als das selektive Laden oder Ignorieren der Dateneingabe, kann sich jedoch besser von Framing-Fehlern erholen.

ÜBERTRAGUNG

Senden ist einfacher als Empfangen. Sorgen Sie dafür, dass ein Stück Code einmal pro Bitzeit ausgeführt wird (wenn Ihr Tick die 3-fache Baudrate hat, führen Sie den Code bei jedem dritten Tick aus). Der einfachste Weg, den Code einzurichten, besteht darin, ein paar Bytes zu verwenden, um die Daten zu speichern, die über den Port gesendet werden sollen, einschließlich Start- und Stoppbits. Es gibt eine Vielzahl von Ansätzen, die man verwenden kann. Ein ziemlich vielseitiger wäre:

  ; At start of interrupt
  btfss  TransmitBuffL,0
   bcf   OUTPUT
  btfsc  TransmitBuffL,0
   bsf   OUTPUT

  ; Later, after having handled reception:
  btfss  TransmitBitsLeft,7 ; Assume this counts down to -1 (i.e. 255)
   decfsz TransmitTicks,f
   goto  noTransmit ; Transmit if no bits left, or no ticks
  movlw  3
  movwf  TransmitTicks ; Handle bit transmission every third interrupt
  decf   TransmitBitsLeft,f
  rrf    TransmitBuffH,f
  rrf    TransmitBuffL,f
NoTransmit:

Man kann ein Byte für die Übertragung vorbereiten, wenn das TransmitTicks-Bit 7 gesetzt ist. Wenn dies der Fall ist, platzieren Sie die zu setzende Bitfolge in TransmitBuffH und TransmitBuffL, setzen Sie TransmitTicks auf die Länge des ersten Bits (typischerweise 3) und TransmitBitsLeft auf die Gesamtlänge des Wortes einschließlich Start- und Stoppbits. Etwas wie:

TxByte: ; Sends byte in W.  Assumes TransmitBitsLeft has high bit set
  movwf   TransmitBuffL,f
  movlw   3
  movwf   TransmitTicks
  movwf   TransmitBuffH,f  ; LSB (stop bit) is set.  Upper bits don't matter.
  movlw   9 ; Total frame length (incl. start and stop) minus one
  movwf   TransmitBitsLeft
  bcf     _STATUS,_CARRY ; Start bit should be low
  rlf     TransmitBuffL,f ; Stick start bit in front of what we're sending
  rlf     TransmitBuffH,f

Dieser Ansatz kann normale Datenrahmen unter Verwendung des angegebenen Stils aussenden. Wenn man mehr Stoppbits benötigt, kann man sicherstellen, dass TransmitBuffH genügend Bits gesetzt hat, und TransmitBitsLeft entsprechend erhöhen. Wenn man ein BREAK senden möchte, kann man TransmitBuffL auf 2 und TransmitBitsLeft auf 1 setzen, TransmitTicks auf die gewünschte Länge des Break-Signals (in Ticks) setzen. Um die Leitung für eine bestimmte Zeitspanne im Leerlauf zu halten, setzen Sie TransmitBuffL auf 1, TransmitBitsLeft auf 0 und TransmitTicks auf die gewünschte Leerlaufzeit (in Ticks).

Vielen Dank für die Tipps - ich werde das bei der Implementierung von Methoden zum Empfangen von Daten auf jeden Fall berücksichtigen. Ich bin jedoch noch nicht über das Stadium des Datenversands hinaus und habe mein Problem vielleicht nicht klar genug formuliert. Also versuche ich, Daten vom PIC an einen PC zu senden. Der PC lauscht mit Realterm auf einem richtigen Port, wobei Baud auf 9600 eingestellt ist. Ich habe versucht, das Bild so zu programmieren, dass es auch Daten mit einer Baudrate von 9600 sendet. Trotzdem funktioniert es nicht. Ich weiß nicht warum und ich hatte gehofft, jemand könnte den Fehler in meinem Code erkennen. Danke
Für die Übertragung würde ich vorschlagen, dass Sie damit beginnen, dass ein Stück Code einmal pro Bitzeit ausgeführt wird. Wenn Sie eine Leitung mit dieser Geschwindigkeit umschalten, sollte der PC einen kontinuierlichen Strom von "U" -Zeichen sehen. Sobald Sie das tun können, verwenden Sie vielleicht etwas, das ich oben gezeigt habe.
Ok, ich glaube, ich habe es geschafft, den Fehler zu lokalisieren - zumindest einen von ihnen. Als ich nämlich den Ausgang von PORTD0 (das ist mein beabsichtigter PIC-Tx-Port) an ein Oszilloskop angeschlossen habe, habe ich durch Senden eines Bytes 01010101 festgestellt, dass die Dauer eines einzelnen Bits etwa 6,2 us beträgt, was eine Baudrate von etwa ergibt 161290,32. Dies ist bei weitem nicht der erwartete Wert von 9600. Als ich versuchte, mit dem Fixing herumzuspielen, bemerkte ich auch, dass es egal war, welche Art von Offset ich in die Register TMR0H und TMR0L schrieb - über die ich aber das steuern konnte Intervall zwischen tmr0-Interrupts. Irgendwelche Vorschläge woran das liegen kann?
Richtig, mir ist also aufgefallen, dass ich das TMR0IF im INTCON-Register nicht gelöscht habe - deshalb ging es wieder zurück, sobald ich die Interrupt-Funktion beendet hatte. So, jetzt kann ich die Timings der Interrupts steuern. Aber etwas scheint immer noch seltsam. Das gesendete Byte wird furchtbar verzerrt - vielleicht muss ich etwas mehr Zeit mit den Timings verbringen. Aber lassen Sie uns den Sendevorgang zusammenfassen: zuerst - setzen Sie den PORTD0-Pin auf 1 ('Markierung'), bevor Sie das Datensatz-Startbit 0 ('Leerzeichen') senden, senden Sie Bits - niedriges Bit zuerst, hohes zuletzt, senden Sie ein Stoppbit 1, wiederholen. Ist das in Ordnung? Vielleicht sollte ich das signifikanteste Bit senden?
@drinker: Die Leitung sollte hoch im Leerlauf sein. Jedes Byte sollte mit einer Bitzeit niedrig beginnen, gefolgt von den Datenbits (LSB zuerst); die Leitung sollte für ein absolutes Minimum von einer halben Bitzeit zwischen den Bytes im Leerlauf sein (der Empfänger muss die Leitung in dem Moment, in dem er es für 9,5 Bitzeiten nach dem Startbit hält, hoch sehen, was geringfügig von dem abweichen kann, was der Sender für 9,5 hält Bitzeiten nach dem Startbit). In der Praxis sollte man normalerweise versuchen sicherzustellen, dass zwischen den Bytes mindestens eine volle Bitzeit im Leerlauf ist, mit einer Ausnahme: Wenn man kontinuierlich empfängt und erneut sendet ...
...Daten, und wenn die Baudratenuhr des Senders relativ zu der eigenen etwas schnell ist (z. B. 1%), dann kann man alle 9,9 Bitzeiten ein Byte empfangen. In der Zeit, die benötigt wird, um 1000 Bytes mit dieser Geschwindigkeit zu empfangen, könnte man nur 990 senden. Um Datenverlust zu vermeiden, müsste man einige Bytes mit etwas kurzen Stoppbits senden; Leider unterstützen dies nicht alle UARTs.