- title "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 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
; 0.2 First attempt at responding to the Anyone command\r
; 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
-Dcount equ 20h\r
-e_LEN equ 21h\r
-\r
-Counter equ 22h\r
-SlaveBreakState equ 23h ; Hold state and time-out information about slave break, indicates when it can happen\r
-Icount equ 2Dh ; Offset of string to print\r
-TxTemp equ 2Eh ; blahblah\r
-TxTemp2 equ 2Fh ; Blahblah2\r
-\r
-LCDWTmp equ 30h\r
-Dcount2 equ 31h\r
-temp equ 32h\r
-\r
-DataCount equ 33h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)\r
-DataStore equ 34h ; This is a kludge\r
-\r
-UnilinkSelected equ 3bh\r
-UnilinkBit equ 3ch ; This is my "bitmask" to be used for requests\r
-UnilinkID equ 3dh ; This is my Bus ID\r
-UnilinkCmdLen equ 3eh ; This gets updated with the actual packet length after CMD1 has been received\r
-UnilinkTXRX equ 3fh ; This is a pointer to the Unilink packet below, used with indirect addressing\r
-\r
-UnilinkRAD equ 40h ; Beginning of Unilink packet - the Receiving Address\r
-UnilinkTAD equ 41h ; Transmitter address\r
-UnilinkCMD1 equ 42h ; CMD1 byte\r
-UnilinkCMD2 equ 43h ; CMD2 byte\r
-UnilinkParity1 equ 44h ; First or only parity byte for short packets (6 bytes)\r
-UnilinkData1 equ 45h ; Extra data for medium/large packets, or zero for short packets\r
-UnilinkData2 equ 46h ;\r
-UnilinkData3 equ 47h ;\r
-UnilinkData4 equ 48h ;\r
-UnilinkData5 equ 49h ; Data5 if this is a large packet\r
-UnilinkParity2M equ 49h ; Parity2 shares the same byte if it's a medium sized packet\r
-UnilinkData6 equ 4ah ; Extra data for large packets, or zero for medium packets\r
-UnilinkData7 equ 4bh ;\r
-UnilinkData8 equ 4ch ;\r
-UnilinkData9 equ 4dh ;\r
-UnilinkParity2 equ 4eh ; Parity byte for large packets\r
-UnilinkZero equ 4fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)\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
+UnilinkCMD1 equ 52h ; CMD1 byte\r
+UnilinkCMD2 equ 53h ; CMD2 byte\r
+UnilinkParity1 equ 54h ; First or only parity byte for short packets (6 bytes)\r
+UnilinkData1 equ 55h ; Extra data for medium/large packets, or zero for short packets\r
+UnilinkData2 equ 56h ;\r
+UnilinkData3 equ 57h ;\r
+UnilinkData4 equ 58h ;\r
+UnilinkData5 equ 59h ; Data5 if this is a large packet\r
+UnilinkParity2M equ 59h ; Parity2 shares the same byte if it's a medium sized packet\r
+UnilinkData6 equ 5ah ; Extra data for large packets, or zero for medium packets\r
+UnilinkData7 equ 5bh ;\r
+UnilinkData8 equ 5ch ;\r
+UnilinkData9 equ 5dh ;\r
+UnilinkParity2 equ 5eh ; Parity byte for large packets\r
+UnilinkZero equ 5fh ; Should always be zero (possibly used to signal corrupt packets from slave to master?)\r
+\r
+UnilinkTimeout equ 60h ; Counts up every 0.5ms to "age out" faulty bytes clocked in\r
+UnilinkSelected equ 61h ; High bit is set when selected\r
+UnilinkBit equ 62h ; This is my "bitmask" to be used for requests\r
+UnilinkID equ 63h ; This is my Bus ID\r
+UnilinkCmdLen equ 64h ; This gets updated with the actual packet length after CMD1 has been received\r
+UnilinkTXRX equ 65h ; This is a pointer to the Unilink packet above, used with indirect addressing\r
+SlaveBreakState equ 66h ; Hold state and time-out information about slave break, indicates when it can happen\r
+DisplayStatus equ 67h ; What information will be put on the display next, bit 7 cleared if nothing\r
+Icount equ 68h ; Offset of string to print\r
+TxTemp equ 69h ; blahblah\r
+TxTemp2 equ 6ah ; Blahblah2\r
+LCDWTmp equ 6bh\r
+Dcount2 equ 6ch\r
+temp equ 6dh\r
+Dcount equ 6eh\r
+e_LEN equ 6fh\r
+\r
+Counter equ 70h\r
+DataCount equ 71h ; Temp storage for the bit counter used during bit shifts (Unilink TX/RX)\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
+RecvBuf equ 0a0h ; Buffer for received data from PC (32 bytes)\r
+RecvBufEnd equ 0bfh ;\r
+\r
subtitl "Startup"\r
page\r
;----------------------------------------------------------------\r
swapf STATUS,w ; Get the status register into w\r
clrf STATUS ; Zero out the status reg, gives Reg Bank0\r
movwf IRQSTATUS ; Store the STATUS reg\r
- movf PCLATH,w ; Get the PCLATH reg\r
+; Not using PCLATH for anything in the ISR right now\r
+; movf PCLATH,w ; Get the PCLATH reg\r
; movwf IRQPCLATH ; And store it\r
; 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
\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
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 a packet, 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
\r
; Check for 01 00 (Bus Re-Initialization)\r
movf UnilinkCMD2,w\r
-; xorlw 00h\r
bnz IRQINTParseNot0100\r
\r
- clrf UnilinkID ; Clear the existing Unilink ID, if any\r
- bcf BUSON_OUT_BIT ; Clear the cascade BUSON pin, not activated again until we have a new ID\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\r
+ goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though)\r
\r
IRQINTParseNot0100\r
\r
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
+ call ClearUnilinkBuffer ; Zero it out completely\r
\r
movlw 10h ; Sending to Master\r
+ addwf UnilinkParity1,f\r
movwf UnilinkRAD\r
movlw 0d0h ; I'm in the MD changer group\r
+ addwf UnilinkParity1,f\r
movwf UnilinkTAD\r
movlw 8ch ; Device discovery command reply\r
+ addwf UnilinkParity1,f\r
movwf UnilinkCMD1\r
- movlw 00h ; 00??\r
+ movlw 10h ; 00??\r
+ addwf UnilinkParity1,f\r
movwf UnilinkCMD2\r
- movlw 6ch ; Hard coded parity (!)\r
- movwf UnilinkParity1\r
+\r
+ movf UnilinkParity1,w\r
+ movwf UnilinkParity2M\r
+\r
movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!??\r
+ addwf UnilinkParity2M,f\r
movwf UnilinkData1\r
- movlw 2ch ; 2c??\r
+ movlw 0a8h ; 2c??\r
+ addwf UnilinkParity2M,f\r
movwf UnilinkData2\r
- movlw 22h ; 22??\r
+ movlw 17h ; 22??\r
+ addwf UnilinkParity2M,f\r
movwf UnilinkData3\r
- movlw 00h ; 00??\r
+ movlw 0a0h ; 00?? 0a0=10 disc?\r
+ addwf UnilinkParity2M,f\r
movwf UnilinkData4\r
- movlw 0deh ; Hard coded parity 2 (!)\r
- movwf UnilinkData5\r
- clrf UnilinkData6\r
+\r
goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet\r
\r
IRQINTParseNot0102\r
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
movwf UnilinkRAD\r
\r
addwf UnilinkParity1,f\r
movwf UnilinkCMD2\r
- clrf UnilinkData6\r
goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet\r
\r
IRQINTParseNot0112\r
\r
+; Check for 01 13 (Request Time poll)\r
+ movf UnilinkCMD2,w\r
+ xorlw 13h\r
+ bnz IRQINTParseNot0113\r
+\r
+ movf UnilinkRAD,w\r
+ xorwf UnilinkID,w ; Is it for me?\r
+ bnz IRQINTParseNot0113 ; Nope\r
+\r
+ btfss DisplayStatus,7 ; If not displaying, skip this\r
+ goto IRQINTParseComplete\r
+\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
+ 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 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
+ 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 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
+IRQINTParseNot0113\r
+\r
+; Check for 01 15 (Who sent the slave break?)\r
+ movf UnilinkCMD2,w\r
+ xorlw 15h\r
+ bnz IRQINTParseNot0115\r
+\r
+ btfss DisplayStatus,7 ; First of all check if there should be anything displayed\r
+ goto IRQINTParseComplete ; No, not at this time\r
+ \r
+ call ClearUnilinkBuffer\r
+ movlw 10h ; Sending to Master\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkRAD\r
+ movlw 18h ; Broadcast address sending in this special case\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkTAD\r
+ movlw 82h ; Who wants to talk reply command\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkCMD1\r
+\r
+ clrw\r
+ call Bit_Frig\r
+ addwf UnilinkParity1,f\r
+ movwf UnilinkCMD2\r
+\r
+ movf UnilinkParity1,w ; Carry the parity forward\r
+ movwf UnilinkParity2M\r
+\r
+ movlw 20h\r
+ call Bit_Frig\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData1\r
+ movlw 40h\r
+ call Bit_Frig\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData2\r
+ movlw 60h\r
+ call Bit_Frig\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData3\r
+ movlw 80h\r
+ call Bit_Frig\r
+ addwf UnilinkParity2M,f\r
+ movwf UnilinkData4\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
+; 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
+\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
+ goto Bit_Frig_Swap\r
+\r
+ movf UnilinkBit, 0\r
+ andlw 0x0F\r
+ return\r
+\r
+Bit_Frig_Swap:\r
+ swapf UnilinkBit, 0\r
+ andlw 0xF0\r
+ return\r
+\r
+IRQINTParseNot0115\r
+\r
IRQINTParseNot01\r
\r
; Check for CMD1 = 02h (Appoint)\r
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
movwf UnilinkRAD\r
movlw 8ch ; Device discovery command again\r
addwf UnilinkParity1,f\r
movwf UnilinkCMD1\r
- movlw 00h\r
+ movlw 10h\r
addwf UnilinkParity1,f\r
movwf UnilinkCMD2\r
\r
movlw 24h\r
addwf UnilinkParity2M,f\r
movwf UnilinkData1\r
- movlw 2ch ; My internal MD sends 1c here... (external/internal difference)\r
+ movlw 0a8h ; My internal MD sends 1c here... (external/internal difference)\r
addwf UnilinkParity2M,f\r
movwf UnilinkData2\r
- movlw 22h\r
+ movlw 17h\r
addwf UnilinkParity2M,f\r
movwf UnilinkData3\r
- movlw 00h\r
+ movlw 0a0h ; 0a0=10disc\r
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
+; 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
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
+IRQINTParseNot9010\r
+\r
+\r
+IRQINTParseNot90\r
+\r
; Check for CMD1 = f0h (Source Select)\r
movf UnilinkCMD1,w\r
xorlw 0f0h\r
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
+ movlw 81h\r
+ movwf DisplayStatus\r
goto IRQINTParseComplete\r
\r
IRQINTParseF0Deselect\r
\r
- bcf UnilinkSelected,7 ; Now we're de-selected\r
- goto IRQINTParseComplete\r
-\r
-IRQINTParseNotF0\r
-\r
-IRQINTParseComplete\r
-\r
-; The CPU ends up here when parsing is complete and it's not interested in sending any reply back to the master\r
-; (that's why we clear out all the packet buffer bytes)\r
-; TODO: Replace this with an FSR access to save space and make the code neater\r
-\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
+ bcf UnilinkSelected,7 ; Now we're de-selected\r
+ bcf DisplayStatus,7\r
+ goto IRQINTParseComplete\r
+\r
+IRQINTParseNotF0\r
+\r
+IRQINTParseComplete\r
+\r
+; The code ends up here when parsing is complete and it's not interested in sending any reply back to the master\r
+; (that's why we clear out all the packet buffer bytes)\r
+\r
+ call ClearUnilinkBuffer\r
+\r
IRQINTParseBypassClear\r
\r
movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer\r
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
+ return\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 5ms (10 loops)\r
-; If a packet would be received 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
+; ClearUnilinkStatus - Zeroes out the Unilink state (used when initializing)\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
+ClearUnilinkStatus\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
- btfss 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
+ 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
\r
-IRQTMR2HighDataOK\r
-IRQTMR2LowDataOK\r
- bsf SlaveBreakState,6 ; Set the "first time around" bit\r
- btfss SlaveBreakState,4 ; Only increment to 0x10\r
- incf SlaveBreakState,f\r
- \r
- movf SlaveBreakState,w\r
- andlw 1fh\r
+ bsf STATUS,RP0 ; Reg bank 1\r
+ bsf DATA_BIT ; Make sure data is tristated\r
+ bcf STATUS,RP0 ; Reg bank 0\r
\r
- btfss SlaveBreakState,7 ; Checking whether it's low or high\r
- goto IRQTMR2FoundLow\r
+ movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer\r
+ movwf UnilinkTXRX ; Store it - this way the next byte that gets received goes into RAD\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
+ clrf UnilinkCmdLen ; No command length while waiting for a new packet\r
\r
-; Issue slave break here\r
- incf Counter,f\r
-; clrf SlaveBreakState\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
+ clrf SlaveBreakState ; Slave Break Processing has to start all over\r
\r
-IRQTMR2FoundLow\r
- xorlw 7\r
- skpz\r
- goto IRQAfterTMR2\r
- movlw 80h ; Prepare for state 2, looking for data line high\r
- movwf SlaveBreakState\r
- goto IRQAfterTMR2\r
+ return\r
\r
-IRQTMR2SlaveBreak\r
- movf SlaveBreakState,w\r
- andlw 01fh\r
- xorlw 4\r
- skpz\r
- goto IRQAfterTMR2\r
- bsf STATUS,RP0\r
- bsf DATA_BIT\r
- bcf STATUS,RP0\r
- clrf SlaveBreakState\r
-\r
-IRQAfterTMR2\r
- bcf PIR1,TMR2IF ; Clear the IRQ source bit to re-enable TMR2 interrupts again\r
-\r
-IRQNotTMR2\r
+;----------------------------------------------------------------\r
+; ClearUnilinkBuffer - Zeroes out the Unilink packet buffer\r
\r
-; Finally restore CPU state and return from the ISR\r
+ClearUnilinkBuffer\r
\r
-; If I have to save the FSR in the beginning I also need to restore it here...\r
+; TODO: Replace this with an FSR access to save space and make the code neater\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
-; 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
subtitl "Main loop"\r
page\r
\r
;----------------------------------------------------------------\r
-; Main program begins here. [Called after bootloader, lcdinit and irqinit...]\r
+; Main program begins here. [Called after bootloader, lcdinit and irqinit...]\r
+; Here all other house keeping tasks are performed, like displaying info on the LCD.. \r
\r
-; org 100h ; Maybe not force this to a specific address later\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
\r
; movlw '0'\r
movf SlaveBreakState,w\r
+ andlw 80h\r
btfsc PORTB,0 ; Test CLK\r
movlw 'C'\r
call TxLCDB\r
\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 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
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
\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
\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
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
call TxLCD ; And send that one as well\r
\r
return\r
+\r
;----------------------------------------------------------------\r
; RxLCDB - recv a byte from the LCD\r
\r
; Data can be stored between 600 and 6ffh...\r
\r
org 600h\r
-StartUpText1\r
- DT "----- WJ UniLink"\r
- \r
+\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
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
\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
\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