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