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.4 Some fixups in the bootstrap code (I actually had to put the PIC in my PICSTART Plus programmer again :))
\r
29 ; 0.3 Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)
\r
30 ; 0.2 First attempt at responding to the Anyone command
\r
31 ; 0.1 Receives Unilink data OK, relays it to serial
\r
32 ; 0.0 Very first "F**king No Work!" version
\r
34 ;----------------------------------------------------------------
\r
36 ;----------------------------------------------------------------
\r
37 ; Unilink BUSON IN (blue) connected to RC2/CCP1
\r
38 ; Unilink DATA (green) connected to RC3
\r
39 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)
\r
40 ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)
\r
41 ; Unilink RST (lilac) connected to RA4
\r
42 ; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)
\r
43 ; LCD RW connected to pin RB2
\r
44 ; LCD E connected to pin RB3
\r
45 ; LCD DB4-DB7 connected to RB4-RB7
\r
46 ; RS-232 TX from computer connected to RC7/RX
\r
47 ; RS-232 RX to computer connected to RC6/TX
\r
48 ; RS-232 RI to computer connected to RC5
\r
50 ; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...
\r
52 #define BUSON_IN_BIT PORTC,2
\r
53 #define DATA_BIT PORTC,3
\r
54 #define BUSON_OUT_BIT PORTC,4
\r
55 #define CLK_BIT PORTB,0
\r
56 #define RST_BIT PORTA,4
\r
58 #define LCD_RS_BIT PORTB,1
\r
59 #define LCD_RW_BIT PORTB,2
\r
60 #define LCD_E_BIT PORTB,3
\r
61 #define LCD_DB4_BIT PORTB,4
\r
62 #define LCD_DB5_BIT PORTB,5
\r
63 #define LCD_DB6_BIT PORTB,6
\r
64 #define LCD_DB7_BIT PORTB,7
\r
66 #define RS232_RI_BIT PORTC,5
\r
68 ;----------------------------------------------------------------
\r
69 ; FILE REGISTER USAGE
\r
70 ;----------------------------------------------------------------
\r
75 SlaveBreakState equ 23h ; Hold state and time-out information about slave break, indicates when it can happen
\r
76 Icount equ 2Dh ; Offset of string to print
\r
77 TxTemp equ 2Eh ; blahblah
\r
78 TxTemp2 equ 2Fh ; Blahblah2
\r
84 DataCount equ 33h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)
\r
85 DataStore equ 34h ; This is a kludge
\r
87 UnilinkSelected equ 3bh
\r
88 UnilinkBit equ 3ch ; This is my "bitmask" to be used for requests
\r
89 UnilinkID equ 3dh ; This is my Bus ID
\r
90 UnilinkCmdLen equ 3eh ; This gets updated with the actual packet length after CMD1 has been received
\r
91 UnilinkTXRX equ 3fh ; This is a pointer to the Unilink packet below, used with indirect addressing
\r
93 UnilinkRAD equ 40h ; Beginning of Unilink packet - the Receiving Address
\r
94 UnilinkTAD equ 41h ; Transmitter address
\r
95 UnilinkCMD1 equ 42h ; CMD1 byte
\r
96 UnilinkCMD2 equ 43h ; CMD2 byte
\r
97 UnilinkParity1 equ 44h ; First or only parity byte for short packets (6 bytes)
\r
98 UnilinkData1 equ 45h ; Extra data for medium/large packets, or zero for short packets
\r
99 UnilinkData2 equ 46h ;
\r
100 UnilinkData3 equ 47h ;
\r
101 UnilinkData4 equ 48h ;
\r
102 UnilinkData5 equ 49h ; Data5 if this is a large packet
\r
103 UnilinkParity2M equ 49h ; Parity2 shares the same byte if it's a medium sized packet
\r
104 UnilinkData6 equ 4ah ; Extra data for large packets, or zero for medium packets
\r
105 UnilinkData7 equ 4bh ;
\r
106 UnilinkData8 equ 4ch ;
\r
107 UnilinkData9 equ 4dh ;
\r
108 UnilinkParity2 equ 4eh ; Parity byte for large packets
\r
109 UnilinkZero equ 4fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)
\r
111 IRQPCLATH equ 7dh ; ISH storage
\r
112 IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks
\r
117 ;----------------------------------------------------------------
\r
118 ; Power up/Reset starting point
\r
120 org 0 ; Start at the beginning of memory (the reset vector)
\r
121 call Bootstrap ; Call Flash Load routine
\r
122 call LCDInit ; Initialize LCD I/F
\r
123 call IRQInit ; Set up and start the IRQ handler
\r
124 goto Main ; Run the main program loop (skip the IRQ handler)
\r
126 subtitl "IRQ Handler"
\r
127 ;----------------------------------------------------------------
\r
128 ; Interrupt handler always starts at addr 4
\r
129 ; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.
\r
130 ; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR
\r
131 ; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to
\r
132 ; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low
\r
133 ; again after that. Other interrupts might introduce latencies, but let's see how this works..
\r
135 org 4 ; ISR vector is at address 4
\r
136 movwf IRQW ; Save W
\r
137 swapf STATUS,w ; Get the status register into w
\r
138 clrf STATUS ; Zero out the status reg, gives Reg Bank0
\r
139 movwf IRQSTATUS ; Store the STATUS reg
\r
140 movf PCLATH,w ; Get the PCLATH reg
\r
141 ; movwf IRQPCLATH ; And store it
\r
142 ; clrf PCLATH ; Go to low memory
\r
143 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)
\r
145 btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK)
\r
146 goto IRQNotINT ; No it's not, check the other sources
\r
148 ; 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
149 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction
\r
150 ; 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
151 ; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of
\r
152 ; 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
153 ; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX
\r
154 ; 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
155 ; here, otherwise collisions will occur..
\r
156 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an
\r
157 ; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here
\r
158 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)
\r
160 movlw 8 ; Loop through the 8 bits
\r
162 movf UnilinkTXRX,w ; Get the pointer
\r
163 movwf FSR ; Store it to make use of indirect addressing
\r
166 btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out)
\r
167 goto IRQINTTristate ; Bit is low, we should tristate bit
\r
168 bcf PORTC,3 ; Otherwise set DATA bit low
\r
169 bsf STATUS,RP0 ; Select high regs
\r
170 bcf TRISC,3 ; And pull low (now it's an output)
\r
171 bcf STATUS,RP0 ; Back to regbank 0
\r
172 goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in
\r
175 bsf STATUS,RP0 ; Select high regs
\r
176 bsf TRISC,3 ; Force the bit to be tristated
\r
177 bcf STATUS,RP0 ; Back to regbank 0
\r
180 btfss PORTC,2 ; Check for BUSON
\r
182 btfsc PORTB,0 ; Wait for clock to go low
\r
183 goto IRQINTCLKWaitLow
\r
186 btfss PORTC,3 ; Test DATA
\r
187 setc ; Set carry if data is LOW (data is inverted!)
\r
188 rlf INDF,f ; Shift it into the "accumulator"
\r
190 decfsz DataCount,f ; Loop once more perhaps?
\r
191 goto IRQINTCLKWaitHigh ; Yes, again!
\r
192 goto IRQINTRecvDone ; No it's done, don't check for clock to go high again
\r
195 btfss PORTC,2 ; Check for BUSON
\r
197 btfss PORTB,0 ; Wait for clock to go high
\r
198 goto IRQINTCLKWaitHigh
\r
199 goto IRQINTBitSet ; Loop again
\r
201 ; Successfully received a byte here, run it through a state machine to figure out what to do
\r
202 ; (several possibilites exists here):
\r
203 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero
\r
204 ; If receive counter is zero and the received byte is a zero byte, discard it
\r
205 ; Otherwise store the byte in our receive buffer and increment receive counter
\r
206 ; 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
207 ; 00 = short 6 byte packet
\r
208 ; 10 = medium 11 byte packet
\r
209 ; 11 = long 16 byte packet
\r
210 ; Update the receive length byte accordingly
\r
211 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing
\r
212 ; the packet and take appropriate action.
\r
215 clrf SlaveBreakState ; First of all, clear the break state - this got in the way, restart detection..
\r
216 movf UnilinkTXRX,w ; Find out which byte # that was received
\r
218 bnz IRQINTRecvNotFirst ; Not the first byte
\r
219 movf UnilinkRAD,w ; Get the first byte received
\r
220 bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter
\r
222 incf UnilinkTXRX,f ; Increment address
\r
224 movf UnilinkTXRX,w ; Get the byte position again
\r
225 andlw 0fh ; Only lower 4 bits of interest
\r
226 xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet)
\r
227 bnz IRQINTRecvNotCMD1 ; No, skip the length code for now
\r
228 movlw 6 ; Assume it's a short packet
\r
229 btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long
\r
230 goto IRQINTRecvShort ; Nope, it's a short packet
\r
231 addlw 5 ; OK, it's long or medium at least
\r
232 btfsc INDF,6 ; Test for long
\r
233 addlw 5 ; Yep, it's a long packet
\r
235 movwf UnilinkCmdLen ; Store the length
\r
238 movf UnilinkTXRX,w ; Get the byte position
\r
239 xorwf UnilinkCmdLen,w ; XOR with the calculated command length
\r
240 andlw 0fh ; and mask - this results in a zero result when finished receiving
\r
241 bnz IRQINTRecvIncomplete ; Packet not ready yet
\r
243 ; Here a packet is actually received a packet, should check the checksum(s) as well, but I don't care right now
\r
244 ; (I need music in my car! :))
\r
245 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here
\r
246 ; (there won't be any more communication for at least another 4.8ms))
\r
248 ; Unilink command parser:
\r
250 ; Check for CMD1 = 01h (System bus commands)
\r
253 bnz IRQINTParseNot01
\r
255 ; Check for 01 00 (Bus Re-Initialization)
\r
258 bnz IRQINTParseNot0100
\r
260 clrf UnilinkID ; Clear the existing Unilink ID, if any
\r
261 bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID
\r
263 goto IRQINTParseComplete ; Don't send any reply to this
\r
267 ; Check for 01 02 (Anyone)
\r
270 bnz IRQINTParseNot0102
\r
272 movf UnilinkID,w ; Do I have an ID already?
\r
273 bnz IRQINTParseNot0102 ; Yep, I don't want another one!
\r
275 movlw 10h ; Sending to Master
\r
277 movlw 0d0h ; I'm in the MD changer group
\r
279 movlw 8ch ; Device discovery command reply
\r
283 movlw 6ch ; Hard coded parity (!)
\r
284 movwf UnilinkParity1
\r
285 movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??
\r
293 movlw 0deh ; Hard coded parity 2 (!)
\r
296 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
300 ; Check for 01 12 (Time poll)
\r
303 bnz IRQINTParseNot0112
\r
306 xorwf UnilinkID,w ; Is it for me?
\r
307 bnz IRQINTParseNot0112 ; Nope
\r
309 clrf UnilinkParity1
\r
310 movlw 10h ; Sending to Master
\r
311 addwf UnilinkParity1,f
\r
313 movf UnilinkID,w ; This is my ID
\r
314 addwf UnilinkParity1,f
\r
317 addwf UnilinkParity1,f
\r
320 movlw 80h ; Idle unless selected
\r
321 btfsc UnilinkSelected,7
\r
324 addwf UnilinkParity1,f
\r
327 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
333 ; Check for CMD1 = 02h (Appoint)
\r
336 bnz IRQINTParseNot02
\r
338 bsf BUSON_OUT_BIT ; Now activate the cascade BUSON pin, to allow others to be discovered
\r
340 movf UnilinkRAD,w ; Get the ID the master has given me
\r
341 movwf UnilinkID ; Store my id
\r
342 movf UnilinkCMD2,w ; Get the bitmask
\r
343 movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding)
\r
345 clrf UnilinkParity1
\r
346 movlw 10h ; Sending to Master
\r
347 addwf UnilinkParity1,f
\r
349 movf UnilinkID,w ; This is my ID
\r
350 addwf UnilinkParity1,f
\r
352 movlw 8ch ; Device discovery command again
\r
353 addwf UnilinkParity1,f
\r
356 addwf UnilinkParity1,f
\r
359 movf UnilinkParity1,w
\r
360 movwf UnilinkParity2M ; That's the parity when sending medium messages
\r
363 addwf UnilinkParity2M,f
\r
365 movlw 2ch ; My internal MD sends 1c here... (external/internal difference)
\r
366 addwf UnilinkParity2M,f
\r
369 addwf UnilinkParity2M,f
\r
372 addwf UnilinkParity2M,f
\r
376 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
380 ; Check for CMD1 = 87h (Power control)
\r
383 bnz IRQINTParseNot87
\r
385 ; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)
\r
386 btfsc UnilinkCMD2,3
\r
387 goto IRQINTParse87PowerOn
\r
389 bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels)
\r
390 goto IRQINTParseComplete
\r
392 IRQINTParse87PowerOn
\r
393 bcf RS232_RI_BIT ; Clear this to make RI pin go high (waking the computer)
\r
394 goto IRQINTParseComplete
\r
398 ; Check for CMD1 = f0h (Source Select)
\r
401 bnz IRQINTParseNotF0
\r
404 xorwf UnilinkID,w ; Check if it's selecting me
\r
405 bnz IRQINTParseF0Deselect
\r
407 bsf UnilinkSelected,7 ; Now we're selected
\r
408 goto IRQINTParseComplete
\r
410 IRQINTParseF0Deselect
\r
412 bcf UnilinkSelected,7 ; Now we're de-selected
\r
413 goto IRQINTParseComplete
\r
417 IRQINTParseComplete
\r
419 ; The CPU ends up here when parsing is complete and it's not interested in sending any reply back to the master
\r
420 ; (that's why we clear out all the packet buffer bytes)
\r
421 ; TODO: Replace this with an FSR access to save space and make the code neater
\r
427 clrf UnilinkParity1
\r
437 clrf UnilinkParity2
\r
440 IRQINTParseBypassClear
\r
442 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
443 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
445 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
448 IRQINTRecvIncomplete
\r
452 movwf DataStore ; Store it so the non-irq code can snoop
\r
455 bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again
\r
459 btfss PIR1,TMR2IF ; Check if it's the TMR2 interrupt (0.5ms timing)
\r
460 goto IRQNotTMR2 ; No it's not, check the other sources
\r
462 ; Slave break opportunity detection here - the logic works as follows:
\r
463 ; Look for a data low period of at least 5 ms (10 loops)
\r
464 ; Look for a data high period of at least 2 ms (4 loops)
\r
465 ; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 5ms (10 loops)
\r
466 ; If a packet would be received the packet handler automatically clears out the SlaveBreakState, which means start all over
\r
468 ; incf Counter,f ; Increment the general purpose counter
\r
470 btfsc SlaveBreakState,5 ; Check if already pulling the data line low
\r
471 goto IRQTMR2SlaveBreak
\r
473 btfsc SlaveBreakState,7 ; Looking for low or high data
\r
474 goto IRQTMR2HighData
\r
475 btfss DATA_BIT ; Looking for a low data line, if it's low, increment state, if it's high, reset state
\r
476 goto IRQTMR2LowDataOK
\r
477 clrf SlaveBreakState ; Got a high data line while waiting for a low one, reset state
\r
478 goto IRQAfterTMR2 ; Leave ISR
\r
481 btfsc DATA_BIT ; Looking for a high data line, if it's high - increment state, otherwise wait
\r
482 goto IRQTMR2HighDataOK
\r
484 btfss SlaveBreakState,6 ; Test the "first time around" bit
\r
485 clrw ; Not the beginning of the state, have to restart the entire thing now, not just this state
\r
486 andwf SlaveBreakState,f ; Mask out the 1 upper control bits and restart this state
\r
491 bsf SlaveBreakState,6 ; Set the "first time around" bit
\r
492 btfss SlaveBreakState,4 ; Only increment to 0x10
\r
493 incf SlaveBreakState,f
\r
495 movf SlaveBreakState,w
\r
498 btfss SlaveBreakState,7 ; Checking whether it's low or high
\r
499 goto IRQTMR2FoundLow
\r
501 xorlw 4 ; It's high now, and if 4 periods have passed we can activate slave break
\r
505 ; Issue slave break here
\r
507 ; clrf SlaveBreakState
\r
509 movwf SlaveBreakState
\r
520 movlw 80h ; Prepare for state 2, looking for data line high
\r
521 movwf SlaveBreakState
\r
525 movf SlaveBreakState,w
\r
533 clrf SlaveBreakState
\r
536 bcf PIR1,TMR2IF ; Clear the IRQ source bit to re-enable TMR2 interrupts again
\r
540 ; Finally restore CPU state and return from the ISR
\r
542 ; If I have to save the FSR in the beginning I also need to restore it here...
\r
545 ; movwf PCLATH ; Restore PCLATH
\r
547 movwf STATUS ; Restore STATUS
\r
549 swapf IRQW,w ; Restore W
\r
550 retfie ; Interrupt return
\r
553 subtitl "Main loop"
\r
556 ;----------------------------------------------------------------
\r
557 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]
\r
559 ; org 100h ; Maybe not force this to a specific address later
\r
564 movlw low StartUpText1 ; Show something on the LCD
\r
569 bcf LCD_RS_BIT ; LCD Command mode
\r
570 movlw 80h ; DisplayRam 0
\r
575 movf Counter,w ; Debug timer
\r
576 btfsc PORTA,4 ; Test RST
\r
581 movf SlaveBreakState,w
\r
582 btfsc PORTB,0 ; Test CLK
\r
587 btfsc PORTC,2 ; Test BUSON-IN
\r
592 btfsc PORTC,3 ; Test DATA
\r
596 movf UnilinkCmdLen,w
\r
597 bz MainDontPrintCmd
\r
603 movf DataCount,w ; Load bit counter (if 0 then byte is available)
\r
607 decf DataCount,f ; Set it non-zero
\r
610 call BootTXB ; Send to terminal
\r
614 ;----------------------------------------------------------------
\r
615 ; IRQInit - Sets up the IRQ Handler
\r
616 ; Set up Timer2 to generate 2000 interrupts per second, used for timing - 1/16 prescaler and a PR2 reg of 156 (0x9c) is set
\r
620 ; Start with clearing the Unilink packet buffer before enabling any interrupts, otherwise the first packet might become corrupt
\r
621 ; TODO: Replace this with FSR access
\r
622 clrf UnilinkSelected
\r
630 clrf UnilinkParity1
\r
640 clrf UnilinkParity2
\r
644 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
645 movwf UnilinkTXRX ; Store it
\r
647 clrf SlaveBreakState ; Zero out the status, we're starting from the beginning
\r
649 ; Fix the output state of RI and BUSON_OUT to a safe default
\r
651 bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)
\r
652 bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first
\r
654 movlw 06h ; Timer2 enabled + 1/16 prescaler
\r
657 bsf STATUS,RP0 ; Reg bank 1
\r
659 movlw 09ch ; Timer PR2 reg giving 2000 interrupts per second
\r
662 bcf RS232_RI_BIT ; Both bits should be outputs
\r
663 bcf BUSON_OUT_BIT ;
\r
665 ; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...
\r
666 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge
\r
668 bsf INTCON,INTE ; Enable the RB0/INT
\r
669 bsf INTCON,PEIE ; Enable the peripheral interrupts
\r
670 bsf PIE1,TMR2IE ; Enable the Timer2 peripheral interrupt
\r
671 bsf INTCON,GIE ; Enable global interrupts
\r
673 bsf TXSTA,TXEN ; Enable UART TX
\r
675 bcf STATUS,RP0 ; Back to bank 0
\r
677 bsf RCSTA,SPEN ; Enable serial port
\r
678 bsf RCSTA,CREN ; Enable UART RX
\r
682 ;----------------------------------------------------------------
\r
683 ; Initialize LCD Controller...
\r
686 clrf PORTB ; First clear PortB data register
\r
687 bsf STATUS,RP0 ; Reg bank 1
\r
688 movlw 001h ; All but RB0 are outputs.
\r
691 bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up
\r
692 bcf STATUS,RP0 ; Restore Reg bank 0
\r
694 ; This is a standard reset sequence for the LCD controller
\r
696 movlw 160 ; Need to delay for at least 15ms, let's go for 16ms delay
\r
699 movlw 3 ; Write 3 to the LCD
\r
700 call TxLCD ; Send to LCD
\r
701 movlw 50 ; Need to delay for at least 4.1ms, let's go for 5ms delay
\r
704 movlw 3 ; Write 3 to the LCD
\r
706 movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay
\r
709 movlw 3 ; Write 3 to the LCD
\r
711 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
714 movlw 2 ; 4-bit interface requested
\r
716 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
719 ; Reset sequence ends here
\r
720 ; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits
\r
722 movlw 28h ; Function Select + 4-bit bus + 2-line display
\r
725 movlw 0ch ; Display Control + LCD On (No cursor)
\r
728 movlw 01h ; Clear Display
\r
731 movlw 06h ; Auto Increment cursor position
\r
736 ;----------------------------------------------------------------
\r
738 ; Send a string to the LCD.
\r
743 movlw 80h ; DisplayRam 0
\r
748 movlw 80h+40 ; DisplayRam 40 (row 2)
\r
754 ;----------------------------------------------------------------
\r
756 ; Send a string to the LCD.
\r
759 ; movwf Icount ; Icount = W
\r
761 movwf e_LEN ; Move to e_LEN
\r
763 Txm_lp movf Icount,w ; get the byte
\r
765 incf Icount,f ; ...else ++Icount (table index)
\r
766 call TxLCDB ; Send out the byte
\r
771 ;----------------------------------------------------------------
\r
772 ; TxLCDB - send a byte to the LCD
\r
775 movwf TxTemp ; Store byte to send for a while...
\r
777 bcf temp,0 ; Clear my temp bit
\r
778 btfss LCD_RS_BIT ; Check if we try the correct reg
\r
781 bsf temp,0 ; Indicate RS change
\r
785 call RxLCDB ; Receive byte from LCD, status reg
\r
787 skpz ; If the bit was set, the zero flag is not
\r
790 btfsc temp,0 ; If we had to clear RS reset it now
\r
793 swapf TxTemp,w ; Hi nibble of data to send in lo w bits
\r
794 call TxLCD ; Send them first...
\r
795 movf TxTemp,w ; Then we have the low nibble in low w bits
\r
796 call TxLCD ; And send that one as well
\r
799 ;----------------------------------------------------------------
\r
800 ; RxLCDB - recv a byte from the LCD
\r
803 call RxLCD ; Receive the high nibble
\r
805 swapf LCDWTmp,f ; Swap it back to file
\r
806 call RxLCD ; Receive the low nibble
\r
807 addwf LCDWTmp,w ; Put the nibbles together and return in W
\r
811 ;----------------------------------------------------------------
\r
812 ; TxLCD - send a nibble to the LCD
\r
815 movwf LCDWTmp ; Write nibble to tmp
\r
816 bcf LCD_DB4_BIT ; Clear previous data
\r
821 btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT
\r
823 btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT
\r
825 btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT
\r
827 btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT
\r
830 bsf LCD_E_BIT ; And set E to clock the data into the LCD module
\r
831 nop ; Let it settle
\r
832 bcf LCD_E_BIT ; And clear the Enable again.
\r
833 return ; Returns without modifying W
\r
835 ;----------------------------------------------------------------
\r
836 ; RxLCD - recv a nibble from the LCD
\r
839 clrw ; Clear W register, return data in lower 4 bits
\r
841 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
843 bsf LCD_DB4_BIT ; This sets the port bit as an input
\r
848 bcf STATUS,RP0 ; Back at reg bank 0
\r
850 bsf LCD_RW_BIT ; Set Read mode for the LCD
\r
851 bsf LCD_E_BIT ; And set E to clock the data out of the LCD module
\r
852 nop ; Let the bus settle
\r
853 btfsc LCD_DB4_BIT ; Transfer a set port bit into W
\r
855 btfsc LCD_DB5_BIT ; Transfer a set port bit into W
\r
857 btfsc LCD_DB6_BIT ; Transfer a set port bit into W
\r
859 btfsc LCD_DB7_BIT ; Transfer a set port bit into W
\r
861 bcf LCD_E_BIT ; And clear the Enable again.
\r
862 bcf LCD_RW_BIT ; Set Write mode for the LCD
\r
864 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
866 bcf LCD_DB4_BIT ; Set the port as an output again
\r
871 bcf STATUS,RP0 ; Back at reg bank 0
\r
873 return ; Returns with data in W
\r
875 ;----------------------------------------------------------------------
\r
876 ; Delay routines (non-interrupt based, therefore not even close to reliable)
\r
877 ; W=10 gives ~ 1ms of delay
\r
878 ; 1ms=5000 instructions wasted, 100us=500 cycles
\r
879 ; Maximum time waited will be 256*100us=25.6ms
\r
882 movwf Dcount ; Set delay counter, number of 100us periods to wait
\r
885 movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more
\r
886 movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount
\r
888 decfsz Dcount2,f ; 1 cycle (or two when exiting the loop)
\r
889 goto DelayInner ; 2 cycles
\r
890 decfsz Dcount,f ; Now decrement number of 100us periods and loop again
\r
895 ;----------------------------------------------------------------
\r
896 ; Data can be stored between 600 and 6ffh...
\r
900 DT "----- WJ UniLink"
\r
902 LookUp movwf PCL ; Go to it (this assumes PCLATH == 06h)
\r
905 subtitl "Bootstrap/Bootloader code"
\r
908 ;----------------------------------------------------------------------
\r
909 ; Bootstrap code - Allows PIC to flash itself with data from the async port.
\r
910 ; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory
\r
911 ; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver
\r
912 ; (one good way of solving that is to wait for the echo from the PIC before sending anything else)
\r
913 ; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration
\r
914 ; register can't be programmed. That means that any references to the config register in the hex file will be ignored.
\r
918 ; RAM usage for the bootstrap code
\r
920 BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog
\r
928 BootNumBytes equ 76h
\r
931 BootHEXTemp equ 73h
\r
933 org 738h ; Place the boot code at the top of memory (currently the loader is exactly 200 bytes)
\r
936 bsf STATUS,RP0 ; Access bank 1
\r
937 bsf TXSTA,TXEN ; Enable UART TX
\r
938 movlw 31 ; Divisor for 9k6 @ 20MHz Fosc
\r
939 movwf SPBRG ; Store
\r
940 bcf STATUS,RP0 ; Back to bank 0
\r
942 bsf RCSTA,SPEN ; Enable serial port
\r
943 bsf RCSTA,CREN ; Enable UART RX
\r
945 movlw low BootStartText ; Send boot banner to the serial port
\r
948 movlw 0e8h ; Initialize timeout timer
\r
954 incf BootTimerL,f ; A 24-bit counter
\r
959 skpnz ; When overflowing here..
\r
960 goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program
\r
961 btfss PIR1,RCIF ; Wait for RX to complete
\r
966 goto BootTimeout ; If it wasn't ESC, wait for another key
\r
969 movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port
\r
977 call BootRXB ; First find the ':'
\r
980 goto BootLoop ; Loop until we find it!
\r
982 call BootRXHEX ; Get one ASCII encoded byte (two chars)
\r
983 movwf BootNumBytes ; This is the number of bytes to be programmed on the line
\r
984 ; Maybe clear cary here?
\r
985 rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format
\r
987 ; Note carry should be clear here as there cannot be odd number of bytes in this format
\r
989 call BootRXHEX ; Receive AddrH
\r
991 call BootRXHEX ; Receive AddrL
\r
993 rrf BootAddrH,f ; Fix the addressing again
\r
996 bcf BootBits,2 ; Assume we should program
\r
997 bsf BootBits,1 ; And assume we should program flash not ee
\r
1000 xorlw 020h ; Check if it's configuration, which we can't program
\r
1001 skpnz ; Skip the bit set if it was false alarm
\r
1002 bsf BootBits,2 ; No programming for this line
\r
1004 xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)
\r
1005 skpnz ; Skip the bit set instr if not EE data address
\r
1006 bcf BootBits,1 ; We should program EE, will ignore the AddrH
\r
1008 call BootRXHEX ; Receive Record Type (must be 0 for real records)
\r
1009 skpz ; Check if zero
\r
1010 goto BootFlashComplete
\r
1013 call BootRXHEX ; Receive low-byte of data word
\r
1015 call BootRXHEX ; Receive high-byte of data word
\r
1018 btfsc BootBits,2 ; Check whether this line should be programmed at all
\r
1019 goto BootWriteSkip
\r
1021 bcf BootBits,0 ; Read mode first, verify if we actually have to write
\r
1024 xorwf BootDataL,f ; Compare and destroy DataL
\r
1025 movwf BootDataL ; Write new data to DataL
\r
1026 skpz ; Skip if no difference, have to check high byte as well
\r
1027 goto BootWrite ; Jump directly to write
\r
1030 xorwf BootDataH,f ; Compare
\r
1031 skpnz ; Skip if no difference, no programming necessary
\r
1032 goto BootWriteSkip
\r
1036 movwf BootDataH ; Have to put the new H byte data in as well
\r
1039 call BootEE ; Write directly into program mem
\r
1041 ; Here a verify can take place, the read-back results are now in DataL/H
\r
1045 incf BootAddrL,f ; Advance counter to next addr
\r
1047 incf BootAddrH,f ; And add to high byte if needed
\r
1049 decfsz BootNumBytes,f
\r
1057 movlw low BootRunText
\r
1060 bsf STATUS,RP0 ; Reg bank 1
\r
1062 btfss TXSTA,TRMT ; Wait for last things to flush
\r
1063 goto BootReturnWait
\r
1064 bcf TXSTA,TXEN ; Disable UART TX
\r
1065 bcf STATUS,RP0 ; Back to bank 0
\r
1067 bcf RCSTA,SPEN ; Disable serial port
\r
1068 bcf RCSTA,CREN ; Disable UART RX
\r
1070 return ; Return to code
\r
1072 ;----------------------------------------------------------------------
\r
1073 ; BootTXB - Sends one byte to the UART, waits for transmitter to become
\r
1074 ; free before sending
\r
1078 btfss PIR1,TXIF ; Wait for TX to empty
\r
1080 movwf TXREG ; Send the byte
\r
1083 ;----------------------------------------------------------------------
\r
1084 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated
\r
1087 movwf BootAddrL ; Store LSB of text pointer
\r
1088 movlw 07h ; MSB of pointer to the text (0700h in this boot loader)
\r
1090 movlw 02h ; Select "Read Program Memory" operation
\r
1093 call BootEE ; Lookup char (actually two packed into one word)
\r
1094 rlf BootDataL,w ; Shift the MSB out into carry (that's the 2nd char LSB)
\r
1095 rlf BootDataH,w ; Shift it into 2nd char
\r
1096 call BootTXB ; Send the high byte first
\r
1097 movf BootDataL,w ; Get the low byte
\r
1098 andlw 07fh ; Mask of the highest bit
\r
1099 skpnz ; Stop if zero
\r
1101 call BootTXB ; Send char
\r
1102 incf BootAddrL,f ; Increment pointer
\r
1103 goto BootTXStrLoop
\r
1105 ;----------------------------------------------------------------------
\r
1106 ; BootRXB - Receives one byte from the UART, waits if nothing available
\r
1110 btfss PIR1,RCIF ; Wait for RX to complete
\r
1112 movf RCREG,w ; Get the recvd byte
\r
1113 call BootTXB ; Echo to terminal
\r
1116 ;----------------------------------------------------------------------
\r
1117 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary
\r
1120 call BootRXB ; Receive nibble
\r
1121 addlw -'A' ; Convert from BCD to binary nibble
\r
1122 skpc ; Test if if was 0-9 or A-F, skip if A-F
\r
1123 addlw 'A' - 10 - '0' ; It was numeric '0'
\r
1124 addlw 10 ; Add 10 (A get to be 0ah etc.)
\r
1127 ;----------------------------------------------------------------------
\r
1128 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available
\r
1129 ; Decodes the bytes as ASCII hex and returns a single byte in W
\r
1132 call BootRXHEXNibble
\r
1134 swapf BootHEXTemp,f ; Swap it up to the high nibble
\r
1136 call BootRXHEXNibble
\r
1137 addwf BootHEXTemp,w ; And add the two nibbles together
\r
1140 ;----------------------------------------------------------------------
\r
1141 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the
\r
1142 ; exact action to take. BootAddrL and BootAddrH has to be initialized
\r
1143 ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash
\r
1144 ; The data to be written has to be put in BootDataL and BootDataH, and
\r
1145 ; data will be to the same place when read back
\r
1148 bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0)
\r
1150 movf BootAddrH,w ; Load desired address
\r
1154 movf BootDataH,w ; And load the data (only used when writing)
\r
1159 bsf STATUS,RP0 ; Go to bank 3
\r
1161 bsf EECON1,EEPGD ; Point to Program Flash mem
\r
1162 btfss BootBits,1 ; Test if that was correct or if we have to clear again
\r
1163 bcf EECON1,EEPGD ; Point to EE DATA mem
\r
1165 btfss BootBits,0 ; Check from read or write
\r
1166 goto BootEERD ; Skip the WR if we were going for a read
\r
1168 bsf EECON1,WREN ; Enable writes
\r
1172 movwf EECON2 ; Unlock write operation
\r
1173 bsf EECON1,WR ; And start a write cycle
\r
1175 btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish
\r
1176 goto BootWRLoop ; These two instructions gets NOPed when flashing
\r
1178 bcf EECON1,WREN ; Finally disable writes again
\r
1179 ; Here we read the data back again, can be used as verify
\r
1181 bsf EECON1,RD ; Start a read cycle
\r
1182 nop ; Only necessary for flash read, same thing as when writing above
\r
1183 nop ; Except I could use the two words for something useful there.. :)
\r
1186 bcf STATUS,RP0 ; Back to bank 2
\r
1187 movf EEDATA,w ; Store our EE-data
\r
1191 bcf STATUS,RP1 ; And finally back to bank 0
\r
1195 ; To produce compact code the end zero byte has to be in the LSB (that means an even number of chars in every string)
\r
1197 DW 0x2bca,0x216f,0x37f4,0x102d,0x1070,0x3965,0x39f3,0x1045,0x29c3,0x1074,0x37a0,0x336c,0x30f3,0x3400
\r
1198 ; DE "WJBoot - press ESC to flash\x00"
\r
1200 DW 0x068a,0x29e5,0x3764,0x1049,0x2748,0x2c38,0x1066,0x34ec,0x32a0,0x376f,0x3bae,0x172e,0x0680
\r
1201 ; DE "\r\nSend INHX8 file now...\r\x00"
\r
1203 DW 0x068a,0x22f8,0x34f4,0x34ee,0x33a0,0x366f,0x30e4,0x32f2,0x0680
\r
1204 ; DE "\r\nExiting loader\r\x00"
\r
1207 ;----------------------------------------------------------------------
\r
1208 ; EE Data (64 bytes), located at 2100h
\r
1211 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r