ERRORLEVEL -302 TITLE "4 Input 4 Output RS485 Controller (c) 2001 Doug Jackson." ; ;************************************************************************************* ; ; 4in4out - An rs485 controller card providing 4 relay outputs, and 4 switched inputs ; ; Copyright (c) 2001 ; Doug Jackson ; doug@stillhq.com ; ; Usage: none - This is in a bit of hardware!!! ; Revision: 1.0 ; Date: 08 Feb, 2001 ; Source: ; Notes: None ; ; ; Serial Parameters - 4800 Baud, 8 data, 1 stop, No Parity ; ; Protocol details: ; ; All data is sent in packets with the following layout ; ; Start Flag | Address | Command Byte | Parameter | Checksum ; ; Start Flag - 0xA5 ; Checksum - byte added to make LSB of the sum of all bytes in packet ; (excluding start byte) = 00 ; ; Allowable commands are: ; ; 1R - Turn Output 'R' on - R=1..4, X=all ; 0R - Turn Output 'R' off - R=1..4, X=all ; IX - Return value of Input 'X' - X=1..4, X=all ; QR - Return status of output 'R' - R=1..4, X=all ; V1 - Return Firmware Version ; V2 - Return Build Date ; V3 - Return Serial Number ; ; ;************************************************************************************* ; Revision History ;************************************************************************************* ; Date By What ;************************************************************************************* ; 20010208 DRJ Initial Creation ; ;************************************************************************************* ; ; CPU configuration ; (It's a 16F84, 4 Mhz xtal resonator, ; watchdog timer off, power-up timer on) processor 16f84 include __config _HS_OSC & _WDT_OFF & _PWRTE_ON PAGE ; ;************************************************************************************* ; Hardware Constants ;************************************************************************************* ; Relay_Port EQU PORTB ;The relay output ports (via 7404 inverters) Relay0 EQU 0 Relay1 EQU 1 Relay2 EQU 2 Relay3 EQU 3 Input_Port EQU PORTB ; The input ports (via 7404 inverters) Input_0 EQU 4 Input_1 EQU 5 Input_2 EQU 6 Input_3 EQU 7 Data_Port EQU PORTA ; The RS-485 Data Bus Din EQU 1 ; Recieved Data Dout EQU 0 ; Send Data Ddir EQU 2 ; Direction Bit 0 = Rx, 1 = Tx Address_Port EQU PORTA ; The Address Dip Switch AddrClk EQU 2 ; AddrData EQU 4 AddrLoad EQU 3 ; Clear and load the latch (4021b) Status_Port EQU PORTA ; The Status LED Status_Led EQU 3 ; The Status LED FALSE EQU 0 TRUE EQU 1 ; ;************************************************************************************* ; Ram Variable assignments ;************************************************************************************* ; CBLOCK 0x0C TEMP ; a temporary register Relay_Status ; The current status of the Relay Outputs Input_Status ; the current status of the Inputs Device_Address ; The current address setting TXREG ; used by serial I/O routines RXREG ; used by serial I/O BITS ; Bit counter for serial I/O routines. ADDRESS ; Storage for the address sent in the packet COMMAND ; Storage for the current command name PARAM1 ; Storage for the parameter sent CHECKSUM ; The checksum of thepacket sent MSGPTR ; a message pointer for sending messages TIMEOUT ; a Timeout flag for Serial Input J ; a loop Variable K ; a loop variable ENDC PAGE ; ;************************************************************************************* ; Code Starts Here ;************************************************************************************* ; org 0x0000 RESET GOTO START ; Reset vector location ; ;************************************************************************************* ; Interrupt Vectors ;************************************************************************************* ; ; No vectored interrupts are used in this system. PAGE ; ;************************************************************************************* ; Start: ; Set up all inputs and outputs as designed. ; and do a self test. ;************************************************************************************* ; START bsf STATUS, RP0 ; Bank 1 movlw b'11110000' ; the upper 4 bits of port b are inputs movwf TRISB movlw b'00010010' ; Bits RA1, and RA4 are inputs movwf TRISA bcf STATUS, RP0 ; select bank 0 again.. clrf TMR0 ; clear Timer 0 clrf INTCON ; Dissable interrupts movlw b'00001111' movwf Relay_Port ; turn off all relays bsf STATUS, RP0 ; select bank 1 movlw B'10000000' ; port B pullups are dissabled movwf OPTION_REG ; bcf STATUS, RP0 ; bank 0 clrf Relay_Status ; turn off all of the relays call Drive_Relays PAGE ; ;************************************************************************************* ; Main Loop. This is where most of the work is done. ;************************************************************************************* ; MAINLOOP call Read_Address ; Read our address from the address switch movf Device_Address,w; andlw 0xff ; btfss STATUS,Z ; don't do a self test if the address is=0 goto MP0 call SelfTest ; do self test until the address <>0. goto MAINLOOP MP0 call Get_Packet ; wait for a command btfsc STATUS,Z ; was there an error? goto MAINLOOP ; yes, don't attempt to process the packet. call Verify_Address ; is this packet destined for us? btfss STATUS,Z ; goto MAINLOOP ; yes, don't attempt to process the packet. movf COMMAND,w ; get the command name sublw '1' ; is it a Relay_On command? btfss STATUS,Z goto MP1 ; test code to cause the status LED to light if the rx char is a 1 ; bsf Status_Port,Status_Led ; movlw 0xE0 ; call var_delay ; movlw 0xE0 ; call var_delay ; movlw 0xE0 ; call var_delay ; movlw 0xE0 ; call var_delay ; bcf Status_Port,Status_Led ; movlw 0xE0 ; call var_delay ; movlw 0xE0 ; call var_delay ; movlw 0xE0 ; call var_delay ; movlw 0xE0 ; call var_delay call Process_Relay_On_Command ; yes, do the Relay_On command goto MPEND MP1 movf COMMAND,w ; get the command name sublw '0' ; is it a Relay_Off command? btfss STATUS,Z goto MP2 call Process_Relay_Off_Command ; yes, do the Relay_Off command goto MPEND MP2 movf COMMAND,w ; get the command name sublw 'I' ; is it an input command? btfss STATUS,Z goto MP3 call Process_Input_Command ; yes, do the input command goto MPEND MP3 movf COMMAND,w ; get the command name sublw 'Q' ; is it a Query Command? btfss STATUS,Z goto MP4 call Process_Query_Command ; yes, do the Query command goto MPEND MP4 movf COMMAND,w ; get the command name sublw 'V' ; is it an Version command? btfss STATUS,Z goto MPEND call Process_Version_Command ; yes, do the Version command goto MPEND MPEND goto MAINLOOP PAGE ; ;************************************************************************************* ; Drive_Relays - send relay commands to the hardware. ; ; Input: composite byte (Relay_Status) ; Output: None ; Comments: Turn on, or off specified relays. ;************************************************************************************* ; Drive_Relays movf Relay_Status,w ; get the current status movwf TEMP ; save it in a temp register comf TEMP,1 ; invert it (low = on!) movlw 0x0f ; mask out andwf TEMP,W ; the upper bits movwf Relay_Port ; set the relays return PAGE ; ;************************************************************************************* ; Read_Input - Read the status of all inputs into the Input Register. ; ; Input: None ; Output: Input_Status ; Comments: ;************************************************************************************* ; Read_Input movf Input_Port,w ; read the inputs movwf TEMP comf TEMP,1 ; invert it (inputs are inverted) movlw 0xf0 ; mask out andwf TEMP,1 ; the lower bits swapf TEMP,w movwf Input_Status ; and save it. return PAGE ; ;************************************************************************************* ; Read_Address - Read the current device address from the Address Switches . ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Read_Address bsf Address_Port,AddrLoad ; Load the switch into the 4021 nop bcf Address_Port,AddrLoad movlw 8 movwf BITS ; we need to get 8 bits ABLoop btfss Address_Port,AddrData ; get the bit goto ABLow goto ABHigh ABLow nop bcf STATUS,0 ; we got a low bit, so reset the STATUS flag goto ABNext ABHigh bsf STATUS,0 ; we got a high bit, so set the STATUS flag goto ABNext ABNext rrf Device_Address,1 ; rotate the status bit into the recieved char bcf Address_Port,AddrClk ; drop the clock on the 4021 nop bsf Address_Port,AddrClk ; raise the clock on the 4021 decfsz BITS,1 ; decrement the number of bits left goto ABLoop ; and get another bit. return PAGE ; ;************************************************************************************* ; Display_Version - Answer a 'display version' query. ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Display_Version movlw 0x00 movwf MSGPTR ; put 'W' into the message pointer mloop movf MSGPTR,w ; Put the offset into w call FIRMTXT ; returns the ASCII char to send into w addlw 0 ; set the ZERO flag if w=0 BTFSC STATUS, Z ; skip if the ZERO flag is not set return call Serout ; output the character incf MSGPTR, f ; point at the next char GOTO mloop ; ;************************************************************************************* ; Display_Serial - Answer a 'display Serial' query. ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Display_Serial movlw 0x00 movwf MSGPTR ; put 'W' into the message pointer mloop1 movf MSGPTR,w ; Put the offset into w call SERIALTXT ; returns the ASCII char to send into w addlw 0 ; set the ZERO flag if w=0 BTFSC STATUS, Z ; skip if the ZERO flag is not set return call Serout ; output the character incf MSGPTR, f ; point at the next char GOTO mloop1 ; ;************************************************************************************* ; Display_Build - Answer a 'display Build' query. ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Display_Build movlw 0x00 movwf MSGPTR ; put 'W' into the message pointer mloop2 movf MSGPTR,w ; Put the offset into w call BUILDTXT ; returns the ASCII char to send into w addlw 0 ; set the ZERO flag if w=0 BTFSC STATUS, Z ; skip if the ZERO flag is not set return call Serout ; output the character incf MSGPTR, f ; point at the next char GOTO mloop2 FIRMTXT addwf PCL,f ; offset added to PCL retlw 'V' retlw 'e' retlw 'r' retlw 's' retlw 'i' retlw 'o' retlw 'n' retlw '-' retlw '0' retlw '.' retlw '0' retlw '1' retlw 0x0d retlw 0x0a retlw 0x00 ; zero to terminate the string SERIALTXT addwf PCL,f ; offset added to PCL retlw 'S' retlw 'e' retlw 'r' retlw 'i' retlw 'a' retlw 'l' retlw '-' retlw '0' retlw '0' retlw '0' retlw '0' retlw '0' retlw '1' retlw 0x0d retlw 0x0a retlw 0x00 ; zero to terminate the string BUILDTXT addwf PCL,f ; offset added to PCL retlw 'B' retlw 'u' retlw 'i' retlw 'l' retlw 'd' retlw '-' retlw '2' retlw '4' retlw '/' retlw '0' retlw '2' retlw '/' retlw '2' retlw '0' retlw '0' retlw '1' retlw 0x0d retlw 0x0a retlw 0x00 ; zero to terminate the string PAGE ; ;************************************************************************************* ; Serin - Get a Character from the RS-485 port. ; ; Input: ; Output: Char in RXREG ; Comments: Return value 0xff= Timeout, 00 = ok. ;************************************************************************************* ; Serin bcf Data_Port,Ddir ; put us into rx mode movlw 8 movwf BITS ; we need to get 8 bits WaitStart ; btfsc TIMEOUT,1 ; has a timeout event occurred? ; retlw 0xff ; yes, return with w=ff to indicate the problem. btfsc Data_Port,Din goto WaitStart ; loop till we see the start bit movlw D'25' ; and wait 1/2 a bit length (49/2) call micro4 BitLoop movlw D'48' ; wait till the bit has been recieved call micro4 btfss Data_Port,Din ; get the bit goto BitLow goto BitHigh BitLow nop bcf STATUS,0 ; we got a low bit, so reset the STATUS flag goto NextBit BitHigh bsf STATUS,0 ; we got a high bit, so set the STATUS flag goto NextBit NextBit rrf RXREG,1 ; rotate the status bit into the recieved char decfsz BITS,1 ; decrement the number of bits left goto BitLoop ; and get another bit. movlw D'48' call micro4 ; wait for the stop bit to go away retlw 00 ; and return. Status = 0 - ok, char is in RXREG. PAGE ; ;************************************************************************************* ; Serout - Send a character to the RS-485 port. ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Serout movwf TXREG ; place char to send into TXREG bsf Data_Port,Ddir ; put us into tx mode movlw 8 ; we will send 8 bits of data movwf BITS ; save this in the counter for bits bcf Data_Port,Dout ; start bit SendLoop movlw D'49' ; delay time (49 * 4uS = 196uS + 12uS = 1 bit@4800 Baud) call micro4 rrf TXREG, f ; roll rightmost bit into carry btfss STATUS,C ; if carry set, we want to send a bit goto clearbit ; otherwise, we want to clear the bit bsf Data_Port,Dout ; send a high goto testdone ; see if we are finished? clearbit bcf Data_Port,Dout ; send a low nop ; padding to ensure both options are 12uS testdone decfsz BITS, f ; 1 less data bit to send, skip when zero goto SendLoop ; there are more data bits left movlw D'52' ; wait the full 208uS for this last data bit call micro4 bsf Data_Port,Dout ; send the stop bit movlw D'52' ; wait the full 208uS for this stop bit call micro4 bcf Data_Port,Ddir ; put us into rx mode return PAGE ; ;************************************************************************************* ; Calculate_Checksum - . ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Calculate_Checksum return PAGE ; ;************************************************************************************* ; Get_Packet - Recieve a packet from the serial line. ; ; Input: Serial Data ; Output: ADDRESS byte set to destination address ; COMMAND byte set to the Command sent by the sender ; PARAM1 set to the first parameter ; CHECKSUM set to the recieved checksum ; Comments: ;************************************************************************************* ; Get_Packet clrf TIMEOUT ; reset the timeout flag clrf ADDRESS clrf COMMAND clrf PARAM1 clrf CHECKSUM call Serin andlw 0xff ; set the status bits as the 'W' reg dictates btfss STATUS,Z ; was the character recieved ok? goto Packet_Error ; no, do the error handler movf RXREG,w ; yes, sublw 0xA5 ; is the start bit valid btfss STATUS,Z ; branch if valid goto Packet_Error call Serin andlw 0xff ; set the status bits as the 'W' reg dictates btfss STATUS,Z ; was the character recieved ok? goto Packet_Error ; no, do the error handler movf RXREG,w ; yes, movwf ADDRESS ; save the address byte call Serin andlw 0xff ; set the status bits as the 'W' reg dictates btfss STATUS,Z ; was the character recieved ok? goto Packet_Error ; no, do the error handler movf RXREG,w movwf COMMAND call Serin andlw 0xff ; set the status bits as the 'W' reg dictates btfss STATUS,Z ; was the character recieved ok? goto Packet_Error ; no, do the error handler movf RXREG,w movwf PARAM1 call Serin andlw 0xff ; set the status bits as the 'W' reg dictates btfss STATUS,Z ; was the character recieved ok? goto Packet_Error ; no, do the error handler movf RXREG,w movwf CHECKSUM Packet_End bsf Status_Port,Status_Led movlw 0x40 call var_delay bcf Status_Port,Status_Led movlw 0x40 call var_delay retlw 0x00 ; return an OK message Packet_Error bsf Status_Port,Status_Led movlw 0x40 call var_delay bcf Status_Port,Status_Led movlw 0x40 call var_delay bsf Status_Port,Status_Led movlw 0x40 call var_delay bcf Status_Port,Status_Led movlw 0x40 call var_delay bsf Status_Port,Status_Led movlw 0x40 call var_delay bcf Status_Port,Status_Led movlw 0x40 call var_delay bsf Status_Port,Status_Led movlw 0x40 call var_delay bcf Status_Port,Status_Led movlw 0x40 call var_delay retlw 0xFF ; indicate an error PAGE ; ;************************************************************************************* ; SelfTest - Cycle through the relays, progressively turning them ; all on, then all off. ; Input: ; Output: ; Comments: ;************************************************************************************* ; SelfTest call Display_Version call Display_Serial call Display_Build movlw 'X' movwf PARAM1 call Process_Relay_Off_Command ; turn off all relays movlw 0xE0 call var_delay movlw '1' movwf PARAM1 call Process_Relay_On_Command ; turn on relay 1 movlw 0xE0 call var_delay movlw '2' movwf PARAM1 call Process_Relay_On_Command ; turn on relay 2 movlw 0xE0 call var_delay movlw '3' movwf PARAM1 call Process_Relay_On_Command ; turn on relay 3 movlw 0xE0 call var_delay movlw '4' movwf PARAM1 call Process_Relay_On_Command ; turn on relay 4 movlw 0xE0 call var_delay movlw '1' movwf PARAM1 call Process_Relay_Off_Command ; turn off relay 1 movlw 0xE0 call var_delay movlw '2' movwf PARAM1 call Process_Relay_Off_Command ; turn off relay 2 movlw 0xE0 call var_delay movlw '3' movwf PARAM1 call Process_Relay_Off_Command ; turn off relay 3 movlw 0xE0 call var_delay movlw '4' movwf PARAM1 call Process_Relay_Off_Command ; turn off relay 4 movlw 0xE0 call var_delay return PAGE ; ;************************************************************************************* ; Process_Relay_On_Command - Process a Relay On Packet . ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Process_Relay_On_Command movf PARAM1,w ; get the Supplied Parameter sublw '1' ; is it 1? btfss STATUS,Z goto PR1C2 ; No, try another bsf Relay_Status,0 ; yes, set the bit on goto PR1C_END ; PR1C2 movf PARAM1,w ; get the Supplied Parameter sublw '2' ; is it 2? btfss STATUS,Z goto PR1C3 ; No, try another bsf Relay_Status,1 ; yes, set the bit on goto PR1C_END ; PR1C3 movf PARAM1,w ; get the Supplied Parameter sublw '3' ; is it 3? btfss STATUS,Z goto PR1C4 ; No, try another bsf Relay_Status,2 ; yes, set the bit on goto PR1C_END ; PR1C4 movf PARAM1,w ; get the Supplied Parameter sublw '4' ; is it 4? btfss STATUS,Z goto PR1CX ; No, try another bsf Relay_Status,3 ; yes, set the bit on goto PR1C_END ; PR1CX movf PARAM1,w ; get the Supplied Parameter sublw 'X' ; is it an X? btfss STATUS,Z goto PR1C_ERR ; No, try another movlw 0x0f movwf Relay_Status ; yes, set all bits on goto PR1C_END ; PR1C_END call Drive_Relays return PR1C_ERR return ; ERROR HANDLER NON-EXISTANT PAGE ; ;************************************************************************************* ; Process_Relay_Off_Command - Process a Process_Relay_Off_Command Packet. ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Process_Relay_Off_Command movf PARAM1,w ; get the Supplied Parameter sublw '1' ; is it 1? btfss STATUS,Z goto PR0C2 ; No, try another bcf Relay_Status,0 ; yes, set the bit off goto PR0C_END ; PR0C2 movf PARAM1,w ; get the Supplied Parameter sublw '2' ; is it 2? btfss STATUS,Z goto PR0C3 ; No, try another bcf Relay_Status,1 ; yes, set the bit off goto PR0C_END ; PR0C3 movf PARAM1,w ; get the Supplied Parameter sublw '3' ; is it 3? btfss STATUS,Z goto PR0C4 ; No, try another bcf Relay_Status,2 ; yes, set the bit off goto PR0C_END ; PR0C4 movf PARAM1,w ; get the Supplied Parameter sublw '4' ; is it 4? btfss STATUS,Z goto PR0CX ; No, try another bcf Relay_Status,3 ; yes, set the bit off goto PR0C_END ; PR0CX movf PARAM1,w ; get the Supplied Parameter sublw 'X' ; is it an X? btfss STATUS,Z goto PR0C_ERR ; No, try another movlw 0x00 movwf Relay_Status ; yes, set all bits off goto PR0C_END ; PR0C_END call Drive_Relays return PR0C_ERR return ; ERROR HANDLER NON-EXISTANT PAGE ; ;************************************************************************************* ; Process_Input_Command - Process an Input packet . ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Process_Input_Command call Read_Input ; read the current state of the inputs movf Input_Status,w movwf TEMP ; save the status in the TEMP register ; we do this because this code is ; shared with Process_Query_Command PRIC0 movf PARAM1,w ; get the Supplied Parameter sublw '1' ; is it 1? btfss STATUS,Z goto PRIC2 ; No, try another ; do the input_1 command goto PRIC_END ; PRIC2 movf PARAM1,w ; get the Supplied Parameter sublw '2' ; is it 2? btfss STATUS,Z goto PRIC3 ; No, try another ; do the input_2 command goto PRIC_END ; PRIC3 movf PARAM1,w ; get the Supplied Parameter sublw '3' ; is it 3? btfss STATUS,Z goto PRIC4 ; No, try another ; do the input_3 command goto PRIC_END ; PRIC4 movf PARAM1,w ; get the Supplied Parameter sublw '4' ; is it 4? btfss STATUS,Z goto PRICX ; No, try another ; do the input_4 command goto PRIC_END ; PRICX movf PARAM1,w ; get the Supplied Parameter sublw 'X' ; is it an X? btfss STATUS,Z goto PRIC_ERR ; No, try another ; do the input_X command goto PR1C_END ; PRIC_END ;call return PRIC_ERR return ; ERROR HANDLER NON-EXISTANT PAGE ; ;************************************************************************************* ; Process_Query_Command - Process a Query Packet. ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Process_Query_Command movf Relay_Status,w ; get the current status of the relays movwf TEMP goto PRIC0 ; jump into the Process_Input code to deal ; with the rest PAGE ; ;************************************************************************************* ; Process_Version_Command - Process a version request packet . ; ; Input: ; Output: ; Comments: ;************************************************************************************* ; Process_Version_Command movlw 0xE0 ; a short delay to allow line turnaround call var_delay ; test code to cause the status LED to light if the rx char is a 1 bsf Status_Port,Status_Led movf PARAM1,w ; get the Supplied Parameter sublw '1' ; is it 1? btfss STATUS,Z goto PRVC2 ; No, try another call Display_Version ; yes, do the V1 command (Firmware Version) goto PRVC_END ; PRVC2 movf PARAM1,w ; get the Supplied Parameter sublw '2' ; is it 2? btfss STATUS,Z goto PRVC3 ; No, try another call Display_Build ; yes, do the V2 command (Build Date) goto PRVC_END ; PRVC3 movf PARAM1,w ; get the Supplied Parameter sublw '3' ; is it 3? btfss STATUS,Z goto PRVC_ERR ; No, try another call Display_Serial ; yes, do the v3 command (Serial Number) goto PRVC_END ; PRVC_END ; do the centralised version command processing return PRVC_ERR return ; ERROR HANDLER NON-EXISTANT goto MAINLOOP PAGE ; ;************************************************************************************* ; Verify_Address - verify that a packet was addressed to us. ; ; Input: Device_Address, Address (Packet Address) ; Output: ; Comments: if addresses are the same, Z will be set, and W=0 ;************************************************************************************* ; Verify_Address movf Device_Address,W ; get the device address subwf ADDRESS,W ; subtract the packet address return ; return with the result in W PAGE ; ;************************************************************************************* ; micro4 - A nx4 uS delay routine . ; ; Input: W reg = number of uS to delay (*4) ; Output: none ; Comments: delay w reg * 4uS. ;************************************************************************************* ; micro4 addlw 0xff ; decrement input btfss STATUS,Z ; skip when zero goto micro4 ; loop till we are done return PAGE ; ;************************************************************************************* ; var_delay - Variable delay routine. Waste some time executing a nested loop ; Input: 'W' - Delay period (Unitless...) ; Output: Time wasted. ; Comments: none ;************************************************************************************* ; var_delay ;return movwf J jloop2 movwf K kloop2 decfsz K,f goto kloop2 decfsz J,f goto jloop2 return PAGE end