summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Haberler <git@mah.priv.at>2012-02-27 10:48:50 +0100
committerMichael Haberler <git@mah.priv.at>2012-02-27 10:48:50 +0100
commita463a350b39492f2561cdcd212b4ef1263817760 (patch)
tree3b524118c45009ecb68778ba533bacca285cbee2
parent4107acf93c0fd28a00965c672840a3bcec2689d6 (diff)
downloadlinuxcnc-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/Makefile2
-rw-r--r--src/hal/user_comps/vfs11_vfd/Submakefile17
-rw-r--r--src/hal/user_comps/vfs11_vfd/dump-params.mio99
-rw-r--r--src/hal/user_comps/vfs11_vfd/vfs11-all-options.ini58
-rw-r--r--src/hal/user_comps/vfs11_vfd/vfs11-rs232.ini25
-rw-r--r--src/hal/user_comps/vfs11_vfd/vfs11-rs485.ini34
-rw-r--r--src/hal/user_comps/vfs11_vfd/vfs11-tcp-client.ini16
-rw-r--r--src/hal/user_comps/vfs11_vfd/vfs11-tcp-server.ini13
-rw-r--r--src/hal/user_comps/vfs11_vfd/vfs11_vfd.c1123
-rw-r--r--src/hal/user_comps/vfs11_vfd/vfs11_vfd.hal30
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(&param);
+ 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, &current);
+ 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 = &param;
+ 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