Arduino Mini Project ทดสอบการเชื่อมต่อกับ RS485 ด้วย TTL to RS485 ครั้งที่4
จากการทดลอง 3 ครั้งแรก เราได้ทดลอง modbus ด้วย arduino uno คราวนี้เราลองเปลี่ยนมาใช้ esp32 กันบ้าง เพื่อที่ในอนาคต เราสามารถไปต่อยอดกับระบบ wifi ได้ด้วย โดยเราจะทดสอบ master , slave โดยใช้โค้ดเดิม แต่อาจจะมีการปรับใช้ pin ใหม่ เนื่องจาก esp32 มี pinout ที่ไม่เหมือนกับของ uno
ตัว uno มี serial port แค่ 1 port ดังนั้นในโค้ดเก่า เราจึงใช้ software serial ในการสร้าง port serial จาก pinout ขาอื่นๆ แต่ใน esp32 นั้นมี hardware serial มาให้ใช้ถึง 3 port ดังนั้นในการใช้งาน เราจะเปลี่ยนจาก software serial ไปใช้ hardware serial 2 แทน (RX2 , TX2 หรือ pin 16 , 17)
Wiring & Code Master
Pin ESP32 MAX485
16 (RX) ----> RO
17 (TX) ---->DI
18 ---->RE
5 ---->DE
5v ----> VCC
GND ----> GND
A(MAX485) ----> A(USB)
B(MAX485) ----> B(USB)
#include <ModbusMaster.h>
#define RXD2 16
#define TXD2 17
#define MAX485_DE 5
#define MAX485_RE_NEG 18
uint8_t i = 0;
unsigned long last_time = 0;
// instantiate ModbusMaster object
ModbusMaster node;
void preTransmission()
{
digitalWrite(MAX485_RE_NEG, 1);
digitalWrite(MAX485_DE, 1);
}
void postTransmission()
{
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
}
void setup()
{
pinMode(MAX485_RE_NEG, OUTPUT);
pinMode(MAX485_DE, OUTPUT);
// Init in receive mode
digitalWrite(MAX485_RE_NEG, 0);
digitalWrite(MAX485_DE, 0);
// Modbus communication runs at 115200 baud
Serial.begin(115200);
Serial2.begin(9600,SERIAL_8N1, RXD2, TXD2);
// Modbus slave ID 2
node.begin(2, Serial2);
// Callbacks allow us to configure the RS485 transceiver correctly
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
}
void loop()
{
uint8_t result = 0;
uint8_t respons = 0;
uint16_t data[10];
if(millis()- last_time > 1000)
{
i++;
if (i > 100)
{
i = 0;
}
node.writeSingleRegister(0x40003,i);
//node.writeSingleRegister(1,456);
//node.setTransmitBuffer(0x40002, i);
//node.setTransmitBuffer(0x40003, 888);
//respons = node.writeMultipleRegisters(0x40000, 4);
result = node.readHoldingRegisters(0x40000, 10);
if (getResultMsg(&node, result))
{
Serial.println("Read data from Slave");
for (int j = 0; j < 10; j++)
{
data[j] = node.getResponseBuffer(j);
Serial.println(data[j]);
}
}
last_time = millis();
}
}
bool getResultMsg(ModbusMaster *node, uint8_t result)
{
String tmpstr2 = "\r\n";
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;
}
จาก code ด้านบน จะมีส่วนที่แตกต่าง และปรับปรุงมากกว่าครั้งที่แล้ว ตรงที่ครั้งนี้เปลี่ยนไปใช้ Serial2 แทน softwareserial และมี function getResultMsg เอาไว้ตรวจสอบสถานะของการสื่อสาร
ทดสอบการส่งข้อมูลจาก mster ไปที่ slave โดยใช้ writeSingleRegister ส่งไปค่าเดียว หรือจะใช้ writeMultipleRegisters ส่งไปจำนวนหลายๆค่าก็ได้
node.writeSingleRegister(0x40003,i);
node.setTransmitBuffer(0x40002, i);
node.setTransmitBuffer(0x40003, 888);
respons = node.writeMultipleRegisters(0x40000, 4);
ส่วนของการรับข้อมูล มีการใช้ function getResultMsg มาตรวจสอบ ถ้าสามารถเชื่อมต่อได้ code จะ print ค่าที่อ่านได้จาก buffer ถ้ามี error function จะ return ค่า error ออกมา ทำให้ตรวจสอบได้ว่า error ที่จุดไหน
result = node.readHoldingRegisters(0x40000, 10);
if (getResultMsg(&node, result))
{
Serial.println("Read data from Slave");
for (int j = 0; j < 10; j++)
{
data[j] = node.getResponseBuffer(j);
Serial.println(data[j]);
}
}
Slave code for UNO (ปรับปรุง)
#include <ModbusSlave.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(10,11);
#define SLAVE_ID 2
#define SERIAL_BAUDRATE 9600
// Comment out the following line if your not using RS485
#define RS485_CTRL_PIN 8
int rdata[10];
int wdata[10];
uint8_t i = 0;
int sensorValue;
#ifdef RS485_CTRL_PIN
Modbus slave(mySerial, SLAVE_ID, RS485_CTRL_PIN);
#else
Modbus slave(mySerial, SLAVE_ID);
#endif
void setup()
{
slave.cbVector[CB_READ_HOLDING_REGISTERS] = readsensor;
slave.cbVector[CB_WRITE_HOLDING_REGISTERS] = writesensor;
Serial.begin(SERIAL_BAUDRATE);
mySerial.begin(SERIAL_BAUDRATE);
slave.begin(SERIAL_BAUDRATE);
}
void loop()
{
slave.poll();
}
uint8_t readsensor(uint8_t fc, uint16_t address, uint16_t length)
{
sensorValue = analogRead(A1);
rdata[0] = sensorValue;
i++;
if (i > 100)
{
i = 0;
}
rdata[1] = i;
slave.writeRegisterToBuffer(0, rdata[0]);
slave.writeRegisterToBuffer(1, rdata[1]);
return STATUS_OK;
}
uint8_t writesensor(uint8_t fc, uint16_t address, uint16_t length)
{
Serial.println(F("Read memory on Master"));
for (uint8_t i = 0; i < length; ++i)
{
wdata[i] = slave.readRegisterFromBuffer(i);
Serial.println(wdata[i]);
}
return STATUS_OK;
}
สมมุติถ้าให้ esp32 เป็น slave ก็สามารถใช้ code เดียวกันได้ โดยเปลี่ยนไปใช้ Serial2 แทน เหมือนกับ code master แต่จะขอไม่ทดสอบเนื่องจากนิยมให้ esp32 เป็น master มากกว่า เพราะจะได้ไปต่อยอดกับระบบ wifi ได้
ทดลองเชื่อมต่อ Master & Slave
จาก code master และ code slave เราสามารถทดลองสื่อสารด้วย modbus จากการต่อสายดังรูป จากนั้นเปิด serial port เพื่อ monitor ค่าของ esp32 และ program hercules เพื่อ monitor port ของ uno
จะเห็นว่าสามารถรับ-ส่งค่าจาก master -> slave และ slave -> masterได้แล้ว!!
ถ้าหาก code มีอาการ error timeout หรือ Invalid Slave ID ของฝั่ง master ให้ทดลองถอดสาย DE ของฝั่ง slave (uno) ออก เพราะจากการทดลองถอดแล้ว error หาย
reference