1 title "PIC16F870 Unilink(R) Interface by Werner Johansson, wj@yodel.net"
\r
2 subtitl "Definitions"
\r
3 list c=150,P=16F870,R=DEC,F=inhx8m
\r
4 include "p16f870.inc" ; Standard equates & Macros
\r
5 ERRORLEVEL 1,-302 ; Get rid of those annoying 302 msgs!
\r
7 ;******************************************************************************
\r
9 ; This program is free software; you can redistribute it and/or modify
\r
10 ; it under the terms of the GNU General Public License as published by
\r
11 ; the Free Software Foundation; either version 2 of the License, or
\r
12 ; (at your option) any later version.
\r
14 ; This program is distributed in the hope that it will be useful,
\r
15 ; but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
16 ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
17 ; GNU General Public License for more details.
\r
19 ; You should have received a copy of the GNU General Public License
\r
20 ; along with this program; if not, write to the Free Software
\r
21 ; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
\r
23 ; Author: Werner Johansson (wj@yodel.net), with some code and ideas from
\r
24 ; Simon Woods' GNUnilink, radix conversion utilities from piclist.com
\r
25 ; and of course the reverse-engineered Unilink(R) command list
\r
27 ;******************************************************************************
\r
29 ;----------------------------------------------------------------
\r
30 ; The Configuration Word
\r
31 __CONFIG _HS_OSC&_WDT_OFF&_PWRTE_ON&_BODEN_ON&_LVP_OFF&_CPD_OFF&_WRT_ENABLE_ON&_DEBUG_OFF&_CP_OFF
\r
33 ;----------------------------------------------------------------
\r
35 ;----------------------------------------------------------------
\r
36 ; Investigate whether I actually have to save PCLATH in ISH, maybe save FSR? - Not saving any of them for now
\r
37 ; Check Overrun errors from the UART
\r
38 ; Implement lots of other Unilink commands (Text display, time display etc.)
\r
39 ; Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..)
\r
40 ; Make the bit shift routine at the beginning of the ISR timeout if the clock suddenly stops (in the middle of a byte)
\r
41 ; (will keep it from hanging until the next bit gets clocked out, just ignore the faulty bits and carry on)
\r
42 ; Implement command b0 0x (change CD to x (1-a))
\r
43 ; Implement command 08 10 (Tel Mute on) and 08 18 (Tel Mute off)?
\r
45 ;----------------------------------------------------------------
\r
47 ;----------------------------------------------------------------
\r
50 ; 0.7 Debug Serial TX in ISR now, checksum check for incoming packets in place, A/D works, solved the master reset prob
\r
51 ; (by calling the INT handler from TMR2 ISR code (too much interrupt latency when transmitting)
\r
52 ; 0.6 Some more LCD info and clean-up of the Unilink recovery code, some problems with master resetting :(
\r
53 ; 0.5 Issues slave breaks seemingly without hickups (!)
\r
54 ; 0.4 Some fixups in the bootstrap code (I actually had to put the PIC in my PICSTART Plus programmer again :))
\r
55 ; 0.3 Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)
\r
56 ; 0.2 First attempt at responding to the Anyone command
\r
57 ; 0.1 Receives Unilink data OK, relays it to serial
\r
58 ; 0.0 Very first "F**king No Work!" version
\r
60 ;----------------------------------------------------------------
\r
62 ;----------------------------------------------------------------
\r
63 ; Unilink BUSON IN (blue) connected to RC2/CCP1
\r
64 ; Unilink DATA (green) connected to RC3
\r
65 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)
\r
66 ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)
\r
67 ; Unilink RST (lilac) connected to RA4
\r
68 ; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)
\r
69 ; LCD RW connected to pin RB2
\r
70 ; LCD E connected to pin RB3
\r
71 ; LCD DB4-DB7 connected to RB4-RB7
\r
72 ; RS-232 TX from computer connected to RC7/RX
\r
73 ; RS-232 RX to computer connected to RC6/TX
\r
74 ; RS-232 RI to computer connected to RC5
\r
75 ; B+ connected via trimmer and resistors to AN0 (divider approx 20k5/5k to give 20.48V maximum scale)
\r
77 ; This leaves RC0, RC1 and four analog inputs (AN1-AN4) free for now...
\r
79 #define BUSON_IN_BIT PORTC,2
\r
80 #define DATA_BIT PORTC,3
\r
81 #define BUSON_OUT_BIT PORTC,4
\r
82 #define CLK_BIT PORTB,0
\r
83 #define RST_BIT PORTA,4
\r
85 #define LCD_RS_BIT PORTB,1
\r
86 #define LCD_RW_BIT PORTB,2
\r
87 #define LCD_E_BIT PORTB,3
\r
88 #define LCD_DB4_BIT PORTB,4
\r
89 #define LCD_DB5_BIT PORTB,5
\r
90 #define LCD_DB6_BIT PORTB,6
\r
91 #define LCD_DB7_BIT PORTB,7
\r
93 #define RS232_RI_BIT PORTC,5
\r
95 ;----------------------------------------------------------------
\r
96 ; FILE REGISTER USAGE
\r
97 ;----------------------------------------------------------------
\r
98 TrackName00 equ 20h ; Buffer for TrackName
\r
100 TrackName02 equ 22h
\r
101 TrackName03 equ 23h
\r
102 TrackName04 equ 24h
\r
103 TrackName05 equ 25h
\r
104 TrackName06 equ 26h
\r
105 TrackName07 equ 27h
\r
106 TrackName08 equ 28h
\r
107 TrackName09 equ 29h
\r
108 TrackName0a equ 2ah
\r
109 TrackName0b equ 2bh
\r
110 TrackName0c equ 2ch
\r
111 TrackName0d equ 2dh
\r
112 TrackName0e equ 2eh
\r
113 TrackName0f equ 2fh
\r
114 TrackName10 equ 30h
\r
115 TrackName11 equ 31h
\r
116 TrackName12 equ 32h
\r
117 TrackName13 equ 33h
\r
118 TrackName14 equ 34h
\r
119 TrackName15 equ 35h
\r
120 TrackName16 equ 36h
\r
121 TrackName17 equ 37h
\r
122 TrackName18 equ 38h
\r
123 TrackName19 equ 39h
\r
124 TrackName1a equ 3ah
\r
125 TrackName1b equ 3bh
\r
126 TrackName1c equ 3ch
\r
127 TrackName1d equ 3dh
\r
128 TrackName1e equ 3eh
\r
129 TrackName1f equ 3fh
\r
130 TrackName20 equ 40h
\r
131 TrackName21 equ 41h
\r
132 TrackName22 equ 42h
\r
133 TrackName23 equ 43h
\r
134 TrackName24 equ 44h
\r
135 TrackName25 equ 45h
\r
136 TrackName26 equ 46h
\r
137 TrackName27 equ 47h
\r
138 TrackName28 equ 48h
\r
139 TrackName29 equ 49h
\r
140 TrackName2a equ 4ah
\r
141 TrackName2b equ 4bh
\r
142 TrackName2c equ 4ch
\r
143 TrackName2d equ 4dh
\r
144 TrackName2e equ 4eh
\r
145 TrackName2f equ 4fh
\r
147 UnilinkRAD equ 50h ; Beginning of Unilink packet - the Receiving Address
\r
148 UnilinkTAD equ 51h ; Transmitter address
\r
149 UnilinkCMD1 equ 52h ; CMD1 byte
\r
150 UnilinkCMD2 equ 53h ; CMD2 byte
\r
151 UnilinkParity1 equ 54h ; First or only parity byte for short packets (6 bytes)
\r
152 UnilinkData1 equ 55h ; Extra data for medium/large packets, or zero for short packets
\r
153 UnilinkData2 equ 56h ;
\r
154 UnilinkData3 equ 57h ;
\r
155 UnilinkData4 equ 58h ;
\r
156 UnilinkData5 equ 59h ; Data5 if this is a large packet
\r
157 UnilinkParity2M equ 59h ; Parity2 shares the same byte if it's a medium sized packet
\r
158 UnilinkData6 equ 5ah ; Extra data for large packets, or zero for medium packets
\r
159 UnilinkData7 equ 5bh ;
\r
160 UnilinkData8 equ 5ch ;
\r
161 UnilinkData9 equ 5dh ;
\r
162 UnilinkParity2 equ 5eh ; Parity byte for large packets
\r
163 UnilinkZero equ 5fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)
\r
165 UnilinkTimeout equ 60h ; Counts up every 0.5ms to "age out" faulty bytes clocked in
\r
166 UnilinkSelected equ 61h ; High bit is set when selected
\r
167 UnilinkBit equ 62h ; This is my "bitmask" to be used for requests
\r
168 UnilinkID equ 63h ; This is my Bus ID
\r
169 UnilinkCmdLen equ 64h ; This gets updated with the actual packet length after CMD1 has been received
\r
170 UnilinkTXRX equ 65h ; This is a pointer to the Unilink packet above, used with indirect addressing
\r
171 SlaveBreakState equ 66h ; Hold state and time-out information about slave break, indicates when it can happen
\r
172 DisplayStatus equ 67h ; What information will be put on the display next, bit 7 cleared if nothing
\r
173 Icount equ 68h ; Offset of string to print
\r
174 TxTemp equ 69h ; blahblah
\r
175 TxTemp2 equ 6ah ; Blahblah2
\r
183 DataCount equ 71h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)
\r
184 UnilinkCurID equ 72h ; This is a kludge
\r
185 DisplayCounter equ 73h
\r
186 UnilinkAttenuation equ 74h ; The amount of attenuation the volume control is currently set to
\r
194 UnilinkReInits equ 7ch
\r
195 IRQPCLATH equ 7dh ; ISH storage
\r
196 IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks
\r
199 DiscName00 equ 0a0h ; Buffer for DiscName
\r
200 DiscName01 equ 0a1h
\r
201 DiscName02 equ 0a2h
\r
202 DiscName03 equ 0a3h
\r
203 DiscName04 equ 0a4h
\r
204 DiscName05 equ 0a5h
\r
205 DiscName06 equ 0a6h
\r
206 DiscName07 equ 0a7h
\r
207 DiscName08 equ 0a8h
\r
208 DiscName09 equ 0a9h
\r
209 DiscName0a equ 0aah
\r
210 DiscName0b equ 0abh
\r
211 DiscName0c equ 0ach
\r
212 DiscName0d equ 0adh
\r
213 DiscName0e equ 0aeh
\r
214 DiscName0f equ 0afh
\r
215 DiscName10 equ 0b0h
\r
216 DiscName11 equ 0b1h
\r
217 DiscName12 equ 0b2h
\r
218 DiscName13 equ 0b3h
\r
219 DiscName14 equ 0b4h
\r
220 DiscName15 equ 0b5h
\r
221 DiscName16 equ 0b6h
\r
222 DiscName17 equ 0b7h
\r
223 DiscName18 equ 0b8h
\r
224 DiscName19 equ 0b9h
\r
225 DiscName1a equ 0bah
\r
226 DiscName1b equ 0bbh
\r
227 DiscName1c equ 0bch
\r
228 DiscName1d equ 0bdh
\r
229 DiscName1e equ 0beh
\r
230 DiscName1f equ 0bfh
\r
234 ;----------------------------------------------------------------
\r
235 ; Power up/Reset starting point
\r
237 org 0 ; Start at the beginning of memory (the reset vector)
\r
238 call Bootstrap ; Call Flash Load routine
\r
239 call LCDInit ; Initialize LCD I/F
\r
240 call IRQInit ; Set up and start the IRQ handler
\r
241 goto Main ; Run the main program loop (skip the IRQ handler)
\r
243 subtitl "IRQ Handler"
\r
244 ;----------------------------------------------------------------
\r
245 ; Interrupt handler always starts at addr 4
\r
246 ; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.
\r
247 ; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR
\r
248 ; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to
\r
249 ; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low
\r
250 ; again after that. Other interrupts might introduce latencies, but let's see how this works..
\r
252 org 4 ; ISR vector is at address 4
\r
253 movwf IRQW ; Save W
\r
254 swapf STATUS,w ; Get the status register into w
\r
255 clrf STATUS ; Zero out the status reg, gives Reg Bank0
\r
256 movwf IRQSTATUS ; Store the STATUS reg
\r
257 ; Not using PCLATH for anything in the ISR right now
\r
258 ; movf PCLATH,w ; Get the PCLATH reg
\r
259 ; movwf IRQPCLATH ; And store it
\r
260 ; clrf PCLATH ; Go to low memory
\r
261 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)
\r
263 call IRQCheckINT ; Implemented as a subroutine as there's a need to call it repeatedly from the other ISRs
\r
265 btfss PIR1,TMR2IF ; Check if it's the TMR2 interrupt (0.5ms timing)
\r
266 goto IRQNotTMR2 ; No it's not, check the other sources
\r
268 incf Counter,f ; Increment the general purpose counter (increments every 0.5ms)
\r
270 ; Slave break opportunity detection here - the logic works as follows:
\r
271 ; Look for a data low period of at least 5 ms (10 loops)
\r
272 ; Look for a data high period of at least 2 ms (4 loops)
\r
273 ; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 4ms (8 loops)
\r
274 ; If a bit would be received (CLK activates) the packet handler automatically clears out the SlaveBreakState, which means start all over
\r
276 call IRQCheckINT ; Check the Unilink INT as well
\r
278 btfsc SlaveBreakState,5 ; Check if already pulling the data line low
\r
279 goto IRQTMR2SlaveBreak
\r
281 btfsc SlaveBreakState,7 ; Looking for low or high data
\r
282 goto IRQTMR2HighData
\r
283 btfss DATA_BIT ; Looking for a low data line, if it's low, increment state, if it's high, reset state
\r
284 goto IRQTMR2LowDataOK
\r
285 clrf SlaveBreakState ; Got a high data line while waiting for a low one, reset state
\r
287 call IRQCheckINT ; Check the Unilink INT as well
\r
289 goto IRQAfterTMR2 ; Leave ISR
\r
292 call IRQCheckINT ; Check the Unilink INT as well
\r
294 btfsc DATA_BIT ; Looking for a high data line, if it's high - increment state, otherwise wait
\r
295 goto IRQTMR2HighDataOK
\r
297 btfsc SlaveBreakState,6 ; Test the "first time around" bit
\r
298 clrw ; Not the beginning of the state, have to restart the entire thing now, not just this state
\r
299 andwf SlaveBreakState,f ; Mask out the 1 upper control bits and restart this state
\r
304 call IRQCheckINT ; Check the Unilink INT as well
\r
306 bsf SlaveBreakState,6 ; Set the "first time around" bit
\r
308 movf SlaveBreakState,w
\r
311 btfss SlaveBreakState,7 ; Checking whether it's low or high
\r
312 goto IRQTMR2FoundLow
\r
314 xorlw 4 ; It's high now, and if 4 periods have passed we can activate slave break
\r
318 ; Issue slave break here
\r
320 clrf SlaveBreakState
\r
324 btfss DisplayStatus,7 ; Only do this if high bit is set
\r
328 movwf SlaveBreakState
\r
340 call IRQCheckINT ; Check the Unilink INT as well
\r
342 movlw 80h ; Prepare for state 2, looking for data line high
\r
343 movwf SlaveBreakState
\r
347 call IRQCheckINT ; Check the Unilink INT as well
\r
349 movf SlaveBreakState,w
\r
357 clrf SlaveBreakState
\r
360 btfss SlaveBreakState,4 ; Only increment to 0x10
\r
361 incf SlaveBreakState,f
\r
362 bcf PIR1,TMR2IF ; Clear the IRQ source bit to re-enable TMR2 interrupts again
\r
366 ; Finally restore CPU state and return from the ISR
\r
368 ; If I have to save the FSR in the beginning I also need to restore it here...
\r
371 ; movwf PCLATH ; Restore PCLATH
\r
373 movwf STATUS ; Restore STATUS
\r
375 swapf IRQW,w ; Restore W
\r
376 retfie ; Interrupt return
\r
378 ;----------------------------------------------------------------
\r
379 ; IRQCheckINT - This part is the actual Unilink tranceiver, have to call it often as there are only ~20 spare cycles
\r
380 ; (which is only a problem if we're going to transmit, but the check can be done anyway, it's cheap if no bit is there)
\r
383 btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK)
\r
384 return ; No it's not, return again after only four cycles
\r
386 ; If there's activity on the clock line (the clock goes high) the CPU will stay in here until eight bits have been clocked in
\r
387 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction
\r
388 ; cycles (5 MIPS @ 20MHz), not even a problem for serial input if it's not receiving more than 6250 bytes per second, and the
\r
389 ; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of
\r
390 ; 1000 times per second, halting INT's for 1/6250 of a second - this gives the CPU ample of time to deal with all bytes from
\r
391 ; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX
\r
392 ; and RX at the same time (in order to receive packets one has to make sure that the packet buffer is zeroed out before entering
\r
393 ; here, otherwise collisions will occur..
\r
394 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an
\r
395 ; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here
\r
396 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)
\r
398 movlw 8 ; Loop through the 8 bits
\r
400 movf UnilinkTXRX,w ; Get the pointer
\r
401 movwf FSR ; Store it to make use of indirect addressing
\r
404 btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out)
\r
405 goto IRQINTTristate ; Bit is low, we should tristate bit
\r
406 bcf PORTC,3 ; Otherwise set DATA bit low
\r
407 bsf STATUS,RP0 ; Select high regs
\r
408 bcf TRISC,3 ; And pull low (now it's an output)
\r
409 bcf STATUS,RP0 ; Back to regbank 0
\r
410 goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in
\r
413 bsf STATUS,RP0 ; Select high regs
\r
414 bsf TRISC,3 ; Force the bit to be tristated
\r
415 bcf STATUS,RP0 ; Back to regbank 0
\r
418 btfss PORTC,2 ; Check for BUSON
\r
420 btfsc PORTB,0 ; Wait for clock to go low
\r
421 goto IRQINTCLKWaitLow
\r
424 btfss PORTC,3 ; Test DATA
\r
425 setc ; Set carry if data is LOW (data is inverted!)
\r
426 rlf INDF,f ; Shift it into the "accumulator"
\r
428 decfsz DataCount,f ; Loop once more perhaps?
\r
429 goto IRQINTCLKWaitHigh ; Yes, again!
\r
430 goto IRQINTRecvDone ; No it's done, don't check for clock to go high again
\r
433 btfss PORTC,2 ; Check for BUSON
\r
435 btfss PORTB,0 ; Wait for clock to go high
\r
436 goto IRQINTCLKWaitHigh
\r
437 goto IRQINTBitSet ; Loop again
\r
439 ; Successfully received a byte here, run it through a state machine to figure out what to do
\r
440 ; (several possibilites exists here):
\r
441 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero
\r
442 ; If receive counter is zero and the received byte is a zero byte, discard it
\r
443 ; Otherwise store the byte in our receive buffer and increment receive counter
\r
444 ; If the receive counter is 3 check the two upper bits of recv'd byte (CMD1) - this tells us the length of the packet
\r
445 ; 00 = short 6 byte packet
\r
446 ; 10 = medium 11 byte packet
\r
447 ; 11 = long 16 byte packet
\r
448 ; Update the receive length byte accordingly
\r
449 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing
\r
450 ; the packet and take appropriate action.
\r
453 clrf SlaveBreakState ; First of all, clear the break state - this got in the way, restart detection..
\r
456 call BootTXB ; Send the byte to the serial port
\r
458 movf UnilinkTXRX,w ; Find out which byte # that was received
\r
460 bnz IRQINTRecvNotFirst ; Not the first byte
\r
461 movf UnilinkRAD,w ; Get the first byte received
\r
462 bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter
\r
464 incf UnilinkTXRX,f ; Increment address
\r
466 movf UnilinkTXRX,w ; Get the byte position again
\r
467 andlw 0fh ; Only lower 4 bits of interest
\r
468 xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet)
\r
469 bnz IRQINTRecvNotCMD1 ; No, skip the length code for now
\r
470 movlw 6 ; Assume it's a short packet
\r
471 btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long
\r
472 goto IRQINTRecvShort ; Nope, it's a short packet
\r
473 addlw 5 ; OK, it's long or medium at least
\r
474 btfsc INDF,6 ; Test for long
\r
475 addlw 5 ; Yep, it's a long packet
\r
477 movwf UnilinkCmdLen ; Store the length
\r
480 movf UnilinkTXRX,w ; Get the byte position
\r
481 xorwf UnilinkCmdLen,w ; XOR with the calculated command length
\r
482 andlw 0fh ; and mask - this results in a zero result when finished receiving
\r
483 bnz IRQINTRecvIncomplete ; Packet not ready yet
\r
485 ; Here a packet is actually received, should check the checksum(s) now
\r
487 movf UnilinkRAD,w ; QnD checksum check
\r
489 addwf UnilinkCMD1,w
\r
490 addwf UnilinkCMD2,w
\r
491 xorwf UnilinkParity1,w ; This should be zero
\r
492 bnz IRQINTParseComplete ; Don't allow packet parsing on corrupt packets
\r
494 btfss UnilinkCMD1,7 ; Test whether there's more parity to check (medium or long packet)
\r
495 goto IRQINTParser ; No, skip directly to parsing logic
\r
497 movf UnilinkParity1,w ; QnD checksum check for the remaining part of the packet
\r
498 addwf UnilinkData1,w
\r
499 addwf UnilinkData2,w
\r
500 addwf UnilinkData3,w
\r
501 addwf UnilinkData4,w
\r
503 btfss UnilinkCMD1,6 ; Test for a long packet
\r
504 goto IRQINTBypassLongPacket
\r
506 xorwf UnilinkParity2M,w ; Fix for the medium packet parity check a few lines down...
\r
507 addwf UnilinkData5,w
\r
508 addwf UnilinkData6,w
\r
509 addwf UnilinkData7,w
\r
510 addwf UnilinkData8,w
\r
511 addwf UnilinkData9,w
\r
512 xorwf UnilinkParity2,w ; This should be zero when xor:ed with the Parity2M
\r
514 IRQINTBypassLongPacket
\r
515 xorwf UnilinkParity2M,w ; This should be zero for valid medium and long packets
\r
516 bnz IRQINTParseComplete
\r
520 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here
\r
521 ; (there won't be any more communication for at least another 4.8ms))
\r
523 ; Unilink command parser:
\r
525 ; Check for CMD1 = 01h (System bus commands)
\r
528 bnz IRQINTParseNot01
\r
530 ; Check for 01 00 (Bus Re-Initialization)
\r
532 bnz IRQINTParseNot0100
\r
534 call ClearUnilinkStatus ; Clear everything Unilink (ID, BUSON_OUT)
\r
536 incf UnilinkReInits,f ; increment the debug counter
\r
538 goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though)
\r
542 ; Check for 01 02 (Anyone)
\r
545 bnz IRQINTParseNot0102
\r
547 movf UnilinkID,w ; Do I have an ID already?
\r
548 bnz IRQINTParseComplete ; Yep, I don't want another one!
\r
550 call ClearUnilinkBuffer ; Zero it out completely
\r
552 movlw 10h ; Sending to Master
\r
553 addwf UnilinkParity1,f
\r
555 movlw 0d0h ; I'm in the MD changer group
\r
556 addwf UnilinkParity1,f
\r
558 movlw 8ch ; Device discovery command reply
\r
559 addwf UnilinkParity1,f
\r
562 addwf UnilinkParity1,f
\r
565 movf UnilinkParity1,w
\r
566 movwf UnilinkParity2M
\r
568 movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??
\r
569 addwf UnilinkParity2M,f
\r
572 addwf UnilinkParity2M,f
\r
575 addwf UnilinkParity2M,f
\r
577 movlw 0a0h ; 00?? 0a0=10 disc?
\r
578 addwf UnilinkParity2M,f
\r
581 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
585 ; Check for 01 12 (Time poll)
\r
588 bnz IRQINTParseNot0112
\r
591 xorwf UnilinkID,w ; Is it for me?
\r
592 bnz IRQINTParseNot0112 ; Nope
\r
594 call ClearUnilinkBuffer
\r
595 movlw 10h ; Sending to Master
\r
596 addwf UnilinkParity1,f
\r
598 movf UnilinkID,w ; This is my ID
\r
599 addwf UnilinkParity1,f
\r
602 addwf UnilinkParity1,f
\r
605 movlw 80h ; Idle unless selected
\r
606 btfsc UnilinkSelected,7
\r
609 addwf UnilinkParity1,f
\r
611 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
615 ; Check for 01 13 (Request Time poll)
\r
618 bnz IRQINTParseNot0113
\r
621 xorwf UnilinkID,w ; Is it for me?
\r
622 bnz IRQINTParseNot0113 ; Nope
\r
624 btfss DisplayStatus,7 ; If not displaying, skip this
\r
625 goto IRQINTParseComplete
\r
627 call ClearUnilinkBuffer
\r
628 movlw 70h ; Sending to Display Group
\r
629 addwf UnilinkParity1,f
\r
631 movf UnilinkID,w ; This is my ID
\r
632 addwf UnilinkParity1,f
\r
635 addwf UnilinkParity1,f
\r
638 addwf UnilinkParity1,f
\r
641 movf UnilinkParity1,w ; Carry the parity forward
\r
642 movwf UnilinkParity2M
\r
644 movf DisplayStatus,w
\r
645 addwf UnilinkParity2M,f
\r
648 addwf UnilinkParity2M,f
\r
651 addwf UnilinkParity2M,f
\r
655 movf DisplayStatus,w
\r
658 addwf UnilinkParity2M,f
\r
661 incf DisplayStatus,f ; Temporary debug info
\r
662 bsf DisplayStatus,7
\r
664 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
668 ; Check for 01 15 (Who sent the slave break?)
\r
671 bnz IRQINTParseNot0115
\r
673 btfss DisplayStatus,7 ; First of all check if there should be anything displayed
\r
674 goto IRQINTParseComplete ; No, not at this time
\r
676 call ClearUnilinkBuffer
\r
677 movlw 10h ; Sending to Master
\r
678 addwf UnilinkParity1,f
\r
680 movlw 18h ; Broadcast address sending in this special case
\r
681 addwf UnilinkParity1,f
\r
683 movlw 82h ; Who wants to talk reply command
\r
684 addwf UnilinkParity1,f
\r
689 addwf UnilinkParity1,f
\r
692 movf UnilinkParity1,w ; Carry the parity forward
\r
693 movwf UnilinkParity2M
\r
697 addwf UnilinkParity2M,f
\r
701 addwf UnilinkParity2M,f
\r
705 addwf UnilinkParity2M,f
\r
709 addwf UnilinkParity2M,f
\r
712 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
714 ;******************************************************************************
\r
715 ; Bit frig - works out which bit to set in the response to Master Poll
\r
716 ; This is taken more or less verbatim from Simon Woods' GNUnilink code!
\r
718 ; W register is input of which stage you are on (0x00, 0x20, 0x40 etc)
\r
719 ; and is returned with the byte to write (0x00 if wrong stage).
\r
722 xorwf UnilinkBit, 0
\r
723 andlw 0xe0 ; Strip off low bits
\r
725 btfsc STATUS, Z ; Do we have a hit?
\r
732 btfss UnilinkBit, 4 ; Do we need to swap nybbles?
\r
740 swapf UnilinkBit, 0
\r
748 ; Check for CMD1 = 02h (Appoint)
\r
751 bnz IRQINTParseNot02
\r
753 movf UnilinkID,w ; Do I have an ID already?
\r
754 bnz IRQINTParseComplete ; Yep, I don't want another one!
\r
756 movf UnilinkRAD,w ; So I don't have any ID yet, see what the master is trying to set
\r
757 andlw 0f0h ; Check the device group
\r
758 xorlw 0d0h ; Verify it's a MD changer
\r
759 bnz IRQINTParseComplete ; No, something else, skip this
\r
761 movf UnilinkRAD,w ; Get the ID the master has given me
\r
762 movwf UnilinkID ; Store my id
\r
763 movf UnilinkCMD2,w ; Get the bitmask
\r
764 movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding)
\r
766 call ClearUnilinkBuffer
\r
767 movlw 10h ; Sending to Master
\r
768 addwf UnilinkParity1,f
\r
770 movf UnilinkID,w ; This is my ID
\r
771 addwf UnilinkParity1,f
\r
773 movlw 8ch ; Device discovery command again
\r
774 addwf UnilinkParity1,f
\r
777 addwf UnilinkParity1,f
\r
780 movf UnilinkParity1,w
\r
781 movwf UnilinkParity2M ; That's the parity when sending medium messages
\r
784 addwf UnilinkParity2M,f
\r
786 movlw 0a8h ; My internal MD sends 1c here... (external/internal difference)
\r
787 addwf UnilinkParity2M,f
\r
790 addwf UnilinkParity2M,f
\r
792 movlw 0a0h ; 0a0=10disc
\r
793 addwf UnilinkParity2M,f
\r
796 bsf BUSON_OUT_BIT ; Now activate the cascade BUSON pin, to allow others after us to be discovered
\r
798 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
802 ; Check for CMD1 = 87h (Power control)
\r
805 bnz IRQINTParseNot87
\r
807 ; This part could use some more packet sniffing (really), it's sketchy to say the least.. :(
\r
808 ; The idea here is that the 18 10 87 2a PP 12 00 80 00 PP ZZ command is sent on power-up from my headunit if green color and
\r
809 ; if amber it looks like 18 10 87 2a PP 02 00 80 00 PP ZZ, giving away that high nibble (or bit 4) of D1 sets the color
\r
810 ; Also interesting is that the exact same commands gets sent when pressing the power-off button, this makes slaves pause
\r
811 ; playing, and then a few seconds later the unit shuts down with the following command:
\r
812 ; 18 10 87 22 PP 02 00 80 00 PP ZZ or
\r
813 ; 18 10 87 22 PP 12 00 80 00 PP ZZ depending on the color of the backlight
\r
814 ; This makes me think that bit 3 in CMD2 reflects the actual power state of the headunit (actually sleeping or bus active)
\r
815 ; Anyway I use this to set/clear the RI pin used for WakeOnRing on my laptop
\r
816 ; Also I de-select to make everything pause and clear the display status (if we're doing slave breaks after power status
\r
817 ; the headunit will never enter sleep!)
\r
818 ; From what I have gathered the bit mapping of DATA1 is as follows:
\r
820 ; X - Backlight color changed if 1
\r
822 ; X - Backlight color, 0=Amber, 1=Green
\r
823 ; X - Dimmer setting changed if 1
\r
824 ; X X - Dimmer setting, 01=Dimmer Auto, 10=Dimmer On, 00=Dimmer Off, 11=???
\r
825 ; X - Beep setting, 0=Beep on(!), 1=Beep off
\r
827 ; Also bit field of CMD2 for now:
\r
829 ; X X - These two bits are set when changing color, beep etc, but now when actually powering on/off the system???
\r
830 ; X - Always set to 1 on my headunit
\r
831 ; X - Always set to 0 on my headunit
\r
832 ; X - Set to 1 when power is on, 0 when powering off (last command sent before bus dies is 87 22 on my unit)
\r
833 ; X - Always set to 0 on my headunit
\r
834 ; X - Always set to 1 on my headunit
\r
835 ; X - Always set to 0 on my headunit
\r
837 ; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)
\r
838 btfsc UnilinkCMD2,3
\r
839 goto IRQINTParse87PowerOn
\r
841 bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels)
\r
842 goto IRQINTParseComplete
\r
844 IRQINTParse87PowerOn
\r
845 bcf RS232_RI_BIT ; Clear this to make RI pin go high (waking the computer)
\r
847 btfsc UnilinkCMD2,7 ; Test high bit if it's just a "set" command, if yes don't clear status
\r
848 goto IRQINTParseComplete
\r
850 bcf UnilinkSelected,7 ; Also de-select us (this gets sent when powering off but before the actual power down)
\r
853 goto IRQINTParseComplete
\r
857 ; Check for CMD1 = 90h (Display/DSP info, volume etc.)
\r
860 bnz IRQINTParseNot90
\r
862 ; Check for 90 10 (Current Volume)
\r
865 bnz IRQINTParseNot9010
\r
867 movf UnilinkData1,w ; Store current volume setting
\r
868 movwf UnilinkAttenuation
\r
870 goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though)
\r
877 ; Check for CMD1 = f0h (Source Select)
\r
880 bnz IRQINTParseNotF0
\r
883 movwf UnilinkCurID ; Store it for display and debugging
\r
885 xorwf UnilinkID,w ; Check if it's selecting me
\r
886 bnz IRQINTParseF0Deselect
\r
888 bsf UnilinkSelected,7 ; Now we're selected
\r
889 bsf DisplayStatus,7
\r
890 goto IRQINTParseComplete
\r
892 IRQINTParseF0Deselect
\r
894 bcf UnilinkSelected,7 ; Now we're de-selected
\r
895 bcf DisplayStatus,7
\r
896 goto IRQINTParseComplete
\r
900 IRQINTParseComplete
\r
902 ; The code ends up here when parsing is complete and it's not interested in sending any reply back to the master
\r
903 ; (that's why we clear out all the packet buffer bytes)
\r
905 call ClearUnilinkBuffer
\r
907 IRQINTParseBypassClear
\r
909 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
910 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
912 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
915 IRQINTRecvIncomplete
\r
919 ; movwf DataStore ; Store it so the non-irq code can snoop
\r
922 bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again
\r
927 ;----------------------------------------------------------------
\r
928 ; ClearUnilinkStatus - Zeroes out the Unilink state (used when initializing)
\r
932 clrf UnilinkID ; Clear the existing Unilink ID, if any
\r
933 clrf UnilinkCurID ; Clear the currently selected ID as well
\r
934 bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID
\r
935 clrf DisplayStatus ; No crazy display updates when resetting.. :)
\r
936 clrf UnilinkSelected ; We're not selected anymore
\r
938 bsf STATUS,RP0 ; Reg bank 1
\r
939 bsf DATA_BIT ; Make sure data is tristated
\r
940 bcf STATUS,RP0 ; Reg bank 0
\r
942 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
943 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
945 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
947 clrf SlaveBreakState ; Slave Break Processing has to start all over
\r
951 ;----------------------------------------------------------------
\r
952 ; ClearUnilinkBuffer - Zeroes out the Unilink packet buffer
\r
956 ; TODO: Replace this with an FSR access to save space and make the code neater
\r
961 clrf UnilinkParity1
\r
971 clrf UnilinkParity2
\r
977 subtitl "Main loop"
\r
980 ;----------------------------------------------------------------
\r
981 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]
\r
982 ; Here all other house keeping tasks are performed, like displaying info on the LCD..
\r
985 movlw high LookUp ; Set the high PC bits to indicate data lookup page
\r
988 movlw 0ffh ; Set infinite attenuation to begin with
\r
989 movwf UnilinkAttenuation
\r
991 clrf UnilinkReInits ; Clear the bus re-initialization counter
\r
993 bsf STATUS,RP0 ; Reg bank 1
\r
994 movlw 080h ; Right adjusted A/D, all analog inputs, no Vrefs
\r
998 movlw 081h ; Activate A/D, ch 0, Fosc/32 (for 20MHz operation)
\r
1001 bsf ADCON0,2 ; Start the first A/D operation
\r
1003 movlw 8 ; Display page timing (approx 8/sec)
\r
1004 movwf DisplayCounter
\r
1006 bcf LCD_RS_BIT ; LCD Command mode
\r
1007 movlw 80h ; DisplayRam 0
\r
1011 movlw low DefaultText1
\r
1015 call TxLCD8BLoop ; Send 80 bytes to the LCD
\r
1019 bcf LCD_RS_BIT ; LCD Command mode
\r
1020 movlw 80h ; DisplayRam 0
\r
1025 movf Counter,w ; Debug timer
\r
1026 btfsc PORTA,4 ; Test RST
\r
1031 movf SlaveBreakState,w
\r
1033 btfsc PORTB,0 ; Test CLK
\r
1038 btfsc PORTC,2 ; Test BUSON-IN
\r
1043 btfsc PORTC,3 ; Test DATA
\r
1047 movf UnilinkCmdLen,w
\r
1048 bz MainDontPrintCmd
\r
1055 ; UnilinkID @ 13-14
\r
1056 ; UnilinkAttenuation @ 16-17
\r
1057 ; UnilinkSelected @ 28-29
\r
1058 ; UnilinkReInits @ 38,39
\r
1059 ; UnilinkCurID @ 54-55
\r
1060 ; DisplayStatus @ 62-63
\r
1061 ; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)
\r
1063 bcf LCD_RS_BIT ; LCD Command mode
\r
1064 movlw 80h+13 ; DisplayRam 13
\r
1071 bcf LCD_RS_BIT ; LCD Command mode
\r
1072 movlw 80h+16 ; DisplayRam 16
\r
1076 movf UnilinkAttenuation,w
\r
1079 bcf LCD_RS_BIT ; LCD Command mode
\r
1080 movlw 80h+28 ; DisplayRam 28
\r
1084 movf UnilinkSelected,w
\r
1087 bcf LCD_RS_BIT ; LCD Command mode
\r
1088 movlw 80h+38 ; DisplayRam 38
\r
1092 movf UnilinkReInits,w
\r
1095 bcf LCD_RS_BIT ; LCD Command mode
\r
1096 movlw 80h+40h+14 ; DisplayRam 54
\r
1100 movf UnilinkCurID,w
\r
1103 bcf LCD_RS_BIT ; LCD Command mode
\r
1104 movlw 80h+40h+22 ; DisplayRam 62
\r
1108 movf DisplayStatus,w
\r
1111 btfsc ADCON0,2 ; Test if A/D is ready
\r
1112 goto MainADNotReady
\r
1115 movf ADRESL,w ; Add to our result
\r
1121 addwf NumH,f ; And the high byte
\r
1125 skpc ; When this overflows we know there are 8 samples collected
\r
1126 goto MainADStartAD
\r
1128 ; Now shift the added results two steps down (/4) as there are 8 added samples here, and filter high bits
\r
1139 bnz MainADSkipDisplay
\r
1141 bcf LCD_RS_BIT ; LCD Command mode
\r
1142 movlw 80h+40h+26 ; DisplayRam 66
\r
1168 bsf ADCON0,2 ; Start a new conversion
\r
1172 ; This part handles display "scroll" by shifting one screen at a time
\r
1174 btfss Counter,7 ; Test high bit
\r
1175 goto MainCounterLow
\r
1177 ; So bit is high, set high bit of displaycounter as well...
\r
1178 bsf DisplayCounter,7
\r
1179 goto MainSkipScroll
\r
1182 ; OK, bit is low, now figure out whether it was high or low last time -> check high bit of DisplayCounter
\r
1183 btfss DisplayCounter,7
\r
1184 goto MainSkipScroll
\r
1186 bcf DisplayCounter,7 ; Clear the high bit to allow countdown to commence
\r
1187 movf Counter,w ; Load it
\r
1189 goto MainSkipScroll
\r
1190 decfsz DisplayCounter,f
\r
1191 goto MainSkipScroll
\r
1194 movwf DisplayCounter
\r
1196 bcf LCD_RS_BIT ; LCD Command mode
\r
1197 movlw 18h ; Display shift Left
\r
1198 call TxLCDB ; Shift it 8 positions
\r
1210 ; Display scroll part ends here...
\r
1212 ; movf DataCount,w ; Load bit counter (if 0 then byte is available)
\r
1216 ; decf DataCount,f ; Set it non-zero
\r
1218 ; movf DataStore,w
\r
1219 ; call BootTXB ; Send to terminal
\r
1223 ;----------------------------------------------------------------
\r
1224 ; IRQInit - Sets up the IRQ Handler
\r
1225 ; Set up Timer2 to generate 2000 interrupts per second, used for timing - 1/16 prescaler and a PR2 reg of 156 (0x9c) is set
\r
1226 ; Also enable INT interrupts for Unilink CLK processing
\r
1230 call ClearUnilinkStatus
\r
1231 call ClearUnilinkBuffer
\r
1233 ; Fix the output state of RI and BUSON_OUT to a safe default
\r
1235 bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)
\r
1236 bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first
\r
1238 movlw 06h ; Timer2 enabled + 1/16 prescaler
\r
1241 bsf STATUS,RP0 ; Reg bank 1
\r
1243 movlw 09ch ; Timer PR2 reg giving 2000 interrupts per second
\r
1246 bcf RS232_RI_BIT ; Both bits should be outputs
\r
1247 bcf BUSON_OUT_BIT ;
\r
1249 ; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...
\r
1250 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge
\r
1252 bsf INTCON,INTE ; Enable the RB0/INT
\r
1253 bsf INTCON,PEIE ; Enable the peripheral interrupts
\r
1254 bsf PIE1,TMR2IE ; Enable the Timer2 peripheral interrupt
\r
1255 bsf INTCON,GIE ; Enable global interrupts
\r
1257 bsf TXSTA,TXEN ; Enable UART TX
\r
1259 bcf STATUS,RP0 ; Back to bank 0
\r
1261 bsf RCSTA,SPEN ; Enable serial port
\r
1262 bsf RCSTA,CREN ; Enable UART RX
\r
1266 ;----------------------------------------------------------------
\r
1267 ; Initialize LCD Controller...
\r
1270 clrf PORTB ; First clear PortB data register
\r
1271 bsf STATUS,RP0 ; Reg bank 1
\r
1272 movlw 001h ; All but RB0 are outputs.
\r
1275 bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up
\r
1276 bcf STATUS,RP0 ; Restore Reg bank 0
\r
1278 ; This is a standard reset sequence for the LCD controller
\r
1280 movlw 160 ; Need to delay for at least 15ms, let's go for 16ms delay
\r
1283 movlw 3 ; Write 3 to the LCD
\r
1284 call TxLCD ; Send to LCD
\r
1285 movlw 50 ; Need to delay for at least 4.1ms, let's go for 5ms delay
\r
1288 movlw 3 ; Write 3 to the LCD
\r
1290 movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay
\r
1293 movlw 3 ; Write 3 to the LCD
\r
1295 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
1298 movlw 2 ; 4-bit interface requested
\r
1300 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
1303 ; Reset sequence ends here
\r
1304 ; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits
\r
1306 movlw 28h ; Function Select + 4-bit bus + 2-line display
\r
1309 movlw 0ch ; Display Control + LCD On (No cursor)
\r
1312 movlw 01h ; Clear Display
\r
1315 movlw 06h ; Auto Increment cursor position
\r
1318 bsf LCD_RS_BIT ; Accept data
\r
1322 ;----------------------------------------------------------------
\r
1324 ; Sends two characters hex to the LCD
\r
1328 ; Original binary to 2-digit hex conversion from piclist.com, modified to fit here
\r
1358 ;----------------------------------------------------------------
\r
1360 ; Binary-to-BCD. Written by John Payson.
\r
1361 ; Taken from piclist.com - why re-invent the wheel when writing open-sourced code?
\r
1363 ; Enter with 16-bit binary number in NumH:NumL.
\r
1364 ; Exits with BCD equivalent in TenK:Thou:Hund:Tens:Ones.
\r
1367 BCDConvert: ; Takes number in NumH:NumL
\r
1368 ; Returns decimal in
\r
1369 ; TenK:Thou:Hund:Tens:Ones
\r
1371 andlw 0Fh ;*** PERSONALLY, I'D REPLACE THESE 2
\r
1372 addlw 0F0h ;*** LINES WITH "IORLW 11110000B" -AW
\r
1408 ; At this point, the original number is
\r
1409 ; equal to TenK*10000+Thou*1000+Hund*100+Tens*10+Ones
\r
1410 ; if those entities are regarded as two's compliment
\r
1411 ; binary. To be precise, all of them are negative
\r
1412 ; except TenK. Now the number needs to be normal-
\r
1413 ; ized, but this can all be done with simple byte
\r
1440 ;----------------------------------------------------------------
\r
1442 ; Send a string to the LCD.
\r
1447 movlw 80h ; DisplayRam 0
\r
1452 movlw 80h+40 ; DisplayRam 40 (row 2)
\r
1458 ;----------------------------------------------------------------
\r
1460 ; Send a string to the LCD.
\r
1463 ; movwf Icount ; Icount = W
\r
1465 movwf e_LEN ; Move to e_LEN
\r
1468 movf Icount,w ; get the byte
\r
1470 incf Icount,f ; ...else ++Icount (table index)
\r
1471 call TxLCDB ; Send out the byte
\r
1476 ;----------------------------------------------------------------
\r
1477 ; TxLCDB - send a byte to the LCD
\r
1480 movwf TxTemp ; Store byte to send for a while...
\r
1482 bcf temp,0 ; Clear my temp bit
\r
1483 btfss LCD_RS_BIT ; Check if we try the correct reg
\r
1486 bsf temp,0 ; Indicate RS change
\r
1490 call RxLCDB ; Receive byte from LCD, status reg
\r
1492 skpz ; If the bit was set, the zero flag is not
\r
1495 btfsc temp,0 ; If we had to clear RS reset it now
\r
1498 swapf TxTemp,w ; Hi nibble of data to send in lo w bits
\r
1499 call TxLCD ; Send them first...
\r
1500 movf TxTemp,w ; Then we have the low nibble in low w bits
\r
1501 call TxLCD ; And send that one as well
\r
1505 ;----------------------------------------------------------------
\r
1506 ; RxLCDB - recv a byte from the LCD
\r
1509 call RxLCD ; Receive the high nibble
\r
1511 swapf LCDWTmp,f ; Swap it back to file
\r
1512 call RxLCD ; Receive the low nibble
\r
1513 addwf LCDWTmp,w ; Put the nibbles together and return in W
\r
1517 ;----------------------------------------------------------------
\r
1518 ; TxLCD - send a nibble to the LCD
\r
1521 movwf LCDWTmp ; Write nibble to tmp
\r
1522 bcf LCD_DB4_BIT ; Clear previous data
\r
1523 bcf LCD_DB5_BIT ;
\r
1527 btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT
\r
1529 btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT
\r
1531 btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT
\r
1533 btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT
\r
1536 bsf LCD_E_BIT ; And set E to clock the data into the LCD module
\r
1537 nop ; Let it settle
\r
1538 bcf LCD_E_BIT ; And clear the Enable again.
\r
1539 return ; Returns without modifying W
\r
1541 ;----------------------------------------------------------------
\r
1542 ; RxLCD - recv a nibble from the LCD
\r
1545 clrw ; Clear W register, return data in lower 4 bits
\r
1547 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
1549 bsf LCD_DB4_BIT ; This sets the port bit as an input
\r
1554 bcf STATUS,RP0 ; Back at reg bank 0
\r
1556 bsf LCD_RW_BIT ; Set Read mode for the LCD
\r
1557 bsf LCD_E_BIT ; And set E to clock the data out of the LCD module
\r
1558 nop ; Let the bus settle
\r
1559 btfsc LCD_DB4_BIT ; Transfer a set port bit into W
\r
1561 btfsc LCD_DB5_BIT ; Transfer a set port bit into W
\r
1563 btfsc LCD_DB6_BIT ; Transfer a set port bit into W
\r
1565 btfsc LCD_DB7_BIT ; Transfer a set port bit into W
\r
1567 bcf LCD_E_BIT ; And clear the Enable again.
\r
1568 bcf LCD_RW_BIT ; Set Write mode for the LCD
\r
1570 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
1572 bcf LCD_DB4_BIT ; Set the port as an output again
\r
1573 bcf LCD_DB5_BIT ;
\r
1577 bcf STATUS,RP0 ; Back at reg bank 0
\r
1579 return ; Returns with data in W
\r
1581 ;----------------------------------------------------------------------
\r
1582 ; Delay routines (non-interrupt based, therefore not even close to reliable)
\r
1583 ; W=10 gives ~ 1ms of delay
\r
1584 ; 1ms=5000 instructions wasted, 100us=500 cycles
\r
1585 ; Maximum time waited will be 256*100us=25.6ms
\r
1588 movwf Dcount ; Set delay counter, number of 100us periods to wait
\r
1591 movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more
\r
1592 movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount
\r
1594 decfsz Dcount2,f ; 1 cycle (or two when exiting the loop)
\r
1595 goto DelayInner ; 2 cycles
\r
1596 decfsz Dcount,f ; Now decrement number of 100us periods and loop again
\r
1601 ;----------------------------------------------------------------
\r
1602 ; Data can be stored between 600 and 6ffh...
\r
1607 ; UnilinkID @ 13-14
\r
1608 ; UnilinkAttenuation @ 16-17
\r
1609 ; UnilinkSelected @ 28-29
\r
1610 ; UnilinkReInits @ 38,39
\r
1611 ; UnilinkCurID @ 54-55
\r
1612 ; DisplayStatus @ 62-63
\r
1613 ; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)
\r
1616 DT "----- WJ", "MyID:xx ", "xx dB at", "Sel:xx B", "Inits:xx"
\r
1617 DT " Unilink", "CurID:xx", "t Dsp:xx", "atxx.xxV", " <WJ>"
\r
1620 LookUp movwf PCL ; Go to it (this assumes PCLATH == 06h)
\r
1623 subtitl "Bootstrap/Bootloader code"
\r
1626 ;----------------------------------------------------------------------
\r
1627 ; Bootstrap code - Allows PIC to flash itself with data from the async port.
\r
1628 ; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory
\r
1629 ; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver
\r
1630 ; (one good way of solving that is to wait for the echo from the PIC before sending anything else)
\r
1631 ; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration
\r
1632 ; register can't be programmed. That means that any references to the config register in the hex file will be ignored.
\r
1634 ; Startup @9600bps
\r
1636 ; RAM usage for the bootstrap code
\r
1638 BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog
\r
1643 BootTimerL equ 79h
\r
1644 BootTimerM equ 78h
\r
1645 BootTimerH equ 77h
\r
1646 BootNumBytes equ 76h
\r
1647 BootDataVL equ 75h
\r
1648 BootDataVH equ 74h
\r
1649 BootHEXTemp equ 73h
\r
1651 org 738h ; Place the boot code at the top of memory (currently the loader is exactly 200 bytes)
\r
1654 bsf STATUS,RP0 ; Access bank 1
\r
1655 bsf TXSTA,TXEN ; Enable UART TX
\r
1656 movlw 31 ; Divisor for 9k6 @ 20MHz Fosc
\r
1657 movwf SPBRG ; Store
\r
1658 bcf STATUS,RP0 ; Back to bank 0
\r
1660 bsf RCSTA,SPEN ; Enable serial port
\r
1661 bsf RCSTA,CREN ; Enable UART RX
\r
1663 movlw low BootStartText ; Send boot banner to the serial port
\r
1666 ; movlw 0e8h ; Initialize timeout timer (e8 is about 3 secs)
\r
1667 movlw 0fdh ; Initialize timeout timer (fd is short enough to get the headunit to recognize us)
\r
1673 incf BootTimerL,f ; A 24-bit counter
\r
1678 skpnz ; When overflowing here..
\r
1679 goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program
\r
1680 btfss PIR1,RCIF ; Wait for RX to complete
\r
1685 goto BootTimeout ; If it wasn't ESC, wait for another key
\r
1688 movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port
\r
1696 call BootRXB ; First find the ':'
\r
1699 goto BootLoop ; Loop until we find it!
\r
1701 call BootRXHEX ; Get one ASCII encoded byte (two chars)
\r
1702 movwf BootNumBytes ; This is the number of bytes to be programmed on the line
\r
1703 ; Maybe clear cary here?
\r
1704 rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format
\r
1706 ; Note carry should be clear here as there cannot be odd number of bytes in this format
\r
1708 call BootRXHEX ; Receive AddrH
\r
1710 call BootRXHEX ; Receive AddrL
\r
1712 rrf BootAddrH,f ; Fix the addressing again
\r
1715 bcf BootBits,2 ; Assume we should program
\r
1716 bsf BootBits,1 ; And assume we should program flash not ee
\r
1719 xorlw 020h ; Check if it's configuration, which we can't program
\r
1720 skpnz ; Skip the bit set if it was false alarm
\r
1721 bsf BootBits,2 ; No programming for this line
\r
1723 xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)
\r
1724 skpnz ; Skip the bit set instr if not EE data address
\r
1725 bcf BootBits,1 ; We should program EE, will ignore the AddrH
\r
1727 call BootRXHEX ; Receive Record Type (must be 0 for real records)
\r
1728 skpz ; Check if zero
\r
1729 goto BootFlashComplete
\r
1732 call BootRXHEX ; Receive low-byte of data word
\r
1734 call BootRXHEX ; Receive high-byte of data word
\r
1737 btfsc BootBits,2 ; Check whether this line should be programmed at all
\r
1738 goto BootWriteSkip
\r
1740 bcf BootBits,0 ; Read mode first, verify if we actually have to write
\r
1743 xorwf BootDataL,f ; Compare and destroy DataL
\r
1744 movwf BootDataL ; Write new data to DataL
\r
1745 skpz ; Skip if no difference, have to check high byte as well
\r
1746 goto BootWrite ; Jump directly to write
\r
1749 xorwf BootDataH,f ; Compare
\r
1750 skpnz ; Skip if no difference, no programming necessary
\r
1751 goto BootWriteSkip
\r
1755 movwf BootDataH ; Have to put the new H byte data in as well
\r
1758 call BootEE ; Write directly into program mem
\r
1760 ; Here a verify can take place, the read-back results are now in DataL/H
\r
1764 incf BootAddrL,f ; Advance counter to next addr
\r
1766 incf BootAddrH,f ; And add to high byte if needed
\r
1768 decfsz BootNumBytes,f
\r
1776 movlw low BootRunText
\r
1779 bsf STATUS,RP0 ; Reg bank 1
\r
1781 btfss TXSTA,TRMT ; Wait for last things to flush
\r
1782 goto BootReturnWait
\r
1783 bcf TXSTA,TXEN ; Disable UART TX
\r
1784 bcf STATUS,RP0 ; Back to bank 0
\r
1786 bcf RCSTA,SPEN ; Disable serial port
\r
1787 bcf RCSTA,CREN ; Disable UART RX
\r
1789 return ; Return to code
\r
1791 ;----------------------------------------------------------------------
\r
1792 ; BootTXB - Sends one byte to the UART, waits for transmitter to become
\r
1793 ; free before sending
\r
1797 btfss PIR1,TXIF ; Wait for TX to empty
\r
1799 movwf TXREG ; Send the byte
\r
1802 ;----------------------------------------------------------------------
\r
1803 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated
\r
1806 movwf BootAddrL ; Store LSB of text pointer
\r
1807 movlw 07h ; MSB of pointer to the text (0700h in this boot loader)
\r
1809 movlw 02h ; Select "Read Program Memory" operation
\r
1812 call BootEE ; Lookup char (actually two packed into one word)
\r
1813 rlf BootDataL,w ; Shift the MSB out into carry (that's the 2nd char LSB)
\r
1814 rlf BootDataH,w ; Shift it into 2nd char
\r
1815 call BootTXB ; Send the high byte first
\r
1816 movf BootDataL,w ; Get the low byte
\r
1817 andlw 07fh ; Mask of the highest bit
\r
1818 skpnz ; Stop if zero
\r
1820 call BootTXB ; Send char
\r
1821 incf BootAddrL,f ; Increment pointer
\r
1822 goto BootTXStrLoop
\r
1824 ;----------------------------------------------------------------------
\r
1825 ; BootRXB - Receives one byte from the UART, waits if nothing available
\r
1829 btfss PIR1,RCIF ; Wait for RX to complete
\r
1831 movf RCREG,w ; Get the recvd byte
\r
1832 call BootTXB ; Echo to terminal
\r
1835 ;----------------------------------------------------------------------
\r
1836 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary
\r
1839 call BootRXB ; Receive nibble
\r
1841 ; This code is from piclist.com, really neat!
\r
1843 addlw -'A' ; Convert from BCD to binary nibble
\r
1844 skpc ; Test if if was 0-9 or A-F, skip if A-F
\r
1845 addlw 'A' - 10 - '0' ; It was numeric '0'
\r
1846 addlw 10 ; Add 10 (A get to be 0ah etc.)
\r
1850 ;----------------------------------------------------------------------
\r
1851 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available
\r
1852 ; Decodes the bytes as ASCII hex and returns a single byte in W
\r
1855 call BootRXHEXNibble
\r
1857 swapf BootHEXTemp,f ; Swap it up to the high nibble
\r
1859 call BootRXHEXNibble
\r
1860 addwf BootHEXTemp,w ; And add the two nibbles together
\r
1863 ;----------------------------------------------------------------------
\r
1864 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the
\r
1865 ; exact action to take. BootAddrL and BootAddrH has to be initialized
\r
1866 ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash
\r
1867 ; The data to be written has to be put in BootDataL and BootDataH, and
\r
1868 ; data will be to the same place when read back
\r
1871 bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0)
\r
1873 movf BootAddrH,w ; Load desired address
\r
1877 movf BootDataH,w ; And load the data (only used when writing)
\r
1882 bsf STATUS,RP0 ; Go to bank 3
\r
1884 bsf EECON1,EEPGD ; Point to Program Flash mem
\r
1885 btfss BootBits,1 ; Test if that was correct or if we have to clear again
\r
1886 bcf EECON1,EEPGD ; Point to EE DATA mem
\r
1888 btfss BootBits,0 ; Check from read or write
\r
1889 goto BootEERD ; Skip the WR if we were going for a read
\r
1891 bsf EECON1,WREN ; Enable writes
\r
1895 movwf EECON2 ; Unlock write operation
\r
1896 bsf EECON1,WR ; And start a write cycle
\r
1898 btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish
\r
1899 goto BootWRLoop ; These two instructions gets NOPed when flashing
\r
1901 bcf EECON1,WREN ; Finally disable writes again
\r
1902 ; Here we read the data back again, can be used as verify
\r
1904 bsf EECON1,RD ; Start a read cycle
\r
1905 nop ; Only necessary for flash read, same thing as when writing above
\r
1906 nop ; Except I could use the two words for something useful there.. :)
\r
1909 bcf STATUS,RP0 ; Back to bank 2
\r
1910 movf EEDATA,w ; Store our EE-data
\r
1914 bcf STATUS,RP1 ; And finally back to bank 0
\r
1918 ; To produce compact code the end zero byte has to be in the LSB (that means an even number of chars in every string)
\r
1920 ; "WJBoot - press ESC to flash\x00"
\r
1921 DW 0x2bca,0x216f,0x37f4,0x102d,0x1070,0x3965,0x39f3,0x1045,0x29c3,0x1074,0x37a0,0x336c,0x30f3,0x3400
\r
1923 ; "\r\nSend INHX8 file now...\r\x00"
\r
1924 DW 0x068a,0x29e5,0x3764,0x1049,0x2748,0x2c38,0x1066,0x34ec,0x32a0,0x376f,0x3bae,0x172e,0x0680
\r
1926 ; "\r\nExiting loader\r\x00"
\r
1927 DW 0x068a,0x22f8,0x34f4,0x34ee,0x33a0,0x366f,0x30e4,0x32f2,0x0680
\r
1930 ;----------------------------------------------------------------------
\r
1931 ; EE Data (64 bytes), located at 2100h
\r
1934 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
1935 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
1936 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
1937 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
1938 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
1939 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
1940 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
1941 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r