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