From: Werner Johansson Date: Tue, 4 Mar 2003 22:08:11 +0000 (-0800) Subject: v0.3 - Implementing more Unilink commands and RingIndicator control (to wake the... X-Git-Url: http://git.xnk.nu/?p=wj-unilink.git;a=commitdiff_plain;h=7f22dea88106ff53b5170855909b02a8332c015b v0.3 - Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep) Signed-off-by: Werner Johansson --- diff --git a/wj-uni.asm b/wj-uni.asm index 130df18..f2a186d 100644 --- a/wj-uni.asm +++ b/wj-uni.asm @@ -1,7 +1,7 @@ title "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 2003" subtitl "Definitions" list c=150,P=16F870,R=DEC,F=inhx8m - include "p16f870.inc" ; Standard equates & Macros + include "p16f870.inc" ; Standard equates & Macros ERRORLEVEL 1,-302 ; Get rid of those annoying 302 msgs! @@ -12,22 +12,23 @@ ;---------------------------------------------------------------- ; TODO ;---------------------------------------------------------------- -; BUSON OUT control isn't implemented +; Fix the DelayW routine so it actually delays W/10 ms... ; No checksum checking is done on incoming packets -; Investigate whether we actually have to save PCLATH in ISH, maybe save FSR? +; Investigate whether I actually have to save PCLATH in ISH, maybe save FSR? ; Move RS232 code into ISH ; Check Overrun errors from the UART -; Implement Bus re-initialize command -; Implement lots of other Unilink commands +; Implement lots of other Unilink commands (Text display, time display etc.) +; Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..) ;---------------------------------------------------------------- ; HISTORY ;---------------------------------------------------------------- ; Version ; +; 0.3 Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep) ; 0.2 First attempt at responding to the Anyone command ; 0.1 Receives Unilink data OK, relays it to serial -; 0.0 Very first "Fucking No Work!" version +; 0.0 Very first "F**king No Work!" version ;---------------------------------------------------------------- ; I/O LAYOUT @@ -37,7 +38,7 @@ ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining) ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin) ; Unilink RST (lilac) connected to RA4 -; LCD RS connected to pin RB1 +; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit) ; LCD RW connected to pin RB2 ; LCD E connected to pin RB3 ; LCD DB4-DB7 connected to RB4-RB7 @@ -110,7 +111,7 @@ IRQW equ 7fh ; subtitl "Startup" page ;---------------------------------------------------------------- -; Power up/Reset starting point [den rulerar] +; Power up/Reset starting point org 0 ; Start at the beginning of memory (the reset vector) call Bootstrap ; Call Flash Load routine @@ -121,12 +122,16 @@ IRQW equ 7fh ; subtitl "IRQ Handler" ;---------------------------------------------------------------- ; Interrupt handler always starts at addr 4 -; In order to save one instruction cycle we put the actual code here directly instead of a goto instruction +; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction. +; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR +; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to +; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low +; again after that. org 4 ; ISR vector is at address 4 movwf IRQW ; Save W swapf STATUS,w ; Get the status register into w - clrf STATUS ; Zero out the status reg, gives us Bank0 all the time + clrf STATUS ; Zero out the status reg, gives Reg Bank0 movwf IRQSTATUS ; Store the STATUS reg movf PCLATH,w ; Get the PCLATH reg movwf IRQPCLATH ; And store it @@ -136,16 +141,16 @@ IRQW equ 7fh ; btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK) goto IRQNotINT ; No it's not, check the other sources -; If there's activity on the clock line (the clock goes high) we stay in here until we have clocked eight bits -; - this saves us a lot of context switching (and it's just a few hundred cpu cycles after all (20us*8 bits= -; 160us=800 instruction cycles (5 MIPS @ 20MHz), not even a problem for serial input if we're not getting more than -; 6250 bytes per second from the UART, and the 2-byte FIFO somehow fills up (this should be impossible even @ 115200 -; as we're only calling this blocking INT handler a maximum of 1000 times per second, halting INT's for 1/6250 of a second, -; this gives the CPU ample of time to deal with all bytes from the USART. I'm checking the OERR (Serial Overrun) bit -; to catch this though.. Note that this piece of code does both TX and RX at the same time (in order to receive packets -; one has to make sure that the packet buffer is zeroed out before entering here, otherwise collisions will occur.. -; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's a -; interrupt driven software based USART - by trigging the interrupt on the rising edge we buy us some extra time here +; If there's activity on the clock line (the clock goes high) the CPU will stay in here until eight bits have been clocked in +; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction +; cycles (5 MIPS @ 20MHz), not even a problem for serial input if it's not receiving more than 6250 bytes per second, and the +; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of +; 1000 times per second, halting INT's for 1/6250 of a second - this gives the CPU ample of time to deal with all bytes from +; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX +; and RX at the same time (in order to receive packets one has to make sure that the packet buffer is zeroed out before entering +; here, otherwise collisions will occur.. +; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an +; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..) movlw 8 ; Loop through the 8 bits @@ -173,14 +178,14 @@ IRQINTCLKWaitLow btfsc PORTB,0 ; Wait for clock to go low goto IRQINTCLKWaitLow - clrc ; Clear carry (this way the DataStore byte doesn't have to be cleared before) + clrc ; Clear carry btfss PORTC,3 ; Test DATA setc ; Set carry if data is LOW (data is inverted!) - rlf INDF,f ; Shift it into our accumulator + rlf INDF,f ; Shift it into the "accumulator" decfsz DataCount,f ; Loop once more perhaps? goto IRQINTCLKWaitHigh ; Yes, again! - goto IRQINTRecvDone ; No we're done, don't check for clock to go high again + goto IRQINTRecvDone ; No it's done, don't check for clock to go high again IRQINTCLKWaitHigh btfss PORTC,2 ; Check for BUSON @@ -190,7 +195,7 @@ IRQINTCLKWaitHigh goto IRQINTBitSet ; Loop again ; Successfully received a byte here, run it through a state machine to figure out what to do -; (several possibilites exists here: +; (several possibilites exists here): ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero ; If receive counter is zero and the received byte is a zero byte, discard it ; Otherwise store the byte in our receive buffer and increment receive counter @@ -203,7 +208,7 @@ IRQINTCLKWaitHigh ; the packet and take appropriate action. IRQINTRecvDone - movf UnilinkTXRX,w ; Find out which byte we got + movf UnilinkTXRX,w ; Find out which byte # that was received andlw 0fh ; Mask bnz IRQINTRecvNotFirst ; Not the first byte movf UnilinkRAD,w ; Get the first byte received @@ -230,10 +235,10 @@ IRQINTRecvNotCMD1 andlw 0fh ; and mask - this results in a zero result when finished receiving bnz IRQINTRecvIncomplete ; Packet not ready yet -; Here we actually have received a packet, should check the checksum(s) as well, but I don't care right now +; Here a packet is actually received a packet, should check the checksum(s) as well, but I don't care right now ; (I need music in my car! :)) -; This is inefficient, I know, I'll improve it later... (Not that it matters, we have plenty of time here -; (there can't be any more communication for another 4.8ms)) +; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here +; (there won't be any more communication for at least another 4.8ms)) ; Unilink command parser: @@ -242,6 +247,18 @@ IRQINTRecvNotCMD1 xorlw 01h bnz IRQINTParseNot01 +; Check for 01 00 (Bus Re-Initialization) + movf UnilinkCMD2,w +; xorlw 00h + bnz IRQINTParseNot0100 + + clrf UnilinkID ; Clear the existing Unilink ID, if any + bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID + + goto IRQINTParseComplete ; Don't send any reply to this + +IRQINTParseNot0100 + ; Check for 01 02 (Anyone) movf UnilinkCMD2,w xorlw 02h @@ -271,7 +288,7 @@ IRQINTRecvNotCMD1 movlw 0deh ; Hard coded parity 2 (!) movwf UnilinkData5 clrf UnilinkData6 - goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send what's in the buffer next time + goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet IRQINTParseNot0102 @@ -281,8 +298,8 @@ IRQINTParseNot0102 bnz IRQINTParseNot0112 movf UnilinkRAD,w - xorwf UnilinkID,w ; Is it for us? - bnz IRQINTParseNot0112 ; nope + xorwf UnilinkID,w ; Is it for me? + bnz IRQINTParseNot0112 ; Nope clrf UnilinkParity1 movlw 10h ; Sending to Master @@ -295,14 +312,14 @@ IRQINTParseNot0102 addwf UnilinkParity1,f movwf UnilinkCMD1 - movlw 80h ; We're idle unless selected + movlw 80h ; Idle unless selected btfsc UnilinkSelected,7 clrw addwf UnilinkParity1,f movwf UnilinkCMD2 clrf UnilinkData6 - goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send! + goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet IRQINTParseNot0112 @@ -313,7 +330,9 @@ IRQINTParseNot01 xorlw 02h bnz IRQINTParseNot02 - movf UnilinkRAD,w ; Get the ID the master has given us + bsf BUSON_OUT_BIT ; Now activate the cascade BUSON pin, to allow others to be discovered + + movf UnilinkRAD,w ; Get the ID the master has given me movwf UnilinkID ; Store my id movf UnilinkCMD2,w ; Get the bitmask movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding) @@ -338,7 +357,7 @@ IRQINTParseNot01 movlw 24h addwf UnilinkParity2M,f movwf UnilinkData1 - movlw 2ch ; My internal MD sends 1c here... (external/internal or 1/10 disc difference?) + movlw 2ch ; My internal MD sends 1c here... (external/internal difference) addwf UnilinkParity2M,f movwf UnilinkData2 movlw 22h @@ -349,30 +368,50 @@ IRQINTParseNot01 movwf UnilinkData4 clrf UnilinkData6 - goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send! + goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet IRQINTParseNot02 +; Check for CMD1 = 87h (Power control) + movf UnilinkCMD1,w + xorlw 087h + bnz IRQINTParseNot87 + +; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on) + btfsc UnilinkCMD2,3 + goto IRQINTParse87PowerOn + + bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels) + goto IRQINTParseComplete + +IRQINTParse87PowerOn + bcf RS232_RI_BIT ; Clear this to make RI pin go high (waking the computer) + goto IRQINTParseComplete + +IRQINTParseNot87 + ; Check for CMD1 = f0h (Source Select) movf UnilinkCMD1,w xorlw 0f0h bnz IRQINTParseNotF0 movf UnilinkCMD2,w - xorwf UnilinkID,w ; Check if it's selecting us + xorwf UnilinkID,w ; Check if it's selecting me bnz IRQINTParseF0Deselect bsf UnilinkSelected,7 ; Now we're selected - goto IRQINTParseNotF0 + goto IRQINTParseComplete IRQINTParseF0Deselect bcf UnilinkSelected,7 ; Now we're de-selected - goto IRQINTParseNotF0 + goto IRQINTParseComplete IRQINTParseNotF0 -; We end up here when parsing is complete and we're not interested in sending any reply back to the master +IRQINTParseComplete + +; The CPU ends up here when parsing is complete and it's not interested in sending any reply back to the master ; (that's why we clear out all the packet buffer bytes) ; TODO: Replace this with an FSR access to save space and make the code neater @@ -398,28 +437,31 @@ IRQINTParseBypassClear movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD - clrf UnilinkCmdLen ; No command length as we're waiting for a new packet + clrf UnilinkCmdLen ; No command length while waiting for a new packet IRQINTRecvIncomplete IRQINTRecvNullByte movf INDF,w - movwf DataStore ; Store it so our non-irq code can snoop + movwf DataStore ; Store it so the non-irq code can snoop IRQAfterINT - bcf INTCON,INTF ; Clear our IRQ source bit so we can receive new bits again + bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again IRQNotINT ; Finally restore CPU state and return from the ISR + +; If I have to save the FSR in the beginning I also need to restore it here... + movf IRQPCLATH,w - movwf PCLATH ; Restore PCLATH + movwf PCLATH ; Restore PCLATH swapf IRQSTATUS,w - movwf STATUS ; Restore STATUS + movwf STATUS ; Restore STATUS swapf IRQW,f - swapf IRQW,w ; Restore W - retfie ; Interrupt return + swapf IRQW,w ; Restore W + retfie ; Interrupt return subtitl "Main loop" @@ -431,247 +473,188 @@ IRQNotINT StartUpText1 DT "----- WJ UniLink" -LookUp movwf PCL ; Go to it +LookUp movwf PCL ; Go to it (this assumes PCLATH == 00h) ;---------------------------------------------------------------- ; Main program begins here. [Called after bootloader, lcdinit and irqinit...] - org 100h + org 100h ; Maybe not force this to a specific address later Main - bsf RS232_RI_BIT ; We want RI to be high (inverted logic, not set) - bcf BUSON_OUT_BIT ; But we don't want BUSON_OUT on just yet, we need to be appointed first - - bsf STATUS,RP0 ; Select bank 1 - - bcf RS232_RI_BIT ; Both bits should be outputs at least - bcf BUSON_OUT_BIT ; - -; bcf STATUS,RP0 -; bsf STATUS,RP0 - - bsf TXSTA,TXEN ; Enable UART TX - bcf STATUS,RP0 ; Back to bank 0 - - bsf RCSTA,SPEN ; Enable serial port - bsf RCSTA,CREN ; Enable UART RX - -; Replace this with an FSR access - clrf UnilinkSelected - clrf UnilinkID - clrf UnilinkBit - clrf UnilinkCmdLen - clrf UnilinkRAD - clrf UnilinkTAD - clrf UnilinkCMD1 - clrf UnilinkCMD2 - clrf UnilinkParity1 - clrf UnilinkData1 - clrf UnilinkData2 - clrf UnilinkData3 - clrf UnilinkData4 - clrf UnilinkData5 - clrf UnilinkData6 - clrf UnilinkData7 - clrf UnilinkData8 - clrf UnilinkData9 - clrf UnilinkParity2 - clrf UnilinkZero + movlw StartUpText1 ; Show something on the LCD + call TxLCD16B - clrf DataStore - movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer - movwf UnilinkTXRX ; Store it +MainLoop - movlw StartUpText1 - call TxLCD16B -retry - - bcf LCD_RS_BIT ;Command mode - movlw 80h ;DisplayRam 0 + bcf LCD_RS_BIT ; LCD Command mode + movlw 80h ; DisplayRam 0 call TxLCDB - bsf LCD_RS_BIT + bsf LCD_RS_BIT movlw '0' - btfsc PORTA,4 ; Test RST + btfsc PORTA,4 ; Test RST movlw 'R' call TxLCDB movlw '0' - btfsc PORTB,0 ; Test CLK + btfsc PORTB,0 ; Test CLK movlw 'C' call TxLCDB movlw '0' - btfsc PORTC,2 ; Test BUSON-IN + btfsc PORTC,2 ; Test BUSON-IN movlw 'B' call TxLCDB movlw '0' - btfsc PORTC,3 ; Test DATA + btfsc PORTC,3 ; Test DATA movlw 'D' call TxLCDB movf UnilinkCmdLen,w - bz DontPrintCmd + bz MainDontPrintCmd addlw '0' call TxLCDB -DontPrintCmd + +MainDontPrintCmd movf DataCount,w ; Load bit counter (if 0 then byte is available) skpz - goto retry + goto MainLoop decf DataCount,f ; Set it non-zero movf DataStore,w call BootTXB ; Send to terminal - goto retry + goto MainLoop +;---------------------------------------------------------------- +; IRQInit - Sets up the IRQ Handler -; movlw StartUpText1 -; call TxLCD16B -; call LongDelay - -; bsf PORTA,4 ; turn off LED +IRQInit -; movlw StartUpText2 -; call TxLCD16B -; call LongDelay +; Start with clearing the Unilink packet buffer before enabling any interrupts, otherwise the first packet might become corrupt +; TODO: Replace this with FSR access + clrf UnilinkSelected + clrf UnilinkID + clrf UnilinkBit + clrf UnilinkCmdLen + clrf UnilinkRAD + clrf UnilinkTAD + clrf UnilinkCMD1 + clrf UnilinkCMD2 + clrf UnilinkParity1 + clrf UnilinkData1 + clrf UnilinkData2 + clrf UnilinkData3 + clrf UnilinkData4 + clrf UnilinkData5 + clrf UnilinkData6 + clrf UnilinkData7 + clrf UnilinkData8 + clrf UnilinkData9 + clrf UnilinkParity2 + clrf UnilinkZero -; bcf PORTA,4 ; turn on LED + clrf DataStore + movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer + movwf UnilinkTXRX ; Store it -; movlw StartUpText3 -; call TxLCD16B -; call LongDelay +; Fix the output state of RI and BUSON_OUT to a safe default -; goto retry + bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output) + bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first + bsf STATUS,RP0 ; Reg bank 1 -;---------------------------------------------------------------- -; IRQInit - Sets up the IRQ Handler + bcf RS232_RI_BIT ; Both bits should be outputs + bcf BUSON_OUT_BIT ; -IRQInit - bsf STATUS,RP0 ; Reg bank 1 +; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use... ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge + bsf INTCON,INTE ; Enable the RB0/INT bsf INTCON,GIE ; Enable global interrupts + + bsf TXSTA,TXEN ; Enable UART TX + bcf STATUS,RP0 ; Back to bank 0 + + bsf RCSTA,SPEN ; Enable serial port + bsf RCSTA,CREN ; Enable UART RX + return ;---------------------------------------------------------------- ; Initialize LCD Controller... LCDInit - clrf PORTB - bsf STATUS,RP0 ; Hi Bank - movlw 001h ; All but RB0 are outputs. - movwf TRISB ; Yep - bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up - bcf STATUS,RP0 ; Restore Lo Bank + clrf PORTB ; First clear PortB data register + bsf STATUS,RP0 ; Reg bank 1 + movlw 001h ; All but RB0 are outputs. + movwf TRISB ; -; bcf PORTA,4 ; turn on LED + bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up + bcf STATUS,RP0 ; Restore Reg bank 0 -; movlw 44 ; Should be 16ms delay - movlw 255 ; Should be 16ms delay +; This is a standard reset sequence for the LCD controller + + movlw 160 ; Need to delay for at least 15ms, let's go for 16ms delay call DelayW - movlw 3 ; Write 3 to the LCD - call TxLCD ; Send to LCD -; movlw 12 ; Should be 5ms delay - movlw 255 ; Should be 16ms delay + movlw 3 ; Write 3 to the LCD + call TxLCD ; Send to LCD + movlw 50 ; Need to delay for at least 4.1ms, let's go for 5ms delay call DelayW - movlw 3 ; Write 3 to the LCD + movlw 3 ; Write 3 to the LCD call TxLCD -; movlw 12 ; Should be 16ms delay - movlw 255 ; Should be 16ms delay + movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay call DelayW - movlw 3 ; Write 3 to the LCD + movlw 3 ; Write 3 to the LCD call TxLCD -; movlw 44 - movlw 255 ; Should be 16ms delay + movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay call DelayW - movlw 2 ;\ - call TxLCD ; | 4-bit interface -; movlw 55 ; | After this we are ready to ROCK! - movlw 255 ; Should be 16ms delay - call DelayW ;/ + movlw 2 ; 4-bit interface requested + call TxLCD ; + movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay + call DelayW ; -; bsf PORTA,4 ; turn off LED +; Reset sequence ends here +; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits - movlw 28h ; Some random commands :))) - call TxLCDB + movlw 28h ; Function Select + 4-bit bus + 2-line display + call TxLCDB - movlw 0ch ; hmmm - call TxLCDB + movlw 0ch ; Display Control + LCD On (No cursor) + call TxLCDB - movlw 01h ; hmmm - call TxLCDB + movlw 01h ; Clear Display + call TxLCDB - movlw 06h ; hmmm - call TxLCDB + movlw 06h ; Auto Increment cursor position + call TxLCDB return ;---------------------------------------------------------------- -; LongDelay - Well, guess that for yourself... - -LongDelay -; btfss PORTB,6 ; Talk to da PC? -; goto PCTalk ; Oh yeah... - - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - return - -;---------------------------------------------------------------- ; TxLCD16B ; Send a string to the LCD. TxLCD16B movwf Icount - bcf LCD_RS_BIT - movlw 80h ;DisplayRam 0 + bcf LCD_RS_BIT + movlw 80h ; DisplayRam 0 call TxLCDB - bsf LCD_RS_BIT + bsf LCD_RS_BIT call TxLCD8B - bcf LCD_RS_BIT - movlw 80h+40 ;DisplayRam 40 (row 2) + bcf LCD_RS_BIT + movlw 80h+40 ; DisplayRam 40 (row 2) call TxLCDB - bsf LCD_RS_BIT + bsf LCD_RS_BIT call TxLCD8B return @@ -680,14 +663,14 @@ TxLCD16B ; Send a string to the LCD. TxLCD8B -; movwf Icount ; Icount = W +; movwf Icount ; Icount = W movlw 8 - movwf e_LEN ; Move to e_LEN + movwf e_LEN ; Move to e_LEN -Txm_lp movf Icount,w ; get the byte +Txm_lp movf Icount,w ; get the byte call LookUp - incf Icount,f ; ...else ++Icount (table index) - call TxLCDB ; Send out the byte + incf Icount,f ; ...else ++Icount (table index) + call TxLCDB ; Send out the byte decfsz e_LEN,f goto Txm_lp return @@ -696,39 +679,39 @@ Txm_lp movf Icount,w ; get the byte ; TxLCDB - send a byte to the LCD TxLCDB - movwf TxTemp ; Store byte to send for a while... + movwf TxTemp ; Store byte to send for a while... - bcf temp,0 ; Clear my temp bit - btfss LCD_RS_BIT ; Check if we try the correct reg + bcf temp,0 ; Clear my temp bit + btfss LCD_RS_BIT ; Check if we try the correct reg goto RxNoProb bcf LCD_RS_BIT - bsf temp,0 ; Indicate RS change + bsf temp,0 ; Indicate RS change RxNoProb NotReady - call RxLCDB ; Receive byte from LCD, status reg + call RxLCDB ; Receive byte from LCD, status reg andlw 80h - btfss STATUS,Z ; If the bit was set, the zero flag is not + skpz ; If the bit was set, the zero flag is not goto NotReady - btfsc temp,0 ; If we had to clear RS reset it now + btfsc temp,0 ; If we had to clear RS reset it now bsf LCD_RS_BIT - swapf TxTemp,w ; Hi nibble of data to send in lo w bits - call TxLCD ; Send them first... - movf TxTemp,w ; Then we have the low nibble in low w bits - call TxLCD ; And send that one as well + swapf TxTemp,w ; Hi nibble of data to send in lo w bits + call TxLCD ; Send them first... + movf TxTemp,w ; Then we have the low nibble in low w bits + call TxLCD ; And send that one as well return ;---------------------------------------------------------------- ; RxLCDB - recv a byte from the LCD RxLCDB - call RxLCD ; Receive the high nibble + call RxLCD ; Receive the high nibble movwf LCDWTmp - swapf LCDWTmp,f ; Swap it back to file - call RxLCD ; Receive the low nibble - addwf LCDWTmp,w ; Put the nibbles together and return in W + swapf LCDWTmp,f ; Swap it back to file + call RxLCD ; Receive the low nibble + addwf LCDWTmp,w ; Put the nibbles together and return in W return @@ -736,90 +719,100 @@ RxLCDB ; TxLCD - send a nibble to the LCD TxLCD - movwf LCDWTmp ; Write nibble to tmp - bcf LCD_DB4_BIT ; Clear previous data - bcf LCD_DB5_BIT ; - bcf LCD_DB6_BIT ; - bcf LCD_DB7_BIT ; + movwf LCDWTmp ; Write nibble to tmp + bcf LCD_DB4_BIT ; Clear previous data + bcf LCD_DB5_BIT ; + bcf LCD_DB6_BIT ; + bcf LCD_DB7_BIT ; - btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT + btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT bsf LCD_DB4_BIT - btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT + btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT bsf LCD_DB5_BIT - btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT + btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT bsf LCD_DB6_BIT - btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT + btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT bsf LCD_DB7_BIT - bsf LCD_E_BIT ; And set E to clock the data into the LCD module - nop ; Let it settle - bcf LCD_E_BIT ; And clear the Enable again. - return ; Returns without modifying W + bsf LCD_E_BIT ; And set E to clock the data into the LCD module + nop ; Let it settle + bcf LCD_E_BIT ; And clear the Enable again. + return ; Returns without modifying W ;---------------------------------------------------------------- ; RxLCD - recv a nibble from the LCD RxLCD - clrw ; Clear W register, return data in lower 4 bits + clrw ; Clear W register, return data in lower 4 bits - bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed + bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed - bsf LCD_DB4_BIT ; This sets the port bit as an input + bsf LCD_DB4_BIT ; This sets the port bit as an input bsf LCD_DB5_BIT bsf LCD_DB6_BIT bsf LCD_DB7_BIT - bcf STATUS,RP0 ; Back at reg bank 0 - bsf LCD_RW_BIT ; Set Read mode for the LCD - bsf LCD_E_BIT ; And set E to clock the data out of the LCD module - nop ; Let the bus settle - btfsc LCD_DB4_BIT ; Transfer a set port bit into W + bcf STATUS,RP0 ; Back at reg bank 0 + + bsf LCD_RW_BIT ; Set Read mode for the LCD + bsf LCD_E_BIT ; And set E to clock the data out of the LCD module + nop ; Let the bus settle + btfsc LCD_DB4_BIT ; Transfer a set port bit into W addlw 1 - btfsc LCD_DB5_BIT ; Transfer a set port bit into W + btfsc LCD_DB5_BIT ; Transfer a set port bit into W addlw 2 - btfsc LCD_DB6_BIT ; Transfer a set port bit into W + btfsc LCD_DB6_BIT ; Transfer a set port bit into W addlw 4 - btfsc LCD_DB7_BIT ; Transfer a set port bit into W + btfsc LCD_DB7_BIT ; Transfer a set port bit into W addlw 8 - bcf LCD_E_BIT ; And clear the Enable again. - bcf LCD_RW_BIT ; Set Write mode for the LCD + bcf LCD_E_BIT ; And clear the Enable again. + bcf LCD_RW_BIT ; Set Write mode for the LCD + + bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed + + bcf LCD_DB4_BIT ; Set the port as an output again + bcf LCD_DB5_BIT ; + bcf LCD_DB6_BIT ; + bcf LCD_DB7_BIT ; - bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed - bcf LCD_DB4_BIT ; Set the port as an output again - bcf LCD_DB5_BIT ; - bcf LCD_DB6_BIT ; - bcf LCD_DB7_BIT ; - bcf STATUS,RP0 ; Back at reg bank 0 + bcf STATUS,RP0 ; Back at reg bank 0 - return ; Returns with data in W + return ; Returns with data in W ;---------------------------------------------------------------------- -; Delay routines (one iteration=3 cycles. That is 0.366211ms @32kHz) -; 2.73* # of ms is good... - -DelayW movwf Dcount ; Set delay counter - clrf Dcount2 - decf Dcount2,f -DelayLp decfsz Dcount,f - goto DelayIn - retlw 0 -DelayIn decfsz Dcount2,f - goto DelayIn2 - decf Dcount2,f - goto DelayLp -DelayIn2 goto $+1 - goto $+1 - goto $+1 - goto DelayIn +; Delay routines (non-interrupt based, therefore not even close to reliable) +; W=10 gives ~ 1ms of delay +; 1ms=5000 instructions wasted, 100us=500 cycles +; Maximum time waited will be 256*100us=25.6ms + +DelayW + movwf Dcount ; Set delay counter, number of 100us periods to wait + +DelayOuter + movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more + movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount +DelayInner + decfsz Dcount2,f ; 1 cycle (or two when exiting the loop) + goto DelayInner ; 2 cycles + decfsz Dcount,f ; Now decrement number of 100us periods and loop again + goto DelayOuter + return + subtitl "Bootstrap/Bootloader code" page ;---------------------------------------------------------------------- -; Bootstrap code - Allows PIC to flash itself with data from async port +; Bootstrap code - Allows PIC to flash itself with data from the async port. +; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory +; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver +; (one good way of solving that is to wait for the echo from the PIC before sending anything else) +; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration +; register can't be programmed. That means that any references to the config register in the hex file will be ignored. +; ; Startup @9600bps - org 700h ; Place the boot code at the top of memory +; RAM usage for the bootstrap code BootRXState equ 7fh ; What are we waiting for @RX BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog @@ -836,9 +829,11 @@ BootDataVH equ 74h BootHEXTemp equ 73h BootStrTemp equ 72h + org 700h ; Place the boot code at the top of memory + Bootstrap movlw 7 - movwf PCLATH + movwf PCLATH ; Make sure the lookup code runs with the high PC bits set! bsf STATUS,RP0 ; Access bank 1 bsf TXSTA,TXEN ; Enable UART TX @@ -851,10 +846,10 @@ Bootstrap ; clrf BootRXState ; Waiting for command - movlw BootStartText + movlw low BootStartText ; Send boot banner to the serial port call BootTXStr - movlw 0e8h + movlw 0e8h ; Initialize timeout timer movwf BootTimerL movwf BootTimerM movwf BootTimerH @@ -871,11 +866,11 @@ BootTimeout goto BootTimeout call BootRXB xorlw 27 ; ESC - btfss STATUS,Z - goto BootTimeout ; If it wasn't space, wait for another key + skpz + goto BootTimeout ; If it wasn't ESC, wait for another key BootFlash - movlw BootFlashText + movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port call BootTXStr bsf BootBits,1 @@ -884,11 +879,12 @@ BootFlash BootLoop call BootRXB ; First find the ':' + call BootTXB ; Echo to terminal xorlw ':' skpz goto BootLoop ; Loop until we find it! - call BootRXHEX ; Get one ASCII encoded byte (two chars) + call BootRXHEX ; Get one ASCII encoded byte (two chars) (this echoes automatically) movwf BootNumBytes ; This is the number of bytes to be programmed on the line ; Maybe clear cary here? rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format @@ -952,13 +948,13 @@ BootWrite ; Here a verify can take place, the read-back results are now in DataL/H - movlw '+' - goto BootWriteDone +; movlw '+' +; goto BootWriteDone BootWriteSkip - movlw '-' +; movlw '-' BootWriteDone - call BootTXB +; call BootTXB incf BootAddrL,f ; Advance counter to next addr skpnz @@ -967,30 +963,30 @@ BootWriteDone decfsz BootNumBytes,f goto BootLineLoop - movlw 13 ; Progress - call BootTXB - movlw 10 ; Progress - call BootTXB +; movlw 13 ; Progress +; call BootTXB +; movlw 10 ; Progress +; call BootTXB goto BootLoop BootFlashComplete BootReturn - movlw BootRunText + movlw low BootRunText call BootTXStr - bsf STATUS,RP0 + bsf STATUS,RP0 ; Reg bank 1 BootReturnWait btfss TXSTA,TRMT ; Wait for last things to flush goto BootReturnWait bcf TXSTA,TXEN ; Disable UART TX bcf STATUS,RP0 ; Back to bank 0 - bcf RCSTA,SPEN ; Enable serial port - bcf RCSTA,CREN ; Enable UART RX + bcf RCSTA,SPEN ; Disable serial port + bcf RCSTA,CREN ; Disable UART RX - clrf PCLATH + clrf PCLATH ; Go back to memory bank 0 return ; Return to code ;---------------------------------------------------------------------- @@ -1032,7 +1028,7 @@ BootRXW1 BootRXHEXNibble call BootRXB ; Receive nibble - + call BootTXB ; Echo to terminal addlw -'A' ; Convert from BCD to binary nibble skpc ; Test if if was 0-9 or A-F, skip if A-F addlw 'A' - 10 - '0' ; It was numeric '0' @@ -1121,6 +1117,6 @@ BootLookup ; EE Data (64 bytes), located at 2100h org 2100h -; data 0f2h, 099h, 000h, 000h, 018h, 0a5h, 090h, 084h +; data 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh END