v0.9 - Interrupt driven UART RX for status updates, using raw unilink packets for...
[wj-unilink.git] / wj-uni.asm
index 08ddc24..c1623e0 100644 (file)
@@ -1,9 +1,30 @@
-       title   "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 2002-2003"\r
+       title   "PIC16F870 Unilink(R) Interface by Werner Johansson, wj@yodel.net"\r
        subtitl "Definitions"\r
        list    c=150,P=16F870,R=DEC,F=inhx8m\r
         include "p16f870.inc"           ; Standard equates & Macros\r
         ERRORLEVEL 1,-302               ; Get rid of those annoying 302 msgs!\r
 \r
+;******************************************************************************\r
+;\r
+;    This program is free software; you can redistribute it and/or modify\r
+;    it under the terms of the GNU General Public License as published by\r
+;    the Free Software Foundation; either version 2 of the License, or\r
+;    (at your option) any later version.\r
+;\r
+;    This program is distributed in the hope that it will be useful,\r
+;    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+;    GNU General Public License for more details.\r
+;\r
+;    You should have received a copy of the GNU General Public License\r
+;    along with this program; if not, write to the Free Software\r
+;    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+;\r
+;    Author: Werner Johansson (wj@yodel.net), with some code and ideas from \r
+;    Simon Woods' GNUnilink, radix conversion utilities from piclist.com\r
+;    and of course the reverse-engineered Unilink(R) command list\r
+;\r
+;******************************************************************************\r
 \r
 ;----------------------------------------------------------------\r
 ;  The Configuration Word\r
 ;----------------------------------------------------------------\r
 ;  TODO\r
 ;----------------------------------------------------------------\r
-;  Fix the DelayW routine so it actually delays W/10 ms...\r
-;  No checksum checking is done on incoming packets\r
-;  Investigate whether I actually have to save PCLATH in ISH, maybe save FSR?\r
-;  Move RS232 code into ISH\r
+;  Check how the slave should sense "power off" de-selection. Now it continues to play, as the slave doesn't stop requesting updates!\r
+;  Investigate whether I actually have to save PCLATH in ISH, maybe save FSR? - Not saving any of them for now\r
 ;  Check Overrun errors from the UART\r
-;  Implement lots of other Unilink commands (Text display, time display etc.)\r
 ;  Implement the Watchdog Timer (might be useful even though I haven't seen it hang yet..)\r
+;  Make the bit shift routine at the beginning of the ISR timeout if the clock suddenly stops (in the middle of a byte)\r
+;   (will keep it from hanging until the next bit gets clocked out, just ignore the faulty bits and carry on)\r
+;  Implement command b0 0x (change CD to x (1-a))?\r
+;  Implement command 08 10 (Tel Mute on) and 08 18 (Tel Mute off)?\r
 \r
 ;----------------------------------------------------------------\r
 ;  HISTORY\r
 ;----------------------------------------------------------------\r
 ;  Version\r
 ;\r
+;  0.9  Interrupt driven UART RX for status updates, using raw unilink packets for everything else, dynamic text\r
+;  0.8  Some text commands implemented, only static text for now though\r
+;  0.7  Debug Serial TX in ISR now, checksum check for incoming packets in place, A/D works, solved the master reset prob\r
+;        (by calling the INT handler from TMR2 ISR code (too much interrupt latency when transmitting)\r
+;  0.6  Some more LCD info and clean-up of the Unilink recovery code, some problems with master resetting :(\r
 ;  0.5  Issues slave breaks seemingly without hickups (!)\r
 ;  0.4  Some fixups in the bootstrap code (I actually had to put the PIC in my PICSTART Plus programmer again :))\r
 ;  0.3  Implementing more Unilink commands and RingIndicator control (to wake the computer from sleep)\r
@@ -47,8 +74,9 @@
 ;  RS-232 TX from computer connected to RC7/RX\r
 ;  RS-232 RX to computer connected to RC6/TX\r
 ;  RS-232 RI to computer connected to RC5\r
+;  B+ connected via trimmer and resistors to AN0 (divider approx 20k5/5k to give 20.48V maximum scale)\r
 ;\r
-;  This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now...\r
+;  This leaves RC0, RC1 and four analog inputs (AN1-AN4) free for now...\r
 \r
 #define BUSON_IN_BIT   PORTC,2\r
 #define DATA_BIT       PORTC,3\r
 ;----------------------------------------------------------------\r
 ;  FILE REGISTER USAGE\r
 ;----------------------------------------------------------------\r
-TrackName00    equ     20h             ; Buffer for TrackName\r
-TrackName01    equ     21h\r
-TrackName02    equ     22h\r
-TrackName03    equ     23h\r
-TrackName04    equ     24h\r
-TrackName05    equ     25h\r
-TrackName06    equ     26h\r
-TrackName07    equ     27h\r
-TrackName08    equ     28h\r
-TrackName09    equ     29h\r
-TrackName0a    equ     2ah\r
-TrackName0b    equ     2bh\r
-TrackName0c    equ     2ch\r
-TrackName0d    equ     2dh\r
-TrackName0e    equ     2eh\r
-TrackName0f    equ     2fh\r
-TrackName10    equ     30h\r
-TrackName11    equ     31h\r
-TrackName12    equ     32h\r
-TrackName13    equ     33h\r
-TrackName14    equ     34h\r
-TrackName15    equ     35h\r
-TrackName16    equ     36h\r
-TrackName17    equ     37h\r
-TrackName18    equ     38h\r
-TrackName19    equ     39h\r
-TrackName1a    equ     3ah\r
-TrackName1b    equ     3bh\r
-TrackName1c    equ     3ch\r
-TrackName1d    equ     3dh\r
-TrackName1e    equ     3eh\r
-TrackName1f    equ     3fh\r
-TrackName20    equ     40h\r
-TrackName21    equ     41h\r
-TrackName22    equ     42h\r
-TrackName23    equ     43h\r
-TrackName24    equ     44h\r
-TrackName25    equ     45h\r
-TrackName26    equ     46h\r
-TrackName27    equ     47h\r
-TrackName28    equ     48h\r
-TrackName29    equ     49h\r
-TrackName2a    equ     4ah\r
-TrackName2b    equ     4bh\r
-TrackName2c    equ     4ch\r
-TrackName2d    equ     4dh\r
-TrackName2e    equ     4eh\r
-TrackName2f    equ     4fh\r
+\r
+; Free from 20h-48h\r
+\r
+TrackName      equ     20h\r
+TracknameEnd   equ     3eh\r
+\r
+DiscName       equ     3fh\r
+DiscNameEnd    equ     48h\r
+\r
+CurDisc                equ     49h\r
+CurTrack       equ     4ah\r
+CurMins                equ     4bh\r
+CurSecs                equ     4ch\r
+\r
+RecvType       equ     4dh\r
+TargetPos      equ     4eh\r
+CurRecvPos     equ     4fh\r
 \r
 UnilinkRAD     equ     50h             ; Beginning of Unilink packet - the Receiving Address\r
 UnilinkTAD     equ     51h             ; Transmitter address\r
@@ -153,47 +150,25 @@ temp              equ     6dh
 Dcount         equ     6eh\r
 e_LEN          equ     6fh\r
 \r
-\r
 Counter                equ     70h\r
 DataCount      equ     71h             ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)\r
-DataStore      equ     72h             ; This is a kludge\r
-\r
+UnilinkCurID   equ     72h             ; This is a kludge\r
+DisplayCounter equ     73h\r
+UnilinkAttenuation     equ     74h     ; The amount of attenuation the volume control is currently set to\r
+NumH           equ     75h\r
+NumL           equ     76h\r
+TenK           equ     77h\r
+Thou           equ     78h\r
+Hund           equ     79h\r
+Tens           equ     7ah\r
+Ones           equ     7bh\r
+UnilinkReInits equ     7ch\r
 IRQPCLATH      equ     7dh             ; ISH storage\r
 IRQSTATUS      equ     7eh             ; Needs to be located in a shared area accessible from all register banks\r
 IRQW           equ     7fh             ; \r
 \r
-DiscName00     equ     0a0h            ; Buffer for DiscName\r
-DiscName01     equ     0a1h\r
-DiscName02     equ     0a2h\r
-DiscName03     equ     0a3h\r
-DiscName04     equ     0a4h\r
-DiscName05     equ     0a5h\r
-DiscName06     equ     0a6h\r
-DiscName07     equ     0a7h\r
-DiscName08     equ     0a8h\r
-DiscName09     equ     0a9h\r
-DiscName0a     equ     0aah\r
-DiscName0b     equ     0abh\r
-DiscName0c     equ     0ach\r
-DiscName0d     equ     0adh\r
-DiscName0e     equ     0aeh\r
-DiscName0f     equ     0afh\r
-DiscName10     equ     0b0h\r
-DiscName11     equ     0b1h\r
-DiscName12     equ     0b2h\r
-DiscName13     equ     0b3h\r
-DiscName14     equ     0b4h\r
-DiscName15     equ     0b5h\r
-DiscName16     equ     0b6h\r
-DiscName17     equ     0b7h\r
-DiscName18     equ     0b8h\r
-DiscName19     equ     0b9h\r
-DiscName1a     equ     0bah\r
-DiscName1b     equ     0bbh\r
-DiscName1c     equ     0bch\r
-DiscName1d     equ     0bdh\r
-DiscName1e     equ     0beh\r
-DiscName1f     equ     0bfh\r
+RecvBuf                equ     0a0h            ; Buffer for received data from PC (32 bytes)\r
+RecvBufEnd     equ     0bfh            ;\r
 \r
        subtitl "Startup"\r
        page\r
@@ -226,8 +201,241 @@ DiscName1f        equ     0bfh
 ;      clrf    PCLATH                  ; Go to low memory\r
 ; Maybe save FSR here as well (if there's a need for it in the non-ISR code)\r
 \r
+       call    IRQCheckINT             ; Implemented as a subroutine as there's a need to call it repeatedly from the other ISRs\r
+\r
+       btfss   PIR1,TMR2IF             ; Check if it's the TMR2 interrupt (0.5ms timing)\r
+       goto    IRQNotTMR2              ; No it's not, check the other sources\r
+\r
+       incf    Counter,f               ; Increment the general purpose counter (increments every 0.5ms)\r
+\r
+; Slave break opportunity detection here - the logic works as follows:\r
+; Look for a data low period of at least 5 ms (10 loops)\r
+; Look for a data high period of at least 2 ms (4 loops)\r
+; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 4ms (8 loops)\r
+; If a bit would be received (CLK activates) the packet handler automatically clears out the SlaveBreakState, which means start all over\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       btfsc   SlaveBreakState,5       ; Check if already pulling the data line low\r
+       goto    IRQTMR2SlaveBreak\r
+\r
+       btfsc   SlaveBreakState,7       ; Looking for low or high data\r
+       goto    IRQTMR2HighData\r
+       btfss   DATA_BIT                ; Looking for a low data line, if it's low, increment state, if it's high, reset state\r
+       goto    IRQTMR2LowDataOK\r
+       clrf    SlaveBreakState         ; Got a high data line while waiting for a low one, reset state\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       goto    IRQAfterTMR2            ; Leave ISR\r
+\r
+IRQTMR2HighData\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       btfsc   DATA_BIT                ; Looking for a high data line, if it's high - increment state, otherwise wait\r
+       goto    IRQTMR2HighDataOK\r
+       movlw   080h\r
+       btfsc   SlaveBreakState,6       ; Test the "first time around" bit\r
+       clrw                            ; Not the beginning of the state, have to restart the entire thing now, not just this state\r
+       andwf   SlaveBreakState,f       ; Mask out the 1 upper control bits and restart this state\r
+       goto    IRQAfterTMR2\r
+\r
+IRQTMR2HighDataOK\r
+IRQTMR2LowDataOK\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       bsf     SlaveBreakState,6       ; Set the "first time around" bit\r
+       \r
+       movf    SlaveBreakState,w\r
+       andlw   1fh\r
+\r
+       btfss   SlaveBreakState,7       ; Checking whether it's low or high\r
+       goto    IRQTMR2FoundLow\r
+\r
+       xorlw   4                       ; It's high now, and if 4 periods have passed we can activate slave break\r
+       skpz\r
+       goto    IRQAfterTMR2\r
+\r
+; Issue slave break here\r
+\r
+       clrf    SlaveBreakState\r
+\r
+;      incf    Counter,f               : Debug!\r
+\r
+       btfss   DisplayStatus,7         ; Only do this if high bit is set\r
+       goto    IRQAfterTMR2\r
+\r
+       movlw   20h\r
+       movwf   SlaveBreakState\r
+       bcf     DATA_BIT\r
+       bsf     STATUS,RP0\r
+       bcf     DATA_BIT\r
+       bcf     STATUS,RP0\r
+       goto    IRQAfterTMR2\r
+\r
+IRQTMR2FoundLow\r
+       xorlw   10\r
+       skpz\r
+       goto    IRQAfterTMR2\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       movlw   80h                     ; Prepare for state 2, looking for data line high\r
+       movwf   SlaveBreakState\r
+       goto    IRQAfterTMR2\r
+       \r
+IRQTMR2SlaveBreak\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       movf    SlaveBreakState,w\r
+       andlw   01fh\r
+       xorlw   8\r
+       skpz\r
+       goto    IRQAfterTMR2\r
+       bsf     STATUS,RP0\r
+       bsf     DATA_BIT\r
+       bcf     STATUS,RP0\r
+       clrf    SlaveBreakState\r
+\r
+IRQAfterTMR2\r
+       btfss   SlaveBreakState,4       ; Only increment to 0x10\r
+       incf    SlaveBreakState,f\r
+       bcf     PIR1,TMR2IF             ; Clear the IRQ source bit to re-enable TMR2 interrupts again\r
+\r
+IRQNotTMR2\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       btfss   PIR1,RCIF               ; Check if it's the UART RX interrupt\r
+       goto    IRQNotRCIF              ; No it's not, check the other sources\r
+;----------------------------------------------------------------\r
+; This is the UART RX routine, this should be control info from the connected PC we're receiving\r
+\r
+       movf    CurRecvPos,w            ; Load fsr\r
+       movwf   FSR\r
+       movf    RCREG,w                 ; Get the byte\r
+       bcf     PIR1,RCIF               ; Clear current interrupt condition\r
+       movwf   INDF                    ; Store the recv byte at the position waiting\r
+       movlw   RecvType\r
+       xorwf   FSR,w                   ; Check if it's the recvtype coming in\r
+       bnz     NotRecvType\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       movf    RecvType,w              ; Load type\r
+       bz      RecvTimeUpdate          ; Position update\r
+       xorlw   1                       ; Track Name perhaps?\r
+       bz      RecvTrackName\r
+       xorlw   3                       ; this is xor 1 and then xor 2, disc name?\r
+       bz      RecvDiscName\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       btfss   RecvType,7              ; Check Hi byte\r
+       goto    IRQNotRCIF\r
+\r
+       movf    RecvType,w\r
+       xorlw   81h\r
+       bnz     NotText\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       movf    RecvType,w\r
+       goto    TextUpdate\r
+NotText\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       movf    RecvType,w\r
+       btfss   DisplayStatus,7         ; Only if not updating already!\r
+TextUpdate\r
+       movwf   DisplayStatus           ; Force it into DisplayStatus\r
+       goto    Finished\r
+       \r
+RecvTimeUpdate\r
+       movlw   CurDisc                 ; Load DL start here\r
+       movwf   CurRecvPos\r
+       movlw   CurSecs+1               ; And stop here\r
+       movwf   TargetPos\r
+       goto    IRQNotRCIF\r
+\r
+RecvTrackName\r
+       movlw   TrackName               ; Load DL start here\r
+       movwf   CurRecvPos\r
+       movlw   TracknameEnd+1          ; And stop here\r
+       movwf   TargetPos\r
+       goto    IRQNotRCIF\r
+\r
+RecvDiscName\r
+       movlw   DiscName                ; Load DL start here\r
+       movwf   CurRecvPos\r
+       movlw   DiscNameEnd+1           ; And stop here\r
+       movwf   TargetPos\r
+       goto    IRQNotRCIF\r
+\r
+NotRecvType\r
+       incf    CurRecvPos,f\r
+       \r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       movf    CurRecvPos,w            ; Check if we're done\r
+       xorwf   TargetPos,w\r
+       bnz     NotFinished\r
+Finished\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+;      movlw   81h                     ; Assume we do it from the beginning (text and pos)\r
+;      movf    RecvType,f              ; Just check for time-update cmd\r
+;      skpnz                           ; No adjustment if another cmd\r
+;      movlw   85h                     ; Skip the first 4 display cmds\r
+;      movwf   DisplayStatus\r
+       \r
+;      call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       movlw   RecvType                ; restore for another go\r
+       movwf   CurRecvPos\r
+\r
+NotFinished\r
+\r
+IRQNotRCIF\r
+\r
+       call    IRQCheckINT             ; Check the Unilink INT as well\r
+\r
+       btfss   PIR1,TXIF               ; Check if it's the UART TX interrupt\r
+       goto    IRQNotTXIF              ; No it's not, check the other sources\r
+       bsf     STATUS,RP0              ; Reg bank 1\r
+       btfss   PIE1,TXIE               ; As TXIF is set as long as nothing gets sent, is the interrupt actually enabled?\r
+       goto    IRQTXIFDisabled\r
+;----------------------------------------------------------------\r
+; This is the UART TX routine, gets called when TXIE has been set and the TX load register is empty\r
+\r
+\r
+\r
+\r
+\r
+IRQTXIFDisabled\r
+       bcf     STATUS,RP0              ; Make sure we're going back to reg bank 0\r
+\r
+IRQNotTXIF\r
+\r
+; Finally restore CPU state and return from the ISR\r
+\r
+; If I have to save the FSR in the beginning I also need to restore it here...\r
+\r
+;      movf    IRQPCLATH,w\r
+;      movwf   PCLATH                  ; Restore PCLATH\r
+       swapf   IRQSTATUS,w\r
+       movwf   STATUS                  ; Restore STATUS\r
+       swapf   IRQW,f\r
+       swapf   IRQW,w                  ; Restore W\r
+       retfie                          ; Interrupt return\r
+\r
+;----------------------------------------------------------------\r
+; IRQCheckINT - This part is the actual Unilink tranceiver, have to call it often as there are only ~20 spare cycles\r
+; (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
+\r
+IRQCheckINT\r
        btfss   INTCON,INTF             ; Check if it's the INT edge interrupt (Unilink CLK)\r
-       goto    IRQNotINT               ; No it's not, check the other sources\r
+       return                          ; No it's not, return again after only four cycles\r
 \r
 ; 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
 ; - this reduces context switching (and it's just a few hundred cpu cycles after all (20us*8 bits=160us=800 instruction\r
@@ -297,6 +505,10 @@ IRQINTCLKWaitHigh
 \r
 IRQINTRecvDone\r
        clrf    SlaveBreakState         ; First of all, clear the break state - this got in the way, restart detection..\r
+\r
+       movf    INDF,w\r
+       call    BootTXB                 ; Send the byte to the serial port\r
+\r
        movf    UnilinkTXRX,w           ; Find out which byte # that was received\r
        andlw   0fh                     ; Mask\r
        bnz     IRQINTRecvNotFirst      ; Not the first byte\r
@@ -324,8 +536,41 @@ IRQINTRecvNotCMD1
        andlw   0fh                     ; and mask - this results in a zero result when finished receiving\r
        bnz     IRQINTRecvIncomplete    ; Packet not ready yet\r
 \r
-; Here a packet is actually received, should check the checksum(s) as well, but I don't care right now\r
-; (I need music in my car! :))\r
+; Here a packet is actually received, should check the checksum(s) now\r
+\r
+       movf    UnilinkRAD,w            ; QnD checksum check\r
+       addwf   UnilinkTAD,w\r
+       addwf   UnilinkCMD1,w\r
+       addwf   UnilinkCMD2,w\r
+       xorwf   UnilinkParity1,w        ; This should be zero\r
+       bnz     IRQINTParseComplete     ; Don't allow packet parsing on corrupt packets\r
+\r
+       btfss   UnilinkCMD1,7           ; Test whether there's more parity to check (medium or long packet)\r
+       goto    IRQINTParser            ; No, skip directly to parsing logic\r
+\r
+       movf    UnilinkParity1,w        ; QnD checksum check for the remaining part of the packet\r
+       addwf   UnilinkData1,w\r
+       addwf   UnilinkData2,w\r
+       addwf   UnilinkData3,w\r
+       addwf   UnilinkData4,w\r
+\r
+       btfss   UnilinkCMD1,6           ; Test for a long packet\r
+       goto    IRQINTBypassLongPacket\r
+\r
+       xorwf   UnilinkParity2M,w       ; Fix for the medium packet parity check a few lines down...\r
+       addwf   UnilinkData5,w\r
+       addwf   UnilinkData6,w\r
+       addwf   UnilinkData7,w\r
+       addwf   UnilinkData8,w\r
+       addwf   UnilinkData9,w\r
+       xorwf   UnilinkParity2,w        ; This should be zero when xor:ed with the Parity2M\r
+\r
+IRQINTBypassLongPacket\r
+       xorwf   UnilinkParity2M,w       ; This should be zero for valid medium and long packets\r
+       bnz     IRQINTParseComplete\r
+\r
+IRQINTParser\r
+\r
 ; This is inefficient, I know, I'll improve it later... (Not that it matters, there's plenty of time here\r
 ; (there won't be any more communication for at least another 4.8ms))\r
 \r
@@ -342,6 +587,8 @@ IRQINTRecvNotCMD1
 \r
        call    ClearUnilinkStatus      ; Clear everything Unilink (ID, BUSON_OUT)\r
 \r
+       incf    UnilinkReInits,f        ; increment the debug counter\r
+\r
        goto    IRQINTParseComplete     ; Don't send any reply to this (clear the packet buffer though)\r
 \r
 IRQINTParseNot0100\r
@@ -352,9 +599,8 @@ IRQINTParseNot0100
        bnz     IRQINTParseNot0102\r
 \r
        movf    UnilinkID,w             ; Do I have an ID already?\r
-       bnz     IRQINTParseNot0102      ; Yep, I don't want another one!\r
+       bnz     IRQINTParseComplete     ; Yep, I don't want another one!\r
 \r
-;      clrf    UnilinkParity1\r
        call    ClearUnilinkBuffer      ; Zero it out completely\r
 \r
        movlw   10h                     ; Sending to Master\r
@@ -386,7 +632,6 @@ IRQINTParseNot0100
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData4\r
 \r
-;        clrf  UnilinkData6\r
        goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
 \r
 IRQINTParseNot0102\r
@@ -400,7 +645,6 @@ IRQINTParseNot0102
        xorwf   UnilinkID,w             ; Is it for me?\r
        bnz     IRQINTParseNot0112      ; Nope\r
 \r
-;      clrf    UnilinkParity1\r
        call    ClearUnilinkBuffer\r
        movlw   10h                     ; Sending to Master\r
        addwf   UnilinkParity1,f\r
@@ -418,7 +662,6 @@ IRQINTParseNot0102
        \r
        addwf   UnilinkParity1,f\r
        movwf   UnilinkCMD2\r
-;        clrf  UnilinkData1\r
        goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
 \r
 IRQINTParseNot0112\r
@@ -435,14 +678,19 @@ IRQINTParseNot0112
        btfss   DisplayStatus,7         ; If not displaying, skip this\r
        goto    IRQINTParseComplete\r
 \r
-;      clrf    UnilinkParity1\r
        call    ClearUnilinkBuffer\r
+\r
        movlw   70h                     ; Sending to Display Group\r
        addwf   UnilinkParity1,f\r
        movwf   UnilinkRAD\r
        movf    UnilinkID,w             ; This is my ID\r
        addwf   UnilinkParity1,f\r
        movwf   UnilinkTAD\r
+\r
+       movf    DisplayStatus,w\r
+       xorlw   80h                     ; First slave break?\r
+       bnz     IRQINTParse0113Not80\r
+\r
        movlw   90h\r
        addwf   UnilinkParity1,f\r
        movwf   UnilinkCMD1\r
@@ -453,26 +701,256 @@ IRQINTParseNot0112
        movf    UnilinkParity1,w        ; Carry the parity forward\r
        movwf   UnilinkParity2M\r
 \r
-;      movlw   01h\r
-       movf    DisplayStatus,w\r
+       movf    CurTrack,w\r
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData1\r
-       movlw   00h\r
+       movf    CurMins,w\r
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData2\r
-       movlw   01h\r
+       movf    CurSecs,w\r
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData3\r
+\r
 ;      movlw   0c0h\r
+       movf    CurDisc,w\r
+;      andlw   0f0h\r
+;      iorlw   0eh\r
+\r
+       addwf   UnilinkParity2M,f\r
+       movwf   UnilinkData4\r
+\r
+;      clrf    DisplayStatus\r
+;      goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
+\r
+       goto    IRQINTParse0113Complete\r
+\r
+IRQINTParse0113Not80\r
+\r
+       movf    DisplayStatus,w\r
+       xorlw   81h                     ; Second slave break?\r
+       bnz     IRQINTParse0113Not81\r
+\r
+       movlw   0cdh                    ; Disc name\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD1\r
+       movf    DiscName,w\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD2\r
+\r
+       movf    UnilinkParity1,w        ; Carry the parity forward\r
+       movwf   UnilinkParity2\r
+\r
+       movf    DiscName+1,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData1\r
+       movf    DiscName+2,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData2\r
+       movf    DiscName+3,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData3\r
+       movf    DiscName+4,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData4\r
+       movf    DiscName+5,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData5\r
+       movf    DiscName+6,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData6\r
+       movf    DiscName+7,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData7\r
+       movlw   00h\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData8\r
+       movlw   0eh\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData9\r
+       goto    IRQINTParse0113Complete\r
+\r
+IRQINTParse0113Not81\r
+\r
+       movf    DisplayStatus,w\r
+       xorlw   82h                     ; Third slave break?\r
+       bnz     IRQINTParse0113Not82\r
+\r
+       movlw   0cdh                    ; Disc name\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD1\r
+       movf    DiscName+8,w\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD2\r
+\r
+       movf    UnilinkParity1,w        ; Carry the parity forward\r
+       movwf   UnilinkParity2\r
+\r
+       movf    DiscName+9,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData1\r
+       movlw   0\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData2\r
+       movlw   0\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData3\r
+       movlw   0\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData4\r
+       movlw   0\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData5\r
+       movlw   0\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData6\r
+       movlw   0\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData7\r
+       movlw   00h\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData8\r
+       movlw   1eh\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData9\r
+       goto    IRQINTParse0113Complete\r
+\r
+IRQINTParse0113Not82\r
+\r
+       movf    DisplayStatus,w\r
+       xorlw   83h                     ; Fourth slave break?\r
+       bnz     IRQINTParse0113Not83\r
+\r
+       movlw   0c9h                    ; Track name 1\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD1\r
+       movf    TrackName,w\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD2\r
+\r
+       movf    UnilinkParity1,w        ; Carry the parity forward\r
+       movwf   UnilinkParity2\r
+\r
+       movf    TrackName+1,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData1\r
+       movf    TrackName+2,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData2\r
+       movf    TrackName+3,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData3\r
+       movf    TrackName+4,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData4\r
+       movf    TrackName+5,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData5\r
+       movf    TrackName+6,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData6\r
+       movf    TrackName+7,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData7\r
+       movlw   00h\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData8\r
+       movlw   0eh\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData9\r
+\r
+       goto    IRQINTParse0113Complete\r
+\r
+IRQINTParse0113Not83\r
+\r
+       movf    DisplayStatus,w\r
+       xorlw   84h                     ; Fifth slave break?\r
+       bnz     IRQINTParse0113Not84\r
+\r
+       movlw   0c9h                    ; Track name (2)\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD1\r
+       movf    TrackName+8,w\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD2\r
+\r
+       movf    UnilinkParity1,w        ; Carry the parity forward\r
+       movwf   UnilinkParity2\r
+\r
+       movf    TrackName+9,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData1\r
+       movf    TrackName+10,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData2\r
+       movf    TrackName+11,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData3\r
+       movf    TrackName+12,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData4\r
+       movf    TrackName+13,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData5\r
+       movf    TrackName+14,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData6\r
+       movf    TrackName+15,w\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData7\r
+       movlw   00h\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData8\r
+       movlw   1eh\r
+       addwf   UnilinkParity2,f\r
+       movwf   UnilinkData9\r
+       goto    IRQINTParse0113Complete\r
+\r
+IRQINTParse0113Not84\r
+\r
        movf    DisplayStatus,w\r
-       andlw   0f0h\r
+       xorlw   85h                     ; Sixth slave break?\r
+       bnz     IRQINTParse0113Not85\r
+\r
+       movlw   90h\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD1\r
+       movlw   50h\r
+       addwf   UnilinkParity1,f\r
+       movwf   UnilinkCMD2\r
+\r
+       movf    UnilinkParity1,w        ; Carry the parity forward\r
+       movwf   UnilinkParity2M\r
+\r
+       movf    CurTrack,w\r
+       addwf   UnilinkParity2M,f\r
+       movwf   UnilinkData1\r
+       movf    CurMins,w\r
+       addwf   UnilinkParity2M,f\r
+       movwf   UnilinkData2\r
+       movf    CurSecs,w\r
+       addwf   UnilinkParity2M,f\r
+       movwf   UnilinkData3\r
+\r
+;      movlw   0c0h\r
+       movf    CurDisc,w\r
+;      andlw   0f0h\r
+;      iorlw   0eh\r
+\r
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData4\r
-       \r
-;        clrf  UnilinkData6\r
 \r
-       incf    DisplayStatus,f         ; Temporary debug info\r
-       bsf     DisplayStatus,7\r
+       clrf    DisplayStatus           ; for now!\r
+       goto    IRQINTParse0113Complete\r
+\r
+IRQINTParse0113Not85\r
+       clrf    DisplayStatus\r
+       incf    DisplayStatus,f         ; Skip step one for now\r
+       goto    IRQINTParseComplete\r
+\r
+IRQINTParse0113Complete        \r
+\r
+       incf    DisplayStatus,f         ; Increment display state counter\r
+;      bsf     DisplayStatus,7\r
 \r
        goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
 \r
@@ -486,7 +964,6 @@ IRQINTParseNot0113
        btfss   DisplayStatus,7         ; First of all check if there should be anything displayed\r
        goto    IRQINTParseComplete     ; No, not at this time\r
        \r
-;      clrf    UnilinkParity1\r
        call    ClearUnilinkBuffer\r
        movlw   10h                     ; Sending to Master\r
        addwf   UnilinkParity1,f\r
@@ -523,28 +1000,27 @@ IRQINTParseNot0113
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData4\r
 \r
-;      clrf    UnilinkData6\r
-\r
        goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
 \r
 ;******************************************************************************\r
 ; Bit frig - works out which bit to set in the response to Master Poll\r
-\r
-; W register is input of which stage you are on (0x00, 0x20, 0x30, 0x40 etc)\r
+;  This is taken more or less verbatim from Simon Woods' GNUnilink code!\r
+;\r
+; W register is input of which stage you are on (0x00, 0x20, 0x40 etc)\r
 ; and is returned with the byte to write (0x00 if wrong stage).\r
 \r
 Bit_Frig:\r
        xorwf   UnilinkBit, 0\r
-       andlw   0xe0                            ; Strip off low bits\r
+       andlw   0xe0                    ; Strip off low bits\r
 \r
-       btfsc   STATUS, Z                       ; Do we have a hit?\r
+       btfsc   STATUS, Z               ; Do we have a hit?\r
        goto    Bit_Frig_Hit\r
 \r
        movlw   0x00\r
        return\r
 \r
 Bit_Frig_Hit:\r
-       btfss   UnilinkBit, 4                   ; Do we need to swap nybbles?\r
+       btfss   UnilinkBit, 4           ; Do we need to swap nybbles?\r
        goto    Bit_Frig_Swap\r
 \r
        movf    UnilinkBit, 0\r
@@ -565,14 +1041,19 @@ IRQINTParseNot01
        xorlw   02h\r
        bnz     IRQINTParseNot02\r
 \r
-       bsf     BUSON_OUT_BIT           ; Now activate the cascade BUSON pin, to allow others to be discovered\r
+       movf    UnilinkID,w             ; Do I have an ID already?\r
+       bnz     IRQINTParseComplete     ; Yep, I don't want another one!\r
+\r
+       movf    UnilinkRAD,w            ; So I don't have any ID yet, see what the master is trying to set\r
+       andlw   0f0h                    ; Check the device group\r
+       xorlw   0d0h                    ; Verify it's a MD changer\r
+       bnz     IRQINTParseComplete     ; No, something else, skip this\r
 \r
        movf    UnilinkRAD,w            ; Get the ID the master has given me\r
        movwf   UnilinkID               ; Store my id\r
        movf    UnilinkCMD2,w           ; Get the bitmask\r
        movwf   UnilinkBit              ; And store it (this is needed when doing slave breaks and actually responding)\r
 \r
-;      clrf    UnilinkParity1\r
        call    ClearUnilinkBuffer\r
        movlw   10h                     ; Sending to Master\r
        addwf   UnilinkParity1,f\r
@@ -603,28 +1084,101 @@ IRQINTParseNot01
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData4\r
 \r
-;        clrf  UnilinkData6\r
+       bsf     BUSON_OUT_BIT           ; Now activate the cascade BUSON pin, to allow others after us to be discovered\r
+\r
        goto    IRQINTParseBypassClear  ; Don't clear the data, the buffer will be sent as the next packet\r
 \r
 IRQINTParseNot02\r
 \r
+; Check for CMD1 = 80h (Display button)\r
+       movf    UnilinkCMD1,w\r
+       xorlw   080h\r
+       bnz     IRQINTParseNot80\r
+\r
+       movf    UnilinkID,w             ; Check if I'm currently selected\r
+       xorwf   UnilinkCurID,w\r
+       skpnz                           ; No, skip this command\r
+;      bsf     DisplayStatus,7         ; Make sure we update the display again\r
+       movlw   81h\r
+       movwf   DisplayStatus\r
+       goto    IRQINTParseComplete\r
+\r
+IRQINTParseNot80\r
+\r
 ; Check for CMD1 = 87h (Power control)\r
        movf    UnilinkCMD1,w\r
        xorlw   087h\r
        bnz     IRQINTParseNot87\r
 \r
-; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)\r
-       btfsc   UnilinkCMD2,3\r
-       goto    IRQINTParse87PowerOn\r
+; This part could use some more packet sniffing (really), it's sketchy to say the least.. :(\r
+; 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
+; 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
+; Also interesting is that the exact same commands gets sent when pressing the power-off button, this makes slaves pause\r
+; playing, and then a few seconds later the unit shuts down with the following command:\r
+; 18 10 87 22 PP 02 00 80 00 PP ZZ or\r
+; 18 10 87 22 PP 12 00 80 00 PP ZZ depending on the color of the backlight\r
+; This makes me think that bit 3 in CMD2 reflects the actual power state of the headunit (actually sleeping or bus active)\r
+; Anyway I use this to set/clear the RI pin used for WakeOnRing on my laptop\r
+; Also I de-select to make everything pause and clear the display status (if we're doing slave breaks after power status\r
+; the headunit will never enter sleep!)\r
+; From what I have gathered the bit mapping of DATA1 is as follows:\r
+; 7 6 5 4 3 2 1 0\r
+; X                - Backlight color changed if 1\r
+;   ? ?\r
+;       X          - Backlight color, 0=Amber, 1=Green\r
+;         X        - Dimmer setting changed if 1\r
+;           X X    - Dimmer setting, 01=Dimmer Auto, 10=Dimmer On, 00=Dimmer Off, 11=???\r
+;               X  - Beep setting, 0=Beep on(!), 1=Beep off\r
+;\r
+; Also bit field of CMD2 for now:\r
+; 7 6 5 4 3 2 1 0\r
+; X X              - These two bits are set when changing color, beep etc, but not when actually powering the system on or off???\r
+;     X            - Always set to 1 on my headunit\r
+;       X          - Always set to 0 on my headunit\r
+;         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
+;           X      - Always set to 0 on my headunit\r
+;             X    - Always set to 1 on my headunit\r
+;               X  - Always set to 0 on my headunit\r
+\r
+; Test for power-on bit (it seems like bit 3 (0x08h) of CMD2 is set when the power is on)\r
+       btfsc   UnilinkCMD2,3\r
+       goto    IRQINTParse87PowerOn\r
+\r
+       bsf     RS232_RI_BIT            ; Set this to make RI pin go low (after RS-232 levels)\r
+       goto    IRQINTParseComplete\r
+\r
+IRQINTParse87PowerOn\r
+       bcf     RS232_RI_BIT            ; Clear this to make RI pin go high (waking the computer)\r
+\r
+       btfsc   UnilinkCMD2,7           ; Test high bit if it's just a "set" command, if yes don't clear status\r
+       goto    IRQINTParseComplete\r
+\r
+       bcf     UnilinkSelected,7       ; Also de-select us (this gets sent when powering off but before the actual power down)\r
+       clrf    DisplayStatus\r
+\r
+       goto    IRQINTParseComplete\r
+\r
+IRQINTParseNot87\r
+\r
+; Check for CMD1 = 90h (Display/DSP info, volume etc.)\r
+       movf    UnilinkCMD1,w\r
+       xorlw   090h\r
+       bnz     IRQINTParseNot90\r
+\r
+; Check for 90 10 (Current Volume)\r
+       movf    UnilinkCMD2,w\r
+       xorlw   010h\r
+       bnz     IRQINTParseNot9010\r
+\r
+       movf    UnilinkData1,w          ; Store current volume setting\r
+       movwf   UnilinkAttenuation\r
+\r
+       goto    IRQINTParseComplete     ; Don't send any reply to this (clear the packet buffer though)\r
 \r
-       bsf     RS232_RI_BIT            ; Set this to make RI pin go low (after RS-232 levels)\r
-       goto    IRQINTParseComplete\r
+IRQINTParseNot9010\r
 \r
-IRQINTParse87PowerOn\r
-       bcf     RS232_RI_BIT            ; Clear this to make RI pin go high (waking the computer)\r
-       goto    IRQINTParseComplete\r
 \r
-IRQINTParseNot87\r
+IRQINTParseNot90\r
 \r
 ; Check for CMD1 = f0h (Source Select)\r
        movf    UnilinkCMD1,w\r
@@ -632,11 +1186,15 @@ IRQINTParseNot87
        bnz     IRQINTParseNotF0\r
 \r
        movf    UnilinkCMD2,w\r
+       movwf   UnilinkCurID            ; Store it for display and debugging\r
+\r
        xorwf   UnilinkID,w             ; Check if it's selecting me\r
        bnz     IRQINTParseF0Deselect\r
 \r
        bsf     UnilinkSelected,7       ; Now we're selected\r
-       bsf     DisplayStatus,7\r
+;      bsf     DisplayStatus,7\r
+       movlw   81h\r
+       movwf   DisplayStatus\r
        goto    IRQINTParseComplete\r
 \r
 IRQINTParseF0Deselect\r
@@ -665,112 +1223,14 @@ IRQINTParseBypassClear
 IRQINTRecvIncomplete\r
 \r
 IRQINTRecvNullByte\r
-       movf    INDF,w\r
-       movwf   DataStore               ; Store it so the non-irq code can snoop\r
+;      movf    INDF,w\r
+;      movwf   DataStore               ; Store it so the non-irq code can snoop\r
 \r
 IRQAfterINT\r
        bcf     INTCON,INTF             ; Clear the IRQ source bit to re-enable INT interrupts again\r
 \r
 IRQNotINT\r
-\r
-       btfss   PIR1,TMR2IF             ; Check if it's the TMR2 interrupt (0.5ms timing)\r
-       goto    IRQNotTMR2              ; No it's not, check the other sources\r
-\r
-; Slave break opportunity detection here - the logic works as follows:\r
-; Look for a data low period of at least 5 ms (10 loops)\r
-; Look for a data high period of at least 2 ms (4 loops)\r
-; If the Slave Break request bit has been set, issue a slave break by holding the data line low for 4ms (8 loops)\r
-; If a bit would be received (CLK activates) the packet handler automatically clears out the SlaveBreakState, which means start all over\r
-\r
-;      incf    Counter,f               ; Increment the general purpose counter\r
-\r
-       btfsc   SlaveBreakState,5       ; Check if already pulling the data line low\r
-       goto    IRQTMR2SlaveBreak\r
-\r
-       btfsc   SlaveBreakState,7       ; Looking for low or high data\r
-       goto    IRQTMR2HighData\r
-       btfss   DATA_BIT                ; Looking for a low data line, if it's low, increment state, if it's high, reset state\r
-       goto    IRQTMR2LowDataOK\r
-       clrf    SlaveBreakState         ; Got a high data line while waiting for a low one, reset state\r
-       goto    IRQAfterTMR2            ; Leave ISR\r
-\r
-IRQTMR2HighData\r
-       btfsc   DATA_BIT                ; Looking for a high data line, if it's high - increment state, otherwise wait\r
-       goto    IRQTMR2HighDataOK\r
-       movlw   080h\r
-       btfsc   SlaveBreakState,6       ; Test the "first time around" bit\r
-       clrw                            ; Not the beginning of the state, have to restart the entire thing now, not just this state\r
-       andwf   SlaveBreakState,f       ; Mask out the 1 upper control bits and restart this state\r
-       goto    IRQAfterTMR2\r
-\r
-IRQTMR2HighDataOK\r
-IRQTMR2LowDataOK\r
-       bsf     SlaveBreakState,6       ; Set the "first time around" bit\r
-       \r
-       movf    SlaveBreakState,w\r
-       andlw   1fh\r
-\r
-       btfss   SlaveBreakState,7       ; Checking whether it's low or high\r
-       goto    IRQTMR2FoundLow\r
-\r
-       xorlw   4                       ; It's high now, and if 4 periods have passed we can activate slave break\r
-       skpz\r
-       goto    IRQAfterTMR2\r
-\r
-; Issue slave break here\r
-\r
-       clrf    SlaveBreakState\r
-\r
-       incf    Counter,f\r
-\r
-       btfss   DisplayStatus,7         ; Only do this if high bit is set\r
-       goto    IRQAfterTMR2\r
-\r
-       movlw   20h\r
-       movwf   SlaveBreakState\r
-       bcf     DATA_BIT\r
-       bsf     STATUS,RP0\r
-       bcf     DATA_BIT\r
-       bcf     STATUS,RP0\r
-       goto    IRQAfterTMR2\r
-\r
-IRQTMR2FoundLow\r
-       xorlw   10\r
-       skpz\r
-       goto    IRQAfterTMR2\r
-       movlw   80h                     ; Prepare for state 2, looking for data line high\r
-       movwf   SlaveBreakState\r
-       goto    IRQAfterTMR2\r
-       \r
-IRQTMR2SlaveBreak\r
-       movf    SlaveBreakState,w\r
-       andlw   01fh\r
-       xorlw   8\r
-       skpz\r
-       goto    IRQAfterTMR2\r
-       bsf     STATUS,RP0\r
-       bsf     DATA_BIT\r
-       bcf     STATUS,RP0\r
-       clrf    SlaveBreakState\r
-\r
-IRQAfterTMR2\r
-       btfss   SlaveBreakState,4       ; Only increment to 0x10\r
-       incf    SlaveBreakState,f\r
-       bcf     PIR1,TMR2IF             ; Clear the IRQ source bit to re-enable TMR2 interrupts again\r
-\r
-IRQNotTMR2\r
-\r
-; Finally restore CPU state and return from the ISR\r
-\r
-; If I have to save the FSR in the beginning I also need to restore it here...\r
-\r
-;      movf    IRQPCLATH,w\r
-;      movwf   PCLATH                  ; Restore PCLATH\r
-       swapf   IRQSTATUS,w\r
-       movwf   STATUS                  ; Restore STATUS\r
-       swapf   IRQW,f\r
-       swapf   IRQW,w                  ; Restore W\r
-       retfie                          ; Interrupt return\r
+       return\r
 \r
 ;----------------------------------------------------------------\r
 ; ClearUnilinkStatus - Zeroes out the Unilink state (used when initializing)\r
@@ -778,6 +1238,7 @@ IRQNotTMR2
 ClearUnilinkStatus\r
 \r
        clrf    UnilinkID               ; Clear the existing Unilink ID, if any\r
+       clrf    UnilinkCurID            ; Clear the currently selected ID as well\r
        bcf     BUSON_OUT_BIT           ; Clear the cascade BUSON pin, not activated again until we have a new ID\r
        clrf    DisplayStatus           ; No crazy display updates when resetting.. :)\r
        clrf    UnilinkSelected         ; We're not selected anymore\r
@@ -791,6 +1252,8 @@ ClearUnilinkStatus
 \r
        clrf    UnilinkCmdLen           ; No command length while waiting for a new packet\r
 \r
+       clrf    SlaveBreakState         ; Slave Break Processing has to start all over\r
+\r
        return\r
        \r
 ;----------------------------------------------------------------\r
@@ -827,11 +1290,41 @@ ClearUnilinkBuffer
 ; Here all other house keeping tasks are performed, like displaying info on the LCD.. \r
 \r
 Main\r
-       movlw   high LookUp\r
+       movlw   high LookUp             ; Set the high PC bits to indicate data lookup page\r
        movwf   PCLATH\r
 \r
-       movlw   low StartUpText1        ; Show something on the LCD\r
-       call    TxLCD16B\r
+       movlw   0ffh                    ; Set infinite attenuation to begin with\r
+       movwf   UnilinkAttenuation\r
+\r
+       clrf    RecvType\r
+       movlw   RecvType\r
+       movwf   CurRecvPos\r
+\r
+       clrf    UnilinkReInits          ; Clear the bus re-initialization counter\r
+\r
+       bsf     STATUS,RP0              ; Reg bank 1\r
+       movlw   080h                    ; Right adjusted A/D, all analog inputs, no Vrefs\r
+       movwf   ADCON1\r
+       bcf     STATUS,RP0\r
+\r
+       movlw   081h                    ; Activate A/D, ch 0, Fosc/32 (for 20MHz operation)\r
+       movwf   ADCON0\r
+\r
+       bsf     ADCON0,2                ; Start the first A/D operation\r
+\r
+       movlw   8                       ; Display page timing (approx 8/sec)\r
+       movwf   DisplayCounter\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h                     ; DisplayRam 0\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       movlw   low DefaultText1\r
+       movwf   Icount\r
+       movlw   80\r
+       movwf   e_LEN\r
+       call    TxLCD8BLoop             ; Send 80 bytes to the LCD\r
 \r
 MainLoop\r
 \r
@@ -870,52 +1363,176 @@ MainLoop
 \r
 MainDontPrintCmd\r
 \r
-       movf    DataCount,w             ; Load bit counter (if 0 then byte is available)\r
+; Default text\r
+; UnilinkID @ 13-14\r
+; UnilinkAttenuation @ 16-17\r
+; UnilinkSelected @ 28-29\r
+; UnilinkReInits @ 38,39\r
+; UnilinkCurID @ 54-55\r
+; DisplayStatus @ 62-63\r
+; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h+13                  ; DisplayRam 13\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       movf    UnilinkID,w\r
+       call    TxLCDHEX\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h+16                  ; DisplayRam 16\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       movf    UnilinkAttenuation,w\r
+       call    TxLCDHEX\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h+28                  ; DisplayRam 28\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       movf    UnilinkSelected,w\r
+       call    TxLCDHEX\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h+38                  ; DisplayRam 38\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       movf    UnilinkReInits,w\r
+       call    TxLCDHEX\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h+40h+14              ; DisplayRam 54\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       movf    UnilinkCurID,w\r
+       call    TxLCDHEX\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h+40h+22              ; DisplayRam 62\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       movf    DisplayStatus,w\r
+       call    TxLCDHEX\r
+\r
+       btfsc   ADCON0,2                ; Test if A/D is ready\r
+       goto    MainADNotReady\r
+\r
+       bsf     STATUS,RP0\r
+       movf    ADRESL,w                ; Add to our result\r
+       bcf     STATUS,RP0\r
+       addwf   NumL,f\r
+       skpnc\r
+       incf    NumH,f\r
+       movf    ADRESL,w\r
+       addwf   NumH,f                  ; And the high byte\r
+\r
+       movlw   20h\r
+       addwf   NumH,f\r
+       skpc                            ; When this overflows we know there are 8 samples collected\r
+       goto    MainADStartAD\r
+\r
+; Now shift the added results two steps down (/4) as there are 8 added samples here, and filter high bits\r
+       movlw   1fh\r
+       andwf   NumH,f\r
+       clrc\r
+       rrf     NumH,f\r
+       rrf     NumL,f\r
+       clrc\r
+       rrf     NumH,f\r
+       rrf     NumL,f\r
+               \r
+       movf    Counter,w\r
+       bnz     MainADSkipDisplay\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   80h+40h+26              ; DisplayRam 66\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+\r
+       call    BCDConvert\r
+       movf    Thou,w\r
+       addlw   30h\r
+       call    TxLCDB\r
+       movf    Hund,w\r
+       addlw   30h\r
+       call    TxLCDB\r
+       movlw   '.'\r
+       call    TxLCDB\r
+       movf    Tens,w\r
+       addlw   30h\r
+       call    TxLCDB\r
+       movf    Ones,w\r
+       addlw   30h\r
+       call    TxLCDB\r
+\r
+MainADSkipDisplay\r
+\r
+       clrf    NumL\r
+       clrf    NumH\r
+\r
+MainADStartAD\r
+       bsf     ADCON0,2                ; Start a new conversion\r
+\r
+MainADNotReady\r
+\r
+; This part handles display "scroll" by shifting one screen at a time\r
+\r
+       btfss   Counter,7               ; Test high bit\r
+       goto    MainCounterLow\r
+\r
+; So bit is high, set high bit of displaycounter as well...\r
+       bsf     DisplayCounter,7\r
+       goto    MainSkipScroll\r
+\r
+MainCounterLow\r
+; OK, bit is low, now figure out whether it was high or low last time -> check high bit of DisplayCounter\r
+       btfss   DisplayCounter,7\r
+       goto    MainSkipScroll\r
+\r
+       bcf     DisplayCounter,7        ; Clear the high bit to allow countdown to commence\r
+       movf    Counter,w               ; Load it\r
        skpz\r
-       goto    MainLoop\r
+       goto    MainSkipScroll\r
+       decfsz  DisplayCounter,f\r
+       goto    MainSkipScroll\r
+       \r
+       movlw   8\r
+       movwf   DisplayCounter\r
+\r
+       bcf     LCD_RS_BIT              ; LCD Command mode\r
+       movlw   18h                     ; Display shift Left\r
+       call    TxLCDB                  ; Shift it 8 positions\r
+       call    TxLCDB\r
+       call    TxLCDB\r
+       call    TxLCDB\r
+       call    TxLCDB\r
+       call    TxLCDB\r
+       call    TxLCDB\r
+       call    TxLCDB\r
+       bsf     LCD_RS_BIT\r
+       \r
+MainSkipScroll\r
 \r
-       decf    DataCount,f             ; Set it non-zero\r
+; Display scroll part ends here...\r
 \r
-       movf    DataStore,w\r
-       call    BootTXB                 ; Send to terminal\r
        goto    MainLoop\r
 \r
 \r
 ;----------------------------------------------------------------\r
 ; IRQInit - Sets up the IRQ Handler\r
 ; 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
+; Also enable INT interrupts for Unilink CLK processing\r
 \r
 IRQInit\r
 \r
-; Start with clearing the Unilink packet buffer before enabling any interrupts, otherwise the first packet might become corrupt\r
-; TODO: Replace this with FSR access\r
-       clrf    UnilinkSelected\r
-       clrf    DisplayStatus\r
-       clrf    UnilinkID\r
-       clrf    UnilinkBit\r
-       clrf    UnilinkCmdLen\r
-       clrf    UnilinkRAD\r
-       clrf    UnilinkTAD\r
-       clrf    UnilinkCMD1\r
-       clrf    UnilinkCMD2\r
-       clrf    UnilinkParity1\r
-       clrf    UnilinkData1\r
-       clrf    UnilinkData2\r
-       clrf    UnilinkData3\r
-       clrf    UnilinkData4\r
-       clrf    UnilinkData5\r
-       clrf    UnilinkData6\r
-       clrf    UnilinkData7\r
-       clrf    UnilinkData8\r
-       clrf    UnilinkData9\r
-       clrf    UnilinkParity2\r
-       clrf    UnilinkZero\r
-\r
-       clrf    DataStore\r
-       movlw   UnilinkRAD              ; Get the pointer to the first byte in the receive buffer\r
-       movwf   UnilinkTXRX             ; Store it\r
-\r
-       clrf    SlaveBreakState         ; Zero out the status, we're starting from the beginning\r
+       call    ClearUnilinkStatus\r
+       call    ClearUnilinkBuffer\r
 \r
 ; Fix the output state of RI and BUSON_OUT to a safe default\r
 \r
@@ -939,6 +1556,7 @@ IRQInit
        bsf     INTCON,INTE             ; Enable the RB0/INT\r
        bsf     INTCON,PEIE             ; Enable the peripheral interrupts\r
        bsf     PIE1,TMR2IE             ; Enable the Timer2 peripheral interrupt\r
+       bsf     PIE1,RCIE               ; Enable the UART receive interrupt\r
        bsf     INTCON,GIE              ; Enable global interrupts\r
 \r
        bsf     TXSTA,TXEN              ; Enable UART TX\r
@@ -964,12 +1582,12 @@ LCDInit
 \r
 ; This is a standard reset sequence for the LCD controller\r
 \r
-       movlw   160                     ; Need to delay for at least 15ms, let's go for 16ms delay\r
+       movlw   170                     ; Need to delay for at least 15ms, let's go for 17ms delay\r
        call    DelayW\r
 \r
        movlw   3                       ; Write 3 to the LCD\r
        call    TxLCD                   ; Send to LCD\r
-       movlw   50                      ; Need to delay for at least 4.1ms, let's go for 5ms delay\r
+       movlw   60                      ; Need to delay for at least 4.1ms, let's go for 6ms delay\r
        call    DelayW\r
 \r
        movlw   3                       ; Write 3 to the LCD\r
@@ -1001,10 +1619,130 @@ LCDInit
 \r
        movlw   06h                     ; Auto Increment cursor position\r
        call    TxLCDB\r
-       \r
+\r
+       bsf     LCD_RS_BIT              ; Accept data\r
+\r
        return\r
    \r
 ;----------------------------------------------------------------\r
+;  TxLCDHEX\r
+;  Sends two characters hex to the LCD\r
+\r
+TxLCDHEX\r
+\r
+; Original binary to 2-digit hex conversion from piclist.com, modified to fit here\r
+\r
+       movwf   Icount\r
+       swapf   Icount,w\r
+       andlw   0x0f\r
+\r
+       addlw   6\r
+       skpndc\r
+       addlw   'A'-('9'+1)\r
+       addlw   '0'-6\r
+\r
+       xorwf   Icount,w\r
+       xorwf   Icount,f\r
+       xorwf   Icount,w\r
+\r
+       andlw   0x0f\r
+\r
+       addlw   6\r
+       skpndc\r
+       addlw   'A'-('9'+1)\r
+       addlw   '0'-6\r
+\r
+       movwf   e_LEN\r
+       movf    Icount,w\r
+       call    TxLCDB\r
+       movf    e_LEN,w\r
+       call    TxLCDB\r
+\r
+       return\r
+\r
+;----------------------------------------------------------------\r
+;\r
+; Binary-to-BCD.  Written by John Payson.\r
+; Taken from piclist.com - why re-invent the wheel when writing open-sourced code?\r
+;\r
+; Enter with 16-bit binary number in NumH:NumL.\r
+; Exits with BCD equivalent in TenK:Thou:Hund:Tens:Ones.\r
+;\r
+\r
+BCDConvert:                            ; Takes number in NumH:NumL\r
+                                       ; Returns decimal in\r
+                                       ; TenK:Thou:Hund:Tens:Ones\r
+       swapf   NumH,w\r
+       andlw   0Fh                     ;*** PERSONALLY, I'D REPLACE THESE 2\r
+       addlw   0F0h                    ;*** LINES WITH "IORLW 11110000B" -AW\r
+       movwf   Thou\r
+       addwf   Thou,f\r
+       addlw   0E2h\r
+       movwf   Hund\r
+       addlw   032h\r
+       movwf   Ones\r
+\r
+       movf    NumH,w\r
+       andlw   0Fh\r
+       addwf   Hund,f\r
+       addwf   Hund,f\r
+       addwf   Ones,f\r
+       addlw   0E9h\r
+       movwf   Tens\r
+       addwf   Tens,f\r
+       addwf   Tens,f\r
+\r
+       swapf   NumL,w\r
+       andlw   0Fh\r
+       addwf   Tens,f\r
+       addwf   Ones,f\r
+\r
+       rlf     Tens,f\r
+       rlf     Ones,f\r
+       comf    Ones,f\r
+       rlf     Ones,f\r
+\r
+       movf    NumL,w\r
+       andlw   0Fh\r
+       addwf   Ones,f\r
+       rlf     Thou,f\r
+\r
+       movlw   07h\r
+       movwf   TenK\r
+\r
+                                       ; At this point, the original number is\r
+                                       ; equal to TenK*10000+Thou*1000+Hund*100+Tens*10+Ones\r
+                                       ; if those entities are regarded as two's compliment\r
+                                       ; binary.  To be precise, all of them are negative\r
+                                       ; except TenK.  Now the number needs to be normal-\r
+                                       ; ized, but this can all be done with simple byte\r
+                                       ; arithmetic.\r
+\r
+       movlw   0Ah                     ; Ten\r
+Lb1:\r
+       addwf   Ones,f\r
+       decf    Tens,f\r
+       btfss   3,0\r
+       goto    Lb1\r
+Lb2:\r
+       addwf   Tens,f\r
+       decf    Hund,f\r
+       btfss   3,0\r
+       goto    Lb2\r
+Lb3:\r
+       addwf   Hund,f\r
+       decf    Thou,f\r
+       btfss   3,0\r
+       goto    Lb3\r
+Lb4:\r
+       addwf   Thou,f\r
+       decf    TenK,f\r
+       btfss   3,0\r
+       goto    Lb4\r
+\r
+       retlw   0\r
+\r
+;----------------------------------------------------------------\r
 ;  TxLCD16B\r
 ;  Send a string to the LCD.\r
 \r
@@ -1031,12 +1769,13 @@ TxLCD8B
        movlw   8\r
        movwf   e_LEN                   ; Move to e_LEN\r
 \r
-Txm_lp movf    Icount,w                ; get the byte\r
+TxLCD8BLoop\r
+       movf    Icount,w                ; get the byte\r
        call    LookUp\r
        incf    Icount,f                ; ...else ++Icount (table index)\r
        call    TxLCDB                  ; Send out the byte\r
        decfsz  e_LEN,f\r
-       goto    Txm_lp\r
+       goto    TxLCD8BLoop\r
        return\r
 \r
 ;----------------------------------------------------------------\r
@@ -1067,6 +1806,7 @@ NotReady
        call    TxLCD                   ; And send that one as well\r
 \r
        return\r
+\r
 ;----------------------------------------------------------------\r
 ; RxLCDB - recv a byte from the LCD\r
 \r
@@ -1167,11 +1907,29 @@ DelayInner
 ;  Data can be stored between 600 and 6ffh...\r
 \r
        org     600h\r
-StartUpText1\r
-       DT      "----- WJ UniLink"\r
 \r
-InfoText1\r
-       DT      "WJ UniLink      "               \r
+; Default text\r
+; UnilinkID @ 13-14\r
+; UnilinkAttenuation @ 16-17\r
+; UnilinkSelected @ 28-29\r
+; UnilinkReInits @ 38,39\r
+; UnilinkCurID @ 54-55\r
+; DisplayStatus @ 62-63\r
+; BattVoltage @ 66,67,69,70 (thou,hund,tens,unit)\r
+\r
+DefaultText1\r
+       DT      "----- WJ", "MyID:xx ", "xx dB at", "Sel:xx B", "Inits:xx"\r
+       DT      " Unilink", "CurID:xx", "t Dsp:xx", "atxx.xxV", "    <WJ>"\r
+\r
+PCWaitText0\r
+;      DT      ">Waiting"\r
+;      DT      "Wait->PC"               \r
+       DT      "Booting."\r
+PCWaitText1\r
+;      DT      " for PC<"\r
+;      DT      " Booting"               \r
+       DT      "..",0,0,0,0,0,0\r
+\r
 LookUp movwf   PCL                     ; Go to it (this assumes PCLATH == 06h)\r
 \r
 \r
@@ -1218,7 +1976,8 @@ Bootstrap
        movlw   low BootStartText       ; Send boot banner to the serial port\r
        call    BootTXStr\r
 \r
-       movlw   0e8h                    ; Initialize timeout timer\r
+;      movlw   0e8h                    ; Initialize timeout timer (e8 is about 3 secs)\r
+       movlw   0fdh                    ; Initialize timeout timer (fd is short enough to get the headunit to recognize us)\r
        movwf   BootTimerL\r
        movwf   BootTimerM\r
        movwf   BootTimerH\r
@@ -1391,10 +2150,14 @@ BootRXW1
 \r
 BootRXHEXNibble\r
        call    BootRXB                 ; Receive nibble\r
+\r
+; This code is from piclist.com, really neat!\r
+\r
        addlw   -'A'                    ; Convert from BCD to binary nibble\r
        skpc                            ; Test if if was 0-9 or A-F, skip if A-F\r
        addlw  'A' - 10 - '0'           ; It was numeric '0'\r
        addlw   10                      ; Add 10 (A get to be 0ah etc.)\r
+\r
        return\r
 \r
 ;----------------------------------------------------------------------\r
@@ -1467,20 +2230,25 @@ BootEEX
 \r
 ; 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
 BootStartText\r
-       DW      0x2bca,0x216f,0x37f4,0x102d,0x1070,0x3965,0x39f3,0x1045,0x29c3,0x1074,0x37a0,0x336c,0x30f3,0x3400\r
-;      DE      "WJBoot - press ESC to flash\x00"\r
+       DA      "WJBoot - press ESC to flash\x00"\r
+\r
 BootFlashText\r
-       DW      0x068a,0x29e5,0x3764,0x1049,0x2748,0x2c38,0x1066,0x34ec,0x32a0,0x376f,0x3bae,0x172e,0x0680\r
-;      DE      "\r\nSend INHX8 file now...\r\x00"\r
-BootRunText\r
-       DW      0x068a,0x22f8,0x34f4,0x34ee,0x33a0,0x366f,0x30e4,0x32f2,0x0680\r
-;      DE      "\r\nExiting loader\r\x00"\r
+       DA      "\r\nSend INHX8 file now...\r\x00"\r
 \r
+BootRunText\r
+       DA      "\r\nExiting loader\r\x00"\r
 \r
 ;----------------------------------------------------------------------\r
 ; EE Data (64 bytes), located at 2100h\r
 \r
         org 2100h\r
 ;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
+;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
+;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
+;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
+;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
+;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
+;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
+;      de      0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh, 0ffh\r
 \r
        END\r