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 ; Check how the slave should sense "power off" de-selection. Now it continues to play, as the slave doesn't stop requesting updates!
\r
37 ; Investigate whether I actually have to save PCLATH in ISH, maybe save FSR? - Not saving any of them for now
\r
38 ; Check Overrun errors from the UART
\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.9 Interrupt driven UART RX for status updates, using raw unilink packets for everything else, dynamic text
\r
51 ; 0.8 Some text commands implemented, only static text for now though
\r
52 ; 0.7 Debug Serial TX in ISR now, checksum check for incoming packets in place, A/D works, solved the master reset prob
\r
53 ; (by calling the INT handler from TMR2 ISR code (too much interrupt latency when transmitting)
\r
54 ; 0.6 Some more LCD info and clean-up of the Unilink recovery code, some problems with master resetting :(
\r
55 ; 0.5 Issues slave breaks seemingly without hickups (!)
\r
56 ; 0.4 Some fixups in the bootstrap code (I actually had to put the PIC in my PICSTART Plus programmer again :))
\r
57 ; 0.3 Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)
\r
58 ; 0.2 First attempt at responding to the Anyone command
\r
59 ; 0.1 Receives Unilink data OK, relays it to serial
\r
60 ; 0.0 Very first "F**king No Work!" version
\r
62 ;----------------------------------------------------------------
\r
64 ;----------------------------------------------------------------
\r
65 ; Unilink BUSON IN (blue) connected to RC2/CCP1
\r
66 ; Unilink DATA (green) connected to RC3
\r
67 ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining)
\r
68 ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin)
\r
69 ; Unilink RST (lilac) connected to RA4
\r
70 ; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit)
\r
71 ; LCD RW connected to pin RB2
\r
72 ; LCD E connected to pin RB3
\r
73 ; LCD DB4-DB7 connected to RB4-RB7
\r
74 ; RS-232 TX from computer connected to RC7/RX
\r
75 ; RS-232 RX to computer connected to RC6/TX
\r
76 ; RS-232 RI to computer connected to RC5
\r
77 ; B+ connected via trimmer and resistors to AN0 (divider approx 20k5/5k to give 20.48V maximum scale)
\r
79 ; This leaves RC0, RC1 and four analog inputs (AN1-AN4) free for now...
\r
81 #define BUSON_IN_BIT PORTC,2
\r
82 #define DATA_BIT PORTC,3
\r
83 #define BUSON_OUT_BIT PORTC,4
\r
84 #define CLK_BIT PORTB,0
\r
85 #define RST_BIT PORTA,4
\r
87 #define LCD_RS_BIT PORTB,1
\r
88 #define LCD_RW_BIT PORTB,2
\r
89 #define LCD_E_BIT PORTB,3
\r
90 #define LCD_DB4_BIT PORTB,4
\r
91 #define LCD_DB5_BIT PORTB,5
\r
92 #define LCD_DB6_BIT PORTB,6
\r
93 #define LCD_DB7_BIT PORTB,7
\r
95 #define RS232_RI_BIT PORTC,5
\r
97 ;----------------------------------------------------------------
\r
98 ; FILE REGISTER USAGE
\r
99 ;----------------------------------------------------------------
\r
101 ; Free from 20h-48h
\r
104 TracknameEnd equ 3eh
\r
107 DiscNameEnd equ 48h
\r
118 UnilinkRAD equ 50h ; Beginning of Unilink packet - the Receiving Address
\r
119 UnilinkTAD equ 51h ; Transmitter address
\r
120 UnilinkCMD1 equ 52h ; CMD1 byte
\r
121 UnilinkCMD2 equ 53h ; CMD2 byte
\r
122 UnilinkParity1 equ 54h ; First or only parity byte for short packets (6 bytes)
\r
123 UnilinkData1 equ 55h ; Extra data for medium/large packets, or zero for short packets
\r
124 UnilinkData2 equ 56h ;
\r
125 UnilinkData3 equ 57h ;
\r
126 UnilinkData4 equ 58h ;
\r
127 UnilinkData5 equ 59h ; Data5 if this is a large packet
\r
128 UnilinkParity2M equ 59h ; Parity2 shares the same byte if it's a medium sized packet
\r
129 UnilinkData6 equ 5ah ; Extra data for large packets, or zero for medium packets
\r
130 UnilinkData7 equ 5bh ;
\r
131 UnilinkData8 equ 5ch ;
\r
132 UnilinkData9 equ 5dh ;
\r
133 UnilinkParity2 equ 5eh ; Parity byte for large packets
\r
134 UnilinkZero equ 5fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)
\r
136 UnilinkTimeout equ 60h ; Counts up every 0.5ms to "age out" faulty bytes clocked in
\r
137 UnilinkSelected equ 61h ; High bit is set when selected
\r
138 UnilinkBit equ 62h ; This is my "bitmask" to be used for requests
\r
139 UnilinkID equ 63h ; This is my Bus ID
\r
140 UnilinkCmdLen equ 64h ; This gets updated with the actual packet length after CMD1 has been received
\r
141 UnilinkTXRX equ 65h ; This is a pointer to the Unilink packet above, used with indirect addressing
\r
142 SlaveBreakState equ 66h ; Hold state and time-out information about slave break, indicates when it can happen
\r
143 DisplayStatus equ 67h ; What information will be put on the display next, bit 7 cleared if nothing
\r
144 Icount equ 68h ; Offset of string to print
\r
145 TxTemp equ 69h ; blahblah
\r
146 TxTemp2 equ 6ah ; Blahblah2
\r
154 DataCount equ 71h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)
\r
155 UnilinkCurID equ 72h ; This is a kludge
\r
156 DisplayCounter equ 73h
\r
157 UnilinkAttenuation equ 74h ; The amount of attenuation the volume control is currently set to
\r
165 UnilinkReInits equ 7ch
\r
166 IRQPCLATH equ 7dh ; ISH storage
\r
167 IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks
\r
170 RecvBuf equ 0a0h ; Buffer for received data from PC (32 bytes)
\r
171 RecvBufEnd equ 0bfh ;
\r
175 ;----------------------------------------------------------------
\r
176 ; Power up/Reset starting point
\r
178 org 0 ; Start at the beginning of memory (the reset vector)
\r
179 call Bootstrap ; Call Flash Load routine
\r
180 call LCDInit ; Initialize LCD I/F
\r
181 call IRQInit ; Set up and start the IRQ handler
\r
182 goto Main ; Run the main program loop (skip the IRQ handler)
\r
184 subtitl "IRQ Handler"
\r
185 ;----------------------------------------------------------------
\r
186 ; Interrupt handler always starts at addr 4
\r
187 ; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction.
\r
188 ; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR
\r
189 ; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to
\r
190 ; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low
\r
191 ; again after that. Other interrupts might introduce latencies, but let's see how this works..
\r
193 org 4 ; ISR vector is at address 4
\r
194 movwf IRQW ; Save W
\r
195 swapf STATUS,w ; Get the status register into w
\r
196 clrf STATUS ; Zero out the status reg, gives Reg Bank0
\r
197 movwf IRQSTATUS ; Store the STATUS reg
\r
198 ; Not using PCLATH for anything in the ISR right now
\r
199 ; movf PCLATH,w ; Get the PCLATH reg
\r
200 ; movwf IRQPCLATH ; And store it
\r
201 ; clrf PCLATH ; Go to low memory
\r
202 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)
\r
204 call IRQCheckINT ; Implemented as a subroutine as there's a need to call it repeatedly from the other ISRs
\r
206 btfss PIR1,TMR2IF ; Check if it's the TMR2 interrupt (0.5ms timing)
\r
207 goto IRQNotTMR2 ; No it's not, check the other sources
\r
209 incf Counter,f ; Increment the general purpose counter (increments every 0.5ms)
\r
211 ; Slave break opportunity detection here - the logic works as follows:
\r
212 ; Look for a data low period of at least 5 ms (10 loops)
\r
213 ; Look for a data high period of at least 2 ms (4 loops)
\r
214 ; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 4ms (8 loops)
\r
215 ; If a bit would be received (CLK activates) the packet handler automatically clears out the SlaveBreakState, which means start all over
\r
217 call IRQCheckINT ; Check the Unilink INT as well
\r
219 btfsc SlaveBreakState,5 ; Check if already pulling the data line low
\r
220 goto IRQTMR2SlaveBreak
\r
222 btfsc SlaveBreakState,7 ; Looking for low or high data
\r
223 goto IRQTMR2HighData
\r
224 btfss DATA_BIT ; Looking for a low data line, if it's low, increment state, if it's high, reset state
\r
225 goto IRQTMR2LowDataOK
\r
226 clrf SlaveBreakState ; Got a high data line while waiting for a low one, reset state
\r
228 call IRQCheckINT ; Check the Unilink INT as well
\r
230 goto IRQAfterTMR2 ; Leave ISR
\r
233 call IRQCheckINT ; Check the Unilink INT as well
\r
235 btfsc DATA_BIT ; Looking for a high data line, if it's high - increment state, otherwise wait
\r
236 goto IRQTMR2HighDataOK
\r
238 btfsc SlaveBreakState,6 ; Test the "first time around" bit
\r
239 clrw ; Not the beginning of the state, have to restart the entire thing now, not just this state
\r
240 andwf SlaveBreakState,f ; Mask out the 1 upper control bits and restart this state
\r
245 call IRQCheckINT ; Check the Unilink INT as well
\r
247 bsf SlaveBreakState,6 ; Set the "first time around" bit
\r
249 movf SlaveBreakState,w
\r
252 btfss SlaveBreakState,7 ; Checking whether it's low or high
\r
253 goto IRQTMR2FoundLow
\r
255 xorlw 4 ; It's high now, and if 4 periods have passed we can activate slave break
\r
259 ; Issue slave break here
\r
261 clrf SlaveBreakState
\r
263 ; incf Counter,f : Debug!
\r
265 btfss DisplayStatus,7 ; Only do this if high bit is set
\r
269 movwf SlaveBreakState
\r
281 call IRQCheckINT ; Check the Unilink INT as well
\r
283 movlw 80h ; Prepare for state 2, looking for data line high
\r
284 movwf SlaveBreakState
\r
288 call IRQCheckINT ; Check the Unilink INT as well
\r
290 movf SlaveBreakState,w
\r
298 clrf SlaveBreakState
\r
301 btfss SlaveBreakState,4 ; Only increment to 0x10
\r
302 incf SlaveBreakState,f
\r
303 bcf PIR1,TMR2IF ; Clear the IRQ source bit to re-enable TMR2 interrupts again
\r
307 call IRQCheckINT ; Check the Unilink INT as well
\r
309 btfss PIR1,RCIF ; Check if it's the UART RX interrupt
\r
310 goto IRQNotRCIF ; No it's not, check the other sources
\r
311 ;----------------------------------------------------------------
\r
312 ; This is the UART RX routine, this should be control info from the connected PC we're receiving
\r
314 movf CurRecvPos,w ; Load fsr
\r
316 movf RCREG,w ; Get the byte
\r
317 bcf PIR1,RCIF ; Clear current interrupt condition
\r
318 movwf INDF ; Store the recv byte at the position waiting
\r
320 xorwf FSR,w ; Check if it's the recvtype coming in
\r
323 call IRQCheckINT ; Check the Unilink INT as well
\r
325 movf RecvType,w ; Load type
\r
326 bz RecvTimeUpdate ; Position update
\r
327 xorlw 1 ; Track Name perhaps?
\r
329 xorlw 3 ; this is xor 1 and then xor 2, disc name?
\r
332 call IRQCheckINT ; Check the Unilink INT as well
\r
334 btfss RecvType,7 ; Check Hi byte
\r
341 call IRQCheckINT ; Check the Unilink INT as well
\r
346 call IRQCheckINT ; Check the Unilink INT as well
\r
349 btfss DisplayStatus,7 ; Only if not updating already!
\r
351 movwf DisplayStatus ; Force it into DisplayStatus
\r
355 movlw CurDisc ; Load DL start here
\r
357 movlw CurSecs+1 ; And stop here
\r
362 movlw TrackName ; Load DL start here
\r
364 movlw TracknameEnd+1 ; And stop here
\r
369 movlw DiscName ; Load DL start here
\r
371 movlw DiscNameEnd+1 ; And stop here
\r
378 call IRQCheckINT ; Check the Unilink INT as well
\r
380 movf CurRecvPos,w ; Check if we're done
\r
384 call IRQCheckINT ; Check the Unilink INT as well
\r
386 ; movlw 81h ; Assume we do it from the beginning (text and pos)
\r
387 ; movf RecvType,f ; Just check for time-update cmd
\r
388 ; skpnz ; No adjustment if another cmd
\r
389 ; movlw 85h ; Skip the first 4 display cmds
\r
390 ; movwf DisplayStatus
\r
392 ; call IRQCheckINT ; Check the Unilink INT as well
\r
394 movlw RecvType ; restore for another go
\r
401 call IRQCheckINT ; Check the Unilink INT as well
\r
403 btfss PIR1,TXIF ; Check if it's the UART TX interrupt
\r
404 goto IRQNotTXIF ; No it's not, check the other sources
\r
405 bsf STATUS,RP0 ; Reg bank 1
\r
406 btfss PIE1,TXIE ; As TXIF is set as long as nothing gets sent, is the interrupt actually enabled?
\r
407 goto IRQTXIFDisabled
\r
408 ;----------------------------------------------------------------
\r
409 ; This is the UART TX routine, gets called when TXIE has been set and the TX load register is empty
\r
416 bcf STATUS,RP0 ; Make sure we're going back to reg bank 0
\r
420 ; Finally restore CPU state and return from the ISR
\r
422 ; If I have to save the FSR in the beginning I also need to restore it here...
\r
425 ; movwf PCLATH ; Restore PCLATH
\r
427 movwf STATUS ; Restore STATUS
\r
429 swapf IRQW,w ; Restore W
\r
430 retfie ; Interrupt return
\r
432 ;----------------------------------------------------------------
\r
433 ; IRQCheckINT - This part is the actual Unilink tranceiver, have to call it often as there are only ~20 spare cycles
\r
434 ; (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
437 btfss INTCON,INTF ; Check if it's the INT edge interrupt (Unilink CLK)
\r
438 return ; No it's not, return again after only four cycles
\r
440 ; 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
441 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction
\r
442 ; 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
443 ; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of
\r
444 ; 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
445 ; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX
\r
446 ; 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
447 ; here, otherwise collisions will occur..
\r
448 ; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an
\r
449 ; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here
\r
450 ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..)
\r
452 movlw 8 ; Loop through the 8 bits
\r
454 movf UnilinkTXRX,w ; Get the pointer
\r
455 movwf FSR ; Store it to make use of indirect addressing
\r
458 btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out)
\r
459 goto IRQINTTristate ; Bit is low, we should tristate bit
\r
460 bcf PORTC,3 ; Otherwise set DATA bit low
\r
461 bsf STATUS,RP0 ; Select high regs
\r
462 bcf TRISC,3 ; And pull low (now it's an output)
\r
463 bcf STATUS,RP0 ; Back to regbank 0
\r
464 goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in
\r
467 bsf STATUS,RP0 ; Select high regs
\r
468 bsf TRISC,3 ; Force the bit to be tristated
\r
469 bcf STATUS,RP0 ; Back to regbank 0
\r
472 btfss PORTC,2 ; Check for BUSON
\r
474 btfsc PORTB,0 ; Wait for clock to go low
\r
475 goto IRQINTCLKWaitLow
\r
478 btfss PORTC,3 ; Test DATA
\r
479 setc ; Set carry if data is LOW (data is inverted!)
\r
480 rlf INDF,f ; Shift it into the "accumulator"
\r
482 decfsz DataCount,f ; Loop once more perhaps?
\r
483 goto IRQINTCLKWaitHigh ; Yes, again!
\r
484 goto IRQINTRecvDone ; No it's done, don't check for clock to go high again
\r
487 btfss PORTC,2 ; Check for BUSON
\r
489 btfss PORTB,0 ; Wait for clock to go high
\r
490 goto IRQINTCLKWaitHigh
\r
491 goto IRQINTBitSet ; Loop again
\r
493 ; Successfully received a byte here, run it through a state machine to figure out what to do
\r
494 ; (several possibilites exists here):
\r
495 ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero
\r
496 ; If receive counter is zero and the received byte is a zero byte, discard it
\r
497 ; Otherwise store the byte in our receive buffer and increment receive counter
\r
498 ; 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
499 ; 00 = short 6 byte packet
\r
500 ; 10 = medium 11 byte packet
\r
501 ; 11 = long 16 byte packet
\r
502 ; Update the receive length byte accordingly
\r
503 ; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing
\r
504 ; the packet and take appropriate action.
\r
507 clrf SlaveBreakState ; First of all, clear the break state - this got in the way, restart detection..
\r
510 call BootTXB ; Send the byte to the serial port
\r
512 movf UnilinkTXRX,w ; Find out which byte # that was received
\r
514 bnz IRQINTRecvNotFirst ; Not the first byte
\r
515 movf UnilinkRAD,w ; Get the first byte received
\r
516 bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter
\r
518 incf UnilinkTXRX,f ; Increment address
\r
520 movf UnilinkTXRX,w ; Get the byte position again
\r
521 andlw 0fh ; Only lower 4 bits of interest
\r
522 xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet)
\r
523 bnz IRQINTRecvNotCMD1 ; No, skip the length code for now
\r
524 movlw 6 ; Assume it's a short packet
\r
525 btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long
\r
526 goto IRQINTRecvShort ; Nope, it's a short packet
\r
527 addlw 5 ; OK, it's long or medium at least
\r
528 btfsc INDF,6 ; Test for long
\r
529 addlw 5 ; Yep, it's a long packet
\r
531 movwf UnilinkCmdLen ; Store the length
\r
534 movf UnilinkTXRX,w ; Get the byte position
\r
535 xorwf UnilinkCmdLen,w ; XOR with the calculated command length
\r
536 andlw 0fh ; and mask - this results in a zero result when finished receiving
\r
537 bnz IRQINTRecvIncomplete ; Packet not ready yet
\r
539 ; Here a packet is actually received, should check the checksum(s) now
\r
541 movf UnilinkRAD,w ; QnD checksum check
\r
543 addwf UnilinkCMD1,w
\r
544 addwf UnilinkCMD2,w
\r
545 xorwf UnilinkParity1,w ; This should be zero
\r
546 bnz IRQINTParseComplete ; Don't allow packet parsing on corrupt packets
\r
548 btfss UnilinkCMD1,7 ; Test whether there's more parity to check (medium or long packet)
\r
549 goto IRQINTParser ; No, skip directly to parsing logic
\r
551 movf UnilinkParity1,w ; QnD checksum check for the remaining part of the packet
\r
552 addwf UnilinkData1,w
\r
553 addwf UnilinkData2,w
\r
554 addwf UnilinkData3,w
\r
555 addwf UnilinkData4,w
\r
557 btfss UnilinkCMD1,6 ; Test for a long packet
\r
558 goto IRQINTBypassLongPacket
\r
560 xorwf UnilinkParity2M,w ; Fix for the medium packet parity check a few lines down...
\r
561 addwf UnilinkData5,w
\r
562 addwf UnilinkData6,w
\r
563 addwf UnilinkData7,w
\r
564 addwf UnilinkData8,w
\r
565 addwf UnilinkData9,w
\r
566 xorwf UnilinkParity2,w ; This should be zero when xor:ed with the Parity2M
\r
568 IRQINTBypassLongPacket
\r
569 xorwf UnilinkParity2M,w ; This should be zero for valid medium and long packets
\r
570 bnz IRQINTParseComplete
\r
574 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here
\r
575 ; (there won't be any more communication for at least another 4.8ms))
\r
577 ; Unilink command parser:
\r
579 ; Check for CMD1 = 01h (System bus commands)
\r
582 bnz IRQINTParseNot01
\r
584 ; Check for 01 00 (Bus Re-Initialization)
\r
586 bnz IRQINTParseNot0100
\r
588 call ClearUnilinkStatus ; Clear everything Unilink (ID, BUSON_OUT)
\r
590 incf UnilinkReInits,f ; increment the debug counter
\r
592 goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though)
\r
596 ; Check for 01 02 (Anyone)
\r
599 bnz IRQINTParseNot0102
\r
601 movf UnilinkID,w ; Do I have an ID already?
\r
602 bnz IRQINTParseComplete ; Yep, I don't want another one!
\r
604 call ClearUnilinkBuffer ; Zero it out completely
\r
606 movlw 10h ; Sending to Master
\r
607 addwf UnilinkParity1,f
\r
609 movlw 0d0h ; I'm in the MD changer group
\r
610 addwf UnilinkParity1,f
\r
612 movlw 8ch ; Device discovery command reply
\r
613 addwf UnilinkParity1,f
\r
616 addwf UnilinkParity1,f
\r
619 movf UnilinkParity1,w
\r
620 movwf UnilinkParity2M
\r
622 movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??
\r
623 addwf UnilinkParity2M,f
\r
626 addwf UnilinkParity2M,f
\r
629 addwf UnilinkParity2M,f
\r
631 movlw 0a0h ; 00?? 0a0=10 disc?
\r
632 addwf UnilinkParity2M,f
\r
635 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
639 ; Check for 01 12 (Time poll)
\r
642 bnz IRQINTParseNot0112
\r
645 xorwf UnilinkID,w ; Is it for me?
\r
646 bnz IRQINTParseNot0112 ; Nope
\r
648 call ClearUnilinkBuffer
\r
649 movlw 10h ; Sending to Master
\r
650 addwf UnilinkParity1,f
\r
652 movf UnilinkID,w ; This is my ID
\r
653 addwf UnilinkParity1,f
\r
656 addwf UnilinkParity1,f
\r
659 movlw 80h ; Idle unless selected
\r
660 btfsc UnilinkSelected,7
\r
663 addwf UnilinkParity1,f
\r
665 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
669 ; Check for 01 13 (Request Time poll)
\r
672 bnz IRQINTParseNot0113
\r
675 xorwf UnilinkID,w ; Is it for me?
\r
676 bnz IRQINTParseNot0113 ; Nope
\r
678 btfss DisplayStatus,7 ; If not displaying, skip this
\r
679 goto IRQINTParseComplete
\r
681 call ClearUnilinkBuffer
\r
683 movlw 70h ; Sending to Display Group
\r
684 addwf UnilinkParity1,f
\r
686 movf UnilinkID,w ; This is my ID
\r
687 addwf UnilinkParity1,f
\r
690 movf DisplayStatus,w
\r
691 xorlw 80h ; First slave break?
\r
692 bnz IRQINTParse0113Not80
\r
695 addwf UnilinkParity1,f
\r
698 addwf UnilinkParity1,f
\r
701 movf UnilinkParity1,w ; Carry the parity forward
\r
702 movwf UnilinkParity2M
\r
705 addwf UnilinkParity2M,f
\r
708 addwf UnilinkParity2M,f
\r
711 addwf UnilinkParity2M,f
\r
719 addwf UnilinkParity2M,f
\r
722 ; clrf DisplayStatus
\r
723 ; goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
725 goto IRQINTParse0113Complete
\r
727 IRQINTParse0113Not80
\r
729 movf DisplayStatus,w
\r
730 xorlw 81h ; Second slave break?
\r
731 bnz IRQINTParse0113Not81
\r
733 movlw 0cdh ; Disc name
\r
734 addwf UnilinkParity1,f
\r
737 addwf UnilinkParity1,f
\r
740 movf UnilinkParity1,w ; Carry the parity forward
\r
741 movwf UnilinkParity2
\r
744 addwf UnilinkParity2,f
\r
747 addwf UnilinkParity2,f
\r
750 addwf UnilinkParity2,f
\r
753 addwf UnilinkParity2,f
\r
756 addwf UnilinkParity2,f
\r
759 addwf UnilinkParity2,f
\r
762 addwf UnilinkParity2,f
\r
765 addwf UnilinkParity2,f
\r
768 addwf UnilinkParity2,f
\r
770 goto IRQINTParse0113Complete
\r
772 IRQINTParse0113Not81
\r
774 movf DisplayStatus,w
\r
775 xorlw 82h ; Third slave break?
\r
776 bnz IRQINTParse0113Not82
\r
778 movlw 0cdh ; Disc name
\r
779 addwf UnilinkParity1,f
\r
782 addwf UnilinkParity1,f
\r
785 movf UnilinkParity1,w ; Carry the parity forward
\r
786 movwf UnilinkParity2
\r
789 addwf UnilinkParity2,f
\r
792 addwf UnilinkParity2,f
\r
795 addwf UnilinkParity2,f
\r
798 addwf UnilinkParity2,f
\r
801 addwf UnilinkParity2,f
\r
804 addwf UnilinkParity2,f
\r
807 addwf UnilinkParity2,f
\r
810 addwf UnilinkParity2,f
\r
813 addwf UnilinkParity2,f
\r
815 goto IRQINTParse0113Complete
\r
817 IRQINTParse0113Not82
\r
819 movf DisplayStatus,w
\r
820 xorlw 83h ; Fourth slave break?
\r
821 bnz IRQINTParse0113Not83
\r
823 movlw 0c9h ; Track name 1
\r
824 addwf UnilinkParity1,f
\r
827 addwf UnilinkParity1,f
\r
830 movf UnilinkParity1,w ; Carry the parity forward
\r
831 movwf UnilinkParity2
\r
834 addwf UnilinkParity2,f
\r
837 addwf UnilinkParity2,f
\r
840 addwf UnilinkParity2,f
\r
843 addwf UnilinkParity2,f
\r
846 addwf UnilinkParity2,f
\r
849 addwf UnilinkParity2,f
\r
852 addwf UnilinkParity2,f
\r
855 addwf UnilinkParity2,f
\r
858 addwf UnilinkParity2,f
\r
861 goto IRQINTParse0113Complete
\r
863 IRQINTParse0113Not83
\r
865 movf DisplayStatus,w
\r
866 xorlw 84h ; Fifth slave break?
\r
867 bnz IRQINTParse0113Not84
\r
869 movlw 0c9h ; Track name (2)
\r
870 addwf UnilinkParity1,f
\r
873 addwf UnilinkParity1,f
\r
876 movf UnilinkParity1,w ; Carry the parity forward
\r
877 movwf UnilinkParity2
\r
880 addwf UnilinkParity2,f
\r
882 movf TrackName+10,w
\r
883 addwf UnilinkParity2,f
\r
885 movf TrackName+11,w
\r
886 addwf UnilinkParity2,f
\r
888 movf TrackName+12,w
\r
889 addwf UnilinkParity2,f
\r
891 movf TrackName+13,w
\r
892 addwf UnilinkParity2,f
\r
894 movf TrackName+14,w
\r
895 addwf UnilinkParity2,f
\r
897 movf TrackName+15,w
\r
898 addwf UnilinkParity2,f
\r
901 addwf UnilinkParity2,f
\r
904 addwf UnilinkParity2,f
\r
906 goto IRQINTParse0113Complete
\r
908 IRQINTParse0113Not84
\r
910 movf DisplayStatus,w
\r
911 xorlw 85h ; Sixth slave break?
\r
912 bnz IRQINTParse0113Not85
\r
915 addwf UnilinkParity1,f
\r
918 addwf UnilinkParity1,f
\r
921 movf UnilinkParity1,w ; Carry the parity forward
\r
922 movwf UnilinkParity2M
\r
925 addwf UnilinkParity2M,f
\r
928 addwf UnilinkParity2M,f
\r
931 addwf UnilinkParity2M,f
\r
939 addwf UnilinkParity2M,f
\r
942 clrf DisplayStatus ; for now!
\r
943 goto IRQINTParse0113Complete
\r
945 IRQINTParse0113Not85
\r
947 incf DisplayStatus,f ; Skip step one for now
\r
948 goto IRQINTParseComplete
\r
950 IRQINTParse0113Complete
\r
952 incf DisplayStatus,f ; Increment display state counter
\r
953 ; bsf DisplayStatus,7
\r
955 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
959 ; Check for 01 15 (Who sent the slave break?)
\r
962 bnz IRQINTParseNot0115
\r
964 btfss DisplayStatus,7 ; First of all check if there should be anything displayed
\r
965 goto IRQINTParseComplete ; No, not at this time
\r
967 call ClearUnilinkBuffer
\r
968 movlw 10h ; Sending to Master
\r
969 addwf UnilinkParity1,f
\r
971 movlw 18h ; Broadcast address sending in this special case
\r
972 addwf UnilinkParity1,f
\r
974 movlw 82h ; Who wants to talk reply command
\r
975 addwf UnilinkParity1,f
\r
980 addwf UnilinkParity1,f
\r
983 movf UnilinkParity1,w ; Carry the parity forward
\r
984 movwf UnilinkParity2M
\r
988 addwf UnilinkParity2M,f
\r
992 addwf UnilinkParity2M,f
\r
996 addwf UnilinkParity2M,f
\r
1000 addwf UnilinkParity2M,f
\r
1001 movwf UnilinkData4
\r
1003 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
1005 ;******************************************************************************
\r
1006 ; Bit frig - works out which bit to set in the response to Master Poll
\r
1007 ; This is taken more or less verbatim from Simon Woods' GNUnilink code!
\r
1009 ; W register is input of which stage you are on (0x00, 0x20, 0x40 etc)
\r
1010 ; and is returned with the byte to write (0x00 if wrong stage).
\r
1013 xorwf UnilinkBit, 0
\r
1014 andlw 0xe0 ; Strip off low bits
\r
1016 btfsc STATUS, Z ; Do we have a hit?
\r
1023 btfss UnilinkBit, 4 ; Do we need to swap nybbles?
\r
1024 goto Bit_Frig_Swap
\r
1026 movf UnilinkBit, 0
\r
1031 swapf UnilinkBit, 0
\r
1035 IRQINTParseNot0115
\r
1039 ; Check for CMD1 = 02h (Appoint)
\r
1040 movf UnilinkCMD1,w
\r
1042 bnz IRQINTParseNot02
\r
1044 movf UnilinkID,w ; Do I have an ID already?
\r
1045 bnz IRQINTParseComplete ; Yep, I don't want another one!
\r
1047 movf UnilinkRAD,w ; So I don't have any ID yet, see what the master is trying to set
\r
1048 andlw 0f0h ; Check the device group
\r
1049 xorlw 0d0h ; Verify it's a MD changer
\r
1050 bnz IRQINTParseComplete ; No, something else, skip this
\r
1052 movf UnilinkRAD,w ; Get the ID the master has given me
\r
1053 movwf UnilinkID ; Store my id
\r
1054 movf UnilinkCMD2,w ; Get the bitmask
\r
1055 movwf UnilinkBit ; And store it (this is needed when doing slave breaks and actually responding)
\r
1057 call ClearUnilinkBuffer
\r
1058 movlw 10h ; Sending to Master
\r
1059 addwf UnilinkParity1,f
\r
1061 movf UnilinkID,w ; This is my ID
\r
1062 addwf UnilinkParity1,f
\r
1064 movlw 8ch ; Device discovery command again
\r
1065 addwf UnilinkParity1,f
\r
1068 addwf UnilinkParity1,f
\r
1071 movf UnilinkParity1,w
\r
1072 movwf UnilinkParity2M ; That's the parity when sending medium messages
\r
1075 addwf UnilinkParity2M,f
\r
1076 movwf UnilinkData1
\r
1077 movlw 0a8h ; My internal MD sends 1c here... (external/internal difference)
\r
1078 addwf UnilinkParity2M,f
\r
1079 movwf UnilinkData2
\r
1081 addwf UnilinkParity2M,f
\r
1082 movwf UnilinkData3
\r
1083 movlw 0a0h ; 0a0=10disc
\r
1084 addwf UnilinkParity2M,f
\r
1085 movwf UnilinkData4
\r
1087 bsf BUSON_OUT_BIT ; Now activate the cascade BUSON pin, to allow others after us to be discovered
\r
1089 goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet
\r
1093 ; Check for CMD1 = 80h (Display button)
\r
1094 movf UnilinkCMD1,w
\r
1096 bnz IRQINTParseNot80
\r
1098 movf UnilinkID,w ; Check if I'm currently selected
\r
1099 xorwf UnilinkCurID,w
\r
1100 skpnz ; No, skip this command
\r
1101 ; bsf DisplayStatus,7 ; Make sure we update the display again
\r
1103 movwf DisplayStatus
\r
1104 goto IRQINTParseComplete
\r
1108 ; Check for CMD1 = 87h (Power control)
\r
1109 movf UnilinkCMD1,w
\r
1111 bnz IRQINTParseNot87
\r
1113 ; This part could use some more packet sniffing (really), it's sketchy to say the least.. :(
\r
1114 ; 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
1115 ; 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
1116 ; Also interesting is that the exact same commands gets sent when pressing the power-off button, this makes slaves pause
\r
1117 ; playing, and then a few seconds later the unit shuts down with the following command:
\r
1118 ; 18 10 87 22 PP 02 00 80 00 PP ZZ or
\r
1119 ; 18 10 87 22 PP 12 00 80 00 PP ZZ depending on the color of the backlight
\r
1120 ; This makes me think that bit 3 in CMD2 reflects the actual power state of the headunit (actually sleeping or bus active)
\r
1121 ; Anyway I use this to set/clear the RI pin used for WakeOnRing on my laptop
\r
1122 ; Also I de-select to make everything pause and clear the display status (if we're doing slave breaks after power status
\r
1123 ; the headunit will never enter sleep!)
\r
1124 ; From what I have gathered the bit mapping of DATA1 is as follows:
\r
1126 ; X - Backlight color changed if 1
\r
1128 ; X - Backlight color, 0=Amber, 1=Green
\r
1129 ; X - Dimmer setting changed if 1
\r
1130 ; X X - Dimmer setting, 01=Dimmer Auto, 10=Dimmer On, 00=Dimmer Off, 11=???
\r
1131 ; X - Beep setting, 0=Beep on(!), 1=Beep off
\r
1133 ; Also bit field of CMD2 for now:
\r
1135 ; X X - These two bits are set when changing color, beep etc, but not when actually powering the system on or off???
\r
1136 ; X - Always set to 1 on my headunit
\r
1137 ; X - Always set to 0 on my headunit
\r
1138 ; 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
1139 ; X - Always set to 0 on my headunit
\r
1140 ; X - Always set to 1 on my headunit
\r
1141 ; X - Always set to 0 on my headunit
\r
1143 ; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)
\r
1144 btfsc UnilinkCMD2,3
\r
1145 goto IRQINTParse87PowerOn
\r
1147 bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels)
\r
1148 goto IRQINTParseComplete
\r
1150 IRQINTParse87PowerOn
\r
1151 bcf RS232_RI_BIT ; Clear this to make RI pin go high (waking the computer)
\r
1153 btfsc UnilinkCMD2,7 ; Test high bit if it's just a "set" command, if yes don't clear status
\r
1154 goto IRQINTParseComplete
\r
1156 bcf UnilinkSelected,7 ; Also de-select us (this gets sent when powering off but before the actual power down)
\r
1157 clrf DisplayStatus
\r
1159 goto IRQINTParseComplete
\r
1163 ; Check for CMD1 = 90h (Display/DSP info, volume etc.)
\r
1164 movf UnilinkCMD1,w
\r
1166 bnz IRQINTParseNot90
\r
1168 ; Check for 90 10 (Current Volume)
\r
1169 movf UnilinkCMD2,w
\r
1171 bnz IRQINTParseNot9010
\r
1173 movf UnilinkData1,w ; Store current volume setting
\r
1174 movwf UnilinkAttenuation
\r
1176 goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though)
\r
1178 IRQINTParseNot9010
\r
1183 ; Check for CMD1 = f0h (Source Select)
\r
1184 movf UnilinkCMD1,w
\r
1186 bnz IRQINTParseNotF0
\r
1188 movf UnilinkCMD2,w
\r
1189 movwf UnilinkCurID ; Store it for display and debugging
\r
1191 xorwf UnilinkID,w ; Check if it's selecting me
\r
1192 bnz IRQINTParseF0Deselect
\r
1194 bsf UnilinkSelected,7 ; Now we're selected
\r
1195 ; bsf DisplayStatus,7
\r
1197 movwf DisplayStatus
\r
1198 goto IRQINTParseComplete
\r
1200 IRQINTParseF0Deselect
\r
1202 bcf UnilinkSelected,7 ; Now we're de-selected
\r
1203 bcf DisplayStatus,7
\r
1204 goto IRQINTParseComplete
\r
1208 IRQINTParseComplete
\r
1210 ; The code ends up here when parsing is complete and it's not interested in sending any reply back to the master
\r
1211 ; (that's why we clear out all the packet buffer bytes)
\r
1213 call ClearUnilinkBuffer
\r
1215 IRQINTParseBypassClear
\r
1217 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
1218 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
1220 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
1223 IRQINTRecvIncomplete
\r
1225 IRQINTRecvNullByte
\r
1227 ; movwf DataStore ; Store it so the non-irq code can snoop
\r
1230 bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again
\r
1235 ;----------------------------------------------------------------
\r
1236 ; ClearUnilinkStatus - Zeroes out the Unilink state (used when initializing)
\r
1238 ClearUnilinkStatus
\r
1240 clrf UnilinkID ; Clear the existing Unilink ID, if any
\r
1241 clrf UnilinkCurID ; Clear the currently selected ID as well
\r
1242 bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID
\r
1243 clrf DisplayStatus ; No crazy display updates when resetting.. :)
\r
1244 clrf UnilinkSelected ; We're not selected anymore
\r
1246 bsf STATUS,RP0 ; Reg bank 1
\r
1247 bsf DATA_BIT ; Make sure data is tristated
\r
1248 bcf STATUS,RP0 ; Reg bank 0
\r
1250 movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer
\r
1251 movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD
\r
1253 clrf UnilinkCmdLen ; No command length while waiting for a new packet
\r
1255 clrf SlaveBreakState ; Slave Break Processing has to start all over
\r
1259 ;----------------------------------------------------------------
\r
1260 ; ClearUnilinkBuffer - Zeroes out the Unilink packet buffer
\r
1262 ClearUnilinkBuffer
\r
1264 ; TODO: Replace this with an FSR access to save space and make the code neater
\r
1269 clrf UnilinkParity1
\r
1279 clrf UnilinkParity2
\r
1285 subtitl "Main loop"
\r
1288 ;----------------------------------------------------------------
\r
1289 ; Main program begins here. [Called after bootloader, lcdinit and irqinit...]
\r
1290 ; Here all other house keeping tasks are performed, like displaying info on the LCD..
\r
1293 movlw high LookUp ; Set the high PC bits to indicate data lookup page
\r
1296 movlw 0ffh ; Set infinite attenuation to begin with
\r
1297 movwf UnilinkAttenuation
\r
1303 clrf UnilinkReInits ; Clear the bus re-initialization counter
\r
1305 bsf STATUS,RP0 ; Reg bank 1
\r
1306 movlw 080h ; Right adjusted A/D, all analog inputs, no Vrefs
\r
1310 movlw 081h ; Activate A/D, ch 0, Fosc/32 (for 20MHz operation)
\r
1313 bsf ADCON0,2 ; Start the first A/D operation
\r
1315 movlw 8 ; Display page timing (approx 8/sec)
\r
1316 movwf DisplayCounter
\r
1318 bcf LCD_RS_BIT ; LCD Command mode
\r
1319 movlw 80h ; DisplayRam 0
\r
1323 movlw low DefaultText1
\r
1327 call TxLCD8BLoop ; Send 80 bytes to the LCD
\r
1331 bcf LCD_RS_BIT ; LCD Command mode
\r
1332 movlw 80h ; DisplayRam 0
\r
1337 movf Counter,w ; Debug timer
\r
1338 btfsc PORTA,4 ; Test RST
\r
1343 movf SlaveBreakState,w
\r
1345 btfsc PORTB,0 ; Test CLK
\r
1350 btfsc PORTC,2 ; Test BUSON-IN
\r
1355 btfsc PORTC,3 ; Test DATA
\r
1359 movf UnilinkCmdLen,w
\r
1360 bz MainDontPrintCmd
\r
1367 ; UnilinkID @ 13-14
\r
1368 ; UnilinkAttenuation @ 16-17
\r
1369 ; UnilinkSelected @ 28-29
\r
1370 ; UnilinkReInits @ 38,39
\r
1371 ; UnilinkCurID @ 54-55
\r
1372 ; DisplayStatus @ 62-63
\r
1373 ; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)
\r
1375 bcf LCD_RS_BIT ; LCD Command mode
\r
1376 movlw 80h+13 ; DisplayRam 13
\r
1383 bcf LCD_RS_BIT ; LCD Command mode
\r
1384 movlw 80h+16 ; DisplayRam 16
\r
1388 movf UnilinkAttenuation,w
\r
1391 bcf LCD_RS_BIT ; LCD Command mode
\r
1392 movlw 80h+28 ; DisplayRam 28
\r
1396 movf UnilinkSelected,w
\r
1399 bcf LCD_RS_BIT ; LCD Command mode
\r
1400 movlw 80h+38 ; DisplayRam 38
\r
1404 movf UnilinkReInits,w
\r
1407 bcf LCD_RS_BIT ; LCD Command mode
\r
1408 movlw 80h+40h+14 ; DisplayRam 54
\r
1412 movf UnilinkCurID,w
\r
1415 bcf LCD_RS_BIT ; LCD Command mode
\r
1416 movlw 80h+40h+22 ; DisplayRam 62
\r
1420 movf DisplayStatus,w
\r
1423 btfsc ADCON0,2 ; Test if A/D is ready
\r
1424 goto MainADNotReady
\r
1427 movf ADRESL,w ; Add to our result
\r
1433 addwf NumH,f ; And the high byte
\r
1437 skpc ; When this overflows we know there are 8 samples collected
\r
1438 goto MainADStartAD
\r
1440 ; Now shift the added results two steps down (/4) as there are 8 added samples here, and filter high bits
\r
1451 bnz MainADSkipDisplay
\r
1453 bcf LCD_RS_BIT ; LCD Command mode
\r
1454 movlw 80h+40h+26 ; DisplayRam 66
\r
1480 bsf ADCON0,2 ; Start a new conversion
\r
1484 ; This part handles display "scroll" by shifting one screen at a time
\r
1486 btfss Counter,7 ; Test high bit
\r
1487 goto MainCounterLow
\r
1489 ; So bit is high, set high bit of displaycounter as well...
\r
1490 bsf DisplayCounter,7
\r
1491 goto MainSkipScroll
\r
1494 ; OK, bit is low, now figure out whether it was high or low last time -> check high bit of DisplayCounter
\r
1495 btfss DisplayCounter,7
\r
1496 goto MainSkipScroll
\r
1498 bcf DisplayCounter,7 ; Clear the high bit to allow countdown to commence
\r
1499 movf Counter,w ; Load it
\r
1501 goto MainSkipScroll
\r
1502 decfsz DisplayCounter,f
\r
1503 goto MainSkipScroll
\r
1506 movwf DisplayCounter
\r
1508 bcf LCD_RS_BIT ; LCD Command mode
\r
1509 movlw 18h ; Display shift Left
\r
1510 call TxLCDB ; Shift it 8 positions
\r
1522 ; Display scroll part ends here...
\r
1527 ;----------------------------------------------------------------
\r
1528 ; IRQInit - Sets up the IRQ Handler
\r
1529 ; 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
1530 ; Also enable INT interrupts for Unilink CLK processing
\r
1534 call ClearUnilinkStatus
\r
1535 call ClearUnilinkBuffer
\r
1537 ; Fix the output state of RI and BUSON_OUT to a safe default
\r
1539 bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output)
\r
1540 bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first
\r
1542 movlw 06h ; Timer2 enabled + 1/16 prescaler
\r
1545 bsf STATUS,RP0 ; Reg bank 1
\r
1547 movlw 09ch ; Timer PR2 reg giving 2000 interrupts per second
\r
1550 bcf RS232_RI_BIT ; Both bits should be outputs
\r
1551 bcf BUSON_OUT_BIT ;
\r
1553 ; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use...
\r
1554 ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge
\r
1556 bsf INTCON,INTE ; Enable the RB0/INT
\r
1557 bsf INTCON,PEIE ; Enable the peripheral interrupts
\r
1558 bsf PIE1,TMR2IE ; Enable the Timer2 peripheral interrupt
\r
1559 bsf PIE1,RCIE ; Enable the UART receive interrupt
\r
1560 bsf INTCON,GIE ; Enable global interrupts
\r
1562 bsf TXSTA,TXEN ; Enable UART TX
\r
1564 bcf STATUS,RP0 ; Back to bank 0
\r
1566 bsf RCSTA,SPEN ; Enable serial port
\r
1567 bsf RCSTA,CREN ; Enable UART RX
\r
1571 ;----------------------------------------------------------------
\r
1572 ; Initialize LCD Controller...
\r
1575 clrf PORTB ; First clear PortB data register
\r
1576 bsf STATUS,RP0 ; Reg bank 1
\r
1577 movlw 001h ; All but RB0 are outputs.
\r
1580 bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up
\r
1581 bcf STATUS,RP0 ; Restore Reg bank 0
\r
1583 ; This is a standard reset sequence for the LCD controller
\r
1585 movlw 170 ; Need to delay for at least 15ms, let's go for 17ms delay
\r
1588 movlw 3 ; Write 3 to the LCD
\r
1589 call TxLCD ; Send to LCD
\r
1590 movlw 60 ; Need to delay for at least 4.1ms, let's go for 6ms delay
\r
1593 movlw 3 ; Write 3 to the LCD
\r
1595 movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay
\r
1598 movlw 3 ; Write 3 to the LCD
\r
1600 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
1603 movlw 2 ; 4-bit interface requested
\r
1605 movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay
\r
1608 ; Reset sequence ends here
\r
1609 ; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits
\r
1611 movlw 28h ; Function Select + 4-bit bus + 2-line display
\r
1614 movlw 0ch ; Display Control + LCD On (No cursor)
\r
1617 movlw 01h ; Clear Display
\r
1620 movlw 06h ; Auto Increment cursor position
\r
1623 bsf LCD_RS_BIT ; Accept data
\r
1627 ;----------------------------------------------------------------
\r
1629 ; Sends two characters hex to the LCD
\r
1633 ; Original binary to 2-digit hex conversion from piclist.com, modified to fit here
\r
1663 ;----------------------------------------------------------------
\r
1665 ; Binary-to-BCD. Written by John Payson.
\r
1666 ; Taken from piclist.com - why re-invent the wheel when writing open-sourced code?
\r
1668 ; Enter with 16-bit binary number in NumH:NumL.
\r
1669 ; Exits with BCD equivalent in TenK:Thou:Hund:Tens:Ones.
\r
1672 BCDConvert: ; Takes number in NumH:NumL
\r
1673 ; Returns decimal in
\r
1674 ; TenK:Thou:Hund:Tens:Ones
\r
1676 andlw 0Fh ;*** PERSONALLY, I'D REPLACE THESE 2
\r
1677 addlw 0F0h ;*** LINES WITH "IORLW 11110000B" -AW
\r
1713 ; At this point, the original number is
\r
1714 ; equal to TenK*10000+Thou*1000+Hund*100+Tens*10+Ones
\r
1715 ; if those entities are regarded as two's compliment
\r
1716 ; binary. To be precise, all of them are negative
\r
1717 ; except TenK. Now the number needs to be normal-
\r
1718 ; ized, but this can all be done with simple byte
\r
1745 ;----------------------------------------------------------------
\r
1747 ; Send a string to the LCD.
\r
1752 movlw 80h ; DisplayRam 0
\r
1757 movlw 80h+40 ; DisplayRam 40 (row 2)
\r
1763 ;----------------------------------------------------------------
\r
1765 ; Send a string to the LCD.
\r
1768 ; movwf Icount ; Icount = W
\r
1770 movwf e_LEN ; Move to e_LEN
\r
1773 movf Icount,w ; get the byte
\r
1775 incf Icount,f ; ...else ++Icount (table index)
\r
1776 call TxLCDB ; Send out the byte
\r
1781 ;----------------------------------------------------------------
\r
1782 ; TxLCDB - send a byte to the LCD
\r
1785 movwf TxTemp ; Store byte to send for a while...
\r
1787 bcf temp,0 ; Clear my temp bit
\r
1788 btfss LCD_RS_BIT ; Check if we try the correct reg
\r
1791 bsf temp,0 ; Indicate RS change
\r
1795 call RxLCDB ; Receive byte from LCD, status reg
\r
1797 skpz ; If the bit was set, the zero flag is not
\r
1800 btfsc temp,0 ; If we had to clear RS reset it now
\r
1803 swapf TxTemp,w ; Hi nibble of data to send in lo w bits
\r
1804 call TxLCD ; Send them first...
\r
1805 movf TxTemp,w ; Then we have the low nibble in low w bits
\r
1806 call TxLCD ; And send that one as well
\r
1810 ;----------------------------------------------------------------
\r
1811 ; RxLCDB - recv a byte from the LCD
\r
1814 call RxLCD ; Receive the high nibble
\r
1816 swapf LCDWTmp,f ; Swap it back to file
\r
1817 call RxLCD ; Receive the low nibble
\r
1818 addwf LCDWTmp,w ; Put the nibbles together and return in W
\r
1822 ;----------------------------------------------------------------
\r
1823 ; TxLCD - send a nibble to the LCD
\r
1826 movwf LCDWTmp ; Write nibble to tmp
\r
1827 bcf LCD_DB4_BIT ; Clear previous data
\r
1828 bcf LCD_DB5_BIT ;
\r
1832 btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT
\r
1834 btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT
\r
1836 btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT
\r
1838 btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT
\r
1841 bsf LCD_E_BIT ; And set E to clock the data into the LCD module
\r
1842 nop ; Let it settle
\r
1843 bcf LCD_E_BIT ; And clear the Enable again.
\r
1844 return ; Returns without modifying W
\r
1846 ;----------------------------------------------------------------
\r
1847 ; RxLCD - recv a nibble from the LCD
\r
1850 clrw ; Clear W register, return data in lower 4 bits
\r
1852 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
1854 bsf LCD_DB4_BIT ; This sets the port bit as an input
\r
1859 bcf STATUS,RP0 ; Back at reg bank 0
\r
1861 bsf LCD_RW_BIT ; Set Read mode for the LCD
\r
1862 bsf LCD_E_BIT ; And set E to clock the data out of the LCD module
\r
1863 nop ; Let the bus settle
\r
1864 btfsc LCD_DB4_BIT ; Transfer a set port bit into W
\r
1866 btfsc LCD_DB5_BIT ; Transfer a set port bit into W
\r
1868 btfsc LCD_DB6_BIT ; Transfer a set port bit into W
\r
1870 btfsc LCD_DB7_BIT ; Transfer a set port bit into W
\r
1872 bcf LCD_E_BIT ; And clear the Enable again.
\r
1873 bcf LCD_RW_BIT ; Set Write mode for the LCD
\r
1875 bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed
\r
1877 bcf LCD_DB4_BIT ; Set the port as an output again
\r
1878 bcf LCD_DB5_BIT ;
\r
1882 bcf STATUS,RP0 ; Back at reg bank 0
\r
1884 return ; Returns with data in W
\r
1886 ;----------------------------------------------------------------------
\r
1887 ; Delay routines (non-interrupt based, therefore not even close to reliable)
\r
1888 ; W=10 gives ~ 1ms of delay
\r
1889 ; 1ms=5000 instructions wasted, 100us=500 cycles
\r
1890 ; Maximum time waited will be 256*100us=25.6ms
\r
1893 movwf Dcount ; Set delay counter, number of 100us periods to wait
\r
1896 movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more
\r
1897 movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount
\r
1899 decfsz Dcount2,f ; 1 cycle (or two when exiting the loop)
\r
1900 goto DelayInner ; 2 cycles
\r
1901 decfsz Dcount,f ; Now decrement number of 100us periods and loop again
\r
1906 ;----------------------------------------------------------------
\r
1907 ; Data can be stored between 600 and 6ffh...
\r
1912 ; UnilinkID @ 13-14
\r
1913 ; UnilinkAttenuation @ 16-17
\r
1914 ; UnilinkSelected @ 28-29
\r
1915 ; UnilinkReInits @ 38,39
\r
1916 ; UnilinkCurID @ 54-55
\r
1917 ; DisplayStatus @ 62-63
\r
1918 ; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)
\r
1921 DT "----- WJ", "MyID:xx ", "xx dB at", "Sel:xx B", "Inits:xx"
\r
1922 DT " Unilink", "CurID:xx", "t Dsp:xx", "atxx.xxV", " <WJ>"
\r
1931 DT "..",0,0,0,0,0,0
\r
1933 LookUp movwf PCL ; Go to it (this assumes PCLATH == 06h)
\r
1936 subtitl "Bootstrap/Bootloader code"
\r
1939 ;----------------------------------------------------------------------
\r
1940 ; Bootstrap code - Allows PIC to flash itself with data from the async port.
\r
1941 ; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory
\r
1942 ; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver
\r
1943 ; (one good way of solving that is to wait for the echo from the PIC before sending anything else)
\r
1944 ; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration
\r
1945 ; register can't be programmed. That means that any references to the config register in the hex file will be ignored.
\r
1947 ; Startup @9600bps
\r
1949 ; RAM usage for the bootstrap code
\r
1951 BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog
\r
1956 BootTimerL equ 79h
\r
1957 BootTimerM equ 78h
\r
1958 BootTimerH equ 77h
\r
1959 BootNumBytes equ 76h
\r
1960 BootDataVL equ 75h
\r
1961 BootDataVH equ 74h
\r
1962 BootHEXTemp equ 73h
\r
1964 org 738h ; Place the boot code at the top of memory (currently the loader is exactly 200 bytes)
\r
1967 bsf STATUS,RP0 ; Access bank 1
\r
1968 bsf TXSTA,TXEN ; Enable UART TX
\r
1969 movlw 31 ; Divisor for 9k6 @ 20MHz Fosc
\r
1970 movwf SPBRG ; Store
\r
1971 bcf STATUS,RP0 ; Back to bank 0
\r
1973 bsf RCSTA,SPEN ; Enable serial port
\r
1974 bsf RCSTA,CREN ; Enable UART RX
\r
1976 movlw low BootStartText ; Send boot banner to the serial port
\r
1979 ; movlw 0e8h ; Initialize timeout timer (e8 is about 3 secs)
\r
1980 movlw 0fdh ; Initialize timeout timer (fd is short enough to get the headunit to recognize us)
\r
1986 incf BootTimerL,f ; A 24-bit counter
\r
1991 skpnz ; When overflowing here..
\r
1992 goto BootReturn ; ..Exit boot loader, no keypress within timeout period, resume program
\r
1993 btfss PIR1,RCIF ; Wait for RX to complete
\r
1998 goto BootTimeout ; If it wasn't ESC, wait for another key
\r
2001 movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port
\r
2009 call BootRXB ; First find the ':'
\r
2012 goto BootLoop ; Loop until we find it!
\r
2014 call BootRXHEX ; Get one ASCII encoded byte (two chars)
\r
2015 movwf BootNumBytes ; This is the number of bytes to be programmed on the line
\r
2016 ; Maybe clear cary here?
\r
2017 rrf BootNumBytes,f ; Right shift because we're double addressing this 8-bit format
\r
2019 ; Note carry should be clear here as there cannot be odd number of bytes in this format
\r
2021 call BootRXHEX ; Receive AddrH
\r
2023 call BootRXHEX ; Receive AddrL
\r
2025 rrf BootAddrH,f ; Fix the addressing again
\r
2028 bcf BootBits,2 ; Assume we should program
\r
2029 bsf BootBits,1 ; And assume we should program flash not ee
\r
2032 xorlw 020h ; Check if it's configuration, which we can't program
\r
2033 skpnz ; Skip the bit set if it was false alarm
\r
2034 bsf BootBits,2 ; No programming for this line
\r
2036 xorlw 001h ; Also check if it's EEPROM memory (first xor 20h then 1 =21h)
\r
2037 skpnz ; Skip the bit set instr if not EE data address
\r
2038 bcf BootBits,1 ; We should program EE, will ignore the AddrH
\r
2040 call BootRXHEX ; Receive Record Type (must be 0 for real records)
\r
2041 skpz ; Check if zero
\r
2042 goto BootFlashComplete
\r
2045 call BootRXHEX ; Receive low-byte of data word
\r
2047 call BootRXHEX ; Receive high-byte of data word
\r
2050 btfsc BootBits,2 ; Check whether this line should be programmed at all
\r
2051 goto BootWriteSkip
\r
2053 bcf BootBits,0 ; Read mode first, verify if we actually have to write
\r
2056 xorwf BootDataL,f ; Compare and destroy DataL
\r
2057 movwf BootDataL ; Write new data to DataL
\r
2058 skpz ; Skip if no difference, have to check high byte as well
\r
2059 goto BootWrite ; Jump directly to write
\r
2062 xorwf BootDataH,f ; Compare
\r
2063 skpnz ; Skip if no difference, no programming necessary
\r
2064 goto BootWriteSkip
\r
2068 movwf BootDataH ; Have to put the new H byte data in as well
\r
2071 call BootEE ; Write directly into program mem
\r
2073 ; Here a verify can take place, the read-back results are now in DataL/H
\r
2077 incf BootAddrL,f ; Advance counter to next addr
\r
2079 incf BootAddrH,f ; And add to high byte if needed
\r
2081 decfsz BootNumBytes,f
\r
2089 movlw low BootRunText
\r
2092 bsf STATUS,RP0 ; Reg bank 1
\r
2094 btfss TXSTA,TRMT ; Wait for last things to flush
\r
2095 goto BootReturnWait
\r
2096 bcf TXSTA,TXEN ; Disable UART TX
\r
2097 bcf STATUS,RP0 ; Back to bank 0
\r
2099 bcf RCSTA,SPEN ; Disable serial port
\r
2100 bcf RCSTA,CREN ; Disable UART RX
\r
2102 return ; Return to code
\r
2104 ;----------------------------------------------------------------------
\r
2105 ; BootTXB - Sends one byte to the UART, waits for transmitter to become
\r
2106 ; free before sending
\r
2110 btfss PIR1,TXIF ; Wait for TX to empty
\r
2112 movwf TXREG ; Send the byte
\r
2115 ;----------------------------------------------------------------------
\r
2116 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated
\r
2119 movwf BootAddrL ; Store LSB of text pointer
\r
2120 movlw 07h ; MSB of pointer to the text (0700h in this boot loader)
\r
2122 movlw 02h ; Select "Read Program Memory" operation
\r
2125 call BootEE ; Lookup char (actually two packed into one word)
\r
2126 rlf BootDataL,w ; Shift the MSB out into carry (that's the 2nd char LSB)
\r
2127 rlf BootDataH,w ; Shift it into 2nd char
\r
2128 call BootTXB ; Send the high byte first
\r
2129 movf BootDataL,w ; Get the low byte
\r
2130 andlw 07fh ; Mask of the highest bit
\r
2131 skpnz ; Stop if zero
\r
2133 call BootTXB ; Send char
\r
2134 incf BootAddrL,f ; Increment pointer
\r
2135 goto BootTXStrLoop
\r
2137 ;----------------------------------------------------------------------
\r
2138 ; BootRXB - Receives one byte from the UART, waits if nothing available
\r
2142 btfss PIR1,RCIF ; Wait for RX to complete
\r
2144 movf RCREG,w ; Get the recvd byte
\r
2145 call BootTXB ; Echo to terminal
\r
2148 ;----------------------------------------------------------------------
\r
2149 ; BootRXHEXNibble - Receives one byte and converts it from ASCII HEX to binary
\r
2152 call BootRXB ; Receive nibble
\r
2154 ; This code is from piclist.com, really neat!
\r
2156 addlw -'A' ; Convert from BCD to binary nibble
\r
2157 skpc ; Test if if was 0-9 or A-F, skip if A-F
\r
2158 addlw 'A' - 10 - '0' ; It was numeric '0'
\r
2159 addlw 10 ; Add 10 (A get to be 0ah etc.)
\r
2163 ;----------------------------------------------------------------------
\r
2164 ; BootRXHEX - Receives two bytes from the UART, waits if nothing available
\r
2165 ; Decodes the bytes as ASCII hex and returns a single byte in W
\r
2168 call BootRXHEXNibble
\r
2170 swapf BootHEXTemp,f ; Swap it up to the high nibble
\r
2172 call BootRXHEXNibble
\r
2173 addwf BootHEXTemp,w ; And add the two nibbles together
\r
2176 ;----------------------------------------------------------------------
\r
2177 ; BootEE - Reads or writes EE or Flash memory, BootBits specify the
\r
2178 ; exact action to take. BootAddrL and BootAddrH has to be initialized
\r
2179 ; to the address of choice (0000-003fh for EE and 0000h-07ffh for flash
\r
2180 ; The data to be written has to be put in BootDataL and BootDataH, and
\r
2181 ; data will be to the same place when read back
\r
2184 bsf STATUS,RP1 ; Select bank 2 (RP0 must be 0)
\r
2186 movf BootAddrH,w ; Load desired address
\r
2190 movf BootDataH,w ; And load the data (only used when writing)
\r
2195 bsf STATUS,RP0 ; Go to bank 3
\r
2197 bsf EECON1,EEPGD ; Point to Program Flash mem
\r
2198 btfss BootBits,1 ; Test if that was correct or if we have to clear again
\r
2199 bcf EECON1,EEPGD ; Point to EE DATA mem
\r
2201 btfss BootBits,0 ; Check from read or write
\r
2202 goto BootEERD ; Skip the WR if we were going for a read
\r
2204 bsf EECON1,WREN ; Enable writes
\r
2208 movwf EECON2 ; Unlock write operation
\r
2209 bsf EECON1,WR ; And start a write cycle
\r
2211 btfsc EECON1,WR ; This executes for EE only not flash, waits for WR to finish
\r
2212 goto BootWRLoop ; These two instructions gets NOPed when flashing
\r
2214 bcf EECON1,WREN ; Finally disable writes again
\r
2215 ; Here we read the data back again, can be used as verify
\r
2217 bsf EECON1,RD ; Start a read cycle
\r
2218 nop ; Only necessary for flash read, same thing as when writing above
\r
2219 nop ; Except I could use the two words for something useful there.. :)
\r
2222 bcf STATUS,RP0 ; Back to bank 2
\r
2223 movf EEDATA,w ; Store our EE-data
\r
2227 bcf STATUS,RP1 ; And finally back to bank 0
\r
2231 ; 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
2233 DA "WJBoot - press ESC to flash\x00"
\r
2236 DA "\r\nSend INHX8 file now...\r\x00"
\r
2239 DA "\r\nExiting loader\r\x00"
\r
2241 ;----------------------------------------------------------------------
\r
2242 ; EE Data (64 bytes), located at 2100h
\r
2245 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
2246 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
2247 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
2248 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
2249 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
2250 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
2251 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r
2252 ; de 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh
\r