From 6778847b6ddb003f5e7399dd9e225dcd9fc943e0 Mon Sep 17 00:00:00 2001 From: hg Date: Sat, 8 Nov 2014 15:16:54 +0100 Subject: [PATCH] modbus slave works, second adc added --- ModbusThermometer.cpp | 27 ++- SimpleModbusMaster.cpp | 515 ----------------------------------------- SimpleModbusMaster.h | 139 ----------- SimpleModbusSlave.cpp | 262 +++++++++++++++++++++ SimpleModbusSlave.h | 63 +++++ 5 files changed, 344 insertions(+), 662 deletions(-) delete mode 100644 SimpleModbusMaster.cpp delete mode 100644 SimpleModbusMaster.h create mode 100644 SimpleModbusSlave.cpp create mode 100644 SimpleModbusSlave.h diff --git a/ModbusThermometer.cpp b/ModbusThermometer.cpp index ec03676..9970bcc 100644 --- a/ModbusThermometer.cpp +++ b/ModbusThermometer.cpp @@ -7,12 +7,15 @@ #include "SimpleModbusSlave.h" const uint8_t LED_PIN = 8; -const uint8_t ADC_CS_PIN = 9; -const uint8_t ADC_RDY_PIN = 7; +const uint8_t ADC_1_CS_PIN = 9; +const uint8_t ADC_1_RDY_PIN = 7; +const uint8_t ADC_2_CS_PIN = 2; +const uint8_t ADC_2_RDY_PIN = 3; const uint8_t MODBUS_TX_ENABLE_PIN = 6; const uint8_t MODBUS_ID = 3; -ADS1210 ads1210; +ADS1210 ads1210_1; +ADS1210 ads1210_2; LED led; Metro secondTick = Metro(1000); uint32_t uptimeSeconds; @@ -22,27 +25,35 @@ struct { union { uint32_t in; uint16_t modbusRegisters[2]; // 0, 1 - } adcValue; + } adc1Value; union { uint32_t in; uint16_t modbusRegisters[2]; // 2, 3 + } adc2Value; + union { + uint32_t in; + uint16_t modbusRegisters[2]; // 4, 5 } uptimeSeconds; } modbusHoldingRegisters; void setup() { led.begin(LED_PIN); - ads1210.begin(ADC_CS_PIN, ADC_RDY_PIN); + ads1210_1.begin(ADC_1_CS_PIN, ADC_1_RDY_PIN); + ads1210_2.begin(ADC_2_CS_PIN, ADC_2_RDY_PIN); modbus_configure(&Serial, 1200, SERIAL_8N2, MODBUS_ID, MODBUS_TX_ENABLE_PIN, - sizeof(modbusHoldingRegisters), modbusHoldingRegisters) + sizeof(modbusHoldingRegisters), (uint16_t*)(&modbusHoldingRegisters)); uptimeSeconds = 0; } void loop() { modbus_update(); - ads1210.exec(); - modbusHoldingRegisters.adcValue.in = ads1210.value; + ads1210_1.exec(); + modbusHoldingRegisters.adc1Value.in = ads1210_1.value; + + ads1210_2.exec(); + modbusHoldingRegisters.adc2Value.in = ads1210_2.value; if (secondTick.check() == 1) { led.toggle(); diff --git a/SimpleModbusMaster.cpp b/SimpleModbusMaster.cpp deleted file mode 100644 index 2fe3029..0000000 --- a/SimpleModbusMaster.cpp +++ /dev/null @@ -1,515 +0,0 @@ -#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: - idle(); - break; - case WAITING_FOR_REPLY: - waiting_for_reply(); - break; - case WAITING_FOR_TURNAROUND: - waiting_for_turnaround(); - break; - } -} - -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 -} diff --git a/SimpleModbusMaster.h b/SimpleModbusMaster.h deleted file mode 100644 index 86bf6b6..0000000 --- a/SimpleModbusMaster.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef SIMPLE_MODBUS_MASTER_H -#define SIMPLE_MODBUS_MASTER_H - -// SimpleModbusMasterV10 - -/* - SimpleModbusMaster allows you to communicate - to any slave using the Modbus RTU protocol. - - To communicate with a slave you need to create a packet that will contain - all the information required to communicate to the slave. - Information counters are implemented for further diagnostic. - These are variables already implemented in a packet. - You can set and clear these variables as needed. - - The following modbus information counters are implemented: - - requests - contains the total requests to a slave - successful_requests - contains the total successful requests - failed_requests - general frame errors, checksum failures and buffer failures - retries - contains the number of retries - exception_errors - contains the specific modbus exception response count - These are normally illegal function, illegal address, illegal data value - or a miscellaneous error response. - - And finally there is a variable called "connection" that - at any given moment contains the current connection - status of the packet. If true then the connection is - active. If false then communication will be stopped - on this packet until the programmer sets the connection - variable to true explicitly. The reason for this is - because of the time out involved in modbus communication. - Each faulty slave that's not communicating will slow down - communication on the line with the time out value. E.g. - Using a time out of 1500ms, if you have 10 slaves and 9 of them - stops communicating the latency burden placed on communication - will be 1500ms * 9 = 13,5 seconds! - Communication will automatically be stopped after the retry count expires - on each specific packet. - - All the error checking, updating and communication multitasking - takes place in the background. - - In general to communicate with to a slave using modbus - RTU you will request information using the specific - slave id, the function request, the starting address - and lastly the data to request. - Function 1, 2, 3, 4, 15 & 16 are supported. In addition to - this broadcasting (id = 0) is supported for function 15 & 16. - - Constants are provided for: - Function 1 - READ_COIL_STATUS - Function 2 - READ_INPUT_STATUS - Function 3 - READ_HOLDING_REGISTERS - Function 4 - READ_INPUT_REGISTERS - Function 15 - FORCE_MULTIPLE_COILS - Function 16 - PRESET_MULTIPLE_REGISTERS - - Note: - The Arduino serial ring buffer is 128 bytes or 64 registers. - Most of the time you will connect the Arduino using a MAX485 or similar. - - In a function 3 or 4 request the master will attempt to read from a - slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES - and two BYTES CRC the master can only request 122 bytes or 61 registers. - - In a function 16 request the master will attempt to write to a - slave and since 9 bytes is already used for ID, FUNCTION, ADDRESS, - NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write - 118 bytes or 59 registers. - - Note: - Using a USB to Serial converter the maximum bytes you can send is - limited to its internal buffer which differs between manufactures. - - Since it is assumed that you will mostly use the Arduino to connect without - using a USB to Serial converter the internal buffer is set the same as the - Arduino Serial ring buffer which is 128 bytes. -*/ - -#include "Arduino.h" - -#define READ_COIL_STATUS 1 // Reads the ON/OFF status of discrete outputs (0X references, coils) in the slave. -#define READ_INPUT_STATUS 2 // Reads the ON/OFF status of discrete inputs (1X references) in the slave. -#define READ_HOLDING_REGISTERS 3 // Reads the binary contents of holding registers (4X references) in the slave. -#define READ_INPUT_REGISTERS 4 // Reads the binary contents of input registers (3X references) in the slave. Not writable. -#define FORCE_MULTIPLE_COILS 15 // Forces each coil (0X reference) in a sequence of coils to either ON or OFF. -#define PRESET_MULTIPLE_REGISTERS 16 // Presets values into a sequence of holding registers (4X references). - -typedef struct -{ - // specific packet info - unsigned char id; - unsigned char function; - unsigned int address; - // 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 - unsigned int data; - unsigned int* register_array; - - // modbus information counters - unsigned int requests; - unsigned int successful_requests; - unsigned int failed_requests; - unsigned int exception_errors; - unsigned int retries; - - // connection status of packet - unsigned char connection; - - bool *connectionValid; - bool *valueValid; - -}Packet; - -typedef Packet* packetPointer; - -// function definitions -void modbus_update(); - -void modbus_construct(Packet *_packet, - unsigned char id, - unsigned char function, - unsigned int address, - unsigned int data, - unsigned int* register_array); - -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); - -#endif \ No newline at end of file diff --git a/SimpleModbusSlave.cpp b/SimpleModbusSlave.cpp new file mode 100644 index 0000000..f17cd9f --- /dev/null +++ b/SimpleModbusSlave.cpp @@ -0,0 +1,262 @@ +#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); +} \ No newline at end of file diff --git a/SimpleModbusSlave.h b/SimpleModbusSlave.h new file mode 100644 index 0000000..40c38d0 --- /dev/null +++ b/SimpleModbusSlave.h @@ -0,0 +1,63 @@ +#ifndef SIMPLE_MODBUS_SLAVE_H +#define SIMPLE_MODBUS_SLAVE_H + +// SimpleModbusSlaveV7 + +/* + SimpleModbusSlave allows you to communicate + to any slave using the Modbus RTU protocol. + + This implementation DOES NOT fully comply with the Modbus specifications. + + Specifically the frame time out have not been implemented according + to Modbus standards. The code does however combine the check for + inter character time out and frame time out by incorporating a maximum + time out allowable when reading from the message stream. + + SimpleModbusSlave implements an unsigned int return value on a call to modbus_update(). + This value is the total error count since the slave started. It's useful for fault finding. + + This code is for a Modbus slave implementing functions 3 and 16 + function 3: Reads the binary contents of holding registers (4X references) + function 16: Presets values into a sequence of holding registers (4X references) + + All the functions share the same register array. + + Note: + The Arduino serial ring buffer is 128 bytes or 64 registers. + Most of the time you will connect the arduino to a master via serial + using a MAX485 or similar. + + In a function 3 request the master will attempt to read from your + slave and since 5 bytes is already used for ID, FUNCTION, NO OF BYTES + and two BYTES CRC the master can only request 122 bytes or 61 registers. + + In a function 16 request the master will attempt to write to your + slave and since a 9 bytes is already used for ID, FUNCTION, ADDRESS, + NO OF REGISTERS, NO OF BYTES and two BYTES CRC the master can only write + 118 bytes or 59 registers. + + Using a USB to Serial converter the maximum bytes you can send is + limited to its internal buffer which differs between manufactures. + + The functions included here have been derived from the + Modbus Specifications and Implementation Guides + + http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf + http://www.modbus.org/docs/PI_MBUS_300.pdf +*/ + +#include "Arduino.h" + +// function definitions +unsigned int modbus_update(); +void modbus_configure(HardwareSerial *SerialPort, + long baud, + unsigned char byteFormat, + unsigned char _slaveID, + unsigned char _TxEnablePin, + unsigned int _holdingRegsSize, + unsigned int* _regs); + +#endif