Blame | Last modification | View Log | Download
/*name testslav.c*//* Copyright (C) Modicon, Inc. 1989, All Rights Reserved. *//*testslav.cThis 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 thefirst SA85 board installed in the system. When a modbus message isreceived from this path, this program will interpret the command, andgenerate 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]);elseprintf("+");}/** 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;}elsereturn 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 inmemory.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 inmemory.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" */}elsereturn 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_cThis routine replaces the control-C handler supplied by DOS. When youtype a control_C, the program will vector to this routine, which willset the completed flag to non-zero. In this test program, this will causethe 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 ************************/