diff options
author | Michael Haberler <git@mah.priv.at> | 2012-02-27 10:48:50 +0100 |
---|---|---|
committer | Michael Haberler <git@mah.priv.at> | 2012-02-27 10:48:50 +0100 |
commit | a463a350b39492f2561cdcd212b4ef1263817760 (patch) | |
tree | 3b524118c45009ecb68778ba533bacca285cbee2 | |
parent | 4107acf93c0fd28a00965c672840a3bcec2689d6 (diff) | |
download | linuxcnc-a463a350b39492f2561cdcd212b4ef1263817760.tar.gz linuxcnc-a463a350b39492f2561cdcd212b4ef1263817760.zip |
hal/user_comps: vfs11_vfd driver for Toshiba VF-S11
This driver is built conditionally subject to libmodbus version 3
installed.
-rw-r--r-- | src/Makefile | 2 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/Submakefile | 17 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/dump-params.mio | 99 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/vfs11-all-options.ini | 58 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/vfs11-rs232.ini | 25 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/vfs11-rs485.ini | 34 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/vfs11-tcp-client.ini | 16 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/vfs11-tcp-server.ini | 13 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/vfs11_vfd.c | 1123 | ||||
-rw-r--r-- | src/hal/user_comps/vfs11_vfd/vfs11_vfd.hal | 30 |
10 files changed, 1416 insertions, 1 deletions
diff --git a/src/Makefile b/src/Makefile index b61c6a567..fa5250cfb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -102,7 +102,7 @@ SUBDIRS := \ rtapi/examples/extint rtapi/examples/fifo rtapi/examples rtapi \ \ hal/components hal/drivers hal/user_comps/devices \ - hal/user_comps hal/user_comps/vismach hal/classicladder hal/utils hal \ + hal/user_comps hal/user_comps/vismach hal/user_comps/vfs11_vfd hal/classicladder hal/utils hal \ \ emc/usr_intf/axis emc/usr_intf/touchy emc/usr_intf/stepconf emc/usr_intf/pncconf \ emc/usr_intf/gremlin \ diff --git a/src/hal/user_comps/vfs11_vfd/Submakefile b/src/hal/user_comps/vfs11_vfd/Submakefile new file mode 100644 index 000000000..3828e82e7 --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/Submakefile @@ -0,0 +1,17 @@ + +ifeq ($(LIBMODBUS3_USABLE),yes) + +VFS11_SRCS = hal/user_comps/vfs11_vfd/vfs11_vfd.c +VFS11_CFLAGS = -DDEBUG $(LIBMODBUS_CFLAGS) +VFS11_LIBS = $(LIBMODBUS_LIBS) + +$(call TOOBJSDEPS, $(VFS11_SRCS)) : EXTRAFLAGS += $(VFS11_CFLAGS) + +USERSRCS += $(VFS11_SRCS) +../bin/vfs11_vfd: $(call TOOBJS, $(VFS11_SRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.0 + $(ECHO) Linking $(notdir $@) + $(Q)$(CC) $(LDFLAGS) $(VFS11_LIBS) -o $@ $^ + +TARGETS += ../bin/vfs11_vfd +endif + diff --git a/src/hal/user_comps/vfs11_vfd/dump-params.mio b/src/hal/user_comps/vfs11_vfd/dump-params.mio new file mode 100644 index 000000000..5cfb7eed8 --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/dump-params.mio @@ -0,0 +1,99 @@ +# readout of all relevant VF-S11 parameters via modbus +# +# I setup my VFD as follows: +# - factory reset by choosing 'typ = 3' (full operations manual section 7.3) +# - set to european defaults 'typ = 1' (mostly switching 60->50Hz) +# +# the I set parameters below +# you can do this on the panel or with the PCS0012 windows utility from Toshiba. +# +# The idea is that by default the panel is enabled +# when modbus controls starts it takes over control with the control/frequency override +# in register FA00 +# when modbus quits, these override bits should be reset in the FA00 register +# or, even better, send a fault reset +# +# ---- reading the parameter values: +# run this file as: +# modio <dump-params.mio >dump-myparams.log +# +# first, do a fault reset +# +debug 0 +# +# if the VFD isnt faulted, this seems a no-op +# if it is faulted, it goes through the startup sequence +preset_single_register 0xfa00 0x2000 +# +# let her settle in case she rebooted +sleep 3 +# +# operated by panel, expect 0001 +read_holding_registers 0x0003 +# +# frequency by panel possible, expect 0000 +read_holding_registers 0x0004 +# +# meter selection, as you like - not important; I use power +# in which case expect 0006 +read_holding_registers 0x0005 +# +# fwd/reverse possible by panel, expect 0002 +read_holding_registers 0x0008 +# +# base frequency - europeans expect 1388 (5000 centiHertz) +read_holding_registers 0x0170 +# +# I think this one's irrelevant, should also be 1388 (1770 for The Colonies) +read_holding_registers 0x0204 +# +# frequency mode selection = potentiometer for panel operation +# with modbus we use the command/frequency override bits in FA00 +# expect 0000 +read_holding_registers 0x0207 +# +# I think this one's irrelevant too, should also be 1388 (1770 for The Colonies) +read_holding_registers 0x0213 +# +# supply voltage correction - duh. expect 0002 +read_holding_registers 0x0307 +# +# the motor nameplate RPM value - my motor reads 1410 rpm +# so I expect 0582 - YMMV +read_holding_registers 0x0417 +# +# I think this one is important: +# change of command/frequency override during operation must be permitted!!! +# so expect 0000: +read_holding_registers 0x0736 +# +# port speed = 19200 - expect 0004 +read_holding_registers 0x0800 +# +# I set the inverter number to 1 +# so expect 0001 +read_holding_registers 0x0802 +# +# this one I set to 0 to disable Modbus timeouts by the VFD +# there is no point in having the VFD timeout in our setup - +# the host will notice (or the user that the PC is gone..) +# expect 0000 +read_holding_registers 0x0803 +# +# likely irrelevant (50/60Hz so expect 1388 or 1770) +read_holding_registers 0x0814 +# +# mucho importante: select protocol to modbus +# so expect to definitely, positively see 0001 +# (otherwise this program wouldnt work in the first place..) +read_holding_registers 0x0829 +# +# I also set shorter acceleration and deceleration times, +# 10 secs is a bitread_holding_registers 0x0814 +# I set these to 3 secs so expect 001e in this case: +read_holding_registers 0x0009 +read_holding_registers 0x0010 +# +# done! + + diff --git a/src/hal/user_comps/vfs11_vfd/vfs11-all-options.ini b/src/hal/user_comps/vfs11_vfd/vfs11-all-options.ini new file mode 100644 index 000000000..856228fc1 --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/vfs11-all-options.ini @@ -0,0 +1,58 @@ +# this lists all options understood by vfs11_vfd +# configure before use + +[VFS11] +# serial connection +#TYPE=rtu + +# serial port +#DEVICE=/dev/ttyS0 + +# TCP server - wait for incoming connection +#TYPE=tcpserver +# tcp portnumber to listen on if TYPE=tcp +#PORT=1502 + +# TCP client - active outgoing connection +#TYPE=tcpclient + +# destination to connect to if TYPE=tcpclient +TCPDEST=192.168.1.1 + +# serial device detail if TYPE=rtu +# 5 6 7 8 +BITS= 5 + +# even odd none +PARITY=none + +# 110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 +BAUD=19200 + +# 1 2 +STOPBITS=1 + +#rs232 rs485 +SERIAL_MODE=rs485 + +# up down none +RTS_MODE=up + +# modbus timers in seconds +# inter-character timer +BYTE_TIMEOUT=0.5 +# packet timer +RESPONSE_TIMEOUT=0.5 + +# target modbus ID +TARGET=1 + +# on I/O failure, try to reconnect after sleeping +# for RECONNECT_DELAY seconds +RECONNECT_DELAY=1 + +# misc flags +DEBUG=10 +MODBUS_DEBUG=0 +POLLCYCLES=10 + diff --git a/src/hal/user_comps/vfs11_vfd/vfs11-rs232.ini b/src/hal/user_comps/vfs11_vfd/vfs11-rs232.ini new file mode 100644 index 000000000..1377c8ec2 --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/vfs11-rs232.ini @@ -0,0 +1,25 @@ +[VFS11] +# serial connection +TYPE=rtu + +# serial port +DEVICE=/dev/ttyS0 + +# 5 6 7 8 +BITS= 8 + +# even odd none +PARITY=even + +# 110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 +BAUD=19200 + +# 1 2 +STOPBITS=1 + +#rs232 rs485 +SERIAL_MODE=rs232 + +# target modbus ID (slave) +TARGET=1 + diff --git a/src/hal/user_comps/vfs11_vfd/vfs11-rs485.ini b/src/hal/user_comps/vfs11_vfd/vfs11-rs485.ini new file mode 100644 index 000000000..b3f87bbcf --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/vfs11-rs485.ini @@ -0,0 +1,34 @@ +# example configuration for RS485 bus, half-duplex, RTS control + +[VFS11] +# serial connection +TYPE=rtu + +# serial port +DEVICE=/dev/ttyS0 + +# serial device detail if TYPE=rtu +# 5 6 7 8 +BITS= 8 + +# even odd none +PARITY=even + +# 110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 +BAUD=19200 + +# 1 2 +STOPBITS=1 + +#rs232 rs485 +SERIAL_MODE=rs485 + +# RS485 direction control +# none: assume RS485 is full-duplex +# up: assert RTS before writing to modbus +# down: deassert RTS before writing to modbus, active during read +# see http://www.libmodbus.org/site_media/html/modbus_rtu_set_rts.html +RTS_MODE=up + +# target modbus ID +TARGET=1 diff --git a/src/hal/user_comps/vfs11_vfd/vfs11-tcp-client.ini b/src/hal/user_comps/vfs11_vfd/vfs11-tcp-client.ini new file mode 100644 index 000000000..447009a6f --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/vfs11-tcp-client.ini @@ -0,0 +1,16 @@ +# example for active outgoing connection + +[VFS11] +# TCP client - actively connect to TCPDEST:PORT +TYPE=tcpclient + +# tcp portnumber for connect +PORT=1502 + +# destination IP address to connect to +TCPDEST=192.168.1.1 + +# target modbus ID +TARGET=1 + + diff --git a/src/hal/user_comps/vfs11_vfd/vfs11-tcp-server.ini b/src/hal/user_comps/vfs11_vfd/vfs11-tcp-server.ini new file mode 100644 index 000000000..5b33a3a26 --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/vfs11-tcp-server.ini @@ -0,0 +1,13 @@ +# example TCP server configuration +# waits for incoming connection requests + +[VFS11] +# TCP server - wait for incoming connection +TYPE=tcpserver + +# tcp portnumber to listen on if TYPE=tcp +PORT=1502 + +# target modbus ID +TARGET=1 + diff --git a/src/hal/user_comps/vfs11_vfd/vfs11_vfd.c b/src/hal/user_comps/vfs11_vfd/vfs11_vfd.c new file mode 100644 index 000000000..788d50c7d --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/vfs11_vfd.c @@ -0,0 +1,1123 @@ +/* + vfs11_vfd.c + + userspace HAL program to control a Toshiba VF-S11 VFD + + Michael Haberler, adapted from Steve Padnos' gs2_vfd.c, + including modifications from John Thornton (jet1024 AT semo DOT net) + + Copyright (C) 2007, 2008 Stephen Wille Padnos, Thoth Systems, Inc. + Copyright (C) 2009,2010,2011,2012 Michael Haberler + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + + see 'man vfs11_vfd' and the VFS11 section in the Drivers manual. + + Add is-stopped pin John Thornton + +*/ + +#ifndef ULAPI +#error This is intended as a userspace component only. +#endif + +#ifdef DEBUG +#define DBG(fmt, ...) \ + do { \ + if (param.debug) printf(fmt, ## __VA_ARGS__); \ + } while(0) +#else +#define DBG(fmt, ...) +#endif + +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <getopt.h> +#include <math.h> +#include <signal.h> +#include <stdarg.h> + +#include "rtapi.h" +#include "hal.h" +#include <modbus.h> +#include <modbus-tcp.h> +#include "inifile.h" + +/* + * VFS-11 parameters: + * + * There are dozens of parameters. Some can be stored permanently in EEPROM (setup parameters), + * some in RAM (operating paramters), and some can be stored both in EEPROM and RAM. The manual + * is a bit unclear which parameters are RAM/EEPROM/both. + * + * There are two communication protocols to talk to the VF-S11, a proprietary but documented + * "Toshiba Inverter Protocol" (TIP), and a simple Modbus subset. TIP can set EEPROM and RAM + * parameters and hence can be used for initial inverter configuration. Modbus control can only + * set operating paramters in RAM. So any setup parameters which you'd like to change (like, + * e.g. maximum output frequency) need to be set up differently, either through the operating + * panel, or through a Windows program supplied by Toshiba named PCS001Z. + * + * Before using this driver, you need at least change the communication protocol from + * TIP (default) to Modbus either way. + * + * Note from TOSVERT VF-S11 Communications Function Instruction Manual: + * + * The EEPROM life is 10,000 operations. + * Do not write in the same parameter that has an EEPROM more than 10,000 times. + * The communication commands (FA00, FA20, FA26), communication frequency command (FA01), + * terminal output data (FA50) and analog output data (FA50) are stored in the RAMs only and no re- + * strictions are placed on them. + * + * NB: "analog output data (FA50)" is obviously a typo in the manual, it's really FA51 + */ + +// VF-S11 registers +// command registers +#define REG_COMMAND1 0xFA00 // "Communication command" - start/stop, fwd/reverse, DC break, fault reset, panel override +#define REG_COMMAND2 0xFA20 +#define REG_COMMAND3 0xFA26 +#define REG_FREQUENCY 0xFA01 // Set frequency in 0.01Hz steps +#define REG_TERMINAL_OUTPUT 0xFA50 +#define REG_ANALOG_OUTPUT 0xFA51 +#define REG_UPPERLIMIT 0x0012 // limit on output frequency in VFD + +// bits in register FA00 - main command register +#define CMD_COMMAND_PRIORITY 0x8000 +#define CMD_FREQUENCY_PRIORITY 0x4000 +#define CMD_FAULT_RESET 0x2000 +#define CMD_EMERGENCY_STOP 0x1000 +#define CMD_COAST_STOP 0x0800 +#define CMD_RUN 0x0400 +#define CMD_REVERSE 0x0200 +#define CMD_JOG_RUN 0x0100 +#define CMD_DC_BRAKE 0x0080 +#define CMD_ACCEL_PATTERN_2 0x0040 +#define CMD_DISABLE_PI_CONTROL 0x0020 +#define CMD_SELECT_MOTOR1_2 0x0010 +#define CMD_SPEED_PRESET1 0x0008 +#define CMD_SPEED_PRESET2 0x0004 +#define CMD_SPEED_PRESET3 0x0002 +#define CMD_SPEED_PRESET4 0x0001 + +// status registers +// the _T suffixed denotes the same layout as the previous register +// but has the status before a trip occured +#define SR_OP_FREQUENCY 0xFD00 // 0.01Hz units +#define SR_OP_FREQUENCY_T 0xFE00 +#define SR_INV_OPSTATUS 0xFD01 // main status register, bits in ST_* below +#define SR_INV_OPSTATUS_T 0xFE01 +#define SR_INV_OPSTATUS3 0xFD42 +#define SR_INV_OPSTATUS3_T 0xFE42 +#define SR_INV_OPSTATUS4 0xFD49 +#define SR_INV_OPSTATUS4_T 0xFE49 +#define SR_INV_OP_CMD_STATUS 0xFE45 +#define SR_INV_FREQ_STATUS 0xFE46 +#define SR_ALARM_MONITOR 0xFC91 // bitmap, bits on AM_ +#define SR_CUMULATIVE_ALARMS 0xFE79 +#define SR_TRIPCODE 0xFC90 // current trip code +#define SR_TRIPCODE_PAST1 0xFE10 // last 4 trips +#define SR_TRIPCODE_PAST2 0xFE11 +#define SR_TRIPCODE_PAST3 0xFE12 +#define SR_TRIPCODE_PAST4 0xFE13 +#define SR_INVERTER_MODEL 0xFB05 + +#define SR_RATED_CURRENT 0xFE70 // 0.1A +#define SR_RATED_VOLTAGE 0xFE71 // 0.1V +#define SR_CPU1_VERSION 0xFE08 +#define SR_EEPROM_VERSION 0xFE09 +#define SR_CPU2_VERSION 0xFE73 + +#define SR_ESTIMATED_OPFREQ 0xFE16 // 0.01Hz +#define SR_INV_LOADFACTOR 0xFE27 // % +#define SR_LOADCURRENT 0xFE03 // % +#define SR_OUTPUT_VOLTAGE 0xFE05 // % + +// Alarm monitor bits +#define AM_OVERCURRENT 0x0001 +#define AM_INVERTER_OVERLOAD 0x0002 +#define AM_MOTOR_OVERLOAD 0x0004 +#define AM_OVERHEAT 0x0008 +#define AM_OVERVOLTAGE 0x0010 +#define AM_MAIN_UNDERVOLTAGE 0x0020 +#define AM_RESERVED1 0x0040 +#define AM_LOW_CURRENT 0x0080 +#define AM_OVER_TORQUE 0x0100 +#define AM_BRAKERESISTOR_OVLD 0x0200 +#define AM_CUM_OP_HOURS 0x0400 +#define AM_RESERVED2 0x0800 +#define AM_RESERVED3 0x1000 +#define AM_MAIN_VOLTAGE 0x2000 +#define AM_BLACKOUT_STOP 0x4000 +#define AM_AUTOSTOP 0x8000 + +// bits in FD01 - main status register +#define ST_RESERVED1 0x8000 +#define ST_STANDBY 0x4000 +#define ST_STANDBY_STON 0x2000 +#define ST_EMERGENCY_STOPPED 0x1000 +#define ST_COAST_STOPPED 0x0800 +#define ST_RUNNING 0x0400 +#define ST_REVERSE 0x0200 +#define ST_JOG_RUN 0x0100 +#define ST_DC_BRAKING 0x0080 +#define ST_ACCEL_PATTERN_2 0x0040 +#define ST_PI_CONTROL_DISABLED 0x0020 +#define ST_MOTOR2_SELECTED 0x0010 +#define ST_RESERVED2 0x0008 +#define ST_ALARMED 0x0004 +#define ST_TRIPPED 0x0002 +#define ST_FAILURE_FL 0x0001 + + +/* There's an assumption in the gs2_vfd code, namely that the interesting registers + * are contiguous and all of them can be read with a single read_holding_registers() + * operation. + * + * However, the interesting VF-S11 registers are not contiguous, and must be read + * one-by-one, because the Toshiba Modbus implementation only supports single-value + * modbus_read_registers() queries, slowing things down considerably. It seems that + * other VFD's have similar restrictions. + * + * Then, not all registers are equally important. We would like to read the + * VFD status and actual frequency on every Modbus turnaround, but there is no need to + * the read CPU version and inverter model more than once at startup, and the load factor etc + * every so often. + */ +#define POLLCYCLES 10 // read less important parameters only on every 10th transaction +#define MODBUS_MIN_OK 10 // assert the modbus-ok pin after 10 successful modbus transactions +#define MAX_RPM 2000 // cap output RPM + + +/* HAL data struct */ +typedef struct { + hal_s32_t *status; + hal_float_t *freq_cmd; // frequency command + hal_float_t *freq_out; // actual output frequency + hal_float_t *curr_out_pct; // output current percentage (base unclear) + hal_float_t *outV_pct; // output voltage percent + hal_float_t *RPM; + hal_float_t *inv_load_pct; + hal_float_t *load_current_pct; + hal_float_t *max_rpm; // calculated based on VFD max frequency setup parameter + hal_s32_t *trip_code; + hal_s32_t *alarm_code; + hal_bit_t *at_speed; // when drive freq_cmd == freq_out and running + hal_bit_t *is_stopped; // when drive freq out is 0 + hal_bit_t *estop; // set estop bit in 0xFA00 - causes 'E trip' + hal_bit_t *is_e_stopped; // true if emergency stop status set in 0xFD00 + hal_bit_t *modbus_ok; // the last MODBUS_OK transactions returned successfully + hal_float_t *speed_command; // speed command input + + hal_bit_t *spindle_on; // spindle 1=on, 0=off + hal_bit_t *DC_brake; // setting this will turn off the spindle and engage the DC brake + hal_bit_t *spindle_fwd; // direction, 0=fwd, 1=rev + hal_bit_t *spindle_rev; // on when in rev and running + hal_bit_t *err_reset; // reset errors when 1 - set fault reset bit in 0xFA00 + hal_bit_t *jog_mode; // termed 'jog-run' in manual - might be useful for spindle positioning + hal_s32_t *errorcount; // number of failed Modbus transactions - hints at logical errors + + hal_float_t looptime; + hal_float_t speed_tolerance; + hal_float_t motor_nameplate_hz; // speeds are scaled in Hz, not RPM + hal_float_t motor_nameplate_RPM; // nameplate RPM at default Hz + hal_float_t rpm_limit; // do-not-exceed output frequency + hal_bit_t *acc_dec_pattern; // if set: choose ramp times as defined in F500+F501 + // if zero (default): choose ramp times as defined in ACC and DEC + hal_bit_t *enabled; // if set: control VFD via Modbus commands, panel control disabled + // if zero (default): manual control through panel enabled + hal_float_t *upper_limit_hz; // VFD setup parameter - maximum output frequency in HZ + + hal_bit_t *max_speed; // 1: run as fast as possible, ignore unimportant registers + // link this to spindle.orient-enable for better orient PID loop behaviour +} haldata_t; + +// configuration and execution state +typedef struct params { + int type; + char *modname; + int modbus_debug; + int debug; + int slave; + int pollcycles; + char *device; + int baud; + int bits; + char parity; + int stopbits; + int rts_mode; + int serial_mode; + struct timeval response_timeout; + struct timeval byte_timeout; + int tcp_portno; + char *progname; + char *section; + FILE *fp; + char *inifile; + int reconnect_delay; + modbus_t *ctx; + haldata_t *haldata; + int hal_comp_id; + int read_initial_done; + int old_err_reset; + uint16_t old_cmd1_reg; // copy of last write to FA00 */ + int modbus_ok; + uint16_t failed_reg; // remember register for failed modbus transaction for debugging + int last_errno; + char *tcp_destip; + int report_device; +} params_type, *param_pointer; + +#define TYPE_RTU 0 +#define TYPE_TCP_SERVER 1 +#define TYPE_TCP_CLIENT 2 + +// default options; read from inifile or command line +static params_type param = { + .type = -1, + .modname = NULL, + .modbus_debug = 0, + .debug = 0, + .slave = 1, + .pollcycles = POLLCYCLES, + .device = "/dev/ttyS0", + .baud = 19200, + .bits = 8, + .parity = 'E', + .stopbits = 1, + .serial_mode = -1, + .rts_mode = -1, + .response_timeout = { .tv_sec = 0, .tv_usec = 500000 }, + .byte_timeout = {.tv_sec = 0, .tv_usec = 500000}, + .tcp_portno = 1502, // MODBUS_TCP_DEFAULT_PORT (502) would require root privileges + .progname = "vfs11_vfd", + .section = "VFS11", + .fp = NULL, + .inifile = NULL, + .reconnect_delay = 1, + .ctx = NULL, + .haldata = NULL, + .hal_comp_id = -1, + .read_initial_done = 0, + .old_err_reset = 0, + .old_cmd1_reg = 0, + .modbus_ok = 0, // set modbus-ok bit if last MODBUS_OK transactions went well + .failed_reg =0, + .last_errno = 0, + .tcp_destip = "127.0.0.1", + .report_device = 0, +}; + + +static int connection_state; +enum connstate {NOT_CONNECTED, OPENING, CONNECTING, CONNECTED, RECOVER, DONE}; + +static char *option_string = "dhrMn:S:I:"; +static struct option long_options[] = { + {"debug", no_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {"modbus-debug", no_argument, 0, 'm'}, + {"report-device", no_argument, 0, 'r'}, + {"ini", required_argument, 0, 'I'}, // default: getenv(INI_FILE_NAME) + {"section", required_argument, 0, 'S'}, // default section = LIBMODBUS + {"name", required_argument, 0, 'n'}, // vfs11_vfd + {0,0,0,0} +}; + + +void windup(param_pointer p) +{ + if (p->haldata && *(p->haldata->errorcount)) { + fprintf(stderr,"%s: %d modbus errors\n",p->progname, *(p->haldata->errorcount)); + fprintf(stderr,"%s: last command register: 0x%.4x\n",p->progname, p->failed_reg); + fprintf(stderr,"%s: last error: %s\n",p->progname, modbus_strerror(p->last_errno)); + } + if (p->hal_comp_id >= 0) + hal_exit(p->hal_comp_id); + if (p->ctx) + modbus_close(p->ctx); +} + +static void toggle_modbus_debug(int sig) +{ + param.modbus_debug = !param.modbus_debug; + modbus_set_debug(param.ctx, param.modbus_debug); +} + +static void toggle_debug(int sig) +{ + param.debug = !param.debug; +} + +static void quit(int sig) +{ + if (param.debug) + fprintf(stderr,"quit(connection_state=%d)\n",connection_state); + + switch (connection_state) { + + case CONNECTING: + // modbus_tcp_accept() or TCP modbus_connect() were interrupted + // these wont return to the main loop, so exit here + windup(¶m); + exit(0); + break; + + default: + connection_state = DONE; + break; + } +} + +enum kwdresult {NAME_NOT_FOUND, KEYWORD_INVALID, KEYWORD_FOUND}; +#define MAX_KWD 10 + +int findkwd(param_pointer p, const char *name, int *result, const char *keyword, int value, ...) +{ + const char *word; + va_list ap; + const char *kwds[MAX_KWD], **s; + int nargs = 0; + + if ((word = iniFind(p->fp, name, p->section)) == NULL) + return NAME_NOT_FOUND; + + kwds[nargs++] = keyword; + va_start(ap, value); + + while (keyword != NULL) { + if (!strcasecmp(word, keyword)) { + *result = value; + va_end(ap); + return KEYWORD_FOUND; + } + keyword = va_arg(ap, const char *); + kwds[nargs++] = keyword; + if (keyword) + value = va_arg(ap, int); + } + fprintf(stderr, "%s: %s:[%s]%s: found '%s' - not one of: ", + p->progname, p->inifile, p->section, name, word); + for (s = kwds; *s; s++) + fprintf(stderr, "%s ", *s); + fprintf(stderr, "\n"); + va_end(ap); + return KEYWORD_INVALID; +} + +int read_ini(param_pointer p) +{ + const char *s; + double f; + int value; + + if ((p->fp = fopen(p->inifile,"r")) != NULL) { + if (!p->debug) + iniFindInt(p->fp, "DEBUG", p->section, &p->debug); + if (!p->modbus_debug) + iniFindInt(p->fp, "MODBUS_DEBUG", p->section, &p->modbus_debug); + iniFindInt(p->fp, "BITS", p->section, &p->bits); + iniFindInt(p->fp, "BAUD", p->section, &p->baud); + iniFindInt(p->fp, "STOPBITS", p->section, &p->stopbits); + iniFindInt(p->fp, "TARGET", p->section, &p->slave); + iniFindInt(p->fp, "POLLCYCLES", p->section, &p->pollcycles); + iniFindInt(p->fp, "PORT", p->section, &p->tcp_portno); + iniFindInt(p->fp, "RECONNECT_DELAY", p->section, &p->reconnect_delay); + + if ((s = iniFind(p->fp, "TCPDEST", p->section))) { + p->tcp_destip = strdup(s); + } + if ((s = iniFind(p->fp, "DEVICE", p->section))) { + p->device = strdup(s); + } + if (iniFindDouble(p->fp, "RESPONSE_TIMEOUT", p->section, &f)) { + p->response_timeout.tv_sec = (int) f; + p->response_timeout.tv_usec = (f-p->response_timeout.tv_sec) * 1000000; + } + if (iniFindDouble(p->fp, "BYTE_TIMEOUT", p->section, &f)) { + p->byte_timeout.tv_sec = (int) f; + p->byte_timeout.tv_usec = (f-p->byte_timeout.tv_sec) * 1000000; + } + value = p->parity; + if (findkwd(p, "PARITY", &value, + "even",'E', + "odd", 'O', + "none", 'N', + NULL) == KEYWORD_INVALID) + return -1; + p->parity = value; + + if (findkwd(p, "RTS_MODE", &p->rts_mode, + "up", MODBUS_RTU_RTS_UP, + "down", MODBUS_RTU_RTS_DOWN, + "none", MODBUS_RTU_RTS_NONE, + NULL) == KEYWORD_INVALID) + return -1; + + if (findkwd(p,"SERIAL_MODE", &p->serial_mode, + "rs232", MODBUS_RTU_RS232, + "rs485", MODBUS_RTU_RS232, + NULL) == KEYWORD_INVALID) + return -1; + + if (findkwd(p, "TYPE", &p->type, + "rtu", TYPE_RTU, + "tcpserver", TYPE_TCP_SERVER, + "tcpclient", TYPE_TCP_CLIENT, + NULL) == NAME_NOT_FOUND) { + fprintf(stderr, "%s: missing required TYPE in section %s\n", + p->progname, p->section); + return -1; + } + } else { + fprintf(stderr, "%s:cant open inifile '%s'\n", + p->progname, p->inifile); + return -1; + } + return 0; +} + +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 vfs11_vfd [options]\n" + "Options are:\n" + "-I or --ini <inifile>\n" + " Use <inifile> (default: take ini filename from environment variable INI_FILE_NAME)\n" + "-S or --section <section-name> (default 8)\n" + " Read parameters from <section_name> (default 'VFS11')\n" + "-d or --debug\n" + " Turn on debugging messages. Toggled by USR1 signal.\n" + "-m or --modbus-debug\n" + " Turn on modbus debugging. This will cause all modbus messages\n" + " to be printed in hex on the terminal. Toggled by USR2 signal.\n" + "-r or --report-device\n" + " Report device properties on console at startup\n"); +} + +int write_data(modbus_t *ctx, haldata_t *haldata, param_pointer p) +{ + hal_float_t hzcalc; + int cmd1_reg; + int freq_reg, freq_cap; + + if (!*(haldata->enabled)) { + // send 0 to FA00 register - no bus control + if (modbus_write_register(ctx, REG_COMMAND1, 0) < 0) { + p->failed_reg = REG_COMMAND1; + (*haldata->errorcount)++; + p->last_errno = errno; + return errno; + } + return 0; + } + retry: + // set frequency register + if (haldata->motor_nameplate_hz < 10) + haldata->motor_nameplate_hz = 50; + if ((haldata->motor_nameplate_RPM < 600) || (haldata->motor_nameplate_RPM > 5000)) + haldata->motor_nameplate_RPM = 1410; + hzcalc = haldata->motor_nameplate_hz/haldata->motor_nameplate_RPM; + + freq_reg = abs((int)(*(haldata->speed_command) * hzcalc * 100)); + freq_cap = abs((int)(haldata->rpm_limit * hzcalc * 100)); + + // limit frequency to frequency set via max-rpm + if (freq_reg > freq_cap) + freq_reg = freq_cap; + + *(haldata->freq_cmd) = freq_reg / 100.0; + + // prepare command register + // force Modbus control - this disables the panel + cmd1_reg = (CMD_COMMAND_PRIORITY|CMD_FREQUENCY_PRIORITY); + if (*haldata->spindle_on){ + cmd1_reg|= (*haldata->jog_mode) ? CMD_JOG_RUN : CMD_RUN; + } + + // if 1, choose ramp times as per F500/F501 + // fix for PID loops where long ramp times cause oscillation + if (haldata->acc_dec_pattern){ + cmd1_reg|= CMD_ACCEL_PATTERN_2; + } + + // rev follows fwd + // two bits for one direction is a mess in the first place + *(haldata->spindle_rev) = *(haldata->spindle_fwd) ? 0 : 1; + *(haldata->spindle_fwd) = *(haldata->spindle_rev) ? 0 : 1; + + if (*haldata->spindle_rev) { + cmd1_reg |= CMD_REVERSE; + } else { + cmd1_reg &= (~CMD_REVERSE); // direction bit = 0 -> forward + } + + // DC brake - turn spindle_on off as well + if (*(haldata->DC_brake)) { + cmd1_reg |= CMD_DC_BRAKE; // set DC brake bit + cmd1_reg &= ~(CMD_RUN | CMD_JOG_RUN); + *(haldata->spindle_on) = 0; + *(haldata->at_speed) = 0; + } else { + cmd1_reg &= ~CMD_DC_BRAKE; + } + + // send CMD_FAULT_RESET and CMD_EMERGENCY_STOP only once so the poor thing comes back + // out of reset/estop status eventually + if (*(haldata->err_reset) && !(p->old_cmd1_reg & CMD_FAULT_RESET )) { // not sent yet + cmd1_reg |= CMD_FAULT_RESET; // fault reset bit = 1 -> clear fault + *(haldata->err_reset) = 0; + } else { + cmd1_reg &= ~CMD_FAULT_RESET; + } + + if (*(haldata->estop) && !(p->old_cmd1_reg & CMD_EMERGENCY_STOP )) { // not sent yet) + cmd1_reg |= CMD_EMERGENCY_STOP; // estop bit -> trip VFD into estop mode + *(haldata->estop) = 0; + *(haldata->spindle_on) = 0; + *(haldata->at_speed) = 0; + } else { + cmd1_reg &= ~CMD_EMERGENCY_STOP; + } + + DBG("write_data: cmd1_reg=0x%4.4X old cmd1_reg=0x%4.4X\n", cmd1_reg,p->old_cmd1_reg); + + if (modbus_write_register(ctx, REG_COMMAND1, cmd1_reg) < 0) { + // modbus transaction timed out. This may happen if VFD is in E-Stop. + // if VFD was in E-Stop, and a fault reset was sent, wait about 2 seconds for recovery + // we must assume that any command and frequency values sent were cleared, so we restart + // the operation. + // note that sending the CMD_EMERGENCY_STOP bit in cmd1_reg causes an immediate reboot + // without a Modbus reply (if the VFD actually was in e-stop) so we ignore this error. + if (cmd1_reg & CMD_EMERGENCY_STOP) { + sleep(2); + goto retry; + } + p->failed_reg = REG_COMMAND1; + (*haldata->errorcount)++; + p->last_errno = errno; + return errno; + } + + // remember so we can toggle fault/estop reset just once + // otherwise the VFD keeps rebooting as long as the fault reset/estop reset bits are sent + p->old_cmd1_reg = cmd1_reg; + + if ((modbus_write_register(ctx, REG_FREQUENCY, freq_reg)) < 0) { + p->failed_reg = REG_FREQUENCY; + (*haldata->errorcount)++; + p->last_errno = errno; + return errno; + } + + if ((*(haldata->freq_cmd) > 0.01) && ((1.0 - *(haldata->freq_out) / *(haldata->freq_cmd)) < haldata->speed_tolerance)){ + *(haldata->at_speed) = 1; + } else { + *(haldata->at_speed) = 0; + } + + if (*(haldata->spindle_on) == 0){ // JET reset at-speed + *(haldata->at_speed) = 0; + } + return 0; +} + +#define GETREG(reg,into) \ + do { \ + curr_reg = reg; \ + if (modbus_read_registers(ctx, reg, 1, into) != 1) \ + goto failed; \ + } while (0) + + +int read_initial(modbus_t *ctx, haldata_t *haldata, param_pointer p) +{ + uint16_t curr_reg, current, + voltage, model, cpu1, cpu2, eeprom, max_freq; + + GETREG(REG_UPPERLIMIT, &max_freq); + *(haldata->upper_limit_hz) = (float)max_freq/100.0; + *(haldata->max_rpm) = *(haldata->upper_limit_hz) * + haldata->motor_nameplate_RPM / + haldata->motor_nameplate_hz; + + if (p->report_device) { + GETREG(SR_RATED_CURRENT, ¤t); + GETREG(SR_RATED_VOLTAGE, &voltage); + GETREG(SR_INVERTER_MODEL, &model); + GETREG(SR_CPU1_VERSION, &cpu1); + GETREG(SR_CPU2_VERSION, &cpu2); + GETREG(SR_EEPROM_VERSION, &eeprom); + + printf("%s: inverter model: %d/0x%4.4x\n", + p->progname, model, model); + printf("%s: maximum ratings: %.1fV %.1fA %.2fHz\n", + p->progname, voltage/10.0, current/10.0, max_freq/100.0); + printf("%s: versions: cpu1=%d/0x%4.4x cpu2=%d/0x%4.4x eeprom=%d/0x%4.4x\n", + p->progname, cpu1, cpu1, cpu2, cpu2, eeprom, eeprom); + } + return 0; + + failed: + p->failed_reg = curr_reg; + p->last_errno = errno; + (*haldata->errorcount)++; + if (p->debug) + fprintf(stderr, "%s: read_initial: modbus_read_registers(0x%4.4x): %s\n", + p->progname, curr_reg, modbus_strerror(errno)); + return p->last_errno; +} + +int read_data(modbus_t *ctx, haldata_t *haldata, param_pointer p) +{ + int retval; + uint16_t curr_reg, val, status_reg, freq_reg; + static int pollcount = 0; + + if (!p->read_initial_done) { + if ((retval = read_initial(ctx, haldata, p))) + return retval; + else + p->read_initial_done = 1; + } + + // we always at least read the main status register SR_INV_OPSTATUS + // and current operating frequency SR_OP_FREQUENCY + GETREG(SR_INV_OPSTATUS, &status_reg); + *(haldata->status) = status_reg; + + GETREG(SR_OP_FREQUENCY, &freq_reg); + *(haldata->freq_out) = freq_reg * 0.01; + + DBG("read_data: status_reg=%4.4x freq_reg=%4.4x\n", status_reg, freq_reg); + + // JET if freq out is 0 then the drive is stopped + *(haldata->is_stopped) = (freq_reg == 0); + + // determine what to do next. + if (status_reg & ST_TRIPPED) { // read and set trip code. + GETREG(SR_TRIPCODE, &val); + *(haldata->trip_code) = val; + // a sensible addition would be to read and convey SR_INV_OPSTATUS_T, the status just before the trip + } else { + *(haldata->trip_code) = 0; + } + + if (status_reg & ST_ALARMED) { // read and set alarm bit map. + GETREG(SR_ALARM_MONITOR, &val); + *(haldata->alarm_code) = val; + } else { + *(haldata->alarm_code) = 0; + } + + if (status_reg & ST_EMERGENCY_STOPPED) { // set e-stop status. + *(haldata->is_e_stopped) = 1; + } else { + *(haldata->is_e_stopped) = 0; + + } + // unsure what to do here with ST_FAILURE_FL bit + + if ((pollcount == 0) && !haldata->max_speed) { + + // less urgent registers + GETREG(SR_ESTIMATED_OPFREQ, &val); + *(haldata->RPM) = val * haldata->motor_nameplate_hz / 100.0; + + GETREG(SR_INV_LOADFACTOR, &val); + *(haldata->inv_load_pct) = val; + + GETREG(SR_LOADCURRENT, &val); + *(haldata->load_current_pct) = val * 0.01; + + GETREG(SR_OUTPUT_VOLTAGE, &val); + *(haldata->outV_pct) = val * 0.01; + } else + pollcount++; + if (pollcount >= p->pollcycles) + pollcount = 0; + p->last_errno = retval = 0; + return 0; + + failed: + p->failed_reg = curr_reg; + p->last_errno = errno; + (*haldata->errorcount)++; + if (p->debug) + fprintf(stderr, "%s: read_data: modbus_read_registers(0x%4.4x): %s\n", + p->progname, curr_reg, modbus_strerror(errno)); + return p->last_errno; +} + +#undef GETREG + +#define PIN(x) \ + do { \ + status = (x); \ + if ((status) != 0) \ + return status; \ + } while (0) + +int hal_setup(int id, haldata_t *h, const char *name) +{ + int status; + PIN(hal_pin_bit_newf(HAL_IN, &(h->acc_dec_pattern), id, "%s.acceleration-pattern", name)); + PIN(hal_pin_s32_newf(HAL_OUT, &(h->alarm_code), id, "%s.alarm-code", name)); + PIN(hal_pin_bit_newf(HAL_OUT, &(h->at_speed), id, "%s.at-speed", name)); + PIN(hal_pin_float_newf(HAL_OUT, &(h->load_current_pct), id, "%s.current-load-percentage", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->DC_brake), id, "%s.dc-brake", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->enabled), id, "%s.enable", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->err_reset), id, "%s.err-reset", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->jog_mode), id, "%s.jog-mode", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->estop), id, "%s.estop", name)); + PIN(hal_pin_float_newf(HAL_OUT, &(h->freq_cmd), id, "%s.frequency-command", name)); + PIN(hal_pin_float_newf(HAL_OUT, &(h->freq_out), id, "%s.frequency-out", name)); + PIN(hal_pin_float_newf(HAL_OUT, &(h->inv_load_pct), id, "%s.inverter-load-percentage", name)); + PIN(hal_pin_bit_newf(HAL_OUT, &(h->is_e_stopped), id, "%s.is-e-stopped", name)); // JET + PIN(hal_pin_bit_newf(HAL_OUT, &(h->is_stopped), id, "%s.is-stopped", name)); // JET + PIN(hal_pin_float_newf(HAL_OUT, &(h->max_rpm), id, "%s.max-rpm", name)); + PIN(hal_pin_bit_newf(HAL_OUT, &(h->modbus_ok), id, "%s.modbus-ok", name)); // JET + PIN(hal_pin_float_newf(HAL_OUT, &(h->RPM), id, "%s.motor-RPM", name)); + PIN(hal_pin_float_newf(HAL_OUT, &(h->curr_out_pct), id, "%s.output-current-percentage", name)); + PIN(hal_pin_float_newf(HAL_OUT, &(h->outV_pct), id, "%s.output-voltage-percentage", name)); + PIN(hal_pin_float_newf(HAL_IN, &(h->speed_command), id, "%s.speed-command", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->spindle_fwd), id, "%s.spindle-fwd", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->spindle_on), id, "%s.spindle-on", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->spindle_rev), id, "%s.spindle-rev", name)); //JET + PIN(hal_pin_s32_newf(HAL_OUT, &(h->status), id, "%s.status", name)); + PIN(hal_pin_s32_newf(HAL_OUT, &(h->trip_code), id, "%s.trip-code", name)); + PIN(hal_pin_bit_newf(HAL_IN, &(h->max_speed), id, "%s.max-speed", name)); + PIN(hal_pin_s32_newf(HAL_OUT, &(h->errorcount), id, "%s.error-count", name)); + PIN(hal_pin_float_newf(HAL_OUT, &(h->upper_limit_hz), id, "%s.frequency-limit", name)); + + // the following limit must be set manually from the panel since its in EEPROM + PIN(hal_param_float_newf(HAL_RW, &(h->looptime), id, "%s.loop-time", name)); + PIN(hal_param_float_newf(HAL_RW, &(h->motor_nameplate_hz), id, "%s.nameplate-HZ", name)); + PIN(hal_param_float_newf(HAL_RW, &(h->motor_nameplate_RPM), id, "%s.nameplate-RPM", name)); + PIN(hal_param_float_newf(HAL_RW, &(h->rpm_limit), id, "%s.rpm-limit", name)); + PIN(hal_param_float_newf(HAL_RW, &(h->speed_tolerance), id, "%s.tolerance", name)); + + return 0; +} +#undef PIN + +int set_defaults(param_pointer p) +{ + haldata_t *h = p->haldata; + + *(h->status) = 0; + *(h->freq_cmd) = 0; + *(h->freq_out) = 0; + *(h->curr_out_pct) = 0; + *(h->outV_pct) = 0; + *(h->RPM) = 0; + *(h->inv_load_pct) = 0; + *(h->load_current_pct) = 0; + *(h->upper_limit_hz) = 0; + *(h->trip_code) = 0; + *(h->alarm_code) = 0; + *(h->at_speed) = 0; + *(h->is_stopped) = 0; + *(h->estop) = 0; + *(h->is_e_stopped) = 0; + *(h->speed_command) = 0; + *(h->modbus_ok) = 0; + + *(h->spindle_on) = 0; + *(h->DC_brake) = 0; + *(h->spindle_fwd) = 1; + *(h->spindle_rev) = 0; + *(h->err_reset) = 0; + *(h->jog_mode) = 0; + *(h->enabled) = 0; + *(h->acc_dec_pattern) = 0; + *(h->errorcount) = 0; + *(h->max_speed) = 0; + + h->looptime = 0.1; + h->speed_tolerance = 0.01; // output frequency within 1% of target frequency + h->motor_nameplate_hz = 50; // folks in The Colonies typically would use 60Hz and 1730 rpm + h->motor_nameplate_RPM = 1410; + h->rpm_limit = MAX_RPM; + + p->failed_reg = 0; + return 0; +} + +int main(int argc, char **argv) +{ + struct timespec loop_timespec, remaining; + int opt, socket; + param_pointer p = ¶m; + int retval = 0; + retval = -1; + p->progname = argv[0]; + connection_state = NOT_CONNECTED; + p->inifile = getenv("INI_FILE_NAME"); + + while ((opt = getopt_long(argc, argv, option_string, long_options, NULL)) != -1) { + switch(opt) { + case 'n': + p->modname = strdup(optarg); + break; + case 'm': + p->modbus_debug = 1; + break; + case 'd': + p->debug = 1; + break; + case 'S': + p->section = optarg; + break; + case 'I': + p->inifile = optarg; + break; + case 'r': + p->report_device = 1; + break; + case 'h': + default: + usage(argc, argv); + exit(0); + } + } + + if (p->inifile) { + if (read_ini(p)) + goto finish; + if (!p->modname) + p->modname = "vfs11_vfd"; + } else { + fprintf(stderr, "%s: ERROR: no inifile - either use '--ini inifile' or set INI_FILE_NAME environment variable\n", p->progname); + goto finish; + } + + signal(SIGINT, quit); + signal(SIGTERM, quit); + signal(SIGUSR1, toggle_debug); + signal(SIGUSR2, toggle_modbus_debug); + + // create HAL component + p->hal_comp_id = hal_init(p->modname); + if ((p->hal_comp_id < 0) || (connection_state == DONE)) { + fprintf(stderr, "%s: ERROR: hal_init(%s) failed: HAL error code=%d\n", + p->progname, p->modname, p->hal_comp_id); + retval = p->hal_comp_id; + goto finish; + } + + // grab some shmem to store the HAL data in + p->haldata = (haldata_t *)hal_malloc(sizeof(haldata_t)); + if ((p->haldata == 0) || (connection_state == DONE)) { + fprintf(stderr, "%s: ERROR: unable to allocate shared memory\n", p->modname); + retval = -1; + goto finish; + } + if (hal_setup(p->hal_comp_id,p->haldata, p->modname)) + goto finish; + + set_defaults(p); + hal_ready(p->hal_comp_id); + + DBG("using libmodbus version %s\n", LIBMODBUS_VERSION_STRING); + + switch (p->type) { + + case TYPE_RTU: + connection_state = OPENING; + if ((p->ctx = modbus_new_rtu(p->device, p->baud, p->parity, p->bits, p->stopbits)) == NULL) { + fprintf(stderr, "%s: ERROR: modbus_new_rtu(%s): %s\n", + p->progname, p->device, modbus_strerror(errno)); + goto finish; + } + if (modbus_set_slave(p->ctx, p->slave) < 0) { + fprintf(stderr, "%s: ERROR: invalid slave number: %d\n", p->modname, p->slave); + goto finish; + } + if ((retval = modbus_connect(p->ctx)) != 0) { + fprintf(stderr, "%s: ERROR: couldn't open serial device: %s\n", p->modname, modbus_strerror(errno)); + goto finish; + } + // see https://github.com/stephane/libmodbus/issues/42 + if ((p->serial_mode != -1) && modbus_rtu_set_serial_mode(p->ctx, p->serial_mode) < 0) { + fprintf(stderr, "%s: ERROR: modbus_rtu_set_serial_mode(%d): %s\n", + p->modname, p->serial_mode, modbus_strerror(errno)); + goto finish; + } + if ((p->rts_mode != -1) && modbus_rtu_set_rts(p->ctx, p->rts_mode) < 0) { + fprintf(stderr, "%s: ERROR: modbus_rtu_set_rts(%d): %s\n", + p->modname, p->rts_mode, modbus_strerror(errno)); + goto finish; + } + DBG("%s: serial port %s connected\n", p->progname, p->device); + break; + + case TYPE_TCP_SERVER: + if ((p->ctx = modbus_new_tcp("127.0.0.1", p->tcp_portno)) == NULL) { + fprintf(stderr, "%s: modbus_new_tcp(%d): %s\n", + p->progname, p->tcp_portno, modbus_strerror(errno)); + goto finish; + } + if ((socket = modbus_tcp_listen(p->ctx, 1)) < 0) { + fprintf(stderr, "%s: modbus_tcp_listen(): %s\n", + p->progname, modbus_strerror(errno)); + goto finish; + } + connection_state = CONNECTING; + if (modbus_tcp_accept(p->ctx, &socket) < 0) { + fprintf(stderr, "%s: modbus_tcp_accept(): %s\n", + p->progname, modbus_strerror(errno)); + goto finish; + } + break; + + case TYPE_TCP_CLIENT: + if ((p->ctx = modbus_new_tcp(p->tcp_destip, p->tcp_portno)) == NULL) { + fprintf(stderr,"%s: Unable to allocate libmodbus TCP context: %s\n", + p->progname, modbus_strerror(errno)); + goto finish; + } + connection_state = CONNECTING; + if (modbus_connect(p->ctx) < 0) { + fprintf(stderr, "%s: TCP connection to %s:%d failed: %s\n", + p->progname, p->tcp_destip, p->tcp_portno, modbus_strerror(errno)); + modbus_free(p->ctx); + goto finish; + } + DBG("main: TCP connected to %s:%d\n", p->tcp_destip, p->tcp_portno); + break; + + default: + fprintf(stderr, "%s: ERROR: invalid connection type %d\n", + p->progname, p->type); + goto finish; + } + + modbus_set_debug(p->ctx, p->modbus_debug); + if (modbus_set_slave(p->ctx, p->slave) < 0) { + fprintf(stderr, "%s: ERROR: invalid slave number: %d\n", p->modname, p->slave); + goto finish; + } + + connection_state = CONNECTED; + while (connection_state != DONE) { + + while (connection_state == CONNECTED) { + if ((retval = read_data(p->ctx, p->haldata, p))) { + p->modbus_ok = 0; + } else { + p->modbus_ok++; + } + if (p->modbus_ok > MODBUS_MIN_OK) { + *(p->haldata->modbus_ok) = 1; + } else { + *(p->haldata->modbus_ok) = 0; + } + if ((retval = write_data(p->ctx, p->haldata, p))) { + p->modbus_ok = 0; + if ((retval == EBADF || retval == ECONNRESET || retval == EPIPE)) { + connection_state = RECOVER; + } + } else { + p->modbus_ok++; + } + if (p->modbus_ok > MODBUS_MIN_OK) { + *(p->haldata->modbus_ok) = 1; + } else { + *(p->haldata->modbus_ok) = 0; + } + /* don't want to scan too fast, and shouldn't delay more than a few seconds */ + if (p->haldata->looptime < 0.001) p->haldata->looptime = 0.001; + if (p->haldata->looptime > 2.0) p->haldata->looptime = 2.0; + loop_timespec.tv_sec = (time_t)(p->haldata->looptime); + loop_timespec.tv_nsec = (long)((p->haldata->looptime - loop_timespec.tv_sec) * 1000000000l); + if (!p->haldata->max_speed) + nanosleep(&loop_timespec, &remaining); + } + + switch (connection_state) { + case DONE: + // cleanup actions before exiting. + modbus_flush(p->ctx); + // clear the command register (control and frequency override) so panel operation gets reactivated + if ((retval = modbus_write_register(p->ctx, REG_COMMAND1, 0)) != 1) { + // not much we can do about it here if it goes wrong, so complain + fprintf(stderr, "%s: failed to release VFD from bus control (write to register 0x%x): %s\n", + p->progname, REG_COMMAND1, modbus_strerror(errno)); + } else { + DBG("%s: VFD released from bus control.\n", p->progname); + } + break; + + case RECOVER: + DBG("recover\n"); + set_defaults(p); + p->read_initial_done = 0; + // reestablish connection to slave + switch (p->type) { + + case TYPE_RTU: + case TYPE_TCP_CLIENT: + modbus_flush(p->ctx); + modbus_close(p->ctx); + while ((connection_state != CONNECTED) && + (connection_state != DONE)) { + sleep(p->reconnect_delay); + if (!modbus_connect(p->ctx)) { + connection_state = CONNECTED; + DBG("rtu/tcpclient reconnect\n"); + } else { + fprintf(stderr, "%s: recovery: modbus_connect(): %s\n", + p->progname, modbus_strerror(errno)); + } + } + break; + + case TYPE_TCP_SERVER: + while ((connection_state != CONNECTED) && + (connection_state != DONE)) { + connection_state = CONNECTING; + sleep(p->reconnect_delay); + if (!modbus_tcp_accept(p->ctx, &socket)) { + fprintf(stderr, "%s: recovery: modbus_tcp_accept(): %s\n", + p->progname, modbus_strerror(errno)); + } else { + connection_state = CONNECTED; + DBG("tcp reconnect\n"); + } + } + break; + + default: + break; + } + break; + default: ; + } + } + retval = 0; + + finish: + windup(p); + return retval; +} + diff --git a/src/hal/user_comps/vfs11_vfd/vfs11_vfd.hal b/src/hal/user_comps/vfs11_vfd/vfs11_vfd.hal new file mode 100644 index 000000000..7f60b9346 --- /dev/null +++ b/src/hal/user_comps/vfs11_vfd/vfs11_vfd.hal @@ -0,0 +1,30 @@ +# +# example usage of the VF-S11 VFD driver +# +# $Id: vfs11_vfd.hal,v 1.1 2009-09-19 23:00:27 mah Exp $ +# +loadusr -Wn spindle-vfd vfs11_vfd -n spindle-vfd + +# connect the spindle direction pins to the VFD +net vfs11-fwd spindle-vfd.spindle-fwd <= motion.spindle-forward +net vfs11-rev spindle-vfd.spindle-rev <= motion.spindle-reverse + +# connect the spindle on pin to the VF-S11 +net vfs11-run spindle-vfd.spindle-on <= motion.spindle-on + +# connect the VF-S11 at speed to the motion at speed +net vfs11-at-speed motion.spindle-at-speed <= spindle-vfd.at-speed + +# connect the spindle RPM to the VF-S11 +net vfs11-RPM spindle-vfd.speed-command <= motion.spindle-speed-out + +# connect the VF-S11 DC brake +# used by default if spindle is stopped - uncomment to use +#net vfs11-spindle-brake motion.spindle-brake => spindle-vfd.dc-brake + +# to use the VFS11 jog mode for spindle orient +# see orient.9 and motion.9 +net spindle-orient motion.spindle-orient spindle-vfd.max-speed spindle-vfd.jog-mode + +# take precedence over control panel +setp spindle-vfd.enable 1
\ No newline at end of file |