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