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