262 lines
8.5 KiB
C++
Executable file
262 lines
8.5 KiB
C++
Executable file
#include "SimpleModbusSlave.h"
|
|
#include "HardwareSerial.h"
|
|
|
|
|
|
#define BUFFER_SIZE 128
|
|
|
|
// frame[] is used to recieve and transmit packages.
|
|
// The maximum serial ring buffer size is 128
|
|
unsigned char frame[BUFFER_SIZE];
|
|
unsigned int holdingRegsSize; // size of the register array
|
|
unsigned int* regs; // user array address
|
|
unsigned char broadcastFlag;
|
|
unsigned char slaveID;
|
|
unsigned char function;
|
|
unsigned char TxEnablePin;
|
|
unsigned int errorCount;
|
|
unsigned int T1_5; // inter character time out
|
|
unsigned int T3_5; // frame delay
|
|
HardwareSerial* ModbusPort;
|
|
|
|
// function definitions
|
|
void exceptionResponse(unsigned char exception);
|
|
unsigned int calculateCRC(unsigned char bufferSize);
|
|
void sendPacket(unsigned char bufferSize);
|
|
|
|
void modbus_configure(HardwareSerial *SerialPort,
|
|
long baud,
|
|
unsigned char byteFormat,
|
|
unsigned char _slaveID,
|
|
unsigned char _TxEnablePin,
|
|
unsigned int _holdingRegsSize,
|
|
unsigned int* _regs)
|
|
{
|
|
ModbusPort = SerialPort;
|
|
(*ModbusPort).begin(baud, byteFormat);
|
|
slaveID = _slaveID;
|
|
holdingRegsSize = _holdingRegsSize;
|
|
regs = _regs;
|
|
TxEnablePin = _TxEnablePin;
|
|
pinMode(TxEnablePin, OUTPUT);
|
|
digitalWrite(TxEnablePin, LOW);
|
|
errorCount = 0; // initialize errorCount
|
|
|
|
// Modbus states that a baud rate higher than 19200 must use a fixed 750 us
|
|
// for inter character time out and 1.75 ms for a frame delay for baud rates
|
|
// below 19200 the timing is more critical and has to be calculated.
|
|
// E.g. 9600 baud in a 10 bit packet is 960 characters per second
|
|
// In milliseconds this will be 960characters per 1000ms. So for 1 character
|
|
// 1000ms/960characters is 1.04167ms per character and finally modbus states
|
|
// an inter-character must be 1.5T or 1.5 times longer than a character. Thus
|
|
// 1.5T = 1.04167ms * 1.5 = 1.5625ms. A frame delay is 3.5T.
|
|
|
|
if (baud > 19200)
|
|
{
|
|
T1_5 = 750;
|
|
T3_5 = 1750;
|
|
}
|
|
else
|
|
{
|
|
T1_5 = 15000000/baud; // 1T * 1.5 = T1.5
|
|
T3_5 = 35000000/baud; // 1T * 3.5 = T3.5
|
|
}
|
|
}
|
|
|
|
unsigned int modbus_update()
|
|
{
|
|
if ((*ModbusPort).available())
|
|
{
|
|
unsigned char buffer = 0;
|
|
unsigned char overflow = 0;
|
|
|
|
while ((*ModbusPort).available())
|
|
{
|
|
// The maximum number of bytes is limited to the serial buffer size of 128 bytes
|
|
// If more bytes is received than the BUFFER_SIZE the overflow flag will be set and the
|
|
// serial buffer will be red untill all the data is cleared from the receive buffer.
|
|
if (overflow)
|
|
(*ModbusPort).read();
|
|
else
|
|
{
|
|
if (buffer == BUFFER_SIZE)
|
|
overflow = 1;
|
|
frame[buffer] = (*ModbusPort).read();
|
|
buffer++;
|
|
}
|
|
delayMicroseconds(T1_5); // inter character time out
|
|
}
|
|
|
|
// If an overflow occurred increment the errorCount
|
|
// variable and return to the main sketch without
|
|
// responding to the request i.e. force a timeout
|
|
if (overflow)
|
|
return errorCount++;
|
|
|
|
// The minimum request packet is 8 bytes for function 3 & 16
|
|
if (buffer > 7)
|
|
{
|
|
unsigned char id = frame[0];
|
|
|
|
broadcastFlag = 0;
|
|
|
|
if (id == 0)
|
|
broadcastFlag = 1;
|
|
|
|
if (id == slaveID || broadcastFlag) // if the recieved ID matches the slaveID or broadcasting id (0), continue
|
|
{
|
|
unsigned int crc = ((frame[buffer - 2] << 8) | frame[buffer - 1]); // combine the crc Low & High bytes
|
|
if (calculateCRC(buffer - 2) == crc) // if the calculated crc matches the recieved crc continue
|
|
{
|
|
function = frame[1];
|
|
unsigned int startingAddress = ((frame[2] << 8) | frame[3]); // combine the starting address bytes
|
|
unsigned int no_of_registers = ((frame[4] << 8) | frame[5]); // combine the number of register bytes
|
|
unsigned int maxData = startingAddress + no_of_registers;
|
|
unsigned char index;
|
|
unsigned char address;
|
|
unsigned int crc16;
|
|
|
|
// broadcasting is not supported for function 3
|
|
if (!broadcastFlag && (function == 3))
|
|
{
|
|
if (startingAddress < holdingRegsSize) // check exception 2 ILLEGAL DATA ADDRESS
|
|
{
|
|
if (maxData <= holdingRegsSize) // check exception 3 ILLEGAL DATA VALUE
|
|
{
|
|
unsigned char noOfBytes = no_of_registers * 2;
|
|
// ID, function, noOfBytes, (dataLo + dataHi)*number of registers,
|
|
// crcLo, crcHi
|
|
unsigned char responseFrameSize = 5 + noOfBytes;
|
|
frame[0] = slaveID;
|
|
frame[1] = function;
|
|
frame[2] = noOfBytes;
|
|
address = 3; // PDU starts at the 4th byte
|
|
unsigned int temp;
|
|
|
|
for (index = startingAddress; index < maxData; index++)
|
|
{
|
|
temp = regs[index];
|
|
frame[address] = temp >> 8; // split the register into 2 bytes
|
|
address++;
|
|
frame[address] = temp & 0xFF;
|
|
address++;
|
|
}
|
|
|
|
crc16 = calculateCRC(responseFrameSize - 2);
|
|
frame[responseFrameSize - 2] = crc16 >> 8; // split crc into 2 bytes
|
|
frame[responseFrameSize - 1] = crc16 & 0xFF;
|
|
sendPacket(responseFrameSize);
|
|
}
|
|
else
|
|
exceptionResponse(3); // exception 3 ILLEGAL DATA VALUE
|
|
}
|
|
else
|
|
exceptionResponse(2); // exception 2 ILLEGAL DATA ADDRESS
|
|
}
|
|
else if (function == 16)
|
|
{
|
|
// Check if the recieved number of bytes matches the calculated bytes
|
|
// minus the request bytes.
|
|
// id + function + (2 * address bytes) + (2 * no of register bytes) +
|
|
// byte count + (2 * CRC bytes) = 9 bytes
|
|
if (frame[6] == (buffer - 9))
|
|
{
|
|
if (startingAddress < holdingRegsSize) // check exception 2 ILLEGAL DATA ADDRESS
|
|
{
|
|
if (maxData <= holdingRegsSize) // check exception 3 ILLEGAL DATA VALUE
|
|
{
|
|
address = 7; // start at the 8th byte in the frame
|
|
|
|
for (index = startingAddress; index < maxData; index++)
|
|
{
|
|
regs[index] = ((frame[address] << 8) | frame[address + 1]);
|
|
address += 2;
|
|
}
|
|
|
|
// only the first 6 bytes are used for CRC calculation
|
|
crc16 = calculateCRC(6);
|
|
frame[6] = crc16 >> 8; // split crc into 2 bytes
|
|
frame[7] = crc16 & 0xFF;
|
|
|
|
// a function 16 response is an echo of the first 6 bytes from
|
|
// the request + 2 crc bytes
|
|
if (!broadcastFlag) // don't respond if it's a broadcast message
|
|
sendPacket(8);
|
|
}
|
|
else
|
|
exceptionResponse(3); // exception 3 ILLEGAL DATA VALUE
|
|
}
|
|
else
|
|
exceptionResponse(2); // exception 2 ILLEGAL DATA ADDRESS
|
|
}
|
|
else
|
|
errorCount++; // corrupted packet
|
|
}
|
|
else
|
|
exceptionResponse(1); // exception 1 ILLEGAL FUNCTION
|
|
}
|
|
else // checksum failed
|
|
errorCount++;
|
|
} // incorrect id
|
|
}
|
|
else if (buffer > 0 && buffer < 8)
|
|
errorCount++; // corrupted packet
|
|
}
|
|
return errorCount;
|
|
}
|
|
|
|
void exceptionResponse(unsigned char exception)
|
|
{
|
|
// each call to exceptionResponse() will increment the errorCount
|
|
errorCount++;
|
|
if (!broadcastFlag) // don't respond if its a broadcast message
|
|
{
|
|
frame[0] = slaveID;
|
|
frame[1] = (function | 0x80); // set MSB bit high, informs the master of an exception
|
|
frame[2] = exception;
|
|
unsigned int crc16 = calculateCRC(3); // ID, function|0x80, exception code
|
|
frame[3] = crc16 >> 8;
|
|
frame[4] = crc16 & 0xFF;
|
|
// exception response is always 5 bytes
|
|
// ID, function + 0x80, exception code, 2 bytes crc
|
|
sendPacket(5);
|
|
}
|
|
}
|
|
|
|
unsigned int calculateCRC(unsigned char bufferSize)
|
|
{
|
|
unsigned int temp, temp2, flag;
|
|
temp = 0xFFFF;
|
|
for (unsigned char i = 0; i < bufferSize; i++)
|
|
{
|
|
temp = temp ^ frame[i];
|
|
for (unsigned char j = 1; j <= 8; j++)
|
|
{
|
|
flag = temp & 0x0001;
|
|
temp >>= 1;
|
|
if (flag)
|
|
temp ^= 0xA001;
|
|
}
|
|
}
|
|
// Reverse byte order.
|
|
temp2 = temp >> 8;
|
|
temp = (temp << 8) | temp2;
|
|
temp &= 0xFFFF;
|
|
// the returned value is already swapped
|
|
// crcLo byte is first & crcHi byte is last
|
|
return temp;
|
|
}
|
|
|
|
void sendPacket(unsigned char bufferSize)
|
|
{
|
|
digitalWrite(TxEnablePin, HIGH);
|
|
|
|
for (unsigned char i = 0; i < bufferSize; i++)
|
|
(*ModbusPort).write(frame[i]);
|
|
|
|
(*ModbusPort).flush();
|
|
|
|
// allow a frame delay to indicate end of transmission
|
|
delayMicroseconds(T3_5);
|
|
|
|
digitalWrite(TxEnablePin, LOW);
|
|
} |