/* The lines with '//*** (XX)' are comments that point out the changes that may be needed to make a new C component from the gs2_vfd.c file. Much of the original file has been snipped to make this document shorter. Copy gs2_vfd.c to its own directory, such as ~/emc2/my_new_mbdevice/ . Rename and edit the file. Copy the Makefile from the vfs11_vfd.c source directory to this new device directory and edit to replace the vfs11_vfd name to the new device name. Compile and install with the command "make && sudo make install". - Kirk Wallace 20011/03/27 */ /* //*** (1) Put new component name here Based on a work (test-modbus program, part of libmodbus) which is Copyright (C) 2001-2005 Stéphane Raimbault <stephane.raimbault@free.fr> This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 2. ... -s or --stopbits {1,2} (default 1) Set serial stop bits to 1 or 2 -t or --target <n> (default 1) Set MODBUS target (slave) number. This must match the device number you set on the GS2. -v or --verbose Turn on debug messages. Note that if there are serial errors, this may become annoying. At the moment, it doesn't make much difference most of the time. */ #include <stdio.h> #include <unistd.h> #include <time.h> .... #include "hal.h" #include "modbus.h" //*** (2) Document and describe new register sets /* Read Registers: 0x2100 = status word 1 0x2101 = status word 2 ... 0x210C = Firmware revision (never saw anything other than 0 here) total of 13 registers */ #define START_REGISTER_R 0x2100 //*** (2) Enter new Modbus slave register start address here #define NUM_REGISTERS_R 13 //*** (2) Enter the new number of registers /* Write registers: 0x91A = Speed reference, in 1/10Hz increments ... 0x91E = serial fault reset, 0=no reset, 1 = reset fault total of 5 registers */ #define START_REGISTER_W 0x091A #define NUM_REGISTERS_W 5 //*** (2) If there are more register sets, add them as needed #undef DEBUG //#define DEBUG //*** (3) Add more variable declarations as needed to match the defines above /* modbus slave data struct */ typedef struct { int slave; /* slave address */ int read_reg_start; /* starting read register number */ int read_reg_count; /* number of registers to read */ int write_reg_start; /* starting write register number */ int write_reg_count; /* number of registers to write */ } slavedata_t; //*** (4) declare pointers needed to create HAL pins (see 9 below). //*** and declare associated variables if needed for data conditioning /* HAL data struct */ typedef struct { hal_s32_t *stat1 // status words from the VFD. Maybe split these out sometime hal_s32_t *stat2; .... hal_bit_t *err_reset; // reset errors when 1 hal_s32_t ack_delay; // number of read/writes before checking at-speed hal_bit_t old_run; // so we can detect changes in the run state hal_bit_t old_dir; hal_bit_t old_err_reset; } haldata_t; static int done; //*** (5) Enter new name. This is the base name for HAL pins and loadusr command char *modname = "gs2_vfd"; static struct option long_options[] = { {"bits", 1, 0, 'b'}, {"device", 1, 0, 'd'}, {"debug", 0, 0, 'g'}, {"help", 0, 0, 'h'}, {"name", 1, 0, 'n'}, {"parity", 1, 0, 'p'}, {"rate", 1, 0, 'r'}, {"stopbits", 1, 0, 's'}, {"target", 1, 0, 't'}, {"verbose", 0, 0, 'v'}, {0,0,0,0} }; static char *option_string = "b:d:hn:p:r:s:t:v"; static char *bitstrings[] = {"5", "6", "7", "8", NULL}; static char *paritystrings[] = {"even", "odd", "none", NULL}; static char *ratestrings[] = {"110", "300", "600", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200", NULL}; static char *stopstrings[] = {"1", "2", NULL}; static void quit(int sig) { done = 1; } //*** (6) This line is for a particular feature of this application and may not be needed static int comm_delay = 0; // JET delay counter for at-speed int match_string(char *string, char **matches) { int len, which, match; which=0; match=-1; if ((matches==NULL) || (string==NULL)) return -1; len = strlen(string); while (matches[which] != NULL) { if ((!strncmp(string, matches[which], len)) && (len <= strlen(matches[which]))) { if (match>=0) return -1; // multiple matches match=which; } ++which; } return match; } //*** (7) Much of this function is data conditioning which may not apply //*** to any another application. The heart of the function is the call of one //*** or more Modbus write functions, such as shown below 'preset_single_register'. //*** Other functions are for writing to coils, multiple registers, etc. int write_data(modbus_param_t *param, slavedata_t *slavedata, haldata_t *haldata) { // int write_data[MAX_WRITE_REGS]; int retval; hal_float_t hzcalc; if (haldata->motor_hz<10) haldata->motor_hz = 60; if ((haldata->motor_RPM < 600) || (haldata->motor_RPM > 5000)) haldata->motor_RPM = 1800; hzcalc = haldata->motor_hz/haldata->motor_RPM; retval=preset_single_register(param, slavedata->slave, slavedata->write_reg_start, abs((int)(*(haldata->speed_command)*hzcalc*10))); ... if (*(haldata->spindle_on)==0){ // JET reset at-speed *(haldata->at_speed) = 0; } haldata->retval = retval; return retval; } void usage(int argc, char **argv) { printf("Usage: %s [options]\n", argv[0]); printf( "This is a userspace HAL program, typically loaded using the halcmd \"loadusr\" command:\n" " loadusr gs2_vfd\n" ... "-v or --verbose\n" " Turn on debug messages. Note that if there are serial errors, this may become annoying.\n" " At the moment, it doesn't make much difference most of the time.\n"); } //*** (6) Much of this function is data conditioning which may not apply //*** to any another application. The heart of the function is the call of one //*** or more Modbus read functions, such as shown below 'read_holding_registers'. //*** Other functions are for reading coils, input registers, etc. int read_data(modbus_param_t *param, slavedata_t *slavedata, haldata_t *hal_data_block) { int receive_data[MAX_READ_HOLD_REGS]; /* a little padding in there */ int retval; /* can't do anything with a null HAL data block */ if (hal_data_block == NULL) return -1; /* but we can signal an error if the other params are null */ if ((param==NULL) || (slavedata == NULL)) { hal_data_block->errorcount++; return -1; } retval = read_holding_registers(param, slavedata->slave, slavedata->read_reg_start, slavedata->read_reg_count, receive_data); if (retval==slavedata->read_reg_count) { ... } } else { hal_data_block->retval = retval; hal_data_block->errorcount++; retval = -1; } return retval; } int main(int argc, char **argv) { int retval; modbus_param_t mb_param; haldata_t *haldata; slavedata_t slavedata; int hal_comp_id; struct timespec loop_timespec, remaining; int baud, bits, stopbits, debug, verbose; char *device, *parity, *endarg; int opt; int argindex, argvalue; done = 0; //*** (7) The following may be changed to match the best defaults for the new application // assume that nothing is specified on the command line baud = 38400; bits = 8; stopbits = 1; debug = 0; verbose = 0; device = "/dev/ttyS0"; parity = "odd"; //*** (8) The following needs to match the above defines and declarations /* slave / register info */ slavedata.slave = 1; slavedata.read_reg_start = START_REGISTER_R; slavedata.read_reg_count = NUM_REGISTERS_R; slavedata.write_reg_start = START_REGISTER_W; slavedata.write_reg_count = NUM_REGISTERS_R; // process command line options while ((opt=getopt_long(argc, argv, option_string, long_options, NULL)) != -1) { switch(opt) { case 'b': // serial data bits, probably should be 8 (and defaults to 8) argindex=match_string(optarg, bitstrings); if (argindex<0) { printf("gs2_vfd: ERROR: invalid number of bits: %s\n", optarg); ... case 'h': default: usage(argc, argv); exit(0); break; } } printf("%s: device='%s', baud=%d, bits=%d, parity='%s', stopbits=%d, address=%d, verbose=%d\n", modname, device, baud, bits, parity, stopbits, slavedata.slave, debug); /* point TERM and INT signals at our quit function */ /* if a signal is received between here and the main loop, it should prevent some initialization from happening */ signal(SIGINT, quit); signal(SIGTERM, quit); /* Assume 38.4k O-8-1 serial settings, device 1 */ modbus_init_rtu(&mb_param, device, baud, parity, bits, stopbits, verbose); mb_param.debug = debug; /* the open has got to work, or we're out of business */ if (((retval = modbus_connect(&mb_param))!=0) || done) { printf("%s: ERROR: couldn't open serial device\n", modname); goto out_noclose; } /* create HAL component */ hal_comp_id = hal_init(modname); if ((hal_comp_id < 0) || done) { printf("%s: ERROR: hal_init failed\n", modname); retval = hal_comp_id; goto out_close; } /* grab some shmem to store the HAL data in */ haldata = (haldata_t *)hal_malloc(sizeof(haldata_t)); if ((haldata == 0) || done) { printf("%s: ERROR: unable to allocate shared memory\n", modname); retval = -1; goto out_close; } //*** (9) Define the new HAL pins here retval = hal_pin_s32_newf(HAL_OUT, &(haldata->stat1), hal_comp_id, "%s.status-1", modname); if (retval!=0) goto out_closeHAL; ... retval = hal_param_s32_newf(HAL_RW, &(haldata->ack_delay), hal_comp_id, "%s.ack-delay", modname); if (retval!=0) goto out_closeHAL; //*** (10) If the pin variables need default values put the value for the new ones here /* make default data match what we expect to use */ *(haldata->stat1) = 0; *(haldata->stat2) = 0; ... haldata->old_err_reset = -1; hal_ready(hal_comp_id); //*** (11) Most applications will read and write registers so from here down will stay unchanged /* here's the meat of the program. loop until done (which may be never) */ while (done==0) { read_data(&mb_param, &slavedata, haldata); write_data(&mb_param, &slavedata, haldata); /* don't want to scan too fast, and shouldn't delay more than a few seconds */ if (haldata->looptime < 0.001) haldata->looptime = 0.001; if (haldata->looptime > 2.0) haldata->looptime = 2.0; loop_timespec.tv_sec = (time_t)(haldata->looptime); loop_timespec.tv_nsec = (long)((haldata->looptime - loop_timespec.tv_sec) * 1000000000l); nanosleep(&loop_timespec, &remaining); } retval = 0; /* if we get here, then everything is fine, so just clean up and exit */ out_closeHAL: hal_exit(hal_comp_id); out_close: modbus_close(&mb_param); out_noclose: return retval; }