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?