Using RS-485; What's the Minimalist method ? Who needs Modbus ?

I have some RS-485 drivers (LT1481) and I downloaded the RS-485 Library but couldn’t get any examples to work. I browsed a Modbus Standards document and it looked much to complicated to mess with. I understand the basic concept of having different functions and device IDs but it looks like too much trouble to implement with an arduino. I’ve used DeviceNet which is very similar but I software tools provided by my company and I didn’t have to write any code (with only a few exceptions). I just want to learn how to use RS-485 for data acquisition
over long cable runs. I would be happy being able to send analogRead Data from a Slave to a Master, or
turn on a relay or motor controlled by a Slave from a Master. One Master and multiple slaves is all I would ever need. I just can’t get past the software. Attached is a schematic of the circuit I have.
The rs-485 library I have appears to have come from here.
I am more than willing to read tutorials but the one’s I’ve seen don’t seem to have any beginning or end
or they assume you already know something about the subject. If I could find a book on how to use arduinos for rs-485 I would consider buying it. I don’t think there’s a book on Modbus that isn’t as thick as a dictionary (I want to say Bible but don’t know if that’s politically correct anymore). At least with something like motor controllers everything is straight-forward, but with a device protocol , learning it could be like jumping in the deep end of pool. I got “the device did not initialize correctly” errors with the following sketches. I have no clue what’s going on with that. I frankly don’t see how the sketches can have serial prints you are supposed to read with the serial monitor when the rs-485 pins are connected to Rx & Tx. I think I need to use Software Serial for one or the other. (rs-232 or rs-485),

Send Data Example

// Include always this library when you are using the RS-485 functions
#include <RS485.h>
//Include the SPI library
#include <SPI.h>
// Create an instance
RS485 myDevice = RS485();


void setup() {

  // Power on the USB for viewing data in the serial monitor
  Serial.begin(115200);
  delay(100);

  // Initializes the module and assigns the SPI
  if ( myDevice.begin() == 0) {
    Serial.println("RS-485 module started successfully");
  } else {
    Serial.println("RS-485 did not initialize correctly");
  }
  delay(100);

  // Configure the baud rate of the module
  myDevice.baudRateConfig(9600);
  // Configure the parity bit as disabled
  myDevice.parityBit(DISABLE);
  // Use one stop bit configuration
  myDevice.stopBitConfig(1);
  // Print hello message
  Serial.println("Hello this is RS-485 communication send data example.");
}



void loop() {
  // Reading the analog input 1
  int analog1 = analogRead(A1);
  //Reading the analog input 2
  int analog2 = analogRead(A2);

  // Send data through RS-485 line
  myDevice.send("Data from analog1 input : ");
  myDevice.send(analog1);
  myDevice.send("\n");

  myDevice.send("Data from analog2 input : ");
  myDevice.send(analog2);
  myDevice.send("\n");

  delay(1000);
}

Receive Data Example

// Include always this library when you are using the RS-485 functions  
#include <RS485.h>
// Include the SPI library
#include <SPI.h>
// Create an instance
RS485 myDevice = RS485();

void setup() { 
  
  // Initializes the serial for viewing data in the serial monitor 
  Serial.begin(115200);
  delay(100);

  // Initializes the module and assigns the SPI
  if ( myDevice.begin() == 0) {
     Serial.println("RS-485 module started successfully");
  } else {
     Serial.println("RS-485 did not initialize correctly");
  }
  delay(100);

  // Configure the baud rate of the module
  myDevice.baudRateConfig(115200);
  // Configure the parity bit as disabled 
  myDevice.parityBit(DISABLE);
  // Use one stop bit configuration 
  myDevice.stopBitConfig(1);  
  // Print hello message
  Serial.println("This is RS-485 communication receive data example.");
}


void loop() {     
  
  // If data in response buffer
  if (myDevice.available()) 
  {
    while (myDevice.available()) {
      // Read one byte from the buffer 
      char data = myDevice.read();
      // Print data received in the serial monitor
      Serial.print(data);      
    }
  }     
  delay(10);
}

What distance is a long cable run? RS-485 can be used with any protocol you can dream up for your project. Modbus is a standard protocol so devices from different manufacturers can play nice with each other. It rides on top of the RS-485 electrical standard.

At slow speed, RS-232 can go quite a distance. It's limitation is the capacitance of the cabling effecting the slew rate of the signal transitions.

Paul

I just want to learn how to use RS-485 for data acquisition
over long cable runs.

Baaahahaha! :smiley:

Like Paul said, RS-485 is an electrical specification ONLY. RS-232 is a similar kind of standard, but it also has mechanical specifications (i.e., connectors). RS-232 has come to mean “UART serial communications over RS-232”. TTL RS-232 means 0-5V instead of -12V and +12V, like the Arduino Serial pins 2 & 3 (or other SoftwareSerial pins). Character encoding, stop bits, parity and baud rates are not technically part of the RS-232 standard.

One way to think about RS-485 is that it’s an “extension cord” for wires between two Arduinos. First, get two Arduinos talking to each other when they’re right next to each other. For example, make one of them do an AnalogRead and send it to the other over SoftwareSerial. The other one reads its SoftwareSerial and prints the results on Serial so you can see it in the Serial Monitor window. Taa daaah! You’re done!

At this point, you should be able to replace the wires with a pair of RS-485 transceivers, connected by 50’ of wire. And it should still work. Really!

However, this is one-way communication only: you will jumper the sender’s TXEN high and RXEN low. You will also jumper the receiver’s TXEN low and RXEN high.

But what if you want two-way communication? Now you have to be more careful about transmitting. They may attempt to transmit at the same time, and neither will receive what was sent. This is called media access. And what if you have more than two devices? It’s not just point-to-point, it’s a network.

You could add a few lines of code to set the TXEN when one of them has data to send to the other. This also uses another digital pin on each Arduino. You can set both RXEN high so they can both receive at all times, but this means they will hear their own transmissions. Or you could use another digital pin to set RXEN low when they’re transmitting. They could still step on each other’s transmission, though. This is called a collision.

You could try “collision avoidance” by timing when Arduinos can send (aka time slices), or by waiting a random amount of time after you have received something (random backoffs).

Or, you could make one Arduino the master, and the other the slave. Then the master has to ask the slaves if they have anything to say. This is called polling.

Or you can ignore trying to avoid the collision and simply catch them when they happen (collision detection). Just write some code that sends and acknowledgement back whenever it receives something. And write the matching code on the transmitter that waits for the ack and retries if it doesn’t get it.

Or you could jumper RXEN high and make sure you receive all the bytes you sent. If they don’t match, you know there was a collision.

So let’s say we get our two-way comms going. What data are you going to send? How does the receiver know where a command (or analog reading) starts and ends? CR/LF? A special start character, followed by the number of bytes that are coming? This is called framing.

What if the bytes are corrupted? This is called error detection. Checksums are one way to detect that one or more bytes were corrupted on their way to the receiver. Do you want to be able to correct the errors? This is called error correction. There are many Error Correcting Codes.

Welcome to the complicated world of Communications! This is why comm standards can get so thick… there are many layers to the problem, and engineers love problems. :smiley:

As you have realized, you have a lot of choices to make. Here are a few ways I would suggest:

  1. Roll your own simple protocol: one-way, CR/LF framing, no error correction/detection. Easy-peasy. AT Modem commands are this simple, with an added “OK” acknowledgement. GPS devices are almost this simple: messages start with ‘$’, an optional xor checksum after ‘*’, and end with CR/LF.

  2. Find a Serial-oriented library for exchanging packets (i.e., it has its own framing, error detection/correction, and acks) and add your own RXEN/TXEN around the calls. This is kind of like TCP, which is oriented to the Ethernet electrical standard and has the concept of “connecting”. The IP standard et al are lurking in there, too. (Shall I mention PPP? SLIP? ssssh!)

  3. Find an RS-485 library that knows how to exchange packets and handles RXEN/TXEN internally. SPI comms work this way: begin, transmit, end.

  4. Pick an industry standard. Then pick a library that “claims” to play according to the rules of that standard. You will have to understand the standard. And you will have to understand the library, because you’ll be debugging it. :confused: This is your only choice if you want to communicate with standard modules (i.e., MODBUS, CAN, etc.).

Whew! And just so you know, this is the end of this tutorial.

Cheers,
/dev

Sorry, "/dev", RS-232 does NOT specify any connector standard. It does not even specify pin numbers. It used the pin identifications as letter pairs, ie. AA, AB, AC, BB, etc.

Paul

What, wikipedia was wrong? ;)

The standard recommended but did not make mandatory the D-subminiature 25-pin connector...

In revision D of EIA-232, the D-subminiature connector was formally included as part of the standard (it was only referenced in the appendix of RS 232 C).

My old DOC only went thru "C"!!!

Paul

My old DOC only went thru "C"!!!

You must have had your appendix removed.

Thanks Paul_KD7HB and /dev for your thorough explanations. I actually already know about TTL Serial rs232 (which is what I was referring to in the following comment):

I think I need to use Software Serial for one or the other. (rs-232 or rs-485),

I have two ATmega328s running Bill Porter's EasyTransfer on breadboards. I don't recall if it uses software serial at the moment. I'll check when I get home from work. I think my best bet is to roll my own simple protocol or find one somewhere that uses software serial for the rs-485 communications so I can monitor things with the serial monitor. I only need one master but need to send analogWrite command values to a slave controlling a PWM motor driver and send relay on/off commands to another slave and read back analogRead values from yet another slave. There may or may not be one or more other slaves performing similar tasks. I have to devise a methodical step by step plan to implement this so each slave can be tested individually and each protocol function can be tested. I don't want to just throw all the functions into the code and connect all the slaves and hope for the best. I want to proceed with one small successful test at a time toward an eventual goal of full functionality.

I'm going to start by trying this tutorial when I get home.

I have an rs485 library I got from somewhere but don't remember where. I'll try to find the source tonight.

Looks like you are well on your way to a solution.

Paul

Looks like you are well on your way to a solution.

"It ain't over until the fat lady sings...." ;D

I have two ATmega328s running Bill Porter's EasyTransfer on breadboards.

Excellent! As I mentioned, you could just hook the TX of one Arduino to the DI (TXn) of one of the chips, and jumper DE (TXENn) high. Then two wires for A/B to the other module, followed by DO (RXn) to the Arduino RX. Your schematic show all RE* pins low. This would work for one-way comms.

I see you need two-way comms, though:

send analogWrite command values to a slave controlling a PWM motor driver, and send relay on/off commands to another slave, and read back analogRead values from yet another slave.

You could continue to use EasyTransfer. It looks like EasyTransfer can take a SoftwareSerial or NewSoftSerial argument in the begin method. He has a comment that seems to imply that you have to change the code, but I think it should work. Both HardwareSerial and SoftwareSerial are derived from Stream, which is what the begin method requires as a 3rd argument.

Just hook the TXEN to an Arduino Digital Pin, then call digitalWrite( pin, HIGH ); before you do the sendData(). You have to wait for it to all go out before you drop TXEN. Because receiving is always enabled (RE* == LOW), you will receive the packet you're sending. So when receiveData() returns true for the packet you're busy sending, you can drop TXEN. The next packet that comes in will be the slave response.

Because you have multiple slaves, you may want to consider addressing. EasyTransfer does not have a from/to field, but you can put whatever you want in your struct. Then the slaves will know who the master is talking to, and the master can confirm that the correct slave is responding.

I don't want to just throw all the functions into the code...

The Big Bang Theory of Testing is never good. :)

Cheers and Good Luck, /dev

No joy. I tried 3 different tutorials last night and couldn't get any of them to work using the LT1481s. I ordered some MAX485 breakout bds ($ 0.99/each) to try to rule out the chip as the problem. I was only using a two chip circuit and tried replacing the receiving side with the same result. I could see the complimentary differential A & B signals on the scope but it still didn't work. I'm hoping I'll have better luck with the breakout boards because they are the exact part used in the Instructable I linked. It's possible it could be something silly like how the RE-NOT pin is connected. The LT1481 has shutdown mode (1uA) if RE-NOT is HIGH and DE is LOW. i don't think that can be it because I was looking at the signals on the scope. I haven't scoped the RO (Receiver Output ) signals yet. Later.

Say, I just went back to your schematic, and I don't see any bypass caps.

You may also want a pull-up resistor on A and a pull-down resistor on B. These things will probably be on the boards you bought.

You may also want a few diodes to prevent under/over voltage on A & B, and even a TVS diode across A & B, depending on how paranoid you are or how noisy your environment is... BTW, your environment is probably noisy. :)

Cheers, /dev

I have 680 ohm pullup/pulldown resistors on my schematic (just like Nick Gammon's tutorial circuit). I didn't have any 100 nF (0.001 uF) caps but I'll get some. I don't think there is anything wrong with the Linear chips. It has to be something simple/stupid that's wrong.

There they are… totally missed 'em. :confused:

Did you try a one-way test?

you could just hook the TX of one Arduino to the DI (TXn) of one of the chips, and jumper DE (TXENn) high. Then two wires for A/B to the other module, followed by DO (RXn) to the Arduino RX. Your schematic show all RE* pins low.

If that didn’t work with your EasyTransfer program, there is something else going on.

Cheers,
/dev

I didn't try it with EasyTransfer because when I looked at the circuit that was working the wires were connect between the arduino tx & rx pins so it wasn't using SoftwareSerial. I will try the loopback test tonight though.

I assume I can just tie the DE (tx EN) HIGH on the sender connected to the arduino and the RE LOW , DE HIGH and jumper RO to DI on receiver running standalone with no arduino ? Won't the data transmitted from the receiver collide with the data received by the receiver ? (it would have to operate in full duplex mode) Does that mean I need an arduino at the receiving end to save the received data and then re -transmit it after receiving it ? One of the tutorials I tried (rs-485 arduino library) does just that but it didn't work.

tie the DE (tx EN) HIGH on the sender

Yes.

RE LOW , DE HIGH and jumper RO to DI on receiver

Sorry, no. DE HIGH on both makes them both drive A/B. There's no way to do a hard-wired loopback test with the RS-485 modules, because the data has to be buffered by a receiving Arduino.

I need an arduino at the receiving end to save the received data and then re -transmit it after receiving it ?

Exactly. The receiving Arduino must wait for all the data to be received before it can set DE HIGH and send the saved data back to the original sender. If the receiving Arduino sets DE HIGH before the sending Arduino is done, it will collide with the send.

I would suggest that you go back to your EasyTransfer program that works without the RS-485 modules. Remember, RS-485 is the same as two wires, except (1) only one transmitter can go at a time (only one DE HIGH), and (2) you have to set DE HIGH only while transmitting (set DE LOW when done).

One small step from the original EasyTransfer program is to replace the two TX/RX wires with a pair of RS-485 modules that are linked by two wires on A/B. Then jumper DE HIGH on one (the sender) and DE LOW on the other (the receiver). Here's the wiring for this one-way app:

Sending   TX  ------  TX1 (DI)   RS-485   A ------ A   RS-485   (DI) TX2   ------ TX   Receiving
Arduino   RX  ------  RX1 (RO)   Module   B ------ B   Module   (RO) RX2   ------ RX   Arduino
          GND ------  RE*                                            RE*   ------ GND
          5V  ------  TXEN1 (DE)                                (DE) TXEN2 ------ GND

For this one-way test, when the Receiving Arduino tries to send something, the module transmit is disabled, so it won't even go out on A/B, and the Sending Arduino will never receive anything, even though the receiving RE* is LOW.

Next, I'll describe the simple changes to extend the EasyTransfer to be two-way.

Cheers, /dev

[Edited to change DO to RO, and add a quote]

Here’s how you transmit the data structure over plain old serial (i.e. Serial1 or a SoftwareSerial instance you declare, like ss):

void loop()
{
  //send the data
  ET.sendData();

  // Just once per second
  delay(1000);
}

To make the EasyTransfer examples into two-way RS-485 apps, lets start with the TX example first. With RS-485, we must set DE HIGH first, then send all the data, then set DE back to LOW. We will need one extra digital pin to control DE. I called it TXEN_pin in this code:

const int TXEN_pin = 7;

void loop()
{
  // Start driving the A/B lines
  digitalWrite( TXEN_pin, HIGH );

  //send the data
  ET.sendData();

  // Wait for all the data to go out.
  ss.flush();

  // Ignore what we just sent
  while (ss.available())
    ss.read();

  // Release the A/B lines
  digitalWrite( TXEN_pin, LOW );

  // Just once per second
  delay(1000);
}

I used ss here for the software serial you’re using. Use whatever you passed to ET.begin(). Also notice the flush call to wait for all the char to go out, and the while loop to ignore all the bytes we “heard” being sent.

That’s it for sending from the TX example. Let’s come back to receiving in the TX example later, and look at receiving in the RX example. We simply loop until something comes in:

void loop()
{
  if(ET.receiveData()){
    Serial.println( F("Data received!") );
    // print some of the structure members?
  }
}

Easy!

When the Receiving Arduino gets a message, it will send a reply message. It’s totally up to you as to what data is sent back. Just define some new members in the structure. From your first post, I see you want to get some analog readings back from a “slave”, so your data structure might look something like this:

struct RECEIVE_DATA_STRUCTURE{
  //put your variable definitions here for the data you want to receive
  //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO
  int analog1;
  int analog2;
  int reply;
};

RECEIVE_DATA_STRUCTURE mydata;

With that struct, we do the same thing as we did in the TX example, plus fill out the struct:

void loop()
{
  if(ET.receiveData()){

    // We received a struct!
    Serial.println( F("Data received!") );
    // print some of the structure members?

    // Fill out the reply message
    mydata.analog1 = analogRead(A1);
    mydata.analog2 = analogRead(A2);
    mydata.reply   = 1;

    // Start driving the A/B lines
    digitalWrite( TXEN_pin, HIGH );

    //send the data
    ET.sendData();

    // Wait for all the data to go out.
    ss.flush();

    // Ignore what we just sent (the reply)
    while (ss.available())
      ss.read();

    // Release the A/B lines, and go back to waiting for a received message.
    digitalWrite( TXEN_pin, LOW );
  }
}

Now let’s go back to the TX example and add the code to wait for the reply:

void loop()
{
  // Start driving the A/B lines
  digitalWrite( TXEN_pin, HIGH );

  //send the data
  ET.sendData();

  // Wait for all the data to go out.
  ss.flush();

  // Ignore what we just sent
  while (ss.available())
    ss.read();

  // Release the A/B lines
  digitalWrite( TXEN_pin, LOW );

  // Wait up to 1s for the reply to come back
  unsigned long start = millis();
  bool reply_received = false;
  while (millis() - start < 1000U) {

    // Check for the data
    if ( ET.receiveData() ) {

      // It finally got here!
      Serial.println( F("Reply received!") );
      reply_received = true;

      // print something from mydata struct?
      // Serial.println( mydata.analog1 );

      // exit the while loop
      break;
    }
  }

  if (!reply_received)
    Serial.println( F("ERROR - No reply received!") );

  // do all this just once per second or so
  delay(1000);
}

I hope you can see how there might be some common functions between the two, and how the TX example is a kind of master, while the RX example acts like a slave.

Cheers,
/dev

+1 !

It worked !

I followed your wiring list and it worked flawlessly. Thank you VERY MUCH for saving me from RS-485 HELL !
I need to learn play around with Easy Transfer and define more data structures and see

I made a YouTube video.

Now I need to learn how to setup software handshaking that allows transfer of data both ways by setting up some kind of “taking turns” control where one side sends something and then waits for a reply after which tells it what to send next and so forth. I have no clue how to do that yet. I got this far (with your help) so maybe if I just keep plugging along I’ll get it eventually. ;D

FYI, we were both typing out posts at the same time but you finished first !
(I’m reading your post NOW) (It’s getting late so I’ll have to try your new code tomorrow after work)