From: Werner Johansson Date: Sun, 2 Mar 2003 10:15:39 +0000 (-0800) Subject: v0.2 - First attempt at responding to the Anyone command X-Git-Url: http://git.xnk.nu/?p=wj-unilink.git;a=commitdiff_plain;h=dbbd88433e0f81f2423d67eadfd1cb4685ffef89 v0.2 - First attempt at responding to the Anyone command Signed-off-by: Werner Johansson --- diff --git a/wj-uni.asm b/wj-uni.asm index 5f47338..130df18 100644 --- a/wj-uni.asm +++ b/wj-uni.asm @@ -1,6 +1,6 @@ - title "PIC16F870 Unilink Interface by Werner Johansson (c) 2003" + title "PIC16F870 Unilink(R) Interface by Werner Johansson (c) 2003" subtitl "Definitions" - list c=132,P=16F870,R=DEC,F=inhx8m + 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! @@ -10,29 +10,48 @@ __CONFIG _HS_OSC&_WDT_OFF&_PWRTE_ON&_BODEN_ON&_LVP_OFF&_CPD_OFF&_WRT_ENABLE_ON&_DEBUG_OFF&_CP_OFF ;---------------------------------------------------------------- -; HISTORY +; 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 Overrun errors from the UART +; Implement Bus re-initialize command +; Implement lots of other Unilink commands + +;---------------------------------------------------------------- +; HISTORY ;---------------------------------------------------------------- ; Version ; +; 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 -; + +;---------------------------------------------------------------- +; I/O LAYOUT ;---------------------------------------------------------------- +; Unilink BUSON IN (blue) connected to RC2/CCP1 +; Unilink DATA (green) connected to RC3 +; 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 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 +; +; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now... -; Unilink BUSON IN (blue) connected to RC2/CCP1 -; Unilink DATA (green) connected to RC3 -; 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 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 - -; This leaves RC0, RC1 and the analog inputs (AN0-AN4) free for now... +#define BUSON_IN_BIT PORTC,2 +#define DATA_BIT PORTC,3 +#define BUSON_OUT_BIT PORTC,4 +#define CLK_BIT PORTB,0 +#define RST_BIT PORTA,4 #define LCD_RS_BIT PORTB,1 #define LCD_RW_BIT PORTB,2 @@ -42,52 +61,80 @@ #define LCD_DB6_BIT PORTB,6 #define LCD_DB7_BIT PORTB,7 -;---------------------------------------------------------------- -; File register usage +#define RS232_RI_BIT PORTC,5 -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 -DataStore equ 34h - -IRQW equ 7fh -IRQSTATUS equ 7eh -IRQPCLATH equ 7dh +;---------------------------------------------------------------- +; 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?) + +IRQPCLATH equ 7dh ; ISH storage +IRQSTATUS equ 7eh ; Needs to be located in a shared area accessible from all register banks +IRQW equ 7fh ; subtitl "Startup" page ;---------------------------------------------------------------- ; Power up/Reset starting point [den rulerar] - org 0 - call Bootstrap ; Call Flash Load routine - call LCDInit ; Initialize LCD I/F - call IRQInit ; Set up and start the IRQ handler - goto Main ; Run the main program loop (skip the IRQ handler) + org 0 ; Start at the beginning of memory (the reset vector) + call Bootstrap ; Call Flash Load routine + call LCDInit ; Initialize LCD I/F + call IRQInit ; Set up and start the IRQ handler + goto Main ; Run the main program loop (skip the IRQ handler) 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 - org 4 ; Must be on 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 - movwf IRQSTATUS - movf PCLATH,w - movwf IRQPCLATH - clrf PCLATH + 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 + movwf IRQSTATUS ; Store the STATUS reg + 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) - btfss INTCON,INTF ; Check if it's INT (CLK) - goto IRQNotINT ; Nope + 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= @@ -95,34 +142,56 @@ IRQPCLATH equ 7dh ; 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.. +; 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 +; (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 this many times + movlw 8 ; Loop through the 8 bits movwf DataCount - -CLKWaitHigh - btfss PORTC,2 ; Check for BUSON - goto IRQAfterINT - btfss PORTB,0 ; Wait for clock to go high - goto CLKWaitHigh - -CLKWaitLow + movf UnilinkTXRX,w ; Get the pointer + movwf FSR ; Store it to make use of indirect addressing + +IRQINTBitSet + btfss INDF,7 ; Test high bit of data (that's the first bit to be clocked out) + goto IRQINTTristate ; Bit is low, we should tristate bit + bcf PORTC,3 ; Otherwise set DATA bit low + bsf STATUS,RP0 ; Select high regs + bcf TRISC,3 ; And pull low (now it's an output) + bcf STATUS,RP0 ; Back to regbank 0 + goto IRQINTCLKWaitLow ; Wait for master to actually clock this bit in + +IRQINTTristate + bsf STATUS,RP0 ; Select high regs + bsf TRISC,3 ; Force the bit to be tristated + bcf STATUS,RP0 ; Back to regbank 0 + +IRQINTCLKWaitLow btfss PORTC,2 ; Check for BUSON goto IRQAfterINT btfsc PORTB,0 ; Wait for clock to go low - goto CLKWaitLow + goto IRQINTCLKWaitLow clrc ; Clear carry (this way the DataStore byte doesn't have to be cleared before) btfss PORTC,3 ; Test DATA setc ; Set carry if data is LOW (data is inverted!) - rlf DataStore,f ; Shift it into our accumulator + rlf INDF,f ; Shift it into our accumulator decfsz DataCount,f ; Loop once more perhaps? - goto CLKWaitHigh + goto IRQINTCLKWaitHigh ; Yes, again! + goto IRQINTRecvDone ; No we're done, don't check for clock to go high again + +IRQINTCLKWaitHigh + btfss PORTC,2 ; Check for BUSON + goto IRQAfterINT + btfss PORTB,0 ; Wait for clock to go high + goto 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: -; If more than 1.1ms has passed since last receive, reset receive counter to zero +;;;;;; 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 ; If the receive counter is 3 check the two upper bits of recv'd byte (CMD1) - this tells us the length of the packet @@ -130,15 +199,220 @@ CLKWaitLow ; 10 = medium 11 byte packet ; 11 = long 16 byte packet ; Update the receive length byte accordingly -; Check whether receive length and receive count are equal, that means that we're finished, flag this by setting -; the high bit of receive length +; Check whether receive length and receive count are equal, that means that we're finished and we can carry on parsing +; the packet and take appropriate action. + +IRQINTRecvDone + movf UnilinkTXRX,w ; Find out which byte we got + andlw 0fh ; Mask + bnz IRQINTRecvNotFirst ; Not the first byte + movf UnilinkRAD,w ; Get the first byte received + bz IRQINTRecvNullByte ; Null byte received, ignore this, don't increment counter +IRQINTRecvNotFirst + incf UnilinkTXRX,f ; Increment address + + movf UnilinkTXRX,w ; Get the byte position again + andlw 0fh ; Only lower 4 bits of interest + xorlw 03h ; Well, is it the third byte? (CMD1, telling us the length of the packet) + bnz IRQINTRecvNotCMD1 ; No, skip the length code for now + movlw 6 ; Assume it's a short packet + btfss INDF,7 ; INDF still points to received byte, test high bit for medium/long + goto IRQINTRecvShort ; Nope, it's a short packet + addlw 5 ; OK, it's long or medium at least + btfsc INDF,6 ; Test for long + addlw 5 ; Yep, it's a long packet +IRQINTRecvShort + movwf UnilinkCmdLen ; Store the length + +IRQINTRecvNotCMD1 + movf UnilinkTXRX,w ; Get the byte position + xorwf UnilinkCmdLen,w ; XOR with the calculated command length + 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)) + +; Unilink command parser: + +; Check for CMD1 = 01h (System bus commands) + movf UnilinkCMD1,w + xorlw 01h + bnz IRQINTParseNot01 + +; 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! + + movlw 10h ; Sending to Master + movwf UnilinkRAD + movlw 0d0h ; I'm in the MD changer group + movwf UnilinkTAD + movlw 8ch ; Device discovery command reply + movwf UnilinkCMD1 + movlw 00h ; 00?? + movwf UnilinkCMD2 + movlw 6ch ; Hard coded parity (!) + movwf UnilinkParity1 + movlw 24h ; My internal MD sends 25 here first time, and then 24 when appointed!?? + movwf UnilinkData1 + movlw 2ch ; 2c?? + movwf UnilinkData2 + movlw 22h ; 22?? + movwf UnilinkData3 + movlw 00h ; 00?? + 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 + +IRQINTParseNot0102 + +; Check for 01 12 (Time poll) + movf UnilinkCMD2,w + xorlw 12h + bnz IRQINTParseNot0112 + + movf UnilinkRAD,w + xorwf UnilinkID,w ; Is it for us? + bnz IRQINTParseNot0112 ; nope + + clrf UnilinkParity1 + movlw 10h ; Sending to Master + addwf UnilinkParity1,f + movwf UnilinkRAD + movf UnilinkID,w ; This is my ID + addwf UnilinkParity1,f + movwf UnilinkTAD + movlw 00h + addwf UnilinkParity1,f + movwf UnilinkCMD1 + + movlw 80h ; We're 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! + +IRQINTParseNot0112 + +IRQINTParseNot01 + +; Check for CMD1 = 02h (Appoint) + movf UnilinkCMD1,w + xorlw 02h + bnz IRQINTParseNot02 + + movf UnilinkRAD,w ; Get the ID the master has given us + 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 + movlw 10h ; Sending to Master + addwf UnilinkParity1,f + movwf UnilinkRAD + movf UnilinkID,w ; This is my ID + addwf UnilinkParity1,f + movwf UnilinkTAD + movlw 8ch ; Device discovery command again + addwf UnilinkParity1,f + movwf UnilinkCMD1 + movlw 00h + addwf UnilinkParity1,f + movwf UnilinkCMD2 + + movf UnilinkParity1,w + movwf UnilinkParity2M ; That's the parity when sending medium messages + + movlw 24h + addwf UnilinkParity2M,f + movwf UnilinkData1 + movlw 2ch ; My internal MD sends 1c here... (external/internal or 1/10 disc difference?) + addwf UnilinkParity2M,f + movwf UnilinkData2 + movlw 22h + addwf UnilinkParity2M,f + movwf UnilinkData3 + movlw 00h + addwf UnilinkParity2M,f + movwf UnilinkData4 + + clrf UnilinkData6 + goto IRQINTParseBypassClear ; We don't want to clear the data, we want to send! + +IRQINTParseNot02 + +; 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 + bnz IRQINTParseF0Deselect + + bsf UnilinkSelected,7 ; Now we're selected + goto IRQINTParseNotF0 + +IRQINTParseF0Deselect + + bcf UnilinkSelected,7 ; Now we're de-selected + goto IRQINTParseNotF0 + +IRQINTParseNotF0 + +; We end up here when parsing is complete and we're 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 + +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 -IRQAfterINT + +IRQINTRecvIncomplete - bcf INTCON,INTF ; Clear our IRQ +IRQINTRecvNullByte + movf INDF,w + movwf DataStore ; Store it so our non-irq code can snoop + +IRQAfterINT + bcf INTCON,INTF ; Clear our IRQ source bit so we can receive new bits again IRQNotINT +; Finally restore CPU state and return from the ISR movf IRQPCLATH,w movwf PCLATH ; Restore PCLATH swapf IRQSTATUS,w @@ -148,7 +422,6 @@ IRQNotINT retfie ; Interrupt return - subtitl "Main loop" page @@ -156,12 +429,7 @@ IRQNotINT ; Data can be stored between here and 100h... StartUpText1 - DT "-WJ UniLink I/F-" -StartUpText2 - DT "Code and design:" -StartUpText3 - DT "**TCC of Yodel**" - + DT "----- WJ UniLink" LookUp movwf PCL ; Go to it @@ -171,41 +439,53 @@ LookUp movwf PCL ; Go to it org 100h Main - bsf STATUS,RP0 + 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 ; Select bank 1 + + bcf RS232_RI_BIT ; Both bits should be outputs at least + bcf BUSON_OUT_BIT ; + +; bcf STATUS,RP0 +; bsf STATUS,RP0 + 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 +; Replace this with an 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 + + movlw StartUpText1 + call TxLCD16B retry -; movlw 8 ; Loop this many times -; movwf DataCount - -;CLKWaitHigh -; btfsc PORTA,4 ; Check for RST -; goto retry -; btfss PORTC,2 ; Check for BUSON -; goto retry -; btfss PORTB,0 ; Wait for clock to go high -; goto CLKWaitHigh -;CLKWaitLow -; btfsc PORTA,4 ; Check for RST -; goto retry -; btfss PORTC,2 ; Check for BUSON -; goto retry -; btfsc PORTB,0 ; Wait for clock to go low -; goto CLKWaitLow - -; clrc ; Clear carry -; btfss PORTC,3 ; Test DATA -; setc ; Set carry if data is LOW (data is inverted!) -; rlf DataStore,f ; Shift it into our accumulator - -; decfsz DataCount,f ; Loop once more perhaps? -; goto CLKWaitHigh - bcf LCD_RS_BIT ;Command mode movlw 80h ;DisplayRam 0 call TxLCDB @@ -231,36 +511,41 @@ retry movlw 'D' call TxLCDB + movf UnilinkCmdLen,w + bz DontPrintCmd + addlw '0' + call TxLCDB +DontPrintCmd + movf DataCount,w ; Load bit counter (if 0 then byte is available) skpz goto retry - movf DataStore,w ; Get the result decf DataCount,f ; Set it non-zero + movf DataStore,w call BootTXB ; Send to terminal - goto retry - movlw StartUpText1 - call TxLCD16B - call LongDelay +; movlw StartUpText1 +; call TxLCD16B +; call LongDelay - bsf PORTA,4 ; turn off LED +; bsf PORTA,4 ; turn off LED - movlw StartUpText2 - call TxLCD16B - call LongDelay +; movlw StartUpText2 +; call TxLCD16B +; call LongDelay - bcf PORTA,4 ; turn on LED +; bcf PORTA,4 ; turn on LED - movlw StartUpText3 - call TxLCD16B - call LongDelay +; movlw StartUpText3 +; call TxLCD16B +; call LongDelay - goto retry +; goto retry ;---------------------------------------------------------------- @@ -280,8 +565,6 @@ IRQInit LCDInit clrf PORTB bsf STATUS,RP0 ; Hi Bank - movlw 0cfh ; RC4 & RC5 should be outputs... - movwf TRISC ; Yep. movlw 001h ; All but RB0 are outputs. movwf TRISB ; Yep bcf OPTION_REG,NOT_RBPU ; Turn on port B pull-up