I've tried to implement a software UART on a PIC18F452 using TIMER0 interrupts and I can't get the timing working.
I'm using MPLAB ASM for compilation and the PICkit2 for programming.
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
The PIC is sending stuff on PORTD0, as it's supposed to, but it's not the data it's supposed to be sending. When I peak what is transmitted (with Realterm), the byte being sent is 11011111 or 10111111 and sometimes 11111111 instead of 01111111.
Also, since I started using 'variables' in the code I noticed that I had to copy the lower part of timing offsets with HIGH and high bytes with LOW into the TMR0H:L registers - anybody knows why it works that way? Maybe I'm confusing the significance of these registers: I used them before as if the TMR0H register would hold the more significant 8 bits of the 2 byte counter - is that correct?