SimpleModbusSlave allows you to communicate to any slave using the Modbus RTU protocol. The crc calculation is based on https://os.mbed.com/users/jpelletier/code/CRC/ The functions implemented are functions 3 and 16. read holding registers and preset multiple registers of the Modbus RTU Protocol. This implementation DOES NOT fully comply with the Modbus specifications.
Diff: SimpleModbusSlave.cpp
- Revision:
- 0:7c5265097fd2
- Child:
- 1:85e226914fb7
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SimpleModbusSlave.cpp Tue Oct 10 12:22:31 2017 +0000 @@ -0,0 +1,292 @@ +#include "mbed.h" +#include "lib_crc.h" +#include "BufferedSerial.h" +#include "SimpleModbusSlave.h" + +#define BUFFER_SIZE 256 + +// frame[] is used to recieve and transmit packages. +// The maximum serial ring buffer size is 256 +char frame[BUFFER_SIZE]; +unsigned int holdingRegsSize; // size of the register array +char broadcastFlag; +char slaveID; +char function; +char TxEnablePin; +unsigned int errorCount = 0; +unsigned int T1; +unsigned int T1_5; // inter character time out +unsigned int T3_5; // frame delay +unsigned char buffer = 0; + +Timer intercharacter_timer; +Timeout interframe_timeout; + + +DigitalOut dir(D4); +BufferedSerial pc(PA_9, PA_10); //tx,rx +Serial usbdebug(PA_2, PA_3); //tx,rx + +// function definitions +void exceptionResponse(unsigned char exception); +unsigned int calculateCRC(char bufferSize); +void sendPacket(unsigned char bufferSize); +unsigned int reverse_CRC(unsigned int calculated_CRC); + +void end_of_transmission() +{ + if (TxEnablePin > 1) + dir = 0; +} + +unsigned int modbus_update(unsigned int *holdingRegs) +{ + unsigned char overflow = 0; + + if (pc.readable()) { + //while (pc.readable()){ + //usbdebug.printf("Serial data...\r\n"); + // 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) { + pc.getc(); + usbdebug.printf("Overflow\n\r"); + } else { + if (buffer == BUFFER_SIZE) + overflow = 1; + frame[buffer] = pc.getc(); + buffer++; + } + intercharacter_timer.reset(); + } + + + if (intercharacter_timer.read_us() > T1_5) + { + // 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 > 6) + { + 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 (reverse_CRC(calculate_crc16_Modbus(frame, 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; + unsigned char responseFrameSize = 5 + noOfBytes; // ID, function, noOfBytes, (dataLo + dataHi) * number of registers, crcLo, crcHi + 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 = holdingRegs[index]; + frame[address] = temp >> 8; // split the register into 2 bytes + address++; + frame[address] = temp & 0xFF; + address++; + } + + crc16 = reverse_CRC(calculate_crc16_Modbus(frame, 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 == 6) + { + if (startingAddress < holdingRegsSize) // check exception 2 ILLEGAL DATA ADDRESS + { + unsigned int startingAddress = ((frame[2] << 8) | frame[3]); + unsigned int regStatus = ((frame[4] << 8) | frame[5]); + unsigned char responseFrameSize = 8; + + holdingRegs[startingAddress] = regStatus; + + //crc16 = calculateCRC(responseFrameSize - 2); + crc16 = reverse_CRC(calculate_crc16_Modbus(frame, responseFrameSize - 2)); + frame[responseFrameSize - 2] = crc16 >> 8; // split crc into 2 bytes + frame[responseFrameSize - 1] = crc16 & 0xFF; + sendPacket(responseFrameSize); + } + 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++) + { + holdingRegs[index] = ((frame[address] << 8) | frame[address + 1]); + address += 2; + } + + // only the first 6 bytes are used for CRC calculation + //crc16 = calculateCRC(6); + crc16 = reverse_CRC(calculate_crc16_Modbus(frame, 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 + usbdebug.printf("The recieved number of bytes does not match the calculated bytes minus the request bytes\r\n"); + } + } + else + exceptionResponse(1); // exception 1 ILLEGAL FUNCTION + } + else // checksum failed + { + errorCount++;usbdebug.printf("Checksum failed\n\r"); + } + } // incorrect id + } + else if (buffer > 0 && buffer < 8) + { + errorCount++; // corrupted packet + usbdebug.printf("Packet < 8 bytes\r\n"); + } + buffer = 0; + //usbdebug.printf("Error count: %d\n\r", errorCount); + } + return errorCount; +} + +void exceptionResponse(unsigned char exception) +{ + errorCount++; // each call to exceptionResponse() will increment the errorCount + if (!broadcastFlag) // don't respond if its a broadcast message + { + frame[0] = slaveID; + frame[1] = (function | 0x80); // set the MSB bit high, informs the master of an exception + frame[2] = exception; + unsigned int crc16 = reverse_CRC(calculate_crc16_Modbus(frame, 3)); + frame[3] = crc16 >> 8; + frame[4] = crc16 & 0xFF; + sendPacket(5); // exception response is always 5 bytes ID, function + 0x80, exception code, 2 bytes crc + } +} + +void modbus_configure(long baud, char _slaveID, char _TxEnablePin, unsigned int _holdingRegsSize, unsigned char _lowLatency) +{ + slaveID = _slaveID; + pc.baud(baud); + usbdebug.baud(115200); + usbdebug.printf("modbus_configure\r\n"); + usbdebug.printf("Baudrate: %d, Slave id: %d\r\n", baud, slaveID); + + if (_TxEnablePin > 1) + { // pin 0 & pin 1 are reserved for RX/TX. To disable set txenpin < 2 + TxEnablePin = _TxEnablePin; + dir = 0; + } + + // 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 timeing 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 finaly modbus states an + // intercharacter must be 1.5T or 1.5 times longer than a normal character and thus + // 1.5T = 1.04167ms * 1.5 = 1.5625ms. A frame delay is 3.5T. + // Added experimental low latency delays. This makes the implementation + // non-standard but practically it works with all major modbus master implementations. + + if (baud == 1000000 && _lowLatency){ + T1_5 = 1; + T3_5 = 10; + } + else if (baud >= 115200 && _lowLatency){ + T1_5 = 75; + T3_5 = 175; + } + else 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 + } + T1 = 10000000/baud; + usbdebug.printf("T1 = %d T1_5 = %d T3_5 = %d\r\n",T1, T1_5, T3_5); + + holdingRegsSize = _holdingRegsSize; + errorCount = 0; // initialize errorCount + + intercharacter_timer.start(); +} + +void sendPacket(unsigned char bufferSize) +{ + if (TxEnablePin > 1) + dir = 1; + + for (unsigned char i = 0; i < bufferSize; i++) + pc.putc(frame[i]); + + // allow a frame delay to indicate end of transmission and wait T3_5 + interframe_timeout.attach_us(&end_of_transmission, T1 * bufferSize + T3_5); +} + +unsigned int reverse_CRC(unsigned int calculated_CRC){ + unsigned int temp; + temp = calculated_CRC >> 8; + calculated_CRC = (calculated_CRC << 8) | temp; + calculated_CRC &= 0xFFFF; + return calculated_CRC; +}