I got this code from Mike McLaren (K8LH) for simple RS232 sending (not duplex) to an LCD. I have used it frequently since when debugging with only 1 signal wire. It's Assembly for the mid-range, but it may help. The delay routine is also from Mike McLaren. It may help. Of course, you can use any reasonably accurate delay routine. Please credit him, if you use it.
;********************************************************************************
; 1) DelayCy(xx*msecs) or (xx*usecs) subsystem macro generator
; 2) Provided by Mike McLaren K8LH
; 3) 03.18.15 Checked and confirmed this is original version for 14-bit cores
;********************************************************************************
clock equ 8 ; 4, 8, 12, 16, or 20 MHz clock
usecs equ clock/4 ; cycles/microsecond multiplier
msecs equ clock/4*1000 ; cycles/millisecond multiplier
DelayCy macro delay ; 12..327690 cycle range
movlw high((delay-11)/5)+1
movwf delayhi
movlw low((delay-11)/5)
call uDelay-((delay-11)%5)
endm
;********************************************************************************
; SUBROUTINES
;********************************************************************************
; DelayCy() subsystem "uDelay"
;********************************************************************************
nop ; entry for (delay-12)%5 == 4
nop ; entry for (delay-12)%5 == 3
nop ; entry for (delay-12)%5 == 2
nop ; entry for (delay-12)%5 == 1
uDelay addlw -1 ; 5 cycle loop
skpc ;
decfsz delayhi,f ;
goto uDelay ;
return ;
;******************************************************************************
; PUT232
; Put232 (9600 baud) -- Mike McLaren, K8LH (Jan-09)
; Change "RA0" to the pin used for RS232 serial out.
; Although LATA is used, writes to PORTA are duplicated in LATA.
; No requirement to change banks.
; The "-10" adjustment to DelayCy (i.e., 100*usecs-10)is in Tcy, not time.
; Added to account for the steps between bit changes. At 9600 Baud, it is
; unnecessary. For example, with MCU at 8 MHz,the macro generates a bit width
; of 109usec witout it and 104usec with it. At 4 MHz, the difference is
; 114usec w/o it and 104usec with it.
;******************************************************************************
Put232 ;must be in Bank0 or Bank2
movlb 0 ;only necessary to call from any bank
movwf txbyte ;save Tx data byte (not preserved)
movlw 10 ;1 start + 8 data + 1 stop bit
movwf bit_ctr ;setup bit counter
bcf STATUS,0 ;STATUS,0 used as flag, C = 0 (start bit)
DelayCy (104*usecs-10) ;104 usecs = Baud 9600, "-10" adj for housekeeping
goto SendBit ;send start bit
NextBit
DelayCy (104*usecs-10)
bsf STATUS,0 ;always shift in a 'stop' bit
rrf txbyte,f ;put data bit in Carry
SendBit
movf LATA,w ;read port latch
iorlw 1<<RA0 ;set Serout pin to 1
btfss STATUS,0 ;if data bit from rrf = 1 skip, else
xorlw 1<<RA0 ;XOR WREG with 1 at the bit position of Serout
movwf LATA ;precise update intervals
decfsz bit_ctr,f ;done? yes, skip, else
goto NextBit ;send next bit
retlw 0 ;returns in Bank0
;*******************************************************************************