English JA

Arduino and RS485

RS485 and switching a transmitter and a receiver

RS485 is half-duplex communication and it shares a same transmitting line for both transmit and receive. A rs485 driver IC should be set as a driver mode during transmitting. After transmitting, should be switched to a receiver mode immediately. In the schematic above, the line named "TXDEN" switches these two modes.
This is an example of sending 100 bytes serial data for each seconds, and how to add a TXDEN functionality on it?


#define TXDEN	3

void setup()
{
	pinMode(TXDEN, OUTPUT);
	Serial.begin(9600);
}

void loop()
{
	byte txData[100];
	Serial.write(txData,100);

	delay(1000);
}

The driver chip in the schematic above, making TXDEN HIGH means transmitter ON & receiver OFF. So this pin should be set as HIGH just before starting transmitting serial data. The problem is when the transmission finished. Serial.print or Serial.write are done by hardware so

digitalWrite(TXDEN, HIGH);
Serial.write(txData,100);
digitalWrite(TXDEN, LOW);

this code won't work properly, the sketch asks hardware to send Serial data, then executes a next line. TXDEN returns to LOW even during transmitting.
The solution is using a TX complete interrupt. Actual code depends on type of MCU.

Sketches for each MCUs

Arduino UNO (ATmega328)

This code will work for any Arduino board with ATmega328 MCU.

UCSR0B |= (1<<TXCIE0); // TX Complete Interrupt Enable 0

This setup allows the interrupt when serial transmission completed.


ISR (USART_TX_vect) {
digitalWrite(TXDEN, LOW);
}

This is "Interrupt Service Routine" which is invoked when the interrupt happened.

Here is an example sketch in the end.


#define TXDEN	3

void setup()
{
	UCSR0B |= (1<<TXCIE0);     // TX Complete Interrupt Enable 0
	pinMode(TXDEN, OUTPUT);
	Serial.begin(9600);
}

ISR (USART_TX_vect)
{
       digitalWrite(TXDEN, LOW);
}

void loop()
{
	byte txData[100];
	digitalWrite(TXDEN, HIGH);
	Serial.write(txData,100);

	delay(1000);
}

Arduino Leonardo (ATmega32u4)

For Leonardo, use Serial1 instead of Serial. Name of using registers are slightly different from UNO.


UCSR1B |= (1<<TXCIE1); // TX Complete Interrupt Enable 1

ISR (USART1_TX_vect) {
digitalWrite(TXDEN, LOW);
}

Sketch example


#define TXDEN	3

void setup()
{
	UCSR1B |= (1<<TXCIE1);     // TX Complete Interrupt Enable 1
	pinMode(TXDEN, OUTPUT);
	Serial1.begin(9600);
}

ISR (USART1_TX_vect)
{
       digitalWrite(TXDEN, LOW);
}

void loop()
{
	byte txData[100];
	digitalWrite(TXDEN, HIGH);
	Serial1.write(txData,100);

	delay(1000);
}

Arduino Zero/M0 (ATMSAD21G18A)

These boards called as SAMD, have different structure from UNO/Leonardo. The Interrupt Service Routine for Serial is occupied by Arduino IDE and you can not call it from the sketch. SAMD boards can have some more serial ports so setup a new serial port and use it for RS485. There are many possible setups but we will try :
D2 : TX
D3 : RX
D4 : TXDEN

This is so confusing but D2 and D4 are swapped between zero and M0. Here are examples for each boards. Another SAMD boards like a MKR zero, will also work for same strategy but might need some modifications.

Arduino M0


// Arduino M0
// D2 PA08 SERCOM2/PAD[0]
Uart Serial3 (&sercom2, 3, 2, SERCOM_RX_PAD_1, UART_TX_PAD_0); 
#define txDenPin 4
void SERCOM2_Handler()
{   
  Serial3.IrqHandler();
  if (SERCOM2->USART.INTFLAG.bit.TXC)
  {
    digitalWrite(txDenPin, LOW);
    SERCOM2->USART.INTFLAG.bit.TXC = 1;
  }
}

void setup() {
  pinMode(txDenPin, OUTPUT);
  Serial3.begin(9600);
  pinPeripheral(3, PIO_SERCOM_ALT);
  pinPeripheral(2, PIO_SERCOM_ALT);
  SERCOM2->USART.INTENSET.bit.TXC = 1;
}

void loop()
{
	byte txData[100];
	digitalWrite(txDenPin, HIGH);
	Serial3.write(txData,100);

	delay(1000);
}

Arduino zero


// Arduino Zero
// D2 PA14 SERCOM2/PAD[2]
Uart Serial3 (&sercom2, 3, 2, SERCOM_RX_PAD_1, UART_TX_PAD_2); 
#define txDenPin 4
void SERCOM2_Handler()
{   
  Serial3.IrqHandler();
  if (SERCOM2->USART.INTFLAG.bit.TXC)
  {
    digitalWrite(txDenPin, LOW);
    SERCOM2->USART.INTFLAG.bit.TXC = 1;
  }
}

void setup() {
  pinMode(txDenPin, OUTPUT);
  Serial3.begin(9600);
  pinPeripheral(3, PIO_SERCOM_ALT);
  pinPeripheral(2, PIO_SERCOM);
  SERCOM2->USART.INTENSET.bit.TXC = 1;
}

void loop()
{
	byte txData[100];
	digitalWrite(txDenPin, HIGH);
	Serial3.write(txData,100);

	delay(1000);
}

Arduino Due (SAM3X8E)

This MCU has RS485 mode and it allows full automatic control of TXDEN. For the example, we will use Serial1.
TX1(D18) : TX
RX1(D19) : RX
D2 : TXDEN
Sketch example:


#define txDenPin 2
void setup() {
  pinMode(txDenPin , OUTPUT);
  Serial1.begin(9600);
  USART0->US_MR |= 0x01;
  REG_PIOB_ABSR &= ~PIO_ABSR_P25;   // select peripheral A
  REG_PIOB_PDR |= PIO_PDR_P25;      // Disable the GPIO and switch to the peripheral
}

void loop() {
  byte txData[100];
  Serial1.write(txData,20);
  delay(1000);
}