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