mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-01-11 21:31:02 +01:00
squish together IO_Modbus
This commit is contained in:
parent
b39523bfa6
commit
755e6ab0cc
461
IO_Modbus.cpp
461
IO_Modbus.cpp
@ -19,7 +19,468 @@
|
|||||||
|
|
||||||
#include "IO_Modbus.h"
|
#include "IO_Modbus.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
void ModbusADU::setTransactionId(uint16_t transactionId) {
|
||||||
|
_setRegister(tcp, 0, transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setProtocolId(uint16_t protocolId) {
|
||||||
|
_setRegister(tcp, 2, protocolId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setLength(uint16_t length) {
|
||||||
|
if (length < 3 || length > 254) _setRegister(tcp, 4, 0);
|
||||||
|
else _setRegister(tcp, 4, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setUnitId(uint8_t unitId) {
|
||||||
|
tcp[6] = unitId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setFunctionCode(uint8_t functionCode) {
|
||||||
|
pdu[0] = functionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setDataRegister(uint8_t index, uint16_t value) {
|
||||||
|
_setRegister(data, index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ModbusADU::setRtuLen(uint16_t rtuLen) {
|
||||||
|
setLength(rtuLen - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setTcpLen(uint16_t tcpLen) {
|
||||||
|
setLength(tcpLen - 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setPduLen(uint16_t pduLen) {
|
||||||
|
setLength(pduLen + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusADU::setDataLen(uint16_t dataLen) {
|
||||||
|
setLength(dataLen + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getTransactionId() {
|
||||||
|
return _getRegister(tcp, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getProtocolId() {
|
||||||
|
return _getRegister(tcp, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getLength() {
|
||||||
|
uint16_t length = _getRegister(tcp, 4);
|
||||||
|
if (length < 3 || length > 254) return 0;
|
||||||
|
else return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ModbusADU::getUnitId() {
|
||||||
|
return tcp[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ModbusADU::getFunctionCode() {
|
||||||
|
return pdu[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getDataRegister(uint8_t index) {
|
||||||
|
return _getRegister(data, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getRtuLen() {
|
||||||
|
uint16_t len = getLength();
|
||||||
|
if (len == 0) return 0;
|
||||||
|
else return len + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getTcpLen() {
|
||||||
|
uint16_t len = getLength();
|
||||||
|
if (len == 0) return 0;
|
||||||
|
else return len + 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getPduLen() {
|
||||||
|
uint16_t len = getLength();
|
||||||
|
if (len == 0) return 0;
|
||||||
|
else return len - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::getDataLen() {
|
||||||
|
uint16_t len = getLength();
|
||||||
|
if (len == 0) return 0;
|
||||||
|
else return len - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ModbusADU::updateCrc() {
|
||||||
|
uint16_t len = getLength();
|
||||||
|
uint16_t crc = _calculateCrc(len);
|
||||||
|
rtu[len] = lowByte(crc);
|
||||||
|
rtu[len + 1] = highByte(crc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModbusADU::crcGood() {
|
||||||
|
uint16_t len = getLength();
|
||||||
|
uint16_t aduCrc = rtu[len] | (rtu[len + 1] << 8);
|
||||||
|
uint16_t calculatedCrc = _calculateCrc(len);
|
||||||
|
if (aduCrc == calculatedCrc) return true;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ModbusADU::prepareExceptionResponse(uint8_t exceptionCode) {
|
||||||
|
pdu[0] |= 0x80;
|
||||||
|
pdu[1] = exceptionCode;
|
||||||
|
setPduLen(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ModbusADU::_setRegister(uint8_t *buf, uint16_t index, uint16_t value) {
|
||||||
|
buf[index] = highByte(value);
|
||||||
|
buf[index + 1] = lowByte(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::_getRegister(uint8_t *buf, uint16_t index) {
|
||||||
|
return (buf[index] << 8) | buf[index + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ModbusADU::_calculateCrc(uint16_t len) {
|
||||||
|
uint16_t value = 0xFFFF;
|
||||||
|
for (uint16_t i = 0; i < len; i++) {
|
||||||
|
value ^= (uint16_t)rtu[i];
|
||||||
|
for (uint8_t j = 0; j < 8; j++) {
|
||||||
|
bool lsb = value & 1;
|
||||||
|
value >>= 1;
|
||||||
|
if (lsb == true) value ^= 0xA001;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t div8RndUp(uint16_t value) {
|
||||||
|
return (value + 7) >> 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUComm::ModbusRTUComm(Stream& serial, int8_t dePin, int8_t rePin) : _serial(serial) {
|
||||||
|
_dePin = dePin;
|
||||||
|
_rePin = rePin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusRTUComm::begin(unsigned long baud, uint32_t config) {
|
||||||
|
unsigned long bitsPerChar;
|
||||||
|
switch (config) {
|
||||||
|
case SERIAL_8E2:
|
||||||
|
case SERIAL_8O2:
|
||||||
|
bitsPerChar = 12;
|
||||||
|
break;
|
||||||
|
case SERIAL_8N2:
|
||||||
|
case SERIAL_8E1:
|
||||||
|
case SERIAL_8O1:
|
||||||
|
bitsPerChar = 11;
|
||||||
|
break;
|
||||||
|
case SERIAL_8N1:
|
||||||
|
default:
|
||||||
|
bitsPerChar = 10;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (baud <= 19200) {
|
||||||
|
_charTimeout = (bitsPerChar * 2500000) / baud;
|
||||||
|
_frameTimeout = (bitsPerChar * 4500000) / baud;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_charTimeout = (bitsPerChar * 1000000) / baud + 750;
|
||||||
|
_frameTimeout = (bitsPerChar * 1000000) / baud + 1750;
|
||||||
|
}
|
||||||
|
#if defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) || defined(ARDUINO_GIGA) || (defined(ARDUINO_NANO_RP2040_CONNECT) && defined(ARDUINO_ARCH_MBED))
|
||||||
|
_postDelay = ((bitsPerChar * 1000000) / baud) + 2;
|
||||||
|
#endif
|
||||||
|
if (_dePin >= 0) {
|
||||||
|
pinMode(_dePin, OUTPUT);
|
||||||
|
digitalWrite(_dePin, LOW);
|
||||||
|
}
|
||||||
|
if (_rePin >= 0) {
|
||||||
|
pinMode(_rePin, OUTPUT);
|
||||||
|
digitalWrite(_rePin, LOW);
|
||||||
|
}
|
||||||
|
clearRxBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusRTUComm::setTimeout(unsigned long timeout) {
|
||||||
|
_readTimeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUCommError ModbusRTUComm::readAdu(ModbusADU& adu) {
|
||||||
|
adu.setRtuLen(0);
|
||||||
|
unsigned long startMillis = millis();
|
||||||
|
while (!_serial.available()) {
|
||||||
|
if (millis() - startMillis >= _readTimeout) return MODBUS_RTU_COMM_TIMEOUT;
|
||||||
|
}
|
||||||
|
uint16_t len = 0;
|
||||||
|
unsigned long startMicros = micros();
|
||||||
|
do {
|
||||||
|
if (_serial.available()) {
|
||||||
|
startMicros = micros();
|
||||||
|
adu.rtu[len] = _serial.read();
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
} while (micros() - startMicros <= _charTimeout && len < 256);
|
||||||
|
adu.setRtuLen(len);
|
||||||
|
while (micros() - startMicros < _frameTimeout);
|
||||||
|
if (_serial.available()) {
|
||||||
|
adu.setRtuLen(0);
|
||||||
|
return MODBUS_RTU_COMM_FRAME_ERROR;
|
||||||
|
}
|
||||||
|
if (!adu.crcGood()) {
|
||||||
|
adu.setRtuLen(0);
|
||||||
|
return MODBUS_RTU_COMM_CRC_ERROR;
|
||||||
|
}
|
||||||
|
return MODBUS_RTU_COMM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusRTUComm::writeAdu(ModbusADU& adu) {
|
||||||
|
adu.updateCrc();
|
||||||
|
if (_dePin >= 0) digitalWrite(_dePin, HIGH);
|
||||||
|
if (_rePin >= 0) digitalWrite(_rePin, HIGH);
|
||||||
|
_serial.write(adu.rtu, adu.getRtuLen());
|
||||||
|
_serial.flush();
|
||||||
|
delayMicroseconds(_postDelay);
|
||||||
|
if (_dePin >= 0) digitalWrite(_dePin, LOW);
|
||||||
|
if (_rePin >= 0) digitalWrite(_rePin, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusRTUComm::clearRxBuffer() {
|
||||||
|
unsigned long startMicros = micros();
|
||||||
|
do {
|
||||||
|
if (_serial.available() > 0) {
|
||||||
|
startMicros = micros();
|
||||||
|
_serial.read();
|
||||||
|
}
|
||||||
|
} while (micros() - startMicros < _frameTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMaster::ModbusRTUMaster(Stream& serial, int8_t dePin, int8_t rePin) : _rtuComm(serial, dePin, rePin) {
|
||||||
|
_rtuComm.setTimeout(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusRTUMaster::setTimeout(unsigned long timeout) {
|
||||||
|
_rtuComm.setTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusRTUMaster::begin(unsigned long baud, uint32_t config) {
|
||||||
|
_rtuComm.begin(baud, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::readCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
||||||
|
return _readValues(id, 1, startAddress, buf, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::readDiscreteInputs(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
||||||
|
return _readValues(id, 2, startAddress, buf, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::readHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
||||||
|
return _readValues(id, 3, startAddress, buf, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::readInputRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
||||||
|
return _readValues(id, 4, startAddress, buf, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::writeSingleCoil(uint8_t id, uint16_t address, bool value) {
|
||||||
|
return _writeSingleValue(id, 5, address, ((value) ? 0xFF00 : 0x0000));
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::writeSingleHoldingRegister(uint8_t id, uint16_t address, uint16_t value) {
|
||||||
|
return _writeSingleValue(id, 6, address, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::writeMultipleCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
||||||
|
const uint8_t functionCode = 15;
|
||||||
|
if (id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
||||||
|
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
||||||
|
if (quantity == 0 || quantity > 1968) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
||||||
|
ModbusADU adu;
|
||||||
|
uint16_t byteCount = div8RndUp(quantity);
|
||||||
|
adu.setUnitId(id);
|
||||||
|
adu.setFunctionCode(functionCode);
|
||||||
|
adu.setDataRegister(0, startAddress);
|
||||||
|
adu.setDataRegister(2, quantity);
|
||||||
|
adu.data[4] = byteCount;
|
||||||
|
for (uint16_t i = 0; i < quantity; i++) {
|
||||||
|
bitWrite(adu.data[5 + (i >> 3)], i & 7, buf[i]);
|
||||||
|
}
|
||||||
|
for (uint16_t i = quantity; i < (byteCount * 8); i++) {
|
||||||
|
bitClear(adu.data[5 + (i >> 3)], i & 7);
|
||||||
|
}
|
||||||
|
adu.setDataLen(5 + byteCount);
|
||||||
|
_rtuComm.writeAdu(adu);
|
||||||
|
if (id == 0) return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
||||||
|
if (commError) return _translateCommError(commError);
|
||||||
|
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
||||||
|
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
||||||
|
_exceptionResponse = adu.data[0];
|
||||||
|
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
||||||
|
}
|
||||||
|
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
||||||
|
if (adu.getDataLen() != 4) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
||||||
|
if (adu.getDataRegister(0) != startAddress) return MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS;
|
||||||
|
if (adu.getDataRegister(2) != quantity) return MODBUS_RTU_MASTER_UNEXPECTED_QUANTITY;
|
||||||
|
return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::writeMultipleHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
||||||
|
uint8_t functionCode = 16;
|
||||||
|
if (id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
||||||
|
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
||||||
|
if (quantity == 0 || quantity > 123) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
||||||
|
uint16_t byteCount = quantity * 2;
|
||||||
|
ModbusADU adu;
|
||||||
|
adu.setUnitId(id);
|
||||||
|
adu.setFunctionCode(functionCode);
|
||||||
|
adu.setDataRegister(0, startAddress);
|
||||||
|
adu.setDataRegister(2, quantity);
|
||||||
|
adu.data[4] = byteCount;
|
||||||
|
for (uint16_t i = 0; i < quantity; i++) {
|
||||||
|
adu.setDataRegister(5 + (i * 2), buf[i]);
|
||||||
|
}
|
||||||
|
adu.setDataLen(5 + byteCount);
|
||||||
|
_rtuComm.writeAdu(adu);
|
||||||
|
if (id == 0) return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
||||||
|
if (commError) return _translateCommError(commError);
|
||||||
|
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
||||||
|
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
||||||
|
_exceptionResponse = adu.data[0];
|
||||||
|
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
||||||
|
}
|
||||||
|
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
||||||
|
if (adu.getDataLen() != 4) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
||||||
|
if (adu.getDataRegister(0) != startAddress) return MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS;
|
||||||
|
if (adu.getDataRegister(2) != quantity) return MODBUS_RTU_MASTER_UNEXPECTED_QUANTITY;
|
||||||
|
return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t ModbusRTUMaster::getExceptionResponse() {
|
||||||
|
return _exceptionResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::_readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
||||||
|
if (id < 1 || id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
||||||
|
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
||||||
|
if (quantity == 0 || quantity > 2000) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
||||||
|
ModbusADU adu;
|
||||||
|
adu.setUnitId(id);
|
||||||
|
adu.setFunctionCode(functionCode);
|
||||||
|
adu.setDataRegister(0, startAddress);
|
||||||
|
adu.setDataRegister(2, quantity);
|
||||||
|
adu.setDataLen(4);
|
||||||
|
_rtuComm.writeAdu(adu);
|
||||||
|
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
||||||
|
if (commError) return _translateCommError(commError);
|
||||||
|
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
||||||
|
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
||||||
|
_exceptionResponse = adu.data[0];
|
||||||
|
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
||||||
|
}
|
||||||
|
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
||||||
|
uint16_t byteCount = div8RndUp(quantity);
|
||||||
|
if (adu.getDataLen() != (1 + byteCount)) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
||||||
|
if (adu.data[0] != byteCount) return MODBUS_RTU_MASTER_UNEXPECTED_BYTE_COUNT;
|
||||||
|
for (uint16_t i = 0; i < quantity; i++) {
|
||||||
|
buf[i] = bitRead(adu.data[1 + (i >> 3)], i & 7);
|
||||||
|
}
|
||||||
|
return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::_readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
||||||
|
if (id < 1 || id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
||||||
|
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
||||||
|
if (quantity == 0 || quantity > 125) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
||||||
|
ModbusADU adu;
|
||||||
|
adu.setUnitId(id);
|
||||||
|
adu.setFunctionCode(functionCode);
|
||||||
|
adu.setDataRegister(0, startAddress);
|
||||||
|
adu.setDataRegister(2, quantity);
|
||||||
|
adu.setDataLen(4);
|
||||||
|
_rtuComm.writeAdu(adu);
|
||||||
|
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
||||||
|
if (commError) return _translateCommError(commError);
|
||||||
|
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
||||||
|
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
||||||
|
_exceptionResponse = adu.data[0];
|
||||||
|
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
||||||
|
}
|
||||||
|
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
||||||
|
uint16_t byteCount = quantity * 2;
|
||||||
|
if (adu.getDataLen() != (1 + byteCount)) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
||||||
|
if (adu.data[0] != byteCount) return MODBUS_RTU_MASTER_UNEXPECTED_BYTE_COUNT;
|
||||||
|
for (uint16_t i = 0; i < quantity; i++) {
|
||||||
|
buf[i] = adu.getDataRegister(1 + (i * 2));
|
||||||
|
}
|
||||||
|
return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::_writeSingleValue(uint8_t id, uint8_t functionCode, uint16_t address, uint16_t value) {
|
||||||
|
if (id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
||||||
|
ModbusADU adu;
|
||||||
|
adu.setUnitId(id);
|
||||||
|
adu.setFunctionCode(functionCode);
|
||||||
|
adu.setDataRegister(0, address);
|
||||||
|
adu.setDataRegister(2, value);
|
||||||
|
adu.setDataLen(4);
|
||||||
|
_rtuComm.writeAdu(adu);
|
||||||
|
if (id == 0) return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
||||||
|
if (commError) return _translateCommError(commError);
|
||||||
|
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
||||||
|
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
||||||
|
_exceptionResponse = adu.data[0];
|
||||||
|
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
||||||
|
}
|
||||||
|
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
||||||
|
if (adu.getDataLen() != 4) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
||||||
|
if (adu.getDataRegister(0) != address) return MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS;
|
||||||
|
if (adu.getDataRegister(2) != value) return MODBUS_RTU_MASTER_UNEXPECTED_VALUE;
|
||||||
|
return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ModbusRTUMasterError ModbusRTUMaster::_translateCommError(ModbusRTUCommError commError) {
|
||||||
|
switch (commError) {
|
||||||
|
case MODBUS_RTU_COMM_SUCCESS:
|
||||||
|
return MODBUS_RTU_MASTER_SUCCESS;
|
||||||
|
case MODBUS_RTU_COMM_TIMEOUT:
|
||||||
|
return MODBUS_RTU_MASTER_RESPONSE_TIMEOUT;
|
||||||
|
case MODBUS_RTU_COMM_FRAME_ERROR:
|
||||||
|
return MODBUS_RTU_MASTER_FRAME_ERROR;
|
||||||
|
case MODBUS_RTU_COMM_CRC_ERROR:
|
||||||
|
return MODBUS_RTU_MASTER_CRC_ERROR;
|
||||||
|
default:
|
||||||
|
return MODBUS_RTU_MASTER_UNKNOWN_COMM_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************
|
/************************************************************
|
||||||
* Modbus implementation
|
* Modbus implementation
|
||||||
|
124
IO_Modbus.h
124
IO_Modbus.h
@ -52,7 +52,129 @@
|
|||||||
#define IO_MODBUS_H
|
#define IO_MODBUS_H
|
||||||
|
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "ModbusRTUMaster.h"
|
class ModbusADU {
|
||||||
|
public:
|
||||||
|
uint8_t *rtu = _adu + 6;
|
||||||
|
uint8_t *tcp = _adu;
|
||||||
|
uint8_t *pdu = _adu + 7;
|
||||||
|
uint8_t *data = _adu + 8;
|
||||||
|
|
||||||
|
void setTransactionId(uint16_t transactionId);
|
||||||
|
void setProtocolId(uint16_t protocolId);
|
||||||
|
void setLength(uint16_t length);
|
||||||
|
void setUnitId(uint8_t unitId);
|
||||||
|
void setFunctionCode(uint8_t functionCode);
|
||||||
|
void setDataRegister(uint8_t index, uint16_t value);
|
||||||
|
|
||||||
|
void setRtuLen(uint16_t rtuLen);
|
||||||
|
void setTcpLen(uint16_t tcpLen);
|
||||||
|
void setPduLen(uint16_t pduLen);
|
||||||
|
void setDataLen(uint16_t dataLen);
|
||||||
|
|
||||||
|
uint16_t getTransactionId();
|
||||||
|
uint16_t getProtocolId();
|
||||||
|
uint16_t getLength();
|
||||||
|
uint8_t getUnitId();
|
||||||
|
uint8_t getFunctionCode();
|
||||||
|
uint16_t getDataRegister(uint8_t index);
|
||||||
|
|
||||||
|
uint16_t getRtuLen();
|
||||||
|
uint16_t getTcpLen();
|
||||||
|
uint16_t getPduLen();
|
||||||
|
uint16_t getDataLen();
|
||||||
|
|
||||||
|
void updateCrc();
|
||||||
|
bool crcGood();
|
||||||
|
|
||||||
|
void prepareExceptionResponse(uint8_t exceptionCode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t _adu[262];
|
||||||
|
void _setRegister(uint8_t *buf, uint16_t index, uint16_t value);
|
||||||
|
uint16_t _getRegister(uint8_t *buf, uint16_t index);
|
||||||
|
uint16_t _calculateCrc(uint16_t len);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
uint16_t div8RndUp(uint16_t value);
|
||||||
|
|
||||||
|
enum ModbusRTUCommError : uint8_t {
|
||||||
|
MODBUS_RTU_COMM_SUCCESS = 0,
|
||||||
|
MODBUS_RTU_COMM_TIMEOUT = 1,
|
||||||
|
MODBUS_RTU_COMM_FRAME_ERROR = 2,
|
||||||
|
MODBUS_RTU_COMM_CRC_ERROR = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModbusRTUComm {
|
||||||
|
public:
|
||||||
|
ModbusRTUComm(Stream& serial, int8_t dePin = -1, int8_t rePin = -1);
|
||||||
|
void begin(unsigned long baud, uint32_t config = SERIAL_8N1);
|
||||||
|
void setTimeout(unsigned long timeout);
|
||||||
|
ModbusRTUCommError readAdu(ModbusADU& adu);
|
||||||
|
void writeAdu(ModbusADU& adu);
|
||||||
|
void clearRxBuffer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Stream& _serial;
|
||||||
|
int8_t _dePin;
|
||||||
|
int8_t _rePin;
|
||||||
|
unsigned long _charTimeout;
|
||||||
|
unsigned long _frameTimeout;
|
||||||
|
unsigned long _postDelay = 0;
|
||||||
|
unsigned long _readTimeout = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ModbusRTUMasterError : uint8_t {
|
||||||
|
MODBUS_RTU_MASTER_SUCCESS = 0,
|
||||||
|
MODBUS_RTU_MASTER_INVALID_ID = 1,
|
||||||
|
MODBUS_RTU_MASTER_INVALID_BUFFER = 2,
|
||||||
|
MODBUS_RTU_MASTER_INVALID_QUANTITY = 3,
|
||||||
|
MODBUS_RTU_MASTER_RESPONSE_TIMEOUT = 4,
|
||||||
|
MODBUS_RTU_MASTER_FRAME_ERROR = 5,
|
||||||
|
MODBUS_RTU_MASTER_CRC_ERROR = 6,
|
||||||
|
MODBUS_RTU_MASTER_UNKNOWN_COMM_ERROR = 7,
|
||||||
|
MODBUS_RTU_MASTER_UNEXPECTED_ID = 8,
|
||||||
|
MODBUS_RTU_MASTER_EXCEPTION_RESPONSE = 9,
|
||||||
|
MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE = 10,
|
||||||
|
MODBUS_RTU_MASTER_UNEXPECTED_LENGTH = 11,
|
||||||
|
MODBUS_RTU_MASTER_UNEXPECTED_BYTE_COUNT = 12,
|
||||||
|
MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS = 13,
|
||||||
|
MODBUS_RTU_MASTER_UNEXPECTED_VALUE = 14,
|
||||||
|
MODBUS_RTU_MASTER_UNEXPECTED_QUANTITY = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModbusRTUMaster {
|
||||||
|
public:
|
||||||
|
ModbusRTUMaster(Stream& serial, int8_t dePin = -1, int8_t rePin = -1);
|
||||||
|
void setTimeout(unsigned long timeout);
|
||||||
|
void begin(unsigned long baud, uint32_t config = SERIAL_8N1);
|
||||||
|
|
||||||
|
ModbusRTUMasterError readCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity);
|
||||||
|
ModbusRTUMasterError readDiscreteInputs(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity);
|
||||||
|
ModbusRTUMasterError readHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
||||||
|
ModbusRTUMasterError readInputRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
||||||
|
|
||||||
|
ModbusRTUMasterError writeSingleCoil(uint8_t id, uint16_t address, bool value);
|
||||||
|
ModbusRTUMasterError writeSingleHoldingRegister(uint8_t id, uint16_t address, uint16_t value);
|
||||||
|
ModbusRTUMasterError writeMultipleCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity);
|
||||||
|
ModbusRTUMasterError writeMultipleHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
||||||
|
|
||||||
|
uint8_t getExceptionResponse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModbusRTUComm _rtuComm;
|
||||||
|
uint8_t _exceptionResponse = 0;
|
||||||
|
|
||||||
|
ModbusRTUMasterError _readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, bool buf[], uint16_t quantity);
|
||||||
|
ModbusRTUMasterError _readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
||||||
|
ModbusRTUMasterError _writeSingleValue(uint8_t id, uint8_t functionCode, uint16_t address, uint16_t value);
|
||||||
|
|
||||||
|
ModbusRTUMasterError _translateCommError(ModbusRTUCommError commError);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* Modbusnode class
|
* Modbusnode class
|
||||||
*
|
*
|
||||||
|
153
ModbusADU.cpp
153
ModbusADU.cpp
@ -1,153 +0,0 @@
|
|||||||
#include "ModbusADU.h"
|
|
||||||
|
|
||||||
void ModbusADU::setTransactionId(uint16_t transactionId) {
|
|
||||||
_setRegister(tcp, 0, transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setProtocolId(uint16_t protocolId) {
|
|
||||||
_setRegister(tcp, 2, protocolId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setLength(uint16_t length) {
|
|
||||||
if (length < 3 || length > 254) _setRegister(tcp, 4, 0);
|
|
||||||
else _setRegister(tcp, 4, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setUnitId(uint8_t unitId) {
|
|
||||||
tcp[6] = unitId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setFunctionCode(uint8_t functionCode) {
|
|
||||||
pdu[0] = functionCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setDataRegister(uint8_t index, uint16_t value) {
|
|
||||||
_setRegister(data, index, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ModbusADU::setRtuLen(uint16_t rtuLen) {
|
|
||||||
setLength(rtuLen - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setTcpLen(uint16_t tcpLen) {
|
|
||||||
setLength(tcpLen - 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setPduLen(uint16_t pduLen) {
|
|
||||||
setLength(pduLen + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusADU::setDataLen(uint16_t dataLen) {
|
|
||||||
setLength(dataLen + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getTransactionId() {
|
|
||||||
return _getRegister(tcp, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getProtocolId() {
|
|
||||||
return _getRegister(tcp, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getLength() {
|
|
||||||
uint16_t length = _getRegister(tcp, 4);
|
|
||||||
if (length < 3 || length > 254) return 0;
|
|
||||||
else return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ModbusADU::getUnitId() {
|
|
||||||
return tcp[6];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t ModbusADU::getFunctionCode() {
|
|
||||||
return pdu[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getDataRegister(uint8_t index) {
|
|
||||||
return _getRegister(data, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getRtuLen() {
|
|
||||||
uint16_t len = getLength();
|
|
||||||
if (len == 0) return 0;
|
|
||||||
else return len + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getTcpLen() {
|
|
||||||
uint16_t len = getLength();
|
|
||||||
if (len == 0) return 0;
|
|
||||||
else return len + 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getPduLen() {
|
|
||||||
uint16_t len = getLength();
|
|
||||||
if (len == 0) return 0;
|
|
||||||
else return len - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::getDataLen() {
|
|
||||||
uint16_t len = getLength();
|
|
||||||
if (len == 0) return 0;
|
|
||||||
else return len - 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ModbusADU::updateCrc() {
|
|
||||||
uint16_t len = getLength();
|
|
||||||
uint16_t crc = _calculateCrc(len);
|
|
||||||
rtu[len] = lowByte(crc);
|
|
||||||
rtu[len + 1] = highByte(crc);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModbusADU::crcGood() {
|
|
||||||
uint16_t len = getLength();
|
|
||||||
uint16_t aduCrc = rtu[len] | (rtu[len + 1] << 8);
|
|
||||||
uint16_t calculatedCrc = _calculateCrc(len);
|
|
||||||
if (aduCrc == calculatedCrc) return true;
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ModbusADU::prepareExceptionResponse(uint8_t exceptionCode) {
|
|
||||||
pdu[0] |= 0x80;
|
|
||||||
pdu[1] = exceptionCode;
|
|
||||||
setPduLen(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ModbusADU::_setRegister(uint8_t *buf, uint16_t index, uint16_t value) {
|
|
||||||
buf[index] = highByte(value);
|
|
||||||
buf[index + 1] = lowByte(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::_getRegister(uint8_t *buf, uint16_t index) {
|
|
||||||
return (buf[index] << 8) | buf[index + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ModbusADU::_calculateCrc(uint16_t len) {
|
|
||||||
uint16_t value = 0xFFFF;
|
|
||||||
for (uint16_t i = 0; i < len; i++) {
|
|
||||||
value ^= (uint16_t)rtu[i];
|
|
||||||
for (uint8_t j = 0; j < 8; j++) {
|
|
||||||
bool lsb = value & 1;
|
|
||||||
value >>= 1;
|
|
||||||
if (lsb == true) value ^= 0xA001;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint16_t div8RndUp(uint16_t value) {
|
|
||||||
return (value + 7) >> 3;
|
|
||||||
}
|
|
52
ModbusADU.h
52
ModbusADU.h
@ -1,52 +0,0 @@
|
|||||||
#ifndef ModbusADU_h
|
|
||||||
#define ModbusADU_h
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
|
|
||||||
class ModbusADU {
|
|
||||||
public:
|
|
||||||
uint8_t *rtu = _adu + 6;
|
|
||||||
uint8_t *tcp = _adu;
|
|
||||||
uint8_t *pdu = _adu + 7;
|
|
||||||
uint8_t *data = _adu + 8;
|
|
||||||
|
|
||||||
void setTransactionId(uint16_t transactionId);
|
|
||||||
void setProtocolId(uint16_t protocolId);
|
|
||||||
void setLength(uint16_t length);
|
|
||||||
void setUnitId(uint8_t unitId);
|
|
||||||
void setFunctionCode(uint8_t functionCode);
|
|
||||||
void setDataRegister(uint8_t index, uint16_t value);
|
|
||||||
|
|
||||||
void setRtuLen(uint16_t rtuLen);
|
|
||||||
void setTcpLen(uint16_t tcpLen);
|
|
||||||
void setPduLen(uint16_t pduLen);
|
|
||||||
void setDataLen(uint16_t dataLen);
|
|
||||||
|
|
||||||
uint16_t getTransactionId();
|
|
||||||
uint16_t getProtocolId();
|
|
||||||
uint16_t getLength();
|
|
||||||
uint8_t getUnitId();
|
|
||||||
uint8_t getFunctionCode();
|
|
||||||
uint16_t getDataRegister(uint8_t index);
|
|
||||||
|
|
||||||
uint16_t getRtuLen();
|
|
||||||
uint16_t getTcpLen();
|
|
||||||
uint16_t getPduLen();
|
|
||||||
uint16_t getDataLen();
|
|
||||||
|
|
||||||
void updateCrc();
|
|
||||||
bool crcGood();
|
|
||||||
|
|
||||||
void prepareExceptionResponse(uint8_t exceptionCode);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t _adu[262];
|
|
||||||
void _setRegister(uint8_t *buf, uint16_t index, uint16_t value);
|
|
||||||
uint16_t _getRegister(uint8_t *buf, uint16_t index);
|
|
||||||
uint16_t _calculateCrc(uint16_t len);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
uint16_t div8RndUp(uint16_t value);
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,98 +0,0 @@
|
|||||||
#include "ModbusRTUComm.h"
|
|
||||||
|
|
||||||
ModbusRTUComm::ModbusRTUComm(Stream& serial, int8_t dePin, int8_t rePin) : _serial(serial) {
|
|
||||||
_dePin = dePin;
|
|
||||||
_rePin = rePin;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusRTUComm::begin(unsigned long baud, uint32_t config) {
|
|
||||||
unsigned long bitsPerChar;
|
|
||||||
switch (config) {
|
|
||||||
case SERIAL_8E2:
|
|
||||||
case SERIAL_8O2:
|
|
||||||
bitsPerChar = 12;
|
|
||||||
break;
|
|
||||||
case SERIAL_8N2:
|
|
||||||
case SERIAL_8E1:
|
|
||||||
case SERIAL_8O1:
|
|
||||||
bitsPerChar = 11;
|
|
||||||
break;
|
|
||||||
case SERIAL_8N1:
|
|
||||||
default:
|
|
||||||
bitsPerChar = 10;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (baud <= 19200) {
|
|
||||||
_charTimeout = (bitsPerChar * 2500000) / baud;
|
|
||||||
_frameTimeout = (bitsPerChar * 4500000) / baud;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_charTimeout = (bitsPerChar * 1000000) / baud + 750;
|
|
||||||
_frameTimeout = (bitsPerChar * 1000000) / baud + 1750;
|
|
||||||
}
|
|
||||||
#if defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) || defined(ARDUINO_GIGA) || (defined(ARDUINO_NANO_RP2040_CONNECT) && defined(ARDUINO_ARCH_MBED))
|
|
||||||
_postDelay = ((bitsPerChar * 1000000) / baud) + 2;
|
|
||||||
#endif
|
|
||||||
if (_dePin >= 0) {
|
|
||||||
pinMode(_dePin, OUTPUT);
|
|
||||||
digitalWrite(_dePin, LOW);
|
|
||||||
}
|
|
||||||
if (_rePin >= 0) {
|
|
||||||
pinMode(_rePin, OUTPUT);
|
|
||||||
digitalWrite(_rePin, LOW);
|
|
||||||
}
|
|
||||||
clearRxBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusRTUComm::setTimeout(unsigned long timeout) {
|
|
||||||
_readTimeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUCommError ModbusRTUComm::readAdu(ModbusADU& adu) {
|
|
||||||
adu.setRtuLen(0);
|
|
||||||
unsigned long startMillis = millis();
|
|
||||||
while (!_serial.available()) {
|
|
||||||
if (millis() - startMillis >= _readTimeout) return MODBUS_RTU_COMM_TIMEOUT;
|
|
||||||
}
|
|
||||||
uint16_t len = 0;
|
|
||||||
unsigned long startMicros = micros();
|
|
||||||
do {
|
|
||||||
if (_serial.available()) {
|
|
||||||
startMicros = micros();
|
|
||||||
adu.rtu[len] = _serial.read();
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
} while (micros() - startMicros <= _charTimeout && len < 256);
|
|
||||||
adu.setRtuLen(len);
|
|
||||||
while (micros() - startMicros < _frameTimeout);
|
|
||||||
if (_serial.available()) {
|
|
||||||
adu.setRtuLen(0);
|
|
||||||
return MODBUS_RTU_COMM_FRAME_ERROR;
|
|
||||||
}
|
|
||||||
if (!adu.crcGood()) {
|
|
||||||
adu.setRtuLen(0);
|
|
||||||
return MODBUS_RTU_COMM_CRC_ERROR;
|
|
||||||
}
|
|
||||||
return MODBUS_RTU_COMM_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusRTUComm::writeAdu(ModbusADU& adu) {
|
|
||||||
adu.updateCrc();
|
|
||||||
if (_dePin >= 0) digitalWrite(_dePin, HIGH);
|
|
||||||
if (_rePin >= 0) digitalWrite(_rePin, HIGH);
|
|
||||||
_serial.write(adu.rtu, adu.getRtuLen());
|
|
||||||
_serial.flush();
|
|
||||||
delayMicroseconds(_postDelay);
|
|
||||||
if (_dePin >= 0) digitalWrite(_dePin, LOW);
|
|
||||||
if (_rePin >= 0) digitalWrite(_rePin, LOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusRTUComm::clearRxBuffer() {
|
|
||||||
unsigned long startMicros = micros();
|
|
||||||
do {
|
|
||||||
if (_serial.available() > 0) {
|
|
||||||
startMicros = micros();
|
|
||||||
_serial.read();
|
|
||||||
}
|
|
||||||
} while (micros() - startMicros < _frameTimeout);
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
#ifndef ModbusRTUComm_h
|
|
||||||
#define ModbusRTUComm_h
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "ModbusADU.h"
|
|
||||||
|
|
||||||
enum ModbusRTUCommError : uint8_t {
|
|
||||||
MODBUS_RTU_COMM_SUCCESS = 0,
|
|
||||||
MODBUS_RTU_COMM_TIMEOUT = 1,
|
|
||||||
MODBUS_RTU_COMM_FRAME_ERROR = 2,
|
|
||||||
MODBUS_RTU_COMM_CRC_ERROR = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
class ModbusRTUComm {
|
|
||||||
public:
|
|
||||||
ModbusRTUComm(Stream& serial, int8_t dePin = -1, int8_t rePin = -1);
|
|
||||||
void begin(unsigned long baud, uint32_t config = SERIAL_8N1);
|
|
||||||
void setTimeout(unsigned long timeout);
|
|
||||||
ModbusRTUCommError readAdu(ModbusADU& adu);
|
|
||||||
void writeAdu(ModbusADU& adu);
|
|
||||||
void clearRxBuffer();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Stream& _serial;
|
|
||||||
int8_t _dePin;
|
|
||||||
int8_t _rePin;
|
|
||||||
unsigned long _charTimeout;
|
|
||||||
unsigned long _frameTimeout;
|
|
||||||
unsigned long _postDelay = 0;
|
|
||||||
unsigned long _readTimeout = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,216 +0,0 @@
|
|||||||
#include "ModbusRTUMaster.h"
|
|
||||||
|
|
||||||
ModbusRTUMaster::ModbusRTUMaster(Stream& serial, int8_t dePin, int8_t rePin) : _rtuComm(serial, dePin, rePin) {
|
|
||||||
_rtuComm.setTimeout(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusRTUMaster::setTimeout(unsigned long timeout) {
|
|
||||||
_rtuComm.setTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModbusRTUMaster::begin(unsigned long baud, uint32_t config) {
|
|
||||||
_rtuComm.begin(baud, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::readCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
|
||||||
return _readValues(id, 1, startAddress, buf, quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::readDiscreteInputs(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
|
||||||
return _readValues(id, 2, startAddress, buf, quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::readHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
|
||||||
return _readValues(id, 3, startAddress, buf, quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::readInputRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
|
||||||
return _readValues(id, 4, startAddress, buf, quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::writeSingleCoil(uint8_t id, uint16_t address, bool value) {
|
|
||||||
return _writeSingleValue(id, 5, address, ((value) ? 0xFF00 : 0x0000));
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::writeSingleHoldingRegister(uint8_t id, uint16_t address, uint16_t value) {
|
|
||||||
return _writeSingleValue(id, 6, address, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::writeMultipleCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
|
||||||
const uint8_t functionCode = 15;
|
|
||||||
if (id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
|
||||||
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
|
||||||
if (quantity == 0 || quantity > 1968) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
|
||||||
ModbusADU adu;
|
|
||||||
uint16_t byteCount = div8RndUp(quantity);
|
|
||||||
adu.setUnitId(id);
|
|
||||||
adu.setFunctionCode(functionCode);
|
|
||||||
adu.setDataRegister(0, startAddress);
|
|
||||||
adu.setDataRegister(2, quantity);
|
|
||||||
adu.data[4] = byteCount;
|
|
||||||
for (uint16_t i = 0; i < quantity; i++) {
|
|
||||||
bitWrite(adu.data[5 + (i >> 3)], i & 7, buf[i]);
|
|
||||||
}
|
|
||||||
for (uint16_t i = quantity; i < (byteCount * 8); i++) {
|
|
||||||
bitClear(adu.data[5 + (i >> 3)], i & 7);
|
|
||||||
}
|
|
||||||
adu.setDataLen(5 + byteCount);
|
|
||||||
_rtuComm.writeAdu(adu);
|
|
||||||
if (id == 0) return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
|
||||||
if (commError) return _translateCommError(commError);
|
|
||||||
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
|
||||||
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
|
||||||
_exceptionResponse = adu.data[0];
|
|
||||||
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
|
||||||
if (adu.getDataLen() != 4) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
|
||||||
if (adu.getDataRegister(0) != startAddress) return MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS;
|
|
||||||
if (adu.getDataRegister(2) != quantity) return MODBUS_RTU_MASTER_UNEXPECTED_QUANTITY;
|
|
||||||
return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::writeMultipleHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
|
||||||
uint8_t functionCode = 16;
|
|
||||||
if (id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
|
||||||
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
|
||||||
if (quantity == 0 || quantity > 123) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
|
||||||
uint16_t byteCount = quantity * 2;
|
|
||||||
ModbusADU adu;
|
|
||||||
adu.setUnitId(id);
|
|
||||||
adu.setFunctionCode(functionCode);
|
|
||||||
adu.setDataRegister(0, startAddress);
|
|
||||||
adu.setDataRegister(2, quantity);
|
|
||||||
adu.data[4] = byteCount;
|
|
||||||
for (uint16_t i = 0; i < quantity; i++) {
|
|
||||||
adu.setDataRegister(5 + (i * 2), buf[i]);
|
|
||||||
}
|
|
||||||
adu.setDataLen(5 + byteCount);
|
|
||||||
_rtuComm.writeAdu(adu);
|
|
||||||
if (id == 0) return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
|
||||||
if (commError) return _translateCommError(commError);
|
|
||||||
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
|
||||||
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
|
||||||
_exceptionResponse = adu.data[0];
|
|
||||||
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
|
||||||
if (adu.getDataLen() != 4) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
|
||||||
if (adu.getDataRegister(0) != startAddress) return MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS;
|
|
||||||
if (adu.getDataRegister(2) != quantity) return MODBUS_RTU_MASTER_UNEXPECTED_QUANTITY;
|
|
||||||
return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uint8_t ModbusRTUMaster::getExceptionResponse() {
|
|
||||||
return _exceptionResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::_readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, bool buf[], uint16_t quantity) {
|
|
||||||
if (id < 1 || id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
|
||||||
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
|
||||||
if (quantity == 0 || quantity > 2000) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
|
||||||
ModbusADU adu;
|
|
||||||
adu.setUnitId(id);
|
|
||||||
adu.setFunctionCode(functionCode);
|
|
||||||
adu.setDataRegister(0, startAddress);
|
|
||||||
adu.setDataRegister(2, quantity);
|
|
||||||
adu.setDataLen(4);
|
|
||||||
_rtuComm.writeAdu(adu);
|
|
||||||
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
|
||||||
if (commError) return _translateCommError(commError);
|
|
||||||
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
|
||||||
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
|
||||||
_exceptionResponse = adu.data[0];
|
|
||||||
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
|
||||||
uint16_t byteCount = div8RndUp(quantity);
|
|
||||||
if (adu.getDataLen() != (1 + byteCount)) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
|
||||||
if (adu.data[0] != byteCount) return MODBUS_RTU_MASTER_UNEXPECTED_BYTE_COUNT;
|
|
||||||
for (uint16_t i = 0; i < quantity; i++) {
|
|
||||||
buf[i] = bitRead(adu.data[1 + (i >> 3)], i & 7);
|
|
||||||
}
|
|
||||||
return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::_readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, uint16_t buf[], uint16_t quantity) {
|
|
||||||
if (id < 1 || id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
|
||||||
if (!buf) return MODBUS_RTU_MASTER_INVALID_BUFFER;
|
|
||||||
if (quantity == 0 || quantity > 125) return MODBUS_RTU_MASTER_INVALID_QUANTITY;
|
|
||||||
ModbusADU adu;
|
|
||||||
adu.setUnitId(id);
|
|
||||||
adu.setFunctionCode(functionCode);
|
|
||||||
adu.setDataRegister(0, startAddress);
|
|
||||||
adu.setDataRegister(2, quantity);
|
|
||||||
adu.setDataLen(4);
|
|
||||||
_rtuComm.writeAdu(adu);
|
|
||||||
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
|
||||||
if (commError) return _translateCommError(commError);
|
|
||||||
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
|
||||||
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
|
||||||
_exceptionResponse = adu.data[0];
|
|
||||||
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
|
||||||
uint16_t byteCount = quantity * 2;
|
|
||||||
if (adu.getDataLen() != (1 + byteCount)) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
|
||||||
if (adu.data[0] != byteCount) return MODBUS_RTU_MASTER_UNEXPECTED_BYTE_COUNT;
|
|
||||||
for (uint16_t i = 0; i < quantity; i++) {
|
|
||||||
buf[i] = adu.getDataRegister(1 + (i * 2));
|
|
||||||
}
|
|
||||||
return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::_writeSingleValue(uint8_t id, uint8_t functionCode, uint16_t address, uint16_t value) {
|
|
||||||
if (id > 247) return MODBUS_RTU_MASTER_INVALID_ID;
|
|
||||||
ModbusADU adu;
|
|
||||||
adu.setUnitId(id);
|
|
||||||
adu.setFunctionCode(functionCode);
|
|
||||||
adu.setDataRegister(0, address);
|
|
||||||
adu.setDataRegister(2, value);
|
|
||||||
adu.setDataLen(4);
|
|
||||||
_rtuComm.writeAdu(adu);
|
|
||||||
if (id == 0) return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
ModbusRTUCommError commError = _rtuComm.readAdu(adu);
|
|
||||||
if (commError) return _translateCommError(commError);
|
|
||||||
if (adu.getUnitId() != id) return MODBUS_RTU_MASTER_UNEXPECTED_ID;
|
|
||||||
if (adu.getFunctionCode() == (functionCode + 0x80)) {
|
|
||||||
_exceptionResponse = adu.data[0];
|
|
||||||
return MODBUS_RTU_MASTER_EXCEPTION_RESPONSE;
|
|
||||||
}
|
|
||||||
if (adu.getFunctionCode() != functionCode) return MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE;
|
|
||||||
if (adu.getDataLen() != 4) return MODBUS_RTU_MASTER_UNEXPECTED_LENGTH;
|
|
||||||
if (adu.getDataRegister(0) != address) return MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS;
|
|
||||||
if (adu.getDataRegister(2) != value) return MODBUS_RTU_MASTER_UNEXPECTED_VALUE;
|
|
||||||
return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ModbusRTUMasterError ModbusRTUMaster::_translateCommError(ModbusRTUCommError commError) {
|
|
||||||
switch (commError) {
|
|
||||||
case MODBUS_RTU_COMM_SUCCESS:
|
|
||||||
return MODBUS_RTU_MASTER_SUCCESS;
|
|
||||||
case MODBUS_RTU_COMM_TIMEOUT:
|
|
||||||
return MODBUS_RTU_MASTER_RESPONSE_TIMEOUT;
|
|
||||||
case MODBUS_RTU_COMM_FRAME_ERROR:
|
|
||||||
return MODBUS_RTU_MASTER_FRAME_ERROR;
|
|
||||||
case MODBUS_RTU_COMM_CRC_ERROR:
|
|
||||||
return MODBUS_RTU_MASTER_CRC_ERROR;
|
|
||||||
default:
|
|
||||||
return MODBUS_RTU_MASTER_UNKNOWN_COMM_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
|||||||
#ifndef ModbusRTUMaster_h
|
|
||||||
#define ModbusRTUMaster_h
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "ModbusADU.h"
|
|
||||||
#include "ModbusRTUComm.h"
|
|
||||||
|
|
||||||
enum ModbusRTUMasterError : uint8_t {
|
|
||||||
MODBUS_RTU_MASTER_SUCCESS = 0,
|
|
||||||
MODBUS_RTU_MASTER_INVALID_ID = 1,
|
|
||||||
MODBUS_RTU_MASTER_INVALID_BUFFER = 2,
|
|
||||||
MODBUS_RTU_MASTER_INVALID_QUANTITY = 3,
|
|
||||||
MODBUS_RTU_MASTER_RESPONSE_TIMEOUT = 4,
|
|
||||||
MODBUS_RTU_MASTER_FRAME_ERROR = 5,
|
|
||||||
MODBUS_RTU_MASTER_CRC_ERROR = 6,
|
|
||||||
MODBUS_RTU_MASTER_UNKNOWN_COMM_ERROR = 7,
|
|
||||||
MODBUS_RTU_MASTER_UNEXPECTED_ID = 8,
|
|
||||||
MODBUS_RTU_MASTER_EXCEPTION_RESPONSE = 9,
|
|
||||||
MODBUS_RTU_MASTER_UNEXPECTED_FUNCTION_CODE = 10,
|
|
||||||
MODBUS_RTU_MASTER_UNEXPECTED_LENGTH = 11,
|
|
||||||
MODBUS_RTU_MASTER_UNEXPECTED_BYTE_COUNT = 12,
|
|
||||||
MODBUS_RTU_MASTER_UNEXPECTED_ADDRESS = 13,
|
|
||||||
MODBUS_RTU_MASTER_UNEXPECTED_VALUE = 14,
|
|
||||||
MODBUS_RTU_MASTER_UNEXPECTED_QUANTITY = 15
|
|
||||||
};
|
|
||||||
|
|
||||||
class ModbusRTUMaster {
|
|
||||||
public:
|
|
||||||
ModbusRTUMaster(Stream& serial, int8_t dePin = -1, int8_t rePin = -1);
|
|
||||||
void setTimeout(unsigned long timeout);
|
|
||||||
void begin(unsigned long baud, uint32_t config = SERIAL_8N1);
|
|
||||||
|
|
||||||
ModbusRTUMasterError readCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity);
|
|
||||||
ModbusRTUMasterError readDiscreteInputs(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity);
|
|
||||||
ModbusRTUMasterError readHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
|
||||||
ModbusRTUMasterError readInputRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
|
||||||
|
|
||||||
ModbusRTUMasterError writeSingleCoil(uint8_t id, uint16_t address, bool value);
|
|
||||||
ModbusRTUMasterError writeSingleHoldingRegister(uint8_t id, uint16_t address, uint16_t value);
|
|
||||||
ModbusRTUMasterError writeMultipleCoils(uint8_t id, uint16_t startAddress, bool buf[], uint16_t quantity);
|
|
||||||
ModbusRTUMasterError writeMultipleHoldingRegisters(uint8_t id, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
|
||||||
|
|
||||||
uint8_t getExceptionResponse();
|
|
||||||
|
|
||||||
private:
|
|
||||||
ModbusRTUComm _rtuComm;
|
|
||||||
uint8_t _exceptionResponse = 0;
|
|
||||||
|
|
||||||
ModbusRTUMasterError _readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, bool buf[], uint16_t quantity);
|
|
||||||
ModbusRTUMasterError _readValues(uint8_t id, uint8_t functionCode, uint16_t startAddress, uint16_t buf[], uint16_t quantity);
|
|
||||||
ModbusRTUMasterError _writeSingleValue(uint8_t id, uint8_t functionCode, uint16_t address, uint16_t value);
|
|
||||||
|
|
||||||
ModbusRTUMasterError _translateCommError(ModbusRTUCommError commError);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
Loading…
Reference in New Issue
Block a user