How to implement Modbus Slave.

I'm trying to sketch out a program to fuzz some pseudorandom values and saw-tooth ramps using Modbus RTU along with reading the analog value of some pots.

I happened upon the Modbus RTU slave code from jpmzometa and it looks like it will work within my specifications.

Upon compiling and uploading that sketch, my Modbus RTU master only gets exception responses from the Arduino.

Does anyone have any experience with using an Arduino as a Modbus RTU slave?

i'm also takin' infos about modbus in arduino.

i'm trying to know if i can use these libraries: libmodbus in Launchpad and Adminpanel

i'll tell you :wink:

Hi all!

I've been working for some months with Arduino and Modbus/RTU. I've taken the code from http://sites.google.com/site/jpmzometa/arduino-mbrt/arduino-modbus-slave and I've turned it into a class. I don't want to make it a library.

Here you'll find the code:

/* supported functions ! */
const unsigned char fsupported[] = { 
  0x03, 0x04, 0x10 };

unsigned long Nowdt = 0;
int lastBytesReceived = 0;

#define MAX_MESSAGE_LENGTH 256

/* constants */
enum { 
  RESPONSE_SIZE = 6, 
  EXCEPTION_SIZE = 3, 
  CHECKSUM_SIZE = 2 
};

/* exceptions code */
enum { 
  NO_REPLY = -1, 
  EXC_FUNC_CODE = 1, 
  EXC_ADDR_RANGE = 2, 
  EXC_REGS_QUANT = 3, 
  EXC_EXECUTE = 4 
};

typedef enum parity {
  P_8N1,P_8E1,P_8O1};

/* positions inside the query/response array */
enum { 
  SLAVE = 0, 
  FUNC, 
  START_H, 
  START_L, 
  REGS_H, 
  REGS_L, 
  BYTE_CNT 
};


class MB_SLAVE {
private:
  unsigned char slave;
  int TXEN_PIN;

  unsigned int crc(unsigned char *buf, unsigned char start, unsigned char cnt);
  void build_read_packet(unsigned char function, unsigned char count, unsigned char *packet);
  void build_write_packet(unsigned char function, unsigned int start_addr, unsigned char count, unsigned char *packet);
  void build_error_packet(unsigned char function,unsigned char exception, unsigned char *packet);
  void modbus_reply(unsigned char *packet, unsigned char string_length);
  int send_reply(unsigned char *query, unsigned char string_length);
  int receive_request(unsigned char *received_string);
  int modbus_request(unsigned char *data, int *regs);
  int validate_request(unsigned char *data, unsigned char length);
  int write_regs(unsigned int start_addr, unsigned char *query, int *regs);
  int preset_multiple_registers(unsigned int start_addr,unsigned char count,unsigned char *query,int *regs);
  int read_holding_registers(unsigned int start_addr, unsigned char reg_count, int *regs);

public:
  MB_SLAVE(int TXEN_PIN);
  void begin( parity par, long baud, unsigned char slave);
  int start_mb_slave(int *regs); 
};

// constructor
MB_SLAVE::MB_SLAVE(int TXEN_PIN) {
  this->TXEN_PIN = TXEN_PIN;
}

// slave comm's start-up 
void MB_SLAVE::begin( parity par, long baud, unsigned char slave) {

  // define digital-output for half-duplex mode
  digitalWrite( TXEN_PIN, HIGH );
  pinMode( TXEN_PIN, OUTPUT );

  // define slave address
  this->slave = slave;

  // set parity
  switch (par) {
  case P_8E1: // 8E1
    {
      UCSR0C |= ((1<<UPM01) | (1<<UCSZ01) | (1<<UCSZ00));
      //      UCSR0C &= ~((1<<UPM00) | (1<<UCSZ02) | (1<<USBS0));
    }  
    break;
  case P_8O1: // 8O1
    {
      UCSR0C |= ((1<<UPM01) | (1<<UPM00) | (1<<UCSZ01) | (1<<UCSZ00));
      //      UCSR0C &= ~((1<<UCSZ02) | (1<<USBS0));
    }  
    break;
  default:
  case P_8N1: // 8N1
    {
      UCSR0C |= ((1<<UCSZ01) | (1<<UCSZ00));
      //      UCSR0C &= ~((1<<UPM01) | (1<<UPM00) | (1<<UCSZ02) | (1<<USBS0));
    }  
    break;
  }

  Serial.begin(baud);
}

unsigned int MB_SLAVE::crc(unsigned char *buf, unsigned char start,unsigned char cnt) 
{
  unsigned char i, j;
  unsigned temp, temp2, flag;

  temp = 0xFFFF;

  for (i = start; i < cnt; i++) {
    temp = temp ^ buf[i];

    for (j = 1; j <= 8; j++) {
      flag = temp & 0x0001;
      temp = temp >> 1;
      if (flag)
        temp = temp ^ 0xA001;
    }
  }

  /* Reverse byte order. */
  temp2 = temp >> 8;
  temp = (temp << 8) | temp2;
  temp &= 0xFFFF;

  return (temp);
}

void MB_SLAVE::build_read_packet(unsigned char function,unsigned char count, unsigned char *packet) 
{
  packet[SLAVE] = slave;
  packet[FUNC] = function;
  packet[2] = count * 2;
} 

void MB_SLAVE::build_write_packet(unsigned char function,unsigned int start_addr,unsigned char count,unsigned char *packet) 
{
  packet[SLAVE] = slave;
  packet[FUNC] = function;
  packet[START_H] = start_addr >> 8;
  packet[START_L] = start_addr & 0x00ff;
  packet[REGS_H] = 0x00;
  packet[REGS_L] = count;
} 

void MB_SLAVE::build_error_packet(unsigned char function,unsigned char exception, unsigned char *packet) 
{
  packet[SLAVE] = slave;
  packet[FUNC] = function + 0x80;
  packet[2] = exception;
  D[ 12 ] ++;
} 

void MB_SLAVE::modbus_reply(unsigned char *packet, unsigned char string_length) 
{
  int temp_crc;

  temp_crc = crc(packet, 0, string_length);
  packet[string_length] = temp_crc >> 8;
  string_length++;
  packet[string_length] = temp_crc & 0x00FF;
} 

int MB_SLAVE::send_reply(unsigned char *query, unsigned char string_length) 
{
  unsigned char i, test;

  // set MAX485 to speak mode
  UCSR0A=UCSR0A |(1 << TXC0);
  digitalWrite( TXEN_PIN, HIGH);
  delay(1);

  modbus_reply(query, string_length);
  string_length += 2;

  // xmit message
  for (i = 0; i < string_length; i++) {
    Serial.print(query[i], BYTE);
  }

  while (!(UCSR0A & (1 << TXC0)));
  digitalWrite( TXEN_PIN, LOW);

  return i;       // it does not mean that the write was succesful, though 
}

int MB_SLAVE::receive_request(unsigned char *received_string) 
{
  int bytes_received = 0;

  while (Serial.available()) {
    received_string[bytes_received] = Serial.read();
    bytes_received++;
    if (bytes_received >= MAX_MESSAGE_LENGTH)
      return NO_REPLY;       // port error 
  }

  return (bytes_received);
}


int MB_SLAVE::modbus_request(unsigned char *data, int *regs) 
{
  int response_length;
  unsigned int crc_calc = 0;
  unsigned int crc_received = 0;
  unsigned char recv_crc_hi;
  unsigned char recv_crc_lo;

  response_length = receive_request(data);

  if (response_length > 0) {
    regs[ MSG_CNT] ++;

    crc_calc = crc(data, 0, response_length - 2);
    crc_received = data[response_length - 2];
    crc_received = (unsigned) crc_received << 8;
    crc_received =
      crc_received | (unsigned) data[response_length - 1];

    if (crc_calc != crc_received) {
      regs[ BAD_CRC_CNT ] ++;
      return NO_REPLY;
    }

    /* check for slave id */
    if ((slave != data[SLAVE]) && (data[SLAVE] != 0)) {
      regs[ NO_REPLY_CNT ] ++;
      return NO_REPLY;
    }
  }
  return (response_length);
}

  if ((data[FUNC] == 0x03) && (data[SLAVE]==0)) fcnt = 0;

  if (0 == fcnt)
    return EXC_FUNC_CODE;


  reg_cnt = ((int) data[REGS_H] << 8) + (int) data[REGS_L];

  if ((reg_cnt < 1) || (reg_cnt > REGS_NUM))
    return EXC_REGS_QUANT;

  /* check registers range, start address is 0 */
  start_addr = ((int) data[START_H] << 8) + (int) data[START_L];
  if ((start_addr + reg_cnt) > REGS_NUM)
    return EXC_ADDR_RANGE;

  return 0;             /* OK, no exception */
}

int MB_SLAVE::write_regs(unsigned int start_addr, unsigned char *query, int *regs) 
{
  int temp;
  unsigned int i;

  for (i = 0; i < query[REGS_L]; i++) {
    /* shift reg hi_byte to temp */
    temp = (int) query[(BYTE_CNT + 1) + i * 2] << 8;
    /* OR with lo_byte           */
    temp = temp | (int) query[(BYTE_CNT + 2) + i * 2];

    regs[start_addr + i] = temp;
  } 
  return i;
}

int MB_SLAVE::preset_multiple_registers(unsigned int start_addr,unsigned char count, 
unsigned char *query,int *regs) 
{
  unsigned char function = 0x10;       
  int status = 0;
  unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE];

  build_write_packet(function, start_addr, count, packet);

  if (write_regs(start_addr, query, regs)) {
    if (query[SLAVE] !=0) {
      regs[ REPLY_CNT ] ++;
      regs[ MSG_CNT ] ++;
      status = send_reply(packet, RESPONSE_SIZE);
    }
    else {
      regs[ NO_REPLY_CNT ] ++;
    }

  }

  return (status);
}

int MB_SLAVE::read_holding_registers(unsigned int start_addr,unsigned char reg_count, int *regs) {

  unsigned char function = 0x03;       
  int packet_size = 3;
  int status;
  unsigned int i;
  unsigned char packet[MAX_MESSAGE_LENGTH];

  build_read_packet(function, reg_count, packet);
  regs[ REPLY_CNT ] ++;
  regs[ MSG_CNT ] ++;

  for (i = start_addr; i < (start_addr + (unsigned int) reg_count); i++) {

    packet[packet_size] = regs[i] >> 8;
    packet_size++;
    packet[packet_size] = regs[i] & 0x00FF;
    packet_size++;
  } 

  status = send_reply(packet, packet_size);

  return (status);
}

int MB_SLAVE::start_mb_slave(int *regs) 
{
  unsigned char query[MAX_MESSAGE_LENGTH];
  unsigned char errpacket[EXCEPTION_SIZE + CHECKSUM_SIZE];
  unsigned int start_addr;
  const unsigned long T35 = 5;
  int exception;
  int length = Serial.available();
  unsigned long now = millis();

  digitalWrite( TXEN_PIN, LOW);

  if (length == 0) {
    lastBytesReceived = 0;
    return 0;
  }

  if (lastBytesReceived != length) {
    lastBytesReceived = length;
    Nowdt = now + T35;
    return 0;
  }
  if (now < Nowdt) return 0;

  lastBytesReceived = 0;
  length = modbus_request(query, regs);

  if (length < 1) {
    return length;
  } 
  else {
    exception = validate_request(query, length);
    if (exception) {
      regs[ EXCEPT_CNT ] ++;
      if (query[SLAVE] != 0) {
        regs[ REPLY_CNT ] ++;
        regs[ MSG_CNT ] ++;
        build_error_packet(query[FUNC], exception, errpacket);
        send_reply(errpacket, EXCEPTION_SIZE);
      }
      else {
        regs[ NO_REPLY_CNT ] ++;
      }
      return (exception);
    } 
    else {
      start_addr =
        ((int) query[START_H] << 8) +
        (int) query[START_L];
      if ((0x03 == query[FUNC]) || (0x04 == query[FUNC]))
        return read_holding_registers(start_addr,query[REGS_L],regs);

        if (0x10 == query[FUNC])
          return preset_multiple_registers(start_addr,query[REGS_L],query,regs);
    }
  }
}

/me

Upon compiling and uploading that sketch, my Modbus RTU master only gets exception responses from the Arduino.

Does anyone have any experience with using an Arduino as a Modbus RTU slave?

I've been using this library for a long time. I've have tested it with QModbus master simulator and it works fine.

Can you post the exception codes you get? When? All the time? Maybe it's because of a wrong serial port or something like this.

If you look at my code, there're some communication events counters. Maybe this helps you...

/me

i'm trying to know if i can use these libraries: libmodbus in Launchpad and Adminpanel

I've also read it thoroughly but I found easier to use the code from http://sites.google.com/site/jpmzometa/arduino-mbrt/arduino-modbus-slave. Explain me your progress on this issue!

please suby, can you explain me in what you work is different from jpmzometa's one? what does this class do better/different from the original sketch?
thank you,

The Class is a first step in order to build a library. Indeed it shares the same code from the original sketch.

There is only one addition, regarding RS-485 networks.