v0.7 - Debug Serial TX in ISR now, checksum check for incoming packets in place,...
authorWerner Johansson <wj@xnk.nu>
Sun, 9 Mar 2003 23:19:40 +0000 (15:19 -0800)
committerWerner Johansson <wj@xnk.nu>
Sun, 17 Oct 2010 00:35:09 +0000 (17:35 -0700)
(by calling the INT handler from TMR2 ISR code (too much interrupt latency when transmitting)

Signed-off-by: Werner Johansson <wj@xnk.nu>

wj-uni.asm

index c6e4840..69624e6 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
-;  No checksum checking is done on incoming packets\r
 ;  Investigate whether I actually have to save PCLATH in ISH, maybe save FSR? - Not saving any of them for now\r
-;  Move RS232 code into ISH\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.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
@@ -49,8 +72,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
@@ -155,12 +179,19 @@ 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
+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
@@ -229,8 +260,128 @@ 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\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
+; 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
@@ -300,6 +451,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
@@ -327,8 +482,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
@@ -345,6 +533,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
@@ -355,9 +545,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
@@ -389,7 +578,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
@@ -403,7 +591,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
@@ -421,7 +608,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
@@ -438,7 +624,6 @@ IRQINTParseNot0112
        btfss   DisplayStatus,7         ; If not displaying, skip this\r
        goto    IRQINTParseComplete\r
 \r
-;      clrf    UnilinkParity1\r
        call    ClearUnilinkBuffer\r
        movlw   70h                     ; Sending to Display Group\r
        addwf   UnilinkParity1,f\r
@@ -456,7 +641,6 @@ IRQINTParseNot0112
        movf    UnilinkParity1,w        ; Carry the parity forward\r
        movwf   UnilinkParity2M\r
 \r
-;      movlw   01h\r
        movf    DisplayStatus,w\r
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData1\r
@@ -466,14 +650,14 @@ IRQINTParseNot0112
        movlw   01h\r
        addwf   UnilinkParity2M,f\r
        movwf   UnilinkData3\r
+\r
 ;      movlw   0c0h\r
        movf    DisplayStatus,w\r
        andlw   0f0h\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
 \r
@@ -489,7 +673,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
@@ -526,28 +709,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
@@ -568,14 +750,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
@@ -606,7 +793,8 @@ 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
@@ -616,6 +804,36 @@ IRQINTParseNot02
        xorlw   087h\r
        bnz     IRQINTParseNot87\r
 \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 now when actually powering on/off the system???\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
@@ -625,6 +843,13 @@ IRQINTParseNot02
 \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
@@ -655,6 +880,8 @@ IRQINTParseNot90
        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
@@ -688,112 +915,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
-       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
-       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
@@ -801,6 +930,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
@@ -814,6 +944,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
@@ -853,9 +985,21 @@ Main
        movlw   high LookUp             ; Set the high PC bits to indicate data lookup page\r
        movwf   PCLATH\r
 \r
-       movlw   0ffh\r
+       movlw   0ffh                    ; Set infinite attenuation to begin with\r
        movwf   UnilinkAttenuation\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
@@ -907,14 +1051,17 @@ MainLoop
 \r
 MainDontPrintCmd\r
 \r
-; Default text (see data declarations for actual text)\r
-; UnilinkID @ 11-12\r
+; Default text\r
+; UnilinkID @ 13-14\r
 ; UnilinkAttenuation @ 16-17\r
-; UnilinkSelected @ 53-54\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+11                  ; DisplayRam 11\r
+       movlw   80h+13                  ; DisplayRam 13\r
        call    TxLCDB\r
        bsf     LCD_RS_BIT\r
 \r
@@ -930,7 +1077,7 @@ MainDontPrintCmd
        call    TxLCDHEX\r
 \r
        bcf     LCD_RS_BIT              ; LCD Command mode\r
-       movlw   80h+40h+13              ; DisplayRam 53\r
+       movlw   80h+28                  ; DisplayRam 28\r
        call    TxLCDB\r
        bsf     LCD_RS_BIT\r
 \r
@@ -938,6 +1085,22 @@ MainDontPrintCmd
        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
@@ -945,6 +1108,67 @@ MainDontPrintCmd
        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
@@ -985,52 +1209,26 @@ MainSkipScroll
 \r
 ; Display scroll part ends here...\r
 \r
-       movf    DataCount,w             ; Load bit counter (if 0 then byte is available)\r
-       skpz\r
-       goto    MainLoop\r
+;      movf    DataCount,w             ; Load bit counter (if 0 then byte is available)\r
+;      skpz\r
+;      goto    MainLoop\r
 \r
-       decf    DataCount,f             ; Set it non-zero\r
+;      decf    DataCount,f             ; Set it non-zero\r
 \r
-       movf    DataStore,w\r
-       call    BootTXB                 ; Send to terminal\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
@@ -1158,6 +1356,88 @@ TxLCDHEX
        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
@@ -1324,14 +1604,17 @@ DelayInner
        org     600h\r
 \r
 ; Default text\r
-; UnilinkID @ 11-12\r
+; UnilinkID @ 13-14\r
 ; UnilinkAttenuation @ 16-17\r
-; UnilinkSelected @ 53-54\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", "ID:xx Se", "xx dB at", "LCDPage3", "LCDPage4"\r
-       DT      " Unilink", "lect:xx ", "t Dsp:xx", " Right 3", " Right 4"\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
                \r
 LookUp movwf   PCL                     ; Go to it (this assumes PCLATH == 06h)\r
@@ -1381,7 +1664,7 @@ Bootstrap
        call    BootTXStr\r
 \r
 ;      movlw   0e8h                    ; Initialize timeout timer (e8 is about 3 secs)\r
-       movlw   0fdh                    ; 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
@@ -1634,14 +1917,14 @@ 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
+;              "WJBoot - press ESC to flash\x00"\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
 BootFlashText\r
+;              "\r\nSend INHX8 file now...\r\x00"\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
+;              "\r\nExiting loader\r\x00"\r
        DW      0x068a,0x22f8,0x34f4,0x34ee,0x33a0,0x366f,0x30e4,0x32f2,0x0680\r
-;      DE      "\r\nExiting loader\r\x00"\r
 \r
 \r
 ;----------------------------------------------------------------------\r
@@ -1649,5 +1932,12 @@ BootRunText
 \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