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?
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).
Trinker
Superkatze
Trinker
Trinker
Superkatze
Superkatze