Using RS-485 with an Arduino

Wow, two blog posts in one day? Gasp!

I didn't find many good guides on how to do this online other than some very poor quality YouTube videos, and had to figure all this out myself.

I hope by sharing my experience, I can help others who may be struggling with this.

Crash course on RS-485

RS-485 is a protocol often used for long-distance communication in industrial environments. It offers relatively high datarates at very long distances. It's remarkably robust and simple. The jist of it is that it uses differential signalling over twisted pair (although at low baud, it'll work over wet spaghetti noodles) to help with noise immunity and robustness.

If you need to transmit data over a couple of metres with an Arduino, just use RS-485 and some 4-connector cable. You'll thank me later.

This protocol is not the same as RS-232. The possible cable runs are much shorter, the signalling speed is much slower, no multipoint, limited multidrop, larger voltage swing, and RS-232 has standard connectors.

It is also not the same as RS-422. RS-422 has more in common with RS-232 than RS-485. RS-485 can have multiple commanding devices and multiple listening devices; RS-422 can only have one commanding device. RS-422 is also a 4-wire system, whereas RS-485 is a two-wire system. RS-422 is also full duplex, whereas RS-485 is half duplex. It also has shorter possible cable runs, like RS-232, but unlike RS-485.

Actually using RS-485 with the Arduino

RS-485 works with voltages of -7 to +12V range, i.e. ±7V with 0-5V signalling.

These voltage ranges will damage or destroy your Arduino. The pins are not tolerant of negative voltage, nor are they tolerant of voltage above the board's Vout (either 3.3V or 5V).

But there are solutions.

Enter MAX485 and MAX3485

Note: The MAX485 family is for 5 volt operation, whereas the MAX3485 is for 3.3 volt operation; otherwise they are morally equivalent. When in doubt, just get the MAX3483 or MAX3485; either is fine, though the MAX3483 has better noise immunity. Make sure to buy DIP packages if you're working with breadboards or perfboard! You can buy them from Mouser.

The MAX485 and MAX3485 are chips by Maxim that can drive RS-485 for us. They essentially isolate your Arduino from the voltages of RS-485 and also generate the correct voltages we need on the wires.

A word about noise

You may notice reading the datasheets on these chips that some of them are slower than others, and it talks about slew-rate limiting.

You might be thinking, “what the Heck does that mean? And why the Heck would I want a slower chip?”

Slew rate is how fast the voltage actually changes. We ideally want nice little square waves, but we rarely get this in reality. Slew-rate limited drivers are slower because they are limited by this rate in how fast they transmit signals to and from the Arduino (waiting for voltage to go to the correct levels at the correct times rather than just transmitting noise), providing cleaner outputs and and helping provide immunity from reflections and noise (which is ubiquitous in our environment). It's about tradeoffs. If you don't need more than 250 kpbs operation (and over the Arduino's UART, you probably don't), slew-rate limited chips are a great option to provide just a bit of extra insurance. But if you need that speed, and have good cables, then slew-rate limited IC's may not be the best choice.

Connections

It's pretty simple to wire up this chip to the Arduino. Be sure to refer to the datasheet for the exact pinouts.

Just connect VCC to the 3.3/5V output of the Arduino, GND to ground, A to your transmit line on your cable, and B to your receive line on your cable. If you're using peripherals, consult your documentation as to which is A or B (A is generally positive, B is generally negative).

Connect RO to your RX pin on your board, and DI to your TX pin. The other two pins, DE and RE, should be connected to whatever spare pins you have, but take note which is which and where they're connected; this will become important later.

Driver enable and receive enable (negative)

RS-485 is a half-duplex protocol. We can't transmit and receive at the same time without an additional pair. We need to set when we transmit and when we receive. This is where the DE and RE pins come in.

The DE and RE pins control the driver and receiver. The receiver is negated (that's what that little bar on top means), to make it easier to just connect it to DE and have it work as you expect. It can however be convenient to connect them separately.

Essentially, when DE is driven high, the chip is in transmit mode unconditionally. When both are low, the chip is in receive mode. We can set a low-power shutdown mode of the chip by driving RE high and DE low, which may be useful in some applications.

Termination

It is a good idea to terminate both ends of your cables with a 120Ω resistor (connecting A and B together with said resistor), but for short runs at low baud, it is not strictly necessary. Longer runs should absolutely terminate their cables to minimise reflections on the line.

Multiple devices

There can be more than two devices on an RS-485 bus. The official limit is 32, but I've heard some data sheets speak about 128 devices on the same bus. If you use multiple devices, you should use a protocol like Modbus, which is the most common option anyway.

Connections on the bus should be made with short stub connections off the main line. These connections to the bus should be kept as short as possible. A termination resistor is not needed between the two connections of the stub.

For short runs under 10 metres with low baud, wiring it any way that's convenient is probably fine.

A warning about ground

It is important that the ground potential between two RS-485 stations be the same. A simple connection between all of the grounds with a large-value resistor for current limiting (in case of large ground potential differences) is enough to ensure reliable transmission and avoid problems.

It is a common myth repeated online that differential signalling means you don't need to worry about ground potentials. That is absolutely untrue, and this myth can destroy your transceivers or even destroy your Arduino (or at best, just distort your signal). Just make ground common, it alleviates so many potential headaches and sadness.

For example: the difference in ground potential between my Arduino connected to my laptop, and my weather vane and anemometer, is actually several volts. I had a lot of transmit and receive errors, until I connected the grounds through a 5.6MΩ resistor (for current limiting, although it would have probably been better to use something like a 1MΩ resistor or less, to reduce impedance and lessen noise on ground).

Programming

Programming the Arduino with these chips is a snap:

// Our receive enable (negative) and driver enable pins
// Set these to where you connected them, as noted above
const int RE_NEG_PIN = 2;
const int DE_PIN = 3;

void read_enable() {
	digitalWrite(RE_NEG_PIN, 0);
	digitalWrite(DE_PIN, 0);
}

void write_enable() {
	digitalWrite(RE_NEG_PIN, 1);
	digitalWrite(DE_PIN, 1);
}

void lopower_shutdown() {
	digitalWrite(RE_NEG_PIN, 1);
	digitalWrite(DE_PIN, 0);
}

void loop() {
	Serial.begin(9600);
	while(!Serial);

	pinMode(RE_NEG_PIN, OUTPUT);
	pinMode(DE_PIN, OUTPUT);

	// Choose what baud you want or need here
	Serial1.begin(9600);
}

void loop() {
	// Enable transmit
	write_enable();

	// We came out of low-power mode. Wait a few milliseconds before transmit.
	delay(3);

	Serial1.write("Meow\n");

	// Ensure we are flushed before we disable writing
	Serial1.flush();

	// Read a response back
	read_enable();
	char ch = Serial1.read();
	Serial.print(ch);

	// Go into low-power mode
	lopower_shutdown();

	// Wait one second
	delay(1000);
}

Conclusion

I don't know why it was so hard to figure out how to do this with just the chip online. In hindsight, this was all incredibly obvious and also right there in the data sheet if I'd known where to look and what all the jargon meant. But everything is working well for my application (weather station components).

— Elizabeth Myers (Elizafox) Fedi (elsewhere): @Elizafox@social.treehouse.systems Tip jar: PayPal || CashApp || LiberaPay