X-Git-Url: http://git.xnk.nu/?p=wj-unilink.git;a=blobdiff_plain;f=wj-uni.asm;h=c1623e0be2b719b2130a5770bf1139bf591070ed;hp=130df184effb4335f816a1a185b2bf0899af7937;hb=HEAD;hpb=dbbd88433e0f81f2423d67eadfd1cb4685ffef89 diff --git a/wj-uni.asm b/wj-uni.asm index 130df18..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 + 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,22 +33,31 @@ ;---------------------------------------------------------------- ; TODO ;---------------------------------------------------------------- -; BUSON OUT control isn't implemented -; No checksum checking is done on incoming packets -; Investigate whether we 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 Bus re-initialize command -; Implement lots of other Unilink commands +; 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 ; 0.1 Receives Unilink data OK, relays it to serial -; 0.0 Very first "Fucking No Work!" version +; 0.0 Very first "F**king No Work!" version ;---------------------------------------------------------------- ; I/O LAYOUT @@ -37,15 +67,16 @@ ; Unilink BUSON OUT (blue) connected to RC4 (this is for daisy-chaining) ; Unilink CLK (yellow) connected to RB0/INT (Interrupt pin) ; Unilink RST (lilac) connected to RA4 -; LCD RS connected to pin RB1 +; LCD RS connected to pin RB1 (The LCD is a standard 16x1 char HD44780 compatible unit) ; LCD RW connected to pin RB2 ; LCD E connected to pin RB3 ; LCD DB4-DB7 connected to RB4-RB7 ; 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 @@ -66,51 +97,83 @@ ;---------------------------------------------------------------- ; FILE REGISTER USAGE ;---------------------------------------------------------------- -Dcount equ 20h -e_LEN equ 21h -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 ;---------------------------------------------------------------- -; Power up/Reset starting point [den rulerar] +; Power up/Reset starting point org 0 ; Start at the beginning of memory (the reset vector) call Bootstrap ; Call Flash Load routine @@ -121,31 +184,269 @@ IRQW equ 7fh ; subtitl "IRQ Handler" ;---------------------------------------------------------------- ; Interrupt handler always starts at addr 4 -; In order to save one instruction cycle we put the actual code here directly instead of a goto instruction +; In order to reduce the INT latency the actual code is put here directly instead of using a goto instruction. +; Also because of the real-time requirements for clocking data onto the Unilink bus the first check in the ISR +; is to see whether the Unilink clock rise was the reason for the interrupt. This results in a "clock rise to +; bit ready" time of less than 30 instruction cycles, should be plenty of spare time waiting for clock to go low +; again after that. Other interrupts might introduce latencies, but let's see how this works.. org 4 ; ISR vector is at address 4 movwf IRQW ; Save W swapf STATUS,w ; Get the status register into w - clrf STATUS ; Zero out the status reg, gives us Bank0 all the time + clrf STATUS ; Zero out the status reg, gives Reg Bank0 movwf IRQSTATUS ; Store the STATUS reg - movf PCLATH,w ; Get the PCLATH reg - movwf IRQPCLATH ; And store it - clrf PCLATH ; Go to low memory +; 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 - -; If there's activity on the clock line (the clock goes high) we stay in here until we have clocked eight bits -; - this saves us a lot of context switching (and it's just a few hundred cpu cycles after all (20us*8 bits= -; 160us=800 instruction cycles (5 MIPS @ 20MHz), not even a problem for serial input if we're not getting more than -; 6250 bytes per second from the UART, and the 2-byte FIFO somehow fills up (this should be impossible even @ 115200 -; as we're only calling this blocking INT handler a maximum of 1000 times per second, halting INT's for 1/6250 of a second, -; this gives the CPU ample of time to deal with all bytes from the USART. I'm checking the OERR (Serial Overrun) bit -; to catch this though.. Note that this piece of code does both TX and RX at the same time (in order to receive packets -; one has to make sure that the packet buffer is zeroed out before entering here, otherwise collisions will occur.. -; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's a -; interrupt driven software based USART - by trigging the interrupt on the rising edge we buy us some extra time here + 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 +; cycles (5 MIPS @ 20MHz), not even a problem for serial input if it's not receiving more than 6250 bytes per second, and the +; 2-byte FIFO somehow fills up (this should be impossible even @ 115200 as this blocking INT handler only runs a maximum of +; 1000 times per second, halting INT's for 1/6250 of a second - this gives the CPU ample of time to deal with all bytes from +; the USART. I should check the OERR (Serial Overrun) bit to catch this though.. Note that this piece of code does both TX +; and RX at the same time (in order to receive packets one has to make sure that the packet buffer is zeroed out before entering +; here, otherwise collisions will occur.. +; According to my logic analyzer this implementation is pretty decent when it comes to timing, even though it's an +; interrupt driven "USART" implemented in software - by trigging the interrupt on the rising edge there's some extra margin here ; (the clock goes high 10us before the master clocks the bit in (on the falling edge), that should be plenty of time..) movlw 8 ; Loop through the 8 bits @@ -173,14 +474,14 @@ IRQINTCLKWaitLow btfsc PORTB,0 ; Wait for clock to go low goto IRQINTCLKWaitLow - clrc ; Clear carry (this way the DataStore byte doesn't have to be cleared before) + clrc ; Clear carry btfss PORTC,3 ; Test DATA setc ; Set carry if data is LOW (data is inverted!) - rlf INDF,f ; Shift it into our accumulator + rlf INDF,f ; Shift it into the "accumulator" decfsz DataCount,f ; Loop once more perhaps? goto IRQINTCLKWaitHigh ; Yes, again! - goto IRQINTRecvDone ; No we're done, don't check for clock to go high again + goto IRQINTRecvDone ; No it's done, don't check for clock to go high again IRQINTCLKWaitHigh btfss PORTC,2 ; Check for BUSON @@ -190,7 +491,7 @@ IRQINTCLKWaitHigh goto IRQINTBitSet ; Loop again ; Successfully received a byte here, run it through a state machine to figure out what to do -; (several possibilites exists here: +; (several possibilites exists here): ;;;;;; If more than 1.1ms has passed since last receive, reset receive counter to zero ; If receive counter is zero and the received byte is a zero byte, discard it ; Otherwise store the byte in our receive buffer and increment receive counter @@ -203,7 +504,12 @@ IRQINTCLKWaitHigh ; the packet and take appropriate action. IRQINTRecvDone - movf UnilinkTXRX,w ; Find out which byte we got + 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 movf UnilinkRAD,w ; Get the first byte received @@ -230,10 +536,43 @@ IRQINTRecvNotCMD1 andlw 0fh ; and mask - this results in a zero result when finished receiving bnz IRQINTRecvIncomplete ; Packet not ready yet -; Here we actually have received a packet, should check the checksum(s) as well, but I don't care right now -; (I need music in my car! :)) -; This is inefficient, I know, I'll improve it later... (Not that it matters, we have plenty of time here -; (there can't be any more communication for another 4.8ms)) +; 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)) ; Unilink command parser: @@ -242,36 +581,58 @@ IRQINTRecvNotCMD1 xorlw 01h bnz IRQINTParseNot01 +; Check for 01 00 (Bus Re-Initialization) + movf UnilinkCMD2,w + bnz IRQINTParseNot0100 + + call ClearUnilinkStatus ; Clear everything Unilink (ID, BUSON_OUT) + + incf UnilinkReInits,f ; increment the debug counter + + goto IRQINTParseComplete ; Don't send any reply to this (clear the packet buffer though) + +IRQINTParseNot0100 + ; Check for 01 02 (Anyone) movf UnilinkCMD2,w xorlw 02h 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 ; We don't want to clear the data, we want to send what's in the buffer next time + + goto IRQINTParseBypassClear ; Don't clear the data, the buffer will be sent as the next packet IRQINTParseNot0102 @@ -281,10 +642,10 @@ IRQINTParseNot0102 bnz IRQINTParseNot0112 movf UnilinkRAD,w - xorwf UnilinkID,w ; Is it for us? - bnz IRQINTParseNot0112 ; nope + xorwf UnilinkID,w ; Is it for me? + bnz IRQINTParseNot0112 ; Nope - clrf UnilinkParity1 + call ClearUnilinkBuffer movlw 10h ; Sending to Master addwf UnilinkParity1,f movwf UnilinkRAD @@ -295,17 +656,384 @@ IRQINTParseNot0102 addwf UnilinkParity1,f movwf UnilinkCMD1 - movlw 80h ; We're idle unless selected + movlw 80h ; Idle unless selected btfsc UnilinkSelected,7 clrw addwf UnilinkParity1,f movwf UnilinkCMD2 - clrf UnilinkData6 - goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send! + 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) @@ -313,12 +1041,20 @@ IRQINTParseNot01 xorlw 02h bnz IRQINTParseNot02 - movf UnilinkRAD,w ; Get the ID the master has given us + 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 @@ -328,7 +1064,7 @@ IRQINTParseNot01 movlw 8ch ; Device discovery command again addwf UnilinkParity1,f movwf UnilinkCMD1 - movlw 00h + movlw 10h addwf UnilinkParity1,f movwf UnilinkCMD2 @@ -338,129 +1074,194 @@ IRQINTParseNot01 movlw 24h addwf UnilinkParity2M,f movwf UnilinkData1 - movlw 2ch ; My internal MD sends 1c here... (external/internal or 1/10 disc 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 - goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send! + 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 + + bsf RS232_RI_BIT ; Set this to make RI pin go low (after RS-232 levels) + goto IRQINTParseComplete + +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 - xorwf UnilinkID,w ; Check if it's selecting us + 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 - goto IRQINTParseNotF0 +; bsf DisplayStatus,7 + movlw 81h + movwf DisplayStatus + goto IRQINTParseComplete IRQINTParseF0Deselect bcf UnilinkSelected,7 ; Now we're de-selected - goto IRQINTParseNotF0 + bcf DisplayStatus,7 + goto IRQINTParseComplete IRQINTParseNotF0 -; We end up here when parsing is complete and we're not interested in sending any reply back to the master +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) -; 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 + call ClearUnilinkBuffer IRQINTParseBypassClear 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 - clrf UnilinkCmdLen ; No command length as we're waiting for a new packet + clrf UnilinkCmdLen ; No command length while waiting for a new packet IRQINTRecvIncomplete IRQINTRecvNullByte - movf INDF,w - movwf DataStore ; Store it so our non-irq code can snoop +; movf INDF,w +; movwf DataStore ; Store it so the non-irq code can snoop IRQAfterINT - bcf INTCON,INTF ; Clear our IRQ source bit so we can receive new bits again + bcf INTCON,INTF ; Clear the IRQ source bit to re-enable INT interrupts again IRQNotINT - -; Finally restore CPU state and return from the ISR - 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 - - - subtitl "Main loop" - page + return ;---------------------------------------------------------------- -; Data can be stored between here and 100h... - -StartUpText1 - DT "----- WJ UniLink" - -LookUp movwf PCL ; Go to it +; ClearUnilinkStatus - Zeroes out the Unilink state (used when initializing) -;---------------------------------------------------------------- -; Main program begins here. [Called after bootloader, lcdinit and irqinit...] +ClearUnilinkStatus - org 100h -Main + 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 - bsf RS232_RI_BIT ; We want RI to be high (inverted logic, not set) - bcf BUSON_OUT_BIT ; But we don't want BUSON_OUT on just yet, we need to be appointed first + bsf STATUS,RP0 ; Reg bank 1 + bsf DATA_BIT ; Make sure data is tristated + bcf STATUS,RP0 ; Reg bank 0 - bsf STATUS,RP0 ; Select bank 1 + 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 - bcf RS232_RI_BIT ; Both bits should be outputs at least - bcf BUSON_OUT_BIT ; + clrf UnilinkCmdLen ; No command length while waiting for a new packet -; bcf STATUS,RP0 -; bsf STATUS,RP0 + clrf SlaveBreakState ; Slave Break Processing has to start all over - bsf TXSTA,TXEN ; Enable UART TX - bcf STATUS,RP0 ; Back to bank 0 + return + +;---------------------------------------------------------------- +; ClearUnilinkBuffer - Zeroes out the Unilink packet buffer - bsf RCSTA,SPEN ; Enable serial port - bsf RCSTA,CREN ; Enable UART RX +ClearUnilinkBuffer -; Replace this with an FSR access - clrf UnilinkSelected - clrf UnilinkID - clrf UnilinkBit - clrf UnilinkCmdLen +; TODO: Replace this with an FSR access to save space and make the code neater clrf UnilinkRAD clrf UnilinkTAD clrf UnilinkCMD1 @@ -478,184 +1279,468 @@ Main clrf UnilinkParity2 clrf UnilinkZero - clrf DataStore - movlw UnilinkRAD ; Get the pointer to the first byte in the receive buffer - movwf UnilinkTXRX ; Store it + return - movlw StartUpText1 - call TxLCD16B -retry - - bcf LCD_RS_BIT ;Command mode - movlw 80h ;DisplayRam 0 + + subtitl "Main loop" + page + +;---------------------------------------------------------------- +; Main program begins here. [Called after bootloader, lcdinit and irqinit...] +; Here all other house keeping tasks are performed, like displaying info on the LCD.. + +Main + movlw high LookUp ; Set the high PC bits to indicate data lookup page + movwf PCLATH + + 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 + bsf LCD_RS_BIT - movlw '0' - btfsc PORTA,4 ; Test RST + movlw low DefaultText1 + movwf Icount + movlw 80 + movwf e_LEN + call TxLCD8BLoop ; Send 80 bytes to the LCD + +MainLoop + + bcf LCD_RS_BIT ; LCD Command mode + movlw 80h ; DisplayRam 0 + call TxLCDB + bsf LCD_RS_BIT + +; movlw '0' + movf Counter,w ; Debug timer + btfsc PORTA,4 ; Test RST movlw 'R' call TxLCDB - movlw '0' - btfsc PORTB,0 ; Test CLK +; movlw '0' + movf SlaveBreakState,w + andlw 80h + btfsc PORTB,0 ; Test CLK movlw 'C' call TxLCDB movlw '0' - btfsc PORTC,2 ; Test BUSON-IN + btfsc PORTC,2 ; Test BUSON-IN movlw 'B' call TxLCDB movlw '0' - btfsc PORTC,3 ; Test DATA + btfsc PORTC,3 ; Test DATA movlw 'D' call TxLCDB movf UnilinkCmdLen,w - bz DontPrintCmd + bz MainDontPrintCmd addlw '0' call TxLCDB -DontPrintCmd - movf DataCount,w ; Load bit counter (if 0 then byte is available) - skpz - goto retry +MainDontPrintCmd - decf DataCount,f ; Set it non-zero +; 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) - movf DataStore,w - call BootTXB ; Send to terminal - goto retry + 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 -; movlw StartUpText1 -; call TxLCD16B -; call LongDelay + movf UnilinkAttenuation,w + call TxLCDHEX -; bsf PORTA,4 ; turn off LED + bcf LCD_RS_BIT ; LCD Command mode + movlw 80h+28 ; DisplayRam 28 + call TxLCDB + bsf LCD_RS_BIT -; movlw StartUpText2 -; call TxLCD16B -; call LongDelay + movf UnilinkSelected,w + call TxLCDHEX -; bcf PORTA,4 ; turn on LED + bcf LCD_RS_BIT ; LCD Command mode + movlw 80h+38 ; DisplayRam 38 + call TxLCDB + bsf LCD_RS_BIT -; movlw StartUpText3 -; call TxLCD16B -; call LongDelay + movf UnilinkReInits,w + call TxLCDHEX -; goto retry + 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 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 + +; Display scroll part ends here... + + 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 + + call ClearUnilinkStatus + call ClearUnilinkBuffer + +; Fix the output state of RI and BUSON_OUT to a safe default + + bsf RS232_RI_BIT ; RS232 RI should be inactive (inverted logic, a set bit here gives a negative output) + bcf BUSON_OUT_BIT ; BUSON_OUT should be disabled for now, must be appointed first + + movlw 06h ; Timer2 enabled + 1/16 prescaler + movwf T2CON + bsf STATUS,RP0 ; Reg bank 1 + + movlw 09ch ; Timer PR2 reg giving 2000 interrupts per second + movwf PR2 + + bcf RS232_RI_BIT ; Both bits should be outputs + bcf BUSON_OUT_BIT ; + +; The default behavior of RB0/INT is to interrupt on the rising edge, that's what we use... ; bcf OPTION_REG,INTEDG ; We want RB0 to give us an IRQ on the falling edge + 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 + bcf STATUS,RP0 ; Back to bank 0 + + bsf RCSTA,SPEN ; Enable serial port + bsf RCSTA,CREN ; Enable UART RX + return ;---------------------------------------------------------------- ; Initialize LCD Controller... LCDInit - clrf PORTB - bsf STATUS,RP0 ; Hi Bank - movlw 001h ; All but RB0 are outputs. - movwf TRISB ; Yep - bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up - bcf STATUS,RP0 ; Restore Lo Bank + clrf PORTB ; First clear PortB data register + bsf STATUS,RP0 ; Reg bank 1 + movlw 001h ; All but RB0 are outputs. + movwf TRISB ; + + bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up + bcf STATUS,RP0 ; Restore Reg bank 0 -; bcf PORTA,4 ; turn on LED +; This is a standard reset sequence for the LCD controller -; movlw 44 ; Should be 16ms delay - movlw 255 ; Should be 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 12 ; Should be 5ms delay - movlw 255 ; Should be 16ms delay + movlw 3 ; Write 3 to the LCD + call TxLCD ; Send to LCD + 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 + movlw 3 ; Write 3 to the LCD call TxLCD -; movlw 12 ; Should be 16ms delay - movlw 255 ; Should be 16ms delay + movlw 10 ; Need to delay for at least 100us, let's go for 1ms delay call DelayW - movlw 3 ; Write 3 to the LCD + movlw 3 ; Write 3 to the LCD call TxLCD -; movlw 44 - movlw 255 ; Should be 16ms delay + movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay call DelayW - movlw 2 ;\ - call TxLCD ; | 4-bit interface -; movlw 55 ; | After this we are ready to ROCK! - movlw 255 ; Should be 16ms delay - call DelayW ;/ + movlw 2 ; 4-bit interface requested + call TxLCD ; + movlw 10 ; Need to delay for at least 40us, let's go for 1ms delay + call DelayW ; + +; Reset sequence ends here +; From this point no delays are needed, now the BUSY bit is valid and the bus I/F is 4 bits + + movlw 28h ; Function Select + 4-bit bus + 2-line display + call TxLCDB -; bsf PORTA,4 ; turn off LED + movlw 0ch ; Display Control + LCD On (No cursor) + call TxLCDB - movlw 28h ; Some random commands :))) - call TxLCDB + movlw 01h ; Clear Display + call TxLCDB - movlw 0ch ; hmmm - call TxLCDB + movlw 06h ; Auto Increment cursor position + call TxLCDB - movlw 01h ; hmmm - call TxLCDB + bsf LCD_RS_BIT ; Accept data - movlw 06h ; hmmm - call TxLCDB - return ;---------------------------------------------------------------- -; LongDelay - Well, guess that for yourself... - -LongDelay -; btfss PORTB,6 ; Talk to da PC? -; goto PCTalk ; Oh yeah... - - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - movlw 255 - call DelayW - 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 @@ -663,15 +1748,15 @@ LongDelay TxLCD16B movwf Icount - bcf LCD_RS_BIT - movlw 80h ;DisplayRam 0 + bcf LCD_RS_BIT + movlw 80h ; DisplayRam 0 call TxLCDB - bsf LCD_RS_BIT + bsf LCD_RS_BIT call TxLCD8B - bcf LCD_RS_BIT - movlw 80h+40 ;DisplayRam 40 (row 2) + bcf LCD_RS_BIT + movlw 80h+40 ; DisplayRam 40 (row 2) call TxLCDB - bsf LCD_RS_BIT + bsf LCD_RS_BIT call TxLCD8B return @@ -680,55 +1765,57 @@ TxLCD16B ; Send a string to the LCD. TxLCD8B -; movwf Icount ; Icount = W +; movwf Icount ; Icount = W movlw 8 - movwf e_LEN ; Move to e_LEN + 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 + incf Icount,f ; ...else ++Icount (table index) + call TxLCDB ; Send out the byte decfsz e_LEN,f - goto Txm_lp + goto TxLCD8BLoop return ;---------------------------------------------------------------- ; TxLCDB - send a byte to the LCD TxLCDB - movwf TxTemp ; Store byte to send for a while... + movwf TxTemp ; Store byte to send for a while... - bcf temp,0 ; Clear my temp bit - btfss LCD_RS_BIT ; Check if we try the correct reg + bcf temp,0 ; Clear my temp bit + btfss LCD_RS_BIT ; Check if we try the correct reg goto RxNoProb bcf LCD_RS_BIT - bsf temp,0 ; Indicate RS change + bsf temp,0 ; Indicate RS change RxNoProb NotReady - call RxLCDB ; Receive byte from LCD, status reg + call RxLCDB ; Receive byte from LCD, status reg andlw 80h - btfss STATUS,Z ; If the bit was set, the zero flag is not + skpz ; If the bit was set, the zero flag is not goto NotReady - btfsc temp,0 ; If we had to clear RS reset it now + btfsc temp,0 ; If we had to clear RS reset it now bsf LCD_RS_BIT - swapf TxTemp,w ; Hi nibble of data to send in lo w bits - call TxLCD ; Send them first... - movf TxTemp,w ; Then we have the low nibble in low w bits - call TxLCD ; And send that one as well + swapf TxTemp,w ; Hi nibble of data to send in lo w bits + call TxLCD ; Send them first... + movf TxTemp,w ; Then we have the low nibble in low w bits + call TxLCD ; And send that one as well return + ;---------------------------------------------------------------- ; RxLCDB - recv a byte from the LCD RxLCDB - call RxLCD ; Receive the high nibble + call RxLCD ; Receive the high nibble movwf LCDWTmp - swapf LCDWTmp,f ; Swap it back to file - call RxLCD ; Receive the low nibble - addwf LCDWTmp,w ; Put the nibbles together and return in W + swapf LCDWTmp,f ; Swap it back to file + call RxLCD ; Receive the low nibble + addwf LCDWTmp,w ; Put the nibbles together and return in W return @@ -736,92 +1823,131 @@ RxLCDB ; TxLCD - send a nibble to the LCD TxLCD - movwf LCDWTmp ; Write nibble to tmp - bcf LCD_DB4_BIT ; Clear previous data - bcf LCD_DB5_BIT ; - bcf LCD_DB6_BIT ; - bcf LCD_DB7_BIT ; + movwf LCDWTmp ; Write nibble to tmp + bcf LCD_DB4_BIT ; Clear previous data + bcf LCD_DB5_BIT ; + bcf LCD_DB6_BIT ; + bcf LCD_DB7_BIT ; - btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT + btfsc LCDWTmp,0 ; Test bit 0, transfer a set bit to LCD PORT bsf LCD_DB4_BIT - btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT + btfsc LCDWTmp,1 ; Test bit 1, transfer a set bit to LCD PORT bsf LCD_DB5_BIT - btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT + btfsc LCDWTmp,2 ; Test bit 2, transfer a set bit to LCD PORT bsf LCD_DB6_BIT - btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT + btfsc LCDWTmp,3 ; Test bit 3, transfer a set bit to LCD PORT bsf LCD_DB7_BIT - bsf LCD_E_BIT ; And set E to clock the data into the LCD module - nop ; Let it settle - bcf LCD_E_BIT ; And clear the Enable again. - return ; Returns without modifying W + bsf LCD_E_BIT ; And set E to clock the data into the LCD module + nop ; Let it settle + bcf LCD_E_BIT ; And clear the Enable again. + return ; Returns without modifying W ;---------------------------------------------------------------- ; RxLCD - recv a nibble from the LCD RxLCD - clrw ; Clear W register, return data in lower 4 bits + clrw ; Clear W register, return data in lower 4 bits - bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed + bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed - bsf LCD_DB4_BIT ; This sets the port bit as an input + bsf LCD_DB4_BIT ; This sets the port bit as an input bsf LCD_DB5_BIT bsf LCD_DB6_BIT bsf LCD_DB7_BIT - bcf STATUS,RP0 ; Back at reg bank 0 - bsf LCD_RW_BIT ; Set Read mode for the LCD - bsf LCD_E_BIT ; And set E to clock the data out of the LCD module - nop ; Let the bus settle - btfsc LCD_DB4_BIT ; Transfer a set port bit into W + bcf STATUS,RP0 ; Back at reg bank 0 + + bsf LCD_RW_BIT ; Set Read mode for the LCD + bsf LCD_E_BIT ; And set E to clock the data out of the LCD module + nop ; Let the bus settle + btfsc LCD_DB4_BIT ; Transfer a set port bit into W addlw 1 - btfsc LCD_DB5_BIT ; Transfer a set port bit into W + btfsc LCD_DB5_BIT ; Transfer a set port bit into W addlw 2 - btfsc LCD_DB6_BIT ; Transfer a set port bit into W + btfsc LCD_DB6_BIT ; Transfer a set port bit into W addlw 4 - btfsc LCD_DB7_BIT ; Transfer a set port bit into W + btfsc LCD_DB7_BIT ; Transfer a set port bit into W addlw 8 - bcf LCD_E_BIT ; And clear the Enable again. - bcf LCD_RW_BIT ; Set Write mode for the LCD + bcf LCD_E_BIT ; And clear the Enable again. + bcf LCD_RW_BIT ; Set Write mode for the LCD + + bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed - bsf STATUS,RP0 ; Select 2nd reg bank, now TRIS regs can be accessed - bcf LCD_DB4_BIT ; Set the port as an output again - bcf LCD_DB5_BIT ; - bcf LCD_DB6_BIT ; - bcf LCD_DB7_BIT ; - bcf STATUS,RP0 ; Back at reg bank 0 + bcf LCD_DB4_BIT ; Set the port as an output again + bcf LCD_DB5_BIT ; + bcf LCD_DB6_BIT ; + bcf LCD_DB7_BIT ; - return ; Returns with data in W + bcf STATUS,RP0 ; Back at reg bank 0 + + return ; Returns with data in W ;---------------------------------------------------------------------- -; Delay routines (one iteration=3 cycles. That is 0.366211ms @32kHz) -; 2.73* # of ms is good... - -DelayW movwf Dcount ; Set delay counter - clrf Dcount2 - decf Dcount2,f -DelayLp decfsz Dcount,f - goto DelayIn - retlw 0 -DelayIn decfsz Dcount2,f - goto DelayIn2 - decf Dcount2,f - goto DelayLp -DelayIn2 goto $+1 - goto $+1 - goto $+1 - goto DelayIn +; Delay routines (non-interrupt based, therefore not even close to reliable) +; W=10 gives ~ 1ms of delay +; 1ms=5000 instructions wasted, 100us=500 cycles +; Maximum time waited will be 256*100us=25.6ms + +DelayW + movwf Dcount ; Set delay counter, number of 100us periods to wait + +DelayOuter + movlw 0a5h ; This gives 165 iterations of the inner loop, wastes 495 cycles + these two + one more + movwf Dcount2 ; exiting the loop + 3 more for the outer loop = 501 cycles for every Dcount +DelayInner + decfsz Dcount2,f ; 1 cycle (or two when exiting the loop) + goto DelayInner ; 2 cycles + decfsz Dcount,f ; Now decrement number of 100us periods and loop again + goto DelayOuter + return + + +;---------------------------------------------------------------- +; Data can be stored between 600 and 6ffh... + + org 600h + +; 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) + subtitl "Bootstrap/Bootloader code" page ;---------------------------------------------------------------------- -; Bootstrap code - Allows PIC to flash itself with data from async port +; Bootstrap code - Allows PIC to flash itself with data from the async port. +; Accepts a standard INHX8 encoded file as input, the only caveat is that the code is slow when writing to memory +; (we have to wait for the flash to complete), and therefore care has to be taken not to overflow the RS232 receiver +; (one good way of solving that is to wait for the echo from the PIC before sending anything else) +; Both program memory and Data EEPROM memory can be programmed, but due to hardware contraints the configuration +; register can't be programmed. That means that any references to the config register in the hex file will be ignored. +; ; Startup @9600bps - org 700h ; Place the boot code at the top of memory +; RAM usage for the bootstrap code -BootRXState equ 7fh ; What are we waiting for @RX BootBits equ 7eh ; bit0 1=write 0=read, bit1 1=PGM 0=EE, bit2 0=normal 1=no-op when prog BootAddrL equ 7dh BootAddrH equ 7ch @@ -834,12 +1960,10 @@ BootNumBytes equ 76h BootDataVL equ 75h BootDataVH equ 74h BootHEXTemp equ 73h -BootStrTemp equ 72h -Bootstrap - movlw 7 - movwf PCLATH + org 738h ; Place the boot code at the top of memory (currently the loader is exactly 200 bytes) +Bootstrap bsf STATUS,RP0 ; Access bank 1 bsf TXSTA,TXEN ; Enable UART TX movlw 31 ; Divisor for 9k6 @ 20MHz Fosc @@ -849,12 +1973,11 @@ Bootstrap bsf RCSTA,SPEN ; Enable serial port bsf RCSTA,CREN ; Enable UART RX -; clrf BootRXState ; Waiting for command - - movlw BootStartText + movlw low BootStartText ; Send boot banner to the serial port call BootTXStr - movlw 0e8h +; 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 @@ -871,11 +1994,11 @@ BootTimeout goto BootTimeout call BootRXB xorlw 27 ; ESC - btfss STATUS,Z - goto BootTimeout ; If it wasn't space, wait for another key + skpz + goto BootTimeout ; If it wasn't ESC, wait for another key BootFlash - movlw BootFlashText + movlw low BootFlashText ; OK, flashing it is, send "start" text to serial port call BootTXStr bsf BootBits,1 @@ -944,21 +2067,12 @@ BootWrite movf BootDataVH,w movwf BootDataH ; Have to put the new H byte data in as well -; movlw '+' -; call BootTXB ; Send progword indicator - bsf BootBits,0 call BootEE ; Write directly into program mem ; Here a verify can take place, the read-back results are now in DataL/H - movlw '+' - goto BootWriteDone - BootWriteSkip - movlw '-' -BootWriteDone - call BootTXB incf BootAddrL,f ; Advance counter to next addr skpnz @@ -967,30 +2081,24 @@ BootWriteDone decfsz BootNumBytes,f goto BootLineLoop - movlw 13 ; Progress - call BootTXB - movlw 10 ; Progress - call BootTXB - goto BootLoop BootFlashComplete BootReturn - movlw BootRunText + movlw low BootRunText call BootTXStr - bsf STATUS,RP0 + bsf STATUS,RP0 ; Reg bank 1 BootReturnWait btfss TXSTA,TRMT ; Wait for last things to flush goto BootReturnWait bcf TXSTA,TXEN ; Disable UART TX bcf STATUS,RP0 ; Back to bank 0 - bcf RCSTA,SPEN ; Enable serial port - bcf RCSTA,CREN ; Enable UART RX + bcf RCSTA,SPEN ; Disable serial port + bcf RCSTA,CREN ; Disable UART RX - clrf PCLATH return ; Return to code ;---------------------------------------------------------------------- @@ -1008,14 +2116,23 @@ BootTXW1 ; BootTXStr - Sends ASCII string pointed to by W, zero terminated BootTXStr - movwf BootStrTemp ; Store offset - call BootLookup ; Lookup char - addlw 0 - skpnz + movwf BootAddrL ; Store LSB of text pointer + movlw 07h ; MSB of pointer to the text (0700h in this boot loader) + movwf BootAddrH + movlw 02h ; Select "Read Program Memory" operation + movwf BootBits +BootTXStrLoop + call BootEE ; Lookup char (actually two packed into one word) + rlf BootDataL,w ; Shift the MSB out into carry (that's the 2nd char LSB) + rlf BootDataH,w ; Shift it into 2nd char + call BootTXB ; Send the high byte first + movf BootDataL,w ; Get the low byte + andlw 07fh ; Mask of the highest bit + skpnz ; Stop if zero return call BootTXB ; Send char - incf BootStrTemp,w ; Retrieve - goto BootTXStr + incf BootAddrL,f ; Increment pointer + goto BootTXStrLoop ;---------------------------------------------------------------------- ; BootRXB - Receives one byte from the UART, waits if nothing available @@ -1025,6 +2142,7 @@ BootRXW1 btfss PIR1,RCIF ; Wait for RX to complete goto BootRXW1 movf RCREG,w ; Get the recvd byte + call BootTXB ; Echo to terminal return ;---------------------------------------------------------------------- @@ -1033,10 +2151,13 @@ 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 ;---------------------------------------------------------------------- @@ -1107,20 +2228,27 @@ BootEEX return +; 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 - DT "WJBoot - press ESC to flash",0 + DA "WJBoot - press ESC to flash\x00" + BootFlashText - DT 13,10,"Send INHX8 file now...",13,10,0 -BootRunText - DT 13,10,"Exiting loader",13,10,0 + DA "\r\nSend INHX8 file now...\r\x00" -BootLookup - movwf PCL ; Go fetch the char data +BootRunText + DA "\r\nExiting loader\r\x00" ;---------------------------------------------------------------------- ; EE Data (64 bytes), located at 2100h org 2100h -; data 0f2h, 099h, 000h, 000h, 018h, 0a5h, 090h, 084h +; 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