PIC Microcontoller Input / Output Methods

RS485 Networking for intelligent house by Frank A. Vorstenbosch

...my colleague Brian wants all of his house rewired. Also, we have been toying around with placing temperature sensors around our office (which we need to prove that our airconditioning doesn't work).

After some thought about power-line RF drivers and such, we decided on RS485-type network at a low data rate (16kbps approx). When used with appropriate slew-rate limited drivers (MAX487) you can have relatively long stubs on your network. The network is terminated on both ends with 100R, with two 1k resistors biasing the network into a '0' state.

The temperature sensors I've built use just four active components: a DS1621 I2C temperature sensor, a MAX487 line driver, a PIC 12C509 and a 78L05. We're planning to use four-wire cable at home (+12V, GND and the two differential data lines) and cat-5 network cabling in the office.

Below is the code for the RS485 protocol that we use. This code is heavily macro'd, but should be readable. If enough people show interest then I'll put the include files needed and the rest of the (not quite working) temperature sensor application up on my or Brian's home page.

Frank

Note: This source requires PC graphics to show the diagrams.

Note 2: JBS   - BTFSC+GOTO
        JBC   - BTFSS+GOTO
        LOOP  - DECFSZ+GOTO
        LOOPI - INCFSZ+GOTO
        STR   - MOVWF
        LD    - MOVF,W
        JZ    - SKPNZ+GOTO
        FRAME..FEND sets up a set of local registers
        etc.


; IMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM;
; :                                                                     :
; :   485.I                                                             :
; :                                                                     :
; :   RS-485 home automation bus interface.                             :
; :                                                                     :
; :   Copyright 1997, Frank A. Vorstenbosch.                            :
; :                                                                     :
; HMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM<
;
; Uses:  FLAG_IDSTRING     - a flag, somewhere in a register
;        FLAG_BROADCAST    - a flag, somewhere in a register (can be same as
 FLAG_IDSTRING)
;        RxTxD             - a port pin for received and transmitted data
;        TxEN              - another port pin set high to enable transmitter

                frame 485
                byte Shifter485
                byte Counter485
                byte TempA485
                byte CRCLow485
                byte CRCHigh485
                byte Timer485
                static OurTC
                fend 485

#define Timer485        TempC485



; ZDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD?
; 3                                                                     3
; 3   Description.                                                      3
; 3                                                                     3
; @DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDY

; Two twisted pairs in cable, one for data and one for power/ground.  Power is
; supplied as 9..12V as measured on the peripheral -- a transmitter may take
; approx. 120mA when using 100j terminators, or 104mA using 120j terminators
; in addition to any local power needs.
;
; The network protocol is designed to run on 4MHz 12-bit PICs at ~16kb/s in
; software.  The longest packet takes less than 23ms to send, the shortest
; packet less than 2.5ms.  CSMA is used to prevent two devices transmitting
; at the same time.

; Synchronous message format:
;
;  - Start bit (100-250us duration)
;       Peripherals try sending a start bit of 100-200us, then float the
;       line, see if the line goes back to 0, and if it does, then drive
;       it low for the remainder of the 20us, then start sending data.
;       If the line is still driven to 1, then the peripheral will back
;       off until the bus is idle for >100us, then try sending another
;       start bit.  The peripheral will try progressively longer start
;       bits up to the limit of 200us.  The host uses a 250us start bit
;       to give it priority over all peripherals (this also means that
;       most peripherals can ignore start bits <250us long).
;  - Flags byte
;       7..6 - transfer direction:
;                 00 - peripheral to host
;                 01 - peer to peer (UNIM)
;                 10 - host to peripheral
;                 11 - reserved
;       5    - reserved
;       4..0 - data length
;  - Peripheral or destination address byte or 255 for broadcast
 (Flags<7..6>=10)
;  - Optional source address byte (Flags<7..6>=01) (UNIM)
;  - Command byte (Flags<7..6>=10) or status byte (Flags<7..6>=00)
;       Command
;          0      - Are you there? (Returns up to 31 byte ID string)
;          1      - Data poll
;          2..255 - User defined commands
;       Status
;          0      - OK
;          1      - CRC error
;          2      - Command error
;          3      - Parameter error
;          4      - No (more) data
;          5..254 - User defined errors
;          255    - Data broadcast
;  - 0..31 data bytes
;  - CRC-16 over Flags, Address and Data bytes

;                        ZDDDDDDDDDDDD//DDDDDDDDDDD?    Z
; Start     line idle    3        100-250us        325us3
;       DDDDDDDDDDDDDDDDDY                         @DDDDY
;       ZDDDD?    Z
; 0-bit 325us325us3
;       Y    @DDDDY
;       ZDDDDDDDDDD?    Z
; 1-bit 3   50us   325us3
;       Y          @DDDDY
;
; The low period at the end of each byte is 50us rather than 25us, giving
; devices time to process the byte.  Thus, the byte 0x54 would be transmitted
 as:
;
; ZDDDD?    ZDDDDDDDDDD?    ZDDDD?    ZDDDDDDDDDD?    ZDDDD?    ZDDDDDDDDDD?
 ZDDDD?    ZDDDD?          Z
; 325us325us3   50us   325us325us325us3   50us   325us325us325us3   50us
 325us325us325us325us3   50us   3
; Y    @DDDDY          @DDDDY    @DDDDY          @DDDDY    @DDDDY
 @DDDDY    @DDDDY    @DDDDDDDDDDY


; ZDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD?
; 3                                                                     3
; 3   Local definitions.                                                3
; 3                                                                     3
; @DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDY

                ifndef FLAG_IDSTRING
                ifndef FLAG_BROADCAST
                error   "FLAG_IDSTRING and/or FLAG_BROADCAST must be defined"
                endif
#define FLAG_IDSTRING FLAG_BROADCAST
                endif
                ifndef FLAG_BROADCAST
#define FLAG_BROADCAST FLAG_IDSTRING
                endif


; ZDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD?
; 3                                                                     3
; 3   Receive a message from the RS485 bus.                             3
; 3                                                                     3
; @DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDY
;
; In:   W       - Size of receive buffer
;       fsr     - pointer to first byte of receive buffer
; Act:  Receive a message from the RS485 bus.
; Out:  Receive buffer filled in with command and data bytes
;       Counter485 - Number of data bytes
;       W - error code
;              0 - no error
;              1 - message was not addressed to us
;              2 - message was too long for our buffer
;              3 - CRC error
; Regs: Shifter485, Counter485, fsr, tmp, TempA485, TempB485, TempC485
; Note: Assumes that the start bit has already been detected.
;       Uses one stack level internally, can only be called from top level

ReceiveMessage  str     Counter485

                TRACE   1,"ReceiveMessage"

                clr     CRCHigh485
                clr     CRCLow485

                jbs     RxTxD,$                     ; wait for start bit to end

                call    RM_Byte                     ; receive flags & length
 byte
                retbc   Shifter485,7,1              ; if peripheral-to-host then
 ignore it

                ld      Shifter485
                str     TempA485
                subwf   Counter485,w
                retc    2

                movlw   31
                andwf   Shifter485,w
                str     Counter485
                inc     Counter485                  ; count command as a data
 byte

                bcf     FLAG_BROADCAST              ; assume is's not a
 broadcast

                call    RM_Byte                     ; receive device ID
                call    DeviceID
                xorwf   Shifter485,w                ; is the message addressed
 to us personally?
                jz      RM_Loop

                incf    Shifter485,w                ; or is it a broadcast?
                retnz   1
                bsf     FLAG_BROADCAST

RM_Loop         call    RM_Byte                     ; receive command/data byte
                ld      Shifter485
                inc     fsr
                str     ind
                loop    Counter485,RM_Loop

                call    RM_Byte                     ; receive first CRC byte
                call    RM_Byte                     ; receive second CRC byte

                ld      TempA485
                str     Counter485

                ld      CRCHigh485
                xorwf   CRCLow485,w
                retnz   3                           ; CRC error

                movlw   31
                andwf   Counter485                  ; clear unwanted bits in the
 length byte

                retlw   0

; zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

RM_Byte         ldk     Timer485,8<<<4

RMB_Loop        jbc     RxTxD,$                     ; wait for bit to start
                clc

RMB_Inner       incfsz  Timer485                    ; time the length of the bit
                jbs     RxTxD,RMB_Inner

                ; here the low nibble of Timer485 should be 6 or 13

                btfsc   Timer485,3                  ; detect 1 bit if 9 counts
 or more
                stc
                ld      status                      ; carry is LSB of status reg
ister
                rl      Shifter485                  ; shift bit into register

                xorwf   CRCLow485,w
                clrc
                rr      CRCHigh485
                rr      CRCLow485
                andlw   1
                xorwf   CRCLow485
                movlw   0xa0
                skipz
                xorwf   CRCHigh485

                movlw   0x0f
                iorwf   Timer485
                loopi   Timer485,RMB_Loop

                retlw   0


; ZDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD?
; 3                                                                     3
; 3   Entry point to subroutines for SendMessage.                       3
; 3                                                                     3
; @DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDY

SM_Byte_tmp     ld      tmp                         ; [1]
SM_Byte         str     Shifter485                  ; [1]
SM_Byte_Sh      ldk     Timer485,8<<4               ; [2]
                jp      SMB_Loop                    ; [2]


; ZDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD?
; 3                                                                     3
; 3   Send a message on the RS485 bus.                                  3
; 3                                                                     3
; @DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDY
;
; In:   W       - Number of data bytes in message
;       tmp     - Status byte
;       fsr     - pointer to first byte to transmit
; Act:  Send a message on the RS485 bus.
; Out:  W - error code
;              0 - no error
;              1 - failed to send message after 8 attempts
; Regs: Shifter485, Counter485, CRCHigh485, CRCLow485, Timer485, fsr, tmp,
 (OurTC)
; Note: Uses one stack level internally, can only be called from top level

SendMessage     str     Shifter485

                TRACE   1,"SendMessage"

                ldk     Counter485,8
                clr     CRCHigh485
                clr     CRCLow485

                clr     gpio                        ; set TxEN low
                movlw   NORMAL_TRIS                 ; float RxTxD (with pull-up)
                tris    gpio

SM_WaitIdle     movlw   30
                subwf   OurTC,w                     ; shorter delay if longer
 start bit used
                str     Timer485
SMWI_Loop       jbs     RxTxD,SM_WaitIdle           ; [2] wait for bus to be
 idle for 100..150us
                loopi   Timer485,SMWI_Loop          ; [3]

                movlw   TxEN_MASK|RxTxD_MASK

                btfss   RxTxD                       ; last chance: if activity
 on bus then don't set TxEN high
                str     gpio                        ; drive TxEN high
                jbc     TxEN,SM_WaitIdle            ; wait some more if the STR
 wasn't executed

                movlw   NORMAL_TRIS&~RxTxD_MASK     ; quickly drive RxTxD to
 high level
                tris    gpio
                movlw   NORMAL_TRIS                 ; and then float it again
                tris    gpio

                ifdef HOST
                movlw   36                          ; host uses 250us timeout
                else
                movlw   13
                addwf   OurTC,w
                addwf   OurTC,w
                endif
                str     Timer485                    ; wait for 100..200us

SM_SendStartH   call    Delay4Ticks                 ; [4]
                loop    Timer485,SM_SendStartH      ; [3]

                clr     gpio                        ; disable transmitter
                brake                               ; it takes ~2.5us for the
 MAX487 to go into read mode
                movlw   TxEN_MASK                   ; moved here to prevent
 back-to-back I/O accesses
                jbs     RxTxD,SM_BackOff            ; if RxTxD still high, then
 back off
                str     gpio                        ; enable transmitter again
                movlw   NORMAL_TRIS&~RxTxD_MASK     ; start driving RxTxD pin
 (to low state)
                tris    gpio

                call    Delay9Ticks

                ld      Shifter485
                andlw   31
                str     Counter485
                call    SM_Byte_Sh                  ; send flags & length byte

                call    Delay13Ticks
                call    DeviceID
                call    SM_Byte                     ; send our ID

                call    Delay16Ticks
                call    SM_Byte_tmp                 ; send status byte

                jbs     FLAG_IDSTRING,SM_LoopCode
                test    Counter485
                jz      SM_CRCNoData                ; [2]
                call    Delay8Ticks                 ; [8]
                jp      SMLD_Enter                  ; [2]

SM_LoopData     call    Delay12Ticks                ; [12]
SMLD_Enter      ld      ind
                inc     fsr
                call    SM_Byte                     ; send data byte
                loop    Counter485,SM_LoopData
                jp      SM_SendCRC

SM_LoopCode     brake
                movlw   31
                andwf   fsr,w
                str     tmp
                movlw   IDString
                addwf   tmp,w
                call    PCW
                inc     fsr
                call    SM_Byte                     ; send ID string byte
                loop    Counter485,SM_LoopCode
                brake

SM_SendCRC      brake
                nop
SM_CRCNoData    call    Delay8Ticks
                ld      CRCHigh485
                str     tmp
                ld      CRCLow485
                call    SM_Byte                     ; send first CRC byte
                call    Delay16Ticks
                call    SM_Byte_tmp                 ; send second CRC byte

                movlw   NORMAL_TRIS
                clr     gpio                        ; disable transmitter
                tris    gpio                        ; float RxTxD

                retlw   0                           ; return OK

; zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

SM_BackOff      incf    OurTC,w                     ; increment our timer
                andlw   7                           ; only 3 LSBs used
                str     OurTC
                decfsz  Counter485,f                ; after 8 attempts, return
 error to caller
                retlw   1
                jp      SM_WaitIdle


; DDDDD Subroutine for SendMessage DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD

SMB_Loop        bsf     RxTxD

                ld      CRCLow485
                xorwf   Shifter485,w
                clc
                rr      CRCHigh485
                rr      CRCLow485

                andlw   1
                xorwf   CRCLow485                   ; if W=0, then do nothing,
                skipz                               ; otherwise CRC^=0xa001
                movlw   0xa0
                xorwf   CRCHigh485

SMB_Wait1       inc     Timer485                    ; [1] 25us high bit for a
 '0'
                movlw   0xf0                        ; [1]
                jbc     Timer485,1,SMB_Wait1        ; [3/2]

                rl      Shifter485
                jnc     SMB_ShortBit

SMB_Wait2       inc     Timer485                    ; [1] 50us high bit for a
 '1'
                jbc     Timer485,3,SMB_Wait2        ; [3/2]
                brake
                nop

SMB_ShortBit    andwf   Timer485                    ; W=0xf0 here
                bcf     RxTxD

SMB_Wait3       inc     Timer485                    ; [1] 25us low bit
                nop                                 ; [1]
                jbc     Timer485,2,SMB_Wait3        ; [3/2]
                movlw   0x0f
                iorwf   Timer485

                loopi   Timer485,SMB_Loop
                retlw   0


; DDDDD EOF 485.I DDDDD

GEORGE R. FORSE Says:

Hello,

I would like to use the program, but don't have the macros. When I try
to download them from sites that indicate where to download them (Google),
I am not able to access them I have spent many hours trying to find them.
Please advise. TIA George

James Newton replies: See: http://web.archive.org/web/20030212211144/www.falstaff.demon.co.uk/picres.html Franks site is no more, but thanks to the wayback...

Interested:

Comments:

Code: