1 title "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 2002-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 ; No checksum checking is done on incoming packets
\r
16 ; Investigate whether I actually have to save PCLATH in ISH, maybe save FSR? - Not saving any of them for now
\r
17 ; Move RS232 code into ISH
\r
18 ; Check Overrun errors from the UART
\r
19 ; Implement lots of other Unilink commands (Text display, time display etc.)
\r
20 ; Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..)
\r
21 ; Make the bit shift routine at the beginning of the ISR timeout if the clock suddenly stops (in the middle of a byte)
\r
22 ; (will keep it from hanging until the next bit gets clocked out, just ignore the faulty bits and carry on)
\r
24 ;----------------------------------------------------------------
\r
26 ;----------------------------------------------------------------
\r
29 ; 0.6 Some more LCD info and clean-up of the Unilink recovery code, some problems with master resetting :(
\r
30 ; 0.5 Issues slave breaks seemingly without hickups (!)
\r
31 ; 0.4 Some fixups in the bootstrap code (I actually had to put the PIC in my PICSTART Plus programmer again :))
\r
32 ; 0.3 Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)
\r
33 ; 0.2 First attempt at responding to the Anyone command
\r
34 ; 0.1 Receives Unilink data OK, relays it to serial
\r
35 ; 0.0 Very first "F**king No Work!" version
\r
37 ;----------------------------------------------------------------
\r
39 ;----------------------------------------------------------------
\r
40 ; Unilink BUSON IN (blue) connected to RC2/CCP1
\r
41 ; Unilink DATA (green) connected to RC3
\r
42 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)
\r
43 ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)
\r
44 ; Unilink RST (lilac) connected to RA4
\r
45 ; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)
\r
46 ; LCD RW connected to pin RB2
\r
47 ; LCD E connected to pin RB3
\r
48 ; LCD DB4-DB7 connected to RB4-RB7
\r
49 ; RS-232 TX from computer connected to RC7/RX
\r
50 ; RS-232 RX to computer connected to RC6/TX
\r
51 ; RS-232 RI to computer connected to RC5
\r
53 ; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...
\r
55 #define BUSON_IN_BIT PORTC,2
\r
56 #define DATA_BIT PORTC,3
\r
57 #define BUSON_OUT_BIT PORTC,4
\r
58 #define CLK_BIT PORTB,0
\r
59 #define RST_BIT PORTA,4
\r
61 #define LCD_RS_BIT PORTB,1
\r
62 #define LCD_RW_BIT PORTB,2
\r
63 #define LCD_E_BIT PORTB,3
\r
64 #define LCD_DB4_BIT PORTB,4
\r
65 #define LCD_DB5_BIT PORTB,5
\r
66 #define LCD_DB6_BIT PORTB,6
\r
67 #define LCD_DB7_BIT PORTB,7
\r
69 #define RS232_RI_BIT PORTC,5
\r
71 ;----------------------------------------------------------------
\r
72 ; FILE REGISTER USAGE
\r
73 ;----------------------------------------------------------------
\r
74 TrackName00 equ 20h ; Buffer for TrackName
\r
100 TrackName1a equ 3ah
\r
101 TrackName1b equ 3bh
\r
102 TrackName1c equ 3ch
\r
103 TrackName1d equ 3dh
\r
104 TrackName1e equ 3eh
\r
105 TrackName1f equ 3fh
\r
106 TrackName20 equ 40h
\r
107 TrackName21 equ 41h
\r
108 TrackName22 equ 42h
\r
109 TrackName23 equ 43h
\r
110 TrackName24 equ 44h
\r
111 TrackName25 equ 45h
\r
112 TrackName26 equ 46h
\r
113 TrackName27 equ 47h
\r
114 TrackName28 equ 48h
\r
115 TrackName29 equ 49h
\r
116 TrackName2a equ 4ah
\r
117 TrackName2b equ 4bh
\r
118 TrackName2c equ 4ch
\r
119 TrackName2d equ 4dh
\r
120 TrackName2e equ 4eh
\r
121 TrackName2f equ 4fh
\r
123 UnilinkRAD equ 50h ; Beginning of Unilink packet - the Receiving Address
\r
124 UnilinkTAD equ 51h ; Transmitter address
\r
125 UnilinkCMD1 equ 52h ; CMD1 byte
\r
126 UnilinkCMD2 equ 53h ; CMD2 byte
\r
127 UnilinkParity1 equ 54h ; First or only parity byte for short packets (6 bytes)
\r
128 UnilinkData1 equ 55h ; Extra data for medium/large packets, or zero for short packets
\r
129 UnilinkData2 equ 56h ;
\r
130 UnilinkData3 equ 57h ;
\r
131 UnilinkData4 equ 58h ;
\r
132 UnilinkData5 equ 59h ; Data5 if this is a large packet
\r
133 UnilinkParity2M equ 59h ; Parity2 shares the same byte if it's a medium sized packet
\r
134 UnilinkData6 equ 5ah ; Extra data for large packets, or zero for medium packets
\r
135 UnilinkData7 equ 5bh ;
\r
136 UnilinkData8 equ 5ch ;
\r
137 UnilinkData9 equ 5dh ;
\r
138 UnilinkParity2 equ 5eh ; Parity byte for large packets
\r
139 UnilinkZero equ 5fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)
\r
141 UnilinkTimeout equ 60h ; Counts up every 0.5ms to "age out" faulty bytes clocked in
\r
142 UnilinkSelected equ 61h ; High bit is set when selected
\r
143 UnilinkBit equ 62h ; This is my "bitmask" to be used for requests
\r
144 UnilinkID equ 63h ; This is my Bus ID
\r
145 UnilinkCmdLen equ 64h ; This gets updated with the actual packet length after CMD1 has been received
\r
146 UnilinkTXRX equ 65h ; This is a pointer to the Unilink packet above, used with indirect addressing
\r
147 SlaveBreakState equ 66h ; Hold state and time-out information about slave break, indicates when it can happen
\r
148 DisplayStatus equ 67h ; What information will be put on the display next, bit 7 cleared if nothing
\r
149 Icount equ 68h ; Offset of string to print
\r
150 TxTemp equ 69h ; blahblah
\r
151 TxTemp2 equ 6ah ; Blahblah2
\r
160 DataCount equ 71h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)
\r
161 DataStore equ 72h ; This is a kludge
\r
162 DisplayCounter equ 73h
\r
163 UnilinkAttenuation equ 74h ; The amount of attenuation the volume control is currently set to
\r
164 IRQPCLATH equ 7dh ; ISH storage
\r
165 IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks
\r
168 DiscName00 equ 0a0h ; Buffer for DiscName
\r
169 DiscName01 equ 0a1h
\r
170 DiscName02 equ 0a2h
\r
171 DiscName03 equ 0a3h
\r
172 DiscName04 equ 0a4h
\r
173 DiscName05 equ 0a5h
\r
174 DiscName06 equ 0a6h
\r
175 DiscName07 equ 0a7h
\r
176 DiscName08 equ 0a8h
\r
177 DiscName09 equ 0a9h
\r
178 DiscName0a equ 0aah
\r
179 DiscName0b equ 0abh
\r
180 DiscName0c equ 0ach
\r
181 DiscName0d equ 0adh
\r
182 DiscName0e equ 0aeh
\r
183 DiscName0f equ 0afh
\r
184 DiscName10 equ 0b0h
\r
185 DiscName11 equ 0b1h
\r
186 DiscName12 equ 0b2h
\r
187 DiscName13 equ 0b3h
\r
188 DiscName14 equ 0b4h
\r
189 DiscName15 equ 0b5h
\r
190 DiscName16 equ 0b6h
\r
191 DiscName17 equ 0b7h
\r
192 DiscName18 equ 0b8h
\r
193 DiscName19 equ 0b9h
\r
194 DiscName1a equ 0bah
\r
195 DiscName1b equ 0bbh
\r
196 DiscName1c equ 0bch
\r
197 DiscName1d equ 0bdh
\r
198 DiscName1e equ 0beh
\r
199 DiscName1f equ 0bfh
\r
203 ;----------------------------------------------------------------
\r
204 ; Power up/Reset starting point
\r
206 org 0 ; Start at the beginning of memory (the reset vector)
\r
207 call Bootstrap ; Call Flash Load routine
\r
208 call LCDInit ; Initialize LCD I/F
\r
209 call IRQInit ; Set up and start the IRQ handler
\r
210 goto Main ; Run the main program loop (skip the IRQ handler)
\r
212 subtitl "IRQ Handler"
\r
213 ;----------------------------------------------------------------
\r
214 ; Interrupt handler always starts at addr 4
\r
215 ; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.
\r
216 ; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR
\r
217 ; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to
\r
218 ; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low
\r
219 ; again after that. Other interrupts might introduce latencies, but let's see how this works..
\r
221 org 4 ; ISR vector is at address 4
\r
222 movwf IRQW ; Save W
\r
223 swapf STATUS,w ; Get the status register into w
\r
224 clrf STATUS ; Zero out the status reg, gives Reg Bank0
\r
225 movwf IRQSTATUS ; Store the STATUS reg
\r
226 ; Not using PCLATH for anything in the ISR right now
\r
227 ; movf PCLATH,w ; Get the PCLATH reg
\r
228 ; movwf IRQPCLATH ; And store it
\r
229 ; clrf PCLATH ; Go to low memory
\r
230 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)
\r
232 btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK)
\r
233 goto IRQNotINT ; No it's not, check the other sources
\r
235 ; 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
236 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction
\r
237 ; 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
238 ; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of
\r
239 ; 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
240 ; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX
\r
241 ; 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
242 ; here, otherwise collisions will occur..
\r
243 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an
\r
244 ; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here
\r
245 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)
\r
247 movlw 8 ; Loop through the 8 bits
\r
249 movf UnilinkTXRX,w ; Get the pointer
\r
250 movwf FSR ; Store it to make use of indirect addressing
\r
253 btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out)
\r
254 goto IRQINTTristate ; Bit is low, we should tristate bit
\r
255 bcf PORTC,3 ; Otherwise set DATA bit low
\r
256 bsf STATUS,RP0 ; Select high regs
\r
257 bcf TRISC,3 ; And pull low (now it's an output)
\r
258 bcf STATUS,RP0 ; Back to regbank 0
\r
259 goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in
\r
262 bsf STATUS,RP0 ; Select high regs
\r
263 bsf TRISC,3 ; Force the bit to be tristated
\r
264 bcf STATUS,RP0 ; Back to regbank 0
\r
267 btfss PORTC,2 ; Check for BUSON
\r
269 btfsc PORTB,0 ; Wait for clock to go low
\r
270 goto IRQINTCLKWaitLow
\r
273 btfss PORTC,3 ; Test DATA
\r
274 setc ; Set carry if data is LOW (data is inverted!)
\r
275 rlf INDF,f ; Shift it into the "accumulator"
\r
277 decfsz DataCount,f ; Loop once more perhaps?
\r
278 goto IRQINTCLKWaitHigh ; Yes, again!
\r
279 goto IRQINTRecvDone ; No it's done, don't check for clock to go high again
\r
282 btfss PORTC,2 ; Check for BUSON
\r
284 btfss PORTB,0 ; Wait for clock to go high
\r
285 goto IRQINTCLKWaitHigh
\r
286 goto IRQINTBitSet ; Loop again
\r
288 ; Successfully received a byte here, run it through a state machine to figure out what to do
\r
289 ; (several possibilites exists here):
\r
290 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero
\r
291 ; If receive counter is zero and the received byte is a zero byte, discard it
\r
292 ; Otherwise store the byte in our receive buffer and increment receive counter
\r
293 ; 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
294 ; 00 = short 6 byte packet
\r
295 ; 10 = medium 11 byte packet
\r
296 ; 11 = long 16 byte packet
\r
297 ; Update the receive length byte accordingly
\r
298 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing
\r
299 ; the packet and take appropriate action.
\r
302 clrf SlaveBreakState ; First of all, clear the break state - this got in the way, restart detection..
\r
303 movf UnilinkTXRX,w ; Find out which byte # that was received
\r
305 bnz IRQINTRecvNotFirst ; Not the first byte
\r
306 movf UnilinkRAD,w ; Get the first byte received
\r
307 bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter
\r
309 incf UnilinkTXRX,f ; Increment address
\r
311 movf UnilinkTXRX,w ; Get the byte position again
\r
312 andlw 0fh ; Only lower 4 bits of interest
\r
313 xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet)
\r
314 bnz IRQINTRecvNotCMD1 ; No, skip the length code for now
\r
315 movlw 6 ; Assume it's a short packet
\r
316 btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long
\r
317 goto IRQINTRecvShort ; Nope, it's a short packet
\r
318 addlw 5 ; OK, it's long or medium at least
\r
319 btfsc INDF,6 ; Test for long
\r
320 addlw 5 ; Yep, it's a long packet
\r
322 movwf UnilinkCmdLen ; Store the length
\r
325 movf UnilinkTXRX,w ; Get the byte position
\r
326 xorwf UnilinkCmdLen,w ; XOR with the calculated command length
\r
327 andlw 0fh ; and mask - this results in a zero result when finished receiving
\r
328 bnz IRQINTRecvIncomplete ; Packet not ready yet
\r
330 ; Here a packet is actually received, should check the checksum(s) as well, but I don't care right now
\r
331 ; (I need music in my car! :))
\r
332 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here
\r
333 ; (there won't be any more communication for at least another 4.8ms))
\r
335 ; Unilink command parser:
\r
337 ; Check for CMD1 = 01h (System bus commands)
\r
340 bnz IRQINTParseNot01
\r
342 ; Check for 01 00 (Bus Re-Initialization)
\r
344 bnz IRQINTParseNot0100
\r
346 call ClearUnilinkStatus ; Clear everything Unilink (ID, BUSON_OUT)
\r
348 goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though)
\r
352 ; Check for 01 02 (Anyone)
\r
355 bnz IRQINTParseNot0102
\r
357 movf UnilinkID,w ; Do I have an ID already?
\r
358 bnz IRQINTParseNot0102 ; Yep, I don't want another one!
\r
360 ; clrf UnilinkParity1
\r
361 call ClearUnilinkBuffer ; Zero it out completely
\r
363 movlw 10h ; Sending to Master
\r
364 addwf UnilinkParity1,f
\r
366 movlw 0d0h ; I'm in the MD changer group
\r
367 addwf UnilinkParity1,f
\r
369 movlw 8ch ; Device discovery command reply
\r
370 addwf UnilinkParity1,f
\r
373 addwf UnilinkParity1,f
\r
376 movf UnilinkParity1,w
\r
377 movwf UnilinkParity2M
\r
379 movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??
\r
380 addwf UnilinkParity2M,f
\r
383 addwf UnilinkParity2M,f
\r
386 addwf UnilinkParity2M,f
\r
388 movlw 0a0h ; 00?? 0a0=10 disc?
\r
389 addwf UnilinkParity2M,f
\r
392 ; clrf UnilinkData6
\r
393 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
397 ; Check for 01 12 (Time poll)
\r
400 bnz IRQINTParseNot0112
\r
403 xorwf UnilinkID,w ; Is it for me?
\r
404 bnz IRQINTParseNot0112 ; Nope
\r
406 ; clrf UnilinkParity1
\r
407 call ClearUnilinkBuffer
\r
408 movlw 10h ; Sending to Master
\r
409 addwf UnilinkParity1,f
\r
411 movf UnilinkID,w ; This is my ID
\r
412 addwf UnilinkParity1,f
\r
415 addwf UnilinkParity1,f
\r
418 movlw 80h ; Idle unless selected
\r
419 btfsc UnilinkSelected,7
\r
422 addwf UnilinkParity1,f
\r
424 ; clrf UnilinkData1
\r
425 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
429 ; Check for 01 13 (Request Time poll)
\r
432 bnz IRQINTParseNot0113
\r
435 xorwf UnilinkID,w ; Is it for me?
\r
436 bnz IRQINTParseNot0113 ; Nope
\r
438 btfss DisplayStatus,7 ; If not displaying, skip this
\r
439 goto IRQINTParseComplete
\r
441 ; clrf UnilinkParity1
\r
442 call ClearUnilinkBuffer
\r
443 movlw 70h ; Sending to Display Group
\r
444 addwf UnilinkParity1,f
\r
446 movf UnilinkID,w ; This is my ID
\r
447 addwf UnilinkParity1,f
\r
450 addwf UnilinkParity1,f
\r
453 addwf UnilinkParity1,f
\r
456 movf UnilinkParity1,w ; Carry the parity forward
\r
457 movwf UnilinkParity2M
\r
460 movf DisplayStatus,w
\r
461 addwf UnilinkParity2M,f
\r
464 addwf UnilinkParity2M,f
\r
467 addwf UnilinkParity2M,f
\r
470 movf DisplayStatus,w
\r
472 addwf UnilinkParity2M,f
\r
475 ; clrf UnilinkData6
\r
477 incf DisplayStatus,f ; Temporary debug info
\r
478 bsf DisplayStatus,7
\r
480 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
484 ; Check for 01 15 (Who sent the slave break?)
\r
487 bnz IRQINTParseNot0115
\r
489 btfss DisplayStatus,7 ; First of all check if there should be anything displayed
\r
490 goto IRQINTParseComplete ; No, not at this time
\r
492 ; clrf UnilinkParity1
\r
493 call ClearUnilinkBuffer
\r
494 movlw 10h ; Sending to Master
\r
495 addwf UnilinkParity1,f
\r
497 movlw 18h ; Broadcast address sending in this special case
\r
498 addwf UnilinkParity1,f
\r
500 movlw 82h ; Who wants to talk reply command
\r
501 addwf UnilinkParity1,f
\r
506 addwf UnilinkParity1,f
\r
509 movf UnilinkParity1,w ; Carry the parity forward
\r
510 movwf UnilinkParity2M
\r
514 addwf UnilinkParity2M,f
\r
518 addwf UnilinkParity2M,f
\r
522 addwf UnilinkParity2M,f
\r
526 addwf UnilinkParity2M,f
\r
529 ; clrf UnilinkData6
\r
531 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
533 ;******************************************************************************
\r
534 ; Bit frig - works out which bit to set in the response to Master Poll
\r
536 ; W register is input of which stage you are on (0x00, 0x20, 0x30, 0x40 etc)
\r
537 ; and is returned with the byte to write (0x00 if wrong stage).
\r
540 xorwf UnilinkBit, 0
\r
541 andlw 0xe0 ; Strip off low bits
\r
543 btfsc STATUS, Z ; Do we have a hit?
\r
550 btfss UnilinkBit, 4 ; Do we need to swap nybbles?
\r
558 swapf UnilinkBit, 0
\r
566 ; Check for CMD1 = 02h (Appoint)
\r
569 bnz IRQINTParseNot02
\r
571 bsf BUSON_OUT_BIT ; Now activate the cascade BUSON pin, to allow others to be discovered
\r
573 movf UnilinkRAD,w ; Get the ID the master has given me
\r
574 movwf UnilinkID ; Store my id
\r
575 movf UnilinkCMD2,w ; Get the bitmask
\r
576 movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding)
\r
578 ; clrf UnilinkParity1
\r
579 call ClearUnilinkBuffer
\r
580 movlw 10h ; Sending to Master
\r
581 addwf UnilinkParity1,f
\r
583 movf UnilinkID,w ; This is my ID
\r
584 addwf UnilinkParity1,f
\r
586 movlw 8ch ; Device discovery command again
\r
587 addwf UnilinkParity1,f
\r
590 addwf UnilinkParity1,f
\r
593 movf UnilinkParity1,w
\r
594 movwf UnilinkParity2M ; That's the parity when sending medium messages
\r
597 addwf UnilinkParity2M,f
\r
599 movlw 0a8h ; My internal MD sends 1c here... (external/internal difference)
\r
600 addwf UnilinkParity2M,f
\r
603 addwf UnilinkParity2M,f
\r
605 movlw 0a0h ; 0a0=10disc
\r
606 addwf UnilinkParity2M,f
\r
609 ; clrf UnilinkData6
\r
610 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
614 ; Check for CMD1 = 87h (Power control)
\r
617 bnz IRQINTParseNot87
\r
619 ; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)
\r
620 btfsc UnilinkCMD2,3
\r
621 goto IRQINTParse87PowerOn
\r
623 bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels)
\r
624 goto IRQINTParseComplete
\r
626 IRQINTParse87PowerOn
\r
627 bcf RS232_RI_BIT ; Clear this to make RI pin go high (waking the computer)
\r
628 goto IRQINTParseComplete
\r
632 ; Check for CMD1 = 90h (Display/DSP info, volume etc.)
\r
635 bnz IRQINTParseNot90
\r
637 ; Check for 90 10 (Current Volume)
\r
640 bnz IRQINTParseNot9010
\r
642 movf UnilinkData1,w ; Store current volume setting
\r
643 movwf UnilinkAttenuation
\r
645 goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though)
\r
652 ; Check for CMD1 = f0h (Source Select)
\r
655 bnz IRQINTParseNotF0
\r
658 xorwf UnilinkID,w ; Check if it's selecting me
\r
659 bnz IRQINTParseF0Deselect
\r
661 bsf UnilinkSelected,7 ; Now we're selected
\r
662 bsf DisplayStatus,7
\r
663 goto IRQINTParseComplete
\r
665 IRQINTParseF0Deselect
\r
667 bcf UnilinkSelected,7 ; Now we're de-selected
\r
668 bcf DisplayStatus,7
\r
669 goto IRQINTParseComplete
\r
673 IRQINTParseComplete
\r
675 ; The code ends up here when parsing is complete and it's not interested in sending any reply back to the master
\r
676 ; (that's why we clear out all the packet buffer bytes)
\r
678 call ClearUnilinkBuffer
\r
680 IRQINTParseBypassClear
\r
682 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
683 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
685 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
688 IRQINTRecvIncomplete
\r
692 movwf DataStore ; Store it so the non-irq code can snoop
\r
695 bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again
\r
699 btfss PIR1,TMR2IF ; Check if it's the TMR2 interrupt (0.5ms timing)
\r
700 goto IRQNotTMR2 ; No it's not, check the other sources
\r
702 incf Counter,f ; Increment the general purpose counter (increments every 0.5ms)
\r
704 ; Slave break opportunity detection here - the logic works as follows:
\r
705 ; Look for a data low period of at least 5 ms (10 loops)
\r
706 ; Look for a data high period of at least 2 ms (4 loops)
\r
707 ; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 4ms (8 loops)
\r
708 ; If a bit would be received (CLK activates) the packet handler automatically clears out the SlaveBreakState, which means start all over
\r
710 btfsc SlaveBreakState,5 ; Check if already pulling the data line low
\r
711 goto IRQTMR2SlaveBreak
\r
713 btfsc SlaveBreakState,7 ; Looking for low or high data
\r
714 goto IRQTMR2HighData
\r
715 btfss DATA_BIT ; Looking for a low data line, if it's low, increment state, if it's high, reset state
\r
716 goto IRQTMR2LowDataOK
\r
717 clrf SlaveBreakState ; Got a high data line while waiting for a low one, reset state
\r
718 goto IRQAfterTMR2 ; Leave ISR
\r
721 btfsc DATA_BIT ; Looking for a high data line, if it's high - increment state, otherwise wait
\r
722 goto IRQTMR2HighDataOK
\r
724 btfsc SlaveBreakState,6 ; Test the "first time around" bit
\r
725 clrw ; Not the beginning of the state, have to restart the entire thing now, not just this state
\r
726 andwf SlaveBreakState,f ; Mask out the 1 upper control bits and restart this state
\r
731 bsf SlaveBreakState,6 ; Set the "first time around" bit
\r
733 movf SlaveBreakState,w
\r
736 btfss SlaveBreakState,7 ; Checking whether it's low or high
\r
737 goto IRQTMR2FoundLow
\r
739 xorlw 4 ; It's high now, and if 4 periods have passed we can activate slave break
\r
743 ; Issue slave break here
\r
745 clrf SlaveBreakState
\r
749 btfss DisplayStatus,7 ; Only do this if high bit is set
\r
753 movwf SlaveBreakState
\r
764 movlw 80h ; Prepare for state 2, looking for data line high
\r
765 movwf SlaveBreakState
\r
769 movf SlaveBreakState,w
\r
777 clrf SlaveBreakState
\r
780 btfss SlaveBreakState,4 ; Only increment to 0x10
\r
781 incf SlaveBreakState,f
\r
782 bcf PIR1,TMR2IF ; Clear the IRQ source bit to re-enable TMR2 interrupts again
\r
786 ; Finally restore CPU state and return from the ISR
\r
788 ; If I have to save the FSR in the beginning I also need to restore it here...
\r
791 ; movwf PCLATH ; Restore PCLATH
\r
793 movwf STATUS ; Restore STATUS
\r
795 swapf IRQW,w ; Restore W
\r
796 retfie ; Interrupt return
\r
798 ;----------------------------------------------------------------
\r
799 ; ClearUnilinkStatus - Zeroes out the Unilink state (used when initializing)
\r
803 clrf UnilinkID ; Clear the existing Unilink ID, if any
\r
804 bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID
\r
805 clrf DisplayStatus ; No crazy display updates when resetting.. :)
\r
806 clrf UnilinkSelected ; We're not selected anymore
\r
808 bsf STATUS,RP0 ; Reg bank 1
\r
809 bsf DATA_BIT ; Make sure data is tristated
\r
810 bcf STATUS,RP0 ; Reg bank 0
\r
812 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
813 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
815 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
819 ;----------------------------------------------------------------
\r
820 ; ClearUnilinkBuffer - Zeroes out the Unilink packet buffer
\r
824 ; TODO: Replace this with an FSR access to save space and make the code neater
\r
829 clrf UnilinkParity1
\r
839 clrf UnilinkParity2
\r
845 subtitl "Main loop"
\r
848 ;----------------------------------------------------------------
\r
849 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]
\r
850 ; Here all other house keeping tasks are performed, like displaying info on the LCD..
\r
853 movlw high LookUp ; Set the high PC bits to indicate data lookup page
\r
857 movwf UnilinkAttenuation
\r
859 movlw 8 ; Display page timing (approx 8/sec)
\r
860 movwf DisplayCounter
\r
862 bcf LCD_RS_BIT ; LCD Command mode
\r
863 movlw 80h ; DisplayRam 0
\r
867 movlw low DefaultText1
\r
871 call TxLCD8BLoop ; Send 80 bytes to the LCD
\r
875 bcf LCD_RS_BIT ; LCD Command mode
\r
876 movlw 80h ; DisplayRam 0
\r
881 movf Counter,w ; Debug timer
\r
882 btfsc PORTA,4 ; Test RST
\r
887 movf SlaveBreakState,w
\r
889 btfsc PORTB,0 ; Test CLK
\r
894 btfsc PORTC,2 ; Test BUSON-IN
\r
899 btfsc PORTC,3 ; Test DATA
\r
903 movf UnilinkCmdLen,w
\r
904 bz MainDontPrintCmd
\r
910 ; Default text (see data declarations for actual text)
\r
911 ; UnilinkID @ 11-12
\r
912 ; UnilinkAttenuation @ 16-17
\r
913 ; UnilinkSelected @ 53-54
\r
914 ; DisplayStatus @ 62-63
\r
916 bcf LCD_RS_BIT ; LCD Command mode
\r
917 movlw 80h+11 ; DisplayRam 11
\r
924 bcf LCD_RS_BIT ; LCD Command mode
\r
925 movlw 80h+16 ; DisplayRam 16
\r
929 movf UnilinkAttenuation,w
\r
932 bcf LCD_RS_BIT ; LCD Command mode
\r
933 movlw 80h+40h+13 ; DisplayRam 53
\r
937 movf UnilinkSelected,w
\r
940 bcf LCD_RS_BIT ; LCD Command mode
\r
941 movlw 80h+40h+22 ; DisplayRam 62
\r
945 movf DisplayStatus,w
\r
948 ; This part handles display "scroll" by shifting one screen at a time
\r
950 btfss Counter,7 ; Test high bit
\r
951 goto MainCounterLow
\r
953 ; So bit is high, set high bit of displaycounter as well...
\r
954 bsf DisplayCounter,7
\r
955 goto MainSkipScroll
\r
958 ; OK, bit is low, now figure out whether it was high or low last time -> check high bit of DisplayCounter
\r
959 btfss DisplayCounter,7
\r
960 goto MainSkipScroll
\r
962 bcf DisplayCounter,7 ; Clear the high bit to allow countdown to commence
\r
963 movf Counter,w ; Load it
\r
965 goto MainSkipScroll
\r
966 decfsz DisplayCounter,f
\r
967 goto MainSkipScroll
\r
970 movwf DisplayCounter
\r
972 bcf LCD_RS_BIT ; LCD Command mode
\r
973 movlw 18h ; Display shift Left
\r
974 call TxLCDB ; Shift it 8 positions
\r
986 ; Display scroll part ends here...
\r
988 movf DataCount,w ; Load bit counter (if 0 then byte is available)
\r
992 decf DataCount,f ; Set it non-zero
\r
995 call BootTXB ; Send to terminal
\r
999 ;----------------------------------------------------------------
\r
1000 ; IRQInit - Sets up the IRQ Handler
\r
1001 ; 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
1005 ; Start with clearing the Unilink packet buffer before enabling any interrupts, otherwise the first packet might become corrupt
\r
1006 ; TODO: Replace this with FSR access
\r
1007 clrf UnilinkSelected
\r
1008 clrf DisplayStatus
\r
1011 clrf UnilinkCmdLen
\r
1016 clrf UnilinkParity1
\r
1026 clrf UnilinkParity2
\r
1030 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
1031 movwf UnilinkTXRX ; Store it
\r
1033 clrf SlaveBreakState ; Zero out the status, we're starting from the beginning
\r
1035 ; Fix the output state of RI and BUSON_OUT to a safe default
\r
1037 bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)
\r
1038 bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first
\r
1040 movlw 06h ; Timer2 enabled + 1/16 prescaler
\r
1043 bsf STATUS,RP0 ; Reg bank 1
\r
1045 movlw 09ch ; Timer PR2 reg giving 2000 interrupts per second
\r
1048 bcf RS232_RI_BIT ; Both bits should be outputs
\r
1049 bcf BUSON_OUT_BIT ;
\r
1051 ; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...
\r
1052 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge
\r
1054 bsf INTCON,INTE ; Enable the RB0/INT
\r
1055 bsf INTCON,PEIE ; Enable the peripheral interrupts
\r
1056 bsf PIE1,TMR2IE ; Enable the Timer2 peripheral interrupt
\r
1057 bsf INTCON,GIE ; Enable global interrupts
\r
1059 bsf TXSTA,TXEN ; Enable UART TX
\r
1061 bcf STATUS,RP0 ; Back to bank 0
\r
1063 bsf RCSTA,SPEN ; Enable serial port
\r
1064 bsf RCSTA,CREN ; Enable UART RX
\r
1068 ;----------------------------------------------------------------
\r
1069 ; Initialize LCD Controller...
\r
1072 clrf PORTB ; First clear PortB data register
\r
1073 bsf STATUS,RP0 ; Reg bank 1
\r
1074 movlw 001h ; All but RB0 are outputs.
\r
1077 bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up
\r
1078 bcf STATUS,RP0 ; Restore Reg bank 0
\r
1080 ; This is a standard reset sequence for the LCD controller
\r
1082 movlw 160 ; Need to delay for at least 15ms, let's go for 16ms delay
\r
1085 movlw 3 ; Write 3 to the LCD
\r
1086 call TxLCD ; Send to LCD
\r
1087 movlw 50 ; Need to delay for at least 4.1ms, let's go for 5ms delay
\r
1090 movlw 3 ; Write 3 to the LCD
\r
1092 movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay
\r
1095 movlw 3 ; Write 3 to the LCD
\r
1097 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
1100 movlw 2 ; 4-bit interface requested
\r
1102 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
1105 ; Reset sequence ends here
\r
1106 ; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits
\r
1108 movlw 28h ; Function Select + 4-bit bus + 2-line display
\r
1111 movlw 0ch ; Display Control + LCD On (No cursor)
\r
1114 movlw 01h ; Clear Display
\r
1117 movlw 06h ; Auto Increment cursor position
\r
1120 bsf LCD_RS_BIT ; Accept data
\r
1124 ;----------------------------------------------------------------
\r
1126 ; Sends two characters hex to the LCD
\r
1130 ; Original binary to 2-digit hex conversion from piclist.com, modified to fit here
\r
1160 ;----------------------------------------------------------------
\r
1162 ; Send a string to the LCD.
\r
1167 movlw 80h ; DisplayRam 0
\r
1172 movlw 80h+40 ; DisplayRam 40 (row 2)
\r
1178 ;----------------------------------------------------------------
\r
1180 ; Send a string to the LCD.
\r
1183 ; movwf Icount ; Icount = W
\r
1185 movwf e_LEN ; Move to e_LEN
\r
1188 movf Icount,w ; get the byte
\r
1190 incf Icount,f ; ...else ++Icount (table index)
\r
1191 call TxLCDB ; Send out the byte
\r
1196 ;----------------------------------------------------------------
\r
1197 ; TxLCDB - send a byte to the LCD
\r
1200 movwf TxTemp ; Store byte to send for a while...
\r
1202 bcf temp,0 ; Clear my temp bit
\r
1203 btfss LCD_RS_BIT ; Check if we try the correct reg
\r
1206 bsf temp,0 ; Indicate RS change
\r
1210 call RxLCDB ; Receive byte from LCD, status reg
\r
1212 skpz ; If the bit was set, the zero flag is not
\r
1215 btfsc temp,0 ; If we had to clear RS reset it now
\r
1218 swapf TxTemp,w ; Hi nibble of data to send in lo w bits
\r
1219 call TxLCD ; Send them first...
\r
1220 movf TxTemp,w ; Then we have the low nibble in low w bits
\r
1221 call TxLCD ; And send that one as well
\r
1225 ;----------------------------------------------------------------
\r
1226 ; RxLCDB - recv a byte from the LCD
\r
1229 call RxLCD ; Receive the high nibble
\r
1231 swapf LCDWTmp,f ; Swap it back to file
\r
1232 call RxLCD ; Receive the low nibble
\r
1233 addwf LCDWTmp,w ; Put the nibbles together and return in W
\r
1237 ;----------------------------------------------------------------
\r
1238 ; TxLCD - send a nibble to the LCD
\r
1241 movwf LCDWTmp ; Write nibble to tmp
\r
1242 bcf LCD_DB4_BIT ; Clear previous data
\r
1243 bcf LCD_DB5_BIT ;
\r
1247 btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT
\r
1249 btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT
\r
1251 btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT
\r
1253 btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT
\r
1256 bsf LCD_E_BIT ; And set E to clock the data into the LCD module
\r
1257 nop ; Let it settle
\r
1258 bcf LCD_E_BIT ; And clear the Enable again.
\r
1259 return ; Returns without modifying W
\r
1261 ;----------------------------------------------------------------
\r
1262 ; RxLCD - recv a nibble from the LCD
\r
1265 clrw ; Clear W register, return data in lower 4 bits
\r
1267 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
1269 bsf LCD_DB4_BIT ; This sets the port bit as an input
\r
1274 bcf STATUS,RP0 ; Back at reg bank 0
\r
1276 bsf LCD_RW_BIT ; Set Read mode for the LCD
\r
1277 bsf LCD_E_BIT ; And set E to clock the data out of the LCD module
\r
1278 nop ; Let the bus settle
\r
1279 btfsc LCD_DB4_BIT ; Transfer a set port bit into W
\r
1281 btfsc LCD_DB5_BIT ; Transfer a set port bit into W
\r
1283 btfsc LCD_DB6_BIT ; Transfer a set port bit into W
\r
1285 btfsc LCD_DB7_BIT ; Transfer a set port bit into W
\r
1287 bcf LCD_E_BIT ; And clear the Enable again.
\r
1288 bcf LCD_RW_BIT ; Set Write mode for the LCD
\r
1290 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
1292 bcf LCD_DB4_BIT ; Set the port as an output again
\r
1293 bcf LCD_DB5_BIT ;
\r
1297 bcf STATUS,RP0 ; Back at reg bank 0
\r
1299 return ; Returns with data in W
\r
1301 ;----------------------------------------------------------------------
\r
1302 ; Delay routines (non-interrupt based, therefore not even close to reliable)
\r
1303 ; W=10 gives ~ 1ms of delay
\r
1304 ; 1ms=5000 instructions wasted, 100us=500 cycles
\r
1305 ; Maximum time waited will be 256*100us=25.6ms
\r
1308 movwf Dcount ; Set delay counter, number of 100us periods to wait
\r
1311 movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more
\r
1312 movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount
\r
1314 decfsz Dcount2,f ; 1 cycle (or two when exiting the loop)
\r
1315 goto DelayInner ; 2 cycles
\r
1316 decfsz Dcount,f ; Now decrement number of 100us periods and loop again
\r
1321 ;----------------------------------------------------------------
\r
1322 ; Data can be stored between 600 and 6ffh...
\r
1327 ; UnilinkID @ 11-12
\r
1328 ; UnilinkAttenuation @ 16-17
\r
1329 ; UnilinkSelected @ 53-54
\r
1330 ; DisplayStatus @ 62-63
\r
1333 DT "----- WJ", "ID:xx Se", "xx dB at", "LCDPage3", "LCDPage4"
\r
1334 DT " Unilink", "lect:xx ", "t Dsp:xx", " Right 3", " Right 4"
\r
1337 LookUp movwf PCL ; Go to it (this assumes PCLATH == 06h)
\r
1340 subtitl "Bootstrap/Bootloader code"
\r
1343 ;----------------------------------------------------------------------
\r
1344 ; Bootstrap code - Allows PIC to flash itself with data from the async port.
\r
1345 ; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory
\r
1346 ; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver
\r
1347 ; (one good way of solving that is to wait for the echo from the PIC before sending anything else)
\r
1348 ; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration
\r
1349 ; register can't be programmed. That means that any references to the config register in the hex file will be ignored.
\r
1351 ; Startup @9600bps
\r
1353 ; RAM usage for the bootstrap code
\r
1355 BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog
\r
1360 BootTimerL equ 79h
\r
1361 BootTimerM equ 78h
\r
1362 BootTimerH equ 77h
\r
1363 BootNumBytes equ 76h
\r
1364 BootDataVL equ 75h
\r
1365 BootDataVH equ 74h
\r
1366 BootHEXTemp equ 73h
\r
1368 org 738h ; Place the boot code at the top of memory (currently the loader is exactly 200 bytes)
\r
1371 bsf STATUS,RP0 ; Access bank 1
\r
1372 bsf TXSTA,TXEN ; Enable UART TX
\r
1373 movlw 31 ; Divisor for 9k6 @ 20MHz Fosc
\r
1374 movwf SPBRG ; Store
\r
1375 bcf STATUS,RP0 ; Back to bank 0
\r
1377 bsf RCSTA,SPEN ; Enable serial port
\r
1378 bsf RCSTA,CREN ; Enable UART RX
\r
1380 movlw low BootStartText ; Send boot banner to the serial port
\r
1383 ; movlw 0e8h ; Initialize timeout timer (e8 is about 3 secs)
\r
1384 movlw 0fdh ; Initialize timeout timer (e8 is about 3 secs)
\r
1390 incf BootTimerL,f ; A 24-bit counter
\r
1395 skpnz ; When overflowing here..
\r
1396 goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program
\r
1397 btfss PIR1,RCIF ; Wait for RX to complete
\r
1402 goto BootTimeout ; If it wasn't ESC, wait for another key
\r
1405 movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port
\r
1413 call BootRXB ; First find the ':'
\r
1416 goto BootLoop ; Loop until we find it!
\r
1418 call BootRXHEX ; Get one ASCII encoded byte (two chars)
\r
1419 movwf BootNumBytes ; This is the number of bytes to be programmed on the line
\r
1420 ; Maybe clear cary here?
\r
1421 rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format
\r
1423 ; Note carry should be clear here as there cannot be odd number of bytes in this format
\r
1425 call BootRXHEX ; Receive AddrH
\r
1427 call BootRXHEX ; Receive AddrL
\r
1429 rrf BootAddrH,f ; Fix the addressing again
\r
1432 bcf BootBits,2 ; Assume we should program
\r
1433 bsf BootBits,1 ; And assume we should program flash not ee
\r
1436 xorlw 020h ; Check if it's configuration, which we can't program
\r
1437 skpnz ; Skip the bit set if it was false alarm
\r
1438 bsf BootBits,2 ; No programming for this line
\r
1440 xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)
\r
1441 skpnz ; Skip the bit set instr if not EE data address
\r
1442 bcf BootBits,1 ; We should program EE, will ignore the AddrH
\r
1444 call BootRXHEX ; Receive Record Type (must be 0 for real records)
\r
1445 skpz ; Check if zero
\r
1446 goto BootFlashComplete
\r
1449 call BootRXHEX ; Receive low-byte of data word
\r
1451 call BootRXHEX ; Receive high-byte of data word
\r
1454 btfsc BootBits,2 ; Check whether this line should be programmed at all
\r
1455 goto BootWriteSkip
\r
1457 bcf BootBits,0 ; Read mode first, verify if we actually have to write
\r
1460 xorwf BootDataL,f ; Compare and destroy DataL
\r
1461 movwf BootDataL ; Write new data to DataL
\r
1462 skpz ; Skip if no difference, have to check high byte as well
\r
1463 goto BootWrite ; Jump directly to write
\r
1466 xorwf BootDataH,f ; Compare
\r
1467 skpnz ; Skip if no difference, no programming necessary
\r
1468 goto BootWriteSkip
\r
1472 movwf BootDataH ; Have to put the new H byte data in as well
\r
1475 call BootEE ; Write directly into program mem
\r
1477 ; Here a verify can take place, the read-back results are now in DataL/H
\r
1481 incf BootAddrL,f ; Advance counter to next addr
\r
1483 incf BootAddrH,f ; And add to high byte if needed
\r
1485 decfsz BootNumBytes,f
\r
1493 movlw low BootRunText
\r
1496 bsf STATUS,RP0 ; Reg bank 1
\r
1498 btfss TXSTA,TRMT ; Wait for last things to flush
\r
1499 goto BootReturnWait
\r
1500 bcf TXSTA,TXEN ; Disable UART TX
\r
1501 bcf STATUS,RP0 ; Back to bank 0
\r
1503 bcf RCSTA,SPEN ; Disable serial port
\r
1504 bcf RCSTA,CREN ; Disable UART RX
\r
1506 return ; Return to code
\r
1508 ;----------------------------------------------------------------------
\r
1509 ; BootTXB - Sends one byte to the UART, waits for transmitter to become
\r
1510 ; free before sending
\r
1514 btfss PIR1,TXIF ; Wait for TX to empty
\r
1516 movwf TXREG ; Send the byte
\r
1519 ;----------------------------------------------------------------------
\r
1520 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated
\r
1523 movwf BootAddrL ; Store LSB of text pointer
\r
1524 movlw 07h ; MSB of pointer to the text (0700h in this boot loader)
\r
1526 movlw 02h ; Select "Read Program Memory" operation
\r
1529 call BootEE ; Lookup char (actually two packed into one word)
\r
1530 rlf BootDataL,w ; Shift the MSB out into carry (that's the 2nd char LSB)
\r
1531 rlf BootDataH,w ; Shift it into 2nd char
\r
1532 call BootTXB ; Send the high byte first
\r
1533 movf BootDataL,w ; Get the low byte
\r
1534 andlw 07fh ; Mask of the highest bit
\r
1535 skpnz ; Stop if zero
\r
1537 call BootTXB ; Send char
\r
1538 incf BootAddrL,f ; Increment pointer
\r
1539 goto BootTXStrLoop
\r
1541 ;----------------------------------------------------------------------
\r
1542 ; BootRXB - Receives one byte from the UART, waits if nothing available
\r
1546 btfss PIR1,RCIF ; Wait for RX to complete
\r
1548 movf RCREG,w ; Get the recvd byte
\r
1549 call BootTXB ; Echo to terminal
\r
1552 ;----------------------------------------------------------------------
\r
1553 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary
\r
1556 call BootRXB ; Receive nibble
\r
1558 ; This code is from piclist.com, really neat!
\r
1560 addlw -'A' ; Convert from BCD to binary nibble
\r
1561 skpc ; Test if if was 0-9 or A-F, skip if A-F
\r
1562 addlw 'A' - 10 - '0' ; It was numeric '0'
\r
1563 addlw 10 ; Add 10 (A get to be 0ah etc.)
\r
1567 ;----------------------------------------------------------------------
\r
1568 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available
\r
1569 ; Decodes the bytes as ASCII hex and returns a single byte in W
\r
1572 call BootRXHEXNibble
\r
1574 swapf BootHEXTemp,f ; Swap it up to the high nibble
\r
1576 call BootRXHEXNibble
\r
1577 addwf BootHEXTemp,w ; And add the two nibbles together
\r
1580 ;----------------------------------------------------------------------
\r
1581 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the
\r
1582 ; exact action to take. BootAddrL and BootAddrH has to be initialized
\r
1583 ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash
\r
1584 ; The data to be written has to be put in BootDataL and BootDataH, and
\r
1585 ; data will be to the same place when read back
\r
1588 bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0)
\r
1590 movf BootAddrH,w ; Load desired address
\r
1594 movf BootDataH,w ; And load the data (only used when writing)
\r
1599 bsf STATUS,RP0 ; Go to bank 3
\r
1601 bsf EECON1,EEPGD ; Point to Program Flash mem
\r
1602 btfss BootBits,1 ; Test if that was correct or if we have to clear again
\r
1603 bcf EECON1,EEPGD ; Point to EE DATA mem
\r
1605 btfss BootBits,0 ; Check from read or write
\r
1606 goto BootEERD ; Skip the WR if we were going for a read
\r
1608 bsf EECON1,WREN ; Enable writes
\r
1612 movwf EECON2 ; Unlock write operation
\r
1613 bsf EECON1,WR ; And start a write cycle
\r
1615 btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish
\r
1616 goto BootWRLoop ; These two instructions gets NOPed when flashing
\r
1618 bcf EECON1,WREN ; Finally disable writes again
\r
1619 ; Here we read the data back again, can be used as verify
\r
1621 bsf EECON1,RD ; Start a read cycle
\r
1622 nop ; Only necessary for flash read, same thing as when writing above
\r
1623 nop ; Except I could use the two words for something useful there.. :)
\r
1626 bcf STATUS,RP0 ; Back to bank 2
\r
1627 movf EEDATA,w ; Store our EE-data
\r
1631 bcf STATUS,RP1 ; And finally back to bank 0
\r
1635 ; 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
1637 DW 0x2bca,0x216f,0x37f4,0x102d,0x1070,0x3965,0x39f3,0x1045,0x29c3,0x1074,0x37a0,0x336c,0x30f3,0x3400
\r
1638 ; DE "WJBoot - press ESC to flash\x00"
\r
1640 DW 0x068a,0x29e5,0x3764,0x1049,0x2748,0x2c38,0x1066,0x34ec,0x32a0,0x376f,0x3bae,0x172e,0x0680
\r
1641 ; DE "\r\nSend INHX8 file now...\r\x00"
\r
1643 DW 0x068a,0x22f8,0x34f4,0x34ee,0x33a0,0x366f,0x30e4,0x32f2,0x0680
\r
1644 ; DE "\r\nExiting loader\r\x00"
\r
1647 ;----------------------------------------------------------------------
\r
1648 ; EE Data (64 bytes), located at 2100h
\r
1651 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r