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.

SimpleModbusSlave.cpp

Committer:
gugani
Date:
2017-10-25
Revision:
1:85e226914fb7
Parent:
0:7c5265097fd2

File content as of revision 1:85e226914fb7:

#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;
unsigned char datarec = 0;
unsigned char datasent = 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);

unsigned int newdatarec(){
    if (datarec){
        datarec = 0;
        return 1;
    }
    else return 0;
}

unsigned int newdatasent(){
    if (datasent){
        datasent = 0;
        return 1;
    }
    else return 0;
}

void end_of_transmission()
{
    if (TxEnablePin > 1)
        dir = 0;
    datasent = 1;
}

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
                {                    
                    datarec = 1;
                    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;
}