WiFiPowerMeter/SimpleModbusMaster.cpp

520 lines
15 KiB
C++
Raw Normal View History

2015-05-02 00:14:40 +02:00
#include "SimpleModbusMaster.h"
#include "HardwareSerial.h"
// state machine states
#define IDLE 1
#define WAITING_FOR_REPLY 2
#define WAITING_FOR_TURNAROUND 3
#define BUFFER_SIZE 128
unsigned char state;
unsigned char retry_count;
unsigned char TxEnablePin;
// frame[] is used to receive and transmit packages.
// The maximum number of bytes in a modbus packet is 256 bytes
// This is limited to the serial buffer of 128 bytes
unsigned char frame[BUFFER_SIZE];
unsigned char buffer;
unsigned int timeout; // timeout interval
unsigned int polling; // turnaround delay interval
unsigned int T1_5; // inter character time out in microseconds
unsigned long delayStart; // init variable for turnaround and timeout delay
unsigned int total_no_of_packets;
Packet* packetArray; // packet starting address
Packet* packet; // current packet
HardwareSerial* ModbusPort;
// function definitions
void idle();
void constructPacket();
unsigned char construct_F15();
unsigned char construct_F16();
void waiting_for_reply();
void processReply();
void waiting_for_turnaround();
void process_F1_F2();
void process_F3_F4();
void process_F15_F16();
void processError();
void processSuccess();
unsigned int calculateCRC(unsigned char bufferSize);
void sendPacket(unsigned char bufferSize);
// Modbus Master State Machine
void modbus_update()
{
switch (state)
{
case IDLE:
2015-05-03 20:53:49 +02:00
//Serial.println("mu 1a");
idle();
//Serial.println("mu 1b");
break;
2015-05-02 00:14:40 +02:00
case WAITING_FOR_REPLY:
2015-05-03 20:53:49 +02:00
//Serial.println("mu 2a");
waiting_for_reply();
//Serial.println("mu 2b");
break;
2015-05-02 00:14:40 +02:00
case WAITING_FOR_TURNAROUND:
2015-05-03 20:53:49 +02:00
waiting_for_turnaround();
break;
2015-05-02 00:14:40 +02:00
}
}
void idle()
{
static unsigned int packet_index;
unsigned int failed_connections = 0;
unsigned char current_connection;
do
{
if (packet_index == total_no_of_packets) // wrap around to the beginning
packet_index = 0;
// proceed to the next packet
packet = &packetArray[packet_index];
// get the current connection status
current_connection = packet->connection;
if (!current_connection)
{
// If all the connection attributes are false return
// immediately to the main sketch
if (++failed_connections == total_no_of_packets)
return;
}
packet_index++;
// if a packet has no connection get the next one
}while (!current_connection);
constructPacket();
}
void constructPacket()
{
packet->requests++;
frame[0] = packet->id;
frame[1] = packet->function;
frame[2] = packet->address >> 8; // address Hi
frame[3] = packet->address & 0xFF; // address Lo
// For functions 1 & 2 data is the number of points
// For functions 3, 4 & 16 data is the number of registers
// For function 15 data is the number of coils
frame[4] = packet->data >> 8; // MSB
frame[5] = packet->data & 0xFF; // LSB
unsigned char frameSize;
// construct the frame according to the modbus function
if (packet->function == PRESET_MULTIPLE_REGISTERS)
frameSize = construct_F16();
else if (packet->function == FORCE_MULTIPLE_COILS)
frameSize = construct_F15();
else // else functions 1,2,3 & 4 is assumed. They all share the exact same request format.
frameSize = 8; // the request is always 8 bytes in size for the above mentioned functions.
unsigned int crc16 = calculateCRC(frameSize - 2);
frame[frameSize - 2] = crc16 >> 8; // split crc into 2 bytes
frame[frameSize - 1] = crc16 & 0xFF;
sendPacket(frameSize);
state = WAITING_FOR_REPLY; // state change
// if broadcast is requested (id == 0) for function 15 or 16 then override
// the previous state and force a success since the slave wont respond
if (packet->id == 0)
processSuccess();
}
unsigned char construct_F15()
{
// function 15 coil information is packed LSB first until the first 16 bits are completed
// It is received the same way..
unsigned char no_of_registers = packet->data / 16;
unsigned char no_of_bytes = no_of_registers * 2;
// if the number of points dont fit in even 2byte amounts (one register) then use another register and pad
if (packet->data % 16 > 0)
{
no_of_registers++;
no_of_bytes++;
}
frame[6] = no_of_bytes;
unsigned char bytes_processed = 0;
unsigned char index = 7; // user data starts at index 7
unsigned int temp;
for (unsigned char i = 0; i < no_of_registers; i++)
{
temp = packet->register_array[i]; // get the data
frame[index] = temp & 0xFF;
bytes_processed++;
if (bytes_processed < no_of_bytes)
{
frame[index + 1] = temp >> 8;
bytes_processed++;
index += 2;
}
}
unsigned char frameSize = (9 + no_of_bytes); // first 7 bytes of the array + 2 bytes CRC + noOfBytes
return frameSize;
}
unsigned char construct_F16()
{
unsigned char no_of_bytes = packet->data * 2;
// first 6 bytes of the array + no_of_bytes + 2 bytes CRC
frame[6] = no_of_bytes; // number of bytes
unsigned char index = 7; // user data starts at index 7
unsigned char no_of_registers = packet->data;
unsigned int temp;
for (unsigned char i = 0; i < no_of_registers; i++)
{
temp = packet->register_array[i]; // get the data
frame[index] = temp >> 8;
index++;
frame[index] = temp & 0xFF;
index++;
}
unsigned char frameSize = (9 + no_of_bytes); // first 7 bytes of the array + 2 bytes CRC + noOfBytes
return frameSize;
}
void waiting_for_turnaround()
{
if ((millis() - delayStart) > polling)
state = IDLE;
}
// get the serial data from the buffer
void waiting_for_reply()
{
if ((*ModbusPort).available()) // is there something to check?
{
unsigned char overflowFlag = 0;
buffer = 0;
while ((*ModbusPort).available())
{
// The maximum number of bytes is limited to the serial buffer size
// of BUFFER_SIZE. If more bytes is received than the BUFFER_SIZE the
// overflow flag will be set and the serial buffer will be read until
// all the data is cleared from the receive buffer, while the slave is
// still responding.
if (overflowFlag)
(*ModbusPort).read();
else
{
if (buffer == BUFFER_SIZE)
overflowFlag = 1;
frame[buffer] = (*ModbusPort).read();
// Serial.print("R: "); Serial.println(frame[buffer], 16);
buffer++;
}
// This is not 100% correct but it will suffice.
// worst case scenario is if more than one character time expires
// while reading from the buffer then the buffer is most likely empty
// If there are more bytes after such a delay it is not supposed to
// be received and thus will force a frame_error.
delayMicroseconds(T1_5); // inter character time out
}
// The minimum buffer size from a slave can be an exception response of
// 5 bytes. If the buffer was partially filled set a frame_error.
// The maximum number of bytes in a modbus packet is 256 bytes.
// The serial buffer limits this to 128 bytes.
if ((buffer < 5) || overflowFlag)
processError();
// Modbus over serial line datasheet states that if an unexpected slave
// responded the master must do nothing and continue with the time out.
// This seems silly cause if an incorrect slave responded you would want to
// have a quick turnaround and poll the right one again. If an unexpected
// slave responded it will most likely be a frame error in any event
else if (frame[0] != packet->id) // check id returned
processError();
else
processReply();
}
else if ((millis() - delayStart) > timeout) // check timeout
{
processError();
state = IDLE; //state change, override processError() state
}
}
void processReply()
{
// combine the crc Low & High bytes
unsigned int received_crc = ((frame[buffer - 2] << 8) | frame[buffer - 1]);
unsigned int calculated_crc = calculateCRC(buffer - 2);
if (calculated_crc == received_crc) // verify checksum
{
// To indicate an exception response a slave will 'OR'
// the requested function with 0x80
if ((frame[1] & 0x80) == 0x80) // extract 0x80
{
packet->exception_errors++;
processError();
}
else
{
switch (frame[1]) // check function returned
{
case READ_COIL_STATUS:
case READ_INPUT_STATUS:
process_F1_F2();
break;
case READ_INPUT_REGISTERS:
case READ_HOLDING_REGISTERS:
process_F3_F4();
break;
case FORCE_MULTIPLE_COILS:
case PRESET_MULTIPLE_REGISTERS:
process_F15_F16();
break;
default: // illegal function returned
processError();
break;
}
}
}
else // checksum failed
{
processError();
}
}
void process_F1_F2()
{
// packet->data for function 1 & 2 is actually the number of boolean points
unsigned char no_of_registers = packet->data / 16;
unsigned char number_of_bytes = no_of_registers * 2;
// if the number of points dont fit in even 2byte amounts (one register) then use another register and pad
if (packet->data % 16 > 0)
{
no_of_registers++;
number_of_bytes++;
}
if (frame[2] == number_of_bytes) // check number of bytes returned
{
unsigned char bytes_processed = 0;
unsigned char index = 3; // start at the 4th element in the frame and combine the Lo byte
unsigned int temp;
for (unsigned char i = 0; i < no_of_registers; i++)
{
temp = frame[index];
bytes_processed++;
if (bytes_processed < number_of_bytes)
{
temp = (frame[index + 1] << 8) | temp;
bytes_processed++;
index += 2;
}
packet->register_array[i] = temp;
}
processSuccess();
}
else // incorrect number of bytes returned
processError();
}
void process_F3_F4()
{
// check number of bytes returned - unsigned int == 2 bytes
// data for function 3 & 4 is the number of registers
if (frame[2] == (packet->data * 2))
{
unsigned char index = 3;
for (unsigned char i = 0; i < packet->data; i++)
{
// start at the 4th element in the frame and combine the Lo byte
packet->register_array[i] = (frame[index] << 8) | frame[index + 1];
index += 2;
}
processSuccess();
}
else // incorrect number of bytes returned
processError();
}
void process_F15_F16()
{
// Functions 15 & 16 is just an echo of the query
unsigned int recieved_address = ((frame[2] << 8) | frame[3]);
unsigned int recieved_data = ((frame[4] << 8) | frame[5]);
if ((recieved_address == packet->address) && (recieved_data == packet->data))
processSuccess();
else
processError();
}
void processError()
{
packet->retries++;
packet->failed_requests++;
if (packet->valueValid != NULL) {
*(packet->valueValid) = false;
}
// if the number of retries have reached the max number of retries
// allowable, stop requesting the specific packet
if (packet->retries == retry_count)
{
packet->connection = 0;
packet->retries = 0;
if (packet->connectionValid != NULL) {
*(packet->connectionValid) = false;
}
}
state = WAITING_FOR_TURNAROUND;
delayStart = millis(); // start the turnaround delay
}
void processSuccess()
{
packet->successful_requests++; // transaction sent successfully
packet->retries = 0; // if a request was successful reset the retry counter
state = WAITING_FOR_TURNAROUND;
delayStart = millis(); // start the turnaround delay
if (packet->valueValid != NULL) {
*(packet->valueValid) = true;
}
if (packet->connectionValid != NULL) {
*(packet->connectionValid) = true;
}
}
void modbus_configure(HardwareSerial* SerialPort,
long baud,
unsigned char byteFormat,
unsigned int _timeout,
unsigned int _polling,
unsigned char _retry_count,
unsigned char _TxEnablePin,
Packet* _packets,
unsigned int _total_no_of_packets)
{
// 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 11 bit packet is 9600/11 = 872 characters per second
// In milliseconds this will be 872 characters per 1000ms. So for 1 character
// 1000ms/872 characters is 1.14583ms 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.14583ms * 1.5 = 1.71875ms. A frame delay is 3.5T.
// Thus the formula is T1.5(us) = (1000ms * 1000(us) * 1.5 * 11bits)/baud
// 1000ms * 1000(us) * 1.5 * 11bits = 16500000 can be calculated as a constant
if (baud > 19200)
T1_5 = 750;
else
T1_5 = 16500000/baud; // 1T * 1.5 = T1.5
// initialize
state = IDLE;
timeout = _timeout;
polling = _polling;
retry_count = _retry_count;
TxEnablePin = _TxEnablePin;
total_no_of_packets = _total_no_of_packets;
packetArray = _packets;
// initialize connection status of each packet
/*for (unsigned char i = 0; i < total_no_of_packets; i++)
{
_packets->connection = 1;
_packets++;
}*/
ModbusPort = SerialPort;
(*ModbusPort).begin(baud, byteFormat);
pinMode(TxEnablePin, OUTPUT);
digitalWrite(TxEnablePin, LOW);
}
void modbus_construct(Packet *_packet,
unsigned char id,
unsigned char function,
unsigned int address,
unsigned int data,
unsigned int* register_array)
{
_packet->id = id;
_packet->function = function;
_packet->address = address;
_packet->data = data;
_packet->register_array = register_array;
_packet->connection = 1;
_packet->connectionValid = NULL;
_packet->valueValid = NULL;
}
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++) {
// Serial.print("S: "); Serial.println(frame[i],16);
(*ModbusPort).write(frame[i]);
}
(*ModbusPort).flush();
// It may be necessary to add a another character delay T1_5 here to
// avoid truncating the message on slow and long distance connections
digitalWrite(TxEnablePin, LOW);
delayStart = millis(); // start the timeout delay
}