RS-485 Library

RS-485 is a multipoint communication which allows multiple devices to be connected to a single bus. The mikroBasic PRO for AVR provides a set of library routines for comfortable work with RS485 system using Master/Slave architecture. Master and Slave devices interchange packets of information. Each of these packets contains synchronization bytes, CRC byte, address byte and the data. Each Slave has unique address and receives only packets addressed to it. The Slave can never initiate communication.

It is the user’s responsibility to ensure that only one device transmits via 485 bus at a time.

The RS-485 routines require the UART module. Pins of UART need to be attached to RS-485 interface transceiver, such as LTC485 or similar (see schematic at the bottom of this page).

Library constants:

  Important :

Library Dependency Tree

RS485 Library Dependency Tree

External dependencies of RS-485 Library

The following variable must be defined in all projects using RS-485 Library: Description : Example :
dim RS485_rxtx_pin as sbit sfr external Control RS-485 Transmit/Receive operation mode dim RS485_rxtx_pin as sbit at PORTD2_bit
dim RS485_rxtx_pin_direction as sbit sfr external Direction of the RS-485 Transmit/Receive pin dim RS485_rxtx_pin_direction as sbit at DDD2_bit

Library Routines

RS485Master_Init

Prototype

sub procedure RS485Master_Init()

Returns

Nothing.

Description

Initializes MCU as a Master for RS-485 communication.

Requires Global variables :
  • RS485_rxtx_pin - this pin is connected to RE/DE input of RS-485 transceiver(see schematic at the bottom of this page). RE/DE signal controls RS-485 transceiver operation mode.

  • RS485_rxtx_pin_direction - direction of the RS-485 Transmit/Receive pin
must be defined before using this function.


UART HW module needs to be initialized. See UARTx_Init.

Example
' RS485 module pinout
dim RS485_rxtx_pin as sbit at PORTD2_bit         
dim RS485_rxtx_pin_direction as sbit at DDD2_bit
' End of RS485 module pinout
...
UART1_Init(9600)                    ' initialize UART module
RS485Master_Init()                  ' intialize MCU as a Master for RS-485 communication

RS485Master_Receive

Prototype

sub procedure RS485Master_Receive(dim byref data_buffer as byte[20])

Returns

Nothing.

Description

Receives messages from Slaves. Messages are multi-byte, so this routine must be called for each byte received.

Parameters :

  • data_buffer: 7 byte buffer for storing received data, in the following manner:

  • data[0..2]: message content
  • data[3]: number of message bytes received, 1–3
  • data[4]: is set to 255 when message is received
  • data[5]: is set to 255 if error has occurred
  • data[6]: address of the Slave which sent the message

The function automatically adjusts data[4] and data[5] upon every received message. These flags need to be cleared by software.

Requires

MCU must be initialized as a Master for RS-485 communication. See RS485Master_Init.

Example
dim msg as byte[20]
...
RS485Master_Receive(msg)

RS485Master_Send

Prototype

sub procedure RS485Master_Send(dim byref data_buffer as byte[20], dim datalen as byte, dim slave_address as byte)

Returns

Nothing.

Description

Sends message to Slave(s). Message format can be found at the bottom of this page.

Parameters :

  • data_buffer: data to be sent
  • datalen: number of bytes for transmition. Valid values: 0...3.
  • slave_address: Slave(s) address

Requires

MCU must be initialized as a Master for RS-485 communication. See RS485Master_Init.

It is the user’s responsibility to ensure (by protocol) that only one device sends data via 485 bus at a time.

Example
dim msg as byte[20]
...
' send 3 bytes of data to slave with address 0x12
RS485Master_Send(msg, 3, 0x12)

RS485Slave_Init

Prototype

sub procedure RS485Slave_Init(dim slave_address as byte)

Returns

Nothing.

Description

Initializes MCU as a Slave for RS-485 communication.

Parameters :

  • slave_address: Slave address

Requires Global variables :
  • RS485_rxtx_pin - this pin is connected to RE/DE input of RS-485 transceiver(see schematic at the bottom of this page). RE/DE signal controls RS-485 transceiver operation mode. Valid values: 1 (for transmitting) and 0 (for receiving)

  • RS485_rxtx_pin_direction - direction of the RS-485 Transmit/Receive pin
must be defined before using this function.


UART HW module needs to be initialized. See UARTx_Init.

Example

Initialize MCU as a Slave with address 160:

' RS485 module pinout
dim RS485_rxtx_pin as sbit at PORTD2_bit         
dim RS485_rxtx_pin_direction as sbit at DDD2_bit
' End of RS485 module pinout
...
UART1_Init(9600)                    ' initialize UART module
RS485Slave_Init(160)                ' intialize MCU as a Slave for RS-485 communication with address 160

RS485Slave_Receive

Prototype

sub procedure RS485Slave_Receive(dim byref data_buffer as byte[20])

Returns

Nothing.

Description

Receives messages from Master. If Slave address and Message address field don't match then the message will be discarded. Messages are multi-byte, so this routine must be called for each byte received.

Parameters :

  • data_buffer: 6 byte buffer for storing received data, in the following manner:

  • data[0..2]: message content
  • data[3]: number of message bytes received, 1–3
  • data[4]: is set to 255 when message is received
  • data[5]: is set to 255 if error has occurred

The function automatically adjusts data[4] and data[5] upon every received message. These flags need to be cleared by software.

Requires

MCU must be initialized as a Slave for RS-485 communication. See RS485Slave_Init.

Example
dim msg as byte[5]
...
RS485Slave_Read(msg)

RS485Slave_Send

Prototype

sub procedure RS485Slave_Send(dim byref data_buffer as byte[20], dim datalen as byte)

Returns

Nothing.

Description

Sends message to Master. Message format can be found at the bottom of this page.

Parameters :

  • data_buffer: data to be sent
  • datalen: number of bytes for transmition. Valid values: 0...3.

Requires

MCU must be initialized as a Slave for RS-485 communication. See RS485Slave_Init. It is the user’s responsibility to ensure (by protocol) that only one device sends data via 485 bus at a time.

Example
dim msg as byte[8]
...
' send 2 bytes of data to the master
RS485Slave_Send(msg, 2)

Library Example

This is a simple demonstration of RS485 Library routines usage.

Master sends message to Slave with address 160 and waits for a response. The Slave accepts data, increments it and sends it back to the Master. Master then does the same and sends incremented data back to Slave, etc.

Master displays received data on P0, while error on receive (0xAA) and number of consecutive unsuccessful retries are displayed on P1. Slave displays received data on P0, while error on receive (0xAA) is displayed on P1. Hardware configurations in this example are made for the EasyAVR5A board and ATmega16.

RS485 Master code:

Copy Code To ClipboardCopy Code To Clipboard
program RS485_Master_Example

dim dat as byte[10]                                  ' buffer for receving/sending messages
    i, j as byte
    cnt as longint

dim rs485_rxtx_pin  as sbit at PORTD2_bit            ' set transcieve pin
    rs485_rxtx_pin_direction as sbit at DDD2_bit     ' set transcieve pin direction

' Interrupt routine
sub procedure interrupt() org 0x16
  RS485Master_Receive(dat)
end sub

main:
  cnt = 0
  PORTA  = 0                          ' clear PORTA
  PORTB  = 0                          ' clear PORTB
  PORTC  = 0                          ' clear PORTC

  DDRA   = 0xFF                       ' set PORTA as output
  DDRB   = 0xFF                       ' set PORTB as output
  DDRC   = 0xFF                       ' set PORTB as output
  
  UART1_Init(9600)                    ' initialize UART1 module
  Delay_ms(100)

  RS485Master_Init()                  ' initialize MCU as Master
  dat[0] = 0xAA
  dat[1] = 0xF0
  dat[2] = 0x0F
  dat[4] = 0                          ' ensure that message received flag is 0
  dat[5] = 0                          ' ensure that error flag is 0
  dat[6] = 0

  RS485Master_Send(dat,1,160)

  SREG_I_bit  = 1                     ' enable global interrupt
  RXCIE_bit   = 1                     ' enable interrupt on UART receive

  while TRUE
    Inc(cnt)
    if (dat[5] <> 0) then             ' if an error detected, signal it
      PORTC = dat[5]                  '   by setting PORTC
    end if
    
    if (dat[4] <> 0) then             ' if message received successfully
      cnt = 0
      dat[4] = 0                      ' clear message received flag
      j = dat[3]
      for i = 1 to dat[3]             ' show data on PORTB
        PORTB = dat[i-1]
      next i
      dat[0] = dat[0]+1               ' increment received dat[0]
      Delay_ms(1)                     ' send back to slave
      RS485Master_Send(dat,1,160)
    end if

    if (cnt > 100000) then            ' if in 100000 poll-cycles the answer
      Inc(PORTA)                      '   was not detected, signal
      cnt = 0                         '   failure of send-message
      RS485Master_Send(dat,1,160)
      if (PORTA > 10) then            ' if sending failed 10 times
        PORTA = 0
        RS485Master_Send(dat,1,50)    '   send message on broadcast address
      end if
    end if
  wend
end.

RS485 Slave code:

Copy Code To ClipboardCopy Code To Clipboard
program RS485_Slave_Example

dim dat as byte[20]                 ' buffer for receving/sending messages
    i, j as byte

dim rs485_rxtx_pin as sbit at PORTD2_bit            ' set transcieve pin
    rs485_rxtx_pin_direction as sbit at DDD2_bit    ' set transcieve pin direction

' Interrupt routine
sub procedure interrupt() org 0x16
  RS485Slave_Receive(dat)
end sub

main:
  PORTB = 0                         ' clear PORTB
  PORTC = 0                         ' clear PORTC

  DDRB = 0xFF                       ' set PORTB as output
  DDRC = 0xFF                       ' set PORTB as output

  UART1_Init(9600)                  ' initialize UART1 module
  Delay_ms(100)
  RS485Slave_Init(160)              ' Intialize MCU as slave, address 160

  dat[4] = 0                        ' ensure that message received flag is 0
  dat[5] = 0                        ' ensure that message received flag is 0
  dat[6] = 0                        ' ensure that error flag is 0

  SREG_I_bit  = 1                   ' enable global interrupt
  RXCIE_bit  = 1                    ' enable interrupt on UARTs receive

  while TRUE
    if (dat[5] <> 0) then           ' if an error detected, signal it by
      PORTC = dat[5]                '   setting PORTC
      dat[5] = 0
    end if
    if (dat[4] <> 0) then           ' upon completed valid message receive
        dat[4] = 0                  '   data[4] is set to 0xFF
        j = dat[3]
        for i = 1 to dat[3]         ' show data on PORTB
          PORTB = dat[i-1]
        next i
        dat[0] = dat[0]+1           ' increment received dat[0]
        Delay_ms(1)
        RS485Slave_Send(dat,1)      '   and send it back to master
    end if
  wend
end.

HW Connection

Example of interfacing PC to ATmega16 MCU via RS485 bus with LTC485 as RS-485 transceiver

Example of interfacing PC to ATmega16 MCU via RS485 bus with LTC485 as RS-485 transceiver

Message format and CRC calculations

Q: How is CRC checksum calculated on RS485 master side?

Copy Code To ClipboardCopy Code To Clipboard
START_BYTE = 0x96; ' 10010110
STOP_BYTE  = 0xA9; ' 10101001


PACKAGE:
-------- 
_START_BYTE 0x96
ADDRESS
DATALEN
[DATA1]            ' if exists
[DATA2]            ' if exists
[DATA3]            ' if exists
CRC
_STOP_BYTE  0xA9


DATALEN bits
------------
bit7 = 1  MASTER SENDS
       0  SLAVE  SENDS
bit6 = 1  ADDRESS WAS XORed with 1, IT WAS EQUAL TO _START_BYTE or _STOP_BYTE
       0  ADDRESS UNCHANGED
bit5 = 0  FIXED
bit4 = 1  DATA3 (if exists) WAS XORed with 1, IT WAS EQUAL TO _START_BYTE or _STOP_BYTE
       0  DATA3 (if exists) UNCHANGED
bit3 = 1  DATA2 (if exists) WAS XORed with 1, IT WAS EQUAL TO _START_BYTE or _STOP_BYTE
       0  DATA2 (if exists) UNCHANGED
bit2 = 1  DATA1 (if exists) WAS XORed with 1, IT WAS EQUAL TO _START_BYTE or _STOP_BYTE
       0  DATA1 (if exists) UNCHANGED
bit1bit0 = 0 to 3 NUMBER OF DATA BYTES SEND


CRC generation :
----------------
crc_send  = datalen ^ address;
crc_send ^= data[0];    ' if exists
crc_send ^= data[1];    ' if exists
crc_send ^= data[2];    ' if exists
crc_send = ~crc_send;
if ((crc_send == _START_BYTE) || (crc_send == _STOP_BYTE))
   crc_send++;
  
NOTE: DATALEN<4..0> can not take the _START_BYTE<4..0> or _STOP_BYTE<4..0> values.
Copyright (c) 2002-2012 mikroElektronika. All rights reserved.
What do you think about this topic ? Send us feedback!
Want more examples and libraries? 
Find them on LibStock - A place for the code