Merge pull request #677 from tinic/master
Add support for Soyan Power SVD-P(S) VFDs
This commit is contained in:
commit
0892a45f95
4 changed files with 805 additions and 0 deletions
1
docs/man/.gitignore
vendored
1
docs/man/.gitignore
vendored
|
|
@ -7,6 +7,7 @@ cat3/
|
||||||
cat9/
|
cat9/
|
||||||
|
|
||||||
man1/hy_gt_vfd.1
|
man1/hy_gt_vfd.1
|
||||||
|
man1/svd-ps_vfd.1
|
||||||
man1/linuxcnc.1
|
man1/linuxcnc.1
|
||||||
man1/halstreamer.1
|
man1/halstreamer.1
|
||||||
man1/thermistor.1
|
man1/thermistor.1
|
||||||
|
|
|
||||||
174
docs/src/man/man1/svd-pd_vfd.1.txt
Normal file
174
docs/src/man/man1/svd-pd_vfd.1.txt
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
= svd-ps_vfd(1)
|
||||||
|
|
||||||
|
|
||||||
|
== NAME
|
||||||
|
|
||||||
|
svd-ps_vfd - HAL userspace component for SVD-P(S) VFDs
|
||||||
|
|
||||||
|
|
||||||
|
== SYNOPSIS
|
||||||
|
|
||||||
|
*svd-ps_vfd* [_OPTIONS_]
|
||||||
|
|
||||||
|
== DESCRIPTION
|
||||||
|
|
||||||
|
The svd-ps_vfd component interfaces a Soyan Power SVD-P(S) VFD to the
|
||||||
|
LinuxCNC HAL. The VFD is connected via RS-485 to the LinuxCNC computer.
|
||||||
|
|
||||||
|
The SVD-P(S) VFDs are also sold under the LAPOND brand.
|
||||||
|
|
||||||
|
== Hardware setup
|
||||||
|
|
||||||
|
The SVD-P(S) VFDs do not come with a Modbus daughterboard by default,
|
||||||
|
it needs to be purchased separately.
|
||||||
|
|
||||||
|
== Firmware setup
|
||||||
|
|
||||||
|
The sad-ps_vfd component uses standard Modbus protocol communication
|
||||||
|
which requires that one parameter be changed from the default settings:
|
||||||
|
|
||||||
|
PD-05 = 1 (Standard Modbus protocol)
|
||||||
|
|
||||||
|
The following settings have been tested successfully and are the
|
||||||
|
default per Soyans documentation:
|
||||||
|
|
||||||
|
PD-00 = 6005 (9600 baud)
|
||||||
|
|
||||||
|
PD-01 = 0 (8N2)
|
||||||
|
|
||||||
|
PD-02 = 1 (Slave address)
|
||||||
|
|
||||||
|
PD-03 = 2 (Response delay)
|
||||||
|
|
||||||
|
PD-04 = 0 (Communication timeout)
|
||||||
|
|
||||||
|
PD-06 = 0 (Current resolution)
|
||||||
|
|
||||||
|
== OPTIONS
|
||||||
|
|
||||||
|
*-b*, *--bits* _N_::
|
||||||
|
|
||||||
|
(default 8) For Modbus communication. Set number of data bits
|
||||||
|
to _N_. _N_ must be between 5 and 8 inclusive.
|
||||||
|
|
||||||
|
*-p*, *--parity* [Even,Odd,None]
|
||||||
|
|
||||||
|
(default None) For Modbus communication. Set serial parity to Even,
|
||||||
|
Odd, or None.
|
||||||
|
|
||||||
|
*-r*, *--rate* _N_::
|
||||||
|
|
||||||
|
(default 9600) For Modbus communication. Set baud rate to _N_.
|
||||||
|
It is an error if the rate is not one of the following: 1200,
|
||||||
|
2400, 4800, 9600, 19200, 38400
|
||||||
|
|
||||||
|
*-s*, *--stopbits* [1,2]::
|
||||||
|
|
||||||
|
(default 2) For Modbus communication. Set serial stop bits to 1
|
||||||
|
or 2.
|
||||||
|
|
||||||
|
*-t*, *--target* _N_::
|
||||||
|
|
||||||
|
(default 1) For Modbus communication. Set Modbus target (slave)
|
||||||
|
number. This must match the device number you set on the Huanyang
|
||||||
|
GT VFD.
|
||||||
|
|
||||||
|
*-d*, *--device* _PATH_::
|
||||||
|
|
||||||
|
(default /dev/ttyS0) For Modbus communication. Set the name of
|
||||||
|
the serial device node to use.
|
||||||
|
|
||||||
|
*-v*, *--verbose*::
|
||||||
|
|
||||||
|
Turn on verbose mode.
|
||||||
|
|
||||||
|
*-S*, *--motor-max-speed* _RPM_::
|
||||||
|
|
||||||
|
The motor's max speed in RPM.
|
||||||
|
|
||||||
|
*-F*, *--max-frequency* _HZ_::
|
||||||
|
|
||||||
|
This is the maximum output frequency of the VFD in Hz.
|
||||||
|
|
||||||
|
*-f*, *--min-frequency* _HZ_::
|
||||||
|
|
||||||
|
This is the minimum output frequency of the VFD in Hz.
|
||||||
|
|
||||||
|
== PINS
|
||||||
|
|
||||||
|
|
||||||
|
*svd-ps_vfd.period* (float, in)::
|
||||||
|
|
||||||
|
The period for the driver's update cycle, in seconds. This is
|
||||||
|
how frequently the driver will wake up, check its HAL pins, and
|
||||||
|
communicate with the VFD. Must be between 0.001 and 2.000 seconds.
|
||||||
|
Default: 0.1 seconds.
|
||||||
|
|
||||||
|
*svd-ps_vfd.speed-cmd* (float, in)::
|
||||||
|
|
||||||
|
The requested motor speed, in RPM.
|
||||||
|
|
||||||
|
*svd-ps_vfd.speed-fb* (float, out)::
|
||||||
|
|
||||||
|
The motor's current speed, in RPM, reported by the VFD.
|
||||||
|
|
||||||
|
*svd-ps_vfd.at-speed* (bit, out)::
|
||||||
|
|
||||||
|
True when the drive is on and at the commanded speed (within 2%),
|
||||||
|
False otherwise.
|
||||||
|
|
||||||
|
*svd-ps_vfd.freq-cmd* (float, out)::
|
||||||
|
|
||||||
|
The requested output frequency, in Hz. This is set from the
|
||||||
|
.speed-cmd value, and is just shown for debugging purposes.
|
||||||
|
|
||||||
|
*svd-ps_vfd.freq-fb* (float, out)::
|
||||||
|
|
||||||
|
The current output frequency of the VFD, in Hz. This is reported
|
||||||
|
from the VFD to the driver.
|
||||||
|
|
||||||
|
*svd-ps_vfd.spindle-on* (bit, in)::
|
||||||
|
|
||||||
|
Set this pin True to command the spindle on, at the speed requested
|
||||||
|
on the .speed-cmd pin. Set this pin False to command the spindle
|
||||||
|
off.
|
||||||
|
|
||||||
|
*svd-ps_vfd.output-voltage* (float, out)::
|
||||||
|
|
||||||
|
The voltage that the VFD is current providing to the motor,
|
||||||
|
in Volts.
|
||||||
|
|
||||||
|
*svd-ps_vfd.output-current* (float, out)::
|
||||||
|
|
||||||
|
The current that the motor is currently drawing from the VFD,
|
||||||
|
in Amperes.
|
||||||
|
|
||||||
|
*hsvd-ps_vfd.output-power* (float, out)::
|
||||||
|
|
||||||
|
The power that the motor is currently drawing from the VFD,
|
||||||
|
in Watts.
|
||||||
|
|
||||||
|
*svd-ps_vfd.dc-bus-voltage* (float, out)::
|
||||||
|
|
||||||
|
The current voltage of the VFD's internal DC power supply, in Volts.
|
||||||
|
|
||||||
|
*svd-ps_vfd.modbus-errors* (u32, out)::
|
||||||
|
|
||||||
|
A count of the number of modbus communication errors between the
|
||||||
|
driver and the VFD. The driver is resilient against communication
|
||||||
|
errors, but a large or growing number here indicates a problem
|
||||||
|
that should be investigated.
|
||||||
|
|
||||||
|
*svd-ps_vfd.input-terminal* (float, out)::
|
||||||
|
|
||||||
|
The VFD's input terminal register.
|
||||||
|
|
||||||
|
*svd-ps_vfd.AI1* (float, out)::
|
||||||
|
|
||||||
|
The VFD's AI1 register.
|
||||||
|
|
||||||
|
*svd-ps_vfd.AI2* (float, out)::
|
||||||
|
|
||||||
|
The VFD's AI2 register.
|
||||||
|
|
||||||
|
== ISSUES
|
||||||
|
|
@ -44,6 +44,19 @@ $(call TOOBJSDEPS, $(HY_GT_SRCS)) : EXTRAFLAGS += $(HY_GT_CCFLAGS)
|
||||||
USERSRCS += $(HY_GT_SRCS)
|
USERSRCS += $(HY_GT_SRCS)
|
||||||
TARGETS += ../bin/hy_gt_vfd
|
TARGETS += ../bin/hy_gt_vfd
|
||||||
|
|
||||||
|
SVD_PS_SRCS := hal/user_comps/svd-ps_vfd.c
|
||||||
|
SVD_PS_LDFLAGS := $(LIBMODBUS_LIBS)
|
||||||
|
SVD_PS_CCFLAGS := $(LIBMODBUS_CFLAGS)
|
||||||
|
|
||||||
|
$(call TOOBJSDEPS, $(SVD_PS_SRCS)) : EXTRAFLAGS += $(SVD_PS_CCFLAGS)
|
||||||
|
|
||||||
|
../bin/svd-ps_vfd: $(call TOOBJS, $(SVD_PS_SRCS)) ../lib/liblinuxcnchal.so.0
|
||||||
|
$(ECHO) Linking $(notdir $@)
|
||||||
|
$(Q)$(CC) -o $@ $^ $(LDFLAGS) $(SVD_PS_LDFLAGS)
|
||||||
|
|
||||||
|
USERSRCS += $(SVD_PS_SRCS)
|
||||||
|
TARGETS += ../bin/svd-ps_vfd
|
||||||
|
|
||||||
endif # HAVE_LIBMODBUS
|
endif # HAVE_LIBMODBUS
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
617
src/hal/user_comps/svd-ps_vfd.c
Normal file
617
src/hal/user_comps/svd-ps_vfd.c
Normal file
|
|
@ -0,0 +1,617 @@
|
||||||
|
/*
|
||||||
|
svd-ps_vfd.c
|
||||||
|
|
||||||
|
This is a userspace program that interfaces the Soyan Power SVD-PS VFDs
|
||||||
|
to the LinuxCNC HAL.
|
||||||
|
|
||||||
|
Copyright (C) 2020 Tinic Uro
|
||||||
|
Copyright (C) 2017 Sebastian Kuzminsky
|
||||||
|
|
||||||
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301-1307 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <modbus.h>
|
||||||
|
|
||||||
|
#include "hal.h"
|
||||||
|
#include "rtapi.h"
|
||||||
|
|
||||||
|
|
||||||
|
// If a modbus transaction fails, retry this many times before giving up.
|
||||||
|
#define NUM_MODBUS_RETRIES 5
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
hal_float_t *period;
|
||||||
|
|
||||||
|
hal_float_t *speed_cmd;
|
||||||
|
hal_float_t *freq_cmd;
|
||||||
|
hal_bit_t *at_speed;
|
||||||
|
|
||||||
|
hal_bit_t *spindle_on;
|
||||||
|
|
||||||
|
hal_u32_t *modbus_errors;
|
||||||
|
} haldata_t;
|
||||||
|
|
||||||
|
haldata_t *haldata;
|
||||||
|
|
||||||
|
int hal_comp_id;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int address; // 0-0xffff for a register, -1 for end-of-list
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
// Multiply the uint16 value read by this multiplier to get the human-
|
||||||
|
// readable value.
|
||||||
|
float multiplier;
|
||||||
|
|
||||||
|
hal_float_t *hal_pin;
|
||||||
|
} modbus_register_t;
|
||||||
|
|
||||||
|
int num_modbus_registers = 0;
|
||||||
|
modbus_register_t *modbus_register;
|
||||||
|
|
||||||
|
|
||||||
|
static int done;
|
||||||
|
char *modname = "svd-ps_vfd";
|
||||||
|
|
||||||
|
float motor_max_speed = 0.0;
|
||||||
|
float max_freq = 0.0;
|
||||||
|
float min_freq = 0.0;
|
||||||
|
|
||||||
|
int baud;
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"device", 1, 0, 'd'},
|
||||||
|
{"rate", 1, 0, 'r'},
|
||||||
|
{"bits", 1, 0, 'b'},
|
||||||
|
{"parity", 1, 0, 'p'},
|
||||||
|
{"stopbits", 1, 0, 's'},
|
||||||
|
{"target", 1, 0, 't'},
|
||||||
|
{"verbose", 0, 0, 'v'},
|
||||||
|
{"help", 0, 0, 'h'},
|
||||||
|
{"motor-max-speed", 1, 0, 'S'},
|
||||||
|
{"max-frequency", 1, 0, 'F'},
|
||||||
|
{"min-frequency", 1, 0, 'f'},
|
||||||
|
{0,0,0,0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *option_string = "d:r:b:p:s:t:vhS:F:f:";
|
||||||
|
|
||||||
|
static char *bitstrings[] = {"5", "6", "7", "8", NULL};
|
||||||
|
|
||||||
|
static char *paritystrings[] = {"even", "odd", "none", NULL};
|
||||||
|
static char paritychars[] = {'E', 'O', 'N'};
|
||||||
|
|
||||||
|
static char *ratestrings[] = {"1200", "2400", "4800", "9600", "19200", "38400", NULL};
|
||||||
|
static char *stopstrings[] = {"1", "2", NULL};
|
||||||
|
|
||||||
|
|
||||||
|
static void quit(int sig) {
|
||||||
|
done = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int match_string(char *string, char **matches) {
|
||||||
|
int len, which, match;
|
||||||
|
which=0;
|
||||||
|
match=-1;
|
||||||
|
if ((matches==NULL) || (string==NULL)) return -1;
|
||||||
|
len = strlen(string);
|
||||||
|
while (matches[which] != NULL) {
|
||||||
|
if ((!strncmp(string, matches[which], len)) && (len <= strlen(matches[which]))) {
|
||||||
|
if (match>=0) return -1; // multiple matches
|
||||||
|
match=which;
|
||||||
|
}
|
||||||
|
++which;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void usage(int argc, char **argv) {
|
||||||
|
printf("Usage: %s [ARGUMENTS]\n", argv[0]);
|
||||||
|
printf(
|
||||||
|
"\n"
|
||||||
|
"This program interfaces the Soyan Power SVD-PS VFD to the LinuxCNC HAL.\n"
|
||||||
|
"\n"
|
||||||
|
"Required arguments:\n"
|
||||||
|
" -S, --motor-max-speed SPEED\n"
|
||||||
|
" The motor's max speed in RPM.\n"
|
||||||
|
" -F, --max-frequency F\n"
|
||||||
|
" This is the maximum output frequency of the VFD in Hz.\n"
|
||||||
|
" -f, --min-frequency F\n"
|
||||||
|
" This is the minimum output frequency of the VFD in Hz.\n"
|
||||||
|
"\n"
|
||||||
|
"Optional arguments:\n"
|
||||||
|
" -d, --device PATH (default /dev/ttyS0)\n"
|
||||||
|
" Set the name of the serial device to use.\n"
|
||||||
|
" -r, --rate RATE (default 9600)\n"
|
||||||
|
" Set baud rate to RATE. It is an error if the rate is not one of\n"
|
||||||
|
" the following: 1200, 2400, 4800, 9600, 19200, 38400\n"
|
||||||
|
" -b, --bits BITS (default 8)\n"
|
||||||
|
" Set number of data bits to BITS, must be between 5 and 8 inclusive.\n"
|
||||||
|
" -p, --parity PARITY (default none)\n"
|
||||||
|
" Set serial parity to one of 'even', 'odd', or 'none'.\n"
|
||||||
|
" -s, --stopbits {1,2} (default 2)\n"
|
||||||
|
" Set serial stop bits to 1 or 2.\n"
|
||||||
|
" -t, --target TARGET (default 1)\n"
|
||||||
|
" Set Modbus target (slave) number. This must match the device\n"
|
||||||
|
" number you set on the SVD-PS VFD.\n"
|
||||||
|
" -v, --verbose\n"
|
||||||
|
" Turn on verbose mode.\n"
|
||||||
|
" -h, --help\n"
|
||||||
|
" Show this help.\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void svd_modbus_sleep(void) {
|
||||||
|
float seconds_per_bit = 1.0 / (float)baud;
|
||||||
|
useconds_t useconds_per_bit = seconds_per_bit * 1000 * 1000;
|
||||||
|
useconds_t delay = useconds_per_bit * 8 * 5;
|
||||||
|
usleep(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int set_motor_on_forward(modbus_t *mb) {
|
||||||
|
int r;
|
||||||
|
uint16_t addr = 0x2000;
|
||||||
|
uint16_t val = 1;
|
||||||
|
|
||||||
|
for (int retries = 0; retries <= NUM_MODBUS_RETRIES; retries ++) {
|
||||||
|
svd_modbus_sleep();
|
||||||
|
r = modbus_write_register(mb, addr, val);
|
||||||
|
if (r == 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%s: error writing %u to register 0x%04x: %s\n", __func__, val, addr, modbus_strerror(errno));
|
||||||
|
*haldata->modbus_errors = *haldata->modbus_errors + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_motor_off(modbus_t *mb) {
|
||||||
|
int r;
|
||||||
|
uint16_t addr = 0x2000;
|
||||||
|
uint16_t val = 5;
|
||||||
|
|
||||||
|
for (int retries = 0; retries <= NUM_MODBUS_RETRIES; retries ++) {
|
||||||
|
svd_modbus_sleep();
|
||||||
|
r = modbus_write_register(mb, addr, val);
|
||||||
|
if (r == 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%s: error writing %u to register 0x%04x: %s\n", __func__, val, addr, modbus_strerror(errno));
|
||||||
|
*haldata->modbus_errors = *haldata->modbus_errors + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int set_motor_frequency(modbus_t *mb, float freq) {
|
||||||
|
int r;
|
||||||
|
uint16_t addr = 0x1000;
|
||||||
|
uint16_t val; // 100 * the frequency percentage
|
||||||
|
val = (freq / max_freq) * 10000;
|
||||||
|
|
||||||
|
for (int retries = 0; retries <= NUM_MODBUS_RETRIES; retries ++) {
|
||||||
|
svd_modbus_sleep();
|
||||||
|
r = modbus_write_register(mb, addr, val);
|
||||||
|
if (r == 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%s: error writing %u to register 0x%04x: %s\n", __func__, val, addr, modbus_strerror(errno));
|
||||||
|
*haldata->modbus_errors = *haldata->modbus_errors + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int read_modbus_register(modbus_t *mb, modbus_register_t *reg) {
|
||||||
|
uint16_t data;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
for (int retries = 0; retries <= NUM_MODBUS_RETRIES; retries ++) {
|
||||||
|
svd_modbus_sleep();
|
||||||
|
r = modbus_read_registers(mb, reg->address, 1, &data);
|
||||||
|
if (r == 1) {
|
||||||
|
*reg->hal_pin = data * reg->multiplier;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%s: error reading %s (register 0x%04x): %s\n", __func__, reg->name, reg->address, modbus_strerror(errno));
|
||||||
|
*haldata->modbus_errors = *haldata->modbus_errors + 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void read_modbus_registers(modbus_t *mb) {
|
||||||
|
for (int i = 0; i < num_modbus_registers; i ++) {
|
||||||
|
(void)read_modbus_register(mb, &modbus_register[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
modbus_register_t *add_modbus_register(modbus_t *mb, int address, const char *pin_name, float multiplier) {
|
||||||
|
int r;
|
||||||
|
modbus_register_t *reg;
|
||||||
|
|
||||||
|
reg = &modbus_register[num_modbus_registers];
|
||||||
|
|
||||||
|
r = hal_pin_float_newf(HAL_OUT, ®->hal_pin, hal_comp_id, "%s.%s", modname, pin_name);
|
||||||
|
if (r != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
reg->address = address;
|
||||||
|
reg->name = pin_name;
|
||||||
|
reg->multiplier = multiplier;
|
||||||
|
|
||||||
|
read_modbus_register(mb, reg);
|
||||||
|
|
||||||
|
num_modbus_registers ++;
|
||||||
|
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
char *device;
|
||||||
|
int bits;
|
||||||
|
char parity;
|
||||||
|
int stopbits;
|
||||||
|
int verbose;
|
||||||
|
|
||||||
|
int retval = 0;
|
||||||
|
modbus_t *mb;
|
||||||
|
int slave;
|
||||||
|
struct timespec period_timespec;
|
||||||
|
char *endarg;
|
||||||
|
int opt;
|
||||||
|
int argindex, argvalue;
|
||||||
|
|
||||||
|
modbus_register_t *speed_fb_reg;
|
||||||
|
|
||||||
|
done = 0;
|
||||||
|
|
||||||
|
// assume that nothing is specified on the command line
|
||||||
|
device = "/dev/ttyS0";
|
||||||
|
baud = 9600;
|
||||||
|
bits = 8;
|
||||||
|
parity = 'N';
|
||||||
|
stopbits = 2;
|
||||||
|
|
||||||
|
verbose = 0;
|
||||||
|
|
||||||
|
/* slave / register info */
|
||||||
|
slave = 1;
|
||||||
|
|
||||||
|
// process command line options
|
||||||
|
while ((opt = getopt_long(argc, argv, option_string, long_options, NULL)) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'd': // device name, default /dev/ttyS0
|
||||||
|
// could check the device name here, but we'll leave it to the library open
|
||||||
|
if (strlen(optarg) > FILENAME_MAX) {
|
||||||
|
printf("ERROR: device node name is too long: %s\n", optarg);
|
||||||
|
retval = -1;
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
device = strdup(optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r': // Baud rate, 9600 default
|
||||||
|
argindex=match_string(optarg, ratestrings);
|
||||||
|
if (argindex<0) {
|
||||||
|
printf("ERROR: invalid baud rate: %s\n", optarg);
|
||||||
|
retval = -1;
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
baud = atoi(ratestrings[argindex]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b': // serial data bits, probably should be 8 (and defaults to 8)
|
||||||
|
argindex=match_string(optarg, bitstrings);
|
||||||
|
if (argindex<0) {
|
||||||
|
printf("ERROR: invalid number of bits: %s\n", optarg);
|
||||||
|
retval = -1;
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
bits = atoi(bitstrings[argindex]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p': // parity, should be a string like "even", "odd", or "none"
|
||||||
|
argindex=match_string(optarg, paritystrings);
|
||||||
|
if (argindex<0) {
|
||||||
|
printf("ERROR: invalid parity: %s\n", optarg);
|
||||||
|
retval = -1;
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
parity = paritychars[argindex];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's': // stop bits, defaults to 2
|
||||||
|
argindex=match_string(optarg, stopstrings);
|
||||||
|
if (argindex<0) {
|
||||||
|
printf("ERROR: invalid number of stop bits: %s\n", optarg);
|
||||||
|
retval = -1;
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
stopbits = atoi(stopstrings[argindex]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't': // target number (MODBUS ID), default 1
|
||||||
|
argvalue = strtol(optarg, &endarg, 10);
|
||||||
|
if ((*endarg != '\0') || (argvalue < 1) || (argvalue > 254)) {
|
||||||
|
printf("ERROR: invalid slave number: %s\n", optarg);
|
||||||
|
retval = -1;
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
slave = argvalue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
verbose = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
usage(argc, argv);
|
||||||
|
exit(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
motor_max_speed = strtof(optarg, &endarg);
|
||||||
|
if ((*endarg != '\0') || (motor_max_speed == 0.0)) {
|
||||||
|
printf("%s: ERROR: invalid motor max speed: %s\n", modname, optarg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'F':
|
||||||
|
max_freq = strtof(optarg, &endarg);
|
||||||
|
if ((*endarg != '\0') || (max_freq == 0.0)) {
|
||||||
|
printf("%s: ERROR: invalid max freq: %s\n", modname, optarg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
min_freq = strtof(optarg, &endarg);
|
||||||
|
if ((*endarg != '\0') || (min_freq == 0.0)) {
|
||||||
|
printf("%s: ERROR: invalid min freq: %s\n", modname, optarg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
usage(argc, argv);
|
||||||
|
exit(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (motor_max_speed == 0.0) {
|
||||||
|
printf("%s: must specify --motor-max-speed\n", modname);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_freq == 0.0) {
|
||||||
|
printf("%s: must specify --max-frequency\n", modname);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min_freq == 0.0) {
|
||||||
|
printf("%s: must specify --min-frequency\n", modname);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min_freq > max_freq) {
|
||||||
|
printf("%s: min frequency (%f) must be less than max frequency (%f)\n", modname, min_freq, max_freq);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
struct sigaction sa;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
sa.sa_handler = quit;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
|
||||||
|
// libmodbus sometimes segfaults if its system calls get
|
||||||
|
// interrupted by signals. This works around it by automatically
|
||||||
|
// restarting the system call instead of having it return EINTR.
|
||||||
|
sa.sa_flags = SA_RESTART;
|
||||||
|
|
||||||
|
r = sigaction(SIGINT, &sa, NULL);
|
||||||
|
if (r != 0) {
|
||||||
|
printf("failed to set SIGINT handler: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
r = sigaction(SIGTERM, &sa, NULL);
|
||||||
|
if (r != 0) {
|
||||||
|
printf("failed to set SIGTERM handler: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s: device='%s', baud=%d, parity='%c', bits=%d, stopbits=%d, address=%d\n",
|
||||||
|
modname, device, baud, parity, bits, stopbits, slave);
|
||||||
|
|
||||||
|
mb = modbus_new_rtu(device, baud, parity, bits, stopbits);
|
||||||
|
if (mb == NULL) {
|
||||||
|
printf("%s: ERROR: couldn't open modbus serial device: %s\n", modname, modbus_strerror(errno));
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
struct timeval t;
|
||||||
|
|
||||||
|
// Set the response timeout.
|
||||||
|
t.tv_sec = 0;
|
||||||
|
t.tv_usec = 30 * 1000;
|
||||||
|
#if (LIBMODBUS_VERSION_CHECK(3, 1, 2))
|
||||||
|
modbus_set_response_timeout(mb, t.tv_sec, t.tv_usec);
|
||||||
|
#else
|
||||||
|
modbus_set_response_timeout(mb, &t);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Disable the byte timeout so it just waits for the complete
|
||||||
|
// response timeout instead.
|
||||||
|
#if (LIBMODBUS_VERSION_CHECK(3, 1, 2))
|
||||||
|
t.tv_sec = 0;
|
||||||
|
t.tv_usec = 0;
|
||||||
|
modbus_set_byte_timeout(mb, t.tv_sec, t.tv_usec);
|
||||||
|
#else
|
||||||
|
t.tv_sec = -1;
|
||||||
|
modbus_set_byte_timeout(mb, &t);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = modbus_connect(mb);
|
||||||
|
if (retval != 0) {
|
||||||
|
printf("%s: ERROR: couldn't open serial device: %s\n", modname, modbus_strerror(errno));
|
||||||
|
goto out_noclose;
|
||||||
|
}
|
||||||
|
|
||||||
|
modbus_set_debug(mb, verbose);
|
||||||
|
|
||||||
|
modbus_set_slave(mb, slave);
|
||||||
|
|
||||||
|
/* create HAL component */
|
||||||
|
hal_comp_id = hal_init(modname);
|
||||||
|
if (hal_comp_id < 0) {
|
||||||
|
printf("%s: ERROR: hal_init failed\n", modname);
|
||||||
|
retval = hal_comp_id;
|
||||||
|
goto out_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
haldata = (haldata_t *)hal_malloc(sizeof(haldata_t));
|
||||||
|
if (haldata == NULL) {
|
||||||
|
printf("%s: ERROR: unable to allocate shared memory\n", modname);
|
||||||
|
retval = -1;
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = hal_pin_float_newf(HAL_IN, &(haldata->period), hal_comp_id, "%s.period-seconds", modname);
|
||||||
|
if (retval != 0) goto out_closeHAL;
|
||||||
|
|
||||||
|
retval = hal_pin_float_newf(HAL_IN, &(haldata->speed_cmd), hal_comp_id, "%s.speed-cmd", modname);
|
||||||
|
if (retval != 0) goto out_closeHAL;
|
||||||
|
|
||||||
|
retval = hal_pin_float_newf(HAL_OUT, &(haldata->freq_cmd), hal_comp_id, "%s.freq-cmd", modname);
|
||||||
|
if (retval != 0) goto out_closeHAL;
|
||||||
|
|
||||||
|
retval = hal_pin_bit_newf(HAL_OUT, &(haldata->at_speed), hal_comp_id, "%s.at-speed", modname);
|
||||||
|
if (retval != 0) goto out_closeHAL;
|
||||||
|
|
||||||
|
retval = hal_pin_bit_newf(HAL_IN, &(haldata->spindle_on), hal_comp_id, "%s.spindle-on", modname);
|
||||||
|
if (retval != 0) goto out_closeHAL;
|
||||||
|
|
||||||
|
retval = hal_pin_u32_newf(HAL_OUT, &(haldata->modbus_errors), hal_comp_id, "%s.modbus-errors", modname);
|
||||||
|
if (retval != 0) goto out_closeHAL;
|
||||||
|
|
||||||
|
*haldata->period = 0.1;
|
||||||
|
|
||||||
|
*haldata->freq_cmd = 0.0;
|
||||||
|
*haldata->at_speed = 0;
|
||||||
|
|
||||||
|
*haldata->modbus_errors = 0;
|
||||||
|
|
||||||
|
modbus_register = (modbus_register_t *)hal_malloc(20 * sizeof(modbus_register_t));
|
||||||
|
if (modbus_register == NULL) {
|
||||||
|
printf("%s: ERROR: unable to allocate memory\n", modname);
|
||||||
|
retval = -1;
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x1001, "freq-fb", 0.01) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x1002, "dc-bus-voltage", 0.1) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x1003, "output-voltage", 1.0) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x1004, "output-current", 1.0) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
speed_fb_reg = add_modbus_register(mb, 0x1005, "speed-fb", 1.0);
|
||||||
|
if (speed_fb_reg == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x1006, "output-power", 1.0) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x1009, "input-terminal", 1) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x100a, "AI1", 0.001) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
if (add_modbus_register(mb, 0x100b, "AI2", 0.001) == NULL) {
|
||||||
|
goto out_closeHAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate HAL component
|
||||||
|
hal_ready(hal_comp_id);
|
||||||
|
|
||||||
|
while (done == 0) {
|
||||||
|
if (*haldata->period < 0.001) *haldata->period = 0.001;
|
||||||
|
if (*haldata->period > 2.0) *haldata->period = 2.0;
|
||||||
|
period_timespec.tv_sec = (time_t)(*haldata->period);
|
||||||
|
period_timespec.tv_nsec = (long)((*haldata->period - period_timespec.tv_sec) * 1000000000l);
|
||||||
|
nanosleep(&period_timespec, NULL);
|
||||||
|
|
||||||
|
read_modbus_registers(mb);
|
||||||
|
|
||||||
|
if (*haldata->spindle_on) {
|
||||||
|
set_motor_on_forward(mb);
|
||||||
|
*haldata->freq_cmd = (*haldata->speed_cmd / motor_max_speed) * max_freq;
|
||||||
|
set_motor_frequency(mb, *haldata->freq_cmd);
|
||||||
|
|
||||||
|
if ((fabs(*haldata->speed_cmd - *speed_fb_reg->hal_pin) / *haldata->speed_cmd) < 0.02) {
|
||||||
|
*haldata->at_speed = 1;
|
||||||
|
} else {
|
||||||
|
*haldata->at_speed = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
set_motor_off(mb);
|
||||||
|
*haldata->at_speed = 0;
|
||||||
|
*haldata->freq_cmd = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(10 * 1000);
|
||||||
|
set_motor_off(mb);
|
||||||
|
|
||||||
|
retval = 0; /* if we get here, then everything is fine, so just clean up and exit */
|
||||||
|
out_closeHAL:
|
||||||
|
hal_exit(hal_comp_id);
|
||||||
|
out_close:
|
||||||
|
modbus_close(mb);
|
||||||
|
modbus_free(mb);
|
||||||
|
out_noclose:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue