Subversion Repositories factorylink.mb_plus

Rev

Blame | Last modification | View Log | Download

/*name testslav.c*/

/* Copyright (C) Modicon, Inc. 1989,  All Rights Reserved. */
/*
testslav.c


This program is an example of how to write a modbus slave application.
The program initializes and opens one of the data slave input paths on the
first SA85 board installed in the system.  When a modbus message is
received from this path, this program will interpret the command, and
generate the appropriate response.
*/
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <time.h>
#include <ctype.h>
#include <math.h>
#include <signal.h>
#include "netbios.h"
#include "netlib.h"
#include "testslav.h"

#define RETURN_GOOD_STATUS  0           /*library call success*/
#define RETURN_BAD_STATUS   -1          /*library call error*/


/*
prototypes
*/
SHORT main(void);
void control_c(int sig);
SHORT allocate_paths(UCHAR address);

SHORT fslave6(USHORT tblbuff[], UCHAR cmdbuff[], 
            UCHAR repbuff[], USHORT dbuff[]);

extern SHORT hi(USHORT arg);
extern SHORT lo(USHORT arg);
extern SHORT send_exc_response(SHORT code, UCHAR *cmdbuff, UCHAR *repbuff);
extern SHORT fslave(USHORT *tblbuff, UCHAR *cmdbuff, UCHAR *repbuff, USHORT *dbuff);
extern SHORT decode_modbus_msg(UCHAR *cmdbuff, UCHAR *repbuff, USHORT *dbuff);
extern SHORT slave_read_0x(void);
extern SHORT slave_read_1x(void);
extern SHORT slave_read_4x(UCHAR *cmdbuff, UCHAR *repbuff, USHORT *dbuff);
extern SHORT slave_read_3x(void);
extern SHORT slave_force_0x(void);
extern SHORT slave_preset_4x(UCHAR *cmdbuff, UCHAR *repbuff);
extern SHORT slave_read_exc_status(void);
extern SHORT slave_loopback(void);
extern SHORT slave_force_0x_blk(void);
extern SHORT slave_preset_4x_blk(UCHAR *cmdbuff, UCHAR *repbuff);
extern SHORT slave_slave_id(void);
extern SHORT slave_wcmd(SHORT nbytes, UCHAR *cmdbuff);
extern SHORT slave_rrep(SHORT *n_bytes, UCHAR *repbuff);
extern SHORT back_dbread(SHORT offset, PUSHORT buffer, SHORT quantity);
extern SHORT back_dbwrite(SHORT offset, PUSHORT buffer, SHORT quantity);

USHORT this_database[10000];
NCB data_ncb;
NCB *data_ncbp;

struct dxparamst DMA_area;
USHORT *T = (USHORT *) &DMA_area.table[0];
UCHAR cmdbuff[524], repbuff[524];
USHORT dbuff[256];
SHORT completed;

SHORT main()
  {

  printf("\nAEG Modicon slave module for Modbus Plus 4/18/89");
  printf("\nhard-coded for Slaveid = 12\n");


  ncb_reset(0);   /* start by resetting the board */

    if( signal( SIGINT, control_c ) == SIG_ERR ) {
        printf( "Unable to redirect Control-C." );
        exit( 1 );
    }
  if ((data_ncbp = ncb_open("DS.1.0.0.0.0", 0)) == NULL) {
    printf("\nUnable to open DATA SLAVE path #1 (0x41 internal).\n");
    exit(1);
  }

    completed = 0;
  while( !completed ) {
        if( kbhit() )
            completed = 1;
        /* loop repeatedly */

    T[0] = 6;       /* port "6" is SA85 Modbus Plus */
    T[1] = 12;        /* we're slave address #12 */
    T[2] = T[3] = 0;  /* don't use these, set them to zero */
    T[4] = 0;       /* doesn't matter */
    fslave6(T, cmdbuff, repbuff, dbuff);

    if (T[5] != 0)
      printf("\nSlave Status = 0x%X", T[5]);
    else 
      printf("+");
    }
     /*
      * Completion Status is returned in T[DXSTATUS] (also returned by
      * the function), and also the detailed status is contained in
      * T[5].
      */

          exit( 0 );
          return 0;
  }

static SHORT swap(USHORT *, SHORT);
static SHORT clength;   /* required only when #DEBUG defined */

SHORT fslave6(tblbuff, cmdbuff, repbuff, dbuff)
USHORT tblbuff[];
UCHAR repbuff[], cmdbuff[];
USHORT dbuff[];
  {
/****************************************************************************
*  f_slave          
*  Simulate Modbus Slave
*  Calling this block will look for a Modbus message on the communications port.
*  If a good (supported) Modbus message, and we're the correct slave,
*       the approprate Modbus slave function will be called.
*  Current status will be returned in tblbuff[5].
*  Received Modbus function number will be returned in tblbuff[6].
*****************************************************************************/
/*    char error_text[80];   goes with decode_status - not needed here */

/* CALL block table of registers: register offsets                      */
/* tblbuff[0] PORT          0     Modbus slave port to use (1-6) on Co-Pro  */
/* tblbuff[1] SLAVE         1     USER: Slave # we'll pretend to be         */
/* tblbuff[2] ADDRESS       2     not used  (in master, USER: Slave data address) */
/* tblbuff[3] QUANTITY      3     not used  (in master, USER: quantity of regs in/out */
/* tblbuff[4] MODE          4     not used  (in master, USER: 0 = ASCII  1 = RTU mode(relaxed timing only)) */
/* tblbuff[5] M_STATUS      5     Modbus slave status word - statuses appear here */
/* tblbuff[6] DATA1         6     function number for Modbus message received */
/*            (all other data regs follow)                              */

  static SHORT nbytes;  /* # bytes read from slave      */

  tblbuff[M_STATUS] = slave_rrep(&nbytes, repbuff);

  if (tblbuff[M_STATUS] == 0) {
        /* now decode the good Modbus message */
    clength = nbytes;
    decode_modbus_msg(cmdbuff, repbuff, dbuff);
                          /* pass address, Co-Pro Port to use */
    tblbuff[DATA1] = repbuff[DATA1];  /* received Modbus function_no number is returned in tblbuff[1] */
    return DXDONE;
    }
  else 
    return DXERROR; /* Bad Modbus message received */
  }

SHORT decode_modbus_msg(cmdbuff, repbuff, dbuff)
UCHAR *repbuff, *cmdbuff;
USHORT *dbuff;
  {         /* decode_modbus_msg */

  switch (repbuff[1]) {

  case 1:       /* Modbus code 01 - read output status */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
/*      slave_read_0x(); */
    break;

  case 2:       /* Modbus code 02 - read input status */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
/*      slave_read_1x(); */
    break;

  case 3:       /* Modbus code 03 - read output registers */
    return (slave_read_4x(cmdbuff, repbuff, dbuff));
    break;

  case 4:       /* Modbus code 04 - read input registers */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
/*      slave_read_3x(); */
    break;

  case 5:       /* Modbus code 05 - force a single coil */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
    break;

  case 6:       /* Modbus code 06 - preset single register */
    slave_preset_4x(cmdbuff, repbuff);
    break;

  case 7:       /* Modbus code 07 - read slave exception coils */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
/*      slave_read_exc_status(); */
    break;

  case 8:       /* Modbus code 08 - loop back test */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
/*      slave_loopback(); */
    break;

  case 15:      /* Modbus code 15 - force multiple coils */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
/*      slave_force_0x_blk(); */
    break;

  case 16:      /* Modbus code 16 - preset multiple registers */
    slave_preset_4x_blk(cmdbuff, repbuff);
    break;

  case 17:      /* Modbus code 17 - report slave id */
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
/*      slave_slave_id(); */
    break;

  default:
    send_exc_response(1, cmdbuff, repbuff);
                        /* bad function, until implemented */
    break;
    }
  return SUCCESS;
  }         /* decode_modbus_msg */


SHORT slave_read_0x()
  {         /* slave_read_0x */
  return SUCCESS;
  }         /* slave_read_0x */

SHORT slave_read_1x()
  {         /* slave_read_1x */
  return SUCCESS;
  }         /* slave_read_1x */

SHORT slave_read_4x(cmdbuff, repbuff, dbuff)
   /* Function to handle request for holding registers */
UCHAR repbuff[], cmdbuff[];
USHORT dbuff[];
  {         /* slave_read_4x */
  static SHORT i, j, offset, quantity;


/*
 * We Received:        |    We will send:
 * ------------------------------------------------------
 * address             |    address
 * function            |    function
 * data start reg high |    byte count
 * data start reg low  |    data out reg high  40108
 * data # regs high    |    data out reg low   40108
 * data # regs low     |    data out reg high  40109
 *                     |    data out reg low   40109
 *                     |    etc
 */
  swap((USHORT *) &repbuff[2], 2);  /* fixup address and quantity */
  offset = *(SHORT *) &repbuff[2];
  quantity = *(SHORT *) &repbuff[4];  /* quantity in 16-bit WORDS, not bytes */

/* validate quantity here. If greater than 125 registers, send back exception response */
  if (quantity > 125) {
    send_exc_response(3, cmdbuff, repbuff);
                        /* bad data */
    return FAILURE;
    }
  else {
    if (back_dbread(offset, dbuff, quantity) == 0) {
                        /* SHORT offset, USHORT *buffer, SHORT qty */

      cmdbuff[0] = repbuff[0];
                        /* byte 1 is our slave # */
      cmdbuff[1] = repbuff[1];
                        /* byte 2 is function code */
      cmdbuff[2] = (UCHAR) quantity * 2;
                        /* byte 3 is byte count */

        /* Note: the qty field in the next line was for REGISTERS, so we have to multiply by 2 */
      for (i = 3,  j = 0;  i < (3 + (2 * quantity));  i += 2,  ++j) {
        cmdbuff[i] = (UCHAR) hi(dbuff[j]);
        cmdbuff[i + 1] = (UCHAR) lo(dbuff[j]);
        }
      slave_wcmd(i, cmdbuff);
                        /* transmit response */
      return SUCCESS;
      }
    else {
/* Also, transmit back exception response here */
      send_exc_response(2, cmdbuff, repbuff);
                        /* bad address */
      return FAILURE;
      }
    }
  }         /* slave_read_4x */

SHORT slave_read_3x()
  {         /* slave_read_3x */
  return SUCCESS;
  }         /* slave_read_3x */

SHORT slave_force_0x()
  {         /* slave_force_0x */
  return SUCCESS;
  }         /* slave_force_0x */

SHORT slave_preset_4x(cmdbuff, repbuff)
   /* Function to handle request for setting a single holding register */
UCHAR repbuff[], cmdbuff[];
  {         /* slave_preset_4x */
  static SHORT offset;
  static USHORT data;
  SHORT broadcast;    /* if 1, broadcast mode, send no response */


/*
 * We Received:        |    We will send:
 * ------------------------------------------------------
 * address             |    address
 * function            |    function
 * data reg high       |    data reg high
 * data reg low        |    data reg low 
 * data value high     |    data value hight
 * data value low      |    data value low
 * (In this case response identical to what we received)
 */
  swap((USHORT *) &repbuff[2], 2);  /* fixup address and data */
  broadcast = !repbuff[0];  /* if broadcast mode, address is 0 */
  offset = *(SHORT *) &repbuff[2];
  data = *(SHORT *) &repbuff[4];

  if (back_dbwrite(offset, &data, 1) == 0) {
                        /* SHORT offset, USHORT *buffer, SHORT qty */
    if (!broadcast) {
                        /* If not Broadcast mode, issue response */
      cmdbuff[0] = repbuff[0];
                        /* byte 1 is our slave # */
      cmdbuff[1] = repbuff[1];
                        /* byte 2 is function code */
      cmdbuff[2] = (UCHAR) hi(offset);
                        /* byte 3 is offset (ho) */
      cmdbuff[3] = (UCHAR) lo(offset);
                        /* byte 4 is offset (lo) */
      cmdbuff[4] = (UCHAR) hi(data);
                        /* byte 5 is data (ho) */
      cmdbuff[5] = (UCHAR) lo(data);
                        /* byte 5 is data (lo) */

      slave_wcmd(6, cmdbuff);
                        /* transmit response if not Broadcast mode */
      }
    return SUCCESS;
    }
  else {
/* Also, transmit back exception response here if not Broadcast mode */
    if (!broadcast)
      send_exc_response(2, cmdbuff, repbuff);
                        /* bad address */
    return FAILURE;
    }
  }         /* slave_preset_4x */

SHORT slave_read_exc_status()
  {         /* slave_read_exc_status */
  return SUCCESS;
  }         /* slave_read_exc_status */

SHORT slave_loopback()
  {         /* slave_loopback */
  return SUCCESS;
  }         /* slave_loopback */

SHORT slave_force_0x_blk()
  {         /* slave_force_0x_blk */
  return SUCCESS;
  }         /* slave_force_0x_blk */

SHORT slave_preset_4x_blk(cmdbuff, repbuff)
   /* Function to handle request for setting a multiple holding registers */
UCHAR repbuff[], cmdbuff[];
  {         /* slave_preset_4x_blk */
  static SHORT offset, quantity;
  static UCHAR *workptr;
  SHORT broadcast;    /* if 1, broadcast mode, send no response */


/*
 * We Received:        |    We will send:
 * ------------------------------------------------------
 * address             |    address
 * function            |    function
 * reg addr high       |    reg addr high
 * reg addr low        |    reg addr low 
 * data quantity high  |    data quantity high
 * data quantity low   |    data quantity low
 * byte count          |
 * data high    reg 1  |
 * data low            |
 * data high    reg 2  |
 * data low            |
 */
  swap((USHORT *) &repbuff[2], 2);  /* fixup address and quantity */
  offset = *(SHORT *) &repbuff[2];
  broadcast = !repbuff[0];  /* If broadcast mode, address is 0 */
  quantity = *(SHORT *) &repbuff[4];  /* quantity in 16-bit WORDS, not bytes */

/*
 * validate quantity here.
 * If greater than 125 registers,
 * and not broadcast mode,
 * send back exception response
 */
  if ((quantity > 125) && (!broadcast)) {
    send_exc_response(3, cmdbuff, repbuff);
                        /* bad data */
    return FAILURE;
    }
  else {
    workptr = (UCHAR *) &repbuff[0];
    workptr += 7; /* skip addr/func/haddr/laddr/hqty/lqty/bytecount */

    swap((USHORT *) workptr, quantity);
            /* swap database databytes */

    if (back_dbwrite(offset, (USHORT *) workptr, quantity) == 0) {
                        /* SHORT offset, USHORT *buffer, SHORT quantity */

      if (!broadcast) {
                        /* If not Broadcast mode, issue response */
        cmdbuff[0] = repbuff[0];
                        /* byte 1 is our slave # */
        cmdbuff[1] = repbuff[1];
                        /* byte 2 is function code */
        cmdbuff[2] = (UCHAR) hi(offset);
                        /* byte 3 is offset (ho)*/
        cmdbuff[3] = (UCHAR) lo(offset);
                        /* byte 4 is offset (lo) */
        cmdbuff[4] = (UCHAR) hi(quantity);
                        /* byte 5 is quantity (ho) */
        cmdbuff[5] = (UCHAR) lo(quantity);
                        /* byte 6 is quantity (lo) */

        slave_wcmd(6, cmdbuff);
                        /* transmit response */
        }
      return SUCCESS;
      }
    else {
                            /* Also, transmit back exception response here */
      if (!broadcast) /* If not Broadcast mode, issue response */
        send_exc_response(2, cmdbuff, repbuff);
                        /* bad address */
      return FAILURE;
      }
    }
  }         /* slave_preset_4x_blk */


SHORT slave_slave_id()
  {         /* slave_slave_id */
  return SUCCESS;
  }         /* slave_slave_id */

static SHORT swap(T, qty)
USHORT T[];
SHORT qty;
  {         /* swap */
  SHORT i;
  USHORT cswap;

  for (i = 0;  i < qty;  ) {
    cswap = T[i];
    cswap >>= 8;
    T[i] <<= 8;
    T[i++] |= cswap;
    }
  return SUCCESS;
  }         /* swap */

SHORT send_exc_response(code, cmdbuff, repbuff)
   /* Send exception response */
SHORT code;       /* Error  code */
/*
 * Error Codes:
 * 1     Illegal Function
 * 2     Illegal Data Address
 * 3     Illegal Data Value
 * 4     Failure in associated device
 * 5     Acknowledge
 * 6     Busy, rejected message
 * 7     Nak-Negative acknowledgement
 */
UCHAR repbuff[], cmdbuff[];
  {         /* send_exc_response */
  cmdbuff[0] = (UCHAR) repbuff[0];
                        /* byte 1 is our slave # */
  cmdbuff[1] = (UCHAR) repbuff[1] | 0x80;
                        /* byte 2 is function code plus exception bit */
  cmdbuff[2] = (UCHAR) code;  /* Error code */
  slave_wcmd(3, cmdbuff);
                        /* transmit response */
  return SUCCESS;
  }         /* send_exc_response */



/*
procedure back_dbwrite()

This procedure writes to the database structure which is contained in
memory.

back_dbwrite()
{
    if( offset + quantity is not within boundary of database )
        return( error );
    move data from buffer into database;
    return( good status );

}
*/

SHORT back_dbwrite(offset, buffer, quantity)
SHORT offset;
PUSHORT buffer;
SHORT quantity;
  {
  long db_size;   /*size of database in words*/

  if (quantity > 255)
    return (RETURN_BAD_STATUS); /*return error*/

  db_size = (sizeof (this_database) * 2L);
                        /*get size in words (8 words/paragraph)*/

  if (((long) offset + (long) quantity) > db_size)
    return (RETURN_BAD_STATUS); /*not entirely within boundaries of database*/

  memmove((UCHAR *) this_database + (offset * 2), buffer, quantity * 2);

  return (RETURN_GOOD_STATUS);  /*return good status*/
  }


/*
procedure back_dbread()

This procedure reads from the database structure which is contained in
memory.

back_dbread()
{
    if( offset + quantity is not within boundary of database )
        return( error );
    move data from database into buffer;
    return( good status );

}
*/

SHORT back_dbread(offset, buffer, quantity)
SHORT offset;
PUSHORT buffer;
SHORT quantity;
  {
  long db_size;   /*size of database in words*/

  if (quantity > 255)
    return (RETURN_BAD_STATUS); /*return error*/

  db_size = (sizeof (this_database) * 2L);
                        /*get size in words (8 words/paragraph)*/

  if (((long) offset + (long) quantity) > db_size)
    return (RETURN_BAD_STATUS); /*not entirely within boundaries of database*/

  memmove(buffer, (UCHAR *) this_database + (offset * 2), quantity * 2);

  return (RETURN_GOOD_STATUS);  /*return good status*/
  }


/*************************************************************************
* slave_wcmd  write given sequence of command bytes to slave network, and append
*       appropriate type of checksum (LRC or CRC) to the message end.
*************************************************************************/

SHORT slave_wcmd(nbytes, cmdbuff)
SHORT nbytes;       /* number of bytes to write     */
UCHAR cmdbuff[];  /* buffer of bytes to send      */
  {


  if (ncb_send(data_ncbp, nbytes, cmdbuff, 30) != 0) {
                        /*send the command*/
    printf("Send error: %d.\n", data_ncbp->NCB_RETCODE);
    return 0x204; /* "failure in associated device" */
    }
  else 
    return 0;

  }

/*************************************************************************
* slave_rrep  read slave reply 
*************************************************************************/

SHORT slave_rrep(n_bytes, repbuff)
SHORT *n_bytes;     /* # of data bytes (ex chksum) read by rrep */
UCHAR repbuff[];  /* buffer to store the reply    */
  {

  *n_bytes = 0;   /* initially set n_bytes to 0 */

  if (ncb_receive_wait(data_ncbp, repbuff, 30) != 0) {
                        /*try to receive*/
    *n_bytes = 0;
    return 0x204; /* "failure in associated device" */
    }
  else {
    *n_bytes = data_ncbp->NCB_LENGTH;
    return 0;
    }
  }

/*************************************************************************
* hi    returns high order byte (in low order bits) of argument
*************************************************************************/

SHORT hi(arg)
USHORT arg;
  {
  return ((arg & 0xFF00) >> 8);
  }

/*************************************************************************
* lo    returns low order byte (in low order bits) of argument
*************************************************************************/

SHORT lo(arg)
USHORT arg;
  {
  return (arg & 0xFF);
  }



/*
control_c


This routine replaces the control-C handler supplied by DOS.  When you
type a control_C, the program will vector to this routine, which will
set the completed flag to non-zero.  In this test program, this will cause
the infinite loop within main to terminate.
*/
void control_c(int sig)
{
    signal( SIGINT, SIG_IGN );          /* disable control-c */
    completed = 1;                      /* will cause main loop to complete */
    signal( SIGINT, control_c );        /* reset control-c handler */
}

/*********************** end of testslav.c ************************/