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 ; Fix the DelayW routine so it actually delays W/10 ms...
\r
16 ; No checksum checking is done on incoming packets
\r
17 ; Investigate whether I 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 lots of other Unilink commands (Text display, time display etc.)
\r
21 ; Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..)
\r
23 ;----------------------------------------------------------------
\r
25 ;----------------------------------------------------------------
\r
28 ; 0.3 Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)
\r
29 ; 0.2 First attempt at responding to the Anyone command
\r
30 ; 0.1 Receives Unilink data OK, relays it to serial
\r
31 ; 0.0 Very first "F**king No Work!" version
\r
33 ;----------------------------------------------------------------
\r
35 ;----------------------------------------------------------------
\r
36 ; Unilink BUSON IN (blue) connected to RC2/CCP1
\r
37 ; Unilink DATA (green) connected to RC3
\r
38 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)
\r
39 ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)
\r
40 ; Unilink RST (lilac) connected to RA4
\r
41 ; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)
\r
42 ; LCD RW connected to pin RB2
\r
43 ; LCD E connected to pin RB3
\r
44 ; LCD DB4-DB7 connected to RB4-RB7
\r
45 ; RS-232 TX from computer connected to RC7/RX
\r
46 ; RS-232 RX to computer connected to RC6/TX
\r
47 ; RS-232 RI to computer connected to RC5
\r
49 ; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...
\r
51 #define BUSON_IN_BIT PORTC,2
\r
52 #define DATA_BIT PORTC,3
\r
53 #define BUSON_OUT_BIT PORTC,4
\r
54 #define CLK_BIT PORTB,0
\r
55 #define RST_BIT PORTA,4
\r
57 #define LCD_RS_BIT PORTB,1
\r
58 #define LCD_RW_BIT PORTB,2
\r
59 #define LCD_E_BIT PORTB,3
\r
60 #define LCD_DB4_BIT PORTB,4
\r
61 #define LCD_DB5_BIT PORTB,5
\r
62 #define LCD_DB6_BIT PORTB,6
\r
63 #define LCD_DB7_BIT PORTB,7
\r
65 #define RS232_RI_BIT PORTC,5
\r
67 ;----------------------------------------------------------------
\r
68 ; FILE REGISTER USAGE
\r
69 ;----------------------------------------------------------------
\r
72 Icount equ 2Dh ; Offset of string to print
\r
73 TxTemp equ 2Eh ; blahblah
\r
74 TxTemp2 equ 2Fh ; Blahblah2
\r
80 DataCount equ 33h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)
\r
81 DataStore equ 34h ; This is a kludge
\r
83 UnilinkSelected equ 3bh
\r
84 UnilinkBit equ 3ch ; This is my "bitmask" to be used for requests
\r
85 UnilinkID equ 3dh ; This is my Bus ID
\r
86 UnilinkCmdLen equ 3eh ; This gets updated with the actual packet length after CMD1 has been received
\r
87 UnilinkTXRX equ 3fh ; This is a pointer to the Unilink packet below, used with indirect addressing
\r
89 UnilinkRAD equ 40h ; Beginning of Unilink packet - the Receiving Address
\r
90 UnilinkTAD equ 41h ; Transmitter address
\r
91 UnilinkCMD1 equ 42h ; CMD1 byte
\r
92 UnilinkCMD2 equ 43h ; CMD2 byte
\r
93 UnilinkParity1 equ 44h ; First or only parity byte for short packets (6 bytes)
\r
94 UnilinkData1 equ 45h ; Extra data for medium/large packets, or zero for short packets
\r
95 UnilinkData2 equ 46h ;
\r
96 UnilinkData3 equ 47h ;
\r
97 UnilinkData4 equ 48h ;
\r
98 UnilinkData5 equ 49h ; Data5 if this is a large packet
\r
99 UnilinkParity2M equ 49h ; Parity2 shares the same byte if it's a medium sized packet
\r
100 UnilinkData6 equ 4ah ; Extra data for large packets, or zero for medium packets
\r
101 UnilinkData7 equ 4bh ;
\r
102 UnilinkData8 equ 4ch ;
\r
103 UnilinkData9 equ 4dh ;
\r
104 UnilinkParity2 equ 4eh ; Parity byte for large packets
\r
105 UnilinkZero equ 4fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)
\r
107 IRQPCLATH equ 7dh ; ISH storage
\r
108 IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks
\r
113 ;----------------------------------------------------------------
\r
114 ; Power up/Reset starting point
\r
116 org 0 ; Start at the beginning of memory (the reset vector)
\r
117 call Bootstrap ; Call Flash Load routine
\r
118 call LCDInit ; Initialize LCD I/F
\r
119 call IRQInit ; Set up and start the IRQ handler
\r
120 goto Main ; Run the main program loop (skip the IRQ handler)
\r
122 subtitl "IRQ Handler"
\r
123 ;----------------------------------------------------------------
\r
124 ; Interrupt handler always starts at addr 4
\r
125 ; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.
\r
126 ; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR
\r
127 ; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to
\r
128 ; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low
\r
129 ; again after that.
\r
131 org 4 ; ISR vector is at address 4
\r
132 movwf IRQW ; Save W
\r
133 swapf STATUS,w ; Get the status register into w
\r
134 clrf STATUS ; Zero out the status reg, gives Reg Bank0
\r
135 movwf IRQSTATUS ; Store the STATUS reg
\r
136 movf PCLATH,w ; Get the PCLATH reg
\r
137 movwf IRQPCLATH ; And store it
\r
138 clrf PCLATH ; Go to low memory
\r
139 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)
\r
141 btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK)
\r
142 goto IRQNotINT ; No it's not, check the other sources
\r
144 ; 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
\r
145 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction
\r
146 ; cycles (5 MIPS @ 20MHz), not even a problem for serial input if it's not receiving more than 6250 bytes per second, and the
\r
147 ; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of
\r
148 ; 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
\r
149 ; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX
\r
150 ; 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
\r
151 ; here, otherwise collisions will occur..
\r
152 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an
\r
153 ; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here
\r
154 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)
\r
156 movlw 8 ; Loop through the 8 bits
\r
158 movf UnilinkTXRX,w ; Get the pointer
\r
159 movwf FSR ; Store it to make use of indirect addressing
\r
162 btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out)
\r
163 goto IRQINTTristate ; Bit is low, we should tristate bit
\r
164 bcf PORTC,3 ; Otherwise set DATA bit low
\r
165 bsf STATUS,RP0 ; Select high regs
\r
166 bcf TRISC,3 ; And pull low (now it's an output)
\r
167 bcf STATUS,RP0 ; Back to regbank 0
\r
168 goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in
\r
171 bsf STATUS,RP0 ; Select high regs
\r
172 bsf TRISC,3 ; Force the bit to be tristated
\r
173 bcf STATUS,RP0 ; Back to regbank 0
\r
176 btfss PORTC,2 ; Check for BUSON
\r
178 btfsc PORTB,0 ; Wait for clock to go low
\r
179 goto IRQINTCLKWaitLow
\r
182 btfss PORTC,3 ; Test DATA
\r
183 setc ; Set carry if data is LOW (data is inverted!)
\r
184 rlf INDF,f ; Shift it into the "accumulator"
\r
186 decfsz DataCount,f ; Loop once more perhaps?
\r
187 goto IRQINTCLKWaitHigh ; Yes, again!
\r
188 goto IRQINTRecvDone ; No it's done, don't check for clock to go high again
\r
191 btfss PORTC,2 ; Check for BUSON
\r
193 btfss PORTB,0 ; Wait for clock to go high
\r
194 goto IRQINTCLKWaitHigh
\r
195 goto IRQINTBitSet ; Loop again
\r
197 ; Successfully received a byte here, run it through a state machine to figure out what to do
\r
198 ; (several possibilites exists here):
\r
199 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero
\r
200 ; If receive counter is zero and the received byte is a zero byte, discard it
\r
201 ; Otherwise store the byte in our receive buffer and increment receive counter
\r
202 ; 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
203 ; 00 = short 6 byte packet
\r
204 ; 10 = medium 11 byte packet
\r
205 ; 11 = long 16 byte packet
\r
206 ; Update the receive length byte accordingly
\r
207 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing
\r
208 ; the packet and take appropriate action.
\r
211 movf UnilinkTXRX,w ; Find out which byte # that was received
\r
213 bnz IRQINTRecvNotFirst ; Not the first byte
\r
214 movf UnilinkRAD,w ; Get the first byte received
\r
215 bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter
\r
217 incf UnilinkTXRX,f ; Increment address
\r
219 movf UnilinkTXRX,w ; Get the byte position again
\r
220 andlw 0fh ; Only lower 4 bits of interest
\r
221 xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet)
\r
222 bnz IRQINTRecvNotCMD1 ; No, skip the length code for now
\r
223 movlw 6 ; Assume it's a short packet
\r
224 btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long
\r
225 goto IRQINTRecvShort ; Nope, it's a short packet
\r
226 addlw 5 ; OK, it's long or medium at least
\r
227 btfsc INDF,6 ; Test for long
\r
228 addlw 5 ; Yep, it's a long packet
\r
230 movwf UnilinkCmdLen ; Store the length
\r
233 movf UnilinkTXRX,w ; Get the byte position
\r
234 xorwf UnilinkCmdLen,w ; XOR with the calculated command length
\r
235 andlw 0fh ; and mask - this results in a zero result when finished receiving
\r
236 bnz IRQINTRecvIncomplete ; Packet not ready yet
\r
238 ; Here a packet is actually received a packet, should check the checksum(s) as well, but I don't care right now
\r
239 ; (I need music in my car! :))
\r
240 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here
\r
241 ; (there won't be any more communication for at least another 4.8ms))
\r
243 ; Unilink command parser:
\r
245 ; Check for CMD1 = 01h (System bus commands)
\r
248 bnz IRQINTParseNot01
\r
250 ; Check for 01 00 (Bus Re-Initialization)
\r
253 bnz IRQINTParseNot0100
\r
255 clrf UnilinkID ; Clear the existing Unilink ID, if any
\r
256 bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID
\r
258 goto IRQINTParseComplete ; Don't send any reply to this
\r
262 ; Check for 01 02 (Anyone)
\r
265 bnz IRQINTParseNot0102
\r
267 movf UnilinkID,w ; Do I have an ID already?
\r
268 bnz IRQINTParseNot0102 ; Yep, I don't want another one!
\r
270 movlw 10h ; Sending to Master
\r
272 movlw 0d0h ; I'm in the MD changer group
\r
274 movlw 8ch ; Device discovery command reply
\r
278 movlw 6ch ; Hard coded parity (!)
\r
279 movwf UnilinkParity1
\r
280 movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??
\r
288 movlw 0deh ; Hard coded parity 2 (!)
\r
291 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
295 ; Check for 01 12 (Time poll)
\r
298 bnz IRQINTParseNot0112
\r
301 xorwf UnilinkID,w ; Is it for me?
\r
302 bnz IRQINTParseNot0112 ; Nope
\r
304 clrf UnilinkParity1
\r
305 movlw 10h ; Sending to Master
\r
306 addwf UnilinkParity1,f
\r
308 movf UnilinkID,w ; This is my ID
\r
309 addwf UnilinkParity1,f
\r
312 addwf UnilinkParity1,f
\r
315 movlw 80h ; Idle unless selected
\r
316 btfsc UnilinkSelected,7
\r
319 addwf UnilinkParity1,f
\r
322 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
328 ; Check for CMD1 = 02h (Appoint)
\r
331 bnz IRQINTParseNot02
\r
333 bsf BUSON_OUT_BIT ; Now activate the cascade BUSON pin, to allow others to be discovered
\r
335 movf UnilinkRAD,w ; Get the ID the master has given me
\r
336 movwf UnilinkID ; Store my id
\r
337 movf UnilinkCMD2,w ; Get the bitmask
\r
338 movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding)
\r
340 clrf UnilinkParity1
\r
341 movlw 10h ; Sending to Master
\r
342 addwf UnilinkParity1,f
\r
344 movf UnilinkID,w ; This is my ID
\r
345 addwf UnilinkParity1,f
\r
347 movlw 8ch ; Device discovery command again
\r
348 addwf UnilinkParity1,f
\r
351 addwf UnilinkParity1,f
\r
354 movf UnilinkParity1,w
\r
355 movwf UnilinkParity2M ; That's the parity when sending medium messages
\r
358 addwf UnilinkParity2M,f
\r
360 movlw 2ch ; My internal MD sends 1c here... (external/internal difference)
\r
361 addwf UnilinkParity2M,f
\r
364 addwf UnilinkParity2M,f
\r
367 addwf UnilinkParity2M,f
\r
371 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
375 ; Check for CMD1 = 87h (Power control)
\r
378 bnz IRQINTParseNot87
\r
380 ; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)
\r
381 btfsc UnilinkCMD2,3
\r
382 goto IRQINTParse87PowerOn
\r
384 bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels)
\r
385 goto IRQINTParseComplete
\r
387 IRQINTParse87PowerOn
\r
388 bcf RS232_RI_BIT ; Clear this to make RI pin go high (waking the computer)
\r
389 goto IRQINTParseComplete
\r
393 ; Check for CMD1 = f0h (Source Select)
\r
396 bnz IRQINTParseNotF0
\r
399 xorwf UnilinkID,w ; Check if it's selecting me
\r
400 bnz IRQINTParseF0Deselect
\r
402 bsf UnilinkSelected,7 ; Now we're selected
\r
403 goto IRQINTParseComplete
\r
405 IRQINTParseF0Deselect
\r
407 bcf UnilinkSelected,7 ; Now we're de-selected
\r
408 goto IRQINTParseComplete
\r
412 IRQINTParseComplete
\r
414 ; The CPU ends up here when parsing is complete and it's not interested in sending any reply back to the master
\r
415 ; (that's why we clear out all the packet buffer bytes)
\r
416 ; TODO: Replace this with an FSR access to save space and make the code neater
\r
422 clrf UnilinkParity1
\r
432 clrf UnilinkParity2
\r
435 IRQINTParseBypassClear
\r
437 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
438 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
440 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
443 IRQINTRecvIncomplete
\r
447 movwf DataStore ; Store it so the non-irq code can snoop
\r
450 bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again
\r
454 ; Finally restore CPU state and return from the ISR
\r
456 ; If I have to save the FSR in the beginning I also need to restore it here...
\r
459 movwf PCLATH ; Restore PCLATH
\r
461 movwf STATUS ; Restore STATUS
\r
463 swapf IRQW,w ; Restore W
\r
464 retfie ; Interrupt return
\r
467 subtitl "Main loop"
\r
470 ;----------------------------------------------------------------
\r
471 ; Data can be stored between here and 100h...
\r
474 DT "----- WJ UniLink"
\r
476 LookUp movwf PCL ; Go to it (this assumes PCLATH == 00h)
\r
478 ;----------------------------------------------------------------
\r
479 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]
\r
481 org 100h ; Maybe not force this to a specific address later
\r
484 movlw StartUpText1 ; Show something on the LCD
\r
489 bcf LCD_RS_BIT ; LCD 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
515 bz MainDontPrintCmd
\r
521 movf DataCount,w ; Load bit counter (if 0 then byte is available)
\r
525 decf DataCount,f ; Set it non-zero
\r
528 call BootTXB ; Send to terminal
\r
532 ;----------------------------------------------------------------
\r
533 ; IRQInit - Sets up the IRQ Handler
\r
537 ; Start with clearing the Unilink packet buffer before enabling any interrupts, otherwise the first packet might become corrupt
\r
538 ; TODO: Replace this with FSR access
\r
539 clrf UnilinkSelected
\r
547 clrf UnilinkParity1
\r
557 clrf UnilinkParity2
\r
561 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
562 movwf UnilinkTXRX ; Store it
\r
564 ; Fix the output state of RI and BUSON_OUT to a safe default
\r
566 bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)
\r
567 bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first
\r
569 bsf STATUS,RP0 ; Reg bank 1
\r
571 bcf RS232_RI_BIT ; Both bits should be outputs
\r
572 bcf BUSON_OUT_BIT ;
\r
574 ; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...
\r
575 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge
\r
577 bsf INTCON,INTE ; Enable the RB0/INT
\r
578 bsf INTCON,GIE ; Enable global interrupts
\r
580 bsf TXSTA,TXEN ; Enable UART TX
\r
582 bcf STATUS,RP0 ; Back to bank 0
\r
584 bsf RCSTA,SPEN ; Enable serial port
\r
585 bsf RCSTA,CREN ; Enable UART RX
\r
589 ;----------------------------------------------------------------
\r
590 ; Initialize LCD Controller...
\r
593 clrf PORTB ; First clear PortB data register
\r
594 bsf STATUS,RP0 ; Reg bank 1
\r
595 movlw 001h ; All but RB0 are outputs.
\r
598 bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up
\r
599 bcf STATUS,RP0 ; Restore Reg bank 0
\r
601 ; This is a standard reset sequence for the LCD controller
\r
603 movlw 160 ; Need to delay for at least 15ms, let's go for 16ms delay
\r
606 movlw 3 ; Write 3 to the LCD
\r
607 call TxLCD ; Send to LCD
\r
608 movlw 50 ; Need to delay for at least 4.1ms, let's go for 5ms delay
\r
611 movlw 3 ; Write 3 to the LCD
\r
613 movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay
\r
616 movlw 3 ; Write 3 to the LCD
\r
618 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
621 movlw 2 ; 4-bit interface requested
\r
623 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
626 ; Reset sequence ends here
\r
627 ; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits
\r
629 movlw 28h ; Function Select + 4-bit bus + 2-line display
\r
632 movlw 0ch ; Display Control + LCD On (No cursor)
\r
635 movlw 01h ; Clear Display
\r
638 movlw 06h ; Auto Increment cursor position
\r
643 ;----------------------------------------------------------------
\r
645 ; Send a string to the LCD.
\r
650 movlw 80h ; DisplayRam 0
\r
655 movlw 80h+40 ; DisplayRam 40 (row 2)
\r
661 ;----------------------------------------------------------------
\r
663 ; Send a string to the LCD.
\r
666 ; movwf Icount ; Icount = W
\r
668 movwf e_LEN ; Move to e_LEN
\r
670 Txm_lp movf Icount,w ; get the byte
\r
672 incf Icount,f ; ...else ++Icount (table index)
\r
673 call TxLCDB ; Send out the byte
\r
678 ;----------------------------------------------------------------
\r
679 ; TxLCDB - send a byte to the LCD
\r
682 movwf TxTemp ; Store byte to send for a while...
\r
684 bcf temp,0 ; Clear my temp bit
\r
685 btfss LCD_RS_BIT ; Check if we try the correct reg
\r
688 bsf temp,0 ; Indicate RS change
\r
692 call RxLCDB ; Receive byte from LCD, status reg
\r
694 skpz ; If the bit was set, the zero flag is not
\r
697 btfsc temp,0 ; If we had to clear RS reset it now
\r
700 swapf TxTemp,w ; Hi nibble of data to send in lo w bits
\r
701 call TxLCD ; Send them first...
\r
702 movf TxTemp,w ; Then we have the low nibble in low w bits
\r
703 call TxLCD ; And send that one as well
\r
706 ;----------------------------------------------------------------
\r
707 ; RxLCDB - recv a byte from the LCD
\r
710 call RxLCD ; Receive the high nibble
\r
712 swapf LCDWTmp,f ; Swap it back to file
\r
713 call RxLCD ; Receive the low nibble
\r
714 addwf LCDWTmp,w ; Put the nibbles together and return in W
\r
718 ;----------------------------------------------------------------
\r
719 ; TxLCD - send a nibble to the LCD
\r
722 movwf LCDWTmp ; Write nibble to tmp
\r
723 bcf LCD_DB4_BIT ; Clear previous data
\r
728 btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT
\r
730 btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT
\r
732 btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT
\r
734 btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT
\r
737 bsf LCD_E_BIT ; And set E to clock the data into the LCD module
\r
738 nop ; Let it settle
\r
739 bcf LCD_E_BIT ; And clear the Enable again.
\r
740 return ; Returns without modifying W
\r
742 ;----------------------------------------------------------------
\r
743 ; RxLCD - recv a nibble from the LCD
\r
746 clrw ; Clear W register, return data in lower 4 bits
\r
748 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
750 bsf LCD_DB4_BIT ; This sets the port bit as an input
\r
755 bcf STATUS,RP0 ; Back at reg bank 0
\r
757 bsf LCD_RW_BIT ; Set Read mode for the LCD
\r
758 bsf LCD_E_BIT ; And set E to clock the data out of the LCD module
\r
759 nop ; Let the bus settle
\r
760 btfsc LCD_DB4_BIT ; Transfer a set port bit into W
\r
762 btfsc LCD_DB5_BIT ; Transfer a set port bit into W
\r
764 btfsc LCD_DB6_BIT ; Transfer a set port bit into W
\r
766 btfsc LCD_DB7_BIT ; Transfer a set port bit into W
\r
768 bcf LCD_E_BIT ; And clear the Enable again.
\r
769 bcf LCD_RW_BIT ; Set Write mode for the LCD
\r
771 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
773 bcf LCD_DB4_BIT ; Set the port as an output again
\r
778 bcf STATUS,RP0 ; Back at reg bank 0
\r
780 return ; Returns with data in W
\r
782 ;----------------------------------------------------------------------
\r
783 ; Delay routines (non-interrupt based, therefore not even close to reliable)
\r
784 ; W=10 gives ~ 1ms of delay
\r
785 ; 1ms=5000 instructions wasted, 100us=500 cycles
\r
786 ; Maximum time waited will be 256*100us=25.6ms
\r
789 movwf Dcount ; Set delay counter, number of 100us periods to wait
\r
792 movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more
\r
793 movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount
\r
795 decfsz Dcount2,f ; 1 cycle (or two when exiting the loop)
\r
796 goto DelayInner ; 2 cycles
\r
797 decfsz Dcount,f ; Now decrement number of 100us periods and loop again
\r
802 subtitl "Bootstrap/Bootloader code"
\r
805 ;----------------------------------------------------------------------
\r
806 ; Bootstrap code - Allows PIC to flash itself with data from the async port.
\r
807 ; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory
\r
808 ; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver
\r
809 ; (one good way of solving that is to wait for the echo from the PIC before sending anything else)
\r
810 ; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration
\r
811 ; register can't be programmed. That means that any references to the config register in the hex file will be ignored.
\r
815 ; RAM usage for the bootstrap code
\r
817 BootRXState equ 7fh ; What are we waiting for @RX
\r
818 BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog
\r
826 BootNumBytes equ 76h
\r
829 BootHEXTemp equ 73h
\r
830 BootStrTemp equ 72h
\r
832 org 700h ; Place the boot code at the top of memory
\r
836 movwf PCLATH ; Make sure the lookup code runs with the high PC bits set!
\r
838 bsf STATUS,RP0 ; Access bank 1
\r
839 bsf TXSTA,TXEN ; Enable UART TX
\r
840 movlw 31 ; Divisor for 9k6 @ 20MHz Fosc
\r
841 movwf SPBRG ; Store
\r
842 bcf STATUS,RP0 ; Back to bank 0
\r
844 bsf RCSTA,SPEN ; Enable serial port
\r
845 bsf RCSTA,CREN ; Enable UART RX
\r
847 ; clrf BootRXState ; Waiting for command
\r
849 movlw low BootStartText ; Send boot banner to the serial port
\r
852 movlw 0e8h ; Initialize timeout timer
\r
858 incf BootTimerL,f ; A 24-bit counter
\r
863 skpnz ; When overflowing here..
\r
864 goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program
\r
865 btfss PIR1,RCIF ; Wait for RX to complete
\r
870 goto BootTimeout ; If it wasn't ESC, wait for another key
\r
873 movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port
\r
881 call BootRXB ; First find the ':'
\r
882 call BootTXB ; Echo to terminal
\r
885 goto BootLoop ; Loop until we find it!
\r
887 call BootRXHEX ; Get one ASCII encoded byte (two chars) (this echoes automatically)
\r
888 movwf BootNumBytes ; This is the number of bytes to be programmed on the line
\r
889 ; Maybe clear cary here?
\r
890 rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format
\r
892 ; Note carry should be clear here as there cannot be odd number of bytes in this format
\r
894 call BootRXHEX ; Receive AddrH
\r
896 call BootRXHEX ; Receive AddrL
\r
898 rrf BootAddrH,f ; Fix the addressing again
\r
901 bcf BootBits,2 ; Assume we should program
\r
902 bsf BootBits,1 ; And assume we should program flash not ee
\r
905 xorlw 020h ; Check if it's configuration, which we can't program
\r
906 skpnz ; Skip the bit set if it was false alarm
\r
907 bsf BootBits,2 ; No programming for this line
\r
909 xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)
\r
910 skpnz ; Skip the bit set instr if not EE data address
\r
911 bcf BootBits,1 ; We should program EE, will ignore the AddrH
\r
913 call BootRXHEX ; Receive Record Type (must be 0 for real records)
\r
914 skpz ; Check if zero
\r
915 goto BootFlashComplete
\r
918 call BootRXHEX ; Receive low-byte of data word
\r
920 call BootRXHEX ; Receive high-byte of data word
\r
923 btfsc BootBits,2 ; Check whether this line should be programmed at all
\r
926 bcf BootBits,0 ; Read mode first, verify if we actually have to write
\r
929 xorwf BootDataL,f ; Compare and destroy DataL
\r
930 movwf BootDataL ; Write new data to DataL
\r
931 skpz ; Skip if no difference, have to check high byte as well
\r
932 goto BootWrite ; Jump directly to write
\r
935 xorwf BootDataH,f ; Compare
\r
936 skpnz ; Skip if no difference, no programming necessary
\r
941 movwf BootDataH ; Have to put the new H byte data in as well
\r
944 ; call BootTXB ; Send progword indicator
\r
947 call BootEE ; Write directly into program mem
\r
949 ; Here a verify can take place, the read-back results are now in DataL/H
\r
952 ; goto BootWriteDone
\r
959 incf BootAddrL,f ; Advance counter to next addr
\r
961 incf BootAddrH,f ; And add to high byte if needed
\r
963 decfsz BootNumBytes,f
\r
966 ; movlw 13 ; Progress
\r
968 ; movlw 10 ; Progress
\r
976 movlw low BootRunText
\r
979 bsf STATUS,RP0 ; Reg bank 1
\r
981 btfss TXSTA,TRMT ; Wait for last things to flush
\r
982 goto BootReturnWait
\r
983 bcf TXSTA,TXEN ; Disable UART TX
\r
984 bcf STATUS,RP0 ; Back to bank 0
\r
986 bcf RCSTA,SPEN ; Disable serial port
\r
987 bcf RCSTA,CREN ; Disable UART RX
\r
989 clrf PCLATH ; Go back to memory bank 0
\r
990 return ; Return to code
\r
992 ;----------------------------------------------------------------------
\r
993 ; BootTXB - Sends one byte to the UART, waits for transmitter to become
\r
994 ; free before sending
\r
998 btfss PIR1,TXIF ; Wait for TX to empty
\r
1000 movwf TXREG ; Send the byte
\r
1003 ;----------------------------------------------------------------------
\r
1004 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated
\r
1007 movwf BootStrTemp ; Store offset
\r
1008 call BootLookup ; Lookup char
\r
1012 call BootTXB ; Send char
\r
1013 incf BootStrTemp,w ; Retrieve
\r
1016 ;----------------------------------------------------------------------
\r
1017 ; BootRXB - Receives one byte from the UART, waits if nothing available
\r
1021 btfss PIR1,RCIF ; Wait for RX to complete
\r
1023 movf RCREG,w ; Get the recvd byte
\r
1026 ;----------------------------------------------------------------------
\r
1027 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary
\r
1030 call BootRXB ; Receive nibble
\r
1031 call BootTXB ; Echo to terminal
\r
1032 addlw -'A' ; Convert from BCD to binary nibble
\r
1033 skpc ; Test if if was 0-9 or A-F, skip if A-F
\r
1034 addlw 'A' - 10 - '0' ; It was numeric '0'
\r
1035 addlw 10 ; Add 10 (A get to be 0ah etc.)
\r
1038 ;----------------------------------------------------------------------
\r
1039 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available
\r
1040 ; Decodes the bytes as ASCII hex and returns a single byte in W
\r
1043 call BootRXHEXNibble
\r
1045 swapf BootHEXTemp,f ; Swap it up to the high nibble
\r
1047 call BootRXHEXNibble
\r
1048 addwf BootHEXTemp,w ; And add the two nibbles together
\r
1051 ;----------------------------------------------------------------------
\r
1052 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the
\r
1053 ; exact action to take. BootAddrL and BootAddrH has to be initialized
\r
1054 ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash
\r
1055 ; The data to be written has to be put in BootDataL and BootDataH, and
\r
1056 ; data will be to the same place when read back
\r
1059 bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0)
\r
1061 movf BootAddrH,w ; Load desired address
\r
1065 movf BootDataH,w ; And load the data (only used when writing)
\r
1070 bsf STATUS,RP0 ; Go to bank 3
\r
1072 bsf EECON1,EEPGD ; Point to Program Flash mem
\r
1073 btfss BootBits,1 ; Test if that was correct or if we have to clear again
\r
1074 bcf EECON1,EEPGD ; Point to EE DATA mem
\r
1076 btfss BootBits,0 ; Check from read or write
\r
1077 goto BootEERD ; Skip the WR if we were going for a read
\r
1079 bsf EECON1,WREN ; Enable writes
\r
1083 movwf EECON2 ; Unlock write operation
\r
1084 bsf EECON1,WR ; And start a write cycle
\r
1086 btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish
\r
1087 goto BootWRLoop ; These two instructions gets NOPed when flashing
\r
1089 bcf EECON1,WREN ; Finally disable writes again
\r
1090 ; Here we read the data back again, can be used as verify
\r
1092 bsf EECON1,RD ; Start a read cycle
\r
1093 nop ; Only necessary for flash read, same thing as when writing above
\r
1094 nop ; Except I could use the two words for something useful there.. :)
\r
1097 bcf STATUS,RP0 ; Back to bank 2
\r
1098 movf EEDATA,w ; Store our EE-data
\r
1102 bcf STATUS,RP1 ; And finally back to bank 0
\r
1107 DT "WJBoot - press ESC to flash",0
\r
1109 DT 13,10,"Send INHX8 file now...",13,10,0
\r
1111 DT 13,10,"Exiting loader",13,10,0
\r
1114 movwf PCL ; Go fetch the char data
\r
1116 ;----------------------------------------------------------------------
\r
1117 ; EE Data (64 bytes), located at 2100h
\r
1120 ; data 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r