# PIC interrupt based soft UART timing trouble

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?

RECEIVING

The normal approach for implementing a software asynchronous receiver is to have a timer tick that runs continuously at 3x or 5x the baud rate (note: odd numbers are better than even numbers). Watch the input to be low on two consecutive ticks. Once that is observed, start sampling the input on every third tick, until you have sampled it nine more times. If the input is high on the ninth time, you've received a properly-framed character. If it's low on the ninth time, you have a framing error.

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


The above timing diagram shows how things should work. The top line shows what happens on each timer tick (each non-blank is a timer tick; the * is a tick waiting for the start bit, a S is checking to ensure that the line is still low after having received what looked like a start bit. The numbers 0-7 represent the sampling of bits 0-7, and S is the stop bit. The bottom two lines show the incoming data in the cases where one just barely catches the beginning of a start bit (on the third asterisk), or where one just barely misses catching it on one interrupt (so the line's been low for a third of a bit time by the time the start bit is noticed). Note that even with that level of uncertainty, the source is guaranteed to be sending bit 0 when it's sampled, and likewise for the rest of the bits.

One software approach is to sample the line at the indicated interrupts and ignore it on the ones marked with dashes. An alternative is to blindly sample the line on every interrupt into a 32-bit shift register, using something like:

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


The last four bits received from the port will be in headtail bits 3..0. The 24 bits before those will be in buff0..buff2, with each one holding every third bit. The bits before that will be in headtail bits 7..4. If one does that, one can check whether headtail holds the bit pattern 00xxxx1x. If it does, copy buff1 to wherever you want the incoming data, copy buff1 to the spot you want the data, OR headtail with 11000011, and full buff0..buff2 with FF. Otherwise do nothing. This approach may be a tiny bit slower than selectively loading or ignoring the data input, but may be better able to recover from framing errors.

TRANSMISSION

Transmission is easier than reception. Arrange for a piece of code to run once per bit time (if your tick is at 3x the baud rate, run the code every third tick). The simplest way to set up the code is to use a couple of bytes to hold the data that should be sent out the port, including start and stop bits. There are a variety of approaches one may use. A rather versatile one would be:

  ; 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:


One may prepare a byte for transmission when TransmitTicks bit 7 is set. When that is the case, place the bit sequence to be set into TransmitBuffH and TransmitBuffL, set TransmitTicks to the length of the first bit (typically 3), and TransmitBitsLeft to the total length of the word including start and stop bits. Something like:

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


This approach can send out normal data frames using the style indicated. If one needs more stop bits, one may make sure TransmitBuffH has enough bits set, and increase TransmitBitsLeft appropriately. If one wants to send out a BREAK, one may set TransmitBuffL to 2 and TransmitBitsLeft to 1, set TransmitTicks to the desired length of the break signal (in ticks). To idle the line for a certain length of time, set TransmitBuffL to 1, TransmitBitsLeft to 0, and set TransmitTicks to the desired idle time (in ticks).

• Thanks very much for the tips - I will certainly take that into consideration whilst implementing receiving data methods. I am however, not yet past the stage of sending data and perhaps didn't state my problem clear enough. So I am trying to send data from the PIC to a PC. The PC listens on a proper port using Realterm, with a baud set to 9600. I've tried programming the pic to also send data with a 9600 baud rate. Yet, it does not work. I don't know why and I was hoping somebody could spot the error in my code. Thanks – drinker May 15 '13 at 16:50
• For transmission, I would suggest that you start by arranging for a piece of code to run once per bit time. If you toggle a wire at that rate, the PC should see a continuous stream of "U" characters. Once you can do that, perhaps use something like I show above. – supercat May 15 '13 at 17:44
• Ok, I think I managed to pinpoint the error - at least one of them. Namely, as I plugged the output of PORTD0 (which is my intended PIC Tx port) to a oscilloscope, I noticed, by sending a byte 01010101, that the duration of a single bit is about 6,2us which gives a baud rate of about 161290,32. This is nowhere near the expected value of 9600. As I tried to play around with fixing that I noticed also that it didn't matter what kind of offset I wrote into registers TMR0H and TMR0L - thru which, I though, I could control the interval between tmr0 interrupts. Any suggestions why that may be? – drinker May 16 '13 at 3:23
• Right, so I noticed that I didn't clear the TMR0IF in the INTCON register - so that's why as soon as I exited the interrupt function, it went back in again. So, now I can control the timings of the interrupts. But something still seems odd. The byte being sent gets terribly distorted - maybe i need to spend some more time on the timings. But lets summarize the sending process: first - set the PORTD0 pin to 1 ('mark'), before sending data set start bit 0 ('space'), send bits - low bit first, high last, send one stop bit 1, repeat. Is that ok? Maybe I should be sending the most significant bit? – drinker May 16 '13 at 7:02
• @drinker: The line should idle high. Each byte should start with one bit time low, followed by the data bits (LSB first); the line should idle for an absolute minimum of half a bit time between bytes (the receiver must see the line high at the moment it considers to be 9.5 bit times after the start bit, which may differ slightly from what the transmitter considers to be 9.5 bit times after the start bit). In practice, one should usually try to ensure there's at least one full bit time of idleness between bytes, with one exception: if one is continuously receiving and retransmitting... – supercat May 16 '13 at 16:40