v0.9 - Interrupt driven UART RX for status updates, using raw unilink packets for...
[wj-unilink.git] / wj-uni.asm
1         title   "PIC16F870 Unilink(R) Interface by Werner Johansson, wj@yodel.net"\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
6 \r
7 ;******************************************************************************\r
8 ;\r
9 ;    This program is free software; you can redistribute it and/or modify\r
10 ;    it under the terms of the GNU General Public License as published by\r
11 ;    the Free Software Foundation; either version 2 of the License, or\r
12 ;    (at your option) any later version.\r
13 ;\r
14 ;    This program is distributed in the hope that it will be useful,\r
15 ;    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
16 ;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
17 ;    GNU General Public License for more details.\r
18 ;\r
19 ;    You should have received a copy of the GNU General Public License\r
20 ;    along with this program; if not, write to the Free Software\r
21 ;    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
22 ;\r
23 ;    Author: Werner Johansson (wj@yodel.net), with some code and ideas from \r
24 ;    Simon Woods' GNUnilink, radix conversion utilities from piclist.com\r
25 ;    and of course the reverse-engineered Unilink(R) command list\r
26 ;\r
27 ;******************************************************************************\r
28 \r
29 ;----------------------------------------------------------------\r
30 ;  The Configuration Word\r
31       __CONFIG _HS_OSC&_WDT_OFF&_PWRTE_ON&_BODEN_ON&_LVP_OFF&_CPD_OFF&_WRT_ENABLE_ON&_DEBUG_OFF&_CP_OFF\r
32 \r
33 ;----------------------------------------------------------------\r
34 ;  TODO\r
35 ;----------------------------------------------------------------\r
36 ;  Check how the slave should sense "power off" de-selection. Now it continues to play, as the slave doesn't stop requesting updates!\r
37 ;  Investigate whether I actually have to save PCLATH in ISH, maybe save FSR? - Not saving any of them for now\r
38 ;  Check Overrun errors from the UART\r
39 ;  Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..)\r
40 ;  Make the bit shift routine at the beginning of the ISR timeout if the clock suddenly stops (in the middle of a byte)\r
41 ;   (will keep it from hanging until the next bit gets clocked out, just ignore the faulty bits and carry on)\r
42 ;  Implement command b0 0x (change CD to x (1-a))?\r
43 ;  Implement command 08 10 (Tel Mute on) and 08 18 (Tel Mute off)?\r
44 \r
45 ;----------------------------------------------------------------\r
46 ;  HISTORY\r
47 ;----------------------------------------------------------------\r
48 ;  Version\r
49 ;\r
50 ;  0.9  Interrupt driven UART RX for status updates, using raw unilink packets for everything else, dynamic text\r
51 ;  0.8  Some text commands implemented, only static text for now though\r
52 ;  0.7  Debug Serial TX in ISR now, checksum check for incoming packets in place, A/D works, solved the master reset prob\r
53 ;        (by calling the INT handler from TMR2 ISR code (too much interrupt latency when transmitting)\r
54 ;  0.6  Some more LCD info and clean-up of the Unilink recovery code, some problems with master resetting :(\r
55 ;  0.5  Issues slave breaks seemingly without hickups (!)\r
56 ;  0.4  Some fixups in the bootstrap code (I actually had to put the PIC in my PICSTART Plus programmer again :))\r
57 ;  0.3  Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)\r
58 ;  0.2  First attempt at responding to the Anyone command\r
59 ;  0.1  Receives Unilink data OK, relays it to serial\r
60 ;  0.0  Very first "F**king No Work!" version\r
61 \r
62 ;----------------------------------------------------------------\r
63 ;  I/O LAYOUT\r
64 ;----------------------------------------------------------------\r
65 ;  Unilink BUSON IN (blue) connected to RC2/CCP1\r
66 ;  Unilink DATA (green) connected to RC3\r
67 ;  Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)\r
68 ;  Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)\r
69 ;  Unilink RST (lilac) connected to RA4\r
70 ;  LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)\r
71 ;  LCD RW connected to pin RB2\r
72 ;  LCD E connected to pin RB3\r
73 ;  LCD DB4-DB7 connected to RB4-RB7\r
74 ;  RS-232 TX from computer connected to RC7/RX\r
75 ;  RS-232 RX to computer connected to RC6/TX\r
76 ;  RS-232 RI to computer connected to RC5\r
77 ;  B+ connected via trimmer and resistors to AN0 (divider approx 20k5/5k to give 20.48V maximum scale)\r
78 ;\r
79 ;  This leaves RC0, RC1 and four analog inputs (AN1-AN4) free for now...\r
80 \r
81 #define BUSON_IN_BIT    PORTC,2\r
82 #define DATA_BIT        PORTC,3\r
83 #define BUSON_OUT_BIT   PORTC,4\r
84 #define CLK_BIT         PORTB,0\r
85 #define RST_BIT         PORTA,4\r
86 \r
87 #define LCD_RS_BIT      PORTB,1\r
88 #define LCD_RW_BIT      PORTB,2\r
89 #define LCD_E_BIT       PORTB,3\r
90 #define LCD_DB4_BIT     PORTB,4\r
91 #define LCD_DB5_BIT     PORTB,5\r
92 #define LCD_DB6_BIT     PORTB,6\r
93 #define LCD_DB7_BIT     PORTB,7\r
94 \r
95 #define RS232_RI_BIT    PORTC,5\r
96 \r
97 ;----------------------------------------------------------------\r
98 ;  FILE REGISTER USAGE\r
99 ;----------------------------------------------------------------\r
100 \r
101 ; Free from 20h-48h\r
102 \r
103 TrackName       equ     20h\r
104 TracknameEnd    equ     3eh\r
105 \r
106 DiscName        equ     3fh\r
107 DiscNameEnd     equ     48h\r
108 \r
109 CurDisc         equ     49h\r
110 CurTrack        equ     4ah\r
111 CurMins         equ     4bh\r
112 CurSecs         equ     4ch\r
113 \r
114 RecvType        equ     4dh\r
115 TargetPos       equ     4eh\r
116 CurRecvPos      equ     4fh\r
117 \r
118 UnilinkRAD      equ     50h             ; Beginning of Unilink packet - the Receiving Address\r
119 UnilinkTAD      equ     51h             ; Transmitter address\r
120 UnilinkCMD1     equ     52h             ; CMD1 byte\r
121 UnilinkCMD2     equ     53h             ; CMD2 byte\r
122 UnilinkParity1  equ     54h             ; First or only parity byte for short packets (6 bytes)\r
123 UnilinkData1    equ     55h             ; Extra data for medium/large packets, or zero for short packets\r
124 UnilinkData2    equ     56h             ;\r
125 UnilinkData3    equ     57h             ;\r
126 UnilinkData4    equ     58h             ;\r
127 UnilinkData5    equ     59h             ; Data5 if this is a large packet\r
128 UnilinkParity2M equ     59h             ; Parity2 shares the same byte if it's a medium sized packet\r
129 UnilinkData6    equ     5ah             ; Extra data for large packets, or zero for medium packets\r
130 UnilinkData7    equ     5bh             ;\r
131 UnilinkData8    equ     5ch             ;\r
132 UnilinkData9    equ     5dh             ;\r
133 UnilinkParity2  equ     5eh             ; Parity byte for large packets\r
134 UnilinkZero     equ     5fh             ; Should always be zero (possibly used to signal corrupt packets from slave to master?)\r
135 \r
136 UnilinkTimeout  equ     60h             ; Counts up every 0.5ms to "age out" faulty bytes clocked in\r
137 UnilinkSelected equ     61h             ; High bit is set when selected\r
138 UnilinkBit      equ     62h             ; This is my "bitmask" to be used for requests\r
139 UnilinkID       equ     63h             ; This is my Bus ID\r
140 UnilinkCmdLen   equ     64h             ; This gets updated with the actual packet length after CMD1 has been received\r
141 UnilinkTXRX     equ     65h             ; This is a pointer to the Unilink packet above, used with indirect addressing\r
142 SlaveBreakState equ     66h             ; Hold state and time-out information about slave break, indicates when it can happen\r
143 DisplayStatus   equ     67h             ; What information will be put on the display next, bit 7 cleared if nothing\r
144 Icount          equ     68h             ; Offset of string to print\r
145 TxTemp          equ     69h             ; blahblah\r
146 TxTemp2         equ     6ah             ; Blahblah2\r
147 LCDWTmp         equ     6bh\r
148 Dcount2         equ     6ch\r
149 temp            equ     6dh\r
150 Dcount          equ     6eh\r
151 e_LEN           equ     6fh\r
152 \r
153 Counter         equ     70h\r
154 DataCount       equ     71h             ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)\r
155 UnilinkCurID    equ     72h             ; This is a kludge\r
156 DisplayCounter  equ     73h\r
157 UnilinkAttenuation      equ     74h     ; The amount of attenuation the volume control is currently set to\r
158 NumH            equ     75h\r
159 NumL            equ     76h\r
160 TenK            equ     77h\r
161 Thou            equ     78h\r
162 Hund            equ     79h\r
163 Tens            equ     7ah\r
164 Ones            equ     7bh\r
165 UnilinkReInits  equ     7ch\r
166 IRQPCLATH       equ     7dh             ; ISH storage\r
167 IRQSTATUS       equ     7eh             ; Needs to be located in a shared area accessible from all register banks\r
168 IRQW            equ     7fh             ; \r
169 \r
170 RecvBuf         equ     0a0h            ; Buffer for received data from PC (32 bytes)\r
171 RecvBufEnd      equ     0bfh            ;\r
172 \r
173         subtitl "Startup"\r
174         page\r
175 ;----------------------------------------------------------------\r
176 ;  Power up/Reset starting point\r
177 \r
178         org     0                       ; Start at the beginning of memory (the reset vector)\r
179         call    Bootstrap               ; Call Flash Load routine\r
180         call    LCDInit                 ; Initialize LCD I/F\r
181         call    IRQInit                 ; Set up and start the IRQ handler\r
182         goto    Main                    ; Run the main program loop (skip the IRQ handler)\r
183 \r
184         subtitl "IRQ Handler"\r
185 ;----------------------------------------------------------------\r
186 ;  Interrupt handler always starts at addr 4\r
187 ;  In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.\r
188 ;  Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR\r
189 ;  is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to\r
190 ;  bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low\r
191 ;  again after that. Other interrupts might introduce latencies, but let's see how this works..\r
192 \r
193         org     4                       ; ISR vector is at address 4\r
194         movwf   IRQW                    ; Save W\r
195         swapf   STATUS,w                ; Get the status register into w\r
196         clrf    STATUS                  ; Zero out the status reg, gives Reg Bank0\r
197         movwf   IRQSTATUS               ; Store the STATUS reg\r
198 ; Not using PCLATH for anything in the ISR right now\r
199 ;       movf    PCLATH,w                ; Get the PCLATH reg\r
200 ;       movwf   IRQPCLATH               ; And store it\r
201 ;       clrf    PCLATH                  ; Go to low memory\r
202 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)\r
203 \r
204         call    IRQCheckINT             ; Implemented as a subroutine as there's a need to call it repeatedly from the other ISRs\r
205 \r
206         btfss   PIR1,TMR2IF             ; Check if it's the TMR2 interrupt (0.5ms timing)\r
207         goto    IRQNotTMR2              ; No it's not, check the other sources\r
208 \r
209         incf    Counter,f               ; Increment the general purpose counter (increments every 0.5ms)\r
210 \r
211 ; Slave break opportunity detection here - the logic works as follows:\r
212 ; Look for a data low period of at least 5 ms (10 loops)\r
213 ; Look for a data high period of at least 2 ms (4 loops)\r
214 ; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 4ms (8 loops)\r
215 ; If a bit would be received (CLK activates) the packet handler automatically clears out the SlaveBreakState, which means start all over\r
216 \r
217         call    IRQCheckINT             ; Check the Unilink INT as well\r
218 \r
219         btfsc   SlaveBreakState,5       ; Check if already pulling the data line low\r
220         goto    IRQTMR2SlaveBreak\r
221 \r
222         btfsc   SlaveBreakState,7       ; Looking for low or high data\r
223         goto    IRQTMR2HighData\r
224         btfss   DATA_BIT                ; Looking for a low data line, if it's low, increment state, if it's high, reset state\r
225         goto    IRQTMR2LowDataOK\r
226         clrf    SlaveBreakState         ; Got a high data line while waiting for a low one, reset state\r
227 \r
228         call    IRQCheckINT             ; Check the Unilink INT as well\r
229 \r
230         goto    IRQAfterTMR2            ; Leave ISR\r
231 \r
232 IRQTMR2HighData\r
233         call    IRQCheckINT             ; Check the Unilink INT as well\r
234 \r
235         btfsc   DATA_BIT                ; Looking for a high data line, if it's high - increment state, otherwise wait\r
236         goto    IRQTMR2HighDataOK\r
237         movlw   080h\r
238         btfsc   SlaveBreakState,6       ; Test the "first time around" bit\r
239         clrw                            ; Not the beginning of the state, have to restart the entire thing now, not just this state\r
240         andwf   SlaveBreakState,f       ; Mask out the 1 upper control bits and restart this state\r
241         goto    IRQAfterTMR2\r
242 \r
243 IRQTMR2HighDataOK\r
244 IRQTMR2LowDataOK\r
245         call    IRQCheckINT             ; Check the Unilink INT as well\r
246 \r
247         bsf     SlaveBreakState,6       ; Set the "first time around" bit\r
248         \r
249         movf    SlaveBreakState,w\r
250         andlw   1fh\r
251 \r
252         btfss   SlaveBreakState,7       ; Checking whether it's low or high\r
253         goto    IRQTMR2FoundLow\r
254 \r
255         xorlw   4                       ; It's high now, and if 4 periods have passed we can activate slave break\r
256         skpz\r
257         goto    IRQAfterTMR2\r
258 \r
259 ; Issue slave break here\r
260 \r
261         clrf    SlaveBreakState\r
262 \r
263 ;       incf    Counter,f               : Debug!\r
264 \r
265         btfss   DisplayStatus,7         ; Only do this if high bit is set\r
266         goto    IRQAfterTMR2\r
267 \r
268         movlw   20h\r
269         movwf   SlaveBreakState\r
270         bcf     DATA_BIT\r
271         bsf     STATUS,RP0\r
272         bcf     DATA_BIT\r
273         bcf     STATUS,RP0\r
274         goto    IRQAfterTMR2\r
275 \r
276 IRQTMR2FoundLow\r
277         xorlw   10\r
278         skpz\r
279         goto    IRQAfterTMR2\r
280 \r
281         call    IRQCheckINT             ; Check the Unilink INT as well\r
282 \r
283         movlw   80h                     ; Prepare for state 2, looking for data line high\r
284         movwf   SlaveBreakState\r
285         goto    IRQAfterTMR2\r
286         \r
287 IRQTMR2SlaveBreak\r
288         call    IRQCheckINT             ; Check the Unilink INT as well\r
289 \r
290         movf    SlaveBreakState,w\r
291         andlw   01fh\r
292         xorlw   8\r
293         skpz\r
294         goto    IRQAfterTMR2\r
295         bsf     STATUS,RP0\r
296         bsf     DATA_BIT\r
297         bcf     STATUS,RP0\r
298         clrf    SlaveBreakState\r
299 \r
300 IRQAfterTMR2\r
301         btfss   SlaveBreakState,4       ; Only increment to 0x10\r
302         incf    SlaveBreakState,f\r
303         bcf     PIR1,TMR2IF             ; Clear the IRQ source bit to re-enable TMR2 interrupts again\r
304 \r
305 IRQNotTMR2\r
306 \r
307         call    IRQCheckINT             ; Check the Unilink INT as well\r
308 \r
309         btfss   PIR1,RCIF               ; Check if it's the UART RX interrupt\r
310         goto    IRQNotRCIF              ; No it's not, check the other sources\r
311 ;----------------------------------------------------------------\r
312 ; This is the UART RX routine, this should be control info from the connected PC we're receiving\r
313 \r
314         movf    CurRecvPos,w            ; Load fsr\r
315         movwf   FSR\r
316         movf    RCREG,w                 ; Get the byte\r
317         bcf     PIR1,RCIF               ; Clear current interrupt condition\r
318         movwf   INDF                    ; Store the recv byte at the position waiting\r
319         movlw   RecvType\r
320         xorwf   FSR,w                   ; Check if it's the recvtype coming in\r
321         bnz     NotRecvType\r
322 \r
323         call    IRQCheckINT             ; Check the Unilink INT as well\r
324 \r
325         movf    RecvType,w              ; Load type\r
326         bz      RecvTimeUpdate          ; Position update\r
327         xorlw   1                       ; Track Name perhaps?\r
328         bz      RecvTrackName\r
329         xorlw   3                       ; this is xor 1 and then xor 2, disc name?\r
330         bz      RecvDiscName\r
331 \r
332         call    IRQCheckINT             ; Check the Unilink INT as well\r
333 \r
334         btfss   RecvType,7              ; Check Hi byte\r
335         goto    IRQNotRCIF\r
336 \r
337         movf    RecvType,w\r
338         xorlw   81h\r
339         bnz     NotText\r
340 \r
341         call    IRQCheckINT             ; Check the Unilink INT as well\r
342 \r
343         movf    RecvType,w\r
344         goto    TextUpdate\r
345 NotText\r
346         call    IRQCheckINT             ; Check the Unilink INT as well\r
347 \r
348         movf    RecvType,w\r
349         btfss   DisplayStatus,7         ; Only if not updating already!\r
350 TextUpdate\r
351         movwf   DisplayStatus           ; Force it into DisplayStatus\r
352         goto    Finished\r
353         \r
354 RecvTimeUpdate\r
355         movlw   CurDisc                 ; Load DL start here\r
356         movwf   CurRecvPos\r
357         movlw   CurSecs+1               ; And stop here\r
358         movwf   TargetPos\r
359         goto    IRQNotRCIF\r
360 \r
361 RecvTrackName\r
362         movlw   TrackName               ; Load DL start here\r
363         movwf   CurRecvPos\r
364         movlw   TracknameEnd+1          ; And stop here\r
365         movwf   TargetPos\r
366         goto    IRQNotRCIF\r
367 \r
368 RecvDiscName\r
369         movlw   DiscName                ; Load DL start here\r
370         movwf   CurRecvPos\r
371         movlw   DiscNameEnd+1           ; And stop here\r
372         movwf   TargetPos\r
373         goto    IRQNotRCIF\r
374 \r
375 NotRecvType\r
376         incf    CurRecvPos,f\r
377         \r
378         call    IRQCheckINT             ; Check the Unilink INT as well\r
379 \r
380         movf    CurRecvPos,w            ; Check if we're done\r
381         xorwf   TargetPos,w\r
382         bnz     NotFinished\r
383 Finished\r
384         call    IRQCheckINT             ; Check the Unilink INT as well\r
385 \r
386 ;       movlw   81h                     ; Assume we do it from the beginning (text and pos)\r
387 ;       movf    RecvType,f              ; Just check for time-update cmd\r
388 ;       skpnz                           ; No adjustment if another cmd\r
389 ;       movlw   85h                     ; Skip the first 4 display cmds\r
390 ;       movwf   DisplayStatus\r
391         \r
392 ;       call    IRQCheckINT             ; Check the Unilink INT as well\r
393 \r
394         movlw   RecvType                ; restore for another go\r
395         movwf   CurRecvPos\r
396 \r
397 NotFinished\r
398 \r
399 IRQNotRCIF\r
400 \r
401         call    IRQCheckINT             ; Check the Unilink INT as well\r
402 \r
403         btfss   PIR1,TXIF               ; Check if it's the UART TX interrupt\r
404         goto    IRQNotTXIF              ; No it's not, check the other sources\r
405         bsf     STATUS,RP0              ; Reg bank 1\r
406         btfss   PIE1,TXIE               ; As TXIF is set as long as nothing gets sent, is the interrupt actually enabled?\r
407         goto    IRQTXIFDisabled\r
408 ;----------------------------------------------------------------\r
409 ; This is the UART TX routine, gets called when TXIE has been set and the TX load register is empty\r
410 \r
411 \r
412 \r
413 \r
414 \r
415 IRQTXIFDisabled\r
416         bcf     STATUS,RP0              ; Make sure we're going back to reg bank 0\r
417 \r
418 IRQNotTXIF\r
419 \r
420 ; Finally restore CPU state and return from the ISR\r
421 \r
422 ; If I have to save the FSR in the beginning I also need to restore it here...\r
423 \r
424 ;       movf    IRQPCLATH,w\r
425 ;       movwf   PCLATH                  ; Restore PCLATH\r
426         swapf   IRQSTATUS,w\r
427         movwf   STATUS                  ; Restore STATUS\r
428         swapf   IRQW,f\r
429         swapf   IRQW,w                  ; Restore W\r
430         retfie                          ; Interrupt return\r
431 \r
432 ;----------------------------------------------------------------\r
433 ; IRQCheckINT - This part is the actual Unilink tranceiver, have to call it often as there are only ~20 spare cycles\r
434 ; (which is only a problem if we're going to transmit, but the check can be done anyway, it's cheap if no bit is there)\r
435 \r
436 IRQCheckINT\r
437         btfss   INTCON,INTF             ; Check if it's the INT edge interrupt (Unilink CLK)\r
438         return                          ; No it's not, return again after only four cycles\r
439 \r
440 ; 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
441 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction\r
442 ; 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
443 ; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of\r
444 ; 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
445 ; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX\r
446 ; 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
447 ; here, otherwise collisions will occur..\r
448 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an\r
449 ; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here\r
450 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)\r
451 \r
452         movlw   8                       ; Loop through the 8 bits\r
453         movwf   DataCount\r
454         movf    UnilinkTXRX,w           ; Get the pointer\r
455         movwf   FSR                     ; Store it to make use of indirect addressing\r
456 \r
457 IRQINTBitSet\r
458         btfss   INDF,7                  ; Test high bit of data (that's the first bit to be clocked out)\r
459         goto    IRQINTTristate          ; Bit is low, we should tristate bit\r
460         bcf     PORTC,3                 ; Otherwise set DATA bit low\r
461         bsf     STATUS,RP0              ; Select high regs\r
462         bcf     TRISC,3                 ; And pull low (now it's an output)\r
463         bcf     STATUS,RP0              ; Back to regbank 0\r
464         goto    IRQINTCLKWaitLow        ; Wait for master to actually clock this bit in\r
465 \r
466 IRQINTTristate\r
467         bsf     STATUS,RP0              ; Select high regs\r
468         bsf     TRISC,3                 ; Force the bit to be tristated\r
469         bcf     STATUS,RP0              ; Back to regbank 0\r
470 \r
471 IRQINTCLKWaitLow\r
472         btfss   PORTC,2                 ; Check for BUSON\r
473         goto    IRQAfterINT\r
474         btfsc   PORTB,0                 ; Wait for clock to go low\r
475         goto    IRQINTCLKWaitLow\r
476 \r
477         clrc                            ; Clear carry\r
478         btfss   PORTC,3                 ; Test DATA\r
479         setc                            ; Set carry if data is LOW (data is inverted!)\r
480         rlf     INDF,f                  ; Shift it into the "accumulator"\r
481 \r
482         decfsz  DataCount,f             ; Loop once more perhaps?\r
483         goto    IRQINTCLKWaitHigh       ; Yes, again!\r
484         goto    IRQINTRecvDone          ; No it's done, don't check for clock to go high again\r
485 \r
486 IRQINTCLKWaitHigh\r
487         btfss   PORTC,2                 ; Check for BUSON\r
488         goto    IRQAfterINT\r
489         btfss   PORTB,0                 ; Wait for clock to go high\r
490         goto    IRQINTCLKWaitHigh\r
491         goto    IRQINTBitSet            ; Loop again\r
492 \r
493 ; Successfully received a byte here, run it through a state machine to figure out what to do\r
494 ; (several possibilites exists here):\r
495 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero\r
496 ; If receive counter is zero and the received byte is a zero byte, discard it\r
497 ; Otherwise store the byte in our receive buffer and increment receive counter\r
498 ; 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
499 ;   00 = short 6 byte packet\r
500 ;   10 = medium 11 byte packet\r
501 ;   11 = long 16 byte packet\r
502 ; Update the receive length byte accordingly\r
503 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing\r
504 ; the packet and take appropriate action.\r
505 \r
506 IRQINTRecvDone\r
507         clrf    SlaveBreakState         ; First of all, clear the break state - this got in the way, restart detection..\r
508 \r
509         movf    INDF,w\r
510         call    BootTXB                 ; Send the byte to the serial port\r
511 \r
512         movf    UnilinkTXRX,w           ; Find out which byte # that was received\r
513         andlw   0fh                     ; Mask\r
514         bnz     IRQINTRecvNotFirst      ; Not the first byte\r
515         movf    UnilinkRAD,w            ; Get the first byte received\r
516         bz      IRQINTRecvNullByte      ; Null byte received, ignore this, don't increment counter\r
517 IRQINTRecvNotFirst\r
518         incf    UnilinkTXRX,f           ; Increment address\r
519 \r
520         movf    UnilinkTXRX,w           ; Get the byte position again\r
521         andlw   0fh                     ; Only lower 4 bits of interest\r
522         xorlw   03h                     ; Well, is it the third byte? (CMD1, telling us the length of the packet)\r
523         bnz     IRQINTRecvNotCMD1       ; No, skip the length code for now\r
524         movlw   6                       ; Assume it's a short packet\r
525         btfss   INDF,7                  ; INDF still points to received byte, test high bit for medium/long\r
526         goto    IRQINTRecvShort         ; Nope, it's a short packet\r
527         addlw   5                       ; OK, it's long or medium at least\r
528         btfsc   INDF,6                  ; Test for long\r
529         addlw   5                       ; Yep, it's a long packet\r
530 IRQINTRecvShort\r
531         movwf   UnilinkCmdLen           ; Store the length\r
532 \r
533 IRQINTRecvNotCMD1\r
534         movf    UnilinkTXRX,w           ; Get the byte position\r
535         xorwf   UnilinkCmdLen,w         ; XOR with the calculated command length\r
536         andlw   0fh                     ; and mask - this results in a zero result when finished receiving\r
537         bnz     IRQINTRecvIncomplete    ; Packet not ready yet\r
538 \r
539 ; Here a packet is actually received, should check the checksum(s) now\r
540 \r
541         movf    UnilinkRAD,w            ; QnD checksum check\r
542         addwf   UnilinkTAD,w\r
543         addwf   UnilinkCMD1,w\r
544         addwf   UnilinkCMD2,w\r
545         xorwf   UnilinkParity1,w        ; This should be zero\r
546         bnz     IRQINTParseComplete     ; Don't allow packet parsing on corrupt packets\r
547 \r
548         btfss   UnilinkCMD1,7           ; Test whether there's more parity to check (medium or long packet)\r
549         goto    IRQINTParser            ; No, skip directly to parsing logic\r
550 \r
551         movf    UnilinkParity1,w        ; QnD checksum check for the remaining part of the packet\r
552         addwf   UnilinkData1,w\r
553         addwf   UnilinkData2,w\r
554         addwf   UnilinkData3,w\r
555         addwf   UnilinkData4,w\r
556 \r
557         btfss   UnilinkCMD1,6           ; Test for a long packet\r
558         goto    IRQINTBypassLongPacket\r
559 \r
560         xorwf   UnilinkParity2M,w       ; Fix for the medium packet parity check a few lines down...\r
561         addwf   UnilinkData5,w\r
562         addwf   UnilinkData6,w\r
563         addwf   UnilinkData7,w\r
564         addwf   UnilinkData8,w\r
565         addwf   UnilinkData9,w\r
566         xorwf   UnilinkParity2,w        ; This should be zero when xor:ed with the Parity2M\r
567 \r
568 IRQINTBypassLongPacket\r
569         xorwf   UnilinkParity2M,w       ; This should be zero for valid medium and long packets\r
570         bnz     IRQINTParseComplete\r
571 \r
572 IRQINTParser\r
573 \r
574 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here\r
575 ; (there won't be any more communication for at least another 4.8ms))\r
576 \r
577 ; Unilink command parser:\r
578 \r
579 ; Check for CMD1 = 01h (System bus commands)\r
580         movf    UnilinkCMD1,w\r
581         xorlw   01h\r
582         bnz     IRQINTParseNot01\r
583 \r
584 ; Check for 01 00 (Bus Re-Initialization)\r
585         movf    UnilinkCMD2,w\r
586         bnz     IRQINTParseNot0100\r
587 \r
588         call    ClearUnilinkStatus      ; Clear everything Unilink (ID, BUSON_OUT)\r
589 \r
590         incf    UnilinkReInits,f        ; increment the debug counter\r
591 \r
592         goto    IRQINTParseComplete     ; Don't send any reply to this (clear the packet buffer though)\r
593 \r
594 IRQINTParseNot0100\r
595 \r
596 ; Check for 01 02 (Anyone)\r
597         movf    UnilinkCMD2,w\r
598         xorlw   02h\r
599         bnz     IRQINTParseNot0102\r
600 \r
601         movf    UnilinkID,w             ; Do I have an ID already?\r
602         bnz     IRQINTParseComplete     ; Yep, I don't want another one!\r
603 \r
604         call    ClearUnilinkBuffer      ; Zero it out completely\r
605 \r
606         movlw   10h                     ; Sending to Master\r
607         addwf   UnilinkParity1,f\r
608         movwf   UnilinkRAD\r
609         movlw   0d0h                    ; I'm in the MD changer group\r
610         addwf   UnilinkParity1,f\r
611         movwf   UnilinkTAD\r
612         movlw   8ch                     ; Device discovery command reply\r
613         addwf   UnilinkParity1,f\r
614         movwf   UnilinkCMD1\r
615         movlw   10h                     ; 00??\r
616         addwf   UnilinkParity1,f\r
617         movwf   UnilinkCMD2\r
618 \r
619         movf    UnilinkParity1,w\r
620         movwf   UnilinkParity2M\r
621 \r
622         movlw   24h                     ; My internal MD sends 25 here first time, and then 24 when appointed!??\r
623         addwf   UnilinkParity2M,f\r
624         movwf   UnilinkData1\r
625         movlw   0a8h                    ; 2c??\r
626         addwf   UnilinkParity2M,f\r
627         movwf   UnilinkData2\r
628         movlw   17h                     ; 22??\r
629         addwf   UnilinkParity2M,f\r
630         movwf   UnilinkData3\r
631         movlw   0a0h                    ; 00?? 0a0=10 disc?\r
632         addwf   UnilinkParity2M,f\r
633         movwf   UnilinkData4\r
634 \r
635         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
636 \r
637 IRQINTParseNot0102\r
638 \r
639 ; Check for 01 12 (Time poll)\r
640         movf    UnilinkCMD2,w\r
641         xorlw   12h\r
642         bnz     IRQINTParseNot0112\r
643 \r
644         movf    UnilinkRAD,w\r
645         xorwf   UnilinkID,w             ; Is it for me?\r
646         bnz     IRQINTParseNot0112      ; Nope\r
647 \r
648         call    ClearUnilinkBuffer\r
649         movlw   10h                     ; Sending to Master\r
650         addwf   UnilinkParity1,f\r
651         movwf   UnilinkRAD\r
652         movf    UnilinkID,w             ; This is my ID\r
653         addwf   UnilinkParity1,f\r
654         movwf   UnilinkTAD\r
655         movlw   00h\r
656         addwf   UnilinkParity1,f\r
657         movwf   UnilinkCMD1\r
658 \r
659         movlw   80h                     ; Idle unless selected\r
660         btfsc   UnilinkSelected,7       \r
661         clrw\r
662         \r
663         addwf   UnilinkParity1,f\r
664         movwf   UnilinkCMD2\r
665         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
666 \r
667 IRQINTParseNot0112\r
668 \r
669 ; Check for 01 13 (Request Time poll)\r
670         movf    UnilinkCMD2,w\r
671         xorlw   13h\r
672         bnz     IRQINTParseNot0113\r
673 \r
674         movf    UnilinkRAD,w\r
675         xorwf   UnilinkID,w             ; Is it for me?\r
676         bnz     IRQINTParseNot0113      ; Nope\r
677 \r
678         btfss   DisplayStatus,7         ; If not displaying, skip this\r
679         goto    IRQINTParseComplete\r
680 \r
681         call    ClearUnilinkBuffer\r
682 \r
683         movlw   70h                     ; Sending to Display Group\r
684         addwf   UnilinkParity1,f\r
685         movwf   UnilinkRAD\r
686         movf    UnilinkID,w             ; This is my ID\r
687         addwf   UnilinkParity1,f\r
688         movwf   UnilinkTAD\r
689 \r
690         movf    DisplayStatus,w\r
691         xorlw   80h                     ; First slave break?\r
692         bnz     IRQINTParse0113Not80\r
693 \r
694         movlw   90h\r
695         addwf   UnilinkParity1,f\r
696         movwf   UnilinkCMD1\r
697         movlw   50h\r
698         addwf   UnilinkParity1,f\r
699         movwf   UnilinkCMD2\r
700 \r
701         movf    UnilinkParity1,w        ; Carry the parity forward\r
702         movwf   UnilinkParity2M\r
703 \r
704         movf    CurTrack,w\r
705         addwf   UnilinkParity2M,f\r
706         movwf   UnilinkData1\r
707         movf    CurMins,w\r
708         addwf   UnilinkParity2M,f\r
709         movwf   UnilinkData2\r
710         movf    CurSecs,w\r
711         addwf   UnilinkParity2M,f\r
712         movwf   UnilinkData3\r
713 \r
714 ;       movlw   0c0h\r
715         movf    CurDisc,w\r
716 ;       andlw   0f0h\r
717 ;       iorlw   0eh\r
718 \r
719         addwf   UnilinkParity2M,f\r
720         movwf   UnilinkData4\r
721 \r
722 ;       clrf    DisplayStatus\r
723 ;       goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
724 \r
725         goto    IRQINTParse0113Complete\r
726 \r
727 IRQINTParse0113Not80\r
728 \r
729         movf    DisplayStatus,w\r
730         xorlw   81h                     ; Second slave break?\r
731         bnz     IRQINTParse0113Not81\r
732 \r
733         movlw   0cdh                    ; Disc name\r
734         addwf   UnilinkParity1,f\r
735         movwf   UnilinkCMD1\r
736         movf    DiscName,w\r
737         addwf   UnilinkParity1,f\r
738         movwf   UnilinkCMD2\r
739 \r
740         movf    UnilinkParity1,w        ; Carry the parity forward\r
741         movwf   UnilinkParity2\r
742 \r
743         movf    DiscName+1,w\r
744         addwf   UnilinkParity2,f\r
745         movwf   UnilinkData1\r
746         movf    DiscName+2,w\r
747         addwf   UnilinkParity2,f\r
748         movwf   UnilinkData2\r
749         movf    DiscName+3,w\r
750         addwf   UnilinkParity2,f\r
751         movwf   UnilinkData3\r
752         movf    DiscName+4,w\r
753         addwf   UnilinkParity2,f\r
754         movwf   UnilinkData4\r
755         movf    DiscName+5,w\r
756         addwf   UnilinkParity2,f\r
757         movwf   UnilinkData5\r
758         movf    DiscName+6,w\r
759         addwf   UnilinkParity2,f\r
760         movwf   UnilinkData6\r
761         movf    DiscName+7,w\r
762         addwf   UnilinkParity2,f\r
763         movwf   UnilinkData7\r
764         movlw   00h\r
765         addwf   UnilinkParity2,f\r
766         movwf   UnilinkData8\r
767         movlw   0eh\r
768         addwf   UnilinkParity2,f\r
769         movwf   UnilinkData9\r
770         goto    IRQINTParse0113Complete\r
771 \r
772 IRQINTParse0113Not81\r
773 \r
774         movf    DisplayStatus,w\r
775         xorlw   82h                     ; Third slave break?\r
776         bnz     IRQINTParse0113Not82\r
777 \r
778         movlw   0cdh                    ; Disc name\r
779         addwf   UnilinkParity1,f\r
780         movwf   UnilinkCMD1\r
781         movf    DiscName+8,w\r
782         addwf   UnilinkParity1,f\r
783         movwf   UnilinkCMD2\r
784 \r
785         movf    UnilinkParity1,w        ; Carry the parity forward\r
786         movwf   UnilinkParity2\r
787 \r
788         movf    DiscName+9,w\r
789         addwf   UnilinkParity2,f\r
790         movwf   UnilinkData1\r
791         movlw   0\r
792         addwf   UnilinkParity2,f\r
793         movwf   UnilinkData2\r
794         movlw   0\r
795         addwf   UnilinkParity2,f\r
796         movwf   UnilinkData3\r
797         movlw   0\r
798         addwf   UnilinkParity2,f\r
799         movwf   UnilinkData4\r
800         movlw   0\r
801         addwf   UnilinkParity2,f\r
802         movwf   UnilinkData5\r
803         movlw   0\r
804         addwf   UnilinkParity2,f\r
805         movwf   UnilinkData6\r
806         movlw   0\r
807         addwf   UnilinkParity2,f\r
808         movwf   UnilinkData7\r
809         movlw   00h\r
810         addwf   UnilinkParity2,f\r
811         movwf   UnilinkData8\r
812         movlw   1eh\r
813         addwf   UnilinkParity2,f\r
814         movwf   UnilinkData9\r
815         goto    IRQINTParse0113Complete\r
816 \r
817 IRQINTParse0113Not82\r
818 \r
819         movf    DisplayStatus,w\r
820         xorlw   83h                     ; Fourth slave break?\r
821         bnz     IRQINTParse0113Not83\r
822 \r
823         movlw   0c9h                    ; Track name 1\r
824         addwf   UnilinkParity1,f\r
825         movwf   UnilinkCMD1\r
826         movf    TrackName,w\r
827         addwf   UnilinkParity1,f\r
828         movwf   UnilinkCMD2\r
829 \r
830         movf    UnilinkParity1,w        ; Carry the parity forward\r
831         movwf   UnilinkParity2\r
832 \r
833         movf    TrackName+1,w\r
834         addwf   UnilinkParity2,f\r
835         movwf   UnilinkData1\r
836         movf    TrackName+2,w\r
837         addwf   UnilinkParity2,f\r
838         movwf   UnilinkData2\r
839         movf    TrackName+3,w\r
840         addwf   UnilinkParity2,f\r
841         movwf   UnilinkData3\r
842         movf    TrackName+4,w\r
843         addwf   UnilinkParity2,f\r
844         movwf   UnilinkData4\r
845         movf    TrackName+5,w\r
846         addwf   UnilinkParity2,f\r
847         movwf   UnilinkData5\r
848         movf    TrackName+6,w\r
849         addwf   UnilinkParity2,f\r
850         movwf   UnilinkData6\r
851         movf    TrackName+7,w\r
852         addwf   UnilinkParity2,f\r
853         movwf   UnilinkData7\r
854         movlw   00h\r
855         addwf   UnilinkParity2,f\r
856         movwf   UnilinkData8\r
857         movlw   0eh\r
858         addwf   UnilinkParity2,f\r
859         movwf   UnilinkData9\r
860 \r
861         goto    IRQINTParse0113Complete\r
862 \r
863 IRQINTParse0113Not83\r
864 \r
865         movf    DisplayStatus,w\r
866         xorlw   84h                     ; Fifth slave break?\r
867         bnz     IRQINTParse0113Not84\r
868 \r
869         movlw   0c9h                    ; Track name (2)\r
870         addwf   UnilinkParity1,f\r
871         movwf   UnilinkCMD1\r
872         movf    TrackName+8,w\r
873         addwf   UnilinkParity1,f\r
874         movwf   UnilinkCMD2\r
875 \r
876         movf    UnilinkParity1,w        ; Carry the parity forward\r
877         movwf   UnilinkParity2\r
878 \r
879         movf    TrackName+9,w\r
880         addwf   UnilinkParity2,f\r
881         movwf   UnilinkData1\r
882         movf    TrackName+10,w\r
883         addwf   UnilinkParity2,f\r
884         movwf   UnilinkData2\r
885         movf    TrackName+11,w\r
886         addwf   UnilinkParity2,f\r
887         movwf   UnilinkData3\r
888         movf    TrackName+12,w\r
889         addwf   UnilinkParity2,f\r
890         movwf   UnilinkData4\r
891         movf    TrackName+13,w\r
892         addwf   UnilinkParity2,f\r
893         movwf   UnilinkData5\r
894         movf    TrackName+14,w\r
895         addwf   UnilinkParity2,f\r
896         movwf   UnilinkData6\r
897         movf    TrackName+15,w\r
898         addwf   UnilinkParity2,f\r
899         movwf   UnilinkData7\r
900         movlw   00h\r
901         addwf   UnilinkParity2,f\r
902         movwf   UnilinkData8\r
903         movlw   1eh\r
904         addwf   UnilinkParity2,f\r
905         movwf   UnilinkData9\r
906         goto    IRQINTParse0113Complete\r
907 \r
908 IRQINTParse0113Not84\r
909 \r
910         movf    DisplayStatus,w\r
911         xorlw   85h                     ; Sixth slave break?\r
912         bnz     IRQINTParse0113Not85\r
913 \r
914         movlw   90h\r
915         addwf   UnilinkParity1,f\r
916         movwf   UnilinkCMD1\r
917         movlw   50h\r
918         addwf   UnilinkParity1,f\r
919         movwf   UnilinkCMD2\r
920 \r
921         movf    UnilinkParity1,w        ; Carry the parity forward\r
922         movwf   UnilinkParity2M\r
923 \r
924         movf    CurTrack,w\r
925         addwf   UnilinkParity2M,f\r
926         movwf   UnilinkData1\r
927         movf    CurMins,w\r
928         addwf   UnilinkParity2M,f\r
929         movwf   UnilinkData2\r
930         movf    CurSecs,w\r
931         addwf   UnilinkParity2M,f\r
932         movwf   UnilinkData3\r
933 \r
934 ;       movlw   0c0h\r
935         movf    CurDisc,w\r
936 ;       andlw   0f0h\r
937 ;       iorlw   0eh\r
938 \r
939         addwf   UnilinkParity2M,f\r
940         movwf   UnilinkData4\r
941 \r
942         clrf    DisplayStatus           ; for now!\r
943         goto    IRQINTParse0113Complete\r
944 \r
945 IRQINTParse0113Not85\r
946         clrf    DisplayStatus\r
947         incf    DisplayStatus,f         ; Skip step one for now\r
948         goto    IRQINTParseComplete\r
949 \r
950 IRQINTParse0113Complete \r
951 \r
952         incf    DisplayStatus,f         ; Increment display state counter\r
953 ;       bsf     DisplayStatus,7\r
954 \r
955         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
956 \r
957 IRQINTParseNot0113\r
958 \r
959 ; Check for 01 15 (Who sent the slave break?)\r
960         movf    UnilinkCMD2,w\r
961         xorlw   15h\r
962         bnz     IRQINTParseNot0115\r
963 \r
964         btfss   DisplayStatus,7         ; First of all check if there should be anything displayed\r
965         goto    IRQINTParseComplete     ; No, not at this time\r
966         \r
967         call    ClearUnilinkBuffer\r
968         movlw   10h                     ; Sending to Master\r
969         addwf   UnilinkParity1,f\r
970         movwf   UnilinkRAD\r
971         movlw   18h                     ; Broadcast address sending in this special case\r
972         addwf   UnilinkParity1,f\r
973         movwf   UnilinkTAD\r
974         movlw   82h                     ; Who wants to talk reply command\r
975         addwf   UnilinkParity1,f\r
976         movwf   UnilinkCMD1\r
977 \r
978         clrw\r
979         call    Bit_Frig\r
980         addwf   UnilinkParity1,f\r
981         movwf   UnilinkCMD2\r
982 \r
983         movf    UnilinkParity1,w        ; Carry the parity forward\r
984         movwf   UnilinkParity2M\r
985 \r
986         movlw   20h\r
987         call    Bit_Frig\r
988         addwf   UnilinkParity2M,f\r
989         movwf   UnilinkData1\r
990         movlw   40h\r
991         call    Bit_Frig\r
992         addwf   UnilinkParity2M,f\r
993         movwf   UnilinkData2\r
994         movlw   60h\r
995         call    Bit_Frig\r
996         addwf   UnilinkParity2M,f\r
997         movwf   UnilinkData3\r
998         movlw   80h\r
999         call    Bit_Frig\r
1000         addwf   UnilinkParity2M,f\r
1001         movwf   UnilinkData4\r
1002 \r
1003         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
1004 \r
1005 ;******************************************************************************\r
1006 ; Bit frig - works out which bit to set in the response to Master Poll\r
1007 ;  This is taken more or less verbatim from Simon Woods' GNUnilink code!\r
1008 ;\r
1009 ; W register is input of which stage you are on (0x00, 0x20, 0x40 etc)\r
1010 ; and is returned with the byte to write (0x00 if wrong stage).\r
1011 \r
1012 Bit_Frig:\r
1013         xorwf   UnilinkBit, 0\r
1014         andlw   0xe0                    ; Strip off low bits\r
1015 \r
1016         btfsc   STATUS, Z               ; Do we have a hit?\r
1017         goto    Bit_Frig_Hit\r
1018 \r
1019         movlw   0x00\r
1020         return\r
1021 \r
1022 Bit_Frig_Hit:\r
1023         btfss   UnilinkBit, 4           ; Do we need to swap nybbles?\r
1024         goto    Bit_Frig_Swap\r
1025 \r
1026         movf    UnilinkBit, 0\r
1027         andlw   0x0F\r
1028         return\r
1029 \r
1030 Bit_Frig_Swap:\r
1031         swapf   UnilinkBit, 0\r
1032         andlw   0xF0\r
1033         return\r
1034 \r
1035 IRQINTParseNot0115\r
1036 \r
1037 IRQINTParseNot01\r
1038 \r
1039 ; Check for CMD1 = 02h (Appoint)\r
1040         movf    UnilinkCMD1,w\r
1041         xorlw   02h\r
1042         bnz     IRQINTParseNot02\r
1043 \r
1044         movf    UnilinkID,w             ; Do I have an ID already?\r
1045         bnz     IRQINTParseComplete     ; Yep, I don't want another one!\r
1046 \r
1047         movf    UnilinkRAD,w            ; So I don't have any ID yet, see what the master is trying to set\r
1048         andlw   0f0h                    ; Check the device group\r
1049         xorlw   0d0h                    ; Verify it's a MD changer\r
1050         bnz     IRQINTParseComplete     ; No, something else, skip this\r
1051 \r
1052         movf    UnilinkRAD,w            ; Get the ID the master has given me\r
1053         movwf   UnilinkID               ; Store my id\r
1054         movf    UnilinkCMD2,w           ; Get the bitmask\r
1055         movwf   UnilinkBit              ; And store it (this is needed when doing slave breaks and actually responding)\r
1056 \r
1057         call    ClearUnilinkBuffer\r
1058         movlw   10h                     ; Sending to Master\r
1059         addwf   UnilinkParity1,f\r
1060         movwf   UnilinkRAD\r
1061         movf    UnilinkID,w             ; This is my ID\r
1062         addwf   UnilinkParity1,f\r
1063         movwf   UnilinkTAD\r
1064         movlw   8ch                     ; Device discovery command again\r
1065         addwf   UnilinkParity1,f\r
1066         movwf   UnilinkCMD1\r
1067         movlw   10h\r
1068         addwf   UnilinkParity1,f\r
1069         movwf   UnilinkCMD2\r
1070 \r
1071         movf    UnilinkParity1,w\r
1072         movwf   UnilinkParity2M         ; That's the parity when sending medium messages\r
1073 \r
1074         movlw   24h\r
1075         addwf   UnilinkParity2M,f\r
1076         movwf   UnilinkData1\r
1077         movlw   0a8h                    ; My internal MD sends 1c here... (external/internal difference)\r
1078         addwf   UnilinkParity2M,f\r
1079         movwf   UnilinkData2\r
1080         movlw   17h\r
1081         addwf   UnilinkParity2M,f\r
1082         movwf   UnilinkData3\r
1083         movlw   0a0h                    ; 0a0=10disc\r
1084         addwf   UnilinkParity2M,f\r
1085         movwf   UnilinkData4\r
1086 \r
1087         bsf     BUSON_OUT_BIT           ; Now activate the cascade BUSON pin, to allow others after us to be discovered\r
1088 \r
1089         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
1090 \r
1091 IRQINTParseNot02\r
1092 \r
1093 ; Check for CMD1 = 80h (Display button)\r
1094         movf    UnilinkCMD1,w\r
1095         xorlw   080h\r
1096         bnz     IRQINTParseNot80\r
1097 \r
1098         movf    UnilinkID,w             ; Check if I'm currently selected\r
1099         xorwf   UnilinkCurID,w\r
1100         skpnz                           ; No, skip this command\r
1101 ;       bsf     DisplayStatus,7         ; Make sure we update the display again\r
1102         movlw   81h\r
1103         movwf   DisplayStatus\r
1104         goto    IRQINTParseComplete\r
1105 \r
1106 IRQINTParseNot80\r
1107 \r
1108 ; Check for CMD1 = 87h (Power control)\r
1109         movf    UnilinkCMD1,w\r
1110         xorlw   087h\r
1111         bnz     IRQINTParseNot87\r
1112 \r
1113 ; This part could use some more packet sniffing (really), it's sketchy to say the least.. :(\r
1114 ; The idea here is that the 18 10 87 2a PP 12 00 80 00 PP ZZ command is sent on power-up from my headunit if green color and\r
1115 ; if amber it looks like    18 10 87 2a PP 02 00 80 00 PP ZZ, giving away that high nibble (or bit 4) of D1 sets the color\r
1116 ; Also interesting is that the exact same commands gets sent when pressing the power-off button, this makes slaves pause\r
1117 ; playing, and then a few seconds later the unit shuts down with the following command:\r
1118 ; 18 10 87 22 PP 02 00 80 00 PP ZZ or\r
1119 ; 18 10 87 22 PP 12 00 80 00 PP ZZ depending on the color of the backlight\r
1120 ; This makes me think that bit 3 in CMD2 reflects the actual power state of the headunit (actually sleeping or bus active)\r
1121 ; Anyway I use this to set/clear the RI pin used for WakeOnRing on my laptop\r
1122 ; Also I de-select to make everything pause and clear the display status (if we're doing slave breaks after power status\r
1123 ; the headunit will never enter sleep!)\r
1124 ; From what I have gathered the bit mapping of DATA1 is as follows:\r
1125 ; 7 6 5 4 3 2 1 0\r
1126 ; X                - Backlight color changed if 1\r
1127 ;   ? ?\r
1128 ;       X          - Backlight color, 0=Amber, 1=Green\r
1129 ;         X        - Dimmer setting changed if 1\r
1130 ;           X X    - Dimmer setting, 01=Dimmer Auto, 10=Dimmer On, 00=Dimmer Off, 11=???\r
1131 ;               X  - Beep setting, 0=Beep on(!), 1=Beep off\r
1132 ;\r
1133 ; Also bit field of CMD2 for now:\r
1134 ; 7 6 5 4 3 2 1 0\r
1135 ; X X              - These two bits are set when changing color, beep etc, but not when actually powering the system on or off???\r
1136 ;     X            - Always set to 1 on my headunit\r
1137 ;       X          - Always set to 0 on my headunit\r
1138 ;         X        - Set to 1 when power is on, 0 when powering off (last command sent before bus dies is 87 22 on my unit)\r
1139 ;           X      - Always set to 0 on my headunit\r
1140 ;             X    - Always set to 1 on my headunit\r
1141 ;               X  - Always set to 0 on my headunit\r
1142 \r
1143 ; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)\r
1144         btfsc   UnilinkCMD2,3\r
1145         goto    IRQINTParse87PowerOn\r
1146 \r
1147         bsf     RS232_RI_BIT            ; Set this to make RI pin go low (after RS-232 levels)\r
1148         goto    IRQINTParseComplete\r
1149 \r
1150 IRQINTParse87PowerOn\r
1151         bcf     RS232_RI_BIT            ; Clear this to make RI pin go high (waking the computer)\r
1152 \r
1153         btfsc   UnilinkCMD2,7           ; Test high bit if it's just a "set" command, if yes don't clear status\r
1154         goto    IRQINTParseComplete\r
1155 \r
1156         bcf     UnilinkSelected,7       ; Also de-select us (this gets sent when powering off but before the actual power down)\r
1157         clrf    DisplayStatus\r
1158 \r
1159         goto    IRQINTParseComplete\r
1160 \r
1161 IRQINTParseNot87\r
1162 \r
1163 ; Check for CMD1 = 90h (Display/DSP info, volume etc.)\r
1164         movf    UnilinkCMD1,w\r
1165         xorlw   090h\r
1166         bnz     IRQINTParseNot90\r
1167 \r
1168 ; Check for 90 10 (Current Volume)\r
1169         movf    UnilinkCMD2,w\r
1170         xorlw   010h\r
1171         bnz     IRQINTParseNot9010\r
1172 \r
1173         movf    UnilinkData1,w          ; Store current volume setting\r
1174         movwf   UnilinkAttenuation\r
1175 \r
1176         goto    IRQINTParseComplete     ; Don't send any reply to this (clear the packet buffer though)\r
1177 \r
1178 IRQINTParseNot9010\r
1179 \r
1180 \r
1181 IRQINTParseNot90\r
1182 \r
1183 ; Check for CMD1 = f0h (Source Select)\r
1184         movf    UnilinkCMD1,w\r
1185         xorlw   0f0h\r
1186         bnz     IRQINTParseNotF0\r
1187 \r
1188         movf    UnilinkCMD2,w\r
1189         movwf   UnilinkCurID            ; Store it for display and debugging\r
1190 \r
1191         xorwf   UnilinkID,w             ; Check if it's selecting me\r
1192         bnz     IRQINTParseF0Deselect\r
1193 \r
1194         bsf     UnilinkSelected,7       ; Now we're selected\r
1195 ;       bsf     DisplayStatus,7\r
1196         movlw   81h\r
1197         movwf   DisplayStatus\r
1198         goto    IRQINTParseComplete\r
1199 \r
1200 IRQINTParseF0Deselect\r
1201 \r
1202         bcf     UnilinkSelected,7       ; Now we're de-selected\r
1203         bcf     DisplayStatus,7\r
1204         goto    IRQINTParseComplete\r
1205 \r
1206 IRQINTParseNotF0\r
1207 \r
1208 IRQINTParseComplete\r
1209 \r
1210 ; The code ends up here when parsing is complete and it's not interested in sending any reply back to the master\r
1211 ; (that's why we clear out all the packet buffer bytes)\r
1212 \r
1213         call    ClearUnilinkBuffer\r
1214 \r
1215 IRQINTParseBypassClear\r
1216 \r
1217         movlw   UnilinkRAD              ; Get the pointer to the first byte in the receive buffer\r
1218         movwf   UnilinkTXRX             ; Store it - this way the next byte that gets received goes into RAD\r
1219 \r
1220         clrf    UnilinkCmdLen           ; No command length while waiting for a new packet\r
1221 \r
1222         \r
1223 IRQINTRecvIncomplete\r
1224 \r
1225 IRQINTRecvNullByte\r
1226 ;       movf    INDF,w\r
1227 ;       movwf   DataStore               ; Store it so the non-irq code can snoop\r
1228 \r
1229 IRQAfterINT\r
1230         bcf     INTCON,INTF             ; Clear the IRQ source bit to re-enable INT interrupts again\r
1231 \r
1232 IRQNotINT\r
1233         return\r
1234 \r
1235 ;----------------------------------------------------------------\r
1236 ; ClearUnilinkStatus - Zeroes out the Unilink state (used when initializing)\r
1237 \r
1238 ClearUnilinkStatus\r
1239 \r
1240         clrf    UnilinkID               ; Clear the existing Unilink ID, if any\r
1241         clrf    UnilinkCurID            ; Clear the currently selected ID as well\r
1242         bcf     BUSON_OUT_BIT           ; Clear the cascade BUSON pin, not activated again until we have a new ID\r
1243         clrf    DisplayStatus           ; No crazy display updates when resetting.. :)\r
1244         clrf    UnilinkSelected         ; We're not selected anymore\r
1245 \r
1246         bsf     STATUS,RP0              ; Reg bank 1\r
1247         bsf     DATA_BIT                ; Make sure data is tristated\r
1248         bcf     STATUS,RP0              ; Reg bank 0\r
1249 \r
1250         movlw   UnilinkRAD              ; Get the pointer to the first byte in the receive buffer\r
1251         movwf   UnilinkTXRX             ; Store it - this way the next byte that gets received goes into RAD\r
1252 \r
1253         clrf    UnilinkCmdLen           ; No command length while waiting for a new packet\r
1254 \r
1255         clrf    SlaveBreakState         ; Slave Break Processing has to start all over\r
1256 \r
1257         return\r
1258         \r
1259 ;----------------------------------------------------------------\r
1260 ; ClearUnilinkBuffer - Zeroes out the Unilink packet buffer\r
1261 \r
1262 ClearUnilinkBuffer\r
1263 \r
1264 ; TODO: Replace this with an FSR access to save space and make the code neater\r
1265         clrf    UnilinkRAD\r
1266         clrf    UnilinkTAD\r
1267         clrf    UnilinkCMD1\r
1268         clrf    UnilinkCMD2\r
1269         clrf    UnilinkParity1\r
1270         clrf    UnilinkData1\r
1271         clrf    UnilinkData2\r
1272         clrf    UnilinkData3\r
1273         clrf    UnilinkData4\r
1274         clrf    UnilinkData5\r
1275         clrf    UnilinkData6\r
1276         clrf    UnilinkData7\r
1277         clrf    UnilinkData8\r
1278         clrf    UnilinkData9\r
1279         clrf    UnilinkParity2\r
1280         clrf    UnilinkZero\r
1281 \r
1282         return\r
1283 \r
1284 \r
1285         subtitl "Main loop"\r
1286         page\r
1287 \r
1288 ;----------------------------------------------------------------\r
1289 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]\r
1290 ; Here all other house keeping tasks are performed, like displaying info on the LCD.. \r
1291 \r
1292 Main\r
1293         movlw   high LookUp             ; Set the high PC bits to indicate data lookup page\r
1294         movwf   PCLATH\r
1295 \r
1296         movlw   0ffh                    ; Set infinite attenuation to begin with\r
1297         movwf   UnilinkAttenuation\r
1298 \r
1299         clrf    RecvType\r
1300         movlw   RecvType\r
1301         movwf   CurRecvPos\r
1302 \r
1303         clrf    UnilinkReInits          ; Clear the bus re-initialization counter\r
1304 \r
1305         bsf     STATUS,RP0              ; Reg bank 1\r
1306         movlw   080h                    ; Right adjusted A/D, all analog inputs, no Vrefs\r
1307         movwf   ADCON1\r
1308         bcf     STATUS,RP0\r
1309 \r
1310         movlw   081h                    ; Activate A/D, ch 0, Fosc/32 (for 20MHz operation)\r
1311         movwf   ADCON0\r
1312 \r
1313         bsf     ADCON0,2                ; Start the first A/D operation\r
1314 \r
1315         movlw   8                       ; Display page timing (approx 8/sec)\r
1316         movwf   DisplayCounter\r
1317 \r
1318         bcf     LCD_RS_BIT              ; LCD Command mode\r
1319         movlw   80h                     ; DisplayRam 0\r
1320         call    TxLCDB\r
1321         bsf     LCD_RS_BIT\r
1322 \r
1323         movlw   low DefaultText1\r
1324         movwf   Icount\r
1325         movlw   80\r
1326         movwf   e_LEN\r
1327         call    TxLCD8BLoop             ; Send 80 bytes to the LCD\r
1328 \r
1329 MainLoop\r
1330 \r
1331         bcf     LCD_RS_BIT              ; LCD Command mode\r
1332         movlw   80h                     ; DisplayRam 0\r
1333         call    TxLCDB\r
1334         bsf     LCD_RS_BIT\r
1335 \r
1336 ;       movlw   '0'\r
1337         movf    Counter,w               ; Debug timer\r
1338         btfsc   PORTA,4                 ; Test RST\r
1339         movlw   'R'\r
1340         call    TxLCDB\r
1341 \r
1342 ;       movlw   '0'\r
1343         movf    SlaveBreakState,w\r
1344         andlw   80h\r
1345         btfsc   PORTB,0                 ; Test CLK\r
1346         movlw   'C'\r
1347         call    TxLCDB\r
1348 \r
1349         movlw   '0'\r
1350         btfsc   PORTC,2                 ; Test BUSON-IN\r
1351         movlw   'B'\r
1352         call    TxLCDB\r
1353 \r
1354         movlw   '0'\r
1355         btfsc   PORTC,3                 ; Test DATA\r
1356         movlw   'D'\r
1357         call    TxLCDB\r
1358 \r
1359         movf    UnilinkCmdLen,w\r
1360         bz      MainDontPrintCmd\r
1361         addlw   '0'\r
1362         call    TxLCDB\r
1363 \r
1364 MainDontPrintCmd\r
1365 \r
1366 ; Default text\r
1367 ; UnilinkID @ 13-14\r
1368 ; UnilinkAttenuation @ 16-17\r
1369 ; UnilinkSelected @ 28-29\r
1370 ; UnilinkReInits @ 38,39\r
1371 ; UnilinkCurID @ 54-55\r
1372 ; DisplayStatus @ 62-63\r
1373 ; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)\r
1374 \r
1375         bcf     LCD_RS_BIT              ; LCD Command mode\r
1376         movlw   80h+13                  ; DisplayRam 13\r
1377         call    TxLCDB\r
1378         bsf     LCD_RS_BIT\r
1379 \r
1380         movf    UnilinkID,w\r
1381         call    TxLCDHEX\r
1382 \r
1383         bcf     LCD_RS_BIT              ; LCD Command mode\r
1384         movlw   80h+16                  ; DisplayRam 16\r
1385         call    TxLCDB\r
1386         bsf     LCD_RS_BIT\r
1387 \r
1388         movf    UnilinkAttenuation,w\r
1389         call    TxLCDHEX\r
1390 \r
1391         bcf     LCD_RS_BIT              ; LCD Command mode\r
1392         movlw   80h+28                  ; DisplayRam 28\r
1393         call    TxLCDB\r
1394         bsf     LCD_RS_BIT\r
1395 \r
1396         movf    UnilinkSelected,w\r
1397         call    TxLCDHEX\r
1398 \r
1399         bcf     LCD_RS_BIT              ; LCD Command mode\r
1400         movlw   80h+38                  ; DisplayRam 38\r
1401         call    TxLCDB\r
1402         bsf     LCD_RS_BIT\r
1403 \r
1404         movf    UnilinkReInits,w\r
1405         call    TxLCDHEX\r
1406 \r
1407         bcf     LCD_RS_BIT              ; LCD Command mode\r
1408         movlw   80h+40h+14              ; DisplayRam 54\r
1409         call    TxLCDB\r
1410         bsf     LCD_RS_BIT\r
1411 \r
1412         movf    UnilinkCurID,w\r
1413         call    TxLCDHEX\r
1414 \r
1415         bcf     LCD_RS_BIT              ; LCD Command mode\r
1416         movlw   80h+40h+22              ; DisplayRam 62\r
1417         call    TxLCDB\r
1418         bsf     LCD_RS_BIT\r
1419 \r
1420         movf    DisplayStatus,w\r
1421         call    TxLCDHEX\r
1422 \r
1423         btfsc   ADCON0,2                ; Test if A/D is ready\r
1424         goto    MainADNotReady\r
1425 \r
1426         bsf     STATUS,RP0\r
1427         movf    ADRESL,w                ; Add to our result\r
1428         bcf     STATUS,RP0\r
1429         addwf   NumL,f\r
1430         skpnc\r
1431         incf    NumH,f\r
1432         movf    ADRESL,w\r
1433         addwf   NumH,f                  ; And the high byte\r
1434 \r
1435         movlw   20h\r
1436         addwf   NumH,f\r
1437         skpc                            ; When this overflows we know there are 8 samples collected\r
1438         goto    MainADStartAD\r
1439 \r
1440 ; Now shift the added results two steps down (/4) as there are 8 added samples here, and filter high bits\r
1441         movlw   1fh\r
1442         andwf   NumH,f\r
1443         clrc\r
1444         rrf     NumH,f\r
1445         rrf     NumL,f\r
1446         clrc\r
1447         rrf     NumH,f\r
1448         rrf     NumL,f\r
1449                 \r
1450         movf    Counter,w\r
1451         bnz     MainADSkipDisplay\r
1452 \r
1453         bcf     LCD_RS_BIT              ; LCD Command mode\r
1454         movlw   80h+40h+26              ; DisplayRam 66\r
1455         call    TxLCDB\r
1456         bsf     LCD_RS_BIT\r
1457 \r
1458         call    BCDConvert\r
1459         movf    Thou,w\r
1460         addlw   30h\r
1461         call    TxLCDB\r
1462         movf    Hund,w\r
1463         addlw   30h\r
1464         call    TxLCDB\r
1465         movlw   '.'\r
1466         call    TxLCDB\r
1467         movf    Tens,w\r
1468         addlw   30h\r
1469         call    TxLCDB\r
1470         movf    Ones,w\r
1471         addlw   30h\r
1472         call    TxLCDB\r
1473 \r
1474 MainADSkipDisplay\r
1475 \r
1476         clrf    NumL\r
1477         clrf    NumH\r
1478 \r
1479 MainADStartAD\r
1480         bsf     ADCON0,2                ; Start a new conversion\r
1481 \r
1482 MainADNotReady\r
1483 \r
1484 ; This part handles display "scroll" by shifting one screen at a time\r
1485 \r
1486         btfss   Counter,7               ; Test high bit\r
1487         goto    MainCounterLow\r
1488 \r
1489 ; So bit is high, set high bit of displaycounter as well...\r
1490         bsf     DisplayCounter,7\r
1491         goto    MainSkipScroll\r
1492 \r
1493 MainCounterLow\r
1494 ; OK, bit is low, now figure out whether it was high or low last time -> check high bit of DisplayCounter\r
1495         btfss   DisplayCounter,7\r
1496         goto    MainSkipScroll\r
1497 \r
1498         bcf     DisplayCounter,7        ; Clear the high bit to allow countdown to commence\r
1499         movf    Counter,w               ; Load it\r
1500         skpz\r
1501         goto    MainSkipScroll\r
1502         decfsz  DisplayCounter,f\r
1503         goto    MainSkipScroll\r
1504         \r
1505         movlw   8\r
1506         movwf   DisplayCounter\r
1507 \r
1508         bcf     LCD_RS_BIT              ; LCD Command mode\r
1509         movlw   18h                     ; Display shift Left\r
1510         call    TxLCDB                  ; Shift it 8 positions\r
1511         call    TxLCDB\r
1512         call    TxLCDB\r
1513         call    TxLCDB\r
1514         call    TxLCDB\r
1515         call    TxLCDB\r
1516         call    TxLCDB\r
1517         call    TxLCDB\r
1518         bsf     LCD_RS_BIT\r
1519         \r
1520 MainSkipScroll\r
1521 \r
1522 ; Display scroll part ends here...\r
1523 \r
1524         goto    MainLoop\r
1525 \r
1526 \r
1527 ;----------------------------------------------------------------\r
1528 ; IRQInit - Sets up the IRQ Handler\r
1529 ; 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
1530 ; Also enable INT interrupts for Unilink CLK processing\r
1531 \r
1532 IRQInit\r
1533 \r
1534         call    ClearUnilinkStatus\r
1535         call    ClearUnilinkBuffer\r
1536 \r
1537 ; Fix the output state of RI and BUSON_OUT to a safe default\r
1538 \r
1539         bsf     RS232_RI_BIT            ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)\r
1540         bcf     BUSON_OUT_BIT           ; BUSON_OUT should be disabled for now, must be appointed first\r
1541 \r
1542         movlw   06h                     ; Timer2 enabled + 1/16 prescaler\r
1543         movwf   T2CON\r
1544 \r
1545         bsf     STATUS,RP0              ; Reg bank 1\r
1546 \r
1547         movlw   09ch                    ; Timer PR2 reg giving 2000 interrupts per second\r
1548         movwf   PR2\r
1549 \r
1550         bcf     RS232_RI_BIT            ; Both bits should be outputs\r
1551         bcf     BUSON_OUT_BIT           ;\r
1552 \r
1553 ; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...\r
1554 ;       bcf     OPTION_REG,INTEDG       ; We want RB0 to give us an IRQ on the falling edge\r
1555 \r
1556         bsf     INTCON,INTE             ; Enable the RB0/INT\r
1557         bsf     INTCON,PEIE             ; Enable the peripheral interrupts\r
1558         bsf     PIE1,TMR2IE             ; Enable the Timer2 peripheral interrupt\r
1559         bsf     PIE1,RCIE               ; Enable the UART receive interrupt\r
1560         bsf     INTCON,GIE              ; Enable global interrupts\r
1561 \r
1562         bsf     TXSTA,TXEN              ; Enable UART TX\r
1563 \r
1564         bcf     STATUS,RP0              ; Back to bank 0\r
1565 \r
1566         bsf     RCSTA,SPEN              ; Enable serial port\r
1567         bsf     RCSTA,CREN              ; Enable UART RX\r
1568 \r
1569         return\r
1570 \r
1571 ;----------------------------------------------------------------\r
1572 ;  Initialize LCD Controller...\r
1573 \r
1574 LCDInit\r
1575         clrf    PORTB                   ; First clear PortB data register\r
1576         bsf     STATUS,RP0              ; Reg bank 1\r
1577         movlw   001h                    ; All but RB0 are outputs.\r
1578         movwf   TRISB                   ;\r
1579 \r
1580         bcf     OPTION_REG,NOT_RBPU     ; Turn on port B pull-up\r
1581         bcf     STATUS,RP0              ; Restore Reg bank 0\r
1582 \r
1583 ; This is a standard reset sequence for the LCD controller\r
1584 \r
1585         movlw   170                     ; Need to delay for at least 15ms, let's go for 17ms delay\r
1586         call    DelayW\r
1587 \r
1588         movlw   3                       ; Write 3 to the LCD\r
1589         call    TxLCD                   ; Send to LCD\r
1590         movlw   60                      ; Need to delay for at least 4.1ms, let's go for 6ms delay\r
1591         call    DelayW\r
1592 \r
1593         movlw   3                       ; Write 3 to the LCD\r
1594         call    TxLCD\r
1595         movlw   10                      ; Need to delay for at least 100us, let's go for 1ms delay\r
1596         call    DelayW\r
1597 \r
1598         movlw   3                       ; Write 3 to the LCD\r
1599         call    TxLCD\r
1600         movlw   10                      ; Need to delay for at least 40us, let's go for 1ms delay\r
1601         call    DelayW\r
1602 \r
1603         movlw   2                       ; 4-bit interface requested\r
1604         call    TxLCD                   ;\r
1605         movlw   10                      ; Need to delay for at least 40us, let's go for 1ms delay\r
1606         call    DelayW                  ;\r
1607 \r
1608 ; Reset sequence ends here\r
1609 ; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits\r
1610 \r
1611         movlw   28h                     ; Function Select + 4-bit bus + 2-line display\r
1612         call    TxLCDB\r
1613 \r
1614         movlw   0ch                     ; Display Control + LCD On (No cursor)\r
1615         call    TxLCDB\r
1616 \r
1617         movlw   01h                     ; Clear Display\r
1618         call    TxLCDB\r
1619 \r
1620         movlw   06h                     ; Auto Increment cursor position\r
1621         call    TxLCDB\r
1622 \r
1623         bsf     LCD_RS_BIT              ; Accept data\r
1624 \r
1625         return\r
1626    \r
1627 ;----------------------------------------------------------------\r
1628 ;  TxLCDHEX\r
1629 ;  Sends two characters hex to the LCD\r
1630 \r
1631 TxLCDHEX\r
1632 \r
1633 ; Original binary to 2-digit hex conversion from piclist.com, modified to fit here\r
1634 \r
1635         movwf   Icount\r
1636         swapf   Icount,w\r
1637         andlw   0x0f\r
1638 \r
1639         addlw   6\r
1640         skpndc\r
1641         addlw   'A'-('9'+1)\r
1642         addlw   '0'-6\r
1643 \r
1644         xorwf   Icount,w\r
1645         xorwf   Icount,f\r
1646         xorwf   Icount,w\r
1647 \r
1648         andlw   0x0f\r
1649 \r
1650         addlw   6\r
1651         skpndc\r
1652         addlw   'A'-('9'+1)\r
1653         addlw   '0'-6\r
1654 \r
1655         movwf   e_LEN\r
1656         movf    Icount,w\r
1657         call    TxLCDB\r
1658         movf    e_LEN,w\r
1659         call    TxLCDB\r
1660 \r
1661         return\r
1662 \r
1663 ;----------------------------------------------------------------\r
1664 ;\r
1665 ; Binary-to-BCD.  Written by John Payson.\r
1666 ; Taken from piclist.com - why re-invent the wheel when writing open-sourced code?\r
1667 ;\r
1668 ; Enter with 16-bit binary number in NumH:NumL.\r
1669 ; Exits with BCD equivalent in TenK:Thou:Hund:Tens:Ones.\r
1670 ;\r
1671 \r
1672 BCDConvert:                             ; Takes number in NumH:NumL\r
1673                                         ; Returns decimal in\r
1674                                         ; TenK:Thou:Hund:Tens:Ones\r
1675         swapf   NumH,w\r
1676         andlw   0Fh                     ;*** PERSONALLY, I'D REPLACE THESE 2\r
1677         addlw   0F0h                    ;*** LINES WITH "IORLW 11110000B" -AW\r
1678         movwf   Thou\r
1679         addwf   Thou,f\r
1680         addlw   0E2h\r
1681         movwf   Hund\r
1682         addlw   032h\r
1683         movwf   Ones\r
1684 \r
1685         movf    NumH,w\r
1686         andlw   0Fh\r
1687         addwf   Hund,f\r
1688         addwf   Hund,f\r
1689         addwf   Ones,f\r
1690         addlw   0E9h\r
1691         movwf   Tens\r
1692         addwf   Tens,f\r
1693         addwf   Tens,f\r
1694 \r
1695         swapf   NumL,w\r
1696         andlw   0Fh\r
1697         addwf   Tens,f\r
1698         addwf   Ones,f\r
1699 \r
1700         rlf     Tens,f\r
1701         rlf     Ones,f\r
1702         comf    Ones,f\r
1703         rlf     Ones,f\r
1704 \r
1705         movf    NumL,w\r
1706         andlw   0Fh\r
1707         addwf   Ones,f\r
1708         rlf     Thou,f\r
1709 \r
1710         movlw   07h\r
1711         movwf   TenK\r
1712 \r
1713                                         ; At this point, the original number is\r
1714                                         ; equal to TenK*10000+Thou*1000+Hund*100+Tens*10+Ones\r
1715                                         ; if those entities are regarded as two's compliment\r
1716                                         ; binary.  To be precise, all of them are negative\r
1717                                         ; except TenK.  Now the number needs to be normal-\r
1718                                         ; ized, but this can all be done with simple byte\r
1719                                         ; arithmetic.\r
1720 \r
1721         movlw   0Ah                     ; Ten\r
1722 Lb1:\r
1723         addwf   Ones,f\r
1724         decf    Tens,f\r
1725         btfss   3,0\r
1726         goto    Lb1\r
1727 Lb2:\r
1728         addwf   Tens,f\r
1729         decf    Hund,f\r
1730         btfss   3,0\r
1731         goto    Lb2\r
1732 Lb3:\r
1733         addwf   Hund,f\r
1734         decf    Thou,f\r
1735         btfss   3,0\r
1736         goto    Lb3\r
1737 Lb4:\r
1738         addwf   Thou,f\r
1739         decf    TenK,f\r
1740         btfss   3,0\r
1741         goto    Lb4\r
1742 \r
1743         retlw   0\r
1744 \r
1745 ;----------------------------------------------------------------\r
1746 ;  TxLCD16B\r
1747 ;  Send a string to the LCD.\r
1748 \r
1749 TxLCD16B\r
1750         movwf   Icount\r
1751         bcf     LCD_RS_BIT\r
1752         movlw   80h                     ; DisplayRam 0\r
1753         call    TxLCDB\r
1754         bsf     LCD_RS_BIT\r
1755         call    TxLCD8B\r
1756         bcf     LCD_RS_BIT\r
1757         movlw   80h+40                  ; DisplayRam 40 (row 2)\r
1758         call    TxLCDB\r
1759         bsf     LCD_RS_BIT\r
1760         call    TxLCD8B\r
1761         return\r
1762 \r
1763 ;----------------------------------------------------------------\r
1764 ;  TxLCD8B\r
1765 ;  Send a string to the LCD.\r
1766 \r
1767 TxLCD8B\r
1768 ;       movwf   Icount                  ; Icount = W\r
1769         movlw   8\r
1770         movwf   e_LEN                   ; Move to e_LEN\r
1771 \r
1772 TxLCD8BLoop\r
1773         movf    Icount,w                ; get the byte\r
1774         call    LookUp\r
1775         incf    Icount,f                ; ...else ++Icount (table index)\r
1776         call    TxLCDB                  ; Send out the byte\r
1777         decfsz  e_LEN,f\r
1778         goto    TxLCD8BLoop\r
1779         return\r
1780 \r
1781 ;----------------------------------------------------------------\r
1782 ; TxLCDB - send a byte to the LCD\r
1783 \r
1784 TxLCDB\r
1785         movwf   TxTemp                  ; Store byte to send for a while...\r
1786 \r
1787         bcf     temp,0                  ; Clear my temp bit\r
1788         btfss   LCD_RS_BIT              ; Check if we try the correct reg\r
1789         goto    RxNoProb\r
1790         bcf     LCD_RS_BIT\r
1791         bsf     temp,0                  ; Indicate RS change\r
1792 RxNoProb\r
1793 \r
1794 NotReady\r
1795         call    RxLCDB                  ; Receive byte from LCD, status reg\r
1796         andlw   80h\r
1797         skpz                            ; If the bit was set, the zero flag is not\r
1798         goto    NotReady\r
1799 \r
1800         btfsc   temp,0                  ; If we had to clear RS reset it now\r
1801         bsf     LCD_RS_BIT\r
1802 \r
1803         swapf   TxTemp,w                ; Hi nibble of data to send in lo w bits\r
1804         call    TxLCD                   ; Send them first...\r
1805         movf    TxTemp,w                ; Then we have the low nibble in low w bits\r
1806         call    TxLCD                   ; And send that one as well\r
1807 \r
1808         return\r
1809 \r
1810 ;----------------------------------------------------------------\r
1811 ; RxLCDB - recv a byte from the LCD\r
1812 \r
1813 RxLCDB\r
1814         call    RxLCD                   ; Receive the high nibble\r
1815         movwf   LCDWTmp\r
1816         swapf   LCDWTmp,f               ; Swap it back to file\r
1817         call    RxLCD                   ; Receive the low nibble\r
1818         addwf   LCDWTmp,w               ; Put the nibbles together and return in W\r
1819 \r
1820         return\r
1821 \r
1822 ;----------------------------------------------------------------\r
1823 ; TxLCD - send a nibble to the LCD\r
1824 \r
1825 TxLCD\r
1826         movwf   LCDWTmp                 ; Write nibble to tmp\r
1827         bcf     LCD_DB4_BIT             ; Clear previous data\r
1828         bcf     LCD_DB5_BIT             ; \r
1829         bcf     LCD_DB6_BIT             ;\r
1830         bcf     LCD_DB7_BIT             ;\r
1831 \r
1832         btfsc   LCDWTmp,0               ; Test bit 0, transfer a set bit to LCD PORT\r
1833         bsf     LCD_DB4_BIT\r
1834         btfsc   LCDWTmp,1               ; Test bit 1, transfer a set bit to LCD PORT\r
1835         bsf     LCD_DB5_BIT\r
1836         btfsc   LCDWTmp,2               ; Test bit 2, transfer a set bit to LCD PORT\r
1837         bsf     LCD_DB6_BIT\r
1838         btfsc   LCDWTmp,3               ; Test bit 3, transfer a set bit to LCD PORT\r
1839         bsf     LCD_DB7_BIT\r
1840 \r
1841         bsf     LCD_E_BIT               ; And set E to clock the data into the LCD module\r
1842         nop                             ; Let it settle\r
1843         bcf     LCD_E_BIT               ; And clear the Enable again.\r
1844         return                          ; Returns without modifying W\r
1845 \r
1846 ;----------------------------------------------------------------\r
1847 ; RxLCD - recv a nibble from the LCD\r
1848 \r
1849 RxLCD\r
1850         clrw                            ; Clear W register, return data in lower 4 bits\r
1851 \r
1852         bsf     STATUS,RP0              ; Select 2nd reg bank, now TRIS regs can be accessed\r
1853         \r
1854         bsf     LCD_DB4_BIT             ; This sets the port bit as an input\r
1855         bsf     LCD_DB5_BIT     \r
1856         bsf     LCD_DB6_BIT     \r
1857         bsf     LCD_DB7_BIT\r
1858 \r
1859         bcf     STATUS,RP0              ; Back at reg bank 0    \r
1860 \r
1861         bsf     LCD_RW_BIT              ; Set Read mode for the LCD\r
1862         bsf     LCD_E_BIT               ; And set E to clock the data out of the LCD module\r
1863         nop                             ; Let the bus settle\r
1864         btfsc   LCD_DB4_BIT             ; Transfer a set port bit into W\r
1865         addlw   1\r
1866         btfsc   LCD_DB5_BIT             ; Transfer a set port bit into W\r
1867         addlw   2\r
1868         btfsc   LCD_DB6_BIT             ; Transfer a set port bit into W\r
1869         addlw   4\r
1870         btfsc   LCD_DB7_BIT             ; Transfer a set port bit into W\r
1871         addlw   8\r
1872         bcf     LCD_E_BIT               ; And clear the Enable again.\r
1873         bcf     LCD_RW_BIT              ; Set Write mode for the LCD\r
1874 \r
1875         bsf     STATUS,RP0              ; Select 2nd reg bank, now TRIS regs can be accessed\r
1876 \r
1877         bcf     LCD_DB4_BIT             ; Set the port as an output again\r
1878         bcf     LCD_DB5_BIT             ; \r
1879         bcf     LCD_DB6_BIT             ;\r
1880         bcf     LCD_DB7_BIT             ;\r
1881 \r
1882         bcf     STATUS,RP0              ; Back at reg bank 0    \r
1883 \r
1884         return                          ; Returns with data in W\r
1885 \r
1886 ;----------------------------------------------------------------------\r
1887 ; Delay routines (non-interrupt based, therefore not even close to reliable)\r
1888 ; W=10 gives ~ 1ms of delay\r
1889 ; 1ms=5000 instructions wasted, 100us=500 cycles\r
1890 ; Maximum time waited will be 256*100us=25.6ms\r
1891 \r
1892 DelayW\r
1893         movwf   Dcount                  ; Set delay counter, number of 100us periods to wait\r
1894 \r
1895 DelayOuter\r
1896         movlw   0a5h                    ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more\r
1897         movwf   Dcount2                 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount\r
1898 DelayInner\r
1899         decfsz  Dcount2,f               ; 1 cycle (or two when exiting the loop)\r
1900         goto    DelayInner              ; 2 cycles\r
1901         decfsz  Dcount,f                ; Now decrement number of 100us periods and loop again\r
1902         goto    DelayOuter\r
1903         return\r
1904 \r
1905 \r
1906 ;----------------------------------------------------------------\r
1907 ;  Data can be stored between 600 and 6ffh...\r
1908 \r
1909         org     600h\r
1910 \r
1911 ; Default text\r
1912 ; UnilinkID @ 13-14\r
1913 ; UnilinkAttenuation @ 16-17\r
1914 ; UnilinkSelected @ 28-29\r
1915 ; UnilinkReInits @ 38,39\r
1916 ; UnilinkCurID @ 54-55\r
1917 ; DisplayStatus @ 62-63\r
1918 ; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)\r
1919 \r
1920 DefaultText1\r
1921         DT      "----- WJ", "MyID:xx ", "xx dB at", "Sel:xx B", "Inits:xx"\r
1922         DT      " Unilink", "CurID:xx", "t Dsp:xx", "atxx.xxV", "    <WJ>"\r
1923 \r
1924 PCWaitText0\r
1925 ;       DT      ">Waiting"\r
1926 ;       DT      "Wait->PC"               \r
1927         DT      "Booting."\r
1928 PCWaitText1\r
1929 ;       DT      " for PC<"\r
1930 ;       DT      " Booting"               \r
1931         DT      "..",0,0,0,0,0,0\r
1932 \r
1933 LookUp  movwf   PCL                     ; Go to it (this assumes PCLATH == 06h)\r
1934 \r
1935 \r
1936         subtitl "Bootstrap/Bootloader code"\r
1937         page\r
1938 \r
1939 ;----------------------------------------------------------------------\r
1940 ; Bootstrap code - Allows PIC to flash itself with data from the async port.\r
1941 ; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory\r
1942 ; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver\r
1943 ; (one good way of solving that is to wait for the echo from the PIC before sending anything else)\r
1944 ; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration\r
1945 ; register can't be programmed. That means that any references to the config register in the hex file will be ignored.\r
1946 ;\r
1947 ; Startup @9600bps\r
1948 \r
1949 ; RAM usage for the bootstrap code\r
1950 \r
1951 BootBits        equ     7eh             ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog\r
1952 BootAddrL       equ     7dh\r
1953 BootAddrH       equ     7ch\r
1954 BootDataL       equ     7bh\r
1955 BootDataH       equ     7ah\r
1956 BootTimerL      equ     79h\r
1957 BootTimerM      equ     78h\r
1958 BootTimerH      equ     77h\r
1959 BootNumBytes    equ     76h\r
1960 BootDataVL      equ     75h\r
1961 BootDataVH      equ     74h\r
1962 BootHEXTemp     equ     73h\r
1963 \r
1964         org     738h                    ; Place the boot code at the top of memory (currently the loader is exactly 200 bytes)\r
1965 \r
1966 Bootstrap\r
1967         bsf     STATUS,RP0              ; Access bank 1\r
1968         bsf     TXSTA,TXEN              ; Enable UART TX\r
1969         movlw   31                      ; Divisor for 9k6 @ 20MHz Fosc\r
1970         movwf   SPBRG                   ; Store\r
1971         bcf     STATUS,RP0              ; Back to bank 0\r
1972 \r
1973         bsf     RCSTA,SPEN              ; Enable serial port\r
1974         bsf     RCSTA,CREN              ; Enable UART RX\r
1975 \r
1976         movlw   low BootStartText       ; Send boot banner to the serial port\r
1977         call    BootTXStr\r
1978 \r
1979 ;       movlw   0e8h                    ; Initialize timeout timer (e8 is about 3 secs)\r
1980         movlw   0fdh                    ; Initialize timeout timer (fd is short enough to get the headunit to recognize us)\r
1981         movwf   BootTimerL\r
1982         movwf   BootTimerM\r
1983         movwf   BootTimerH\r
1984 \r
1985 BootTimeout\r
1986         incf    BootTimerL,f            ; A 24-bit counter\r
1987         skpnz\r
1988         incf    BootTimerM,f\r
1989         skpnz\r
1990         incf    BootTimerH,f\r
1991         skpnz                           ; When overflowing here..\r
1992         goto    BootReturn              ; ..Exit boot loader, no keypress within timeout period, resume program\r
1993         btfss   PIR1,RCIF               ; Wait for RX to complete\r
1994         goto    BootTimeout\r
1995         call    BootRXB\r
1996         xorlw   27                      ; ESC\r
1997         skpz\r
1998         goto    BootTimeout             ; If it wasn't ESC, wait for another key\r
1999 \r
2000 BootFlash\r
2001         movlw   low BootFlashText       ; OK, flashing it is, send "start" text to serial port\r
2002         call    BootTXStr\r
2003 \r
2004         bsf     BootBits,1\r
2005         clrf    BootAddrL\r
2006         clrf    BootAddrH\r
2007 \r
2008 BootLoop\r
2009         call    BootRXB                 ; First find the ':'\r
2010         xorlw   ':'\r
2011         skpz\r
2012         goto    BootLoop                ; Loop until we find it!\r
2013 \r
2014         call    BootRXHEX               ; Get one ASCII encoded byte (two chars)\r
2015         movwf   BootNumBytes            ; This is the number of bytes to be programmed on the line\r
2016 ; Maybe clear cary here?\r
2017         rrf     BootNumBytes,f          ; Right shift because we're double addressing this 8-bit format\r
2018 \r
2019 ; Note carry should be clear here as there cannot be odd number of bytes in this format\r
2020 \r
2021         call    BootRXHEX               ; Receive AddrH\r
2022         movwf   BootAddrH\r
2023         call    BootRXHEX               ; Receive AddrL\r
2024         movwf   BootAddrL\r
2025         rrf     BootAddrH,f             ; Fix the addressing again\r
2026         rrf     BootAddrL,f\r
2027 \r
2028         bcf     BootBits,2              ; Assume we should program\r
2029         bsf     BootBits,1              ; And assume we should program flash not ee\r
2030 \r
2031         movf    BootAddrH,w\r
2032         xorlw   020h                    ; Check if it's configuration, which we can't program\r
2033         skpnz                           ; Skip the bit set if it was false alarm\r
2034         bsf     BootBits,2              ; No programming for this line\r
2035 \r
2036         xorlw   001h                    ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)\r
2037         skpnz                           ; Skip the bit set instr if not EE data address\r
2038         bcf     BootBits,1              ; We should program EE, will ignore the AddrH\r
2039 \r
2040         call    BootRXHEX               ; Receive Record Type (must be 0 for real records)\r
2041         skpz                            ; Check if zero\r
2042         goto    BootFlashComplete\r
2043 \r
2044 BootLineLoop\r
2045         call    BootRXHEX               ; Receive low-byte of data word\r
2046         movwf   BootDataVL\r
2047         call    BootRXHEX               ; Receive high-byte of data word\r
2048         movwf   BootDataVH\r
2049         \r
2050         btfsc   BootBits,2              ; Check whether this line should be programmed at all\r
2051         goto    BootWriteSkip\r
2052 \r
2053         bcf     BootBits,0              ; Read mode first, verify if we actually have to write\r
2054         call    BootEE\r
2055         movf    BootDataVL,w\r
2056         xorwf   BootDataL,f             ; Compare and destroy DataL\r
2057         movwf   BootDataL               ; Write new data to DataL\r
2058         skpz                            ; Skip if no difference, have to check high byte as well\r
2059         goto    BootWrite               ; Jump directly to write\r
2060 \r
2061         movf    BootDataVH,w\r
2062         xorwf   BootDataH,f             ; Compare\r
2063         skpnz                           ; Skip if no difference, no programming necessary\r
2064         goto    BootWriteSkip\r
2065 \r
2066 BootWrite\r
2067         movf    BootDataVH,w\r
2068         movwf   BootDataH               ; Have to put the new H byte data in as well\r
2069 \r
2070         bsf     BootBits,0\r
2071         call    BootEE                  ; Write directly into program mem\r
2072 \r
2073 ; Here a verify can take place, the read-back results are now in DataL/H\r
2074 \r
2075 BootWriteSkip\r
2076 \r
2077         incf    BootAddrL,f             ; Advance counter to next addr\r
2078         skpnz\r
2079         incf    BootAddrH,f             ; And add to high byte if needed\r
2080 \r
2081         decfsz  BootNumBytes,f\r
2082         goto    BootLineLoop\r
2083 \r
2084         goto    BootLoop\r
2085 \r
2086 BootFlashComplete\r
2087         \r
2088 BootReturn\r
2089         movlw   low BootRunText\r
2090         call    BootTXStr\r
2091 \r
2092         bsf     STATUS,RP0              ; Reg bank 1\r
2093 BootReturnWait\r
2094         btfss   TXSTA,TRMT              ; Wait for last things to flush\r
2095         goto    BootReturnWait\r
2096         bcf     TXSTA,TXEN              ; Disable UART TX\r
2097         bcf     STATUS,RP0              ; Back to bank 0\r
2098 \r
2099         bcf     RCSTA,SPEN              ; Disable serial port\r
2100         bcf     RCSTA,CREN              ; Disable UART RX\r
2101 \r
2102         return                          ; Return to code        \r
2103 \r
2104 ;----------------------------------------------------------------------\r
2105 ; BootTXB - Sends one byte to the UART, waits for transmitter to become\r
2106 ;  free before sending\r
2107 \r
2108 BootTXB\r
2109 BootTXW1\r
2110         btfss   PIR1,TXIF               ; Wait for TX to empty\r
2111         goto    BootTXW1\r
2112         movwf   TXREG                   ; Send the byte\r
2113         return\r
2114 \r
2115 ;----------------------------------------------------------------------\r
2116 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated\r
2117 \r
2118 BootTXStr\r
2119         movwf   BootAddrL               ; Store LSB of text pointer\r
2120         movlw   07h                     ; MSB of pointer to the text (0700h in this boot loader)\r
2121         movwf   BootAddrH\r
2122         movlw   02h                     ; Select "Read Program Memory" operation\r
2123         movwf   BootBits        \r
2124 BootTXStrLoop\r
2125         call    BootEE                  ; Lookup char (actually two packed into one word)\r
2126         rlf     BootDataL,w             ; Shift the MSB out into carry (that's the 2nd char LSB)\r
2127         rlf     BootDataH,w             ; Shift it into 2nd char\r
2128         call    BootTXB                 ; Send the high byte first\r
2129         movf    BootDataL,w             ; Get the low byte\r
2130         andlw   07fh                    ; Mask of the highest bit\r
2131         skpnz                           ; Stop if zero\r
2132         return\r
2133         call    BootTXB                 ; Send char\r
2134         incf    BootAddrL,f             ; Increment pointer\r
2135         goto    BootTXStrLoop\r
2136 \r
2137 ;----------------------------------------------------------------------\r
2138 ; BootRXB - Receives one byte from the UART, waits if nothing available\r
2139 \r
2140 BootRXB\r
2141 BootRXW1\r
2142         btfss   PIR1,RCIF               ; Wait for RX to complete\r
2143         goto    BootRXW1\r
2144         movf    RCREG,w                 ; Get the recvd byte\r
2145         call    BootTXB                 ; Echo to terminal\r
2146         return\r
2147 \r
2148 ;----------------------------------------------------------------------\r
2149 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary\r
2150 \r
2151 BootRXHEXNibble\r
2152         call    BootRXB                 ; Receive nibble\r
2153 \r
2154 ; This code is from piclist.com, really neat!\r
2155 \r
2156         addlw   -'A'                    ; Convert from BCD to binary nibble\r
2157         skpc                            ; Test if if was 0-9 or A-F, skip if A-F\r
2158         addlw  'A' - 10 - '0'           ; It was numeric '0'\r
2159         addlw   10                      ; Add 10 (A get to be 0ah etc.)\r
2160 \r
2161         return\r
2162 \r
2163 ;----------------------------------------------------------------------\r
2164 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available\r
2165 ;  Decodes the bytes as ASCII hex and returns a single byte in W\r
2166 \r
2167 BootRXHEX\r
2168         call    BootRXHEXNibble\r
2169         movwf   BootHEXTemp\r
2170         swapf   BootHEXTemp,f           ; Swap it up to the high nibble\r
2171 \r
2172         call    BootRXHEXNibble\r
2173         addwf   BootHEXTemp,w           ; And add the two nibbles together\r
2174         return\r
2175 \r
2176 ;----------------------------------------------------------------------\r
2177 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the\r
2178 ;  exact action to take. BootAddrL and BootAddrH has to be initialized\r
2179 ;  to the address of choice (0000-003fh for EE and 0000h-07ffh for flash\r
2180 ;  The data to be written has to be put in BootDataL and BootDataH, and\r
2181 ;  data will be to the same place when read back\r
2182 \r
2183 BootEE\r
2184         bsf     STATUS,RP1              ; Select bank 2 (RP0 must be 0)\r
2185 \r
2186         movf    BootAddrH,w             ; Load desired address\r
2187         movwf   EEADRH\r
2188         movf    BootAddrL,w\r
2189         movwf   EEADR\r
2190         movf    BootDataH,w             ; And load the data (only used when writing)\r
2191         movwf   EEDATH\r
2192         movf    BootDataL,w\r
2193         movwf   EEDATA\r
2194 \r
2195         bsf     STATUS,RP0              ; Go to bank 3\r
2196 \r
2197         bsf     EECON1,EEPGD            ; Point to Program Flash mem\r
2198         btfss   BootBits,1              ; Test if that was correct or if we have to clear again\r
2199         bcf     EECON1,EEPGD            ; Point to EE DATA mem\r
2200 \r
2201         btfss   BootBits,0              ; Check from read or write\r
2202         goto    BootEERD                ; Skip the WR if we were going for a read\r
2203 \r
2204         bsf     EECON1,WREN             ; Enable writes\r
2205         movlw   55h\r
2206         movwf   EECON2\r
2207         movlw   0AAh\r
2208         movwf   EECON2                  ; Unlock write operation\r
2209         bsf     EECON1,WR               ; And start a write cycle\r
2210 BootWRLoop\r
2211         btfsc   EECON1,WR               ; This executes for EE only not flash, waits for WR to finish\r
2212         goto    BootWRLoop              ; These two instructions gets NOPed when flashing\r
2213 \r
2214         bcf     EECON1,WREN             ; Finally disable writes again\r
2215                                         ; Here we read the data back again, can be used as verify\r
2216 BootEERD\r
2217         bsf     EECON1,RD               ; Start a read cycle\r
2218         nop                             ; Only necessary for flash read, same thing as when writing above\r
2219         nop                             ; Except I could use the two words for something useful there.. :)\r
2220 \r
2221 BootEEX\r
2222         bcf     STATUS,RP0              ; Back to bank 2\r
2223         movf    EEDATA,w                ; Store our EE-data\r
2224         movwf   BootDataL\r
2225         movf    EEDATH,w\r
2226         movwf   BootDataH\r
2227         bcf     STATUS,RP1              ; And finally back to bank 0\r
2228 \r
2229         return\r
2230 \r
2231 ; 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
2232 BootStartText\r
2233         DA      "WJBoot - press ESC to flash\x00"\r
2234 \r
2235 BootFlashText\r
2236         DA      "\r\nSend INHX8 file now...\r\x00"\r
2237 \r
2238 BootRunText\r
2239         DA      "\r\nExiting loader\r\x00"\r
2240 \r
2241 ;----------------------------------------------------------------------\r
2242 ; EE Data (64 bytes), located at 2100h\r
2243 \r
2244         org 2100h\r
2245 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2246 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2247 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2248 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2249 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2250 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2251 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2252 ;       de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
2253 \r
2254         END\r