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