English JA

ArduinoとRS485

RS485と送受信の切り替え

RS485通信は半二重通信なので、同じ通信ラインで送信と受信両方を行います。そのため、RS485用のドライバICは送信と受信どちらもできるようになっていて、送信時のみ送信機をオン(受信機をオフ)、送信が終わったらすぐに送信機をオフにして、受信機をオンにする必要があります。この切り替えを、上記の回路図では「TXDEN」ピンが担っています。
Arduino上で、1秒に1回、100バイトを送るスケッチを書いて、これにTXDENピンの処理を加えることを考えます。


#define TXDEN	3

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

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

	delay(1000);
}

上記回路図のICではTXDENをHIGHにすると送信機ON/受信機OFFになりますので、シリアルを送信する前にTXDENピンをHIGHにすることになりますが、送信の完了時が問題になります。Serial.printやSerial.writeはハードウェアで行われるため、例えば

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

と書いても、スケッチはシリアルの送信をハードウェアに指令するだけですぐに次の行の実行を始めてしまい、送信中にも関わらずTXDENがLOWに戻ってしまいます。これをうまくやるには、シリアルの送信が終わったら割込みを発生させ、その中でTXDENをLOWに戻すようにします。送信完了で割込みを発生させる部分は、MCUの種類によって異なります。

MCUごとのプログラム

Arduino UNO (ATmega328)

UNO以外でも、ATmega328を使ったボードであれば同じです。

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

この設定を事前に行っておくことで、シリアルの送信完了時に割込みが発生するようになります。


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

ここが実際に割込みが発生した場合に実行される部分(割り込みハンドラ)です。

上記のスケッチにこれらの処理を書き加えると以下のようになります。


#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)

LeonardoではSerialではなくてSerial1を使います。使用するレジスタ名も合わせて少し異なります。


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

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

スケッチ例


#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)

SAMDと呼ばれるこれらのボードは構造が結構違い、Serialの割り込みハンドラがArduino本体の方で使われているようで、スケッチの側から呼び出すことができません。が、SAMDはシリアルポートを複数作ることができるので、Arduino本体で使われていない新しいシリアルポートを作ることによってこの問題を回避します。ただし通常のTX/RXピンとは別のピンを使うことになります。
色々な設定方法があり得るのですが、ここでは
D2 : TX
D3 : RX
D4 : TXDEN
という設定で考えてみます。大変ややこしいのですがzeroとM0は、D2ピンとD4ピンが入れ替わっているため、スケッチも異なってきます。
ほかにMKR zeroなど同じMCUのボードも同様にできると思われますが、ピン配置はそれぞれ異なっているようですので、改変が必要そうです。

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)

このMCUはRS485モードというものがあり、割込み処理を行わなくても、送信中の時だけ自動でピンをHIGHにすることが可能です。RTSという機能をピンに割り付けることによって実現します。
TX1(D18) : TX
RX1(D19) : RX
D2 : TXDEN
という設定のスケッチ例です。


#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);
}