First commit
This commit is contained in:
616
firmware/Modbus-Arduino/src/Modbus.cpp
Normal file
616
firmware/Modbus-Arduino/src/Modbus.cpp
Normal file
@@ -0,0 +1,616 @@
|
||||
/*
|
||||
Modbus.cpp - Source for Modbus Base Library
|
||||
Copyright (C) 2014 André Sarmento Barbosa
|
||||
Copyright (C) 2023 Pascal JEAN aka epsilonrt
|
||||
*/
|
||||
#include "Modbus.h"
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
Modbus::Modbus() :
|
||||
_regs_head (0),
|
||||
_regs_last (0),
|
||||
_additional_data (0),
|
||||
_frame (nullptr),
|
||||
_len (0),
|
||||
_reply (0),
|
||||
_debug (nullptr) {
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
TRegister *Modbus::searchRegister (word address) {
|
||||
TRegister *reg = _regs_head;
|
||||
//if there is no register configured, bail
|
||||
if (reg == 0) {
|
||||
return (0);
|
||||
}
|
||||
//scan through the linked list until the end of the list or the register is found.
|
||||
//return the pointer.
|
||||
do {
|
||||
if (reg->address == address) {
|
||||
return (reg);
|
||||
}
|
||||
reg = reg->next;
|
||||
}
|
||||
while (reg);
|
||||
return (0);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::addReg (word address, word value) {
|
||||
TRegister *newreg;
|
||||
|
||||
newreg = (TRegister *) malloc (sizeof (TRegister));
|
||||
newreg->address = address;
|
||||
newreg->value = value;
|
||||
newreg->edata = 0;
|
||||
newreg->next = 0;
|
||||
|
||||
if (_regs_head == 0) {
|
||||
_regs_head = newreg;
|
||||
_regs_last = _regs_head;
|
||||
}
|
||||
else {
|
||||
//Assign the last register's next pointer to newreg.
|
||||
_regs_last->next = newreg;
|
||||
//then make temp the last register in the list.
|
||||
_regs_last = newreg;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
bool Modbus::setReg (word address, word value) {
|
||||
TRegister *reg;
|
||||
//search for the register address
|
||||
reg = searchRegister (address);
|
||||
//if found then assign the register value to the new value.
|
||||
if (reg) {
|
||||
reg->value = value;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
word Modbus::reg (word address) {
|
||||
TRegister *reg;
|
||||
reg = searchRegister (address);
|
||||
if (reg) {
|
||||
return (reg->value);
|
||||
}
|
||||
else {
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
bool Modbus::setRegBounds (word address, word min, word max) {
|
||||
TRegister *reg;
|
||||
|
||||
reg = searchRegister (address);
|
||||
|
||||
if (reg) {
|
||||
|
||||
if (!reg->edata) {
|
||||
|
||||
reg->edata = new TExtData;
|
||||
}
|
||||
if (reg->edata) {
|
||||
|
||||
reg->edata->min = min;
|
||||
reg->edata->max = max;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
bool Modbus::regOutOfBounds (word address, word value) {
|
||||
TRegister *reg;
|
||||
|
||||
reg = searchRegister (address);
|
||||
|
||||
if (reg) {
|
||||
|
||||
if (reg->edata) {
|
||||
|
||||
return (value < reg->edata->min) || (value > reg->edata->max);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// protected
|
||||
void Modbus::receivePDU (byte *frame) {
|
||||
byte fcode = frame[0];
|
||||
word field1 = (word) frame[1] << 8 | (word) frame[2];
|
||||
word field2 = (word) frame[3] << 8 | (word) frame[4];
|
||||
|
||||
switch (fcode) {
|
||||
|
||||
case MB_FC_WRITE_REG:
|
||||
//field1 = reg, field2 = value
|
||||
writeSingleRegister (field1, field2);
|
||||
break;
|
||||
|
||||
case MB_FC_WRITE_REGS:
|
||||
//field1 = startreg, field2 = status
|
||||
writeMultipleRegisters (frame, field1, field2, frame[5]);
|
||||
break;
|
||||
|
||||
case MB_FC_READ_REGS:
|
||||
//field1 = startreg, field2 = numregs
|
||||
readRegisters (fcode, field1, field2);
|
||||
break;
|
||||
|
||||
#ifndef USE_HOLDING_REGISTERS_ONLY
|
||||
case MB_FC_READ_INPUT_REGS:
|
||||
//field1 = startreg, field2 = numregs
|
||||
readRegisters (fcode, field1, field2);
|
||||
break;
|
||||
|
||||
case MB_FC_READ_INPUT_STAT:
|
||||
case MB_FC_READ_COILS:
|
||||
//field1 = startreg, field2 = numregs
|
||||
readBits (fcode, field1, field2);
|
||||
break;
|
||||
|
||||
case MB_FC_WRITE_COIL:
|
||||
//field1 = reg, field2 = status
|
||||
writeSingleCoil (field1, field2);
|
||||
break;
|
||||
|
||||
case MB_FC_WRITE_COILS:
|
||||
//field1 = startreg, field2 = numoutputs
|
||||
writeMultipleCoils (frame, field1, field2, frame[5]);
|
||||
break;
|
||||
|
||||
case MB_FC_REPORT_SERVER_ID:
|
||||
reportServerId();
|
||||
break;
|
||||
|
||||
#endif
|
||||
|
||||
default:
|
||||
exceptionResponse (fcode, MB_EX_ILLEGAL_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::exceptionResponse (const byte fcode, const byte excode) {
|
||||
|
||||
_len = 2;
|
||||
_frame = (byte *) realloc (_frame, _len);
|
||||
_frame[0] = fcode + 0x80;
|
||||
_frame[1] = excode;
|
||||
|
||||
_reply = MB_REPLY_NORMAL;
|
||||
}
|
||||
|
||||
/* Func 06: Write Single Register
|
||||
Request
|
||||
Function code 1 Byte 0x06
|
||||
Register Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Register Value 2 Bytes 0x0000 to 0xFFFF
|
||||
Response
|
||||
Function code 1 Byte 0x06
|
||||
Register Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Register Value 2 Bytes 0x0000 to 0xFFFF
|
||||
*/
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::writeSingleRegister (const word reg, const word value) {
|
||||
|
||||
if (hregOutOfBounds (reg, value)) {
|
||||
exceptionResponse (MB_FC_WRITE_REG, MB_EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check Address and execute (reg exists?)
|
||||
if (!setHreg (reg, value)) {
|
||||
exceptionResponse (MB_FC_WRITE_REG, MB_EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check for failure ? Why ???
|
||||
/*
|
||||
if (hreg (reg) != value) {
|
||||
exceptionResponse (MB_FC_WRITE_REG, MB_EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
_reply = MB_REPLY_ECHO; // reply with received frame
|
||||
}
|
||||
|
||||
/* Func 16: Write Multiple registers
|
||||
Request
|
||||
Function code 1 Byte 0x10
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of Registers 2 Bytes 0x0001 to 0x007B
|
||||
Byte Count 1 Byte 2 x N*
|
||||
Registers Value N* x 2 Bytes value
|
||||
N = Quantity of Registers
|
||||
Response
|
||||
Function code 1 Byte 0x10
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of Registers 2 Bytes 1 to 123 (0x7B)
|
||||
*/
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::writeMultipleRegisters (const byte *frame, const word startreg, const word numoutputs, const byte bytecount) {
|
||||
//Check value
|
||||
if (numoutputs < 1 || numoutputs > 123 || bytecount != 2 * numoutputs) {
|
||||
exceptionResponse (MB_FC_WRITE_REGS, MB_EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check Address (startreg...startreg + numregs - 1)
|
||||
for (word k = 0; k < numoutputs; k++) {
|
||||
if (!searchRegister (startreg + TRegister::HregOffset + k)) {
|
||||
exceptionResponse (MB_FC_WRITE_REGS, MB_EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (word i = 0; i < numoutputs; i++) {
|
||||
|
||||
word val = (word) frame[6 + i * 2] << 8 | (word) frame[7 + i * 2];
|
||||
if (hregOutOfBounds (startreg + i, val)) {
|
||||
|
||||
exceptionResponse (MB_FC_WRITE_REG, MB_EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
setHreg (startreg + i, val);
|
||||
}
|
||||
|
||||
//Clean frame buffer
|
||||
free (_frame);
|
||||
_len = 5;
|
||||
_frame = (byte *) malloc (_len);
|
||||
if (!_frame) {
|
||||
exceptionResponse (MB_FC_WRITE_REGS, MB_EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
_frame[0] = MB_FC_WRITE_REGS;
|
||||
_frame[1] = startreg >> 8;
|
||||
_frame[2] = startreg & 0x00FF;
|
||||
_frame[3] = numoutputs >> 8;
|
||||
_frame[4] = numoutputs & 0x00FF;
|
||||
_reply = MB_REPLY_NORMAL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Func 03: Read Holding Registers
|
||||
Request
|
||||
Function code 1 Byte 0x03
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of Registers 2 Bytes 1 to 125 (0x7D)
|
||||
Response
|
||||
Function code 1 Byte 0x03
|
||||
Byte count 1 Byte 2 x N*
|
||||
Register value N* x 2 Bytes
|
||||
N = Quantity of Registers
|
||||
Func 04: Read Input Registers
|
||||
Request
|
||||
Function code 1 Byte 0x04
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of Input Registers 2 Bytes 0x0001 to 0x007D
|
||||
Response
|
||||
Function code 1 Byte 0x04
|
||||
Byte count 1 Byte 2 x N*
|
||||
Input Registers N* x 2 Bytes
|
||||
N = Quantity of Input Registers
|
||||
*/
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::readRegisters (const byte fcode, const word startreg, const word numregs) {
|
||||
|
||||
// Check value (numregs)
|
||||
if (numregs < 1 || numregs > 0x7D) {
|
||||
|
||||
exceptionResponse (fcode, MB_EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate the query reply message length
|
||||
// for each register queried add 2 bytes
|
||||
_len = 2 + numregs * 2;
|
||||
|
||||
_frame = (byte *) realloc (_frame, _len);
|
||||
if (!_frame) {
|
||||
|
||||
exceptionResponse (fcode, MB_EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
_frame[0] = fcode;
|
||||
_frame[1] = _len - 2; //byte count
|
||||
|
||||
const word offset = (fcode == MB_FC_READ_REGS) ? TRegister::HregOffset : TRegister::IregOffset;
|
||||
for (word i = 0; i < numregs; i++) {
|
||||
|
||||
const word address = startreg + offset + i;
|
||||
if (searchRegister (address)) {
|
||||
|
||||
// retrieve the value from the register bank for the current register
|
||||
word val = reg (address);
|
||||
|
||||
// write the high byte of the register value
|
||||
_frame[2 + i * 2] = val >> 8;
|
||||
// write the low byte of the register value
|
||||
_frame[3 + i * 2] = val & 0xFF;
|
||||
}
|
||||
else {
|
||||
|
||||
exceptionResponse (fcode, MB_EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_reply = MB_REPLY_NORMAL;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
// protected
|
||||
void Modbus::debugMessage (bool reply) {
|
||||
|
||||
if (_len && isDebug()) {
|
||||
char str[3]; // buffer for sprintf
|
||||
for (uint8_t i = 0; i < _len; i++) {
|
||||
|
||||
sprintf (str, "%02X", _frame[i]); // str stores the string representation of the byte, in hex form
|
||||
_debug->write (!reply ? '[' : '<');
|
||||
_debug->print (str);
|
||||
_debug->write (!reply ? ']' : '>');
|
||||
}
|
||||
_debug->println ("");
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef USE_HOLDING_REGISTERS_ONLY
|
||||
//-------------------------------------------------------------------------------
|
||||
/* Func 01: Read Coils
|
||||
Request
|
||||
Function code 1 Byte 0x01
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of coils 2 Bytes 1 to 2000 (0x7D0)
|
||||
Response
|
||||
Function code 1 Byte 0x01
|
||||
Byte count 1 Byte N*
|
||||
Coil Status n Byte n = N or N+1
|
||||
N = Quantity of Outputs / 8, if the remainder is different of 0 -> N = N+1
|
||||
Func 02: Read Discrete Inputs
|
||||
Request
|
||||
Function code 1 Byte 0x02
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of Inputs 2 Bytes 1 to 2000 (0x7D0)
|
||||
Response
|
||||
Function code 1 Byte 0x02
|
||||
Byte count 1 Byte N*
|
||||
Input Status N* x 1 Byte
|
||||
N = Quantity of Inputs / 8, if the remainder is different of 0 -> N = N+1
|
||||
*/
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::readBits (const byte fcode, const word startreg, const word numregs) {
|
||||
|
||||
//Check value (numregs)
|
||||
if (numregs < 0x0001 || numregs > 0x07D0) {
|
||||
|
||||
exceptionResponse (fcode, MB_EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the message length = function type, byte count and
|
||||
// for each group of 8 registers the message length increases by 1
|
||||
_len = 2 + numregs / 8;
|
||||
if (numregs % 8) {
|
||||
_len++; // Add 1 to the message length for the partial byte.
|
||||
}
|
||||
|
||||
_frame = (byte *) realloc (_frame, _len);
|
||||
if (!_frame) {
|
||||
|
||||
exceptionResponse (fcode, MB_EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
_frame[0] = fcode;
|
||||
_frame[1] = _len - 2; // byte count (_len - function code and byte count)
|
||||
|
||||
const word offset = (fcode == MB_FC_READ_COILS) ? TRegister::CoilOffset : TRegister::IstsOffset;
|
||||
for (word i = 0; i < numregs; i++) {
|
||||
|
||||
const word address = startreg + offset + i;
|
||||
if (searchRegister (address)) {
|
||||
|
||||
word byteIndex = (i / 8) + 2;
|
||||
byte bitIndex = i % 8;
|
||||
if (reg (address) == 0xFF00) {
|
||||
|
||||
bitSet (_frame[byteIndex], bitIndex);
|
||||
}
|
||||
else {
|
||||
|
||||
bitClear (_frame[byteIndex], bitIndex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
exceptionResponse (fcode, MB_EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_reply = MB_REPLY_NORMAL;
|
||||
}
|
||||
|
||||
|
||||
/* Func 05: Write Single Coil
|
||||
Request
|
||||
Function code 1 Byte 0x05
|
||||
Output Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Output Value 2 Bytes 0x0000 or 0xFF00
|
||||
Response
|
||||
Function code 1 Byte 0x05
|
||||
Output Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Output Value 2 Bytes 0x0000 or 0xFF00
|
||||
*/
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::writeSingleCoil (const word reg, const word status) {
|
||||
//Check value (status)
|
||||
if (status != 0xFF00 && status != 0x0000) {
|
||||
exceptionResponse (MB_FC_WRITE_COIL, MB_EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check Address and execute (reg exists?)
|
||||
if (!setCoil (reg, status != 0)) {
|
||||
exceptionResponse (MB_FC_WRITE_COIL, MB_EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check for failure ?? Why ?
|
||||
/*
|
||||
if (coil (reg) != (bool) status) {
|
||||
exceptionResponse (MB_FC_WRITE_COIL, MB_EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
_reply = MB_REPLY_ECHO; // reply with received frame
|
||||
}
|
||||
|
||||
/* Func 15: Write Multiple Coils
|
||||
Request
|
||||
Function code 1 Byte 0x0F
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of Outputs 2 Bytes 0x0001 to 0x07B0
|
||||
Byte Count 1 Byte N* (Quantity of Outputs / 8, if the remainder is different of 0 -> N = N+1)
|
||||
Outputs Value N* x 1 Byte
|
||||
Response
|
||||
Function code 1 Byte 0x0F
|
||||
Starting Address 2 Bytes 0x0000 to 0xFFFF
|
||||
Quantity of Outputs 2 Bytes 0x0001 to 0x07B0
|
||||
*/
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::writeMultipleCoils (const byte *frame, const word startreg, const word numoutputs, const byte bytecount) {
|
||||
|
||||
// Check value
|
||||
word bytecount_calc = numoutputs / 8;
|
||||
if (numoutputs % 8) {
|
||||
|
||||
bytecount_calc++;
|
||||
}
|
||||
|
||||
if (numoutputs < 0x0001 || numoutputs > 0x07B0 || bytecount != bytecount_calc) {
|
||||
|
||||
exceptionResponse (MB_FC_WRITE_COILS, MB_EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
for (word i = 0; i < numoutputs; i++) {
|
||||
|
||||
const word address = startreg + i;
|
||||
if (searchRegister (address + TRegister::CoilOffset)) {
|
||||
|
||||
word byteIndex = (i / 8) + 6;
|
||||
byte bitIndex = i % 8;
|
||||
setCoil (address, bitRead (frame[byteIndex], bitIndex));
|
||||
}
|
||||
else {
|
||||
|
||||
exceptionResponse (MB_FC_WRITE_COILS, MB_EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_len = 5;
|
||||
_frame = (byte *) realloc (_frame, _len);
|
||||
if (!_frame) {
|
||||
|
||||
exceptionResponse (MB_FC_WRITE_COILS, MB_EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
_frame[0] = MB_FC_WRITE_COILS;
|
||||
_frame[1] = startreg >> 8;
|
||||
_frame[2] = startreg & 0x00FF;
|
||||
_frame[3] = numoutputs >> 8;
|
||||
_frame[4] = numoutputs & 0x00FF;
|
||||
|
||||
_reply = MB_REPLY_NORMAL;
|
||||
}
|
||||
|
||||
/*
|
||||
Func 17: Report Server ID
|
||||
Request
|
||||
Function code 1 Byte 0x11
|
||||
Response
|
||||
Function code 1 Byte 0x11
|
||||
Byte Count 1 Byte
|
||||
Server ID device specific
|
||||
Run Indicator Status 1 Byte 0x00 = OFF, 0xFF = ON
|
||||
Additional Data
|
||||
*/
|
||||
//-------------------------------------------------------------------------------
|
||||
// private
|
||||
void Modbus::reportServerId() {
|
||||
|
||||
_len = 4;
|
||||
if (_additional_data) {
|
||||
|
||||
_len += strlen (_additional_data);
|
||||
}
|
||||
|
||||
_frame = (byte *) realloc (_frame, _len);
|
||||
if (!_frame) {
|
||||
|
||||
exceptionResponse (MB_FC_REPORT_SERVER_ID, MB_EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
_frame[0] = MB_FC_REPORT_SERVER_ID;
|
||||
_frame[1] = _len - 2; //byte count
|
||||
_frame[2] = 0x00; // Server ID
|
||||
_frame[3] = 0xFF; // Run Indicator Status
|
||||
if (_additional_data) { // Additional Data
|
||||
|
||||
strcpy ( (char *) &_frame[4], _additional_data);
|
||||
}
|
||||
_reply = MB_REPLY_NORMAL;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
int Modbus::setAdditionalServerData (const char data[]) {
|
||||
|
||||
free (_additional_data);
|
||||
if (data) {
|
||||
size_t l = strlen (data) + 1;
|
||||
|
||||
_additional_data = (char *) malloc (l);
|
||||
if (_additional_data) {
|
||||
|
||||
strcpy (_additional_data, data);
|
||||
return l;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------------
|
||||
#endif // USE_HOLDING_REGISTERS_ONLY not defined
|
357
firmware/Modbus-Arduino/src/Modbus.h
Normal file
357
firmware/Modbus-Arduino/src/Modbus.h
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
Modbus.h - Header for Modbus Base Library
|
||||
Copyright (C) 2014 André Sarmento Barbosa
|
||||
Copyright (C) 2023 Pascal JEAN aka epsilonrt
|
||||
*/
|
||||
#include "Arduino.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MAX_REGS 32 // Unused !
|
||||
#define MAX_FRAME 128 // Unused !
|
||||
//#define USE_HOLDING_REGISTERS_ONLY
|
||||
|
||||
// Function Codes
|
||||
enum {
|
||||
MB_FC_READ_COILS = 0x01, ///< Read Coils (Output) Status 0xxxx
|
||||
MB_FC_READ_INPUT_STAT = 0x02, ///< Read Input Status (Discrete Inputs) 1xxxx
|
||||
MB_FC_READ_REGS = 0x03, ///< Read Holding Registers 4xxxx
|
||||
MB_FC_READ_INPUT_REGS = 0x04, ///< Read Input Registers 3xxxx
|
||||
MB_FC_WRITE_COIL = 0x05, ///< Write Single Coil (Output) 0xxxx
|
||||
MB_FC_WRITE_REG = 0x06, ///< Preset Single Register 4xxxx
|
||||
MB_FC_WRITE_COILS = 0x0F, ///< Write Multiple Coils (Outputs) 0xxxx
|
||||
MB_FC_WRITE_REGS = 0x10, ///< Write block of contiguous registers 4xxxx
|
||||
MB_FC_REPORT_SERVER_ID = 0x11, ///< Report Server ID
|
||||
};
|
||||
|
||||
// Exception Codes
|
||||
enum {
|
||||
MB_EX_ILLEGAL_FUNCTION = 0x01, ///< Function Code not Supported
|
||||
MB_EX_ILLEGAL_ADDRESS = 0x02, ///< Address not in Range
|
||||
MB_EX_ILLEGAL_VALUE = 0x03, ///< Data Value not in Range
|
||||
MB_EX_SLAVE_FAILURE = 0x04, ///< Slave Deive Fails to process request
|
||||
};
|
||||
|
||||
// Reply Types
|
||||
enum {
|
||||
MB_REPLY_OFF = 0x01, ///< No reply
|
||||
MB_REPLY_ECHO = 0x02, ///< Return the message from the bus master as an reply
|
||||
MB_REPLY_NORMAL = 0x03, ///< Reply with adding slave data
|
||||
};
|
||||
|
||||
#ifndef __DOXYGEN__
|
||||
struct TExtData {
|
||||
word min;
|
||||
word max;
|
||||
};
|
||||
struct TRegister {
|
||||
word address;
|
||||
word value;
|
||||
TExtData *edata;
|
||||
TRegister *next;
|
||||
enum {
|
||||
CoilOffset = 1,
|
||||
IstsOffset = 10001,
|
||||
IregOffset = 30001,
|
||||
HregOffset = 40001
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
@class Modbus
|
||||
@brief Modbus base class
|
||||
*/
|
||||
class Modbus {
|
||||
#ifndef __DOXYGEN__
|
||||
private:
|
||||
TRegister *_regs_head;
|
||||
TRegister *_regs_last;
|
||||
char *_additional_data;
|
||||
|
||||
void readRegisters (const byte fcode, const word startreg, const word numregs);
|
||||
void writeSingleRegister (const word reg, const word value);
|
||||
void writeMultipleRegisters (const byte *frame, const word startreg, const word numoutputs, const byte bytecount);
|
||||
void exceptionResponse (const byte fcode, const byte excode);
|
||||
|
||||
#ifndef USE_HOLDING_REGISTERS_ONLY
|
||||
void readBits (const byte fcode, const word startreg, const word numregs);
|
||||
void writeSingleCoil (const word reg, const word status);
|
||||
void writeMultipleCoils (const byte *frame, const word startreg, const word numoutputs, const byte bytecount);
|
||||
#endif
|
||||
|
||||
TRegister *searchRegister (word addr);
|
||||
|
||||
void addReg (word address, word value = 0);
|
||||
bool setReg (word address, word value);
|
||||
word reg (word address);
|
||||
bool setRegBounds (word address, word min, word max);
|
||||
bool regOutOfBounds (word address, word value);
|
||||
|
||||
// @deprecated
|
||||
inline bool Reg (word address, word value) {
|
||||
return setReg (address, value);
|
||||
}
|
||||
// @deprecated
|
||||
inline word Reg (word address) {
|
||||
return reg (address);
|
||||
}
|
||||
|
||||
protected:
|
||||
byte *_frame;
|
||||
byte _len;
|
||||
byte _reply;
|
||||
Print *_debug;
|
||||
void receivePDU (byte *frame);
|
||||
void reportServerId();
|
||||
void debugMessage (bool reply = false);
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
@brief Default constructor
|
||||
*/
|
||||
Modbus();
|
||||
|
||||
/**
|
||||
@brief Add a holding register to the list
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value default value
|
||||
*/
|
||||
inline void addHreg (word offset, word value = 0) {
|
||||
this->addReg (offset + TRegister::HregOffset, value);
|
||||
}
|
||||
/**
|
||||
@brief Change the value of a holding register
|
||||
This value will be returned when bus read, the master can also modify it.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if register not found.
|
||||
*/
|
||||
inline bool setHreg (word offset, word value) {
|
||||
return setReg (offset + TRegister::HregOffset, value);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of a holding register
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@return register value
|
||||
*/
|
||||
inline word hreg (word offset) {
|
||||
return reg (offset + TRegister::HregOffset);
|
||||
}
|
||||
/**
|
||||
@brief Sets the bounds of a holding register
|
||||
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param min min value
|
||||
@param max max value
|
||||
@return true, false if register not found.
|
||||
*/
|
||||
inline bool setHregBounds (word offset, word min, word max) {
|
||||
return setRegBounds (offset + TRegister::HregOffset, min, max);
|
||||
}
|
||||
/**
|
||||
@brief Checks if the value is outside the bounds of a holging register
|
||||
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value value to check
|
||||
@return true if the value is outside the bounds, false otherwise
|
||||
*/
|
||||
inline bool hregOutOfBounds (word offset, word value) {
|
||||
return regOutOfBounds (offset + TRegister::HregOffset, value);
|
||||
}
|
||||
;
|
||||
/**
|
||||
@brief Change the value of a holding register
|
||||
This value will be returned when bus read, the master can also modify it.
|
||||
@deprecated removed in next major release, use setHreg() instead.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if register not found.
|
||||
*/
|
||||
inline bool Hreg (word offset, word value) {
|
||||
return setHreg (offset, value);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of a holding register
|
||||
@deprecated removed in next major release, use hreg() instead.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@return register value
|
||||
*/
|
||||
inline word Hreg (word offset) {
|
||||
return hreg (offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Enable debug mode
|
||||
*/
|
||||
inline void setDebug (Print &print) {
|
||||
_debug = &print;
|
||||
_debug->println ("Modbus: debug enabled....");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Returns true if debug mode is enabled
|
||||
*/
|
||||
inline bool isDebug () {
|
||||
return _debug != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Print a debug message, only if debug mode is enabled
|
||||
@param msg message to print
|
||||
*/
|
||||
inline void debug (const char *msg) {
|
||||
if (_debug) {
|
||||
_debug->println (msg);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef USE_HOLDING_REGISTERS_ONLY
|
||||
|
||||
/**
|
||||
@brief Add a coil
|
||||
@param offset coil offset (PDU addressing: 0-9999)
|
||||
@param value default value
|
||||
*/
|
||||
void addCoil (word offset, bool value = false) {
|
||||
this->addReg (offset + TRegister::CoilOffset, value ? 0xFF00 : 0x0000);
|
||||
}
|
||||
/**
|
||||
@brief Add a discrete input
|
||||
@param offset input offset (PDU addressing: 0-9999)
|
||||
@param value default value
|
||||
*/
|
||||
inline void addIsts (word offset, bool value = false) {
|
||||
this->addReg (offset + TRegister::IstsOffset, value ? 0xFF00 : 0x0000);
|
||||
}
|
||||
/**
|
||||
@brief Add a input register
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value default value
|
||||
*/
|
||||
inline void addIreg (word offset, word value = 0) {
|
||||
this->addReg (offset + TRegister::IregOffset, value);
|
||||
}
|
||||
/**
|
||||
@brief Change the value of a coil
|
||||
This value will be returned when bus read, the master can also modify it.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if coil not found.
|
||||
*/
|
||||
inline bool setCoil (word offset, bool value) {
|
||||
return setReg (offset + TRegister::CoilOffset, value ? 0xFF00 : 0x0000);
|
||||
}
|
||||
/**
|
||||
@brief Change the value of a discrete input
|
||||
This value will be returned when bus read,.
|
||||
@param offset input offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if input not found.
|
||||
*/
|
||||
inline bool setIsts (word offset, bool value) {
|
||||
return setReg (offset + TRegister::IstsOffset, value ? 0xFF00 : 0x0000);
|
||||
}
|
||||
/**
|
||||
@brief Change the value of an input register
|
||||
This value will be returned when bus read.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if register not found.
|
||||
*/
|
||||
inline bool setIreg (word offset, word value) {
|
||||
return setReg (offset + TRegister::IregOffset, value);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of a coil
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@return coil value
|
||||
*/
|
||||
inline bool coil (word offset) {
|
||||
return (reg (offset + TRegister::CoilOffset) == 0xFF00);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of a discrete input
|
||||
@param offset input offset (PDU addressing: 0-9999)
|
||||
@return input value
|
||||
*/
|
||||
inline bool ists (word offset) {
|
||||
return (reg (offset + TRegister::IstsOffset) == 0xFF00);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of an input register
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@return register value
|
||||
*/
|
||||
inline word ireg (word offset) {
|
||||
return reg (offset + TRegister::IregOffset);
|
||||
}
|
||||
/**
|
||||
@brief Change the value of a coil
|
||||
This value will be returned when bus read, the master can also modify it.
|
||||
@deprecated removed in next major release, use setCoil() instead.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if coil not found.
|
||||
*/
|
||||
inline bool Coil (word offset, bool value) {
|
||||
return setCoil (offset, value);
|
||||
}
|
||||
/**
|
||||
@brief Change the value of a discrete input
|
||||
This value will be returned when bus read,.
|
||||
@deprecated removed in next major release, use setIsts() instead.
|
||||
@param offset input offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if input not found.
|
||||
*/
|
||||
inline bool Ists (word offset, bool value) {
|
||||
return setIsts (offset, value);
|
||||
}
|
||||
/**
|
||||
@brief Change the value of an input register
|
||||
This value will be returned when bus read.
|
||||
@deprecated removed in next major release, use setIreg() instead.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@param value new value
|
||||
@return true, false if register not found.
|
||||
*/
|
||||
inline bool Ireg (word offset, word value) {
|
||||
return setIreg (offset, value);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of a coil
|
||||
@deprecated removed in next major release, use coil() instead.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@return coil value
|
||||
*/
|
||||
inline bool Coil (word offset) {
|
||||
return coil (offset);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of a discrete input
|
||||
@deprecated removed in next major release, use ists() instead.
|
||||
@param offset input offset (PDU addressing: 0-9999)
|
||||
@return input value
|
||||
*/
|
||||
inline bool Ists (word offset) {
|
||||
return ists (offset);
|
||||
}
|
||||
/**
|
||||
@brief Return the value of an input register
|
||||
@deprecated removed in next major release, use ireg() instead.
|
||||
@param offset register offset (PDU addressing: 0-9999)
|
||||
@return register value
|
||||
*/
|
||||
inline word Ireg (word offset) {
|
||||
return ireg (offset);
|
||||
}
|
||||
/**
|
||||
@brief Sets additional Data for Report Server ID function
|
||||
@param data data string
|
||||
@return the number of chars gets from data (249 max)
|
||||
*/
|
||||
int setAdditionalServerData (const char data[]);
|
||||
#endif
|
||||
};
|
Reference in New Issue
Block a user