1 title "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 2003"
\r
2 subtitl "Definitions"
\r
3 list c=150,P=16F870,R=DEC,F=inhx8m
\r
4 include "p16f870.inc" ; Standard equates & Macros
\r
5 ERRORLEVEL 1,-302 ; Get rid of those annoying 302 msgs!
\r
8 ;----------------------------------------------------------------
\r
9 ; The Configuration Word
\r
10 __CONFIG _HS_OSC&_WDT_OFF&_PWRTE_ON&_BODEN_ON&_LVP_OFF&_CPD_OFF&_WRT_ENABLE_ON&_DEBUG_OFF&_CP_OFF
\r
12 ;----------------------------------------------------------------
\r
14 ;----------------------------------------------------------------
\r
15 ; BUSON OUT control isn't implemented
\r
16 ; No checksum checking is done on incoming packets
\r
17 ; Investigate whether we actually have to save PCLATH in ISH, maybe save FSR?
\r
18 ; Move RS232 code into ISH
\r
19 ; Check Overrun errors from the UART
\r
20 ; Implement Bus re-initialize command
\r
21 ; Implement lots of other Unilink commands
\r
23 ;----------------------------------------------------------------
\r
25 ;----------------------------------------------------------------
\r
28 ; 0.2 First attempt at responding to the Anyone command
\r
29 ; 0.1 Receives Unilink data OK, relays it to serial
\r
30 ; 0.0 Very first "Fucking No Work!" version
\r
32 ;----------------------------------------------------------------
\r
34 ;----------------------------------------------------------------
\r
35 ; Unilink BUSON IN (blue) connected to RC2/CCP1
\r
36 ; Unilink DATA (green) connected to RC3
\r
37 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)
\r
38 ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)
\r
39 ; Unilink RST (lilac) connected to RA4
\r
40 ; LCD RS connected to pin RB1
\r
41 ; LCD RW connected to pin RB2
\r
42 ; LCD E connected to pin RB3
\r
43 ; LCD DB4-DB7 connected to RB4-RB7
\r
44 ; RS-232 TX from computer connected to RC7/RX
\r
45 ; RS-232 RX to computer connected to RC6/TX
\r
46 ; RS-232 RI to computer connected to RC5
\r
48 ; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...
\r
50 #define BUSON_IN_BIT PORTC,2
\r
51 #define DATA_BIT PORTC,3
\r
52 #define BUSON_OUT_BIT PORTC,4
\r
53 #define CLK_BIT PORTB,0
\r
54 #define RST_BIT PORTA,4
\r
56 #define LCD_RS_BIT PORTB,1
\r
57 #define LCD_RW_BIT PORTB,2
\r
58 #define LCD_E_BIT PORTB,3
\r
59 #define LCD_DB4_BIT PORTB,4
\r
60 #define LCD_DB5_BIT PORTB,5
\r
61 #define LCD_DB6_BIT PORTB,6
\r
62 #define LCD_DB7_BIT PORTB,7
\r
64 #define RS232_RI_BIT PORTC,5
\r
66 ;----------------------------------------------------------------
\r
67 ; FILE REGISTER USAGE
\r
68 ;----------------------------------------------------------------
\r
71 Icount equ 2Dh ; Offset of string to print
\r
72 TxTemp equ 2Eh ; blahblah
\r
73 TxTemp2 equ 2Fh ; Blahblah2
\r
79 DataCount equ 33h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)
\r
80 DataStore equ 34h ; This is a kludge
\r
82 UnilinkSelected equ 3bh
\r
83 UnilinkBit equ 3ch ; This is my "bitmask" to be used for requests
\r
84 UnilinkID equ 3dh ; This is my Bus ID
\r
85 UnilinkCmdLen equ 3eh ; This gets updated with the actual packet length after CMD1 has been received
\r
86 UnilinkTXRX equ 3fh ; This is a pointer to the Unilink packet below, used with indirect addressing
\r
88 UnilinkRAD equ 40h ; Beginning of Unilink packet - the Receiving Address
\r
89 UnilinkTAD equ 41h ; Transmitter address
\r
90 UnilinkCMD1 equ 42h ; CMD1 byte
\r
91 UnilinkCMD2 equ 43h ; CMD2 byte
\r
92 UnilinkParity1 equ 44h ; First or only parity byte for short packets (6 bytes)
\r
93 UnilinkData1 equ 45h ; Extra data for medium/large packets, or zero for short packets
\r
94 UnilinkData2 equ 46h ;
\r
95 UnilinkData3 equ 47h ;
\r
96 UnilinkData4 equ 48h ;
\r
97 UnilinkData5 equ 49h ; Data5 if this is a large packet
\r
98 UnilinkParity2M equ 49h ; Parity2 shares the same byte if it's a medium sized packet
\r
99 UnilinkData6 equ 4ah ; Extra data for large packets, or zero for medium packets
\r
100 UnilinkData7 equ 4bh ;
\r
101 UnilinkData8 equ 4ch ;
\r
102 UnilinkData9 equ 4dh ;
\r
103 UnilinkParity2 equ 4eh ; Parity byte for large packets
\r
104 UnilinkZero equ 4fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)
\r
106 IRQPCLATH equ 7dh ; ISH storage
\r
107 IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks
\r
112 ;----------------------------------------------------------------
\r
113 ; Power up/Reset starting point [den rulerar]
\r
115 org 0 ; Start at the beginning of memory (the reset vector)
\r
116 call Bootstrap ; Call Flash Load routine
\r
117 call LCDInit ; Initialize LCD I/F
\r
118 call IRQInit ; Set up and start the IRQ handler
\r
119 goto Main ; Run the main program loop (skip the IRQ handler)
\r
121 subtitl "IRQ Handler"
\r
122 ;----------------------------------------------------------------
\r
123 ; Interrupt handler always starts at addr 4
\r
124 ; In order to save one instruction cycle we put the actual code here directly instead of a goto instruction
\r
126 org 4 ; ISR vector is at address 4
\r
127 movwf IRQW ; Save W
\r
128 swapf STATUS,w ; Get the status register into w
\r
129 clrf STATUS ; Zero out the status reg, gives us Bank0 all the time
\r
130 movwf IRQSTATUS ; Store the STATUS reg
\r
131 movf PCLATH,w ; Get the PCLATH reg
\r
132 movwf IRQPCLATH ; And store it
\r
133 clrf PCLATH ; Go to low memory
\r
134 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)
\r
136 btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK)
\r
137 goto IRQNotINT ; No it's not, check the other sources
\r
139 ; If there's activity on the clock line (the clock goes high) we stay in here until we have clocked eight bits
\r
140 ; - this saves us a lot of context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=
\r
141 ; 160us=800 instruction cycles (5 MIPS @ 20MHz), not even a problem for serial input if we're not getting more than
\r
142 ; 6250 bytes per second from the UART, and the 2-byte FIFO somehow fills up (this should be impossible even @ 115200
\r
143 ; 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,
\r
144 ; this gives the CPU ample of time to deal with all bytes from the USART. I'm checking the OERR (Serial Overrun) bit
\r
145 ; to catch this though.. Note that this piece of code does both TX and RX at the same time (in order to receive packets
\r
146 ; one has to make sure that the packet buffer is zeroed out before entering here, otherwise collisions will occur..
\r
147 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's a
\r
148 ; interrupt driven software based USART - by trigging the interrupt on the rising edge we buy us some extra time here
\r
149 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)
\r
151 movlw 8 ; Loop through the 8 bits
\r
153 movf UnilinkTXRX,w ; Get the pointer
\r
154 movwf FSR ; Store it to make use of indirect addressing
\r
157 btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out)
\r
158 goto IRQINTTristate ; Bit is low, we should tristate bit
\r
159 bcf PORTC,3 ; Otherwise set DATA bit low
\r
160 bsf STATUS,RP0 ; Select high regs
\r
161 bcf TRISC,3 ; And pull low (now it's an output)
\r
162 bcf STATUS,RP0 ; Back to regbank 0
\r
163 goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in
\r
166 bsf STATUS,RP0 ; Select high regs
\r
167 bsf TRISC,3 ; Force the bit to be tristated
\r
168 bcf STATUS,RP0 ; Back to regbank 0
\r
171 btfss PORTC,2 ; Check for BUSON
\r
173 btfsc PORTB,0 ; Wait for clock to go low
\r
174 goto IRQINTCLKWaitLow
\r
176 clrc ; Clear carry (this way the DataStore byte doesn't have to be cleared before)
\r
177 btfss PORTC,3 ; Test DATA
\r
178 setc ; Set carry if data is LOW (data is inverted!)
\r
179 rlf INDF,f ; Shift it into our accumulator
\r
181 decfsz DataCount,f ; Loop once more perhaps?
\r
182 goto IRQINTCLKWaitHigh ; Yes, again!
\r
183 goto IRQINTRecvDone ; No we're done, don't check for clock to go high again
\r
186 btfss PORTC,2 ; Check for BUSON
\r
188 btfss PORTB,0 ; Wait for clock to go high
\r
189 goto IRQINTCLKWaitHigh
\r
190 goto IRQINTBitSet ; Loop again
\r
192 ; Successfully received a byte here, run it through a state machine to figure out what to do
\r
193 ; (several possibilites exists here:
\r
194 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero
\r
195 ; If receive counter is zero and the received byte is a zero byte, discard it
\r
196 ; Otherwise store the byte in our receive buffer and increment receive counter
\r
197 ; If the receive counter is 3 check the two upper bits of recv'd byte (CMD1) - this tells us the length of the packet
\r
198 ; 00 = short 6 byte packet
\r
199 ; 10 = medium 11 byte packet
\r
200 ; 11 = long 16 byte packet
\r
201 ; Update the receive length byte accordingly
\r
202 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing
\r
203 ; the packet and take appropriate action.
\r
206 movf UnilinkTXRX,w ; Find out which byte we got
\r
208 bnz IRQINTRecvNotFirst ; Not the first byte
\r
209 movf UnilinkRAD,w ; Get the first byte received
\r
210 bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter
\r
212 incf UnilinkTXRX,f ; Increment address
\r
214 movf UnilinkTXRX,w ; Get the byte position again
\r
215 andlw 0fh ; Only lower 4 bits of interest
\r
216 xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet)
\r
217 bnz IRQINTRecvNotCMD1 ; No, skip the length code for now
\r
218 movlw 6 ; Assume it's a short packet
\r
219 btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long
\r
220 goto IRQINTRecvShort ; Nope, it's a short packet
\r
221 addlw 5 ; OK, it's long or medium at least
\r
222 btfsc INDF,6 ; Test for long
\r
223 addlw 5 ; Yep, it's a long packet
\r
225 movwf UnilinkCmdLen ; Store the length
\r
228 movf UnilinkTXRX,w ; Get the byte position
\r
229 xorwf UnilinkCmdLen,w ; XOR with the calculated command length
\r
230 andlw 0fh ; and mask - this results in a zero result when finished receiving
\r
231 bnz IRQINTRecvIncomplete ; Packet not ready yet
\r
233 ; Here we actually have received a packet, should check the checksum(s) as well, but I don't care right now
\r
234 ; (I need music in my car! :))
\r
235 ; This is inefficient, I know, I'll improve it later... (Not that it matters, we have plenty of time here
\r
236 ; (there can't be any more communication for another 4.8ms))
\r
238 ; Unilink command parser:
\r
240 ; Check for CMD1 = 01h (System bus commands)
\r
243 bnz IRQINTParseNot01
\r
245 ; Check for 01 02 (Anyone)
\r
248 bnz IRQINTParseNot0102
\r
250 movf UnilinkID,w ; Do I have an ID already?
\r
251 bnz IRQINTParseNot0102 ; Yep, I don't want another one!
\r
253 movlw 10h ; Sending to Master
\r
255 movlw 0d0h ; I'm in the MD changer group
\r
257 movlw 8ch ; Device discovery command reply
\r
261 movlw 6ch ; Hard coded parity (!)
\r
262 movwf UnilinkParity1
\r
263 movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??
\r
271 movlw 0deh ; Hard coded parity 2 (!)
\r
274 goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send what's in the buffer next time
\r
278 ; Check for 01 12 (Time poll)
\r
281 bnz IRQINTParseNot0112
\r
284 xorwf UnilinkID,w ; Is it for us?
\r
285 bnz IRQINTParseNot0112 ; nope
\r
287 clrf UnilinkParity1
\r
288 movlw 10h ; Sending to Master
\r
289 addwf UnilinkParity1,f
\r
291 movf UnilinkID,w ; This is my ID
\r
292 addwf UnilinkParity1,f
\r
295 addwf UnilinkParity1,f
\r
298 movlw 80h ; We're idle unless selected
\r
299 btfsc UnilinkSelected,7
\r
302 addwf UnilinkParity1,f
\r
305 goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send!
\r
311 ; Check for CMD1 = 02h (Appoint)
\r
314 bnz IRQINTParseNot02
\r
316 movf UnilinkRAD,w ; Get the ID the master has given us
\r
317 movwf UnilinkID ; Store my id
\r
318 movf UnilinkCMD2,w ; Get the bitmask
\r
319 movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding)
\r
321 clrf UnilinkParity1
\r
322 movlw 10h ; Sending to Master
\r
323 addwf UnilinkParity1,f
\r
325 movf UnilinkID,w ; This is my ID
\r
326 addwf UnilinkParity1,f
\r
328 movlw 8ch ; Device discovery command again
\r
329 addwf UnilinkParity1,f
\r
332 addwf UnilinkParity1,f
\r
335 movf UnilinkParity1,w
\r
336 movwf UnilinkParity2M ; That's the parity when sending medium messages
\r
339 addwf UnilinkParity2M,f
\r
341 movlw 2ch ; My internal MD sends 1c here... (external/internal or 1/10 disc difference?)
\r
342 addwf UnilinkParity2M,f
\r
345 addwf UnilinkParity2M,f
\r
348 addwf UnilinkParity2M,f
\r
352 goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send!
\r
356 ; Check for CMD1 = f0h (Source Select)
\r
359 bnz IRQINTParseNotF0
\r
362 xorwf UnilinkID,w ; Check if it's selecting us
\r
363 bnz IRQINTParseF0Deselect
\r
365 bsf UnilinkSelected,7 ; Now we're selected
\r
366 goto IRQINTParseNotF0
\r
368 IRQINTParseF0Deselect
\r
370 bcf UnilinkSelected,7 ; Now we're de-selected
\r
371 goto IRQINTParseNotF0
\r
375 ; We end up here when parsing is complete and we're not interested in sending any reply back to the master
\r
376 ; (that's why we clear out all the packet buffer bytes)
\r
377 ; TODO: Replace this with an FSR access to save space and make the code neater
\r
383 clrf UnilinkParity1
\r
393 clrf UnilinkParity2
\r
396 IRQINTParseBypassClear
\r
398 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
399 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
401 clrf UnilinkCmdLen ; No command length as we're waiting for a new packet
\r
404 IRQINTRecvIncomplete
\r
408 movwf DataStore ; Store it so our non-irq code can snoop
\r
411 bcf INTCON,INTF ; Clear our IRQ source bit so we can receive new bits again
\r
415 ; Finally restore CPU state and return from the ISR
\r
417 movwf PCLATH ; Restore PCLATH
\r
419 movwf STATUS ; Restore STATUS
\r
421 swapf IRQW,w ; Restore W
\r
422 retfie ; Interrupt return
\r
425 subtitl "Main loop"
\r
428 ;----------------------------------------------------------------
\r
429 ; Data can be stored between here and 100h...
\r
432 DT "----- WJ UniLink"
\r
434 LookUp movwf PCL ; Go to it
\r
436 ;----------------------------------------------------------------
\r
437 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]
\r
442 bsf RS232_RI_BIT ; We want RI to be high (inverted logic, not set)
\r
443 bcf BUSON_OUT_BIT ; But we don't want BUSON_OUT on just yet, we need to be appointed first
\r
445 bsf STATUS,RP0 ; Select bank 1
\r
447 bcf RS232_RI_BIT ; Both bits should be outputs at least
\r
448 bcf BUSON_OUT_BIT ;
\r
453 bsf TXSTA,TXEN ; Enable UART TX
\r
454 bcf STATUS,RP0 ; Back to bank 0
\r
456 bsf RCSTA,SPEN ; Enable serial port
\r
457 bsf RCSTA,CREN ; Enable UART RX
\r
459 ; Replace this with an FSR access
\r
460 clrf UnilinkSelected
\r
468 clrf UnilinkParity1
\r
478 clrf UnilinkParity2
\r
482 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
483 movwf UnilinkTXRX ; Store it
\r
489 bcf LCD_RS_BIT ;Command mode
\r
490 movlw 80h ;DisplayRam 0
\r
495 btfsc PORTA,4 ; Test RST
\r
500 btfsc PORTB,0 ; Test CLK
\r
505 btfsc PORTC,2 ; Test BUSON-IN
\r
510 btfsc PORTC,3 ; Test DATA
\r
514 movf UnilinkCmdLen,w
\r
520 movf DataCount,w ; Load bit counter (if 0 then byte is available)
\r
524 decf DataCount,f ; Set it non-zero
\r
527 call BootTXB ; Send to terminal
\r
532 ; movlw StartUpText1
\r
536 ; bsf PORTA,4 ; turn off LED
\r
538 ; movlw StartUpText2
\r
542 ; bcf PORTA,4 ; turn on LED
\r
544 ; movlw StartUpText3
\r
551 ;----------------------------------------------------------------
\r
552 ; IRQInit - Sets up the IRQ Handler
\r
555 bsf STATUS,RP0 ; Reg bank 1
\r
556 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge
\r
557 bsf INTCON,INTE ; Enable the RB0/INT
\r
558 bsf INTCON,GIE ; Enable global interrupts
\r
559 bcf STATUS,RP0 ; Back to bank 0
\r
562 ;----------------------------------------------------------------
\r
563 ; Initialize LCD Controller...
\r
567 bsf STATUS,RP0 ; Hi Bank
\r
568 movlw 001h ; All but RB0 are outputs.
\r
570 bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up
\r
571 bcf STATUS,RP0 ; Restore Lo Bank
\r
573 ; bcf PORTA,4 ; turn on LED
\r
575 ; movlw 44 ; Should be 16ms delay
\r
576 movlw 255 ; Should be 16ms delay
\r
579 movlw 3 ; Write 3 to the LCD
\r
580 call TxLCD ; Send to LCD
\r
581 ; movlw 12 ; Should be 5ms delay
\r
582 movlw 255 ; Should be 16ms delay
\r
585 movlw 3 ; Write 3 to the LCD
\r
587 ; movlw 12 ; Should be 16ms delay
\r
588 movlw 255 ; Should be 16ms delay
\r
591 movlw 3 ; Write 3 to the LCD
\r
594 movlw 255 ; Should be 16ms delay
\r
598 call TxLCD ; | 4-bit interface
\r
599 ; movlw 55 ; | After this we are ready to ROCK!
\r
600 movlw 255 ; Should be 16ms delay
\r
603 ; bsf PORTA,4 ; turn off LED
\r
605 movlw 28h ; Some random commands :)))
\r
619 ;----------------------------------------------------------------
\r
620 ; LongDelay - Well, guess that for yourself...
\r
623 ; btfss PORTB,6 ; Talk to da PC?
\r
624 ; goto PCTalk ; Oh yeah...
\r
660 ;----------------------------------------------------------------
\r
662 ; Send a string to the LCD.
\r
667 movlw 80h ;DisplayRam 0
\r
672 movlw 80h+40 ;DisplayRam 40 (row 2)
\r
678 ;----------------------------------------------------------------
\r
680 ; Send a string to the LCD.
\r
683 ; movwf Icount ; Icount = W
\r
685 movwf e_LEN ; Move to e_LEN
\r
687 Txm_lp movf Icount,w ; get the byte
\r
689 incf Icount,f ; ...else ++Icount (table index)
\r
690 call TxLCDB ; Send out the byte
\r
695 ;----------------------------------------------------------------
\r
696 ; TxLCDB - send a byte to the LCD
\r
699 movwf TxTemp ; Store byte to send for a while...
\r
701 bcf temp,0 ; Clear my temp bit
\r
702 btfss LCD_RS_BIT ; Check if we try the correct reg
\r
705 bsf temp,0 ; Indicate RS change
\r
709 call RxLCDB ; Receive byte from LCD, status reg
\r
711 btfss STATUS,Z ; If the bit was set, the zero flag is not
\r
714 btfsc temp,0 ; If we had to clear RS reset it now
\r
717 swapf TxTemp,w ; Hi nibble of data to send in lo w bits
\r
718 call TxLCD ; Send them first...
\r
719 movf TxTemp,w ; Then we have the low nibble in low w bits
\r
720 call TxLCD ; And send that one as well
\r
723 ;----------------------------------------------------------------
\r
724 ; RxLCDB - recv a byte from the LCD
\r
727 call RxLCD ; Receive the high nibble
\r
729 swapf LCDWTmp,f ; Swap it back to file
\r
730 call RxLCD ; Receive the low nibble
\r
731 addwf LCDWTmp,w ; Put the nibbles together and return in W
\r
735 ;----------------------------------------------------------------
\r
736 ; TxLCD - send a nibble to the LCD
\r
739 movwf LCDWTmp ; Write nibble to tmp
\r
740 bcf LCD_DB4_BIT ; Clear previous data
\r
745 btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT
\r
747 btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT
\r
749 btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT
\r
751 btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT
\r
754 bsf LCD_E_BIT ; And set E to clock the data into the LCD module
\r
755 nop ; Let it settle
\r
756 bcf LCD_E_BIT ; And clear the Enable again.
\r
757 return ; Returns without modifying W
\r
759 ;----------------------------------------------------------------
\r
760 ; RxLCD - recv a nibble from the LCD
\r
763 clrw ; Clear W register, return data in lower 4 bits
\r
765 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
767 bsf LCD_DB4_BIT ; This sets the port bit as an input
\r
771 bcf STATUS,RP0 ; Back at reg bank 0
\r
773 bsf LCD_RW_BIT ; Set Read mode for the LCD
\r
774 bsf LCD_E_BIT ; And set E to clock the data out of the LCD module
\r
775 nop ; Let the bus settle
\r
776 btfsc LCD_DB4_BIT ; Transfer a set port bit into W
\r
778 btfsc LCD_DB5_BIT ; Transfer a set port bit into W
\r
780 btfsc LCD_DB6_BIT ; Transfer a set port bit into W
\r
782 btfsc LCD_DB7_BIT ; Transfer a set port bit into W
\r
784 bcf LCD_E_BIT ; And clear the Enable again.
\r
785 bcf LCD_RW_BIT ; Set Write mode for the LCD
\r
787 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
788 bcf LCD_DB4_BIT ; Set the port as an output again
\r
792 bcf STATUS,RP0 ; Back at reg bank 0
\r
794 return ; Returns with data in W
\r
796 ;----------------------------------------------------------------------
\r
797 ; Delay routines (one iteration=3 cycles. That is 0.366211ms @32kHz)
\r
798 ; 2.73* # of ms is good...
\r
800 DelayW movwf Dcount ; Set delay counter
\r
803 DelayLp decfsz Dcount,f
\r
806 DelayIn decfsz Dcount2,f
\r
815 subtitl "Bootstrap/Bootloader code"
\r
818 ;----------------------------------------------------------------------
\r
819 ; Bootstrap code - Allows PIC to flash itself with data from async port
\r
822 org 700h ; Place the boot code at the top of memory
\r
824 BootRXState equ 7fh ; What are we waiting for @RX
\r
825 BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog
\r
833 BootNumBytes equ 76h
\r
836 BootHEXTemp equ 73h
\r
837 BootStrTemp equ 72h
\r
843 bsf STATUS,RP0 ; Access bank 1
\r
844 bsf TXSTA,TXEN ; Enable UART TX
\r
845 movlw 31 ; Divisor for 9k6 @ 20MHz Fosc
\r
846 movwf SPBRG ; Store
\r
847 bcf STATUS,RP0 ; Back to bank 0
\r
849 bsf RCSTA,SPEN ; Enable serial port
\r
850 bsf RCSTA,CREN ; Enable UART RX
\r
852 ; clrf BootRXState ; Waiting for command
\r
854 movlw BootStartText
\r
863 incf BootTimerL,f ; A 24-bit counter
\r
868 skpnz ; When overflowing here..
\r
869 goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program
\r
870 btfss PIR1,RCIF ; Wait for RX to complete
\r
875 goto BootTimeout ; If it wasn't space, wait for another key
\r
878 movlw BootFlashText
\r
886 call BootRXB ; First find the ':'
\r
889 goto BootLoop ; Loop until we find it!
\r
891 call BootRXHEX ; Get one ASCII encoded byte (two chars)
\r
892 movwf BootNumBytes ; This is the number of bytes to be programmed on the line
\r
893 ; Maybe clear cary here?
\r
894 rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format
\r
896 ; Note carry should be clear here as there cannot be odd number of bytes in this format
\r
898 call BootRXHEX ; Receive AddrH
\r
900 call BootRXHEX ; Receive AddrL
\r
902 rrf BootAddrH,f ; Fix the addressing again
\r
905 bcf BootBits,2 ; Assume we should program
\r
906 bsf BootBits,1 ; And assume we should program flash not ee
\r
909 xorlw 020h ; Check if it's configuration, which we can't program
\r
910 skpnz ; Skip the bit set if it was false alarm
\r
911 bsf BootBits,2 ; No programming for this line
\r
913 xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)
\r
914 skpnz ; Skip the bit set instr if not EE data address
\r
915 bcf BootBits,1 ; We should program EE, will ignore the AddrH
\r
917 call BootRXHEX ; Receive Record Type (must be 0 for real records)
\r
918 skpz ; Check if zero
\r
919 goto BootFlashComplete
\r
922 call BootRXHEX ; Receive low-byte of data word
\r
924 call BootRXHEX ; Receive high-byte of data word
\r
927 btfsc BootBits,2 ; Check whether this line should be programmed at all
\r
930 bcf BootBits,0 ; Read mode first, verify if we actually have to write
\r
933 xorwf BootDataL,f ; Compare and destroy DataL
\r
934 movwf BootDataL ; Write new data to DataL
\r
935 skpz ; Skip if no difference, have to check high byte as well
\r
936 goto BootWrite ; Jump directly to write
\r
939 xorwf BootDataH,f ; Compare
\r
940 skpnz ; Skip if no difference, no programming necessary
\r
945 movwf BootDataH ; Have to put the new H byte data in as well
\r
948 ; call BootTXB ; Send progword indicator
\r
951 call BootEE ; Write directly into program mem
\r
953 ; Here a verify can take place, the read-back results are now in DataL/H
\r
963 incf BootAddrL,f ; Advance counter to next addr
\r
965 incf BootAddrH,f ; And add to high byte if needed
\r
967 decfsz BootNumBytes,f
\r
970 movlw 13 ; Progress
\r
972 movlw 10 ; Progress
\r
985 btfss TXSTA,TRMT ; Wait for last things to flush
\r
986 goto BootReturnWait
\r
987 bcf TXSTA,TXEN ; Disable UART TX
\r
988 bcf STATUS,RP0 ; Back to bank 0
\r
990 bcf RCSTA,SPEN ; Enable serial port
\r
991 bcf RCSTA,CREN ; Enable UART RX
\r
994 return ; Return to code
\r
996 ;----------------------------------------------------------------------
\r
997 ; BootTXB - Sends one byte to the UART, waits for transmitter to become
\r
998 ; free before sending
\r
1002 btfss PIR1,TXIF ; Wait for TX to empty
\r
1004 movwf TXREG ; Send the byte
\r
1007 ;----------------------------------------------------------------------
\r
1008 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated
\r
1011 movwf BootStrTemp ; Store offset
\r
1012 call BootLookup ; Lookup char
\r
1016 call BootTXB ; Send char
\r
1017 incf BootStrTemp,w ; Retrieve
\r
1020 ;----------------------------------------------------------------------
\r
1021 ; BootRXB - Receives one byte from the UART, waits if nothing available
\r
1025 btfss PIR1,RCIF ; Wait for RX to complete
\r
1027 movf RCREG,w ; Get the recvd byte
\r
1030 ;----------------------------------------------------------------------
\r
1031 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary
\r
1034 call BootRXB ; Receive nibble
\r
1036 addlw -'A' ; Convert from BCD to binary nibble
\r
1037 skpc ; Test if if was 0-9 or A-F, skip if A-F
\r
1038 addlw 'A' - 10 - '0' ; It was numeric '0'
\r
1039 addlw 10 ; Add 10 (A get to be 0ah etc.)
\r
1042 ;----------------------------------------------------------------------
\r
1043 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available
\r
1044 ; Decodes the bytes as ASCII hex and returns a single byte in W
\r
1047 call BootRXHEXNibble
\r
1049 swapf BootHEXTemp,f ; Swap it up to the high nibble
\r
1051 call BootRXHEXNibble
\r
1052 addwf BootHEXTemp,w ; And add the two nibbles together
\r
1055 ;----------------------------------------------------------------------
\r
1056 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the
\r
1057 ; exact action to take. BootAddrL and BootAddrH has to be initialized
\r
1058 ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash
\r
1059 ; The data to be written has to be put in BootDataL and BootDataH, and
\r
1060 ; data will be to the same place when read back
\r
1063 bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0)
\r
1065 movf BootAddrH,w ; Load desired address
\r
1069 movf BootDataH,w ; And load the data (only used when writing)
\r
1074 bsf STATUS,RP0 ; Go to bank 3
\r
1076 bsf EECON1,EEPGD ; Point to Program Flash mem
\r
1077 btfss BootBits,1 ; Test if that was correct or if we have to clear again
\r
1078 bcf EECON1,EEPGD ; Point to EE DATA mem
\r
1080 btfss BootBits,0 ; Check from read or write
\r
1081 goto BootEERD ; Skip the WR if we were going for a read
\r
1083 bsf EECON1,WREN ; Enable writes
\r
1087 movwf EECON2 ; Unlock write operation
\r
1088 bsf EECON1,WR ; And start a write cycle
\r
1090 btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish
\r
1091 goto BootWRLoop ; These two instructions gets NOPed when flashing
\r
1093 bcf EECON1,WREN ; Finally disable writes again
\r
1094 ; Here we read the data back again, can be used as verify
\r
1096 bsf EECON1,RD ; Start a read cycle
\r
1097 nop ; Only necessary for flash read, same thing as when writing above
\r
1098 nop ; Except I could use the two words for something useful there.. :)
\r
1101 bcf STATUS,RP0 ; Back to bank 2
\r
1102 movf EEDATA,w ; Store our EE-data
\r
1106 bcf STATUS,RP1 ; And finally back to bank 0
\r
1111 DT "WJBoot - press ESC to flash",0
\r
1113 DT 13,10,"Send INHX8 file now...",13,10,0
\r
1115 DT 13,10,"Exiting loader",13,10,0
\r
1118 movwf PCL ; Go fetch the char data
\r
1120 ;----------------------------------------------------------------------
\r
1121 ; EE Data (64 bytes), located at 2100h
\r
1124 ; data 0f2h, 099h, 000h, 000h, 018h, 0a5h, 090h, 084h
\r