v0.3 - Implementing more Unilink commands and RingIndicator control (to wake the...
[wj-unilink.git] / wj-uni.asm
1         title   "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 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 ;  Fix the DelayW routine so it actually delays W/10 ms...\r
16 ;  No checksum checking is done on incoming packets\r
17 ;  Investigate whether I actually have to save PCLATH in ISH, maybe save FSR?\r
18 ;  Move RS232 code into ISH\r
19 ;  Check Overrun errors from the UART\r
20 ;  Implement lots of other Unilink commands (Text display, time display etc.)\r
21 ;  Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..)\r
22 \r
23 ;----------------------------------------------------------------\r
24 ;  HISTORY\r
25 ;----------------------------------------------------------------\r
26 ;  Version\r
27 ;\r
28 ;  0.3  Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)\r
29 ;  0.2  First attempt at responding to the Anyone command\r
30 ;  0.1  Receives Unilink data OK, relays it to serial\r
31 ;  0.0  Very first "F**king No Work!" version\r
32 \r
33 ;----------------------------------------------------------------\r
34 ;  I/O LAYOUT\r
35 ;----------------------------------------------------------------\r
36 ;  Unilink BUSON IN (blue) connected to RC2/CCP1\r
37 ;  Unilink DATA (green) connected to RC3\r
38 ;  Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)\r
39 ;  Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)\r
40 ;  Unilink RST (lilac) connected to RA4\r
41 ;  LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)\r
42 ;  LCD RW connected to pin RB2\r
43 ;  LCD E connected to pin RB3\r
44 ;  LCD DB4-DB7 connected to RB4-RB7\r
45 ;  RS-232 TX from computer connected to RC7/RX\r
46 ;  RS-232 RX to computer connected to RC6/TX\r
47 ;  RS-232 RI to computer connected to RC5\r
48 ;\r
49 ;  This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...\r
50 \r
51 #define BUSON_IN_BIT    PORTC,2\r
52 #define DATA_BIT        PORTC,3\r
53 #define BUSON_OUT_BIT   PORTC,4\r
54 #define CLK_BIT         PORTB,0\r
55 #define RST_BIT         PORTA,4\r
56 \r
57 #define LCD_RS_BIT      PORTB,1\r
58 #define LCD_RW_BIT      PORTB,2\r
59 #define LCD_E_BIT       PORTB,3\r
60 #define LCD_DB4_BIT     PORTB,4\r
61 #define LCD_DB5_BIT     PORTB,5\r
62 #define LCD_DB6_BIT     PORTB,6\r
63 #define LCD_DB7_BIT     PORTB,7\r
64 \r
65 #define RS232_RI_BIT    PORTC,5\r
66 \r
67 ;----------------------------------------------------------------\r
68 ;  FILE REGISTER USAGE\r
69 ;----------------------------------------------------------------\r
70 Dcount          equ     20h\r
71 e_LEN           equ     21h\r
72 Icount          equ     2Dh             ; Offset of string to print\r
73 TxTemp          equ     2Eh             ; blahblah\r
74 TxTemp2         equ     2Fh             ; Blahblah2\r
75 \r
76 LCDWTmp         equ     30h\r
77 Dcount2         equ     31h\r
78 temp            equ     32h\r
79 \r
80 DataCount       equ     33h             ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)\r
81 DataStore       equ     34h             ; This is a kludge\r
82 \r
83 UnilinkSelected equ     3bh\r
84 UnilinkBit      equ     3ch             ; This is my "bitmask" to be used for requests\r
85 UnilinkID       equ     3dh             ; This is my Bus ID\r
86 UnilinkCmdLen   equ     3eh             ; This gets updated with the actual packet length after CMD1 has been received\r
87 UnilinkTXRX     equ     3fh             ; This is a pointer to the Unilink packet below, used with indirect addressing\r
88 \r
89 UnilinkRAD      equ     40h             ; Beginning of Unilink packet - the Receiving Address\r
90 UnilinkTAD      equ     41h             ; Transmitter address\r
91 UnilinkCMD1     equ     42h             ; CMD1 byte\r
92 UnilinkCMD2     equ     43h             ; CMD2 byte\r
93 UnilinkParity1  equ     44h             ; First or only parity byte for short packets (6 bytes)\r
94 UnilinkData1    equ     45h             ; Extra data for medium/large packets, or zero for short packets\r
95 UnilinkData2    equ     46h             ;\r
96 UnilinkData3    equ     47h             ;\r
97 UnilinkData4    equ     48h             ;\r
98 UnilinkData5    equ     49h             ; Data5 if this is a large packet\r
99 UnilinkParity2M equ     49h             ; Parity2 shares the same byte if it's a medium sized packet\r
100 UnilinkData6    equ     4ah             ; Extra data for large packets, or zero for medium packets\r
101 UnilinkData7    equ     4bh             ;\r
102 UnilinkData8    equ     4ch             ;\r
103 UnilinkData9    equ     4dh             ;\r
104 UnilinkParity2  equ     4eh             ; Parity byte for large packets\r
105 UnilinkZero     equ     4fh             ; Should always be zero (possibly used to signal corrupt packets from slave to master?)\r
106 \r
107 IRQPCLATH       equ     7dh             ; ISH storage\r
108 IRQSTATUS       equ     7eh             ; Needs to be located in a shared area accessible from all register banks\r
109 IRQW            equ     7fh             ; \r
110 \r
111         subtitl "Startup"\r
112         page\r
113 ;----------------------------------------------------------------\r
114 ;  Power up/Reset starting point\r
115 \r
116         org     0                       ; Start at the beginning of memory (the reset vector)\r
117         call    Bootstrap               ; Call Flash Load routine\r
118         call    LCDInit                 ; Initialize LCD I/F\r
119         call    IRQInit                 ; Set up and start the IRQ handler\r
120         goto    Main                    ; Run the main program loop (skip the IRQ handler)\r
121 \r
122         subtitl "IRQ Handler"\r
123 ;----------------------------------------------------------------\r
124 ;  Interrupt handler always starts at addr 4\r
125 ;  In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.\r
126 ;  Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR\r
127 ;  is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to\r
128 ;  bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low\r
129 ;  again after that.\r
130 \r
131         org     4                       ; ISR vector is at address 4\r
132         movwf   IRQW                    ; Save W\r
133         swapf   STATUS,w                ; Get the status register into w\r
134         clrf    STATUS                  ; Zero out the status reg, gives Reg Bank0\r
135         movwf   IRQSTATUS               ; Store the STATUS reg\r
136         movf    PCLATH,w                ; Get the PCLATH reg\r
137         movwf   IRQPCLATH               ; And store it\r
138         clrf    PCLATH                  ; Go to low memory\r
139 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)\r
140 \r
141         btfss   INTCON,INTF             ; Check if it's the INT edge interrupt (Unilink CLK)\r
142         goto    IRQNotINT               ; No it's not, check the other sources\r
143 \r
144 ; 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
145 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction\r
146 ; 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
147 ; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of\r
148 ; 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
149 ; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX\r
150 ; 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
151 ; here, otherwise collisions will occur..\r
152 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an\r
153 ; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here\r
154 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)\r
155 \r
156         movlw   8                       ; Loop through the 8 bits\r
157         movwf   DataCount\r
158         movf    UnilinkTXRX,w           ; Get the pointer\r
159         movwf   FSR                     ; Store it to make use of indirect addressing\r
160 \r
161 IRQINTBitSet\r
162         btfss   INDF,7                  ; Test high bit of data (that's the first bit to be clocked out)\r
163         goto    IRQINTTristate          ; Bit is low, we should tristate bit\r
164         bcf     PORTC,3                 ; Otherwise set DATA bit low\r
165         bsf     STATUS,RP0              ; Select high regs\r
166         bcf     TRISC,3                 ; And pull low (now it's an output)\r
167         bcf     STATUS,RP0              ; Back to regbank 0\r
168         goto    IRQINTCLKWaitLow        ; Wait for master to actually clock this bit in\r
169 \r
170 IRQINTTristate\r
171         bsf     STATUS,RP0              ; Select high regs\r
172         bsf     TRISC,3                 ; Force the bit to be tristated\r
173         bcf     STATUS,RP0              ; Back to regbank 0\r
174 \r
175 IRQINTCLKWaitLow\r
176         btfss   PORTC,2                 ; Check for BUSON\r
177         goto    IRQAfterINT\r
178         btfsc   PORTB,0                 ; Wait for clock to go low\r
179         goto    IRQINTCLKWaitLow\r
180 \r
181         clrc                            ; Clear carry\r
182         btfss   PORTC,3                 ; Test DATA\r
183         setc                            ; Set carry if data is LOW (data is inverted!)\r
184         rlf     INDF,f                  ; Shift it into the "accumulator"\r
185 \r
186         decfsz  DataCount,f             ; Loop once more perhaps?\r
187         goto    IRQINTCLKWaitHigh       ; Yes, again!\r
188         goto    IRQINTRecvDone          ; No it's done, don't check for clock to go high again\r
189 \r
190 IRQINTCLKWaitHigh\r
191         btfss   PORTC,2                 ; Check for BUSON\r
192         goto    IRQAfterINT\r
193         btfss   PORTB,0                 ; Wait for clock to go high\r
194         goto    IRQINTCLKWaitHigh\r
195         goto    IRQINTBitSet            ; Loop again\r
196 \r
197 ; Successfully received a byte here, run it through a state machine to figure out what to do\r
198 ; (several possibilites exists here):\r
199 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero\r
200 ; If receive counter is zero and the received byte is a zero byte, discard it\r
201 ; Otherwise store the byte in our receive buffer and increment receive counter\r
202 ; 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
203 ;   00 = short 6 byte packet\r
204 ;   10 = medium 11 byte packet\r
205 ;   11 = long 16 byte packet\r
206 ; Update the receive length byte accordingly\r
207 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing\r
208 ; the packet and take appropriate action.\r
209 \r
210 IRQINTRecvDone\r
211         movf    UnilinkTXRX,w           ; Find out which byte # that was received\r
212         andlw   0fh                     ; Mask\r
213         bnz     IRQINTRecvNotFirst      ; Not the first byte\r
214         movf    UnilinkRAD,w            ; Get the first byte received\r
215         bz      IRQINTRecvNullByte      ; Null byte received, ignore this, don't increment counter\r
216 IRQINTRecvNotFirst\r
217         incf    UnilinkTXRX,f           ; Increment address\r
218 \r
219         movf    UnilinkTXRX,w           ; Get the byte position again\r
220         andlw   0fh                     ; Only lower 4 bits of interest\r
221         xorlw   03h                     ; Well, is it the third byte? (CMD1, telling us the length of the packet)\r
222         bnz     IRQINTRecvNotCMD1       ; No, skip the length code for now\r
223         movlw   6                       ; Assume it's a short packet\r
224         btfss   INDF,7                  ; INDF still points to received byte, test high bit for medium/long\r
225         goto    IRQINTRecvShort         ; Nope, it's a short packet\r
226         addlw   5                       ; OK, it's long or medium at least\r
227         btfsc   INDF,6                  ; Test for long\r
228         addlw   5                       ; Yep, it's a long packet\r
229 IRQINTRecvShort\r
230         movwf   UnilinkCmdLen           ; Store the length\r
231 \r
232 IRQINTRecvNotCMD1\r
233         movf    UnilinkTXRX,w           ; Get the byte position\r
234         xorwf   UnilinkCmdLen,w         ; XOR with the calculated command length\r
235         andlw   0fh                     ; and mask - this results in a zero result when finished receiving\r
236         bnz     IRQINTRecvIncomplete    ; Packet not ready yet\r
237 \r
238 ; Here a packet is actually received a packet, should check the checksum(s) as well, but I don't care right now\r
239 ; (I need music in my car! :))\r
240 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here\r
241 ; (there won't be any more communication for at least another 4.8ms))\r
242 \r
243 ; Unilink command parser:\r
244 \r
245 ; Check for CMD1 = 01h (System bus commands)\r
246         movf    UnilinkCMD1,w\r
247         xorlw   01h\r
248         bnz     IRQINTParseNot01\r
249 \r
250 ; Check for 01 00 (Bus Re-Initialization)\r
251         movf    UnilinkCMD2,w\r
252 ;       xorlw   00h\r
253         bnz     IRQINTParseNot0100\r
254 \r
255         clrf    UnilinkID               ; Clear the existing Unilink ID, if any\r
256         bcf     BUSON_OUT_BIT           ; Clear the cascade BUSON pin, not activated again until we have a new ID\r
257 \r
258         goto    IRQINTParseComplete     ; Don't send any reply to this\r
259 \r
260 IRQINTParseNot0100\r
261 \r
262 ; Check for 01 02 (Anyone)\r
263         movf    UnilinkCMD2,w\r
264         xorlw   02h\r
265         bnz     IRQINTParseNot0102\r
266 \r
267         movf    UnilinkID,w             ; Do I have an ID already?\r
268         bnz     IRQINTParseNot0102      ; Yep, I don't want another one!\r
269 \r
270         movlw   10h                     ; Sending to Master\r
271         movwf   UnilinkRAD\r
272         movlw   0d0h                    ; I'm in the MD changer group\r
273         movwf   UnilinkTAD\r
274         movlw   8ch                     ; Device discovery command reply\r
275         movwf   UnilinkCMD1\r
276         movlw   00h                     ; 00??\r
277         movwf   UnilinkCMD2\r
278         movlw   6ch                     ; Hard coded parity (!)\r
279         movwf   UnilinkParity1\r
280         movlw   24h                     ; My internal MD sends 25 here first time, and then 24 when appointed!??\r
281         movwf   UnilinkData1\r
282         movlw   2ch                     ; 2c??\r
283         movwf   UnilinkData2\r
284         movlw   22h                     ; 22??\r
285         movwf   UnilinkData3\r
286         movlw   00h                     ; 00??\r
287         movwf   UnilinkData4\r
288         movlw   0deh                    ; Hard coded parity 2 (!)\r
289         movwf   UnilinkData5\r
290         clrf    UnilinkData6\r
291         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
292 \r
293 IRQINTParseNot0102\r
294 \r
295 ; Check for 01 12 (Time poll)\r
296         movf    UnilinkCMD2,w\r
297         xorlw   12h\r
298         bnz     IRQINTParseNot0112\r
299 \r
300         movf    UnilinkRAD,w\r
301         xorwf   UnilinkID,w             ; Is it for me?\r
302         bnz     IRQINTParseNot0112      ; Nope\r
303 \r
304         clrf    UnilinkParity1\r
305         movlw   10h                     ; Sending to Master\r
306         addwf   UnilinkParity1,f\r
307         movwf   UnilinkRAD\r
308         movf    UnilinkID,w             ; This is my ID\r
309         addwf   UnilinkParity1,f\r
310         movwf   UnilinkTAD\r
311         movlw   00h\r
312         addwf   UnilinkParity1,f\r
313         movwf   UnilinkCMD1\r
314 \r
315         movlw   80h                     ; Idle unless selected\r
316         btfsc   UnilinkSelected,7       \r
317         clrw\r
318         \r
319         addwf   UnilinkParity1,f\r
320         movwf   UnilinkCMD2\r
321         clrf    UnilinkData6\r
322         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
323 \r
324 IRQINTParseNot0112\r
325 \r
326 IRQINTParseNot01\r
327 \r
328 ; Check for CMD1 = 02h (Appoint)\r
329         movf    UnilinkCMD1,w\r
330         xorlw   02h\r
331         bnz     IRQINTParseNot02\r
332 \r
333         bsf     BUSON_OUT_BIT           ; Now activate the cascade BUSON pin, to allow others to be discovered\r
334 \r
335         movf    UnilinkRAD,w            ; Get the ID the master has given me\r
336         movwf   UnilinkID               ; Store my id\r
337         movf    UnilinkCMD2,w           ; Get the bitmask\r
338         movwf   UnilinkBit              ; And store it (this is needed when doing slave breaks and actually responding)\r
339 \r
340         clrf    UnilinkParity1\r
341         movlw   10h                     ; Sending to Master\r
342         addwf   UnilinkParity1,f\r
343         movwf   UnilinkRAD\r
344         movf    UnilinkID,w             ; This is my ID\r
345         addwf   UnilinkParity1,f\r
346         movwf   UnilinkTAD\r
347         movlw   8ch                     ; Device discovery command again\r
348         addwf   UnilinkParity1,f\r
349         movwf   UnilinkCMD1\r
350         movlw   00h\r
351         addwf   UnilinkParity1,f\r
352         movwf   UnilinkCMD2\r
353 \r
354         movf    UnilinkParity1,w\r
355         movwf   UnilinkParity2M         ; That's the parity when sending medium messages\r
356 \r
357         movlw   24h\r
358         addwf   UnilinkParity2M,f\r
359         movwf   UnilinkData1\r
360         movlw   2ch                     ; My internal MD sends 1c here... (external/internal difference)\r
361         addwf   UnilinkParity2M,f\r
362         movwf   UnilinkData2\r
363         movlw   22h\r
364         addwf   UnilinkParity2M,f\r
365         movwf   UnilinkData3\r
366         movlw   00h\r
367         addwf   UnilinkParity2M,f\r
368         movwf   UnilinkData4\r
369 \r
370         clrf    UnilinkData6\r
371         goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
372 \r
373 IRQINTParseNot02\r
374 \r
375 ; Check for CMD1 = 87h (Power control)\r
376         movf    UnilinkCMD1,w\r
377         xorlw   087h\r
378         bnz     IRQINTParseNot87\r
379 \r
380 ; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)\r
381         btfsc   UnilinkCMD2,3\r
382         goto    IRQINTParse87PowerOn\r
383 \r
384         bsf     RS232_RI_BIT            ; Set this to make RI pin go low (after RS-232 levels)\r
385         goto    IRQINTParseComplete\r
386 \r
387 IRQINTParse87PowerOn\r
388         bcf     RS232_RI_BIT            ; Clear this to make RI pin go high (waking the computer)\r
389         goto    IRQINTParseComplete\r
390 \r
391 IRQINTParseNot87\r
392 \r
393 ; Check for CMD1 = f0h (Source Select)\r
394         movf    UnilinkCMD1,w\r
395         xorlw   0f0h\r
396         bnz     IRQINTParseNotF0\r
397 \r
398         movf    UnilinkCMD2,w\r
399         xorwf   UnilinkID,w             ; Check if it's selecting me\r
400         bnz     IRQINTParseF0Deselect\r
401 \r
402         bsf     UnilinkSelected,7       ; Now we're selected\r
403         goto    IRQINTParseComplete\r
404 \r
405 IRQINTParseF0Deselect\r
406 \r
407         bcf     UnilinkSelected,7       ; Now we're de-selected\r
408         goto    IRQINTParseComplete\r
409 \r
410 IRQINTParseNotF0\r
411 \r
412 IRQINTParseComplete\r
413 \r
414 ; The CPU ends up here when parsing is complete and it's not interested in sending any reply back to the master\r
415 ; (that's why we clear out all the packet buffer bytes)\r
416 ; TODO: Replace this with an FSR access to save space and make the code neater\r
417 \r
418         clrf    UnilinkRAD\r
419         clrf    UnilinkTAD\r
420         clrf    UnilinkCMD1\r
421         clrf    UnilinkCMD2\r
422         clrf    UnilinkParity1\r
423         clrf    UnilinkData1\r
424         clrf    UnilinkData2\r
425         clrf    UnilinkData3\r
426         clrf    UnilinkData4\r
427         clrf    UnilinkData5\r
428         clrf    UnilinkData6\r
429         clrf    UnilinkData7\r
430         clrf    UnilinkData8\r
431         clrf    UnilinkData9\r
432         clrf    UnilinkParity2\r
433         clrf    UnilinkZero\r
434 \r
435 IRQINTParseBypassClear\r
436 \r
437         movlw   UnilinkRAD              ; Get the pointer to the first byte in the receive buffer\r
438         movwf   UnilinkTXRX             ; Store it - this way the next byte that gets received goes into RAD\r
439 \r
440         clrf    UnilinkCmdLen           ; No command length while waiting for a new packet\r
441 \r
442         \r
443 IRQINTRecvIncomplete\r
444 \r
445 IRQINTRecvNullByte\r
446         movf    INDF,w\r
447         movwf   DataStore               ; Store it so the non-irq code can snoop\r
448 \r
449 IRQAfterINT\r
450         bcf     INTCON,INTF             ; Clear the IRQ source bit to re-enable INT interrupts again\r
451 \r
452 IRQNotINT\r
453 \r
454 ; Finally restore CPU state and return from the ISR\r
455 \r
456 ; If I have to save the FSR in the beginning I also need to restore it here...\r
457 \r
458         movf    IRQPCLATH,w\r
459         movwf   PCLATH                  ; Restore PCLATH\r
460         swapf   IRQSTATUS,w\r
461         movwf   STATUS                  ; Restore STATUS\r
462         swapf   IRQW,f\r
463         swapf   IRQW,w                  ; Restore W\r
464         retfie                          ; Interrupt return\r
465 \r
466 \r
467         subtitl "Main loop"\r
468         page\r
469 \r
470 ;----------------------------------------------------------------\r
471 ;  Data can be stored between here and 100h...\r
472 \r
473 StartUpText1\r
474         DT      "----- WJ UniLink"\r
475                \r
476 LookUp  movwf   PCL                     ; Go to it (this assumes PCLATH == 00h)\r
477 \r
478 ;----------------------------------------------------------------\r
479 ;  Main program begins here. [Called after bootloader, lcdinit and irqinit...]\r
480 \r
481         org     100h                    ; Maybe not force this to a specific address later\r
482 Main\r
483 \r
484         movlw   StartUpText1            ; Show something on the LCD\r
485         call    TxLCD16B\r
486 \r
487 MainLoop\r
488 \r
489         bcf     LCD_RS_BIT              ; LCD Command mode\r
490         movlw   80h                     ; DisplayRam 0\r
491         call    TxLCDB\r
492         bsf     LCD_RS_BIT\r
493 \r
494         movlw   '0'\r
495         btfsc   PORTA,4                 ; Test RST\r
496         movlw   'R'\r
497         call    TxLCDB\r
498 \r
499         movlw   '0'\r
500         btfsc   PORTB,0                 ; Test CLK\r
501         movlw   'C'\r
502         call    TxLCDB\r
503 \r
504         movlw   '0'\r
505         btfsc   PORTC,2                 ; Test BUSON-IN\r
506         movlw   'B'\r
507         call    TxLCDB\r
508 \r
509         movlw   '0'\r
510         btfsc   PORTC,3                 ; Test DATA\r
511         movlw   'D'\r
512         call    TxLCDB\r
513 \r
514         movf    UnilinkCmdLen,w\r
515         bz      MainDontPrintCmd\r
516         addlw   '0'\r
517         call    TxLCDB\r
518 \r
519 MainDontPrintCmd\r
520 \r
521         movf    DataCount,w             ; Load bit counter (if 0 then byte is available)\r
522         skpz\r
523         goto    MainLoop\r
524 \r
525         decf    DataCount,f             ; Set it non-zero\r
526 \r
527         movf    DataStore,w\r
528         call    BootTXB                 ; Send to terminal\r
529         goto    MainLoop\r
530 \r
531 \r
532 ;----------------------------------------------------------------\r
533 ; IRQInit - Sets up the IRQ Handler\r
534 \r
535 IRQInit\r
536 \r
537 ; Start with clearing the Unilink packet buffer before enabling any interrupts, otherwise the first packet might become corrupt\r
538 ; TODO: Replace this with FSR access\r
539         clrf    UnilinkSelected\r
540         clrf    UnilinkID\r
541         clrf    UnilinkBit\r
542         clrf    UnilinkCmdLen\r
543         clrf    UnilinkRAD\r
544         clrf    UnilinkTAD\r
545         clrf    UnilinkCMD1\r
546         clrf    UnilinkCMD2\r
547         clrf    UnilinkParity1\r
548         clrf    UnilinkData1\r
549         clrf    UnilinkData2\r
550         clrf    UnilinkData3\r
551         clrf    UnilinkData4\r
552         clrf    UnilinkData5\r
553         clrf    UnilinkData6\r
554         clrf    UnilinkData7\r
555         clrf    UnilinkData8\r
556         clrf    UnilinkData9\r
557         clrf    UnilinkParity2\r
558         clrf    UnilinkZero\r
559 \r
560         clrf    DataStore\r
561         movlw   UnilinkRAD              ; Get the pointer to the first byte in the receive buffer\r
562         movwf   UnilinkTXRX             ; Store it\r
563 \r
564 ; Fix the output state of RI and BUSON_OUT to a safe default\r
565 \r
566         bsf     RS232_RI_BIT            ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)\r
567         bcf     BUSON_OUT_BIT           ; BUSON_OUT should be disabled for now, must be appointed first\r
568 \r
569         bsf     STATUS,RP0              ; Reg bank 1\r
570 \r
571         bcf     RS232_RI_BIT            ; Both bits should be outputs\r
572         bcf     BUSON_OUT_BIT           ;\r
573 \r
574 ; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...\r
575 ;       bcf     OPTION_REG,INTEDG       ; We want RB0 to give us an IRQ on the falling edge\r
576 \r
577         bsf     INTCON,INTE             ; Enable the RB0/INT\r
578         bsf     INTCON,GIE              ; Enable global interrupts\r
579 \r
580         bsf     TXSTA,TXEN              ; Enable UART TX\r
581 \r
582         bcf     STATUS,RP0              ; Back to bank 0\r
583 \r
584         bsf     RCSTA,SPEN              ; Enable serial port\r
585         bsf     RCSTA,CREN              ; Enable UART RX\r
586 \r
587         return\r
588 \r
589 ;----------------------------------------------------------------\r
590 ;  Initialize LCD Controller...\r
591 \r
592 LCDInit\r
593         clrf    PORTB                   ; First clear PortB data register\r
594         bsf     STATUS,RP0              ; Reg bank 1\r
595         movlw   001h                    ; All but RB0 are outputs.\r
596         movwf   TRISB                   ;\r
597 \r
598         bcf     OPTION_REG,NOT_RBPU     ; Turn on port B pull-up\r
599         bcf     STATUS,RP0              ; Restore Reg bank 0\r
600 \r
601 ; This is a standard reset sequence for the LCD controller\r
602 \r
603         movlw   160                     ; Need to delay for at least 15ms, let's go for 16ms delay\r
604         call    DelayW\r
605 \r
606         movlw   3                       ; Write 3 to the LCD\r
607         call    TxLCD                   ; Send to LCD\r
608         movlw   50                      ; Need to delay for at least 4.1ms, let's go for 5ms delay\r
609         call    DelayW\r
610 \r
611         movlw   3                       ; Write 3 to the LCD\r
612         call    TxLCD\r
613         movlw   10                      ; Need to delay for at least 100us, let's go for 1ms delay\r
614         call    DelayW\r
615 \r
616         movlw   3                       ; Write 3 to the LCD\r
617         call    TxLCD\r
618         movlw   10                      ; Need to delay for at least 40us, let's go for 1ms delay\r
619         call    DelayW\r
620 \r
621         movlw   2                       ; 4-bit interface requested\r
622         call    TxLCD                   ;\r
623         movlw   10                      ; Need to delay for at least 40us, let's go for 1ms delay\r
624         call    DelayW                  ;\r
625 \r
626 ; Reset sequence ends here\r
627 ; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits\r
628 \r
629         movlw   28h                     ; Function Select + 4-bit bus + 2-line display\r
630         call    TxLCDB\r
631 \r
632         movlw   0ch                     ; Display Control + LCD On (No cursor)\r
633         call    TxLCDB\r
634 \r
635         movlw   01h                     ; Clear Display\r
636         call    TxLCDB\r
637 \r
638         movlw   06h                     ; Auto Increment cursor position\r
639         call    TxLCDB\r
640         \r
641         return\r
642    \r
643 ;----------------------------------------------------------------\r
644 ;  TxLCD16B\r
645 ;  Send a string to the LCD.\r
646 \r
647 TxLCD16B\r
648         movwf   Icount\r
649         bcf     LCD_RS_BIT\r
650         movlw   80h                     ; DisplayRam 0\r
651         call    TxLCDB\r
652         bsf     LCD_RS_BIT\r
653         call    TxLCD8B\r
654         bcf     LCD_RS_BIT\r
655         movlw   80h+40                  ; DisplayRam 40 (row 2)\r
656         call    TxLCDB\r
657         bsf     LCD_RS_BIT\r
658         call    TxLCD8B\r
659         return\r
660 \r
661 ;----------------------------------------------------------------\r
662 ;  TxLCD8B\r
663 ;  Send a string to the LCD.\r
664 \r
665 TxLCD8B\r
666 ;       movwf   Icount                  ; Icount = W\r
667         movlw   8\r
668         movwf   e_LEN                   ; Move to e_LEN\r
669 \r
670 Txm_lp  movf    Icount,w                ; get the byte\r
671         call    LookUp\r
672         incf    Icount,f                ; ...else ++Icount (table index)\r
673         call    TxLCDB                  ; Send out the byte\r
674         decfsz  e_LEN,f\r
675         goto    Txm_lp\r
676         return\r
677 \r
678 ;----------------------------------------------------------------\r
679 ; TxLCDB - send a byte to the LCD\r
680 \r
681 TxLCDB\r
682         movwf   TxTemp                  ; Store byte to send for a while...\r
683 \r
684         bcf     temp,0                  ; Clear my temp bit\r
685         btfss   LCD_RS_BIT              ; Check if we try the correct reg\r
686         goto    RxNoProb\r
687         bcf     LCD_RS_BIT\r
688         bsf     temp,0                  ; Indicate RS change\r
689 RxNoProb\r
690 \r
691 NotReady\r
692         call    RxLCDB                  ; Receive byte from LCD, status reg\r
693         andlw   80h\r
694         skpz                            ; If the bit was set, the zero flag is not\r
695         goto    NotReady\r
696 \r
697         btfsc   temp,0                  ; If we had to clear RS reset it now\r
698         bsf     LCD_RS_BIT\r
699 \r
700         swapf   TxTemp,w                ; Hi nibble of data to send in lo w bits\r
701         call    TxLCD                   ; Send them first...\r
702         movf    TxTemp,w                ; Then we have the low nibble in low w bits\r
703         call    TxLCD                   ; And send that one as well\r
704 \r
705         return\r
706 ;----------------------------------------------------------------\r
707 ; RxLCDB - recv a byte from the LCD\r
708 \r
709 RxLCDB\r
710         call    RxLCD                   ; Receive the high nibble\r
711         movwf   LCDWTmp\r
712         swapf   LCDWTmp,f               ; Swap it back to file\r
713         call    RxLCD                   ; Receive the low nibble\r
714         addwf   LCDWTmp,w               ; Put the nibbles together and return in W\r
715 \r
716         return\r
717 \r
718 ;----------------------------------------------------------------\r
719 ; TxLCD - send a nibble to the LCD\r
720 \r
721 TxLCD\r
722         movwf   LCDWTmp                 ; Write nibble to tmp\r
723         bcf     LCD_DB4_BIT             ; Clear previous data\r
724         bcf     LCD_DB5_BIT             ; \r
725         bcf     LCD_DB6_BIT             ;\r
726         bcf     LCD_DB7_BIT             ;\r
727 \r
728         btfsc   LCDWTmp,0               ; Test bit 0, transfer a set bit to LCD PORT\r
729         bsf     LCD_DB4_BIT\r
730         btfsc   LCDWTmp,1               ; Test bit 1, transfer a set bit to LCD PORT\r
731         bsf     LCD_DB5_BIT\r
732         btfsc   LCDWTmp,2               ; Test bit 2, transfer a set bit to LCD PORT\r
733         bsf     LCD_DB6_BIT\r
734         btfsc   LCDWTmp,3               ; Test bit 3, transfer a set bit to LCD PORT\r
735         bsf     LCD_DB7_BIT\r
736 \r
737         bsf     LCD_E_BIT               ; And set E to clock the data into the LCD module\r
738         nop                             ; Let it settle\r
739         bcf     LCD_E_BIT               ; And clear the Enable again.\r
740         return                          ; Returns without modifying W\r
741 \r
742 ;----------------------------------------------------------------\r
743 ; RxLCD - recv a nibble from the LCD\r
744 \r
745 RxLCD\r
746         clrw                            ; Clear W register, return data in lower 4 bits\r
747 \r
748         bsf     STATUS,RP0              ; Select 2nd reg bank, now TRIS regs can be accessed\r
749         \r
750         bsf     LCD_DB4_BIT             ; This sets the port bit as an input\r
751         bsf     LCD_DB5_BIT     \r
752         bsf     LCD_DB6_BIT     \r
753         bsf     LCD_DB7_BIT\r
754 \r
755         bcf     STATUS,RP0              ; Back at reg bank 0    \r
756 \r
757         bsf     LCD_RW_BIT              ; Set Read mode for the LCD\r
758         bsf     LCD_E_BIT               ; And set E to clock the data out of the LCD module\r
759         nop                             ; Let the bus settle\r
760         btfsc   LCD_DB4_BIT             ; Transfer a set port bit into W\r
761         addlw   1\r
762         btfsc   LCD_DB5_BIT             ; Transfer a set port bit into W\r
763         addlw   2\r
764         btfsc   LCD_DB6_BIT             ; Transfer a set port bit into W\r
765         addlw   4\r
766         btfsc   LCD_DB7_BIT             ; Transfer a set port bit into W\r
767         addlw   8\r
768         bcf     LCD_E_BIT               ; And clear the Enable again.\r
769         bcf     LCD_RW_BIT              ; Set Write mode for the LCD\r
770 \r
771         bsf     STATUS,RP0              ; Select 2nd reg bank, now TRIS regs can be accessed\r
772 \r
773         bcf     LCD_DB4_BIT             ; Set the port as an output again\r
774         bcf     LCD_DB5_BIT             ; \r
775         bcf     LCD_DB6_BIT             ;\r
776         bcf     LCD_DB7_BIT             ;\r
777 \r
778         bcf     STATUS,RP0              ; Back at reg bank 0    \r
779 \r
780         return                          ; Returns with data in W\r
781 \r
782 ;----------------------------------------------------------------------\r
783 ; Delay routines (non-interrupt based, therefore not even close to reliable)\r
784 ; W=10 gives ~ 1ms of delay\r
785 ; 1ms=5000 instructions wasted, 100us=500 cycles\r
786 ; Maximum time waited will be 256*100us=25.6ms\r
787 \r
788 DelayW\r
789         movwf   Dcount                  ; Set delay counter, number of 100us periods to wait\r
790 \r
791 DelayOuter\r
792         movlw   0a5h                    ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more\r
793         movwf   Dcount2                 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount\r
794 DelayInner\r
795         decfsz  Dcount2,f               ; 1 cycle (or two when exiting the loop)\r
796         goto    DelayInner              ; 2 cycles\r
797         decfsz  Dcount,f                ; Now decrement number of 100us periods and loop again\r
798         goto    DelayOuter\r
799         return\r
800 \r
801 \r
802         subtitl "Bootstrap/Bootloader code"\r
803         page\r
804 \r
805 ;----------------------------------------------------------------------\r
806 ; Bootstrap code - Allows PIC to flash itself with data from the async port.\r
807 ; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory\r
808 ; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver\r
809 ; (one good way of solving that is to wait for the echo from the PIC before sending anything else)\r
810 ; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration\r
811 ; register can't be programmed. That means that any references to the config register in the hex file will be ignored.\r
812 ;\r
813 ; Startup @9600bps\r
814 \r
815 ; RAM usage for the bootstrap code\r
816 \r
817 BootRXState     equ     7fh             ; What are we waiting for @RX\r
818 BootBits        equ     7eh             ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog\r
819 BootAddrL       equ     7dh\r
820 BootAddrH       equ     7ch\r
821 BootDataL       equ     7bh\r
822 BootDataH       equ     7ah\r
823 BootTimerL      equ     79h\r
824 BootTimerM      equ     78h\r
825 BootTimerH      equ     77h\r
826 BootNumBytes    equ     76h\r
827 BootDataVL      equ     75h\r
828 BootDataVH      equ     74h\r
829 BootHEXTemp     equ     73h\r
830 BootStrTemp     equ     72h\r
831 \r
832         org     700h                    ; Place the boot code at the top of memory\r
833 \r
834 Bootstrap\r
835         movlw   7\r
836         movwf   PCLATH                  ; Make sure the lookup code runs with the high PC bits set!\r
837 \r
838         bsf     STATUS,RP0              ; Access bank 1\r
839         bsf     TXSTA,TXEN              ; Enable UART TX\r
840         movlw   31                      ; Divisor for 9k6 @ 20MHz Fosc\r
841         movwf   SPBRG                   ; Store\r
842         bcf     STATUS,RP0              ; Back to bank 0\r
843 \r
844         bsf     RCSTA,SPEN              ; Enable serial port\r
845         bsf     RCSTA,CREN              ; Enable UART RX\r
846 \r
847 ;       clrf    BootRXState             ; Waiting for command\r
848 \r
849         movlw   low BootStartText       ; Send boot banner to the serial port\r
850         call    BootTXStr\r
851 \r
852         movlw   0e8h                    ; Initialize timeout timer\r
853         movwf   BootTimerL\r
854         movwf   BootTimerM\r
855         movwf   BootTimerH\r
856 \r
857 BootTimeout\r
858         incf    BootTimerL,f            ; A 24-bit counter\r
859         skpnz\r
860         incf    BootTimerM,f\r
861         skpnz\r
862         incf    BootTimerH,f\r
863         skpnz                           ; When overflowing here..\r
864         goto    BootReturn              ; ..Exit boot loader, no keypress within timeout period, resume program\r
865         btfss   PIR1,RCIF               ; Wait for RX to complete\r
866         goto    BootTimeout\r
867         call    BootRXB\r
868         xorlw   27                      ; ESC\r
869         skpz\r
870         goto    BootTimeout             ; If it wasn't ESC, wait for another key\r
871 \r
872 BootFlash\r
873         movlw   low BootFlashText       ; OK, flashing it is, send "start" text to serial port\r
874         call    BootTXStr\r
875 \r
876         bsf     BootBits,1\r
877         clrf    BootAddrL\r
878         clrf    BootAddrH\r
879 \r
880 BootLoop\r
881         call    BootRXB                 ; First find the ':'\r
882         call    BootTXB                 ; Echo to terminal\r
883         xorlw   ':'\r
884         skpz\r
885         goto    BootLoop                ; Loop until we find it!\r
886 \r
887         call    BootRXHEX               ; Get one ASCII encoded byte (two chars) (this echoes automatically)\r
888         movwf   BootNumBytes            ; This is the number of bytes to be programmed on the line\r
889 ; Maybe clear cary here?\r
890         rrf     BootNumBytes,f          ; Right shift because we're double addressing this 8-bit format\r
891 \r
892 ; Note carry should be clear here as there cannot be odd number of bytes in this format\r
893 \r
894         call    BootRXHEX               ; Receive AddrH\r
895         movwf   BootAddrH\r
896         call    BootRXHEX               ; Receive AddrL\r
897         movwf   BootAddrL\r
898         rrf     BootAddrH,f             ; Fix the addressing again\r
899         rrf     BootAddrL,f\r
900 \r
901         bcf     BootBits,2              ; Assume we should program\r
902         bsf     BootBits,1              ; And assume we should program flash not ee\r
903 \r
904         movf    BootAddrH,w\r
905         xorlw   020h                    ; Check if it's configuration, which we can't program\r
906         skpnz                           ; Skip the bit set if it was false alarm\r
907         bsf     BootBits,2              ; No programming for this line\r
908 \r
909         xorlw   001h                    ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)\r
910         skpnz                           ; Skip the bit set instr if not EE data address\r
911         bcf     BootBits,1              ; We should program EE, will ignore the AddrH\r
912 \r
913         call    BootRXHEX               ; Receive Record Type (must be 0 for real records)\r
914         skpz                            ; Check if zero\r
915         goto    BootFlashComplete\r
916 \r
917 BootLineLoop\r
918         call    BootRXHEX               ; Receive low-byte of data word\r
919         movwf   BootDataVL\r
920         call    BootRXHEX               ; Receive high-byte of data word\r
921         movwf   BootDataVH\r
922         \r
923         btfsc   BootBits,2              ; Check whether this line should be programmed at all\r
924         goto    BootWriteSkip\r
925 \r
926         bcf     BootBits,0              ; Read mode first, verify if we actually have to write\r
927         call    BootEE\r
928         movf    BootDataVL,w\r
929         xorwf   BootDataL,f             ; Compare and destroy DataL\r
930         movwf   BootDataL               ; Write new data to DataL\r
931         skpz                            ; Skip if no difference, have to check high byte as well\r
932         goto    BootWrite               ; Jump directly to write\r
933 \r
934         movf    BootDataVH,w\r
935         xorwf   BootDataH,f             ; Compare\r
936         skpnz                           ; Skip if no difference, no programming necessary\r
937         goto    BootWriteSkip\r
938 \r
939 BootWrite\r
940         movf    BootDataVH,w\r
941         movwf   BootDataH               ; Have to put the new H byte data in as well\r
942 \r
943 ;       movlw   '+'\r
944 ;       call    BootTXB                 ; Send progword indicator\r
945 \r
946         bsf     BootBits,0\r
947         call    BootEE                  ; Write directly into program mem\r
948 \r
949 ; Here a verify can take place, the read-back results are now in DataL/H\r
950 \r
951 ;       movlw   '+'\r
952 ;       goto    BootWriteDone\r
953 \r
954 BootWriteSkip\r
955 ;       movlw   '-'\r
956 BootWriteDone\r
957 ;       call    BootTXB\r
958 \r
959         incf    BootAddrL,f             ; Advance counter to next addr\r
960         skpnz\r
961         incf    BootAddrH,f             ; And add to high byte if needed\r
962 \r
963         decfsz  BootNumBytes,f\r
964         goto    BootLineLoop\r
965 \r
966 ;       movlw   13                      ; Progress\r
967 ;       call    BootTXB\r
968 ;       movlw   10                      ; Progress\r
969 ;       call    BootTXB\r
970 \r
971         goto    BootLoop\r
972 \r
973 BootFlashComplete\r
974         \r
975 BootReturn\r
976         movlw   low BootRunText\r
977         call    BootTXStr\r
978 \r
979         bsf     STATUS,RP0              ; Reg bank 1\r
980 BootReturnWait\r
981         btfss   TXSTA,TRMT              ; Wait for last things to flush\r
982         goto    BootReturnWait\r
983         bcf     TXSTA,TXEN              ; Disable UART TX\r
984         bcf     STATUS,RP0              ; Back to bank 0\r
985 \r
986         bcf     RCSTA,SPEN              ; Disable serial port\r
987         bcf     RCSTA,CREN              ; Disable UART RX\r
988 \r
989         clrf    PCLATH                  ; Go back to memory bank 0\r
990         return                          ; Return to code        \r
991 \r
992 ;----------------------------------------------------------------------\r
993 ; BootTXB - Sends one byte to the UART, waits for transmitter to become\r
994 ;  free before sending\r
995 \r
996 BootTXB\r
997 BootTXW1\r
998         btfss   PIR1,TXIF               ; Wait for TX to empty\r
999         goto    BootTXW1\r
1000         movwf   TXREG                   ; Send the byte\r
1001         return\r
1002 \r
1003 ;----------------------------------------------------------------------\r
1004 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated\r
1005 \r
1006 BootTXStr\r
1007         movwf   BootStrTemp             ; Store offset\r
1008         call    BootLookup              ; Lookup char\r
1009         addlw   0\r
1010         skpnz\r
1011         return\r
1012         call    BootTXB                 ; Send char\r
1013         incf    BootStrTemp,w           ; Retrieve\r
1014         goto    BootTXStr\r
1015 \r
1016 ;----------------------------------------------------------------------\r
1017 ; BootRXB - Receives one byte from the UART, waits if nothing available\r
1018 \r
1019 BootRXB\r
1020 BootRXW1\r
1021         btfss   PIR1,RCIF               ; Wait for RX to complete\r
1022         goto    BootRXW1\r
1023         movf    RCREG,w                 ; Get the recvd byte\r
1024         return\r
1025 \r
1026 ;----------------------------------------------------------------------\r
1027 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary\r
1028 \r
1029 BootRXHEXNibble\r
1030         call    BootRXB                 ; Receive nibble\r
1031         call    BootTXB                 ; Echo to terminal\r
1032         addlw   -'A'                    ; Convert from BCD to binary nibble\r
1033         skpc                            ; Test if if was 0-9 or A-F, skip if A-F\r
1034         addlw  'A' - 10 - '0'           ; It was numeric '0'\r
1035         addlw   10                      ; Add 10 (A get to be 0ah etc.)\r
1036         return\r
1037 \r
1038 ;----------------------------------------------------------------------\r
1039 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available\r
1040 ;  Decodes the bytes as ASCII hex and returns a single byte in W\r
1041 \r
1042 BootRXHEX\r
1043         call    BootRXHEXNibble\r
1044         movwf   BootHEXTemp\r
1045         swapf   BootHEXTemp,f           ; Swap it up to the high nibble\r
1046 \r
1047         call    BootRXHEXNibble\r
1048         addwf   BootHEXTemp,w           ; And add the two nibbles together\r
1049         return\r
1050 \r
1051 ;----------------------------------------------------------------------\r
1052 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the\r
1053 ;  exact action to take. BootAddrL and BootAddrH has to be initialized\r
1054 ;  to the address of choice (0000-003fh for EE and 0000h-07ffh for flash\r
1055 ;  The data to be written has to be put in BootDataL and BootDataH, and\r
1056 ;  data will be to the same place when read back\r
1057 \r
1058 BootEE\r
1059         bsf     STATUS,RP1              ; Select bank 2 (RP0 must be 0)\r
1060 \r
1061         movf    BootAddrH,w             ; Load desired address\r
1062         movwf   EEADRH\r
1063         movf    BootAddrL,w\r
1064         movwf   EEADR\r
1065         movf    BootDataH,w             ; And load the data (only used when writing)\r
1066         movwf   EEDATH\r
1067         movf    BootDataL,w\r
1068         movwf   EEDATA\r
1069 \r
1070         bsf     STATUS,RP0              ; Go to bank 3\r
1071 \r
1072         bsf     EECON1,EEPGD            ; Point to Program Flash mem\r
1073         btfss   BootBits,1              ; Test if that was correct or if we have to clear again\r
1074         bcf     EECON1,EEPGD            ; Point to EE DATA mem\r
1075 \r
1076         btfss   BootBits,0              ; Check from read or write\r
1077         goto    BootEERD                ; Skip the WR if we were going for a read\r
1078 \r
1079         bsf     EECON1,WREN             ; Enable writes\r
1080         movlw   55h\r
1081         movwf   EECON2\r
1082         movlw   0AAh\r
1083         movwf   EECON2                  ; Unlock write operation\r
1084         bsf     EECON1,WR               ; And start a write cycle\r
1085 BootWRLoop\r
1086         btfsc   EECON1,WR               ; This executes for EE only not flash, waits for WR to finish\r
1087         goto    BootWRLoop              ; These two instructions gets NOPed when flashing\r
1088 \r
1089         bcf     EECON1,WREN             ; Finally disable writes again\r
1090                                         ; Here we read the data back again, can be used as verify\r
1091 BootEERD\r
1092         bsf     EECON1,RD               ; Start a read cycle\r
1093         nop                             ; Only necessary for flash read, same thing as when writing above\r
1094         nop                             ; Except I could use the two words for something useful there.. :)\r
1095 \r
1096 BootEEX\r
1097         bcf     STATUS,RP0              ; Back to bank 2\r
1098         movf    EEDATA,w                ; Store our EE-data\r
1099         movwf   BootDataL\r
1100         movf    EEDATH,w\r
1101         movwf   BootDataH\r
1102         bcf     STATUS,RP1              ; And finally back to bank 0\r
1103 \r
1104         return\r
1105 \r
1106 BootStartText\r
1107         DT      "WJBoot - press ESC to flash",0\r
1108 BootFlashText\r
1109         DT      13,10,"Send INHX8 file now...",13,10,0\r
1110 BootRunText\r
1111         DT      13,10,"Exiting loader",13,10,0\r
1112 \r
1113 BootLookup\r
1114         movwf   PCL                     ; Go fetch the char data\r
1115 \r
1116 ;----------------------------------------------------------------------\r
1117 ; EE Data (64 bytes), located at 2100h\r
1118 \r
1119         org 2100h\r
1120 ;        data 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
1121 \r
1122         END\r