Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Revision 0:7a9f746fb93e, committed 2014-08-27
- Comitter:
- homayoun
- Date:
- Wed Aug 27 18:13:19 2014 +0000
- Commit message:
- Arduino SimpleModbusMaster modified for mbed
Changed in this revision
SimpleModbus.cpp | Show annotated file Show diff for this revision Revisions of this file |
SimpleModbus.h | Show annotated file Show diff for this revision Revisions of this file |
diff -r 000000000000 -r 7a9f746fb93e SimpleModbus.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SimpleModbus.cpp Wed Aug 27 18:13:19 2014 +0000 @@ -0,0 +1,443 @@ +#include "mbed.h" +#include "SimpleModbus.h" + +// state machine states +#define IDLE 1 +#define WAITING_FOR_REPLY 2 +#define WAITING_FOR_TURNAROUND 3 + +#define BUFFER_SIZE 64 + +Timer ModBusTimer; +Serial ModbusPort(SERIAL_TX, SERIAL_RX); + +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 64 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 int total_no_of_packets; +Packet* packetArray; // packet starting address +Packet* packet; // current packet + +// 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 (ModBusTimer.read_ms() > polling) + state = IDLE; +} + +// get the serial data from the buffer +void waiting_for_reply() +{ + if (ModbusPort.readable()) { // is there something to check? + unsigned char overflowFlag = 0; + buffer = 0; + while (ModbusPort.readable()) { + // 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.getc(); + else { + if (buffer == BUFFER_SIZE) + overflowFlag = 1; + + frame[buffer] = ModbusPort.getc(); + 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. + wait_ms(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 (ModBusTimer.read_ms() > 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 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; + } + state = WAITING_FOR_TURNAROUND; + ModBusTimer.start(); // 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; + ModBusTimer.start(); // start the turnaround delay +} + +void modbus_configure(unsigned int _timeout, + unsigned int _polling, + unsigned char _retry_count, + 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 + + + T1_5 = 750; + + // initialize + state = IDLE; + timeout = _timeout; + polling = _polling; + retry_count = _retry_count; + total_no_of_packets = _total_no_of_packets; + packetArray = _packets; + + ModbusPort.baud(9600); + ModbusPort.format(8,SerialBase::None,2); +} + +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; +} + +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) +{ + + + for (unsigned char i = 0; i < bufferSize; i++) + ModbusPort.putc(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 + + ModBusTimer.start(); // start the timeout delay +} \ No newline at end of file
diff -r 000000000000 -r 7a9f746fb93e SimpleModbus.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SimpleModbus.h Wed Aug 27 18:13:19 2014 +0000 @@ -0,0 +1,129 @@ +#ifndef SIMPLE_MODBUS_MASTER_H +#define SIMPLE_MODBUS_MASTER_H + +// SimpleModbusMasterV12 + +/* + 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 64 bytes or 32 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 58 bytes or 29 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 + 54 bytes or 27 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 64 bytes. +*/ + +#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; + +} 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(unsigned int _timeout, + unsigned int _polling, + unsigned char _retry_count, + Packet* _packets, + unsigned int _total_no_of_packets); + +#endif \ No newline at end of file