Last update: 2012-09-25
This project implements a half-duplex, master/slave communications protocol based on
RS-485 bus hardware.
A Windows PC plays the role of bus-master and several Atmel 8-bit AVRs assume the role of
The Windows part is written in C++, the ninth data bit is simulated by the parity bit, the
transceiver direction is switched by the RTS line.
The AVR part is written in plain C and features the multi-processor communication mode
The protocol allows for up to 128 participants.
Code and makefile are written for an ATmega8 but should be easily adaptable to any
other AVR with UART.
The following circuit can be used to convert a PC's standard serial interface (RS-232) to
RS-485 in case you do not have an RS-232 to RS-485 converter already or even an
RS-485 PCI card with suitable Windows drivers.
The converter is passive in that it does not involve a microcontroller with two USARTs
The +/-12V RS-232 signals are converted to 0V to +5V (TTL level) by a MAX232 (or similar).
The TTL signals are directly feed into a MAX485 (or compatible) RS-485 transceiver.
Not only the TXD/RXD lines are taken into account.
The RTS line is also brought down to TTL in order to control the direction of the RS-485
The above circuit is a Windows PC's ticket to the RS-485 world.
Later we will see an extension of this circuit.
It also contains an ATmega8 for testing RS-485 communication between Windows and AVR.
I used this circuit for developing this library.
|Passive RS-232 to RS-485 converter
The library implements a basic master/slave bus. There is a single master and
multiple slaves. Each slave has
its individual/unique address. (The master though has no address.) All communication
is initiated by the master. The slave which was addressed takes the data sent by the
master and answers with a response message. If the master sends a message to address 0
then all slaves will read that message but none will respond. Address 0 is the broadcast
address. Answering would not be a good idea because all the slaves would answer on the
same bus at the same time which is not possible and results in communication error.
Low Level Details
The communication happens in a 9N1 style, that is nine data bits, no parity and one stop
bit. The ninth bit is set when an address byte is sent by the master. It is cleared in all
other cases (master sends data bytes, slave returns response).
The baud rate can be defined in the makefile. By default I use 38,400 baud. This rate
can be produced by an AVR driven by the internal 8MHz oscillator with a minimum error
rate of 0.2%. You are free to chose any baud rate with an error rate of 0% if you decide
to use a baud crystal (say 14,7456 MHz). (A MAX485 has a maximum data rate of 2.5 Mbps.
That means that it is switching faster than your ATMega8. So any baud rate that the
ATmega8 delivers can be used.)
The library is built for a half-duplex (two wire) bus. That means that every participant
uses only one RS485 transceiver.
The library supports up to 128 participants. How many transceivers can be actually
connected to the bus depends on the transceivers. For example MAX485 allows 32
transceivers where MAX487 allows 128.
- The AVR uses
PD2 to select direction of the transceiver.
low means receive,
PD2 high means send.
- The Windows PC uses the
RTS line to select direction of the transceiver.
high means receive,
RTS low means send.
Configuring the RS-485 Transceiver Control Pin
The latest version allows for configuring the port/pin that controls direction of the
RS-485 transceiver. PD2 is used as default. Should you want/need to use a different port
then use the defines as shown in the following table.
(Please be aware that data direction register and port register have to belong to the same
port. Furthermore direction bit and pin bit need to designate the same pin.)
How to configure the RS-485 transceiver control pin
|Data direction register of your port.|
|Data direction bit corresponding to your pin.|
|Output register of your port.|
|Port bit corresponding to your pin.|
Configuring Use of 1st/2nd UART for Dual UART AVRs
It goes without saying that, for single UART AVRs, this library uses the only UART
available. For dual UART models UART0 is used by default. Set the define
1 if you want to use UART1 instead.
Otherwise leave it undefined or set it to
In theory this library is supposed to support all AVRs with one/two UARTs.
But it has been tested with these models only: ATmega8, ATmega162 and ATmega1284P.
Request messages are only sent by the master (Windows PC). Slaves do not send requests.
They only receive requests and process them. Slaves may return a response message
depending on the communication pattern.
Request message bytes explained
The address byte actually serves two purposes:
|Byte||Meaning||Allowed value||Ninth bit|
|1||Slave address||0-127 + |
RESP (see below)
|3||Number of parameters||0-8||0|
- Bits 0 to 6 contain the address information. That is every address from 0 to 127
is a valid address. (0 is the broadcast address.)
- The most significant bit is the
RESP=0 signals that the slave must not return a response message.
RESP=1 indicates that the slave has to return a response message.
For broadcast messages (address 0)
RESP always has to be cleared.
(It follows that 0b10000000 is an invalid address byte.)
Structure of the address byte
It is up to the user program to assign each slave a unique address. It is also up to the
user program to define commands and assign actions to commands. Each slave may show another
individual reaction to the same command. But you may also standardize commands so that
their meaning is identical on all slaves - it's up to you.
Appart from the command byte the library (by default) allows to send up to eight
If that does not suit your application's needs then it can be adapted by setting the
NB_PARAM_BYTES definition (in the makefile) to the desired value.
Please note that the gray bytes are optional. If "Number of parameters" is zero then
no parameters will be sent by the master. If "Number of parameters" is four then
only four parameters will be sent by the master etc.
Response messages are only sent by the slave (AVR). The master does not send responses.
The master only receives responses and processes them.
Response message bytes explained
The library (by default) allows to return up to eight bytes with the response message.
If that does not suit your application's needs then it can be adapted by setting the
|Byte||Meaning||Allowed value||Ninth bit|
|1||Number of return values||0-8||0|
|2||Return value 1||0-255||0|
|3||Return value 2||0-255||0|
|4||Return value 3||0-255||0|
|5||Return value 4||0-255||0|
|6||Return value 5||0-255||0|
|7||Return value 6||0-255||0|
|8||Return value 7||0-255||0|
|9||Return value 8||0-255||0|
NB_RET_BYTES definition (in the makefile) to the desired value.
See above for details.
Please note that the gray bytes are optional. If "Number of return values" is zero then
no return values will be sent by the slave. If "Number of return values" is five then
only five return values will be sent by the slave etc.
Request and Response
When the master directly addresses a particular slave then the request/response pattern
may come into play. That means that the master sends a request message to a particular
slave and expects that slave to send a response message.
In that case set the most significant bit of the adressbyte (
The response message must not be omitted by the slave when
RESP is set.
It may consist of a single zero byte (empty response message).
This should be the mainly used pattern of communication. Typically the master
addresses a particular slave to ask that slave to do something or to return measured data.
When receiving a response - and be it just the empty response message - the master
knows that the slave actually received the command.
Fire and Forget
The master may send a request to all slaves. Therefore it uses address zero which is the
broadcast address. In that case no response by any slave is allowed. In case of broadcast
no slave must return a response message.
Leaf the most significant bit of the adressbyte cleared (
RESP=0) if the slave
is addressed directly and you want to use fire & forget pattern.
Use this pattern sparely because the master does not know whether the slave received
the command or not. An appropriate situation is when controlling the motors.
Once the master told the slave to drive into a certain direction the master will do
subsequent request in order to query whether the robot is still driving and at what
speed/direction it is driving. Therefore the "start your engines" command can well be
a fire & forget communication (because subsequent requests will reveal if the slave
received the command or not).
Both libraries (Windows and AVR) contain lots of comments. I don't want to repeat myself
here. Instead I just want to mention the key points here and refer to the source code for
The AVR code is implemented as a state machine. The transitions mainly happen in the
interrupt service routines (ISR) when sending of a byte is complete or a byte was
received. The following state diagram explains what states there are, under what condition
the transitions happen and which part of the software is involved.
|State diagram of the AVR RS485 slave library
Multi-Processor Communication Mode
The MPCM bit is set while waiting for an address byte (begin of a message). Therefore
the slaves which are not addressed are not receiving any databytes. This saves MCU time.
The MCU ISR for incoming traffic is only invoked on address bytes (ninth bit set).
Once a MCU feels that it was addresses MPCM is cleared to allow also the data bytes to
come in. All other MCUs leave their MPCM bit set and are waiting for them to be called
by the master.
Whenever an address byte is received (ninth bit set) the state machine goes into state
AWAIT_REQUEST_FETCH_ADDRESS. This is meant to synchronize master and slave.
It may happen (because of programming error or disturbed data transfer) that the master
is starting a new frame (beginning with the address byte) while the slave is in a
state where it is still expecting data not address. This measure resets the state machine
so to say.
Errors that occur inside the ISRs can be inspected by the user program using
The reader may want to transmit RS-485 error codes from AVR to PC (status request).
Therefore the error codes have been separated in the file
/common folder. On the Windows side this file can be included and
made use of. To use the AVR error codes on the Windows side is completely application
specific and individual.
The send routine is basically a sequence of synchronous/blocking writes and reads.
Refer to the source code for further details.
I encountered the following obstacles which I had to get around:
Please note that the most challenging part was the Windows side. I expected it more
demanding to implement the AVR side since you cannot simply debug or debug-print!
But the AVR side was amazingly easy to do.
There are probably three solutions to the timing problem:
- Nine data bits - Windows allows only eight data bits. This is due to the fact that
standard PC UARTs only support eight bits. Furthermore the Win32 API also supports only up to
eight bits. The solution was to use the parity bit for the ninth data bit.
- C++/CLI (managed C++) - I'm new to Windows development. So I got into that
trap. Projects are what Microsoft calls managed if you build an application with a GUI.
In managed code you cannot simply use the COM interface. Managed roughly means that your
code is executed in a sand-boxed virtual machine. This allows all sorts of .NET languages
like Visual Basic, C#, Visual C++ and others to run in one Environment using components
written in any other .NET language. But this also hinders direct access to low level
resources. For example I got "access denied" exceptions and "serialization exceptions"
and all sorts of things. For the sake of simplicity I decided to write the RS485 class
for Windows as a plain C++ class. The test program is a command line program - hence no
managed code. But there are ways to use unmanaged code in managed code. See the references
at the end of this document.
- Timing - I had to add
Sleep() statements into the Windows code.
This is surprising for me because the
WriteFile() is supposed to be
synchronous. That means that the byte should have been sent once
returns. But that seems not to be the case. I had to add the
WriteFile() and before switching the parity bit. If the byte was not
actually sent then the changed parity may have an effect on that byte still in the buffer.
I'm not sure what happens here.
I also tried the
FILE_FLAG_WRITE_THROUGH flags when creating the file with
CreateFile(). Furthermore I tried
I failed to make the program write its bytes immediately. I also could not identify a
method to determine whether all bytes where written.
returns the number of bytes I intended to write. Even the
in conjunction with
ClearCommError() tells me that there are no bytes to
I have no equipment to check whether bytes are flowing over the wires
or not. But from what I can observe and from how I understand UART etc. it seems that
bytes are not written immediately. I would be thankful for any hint here.
So eventually I have to live with a few
- Find a way to determine when Windows has finished sending the bytes. Replace the
Sleep() by an
- Make the RS-485 full-duplex, that is use two transceivers. This avoids that the master
(Windows PC) has to switch direction of its RS-485 transceiver. Only the multiple slaves (AVRs)
need to go on or off the bus. But this does not seem to be the critical point. However
this would make the bus four instead of two wire. It would also require an additional
MAX485 IC for every participant. I want to avoid that.
- Use a timer (NE555) to detect the startbit and immediately switch direction of
the transceiver for a certain time. I found that solution in an
article by Jan Axelson.
However this would mean a further IC and a couple of resistors/capacitors. I want
to avoid that.
Sleep() statements for the sake
of simple circuitry and wiring.
- Download source from here
- Have the following tools installed:
- Visual C++ 2010 (Express)
- avr-gcc (AVR Studio or WinAVR for Windows, CrossPack for Mac OS X)
rs485slaveLibAVR.h into your header folder. Include it in your
AVR program. Copy
rs485slaveLibAVR.c into your your source folder and
add it to your makefile.
rs485masterLibWin.h into your header folder. Include it in your
Windows program. Copy
rs485masterLibWin.cpp into your your source folder (and
add it to your makefile).
avrErrors.h wherever you want. Make those files accessible to the AVR
C compiler and also to the Windows Visuale C++ compiler.
- Build a RS232 (Windows PC) to RS485 bridge. Build a minimum ATmega8 circuit including
a RS485 transceiver. See wiring diagram below.
|Wiring diagram for a minimum circuit which the test program is written for
The Testing Trick
Contained in this project are testprograms for the AVR and for the Windows side.
However, when developing AVR based devices to be added to the bus a trick might help
to ease testing.
Circuits added to the RS-485 bus are likely to be more complicated than the above one.
Application specific circuitry and software will be part of such a device apart from the
ATmega8 and the RS-485 transceiver.
I like to use debug-printing when developing such application specific circuits and
My experience is that it is the easiest way to first develop the circuit/software and
then add the RS-485 functionality.
Doing both at the same time makes things more complicated (at least that is what I feel).
For me it is easier to develop such software enriched with debug-print statements and
use a terminal program on a host computer, e.g. PuTTY,
The problem now with the protocol introduced herein is that the only UART of the ATmega8
is used for an RS-485 communications protocol which is not really designed for
My solution to that is the following:
This allows for usage of a terminal program for display and debug-printing. Once the
application specific code/circuit is done you can add the library described in this
Unidirectional communication - from AVR to host computer - is prerequisite for this to
work since we are choosing the communication direction statically.
Other bus slaves are not expected to interfere because they are only active in case they
were called by the master. In this scenario there is no master we simple tunnel our
UART communication via RS-232 through the RS-485 bus.
In the AVR software I set the port pin PD2. This tells the RS-485 transceiver to send.
- On the host computer use a terminal program such as PuTTY. Choose RTS/CTS flow
control. With the RS-232/RS-485 bridge described herein this makes the host computer's
transceiver to listen.
|Prototype of RS-232/485 bridge and a bus slave
|Screenshot taken from the test program
|1.0.0||2012-05-29||Initial version. Basic RS-485 communication for
Windows (RS232) and Atmel 8-bit AVR.
|2.0.0||2012-07-12||Windows and AVR now share some common code.
Fire & forget communication pattern now available not only for broadcast messages.
Number of parameter and return bytes may now be chosen differently.
(By default they are both eight bytes.)
|2.1.0||2012-07-21||Now uses error buffer from
libAvrUtils in order to buffer
multiple/repeated occurrence of errors. Before that error code was stored in a scalar
where it was overwritten in case of multiple/repeated errors.
||2.2.0||2012-09-25||Now supports dual UART AVRs. Pin for
controlling state/direction of the RS-485 transceiver can now be configured.
rs485libWinAVR Copyright (C) 2012 Holger Zahnleiter
This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome
to redistribute it under certain conditions.
The program and its source code are published under the GNU General Public License (GPL).
Creating this library would not have been possible without the achievements of the following
- A big thanks goes to Objective Development Software GmbH for providing
CrossPack AVR Development
for Mac OS X.
- I also want to thank the people behind
mikrocontroller.net for providing such great
- It is great that CadSoft provides a free version of
The information on this web site and the documents downloadable from here have been
carefully reviewed and is believed to be reliable, but I do not assume any liability
arising out of the application or use of any documents, programs or circuit described
Furthermore I want to declare that I'm not responsible in any way for the content of other
web pages, books and other sources I'm refering to.