MODBUS

Arduino / ESP8266 / ESP32를 위한 저렴한 RS-485 인터페이스

기하 2021. 9. 2. 02:22

RS485 인터페이스를 통해
최대 1200미터 거리에 있는 장비와 간섭 없는 연결을 제공할 수 있습니다. 
문제는 RS485 인터페이스를 통해 작동하는 장비에
마이크로컨트롤러를 안전하게 연결하는 방법입니다.

 

번개가 칠 때 긴 케이블로 연결된 전자 장비는 소실될 수 있습니다. 

또한 RS485 장비는 종종 마이크로 컨트롤러가 작동하는 3.5V 또는 5V보다
높은 전압에서 작동하므로 라인에 증가된 전압을 출력하여 RS485 칩을 비활성화할 수 있습니다.

 

Arduino RS485 Shield 또는 저렴한 RS485 보드(MAX485) 
MAX485 인터페이스 컨버터 칩을 이러한 공격으로부터 보호하지 못하므로
산업용으로는 적합하지 않습니다. 

 

여기서는  RS485 인터페이스를 제공하는 동시에
산업 장비에 사용하기에 충분히 보호되는 매우 저렴한 모듈을 살펴보겠습니다 .

XY-485 및 XY-017

XY-485

다소 비싸고 품질이 좋은 XY-485 모듈 부터 시작하겠습니다 . 
아주 잘 조립되어 있고 커넥터가 배선되어 있으며 표시가 잘 되어 있습니다. 
일반적으로 손에 들고 있으면 괜찮은 공장에서 모듈을 조립하고 있다는 느낌을 받습니다.

XY-485 인터페이스 RS485 변환기 모듈

 

즉시 모듈에서 수신 / 전송 제어가 자동으로 수행된다는 점에 유의하십시오. 
Arduino 485 Shield 와 같이 RE/DE 핀의 설정을 HIGH/LOW로 수동으로 제어할 필요는 없습니다 . 

XY-017

XY-485의 쌍둥이 형제. 커넥터가 납땜되지 않고 표시가 더 나빠지지만
보드는 구성 요소 및 요소 배치 측면에서 여전히 서로 매우 유사합니다. 
레이아웃은 약간 다르지만 많이는 아닙니다.

 

RS485 장비가 A + 및 B-에 잘못 연결된 경우
낙뢰, 과전압 및 연결 오류로부터 MAX485 칩의 출력을 보호하는
두 버전의 모듈에 퓨즈와 제너 다이오드가 있다는 점에 주의하십시오.

또한 보드에는 데이터 송수신을 나타내는 2개의 Rx/Tx LED가 있습니다. 

실험을 위해 RX-485 모듈, Arduino Uno 및 RS485로 작동하는 XY-MD01 SHT20 을 사용합니다.

#include "ModbusMaster.h"
#define Slave_ID       1 

ModbusMaster node; // instantiate ModbusMaster object

void setup()
{ 
  Serial.begin(9600, SERIAL_8N1); // Modbus communication runs at 9600 baud
  node.begin(Slave_ID, Serial);   // Modbus slave ID 1
}

void loop()
{  
  uint8_t result = node.readInputRegisters(0x01, 2);// Read 2 registers starting at 0x01
  if (getResultMsg(result))
  {
    Serial.println();
    
    double res_dbl = node.getResponseBuffer(0)/10;
    String res = "Temperature: " + String(res_dbl) + " C\r\n";
    res_dbl = node.getResponseBuffer(1)/10;
    res += "Humidity: " + String(res_dbl) + " %";
    Serial.println(res);
 }
 delay(10000);
}

bool getResultMsg(uint8_t result)
{
  String tmpstr2;

  switch (result) {
  case node.ku8MBSuccess:
    return true;
    break;
  case node.ku8MBIllegalFunction:
    tmpstr2 = "Illegal Function";
    break;
  case node.ku8MBIllegalDataAddress:
    tmpstr2 = "Illegal Data Address";
    break;
  case node.ku8MBIllegalDataValue:
    tmpstr2 = "Illegal Data Value";
    break;
  case node.ku8MBSlaveDeviceFailure:
    tmpstr2 = "Slave Device Failure";
    break;
  case node.ku8MBInvalidSlaveID:
    tmpstr2 = "Invalid Slave ID";
    break;
  case node.ku8MBInvalidFunction:
    tmpstr2 = "Invalid Function";
    break;
  case node.ku8MBResponseTimedOut:
    tmpstr2 = "Response Timed Out";
    break;
  case node.ku8MBInvalidCRC:
    tmpstr2 = "Invalid CRC";
    break;
  default:
    tmpstr2 = "Unknown error: " + String(result);
    break;
  }
  Serial.println(tmpstr2);
  return false;
}

 

아두이노 우노 XY 485 온습도계
Rx (PIN0) RXD A+ A+
TX (PIN1) TXD B+ B+
5 V VCC    
GND GND 接大地 GND  

 

ESP8266 과  XY-485

XY-485 모듈을 납땜되지 않은 ESP8266이 있는 보드에 연결할 때 가장 중요한 것은:

ESP8266 GPIO를 태우지 않도록 XY-485를 3.3V 전원 공급 장치에 연결하십시오!

 

ESP8266이 납땜된 보드의 해당 핀에 모듈을 연결할 수 있습니다. 

거의 모든 개발 보드는 3.3V 출력을 가지고 있지만

별도의 전원 공급 장치에서 전원을 공급하는 것을 선호합니다.

 

Arduino Uno에서 ESP8266의 RX/TX 입력으로 RX/TX를 전송할 때 문제가 없어야 할 것 같습니다. 

그러나 그것은 이론상입니다.  실제로 회로는 즉시 작동을 멈춥니다. 

"Invalid Slave ID" 오류가 있지만 센서에 연결되지 않은 경우 오류는 "Response Timed Out"입니다.

 

하드웨어 직렬 포트로 어려움을 겪은 저는 

Peter Lerup(@plerup)  ESP SoftwareSerial 라이브러리  사용하여

무료 GPIO 핀에서 RX/TX를 에뮬레이트했습니다 . 

 

일반 GPIO에서는 최대 115200의 속도와 여러 SoftwareSerial 포트가 지원됩니다. 

직렬 포트의 소프트웨어 에뮬레이션이므로 저속을 사용하는 것이 좋습니다.

 

직렬 포트의 소프트웨어 에뮬레이션으로 모든 것이 안정적으로 작동하고 센서에서 매개변수를 읽습니다. 

회로의 모든 요소가 접지된 경우(Wemos D1, XY-485, RS485 온도 센서) 그리고

XY-485와 온도 센서가 USB를 통해 PC에 연결된 Wemos D1이 아닌

별도의 전원 공급 장치에서 전원이 공급되는 경우, "Invalid Slave ID" 오류가 나타나지 않습니다.

 

#include "ModbusMaster.h"
#include "SoftwareSerial.h" //https://github.com/plerup/espsoftwareserial  

#define Slave_ID    1 
//#define RX_PIN      14  //D5
#define RX_PIN      15  //D8
//#define TX_PIN      12  //D6    
#define TX_PIN      13  //D7
   
SoftwareSerial swSer(RX_PIN, TX_PIN, false, 128);

// instantiate ModbusMaster object
ModbusMaster node;

void setup()
{
  //********** CHANGE PIN FUNCTION  TO TX/RX ********** 
  //GPIO 1 (TX) swap the pin to a TX.
  //GPIO 7 (TXD) swap pint to a TX.  
  //pinMode(7, FUNCTION_4); 
  //GPIO 3 (RX) swap the pin to a RX.
  //GPIO 8 (RX) swap the pin to a RX.
  //pinMode(8, FUNCTION_4); 
  //***************************************************

  // Modbus communication runs at 9600 baud
  Serial.begin(9600, SERIAL_8N1);
  //Serial.setRxBufferSize(128); //Change default 256 to 128
  
  swSer.begin(9600);

  // Modbus slave ID
  //node.begin(Slave_ID, Serial);
  node.begin(Slave_ID, swSer);
}

이 코드는 비교적 안정적입니다. loop() 및 getResultMsg() 함수는 변경되지 않았습니다. 

실험 중에 사용된 깨진 코드 조각은 제거하지 않고 주석 처리만 했습니다. 도움이 될 수 있습니다.

 

선택한 GPIO 15(Wemos D1의 D8)는 그다지 성공적이지 못했습니다. 서비스 1이며 스케치를 업로드할 때 일정 수준을 설정해야 합니다. 이 때문에 업로드할 때 항상 오류가 발생합니다. D1(GPIO5)으로 전송해야 했습니다.

예를 들어 RX / TX PIN 중 하나를 제거하면 문제가 발생합니다. ModbusMaster가 데이터를 수신하지 않으면 스택에 푸시됩니다. 해결 방법으로 센서를 폴링할 때 워치독을 일시적으로 비활성화할 수 있습니다.

  // Read 2 registers starting at 0x01
  ESP.wdtDisable();
  uint8_t result = node->readInputRegisters(0x01, 2);
  ESP.wdtEnable(1);

또 다른 해결 방법은 설정 섹션에 다음 행을 포함하는 것입니다.

node.begin(Slave_ID2, Serial2);
node.idle(yield);

 

SoftwareSerial 및 ModbusMaster 개체의 동적 생성

경우에 따라 정적 개체 생성이 작동하지 않습니다. 동적으로 생성해야 합니다. 

이 경우 소스 코드는 다음과 같습니다.

#include "ModbusMaster.h"
#include "SoftwareSerial.h"

#define Slave_ID    1 
#define RX_PIN      15  //D8
#define TX_PIN      13  //D7

SoftwareSerial* swSer; //(RX_PIN, TX_PIN, false, 128);

// instantiate ModbusMaster object
ModbusMaster *node;

void setup()
{
  // Modbus communication runs at 9600 baud
  swSer = new SoftwareSerial(RX_PIN, TX_PIN, false, 128);
  
  Serial.begin(9600, SERIAL_8N1);
  //Serial.setRxBufferSize(128); //Change default 256 to 128
  
  swSer->begin(9600);

  node = new ModbusMaster();
  
  // Modbus slave ID 1
  node->begin(Slave_ID, *swSer);
}

void loop()
{
  Serial.println("Try to read data");
  // Read 2 registers starting at 0x01
  ESP.wdtDisable();
  uint8_t result = node->readInputRegisters(0x01, 2);
  ESP.wdtEnable(1);
  
  if (getResultMsg(*node, result))
  {
    Serial.println();
    
    double res_dbl = node->getResponseBuffer(0)/10;
    String res = "Temperature: " + String(res_dbl) + " C\r\n";
    res_dbl = node->getResponseBuffer(1)/10;
    res += "Humidity: " + String(res_dbl) + " %";
    Serial.println(res);
 }
 delay(1000);
}

기능에서

bool getResultMsg(ModbusMaster node, uint8_t result)

한 주장을 덧붙였습니다.

 

void *를 통해 객체 전달

때로는 라이브러리가 클래스의 속성을 선언해야 할 필요가 있습니다.

나중에 이 속성에 개체에 대한 참조를 전달해야 합니다. 이 작업을 수행하는 방법에 대한 작은 예입니다.

 

// instantiate ModbusMaster object
void* p; //ModbusMaster *node;

void setup()
{
  // Modbus communication runs at 9600 baud
  swSer = new SoftwareSerial(RX_PIN, TX_PIN, false, 128);
  
  Serial.begin(9600, SERIAL_8N1);
  //Serial.setRxBufferSize(128); //Change default 256 to 128
  
  swSer->begin(9600);

  p = new ModbusMaster();

  ModbusMaster* node = static_cast<ModbusMaster*>(p);
  node->begin(Slave_ID, *swSer);
}

'MODBUS' 카테고리의 다른 글

Modbus TCP/IP  (0) 2021.11.12
완전한 Modbus 가이드  (0) 2021.09.06
Inexpensive RS485 module with ESP32 (software serial)  (0) 2021.09.02
Modbus Configuration files for ESP8266/Arduino  (0) 2021.08.24
MODBUS Protocol  (0) 2021.08.17