Ciao a tutti,
vorrei condividere un driver che ho scritto e che potrebbe essere utile ad altri.
Serve a controllare, da LinuxCNC, il motore principale di una fresa o un tornio Wabeco nella versione 'hs', ovvero equipaggiati di un motore da 2kW invece che quello standard da 1.4kW.
Più in generale, per controllare un Varicon ("Three-Phase Motor with Integrated Frequency Converter") della ditta Hanning Elektro-Werke GmbH, che poi sarebbe il motore che entrambe le macchine della Wabeco montano.
Premessa:un paio di anni fa ho acquistato una fresa e un tornio Wabeco, entrambi nella versione 'hs'.
Dopo aver costruito un sistema per il controllo degli assi per poter utilizzare LinuxCNC (entrambe le macchine erano già dotate di motori passo) e aver installato sul tornio un decoder sull'asse del mandrino (questi potrebbero essere argomenti di altri post; se qualcuno è interessato mi faccia sapere), mi mancava solo realizzare il controllo del motore principale.
Il motore Hanning Varicon è dotato di un box di controllo che prevede un deviatore per la selezione del movimento (CW o CCW) e di un potenziometro per l'impostazione della velocità di rotazione.
Dal manuale delle macchine ho visto che il controllo della velocità veniva fatto con una tensione da 0 a 10 volt e del movimento con un paio di segnali a 24 volt. Questo però mi avrebbe imposto l'uso di relè e cablaggi a vari fili per selezionare da PC la macchina da controllare e per scegliere la modalità di controllo, manuale o da programma. Una vera scocciatura per un pigro come me!
Aprendo il box ho notato con piacere la presenza di un connettore che suggeriva la possibilità di collegamenti esterni.
Allegato:
Box prima.jpg
Ho scritto alla Wabeco che mi ha gentilmente risposto che non aveva niente di più dello schema già presente nei manuali
. Allora mi sono messo alla ricerca su Internet e alla fine ho trovato un pdf con le istruzioni:
https://www.cncitalia.net/forum/www.gather-industrie.fr/wp-content/uploads/2015/09/Varicon-1.2-BA-OI_65158-NEUE-BAUREIHE-08_2007-engl.pdfda cui ho visto che il motore può essere controllato anche tramite una linea seriale RS485!
La ricerca si è orientata quindi a reperire qualcosa di già fatto. Purtroppo con nessun esito!
Gli unici driver HAL seriali che si trovano per LinuxCNC sono per variatori di velocità con controllo ModBus.
A questo punto ho preso la decisione di scrivermi il driver che mi serviva. Ho selezionato il driver esistente che più si avvicinava alle mie esigenze, ovvero 'Mitsub VFD Driver' per controllo di VFD Mitsubishi, scritto in Python nel 2017 da Chris Morley:
http://linuxcnc.org/docs/html/drivers/mitsub_vfd.html e l'ho modificato per adattarsi al protocollo di comunicazione utilizzato dal Varicon.
Il collegamento fisico l'ho realizzato utilizzando un semplice cavetto bipolare schermato e un adattatore USB seriale RS485 della Moxa (UPort 1130), mettendo in parallelo le due macchine in modalità 'RS485 a 2 fili'. Fortunatamente i box presenti sulle macchine prevedono dei 'tappi' per uscite di cavi ausiliari, che ho prontamente utilizzato!
Allegato:
Box dopo.jpg
Il drive, scritto in Python, mette a disposizione una serie di 'pin' (do per scontato una conoscenza base dell'HAL di LinuxCNC) di input e di output che permettono il controllo del motore aggiungendo alcune linee al file <xxx>.hal della configurazione utilizzata.
Questo è l'elenco dei pin del driver:
Codice:
Nome pin Tipo Dir Significato
---------------- ----- ---- -------------------------------------------------------------
fwd BIT IN Impostare a True per rotazione destrorsa
run BIT IN Impostare a True per avviare il motore
debug BIT IN Impostare a True per abilitare i messaggi diagnostici
enabled BIT IN Impostare a True per abilitare il controllo via RS485 (Reg 000, 001 and 002)
monitor BIT IN Impostare a True per abilitare l'aggiornamento continuo dello stato
motion-reversed BIT IN Impostare a True se la rotazione del motore e' invertita (es. nella fresa Wabeco)
speed-scale FLOAT IN Configurare questo valore per impostare il rapporto di trasmissione meccanico
motor-speed-rpm FLOAT IN Modificare questo valore per impostare la velocita' desiderata, in RPM
motor-fb-rpm FLOAT OUT Velocita' attuale del motore, in RPM
motor-fb-rps FLOAT OUT Velocita' attuale del motore, in RPS
motor-amps FLOAT OUT Valore della corrente di assorbimento del motore, in ampere
motor-amps-enab BIT IN Impostare a True per aggiornare il valore di 'motor-amps' (quando anche 'monitor' e' True)
estop BIT IN Impostare a True per eseguire uno stop di emergenza
error-code U32 OUT Valore del codice di errore, quando 'sts-gen-error' e' True
sts-temp-warn BIT OUT Avviso di temperatura: 10'C o meno sotto il limite di blocco
sts-temp-rise BIT OUT Temperatura dell'inverter in aumento
sts-brake-stop BIT OUT Freno motore disattivato (tensione troppo elevata)
sts-chopper-on BIT OUT Chopper attivo
sts-enabled BIT OUT Abilitazione negata (pin #14 del connettore a 0)
sts-clkpuls-on BIT OUT Impulsi di clock attivi
sts-gen-error BIT OUT Errore generale (vedi 'error-code')
sts-standstill BIT OUT Frequenza 0 / motore fermo
sts-speed-ok BIT OUT Velocità impostata raggiunta
sts-rot-ccw BIT OUT Direzione di rotazione sinistrorsa (negativa)
sts-pwr-115v BIT OUT Alimentazione principale a 115 V
sts-pwr-400v BIT OUT Alimentazione principale a 400 V
Per esempio, il mio file fresa.hal contiene le seguenti linee:
Codice:
loadusr -Wn spindle hanning_vfd.py -p /dev/ttyUSB0 spindle=32
# ********** mill spindle vfd setup (slave 32) *************
net spindle-vel-cmd-rpm-abs <= motion.spindle-speed-out-abs
net spindle-vel-cmd-rpm-abs => spindle.motor-speed-rpm
net spindle-enable <= motion.spindle-on
net spindle-enable => spindle.run
net spindle-cw <= motion.spindle-forward
net spindle-cw => spindle.fwd
net spindle-at-speed <= spindle.sts-speed-ok
net spindle-at-speed => motion.spindle-at-speed
net spindle-speed-rps <= spindle.motor-fb-rps
net spindle-speed-rps => motion.spindle-speed-in
net machine-is-enabled <= motion.motion-enabled
net machine-is-enabled => spindle.monitor
net machine-is-enabled => spindle.enabled
net estop-out <= iocontrol.0.user-enable-out
net estop-out => iocontrol.0.emc-enable-in
net estop-out => spindle.estop
net spindle-error-condition <= spindle.sts-gen-error
net spindle-error-code <= spindle.error-code
net spindle-current <= spindle.motor-amps
# --- config mill spindle pins ---
setp spindle.speed-scale 1.46154 # 38/26
setp spindle.debug 0
setp spindle.motor-amps-enab 0
setp spindle.motion-reversed 1
Per installare il driver si procede come segue:
Hardware:> procurarsi un cavo coassiale a due fili + calza, tipo quello usati per i microfoni lungo a sufficenza per collegare il Varicon al PC e un connettore femmina volante DB9 a vaschetta;
> saldare quello che sarà il filo del segnale A (il positivo dell'RS485) al terminale 3, il filo del segnale B (negativo) al 4 e la calza al 5;
> aprire il box del varicon e infilare l'altro capo del cavo coassiale in uno dei fori ausiliari previsti nel box, quindi collegare il filo del segnale A al terminale 1 del connettore grigio a 15 posizioni, il segnale B al terminale 2 e la calza al terminale 6.
NOTA: in teoria un collegamento RS485 dovrebbe essere terminato alle sue estremità su una resistenza da 120 ohm. Se il collegamento non è troppo lungo si può anche evitare di farlo; se invece volete essere tranquilli si può inserire una resistenza da 120 ohm tra i terminali 1 e 2 dentro al box. Io ho preferito metterla in quanto ho collegato le due macchine tra loro con un cavo non tanto corto (vedi foto)
> richiudere il tutto facendo passare il cavo dove dia fastidio per collegarlo all'adattatore RS485 USB.
Software:> verificare, se si è editato il file del driver sotto Windows, che il formato degli 'a capo' sia quello Unix, cioè solo un LF a fine linea e non CR+LF usato da Windows;
> copiare il file in una directory inclusa nel PATH; io ho usato /usr/local/bin. Per copiare il file in questa directory occorre avere i diritti di 'root', cioè utilizzare il comando 'sudo';
> collegare l'adattatore seriale RS485 ad una porta USB del PC;
> identificare il nome del dispositivo dell'adattatore RS485 collegato, utilizzando comandi linux tipo
Codice:
ls /dev | grep USB
Codice:
echo -n "/dev/";dmesg|grep tty|grep USB|rev|awk '{print $1}'|rev
Sul mio PC è '/dev/ttyUSB0'.
> modificare il file .hal della configurazione di LinuxCNC utilizzata, includendo linee tipo quelle riportate sopra (esempio 'fresa.hal') e verificando che il nome dell'adattatore RS485 nel comando 'loadusr' sia impostato correttamente;
> verificare anche i valori di configurazione dei pin 'spindle.speed-scale' e 'spindle.motion-reversed'; per me hanno questi valori:
fresa:
Codice:
setp spindle.speed-scale 1.46154
setp spindle.motion-reversed 1
tornio:
Codice:
setp spindle.speed-scale 1
setp spindle.motion-reversed 0
Note aggiuntive:Il Varicon è configurato di default per utilizzare l'indirizzo 32 e un baudrate di 9600.
I valori dell'indirizzo possono andare da 32 a 63 e il baudrate massimo è di 19200.
Se si collegano più macchine in parallelo, come ho fatto io, occorre modificare gli indirizzi in modo che siano tutti diversi tra loro!
Per il baudrate ho deciso di utilizzare quello di default, ma nel caso si volesse modificare, portandolo a 19200 baud, occorrerà modificarlo sia sulle macchine che specificndolo come parametro sempre nel comando 'loadusr'.
Per la modifica dell'indirizzo e/o del baudrate sul Varicon, si deve seguire un procedura un po' più complessa; se qualcuno fosse interessato sono più che disponibile a fornire i dettagli.
------------------------------------------------------------------------------------------------------------------
E finalmente, il driver
hanning_vfd.py:
Codice:
#!/usr/bin/env python
# This is a component of linuxcnc
# hanning_vfd - Copyright 2020 by Franco Basso
# version 1.0 - 2020-03-02
# (based on mitsub_vfd, by Chris Morley)
#
#----------------------------------------------------------------------------------
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#----------------------------------------------------------------------------------
#
# LinuxCNC user space component for controlling a Hanning Varicon inverter
# over the serial port using RS485 standard; this model of inverter
# is integrated in 2kW motors used in Wabeco lathes and mills ('hs' series)
#
# This driver is based on the Hanning "Varicon Operating Instructions"
# manual, version 1.2 (08/2007), 222 pages.
#
import re
import hal
import time
import serial
import traceback
SLEEPTIME = 0.03 # delay between tx and rx messages
HannReg = { # Varicon registers used by this driver
'mode': 0,
'direction': 1,
'speed_0': 2,
'speed': 3,
'setspeed': 22,
'errorcode': 23,
'status': 51,
'I_motor': 156
}
class hanning_serial:
def __init__ (self, vfd_names = [['hanning_vfd', '32']], baudrate = 9600, port = '/dev/ttyUSB0'):
try:
self.ser = serial.Serial (
port,
baudrate,
parity = serial.PARITY_EVEN,
stopbits = serial.STOPBITS_ONE,
bytesize = serial.SEVENBITS
)
self.ser.timeout = 0.1
self.ser.is_open
except:
print ('ERROR : hanning_vfd - No serial interface found at {0}'.format (port))
raise SystemExit
print ('Hanning VFD serial computer link has loaded')
print ('Port: {0},{1},7,E,1'.format (port, baudrate))
self.h = []
self.comp_names = vfd_names
for index, name in enumerate (self.comp_names):
# print (index, ' NAME:', name[0], ' SLAVE:', name[1])
c = hal.component (name[0])
c.newpin ('fwd', hal.HAL_BIT, hal.HAL_IN) # Set to True for forward motion
c.newpin ('run', hal.HAL_BIT, hal.HAL_IN) # Set to True to start the spindle
c.newpin ('debug', hal.HAL_BIT, hal.HAL_IN) # Set to True to enable debug output messages
c.newpin ('enabled', hal.HAL_BIT, hal.HAL_IN) # Set to True to enable spindle control via RS485 (Reg 000, 001 and 002)
c.newpin ('monitor', hal.HAL_BIT, hal.HAL_IN) # Set to True to enable continuous status polling
c.newpin ('motion-reversed', hal.HAL_BIT, hal.HAL_IN) # Set to True if the spindle motion is reversed (i.e. in Wabeco mills)
c.newpin ('speed-scale', hal.HAL_FLOAT, hal.HAL_IN) # Configure this to set the ratio of mechanical transmission
c.newpin ('motor-speed-rpm', hal.HAL_FLOAT, hal.HAL_IN) # Modify this to set the desired spindle speed, in RPM
c.newpin ('motor-fb-rpm', hal.HAL_FLOAT, hal.HAL_OUT) # Value of actual spindle speed, in RPM
c.newpin ('motor-fb-rps', hal.HAL_FLOAT, hal.HAL_OUT) # Value of actual spindle speed, in RPS
c.newpin ('motor-amps', hal.HAL_FLOAT, hal.HAL_OUT) # Value of motor current, in ampere
c.newpin ('motor-amps-enab', hal.HAL_BIT, hal.HAL_IN) # Set to True to poll for 'motor-amps' value (when 'monitor' is True)
c.newpin ('estop', hal.HAL_BIT, hal.HAL_IN) # Set to True to do an emergecy stop
c.newpin ('rx-error-cnt', hal.HAL_U32, hal.HAL_IO) # Number of communication errors
c.newpin ('error-code', hal.HAL_U32, hal.HAL_OUT) # Value of error code, when 'sts-gen-error' is True
c.newpin ('sts-temp-warn', hal.HAL_BIT, hal.HAL_OUT) # Temperature warning: 10'C or less below shutdown temperature
c.newpin ('sts-temp-rise', hal.HAL_BIT, hal.HAL_OUT) # Temperature rise inverter
c.newpin ('sts-brake-stop', hal.HAL_BIT, hal.HAL_OUT) # Brake ramp stopped (voltage too high)
c.newpin ('sts-chopper-on', hal.HAL_BIT, hal.HAL_OUT) # Chopper active
c.newpin ('sts-enabled', hal.HAL_BIT, hal.HAL_OUT) # Enable issued
c.newpin ('sts-clkpuls-on', hal.HAL_BIT, hal.HAL_OUT) # Clock pulse turned on
c.newpin ('sts-gen-error', hal.HAL_BIT, hal.HAL_OUT) # General error (see 'error-code')
c.newpin ('sts-standstill', hal.HAL_BIT, hal.HAL_OUT) # Frequency 0 / standstill
c.newpin ('sts-speed-ok', hal.HAL_BIT, hal.HAL_OUT) # Setspeed attained
c.newpin ('sts-rot-ccw', hal.HAL_BIT, hal.HAL_OUT) # Direction of rotation counter-clockwise (negative)
c.newpin ('sts-pwr-115v', hal.HAL_BIT, hal.HAL_OUT) # Device for 115 V mains voltage
c.newpin ('sts-pwr-400v', hal.HAL_BIT, hal.HAL_OUT) # Device for 400 V mains voltag
# set reasonable defaults
c['fwd'] = True
c['run'] = False
c['debug'] = False
c['enabled'] = False
c['monitor'] = False
c['motion-reversed'] = False
c['motor-speed-rpm'] = False
c['motor-amps-enab'] = False
c['estop'] = False
c['speed-scale'] = 1
c['rx-error-cnt'] = 0
# flags for each device
self['last_enabled%d'%index] = c['enabled']
self['last_run%d'%index] = c['run']
self['last_fwd%d'%index] = c['fwd']
self['last_cmd%d'%index] = c['motor-speed-rpm']
self['last_monitor%d'%index] = c['monitor']
self['last_estop%d'%index] = c['estop']
self['last_enq_code%d'%index] = 0
#add device to component reference variable
self.h.append (c)
print ('Hanning {0} VFD: slave# {1} added'.format (name[0], name[1]))
# only issue ready when all the components are ready
for i in self.h:
i.ready ()
def loop (self):
global SLEEPTIME, HannReg
while 1:
try:
for index, ids in enumerate (self.comp_names):
self.slave_num = int (ids[1])
# MONITOR for up-to-speed (sts-speed-ok), alarms and running frequency
# 51 is the address for 13 status bits ( b0 - b12 )
if self.h[index]['monitor']:
# empty the rx buffer
while self.ser.in_waiting > 0:
raw = self.ser.read (1)
# build the message with the status enquiry
msg = self.prepare_enquiry (index, HannReg['status'])
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
# and sends it
self.ser.write (msg)
# wait for the response
time.sleep (SLEEPTIME)
resp_ok, value, resp = self.poll_output (index)
if self.h[index]['debug']:
print ('DEBUG: Rx ' + repr (resp))
if resp_ok:
# print (value)
# decode the status word
try:
binary = '{0:#015b}'.format (value, 10)
except:
binary = '0b0000000000000'
self.h[index]['sts-temp-warn'] = ((int (binary, 2) & 2) != 0)
self.h[index]['sts-temp-rise'] = ((int (binary, 2) & 4) != 0)
self.h[index]['sts-brake-stop'] = ((int (binary, 2) & 8) != 0)
self.h[index]['sts-chopper-on'] = ((int (binary, 2) & 16) != 0)
self.h[index]['sts-enabled'] = ((int (binary, 2) & 32) != 0)
self.h[index]['sts-clkpuls-on'] = ((int (binary, 2) & 64) != 0)
self.h[index]['sts-gen-error'] = ((int (binary, 2) & 128) != 0)
self.h[index]['sts-standstill'] = ((int (binary, 2) & 256) != 0)
self.h[index]['sts-speed-ok'] = ((int (binary, 2) & 512) != 0)
self.h[index]['sts-rot-ccw'] = ((int (binary, 2) & 1024) != 0)
self.h[index]['sts-pwr-115v'] = ((int (binary, 2) & 2048) != 0)
self.h[index]['sts-pwr-400v'] = ((int (binary, 2) & 4096) != 0)
# if error, reads the error code
if self.h[index]['sts-gen-error']:
msg = self.prepare_enquiry (index, HannReg['errorcode'])
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp_ok, value, resp = self.poll_output (index)
if self.h[index]['debug']:
print ('DEBUG: Rx ' + repr (resp))
if resp_ok:
# Code Error description
# 0: No ERROR
# 1: Undervoltage error
# 2: Overvoltage error
# 4: Inverter overtemperature error
# 5: Motor overtemperature error
# 6: Fan overtemperature error
# 8: EEPROM error
# 9: Inverter peak current error
# 10: Inverter overcurrent error
# 11: Inverter continuous current error
# 12: Motor overcurrent error
# 16: Enable error
# 64: Short-circuit error
# 128: Power On error
# 250: Watch Dog error
# 255: Timeout error
self.h[index]['error-code'] = int (value)
else:
# 256: Communication error
self.h[index]['error-code'] = 256
else:
# 0: No ERROR
self.h[index]['error-code'] = 0
# gets the running motor speed status
if self.h[index]['sts-standstill']:
self.h[index]['motor-fb-rpm'] = 0.0
self.h[index]['motor-fb-rps'] = 0.0
else:
msg = self.prepare_enquiry (index, HannReg['speed'])
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp_ok, value, resp = self.poll_output (index)
if self.h[index]['debug']:
print ('DEBUG: Rx ' + repr (resp))
if resp_ok:
if self.h[index]['motion-reversed']:
value = -value
self.h[index]['motor-fb-rpm'] = int (value * self.h[index]['speed-scale'])
self.h[index]['motor-fb-rps'] = self.h[index]['motor-fb-rpm'] / 60.0
if self.h[index]['debug'] and 1 == 2:
print ('monitor frequency:', value, string, string[3:7], len (string))
# gets the motor current (amps)
if self.h[index]['motor-amps-enab']:
msg = self.prepare_enquiry (index, HannReg['I_motor'])
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp_ok, value, resp = self.poll_output (index)
if self.h[index]['debug']:
print ('DEBUG: Rx ' + repr (resp))
if resp_ok:
self.h[index]['motor-amps'] = value * 0.1
if self.h[index]['debug'] and 1 == 2:
print ('monitor amps:', value, string, string[3:7], len (string))
# stop on ESTOP
# if ESTOP is false it stops the output
# when ESTOP is reset the run command must be re-issued (cycled false to true) to start motor
if not self['last_estop%d'%index] == self.h[index]['estop']:
if not self.h[index]['estop']:
msg = self.prepare_select (HannReg['mode'], 0)
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
self['last_run%d'%index] = self.h[index]['run'] = False
msg = self.prepare_select (HannReg['setspeed'], 7)
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
self['last_enabled%d'%index] = self.h[index]['enabled'] = False
self['last_estop%d'%index] = self.h[index]['estop']
print ('**** Hanning VFD: {0} stopped due to Estop Signal'.format (ids[0]))
continue
print ('**** Hanning VFD: Estop cleared - Must re-issue run command to start {0}.'.format (ids[0]))
self['last_estop%d'%index] = self.h[index]['estop']
# set input mode
if not self['last_enabled%d'%index] == self.h[index]['enabled']:
if self.h[index]['enabled']:
msg = self.prepare_select (HannReg['errorcode'], 0)
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
if self.h[index]['debug']:
print ('DEBUG: Rx ' + resp)
time.sleep (SLEEPTIME)
data = 0 # Regs 000, 001 and 002 controlled by PC via RS485 link
else:
data = 7 # Regs 000, 001 and 002 controlled by manual switch and pot knob
msg = self.prepare_select (HannReg['setspeed'], data)
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
self['last_enabled%d'%index] = self.h[index]['enabled']
if self.h[index]['debug']:
print ('DEBUG: Rx ' + resp)
# set run
if not self['last_run%d'%index] == self.h[index]['run']:
if self.h[index]['run']:
if self.h[index]['fwd']:
data = 1 # forward (CW)
else:
data = 0 # backward (CCW)
if self.h[index]['motion-reversed']:
data = 1 - data
msg = self.prepare_select (HannReg['direction'], data)
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
self['last_fwd%d'%index] = self.h[index]['fwd']
if self.h[index]['debug']:
print ('DEBUG: Rx ' + resp)
data = 1 # run
else:
data = 0 # stop
msg = self.prepare_select (HannReg['mode'], data)
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
self['last_run%d'%index] = self.h[index]['run']
if self.h[index]['debug']:
print ('DEBUG: Rx ' + resp)
# set direction
if not self['last_fwd%d'%index] == self.h[index]['fwd']:
if self.h[index]['fwd']:
data = 1 # forward (CW)
else:
data = 0 # backward (CCW)
if self.h[index]['motion-reversed']:
data = 1 - data
msg = self.prepare_select (HannReg['direction'], data)
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
self['last_fwd%d'%index] = self.h[index]['fwd']
if self.h[index]['debug']:
print ('DEBUG: Rx ' + resp)
# set speed
if not self['last_cmd%d'%index] == self.h[index]['motor-speed-rpm']:
rpm = int (abs (self.h[index]['motor-speed-rpm']) / self.h[index]['speed-scale'])
self['last_cmd%d'%index] = self.h[index]['motor-speed-rpm']
msg = self.prepare_select (HannReg['speed_0'], rpm)
if self.h[index]['debug']:
print ('DEBUG: Tx ' + repr (str (msg)))
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
if self.h[index]['debug']:
print ('DEBUG: Rx ' + resp)
except KeyboardInterrupt:
self.kill_output ()
raise
except:
print ('error', ids)
print (sys.exc_info ()[0])
print ('Loop end!')
def kill_output (self):
global SLEEPTIME, HannReg
for index, ids in enumerate (self.comp_names):
# stop the motion
self.slave_num = int (ids[1])
time.sleep (SLEEPTIME)
msg = self.prepare_select (HannReg['mode'], 0)
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
time.sleep (SLEEPTIME)
# reset to manual control
msg = self.prepare_select (HannReg['setspeed'], 7)
self.ser.write (msg)
time.sleep (SLEEPTIME)
resp = self.poll_acknak (index)
print ('Hanning VFD: Kill-> ' + ids[0])
# build the 'enquiry' message (to read a Varicon register)
def prepare_enquiry (self, index, code = 51):
self['last_enq_code%d'%index] = code
converted_data = bytearray (chr (0x04) + chr (self.slave_num) + '{:03}'.format(code) + chr (0x05), 'utf8')
return converted_data
# build the 'select' message (to modify a Varicon register)
def prepare_select (self, code, data):
textval = '{:03}={}\x03'.format (code, data)
s = 0
for i in range (0, len (textval)):
s = s ^ ord (textval[i])
converted_data = bytearray(chr (0x04) + chr (self.slave_num) + chr (0x02) + textval + chr (s), 'utf8')
return converted_data
# decode the 'enquiry' response
def poll_output (self, index):
code = value = 0
ok = False
resp = raw = self.ser.read (1)
if raw == '\x02':
core = self.ser.read_until ('\x03', 12)
resp += core
if core[-1] == '\x03':
# message received, check BCC
bcc = self.ser.read (1)
resp += bcc
s = 0
for i in range (0, len (core)):
s = s ^ ord (core[i])
if ord (bcc[0]) == s:
# BCC ok, message valid
mtch = re.match ('([0-9]+)=(-?[0-9]+)\x03', core)
if mtch != None:
# extract code and value
code = int (mtch.group(1))
if code == self['last_enq_code%d'%index]:
value = int (mtch.group(2))
ok = True
return ok, value, resp
self.h[index]['rx-error-cnt'] += 1
return ok, value, resp
# decode the 'select' response
def poll_acknak (self, index):
resp = b''
res = 'err'
while self.ser.in_waiting > 0:
resp += self.ser.read (1)
if len (resp) > 0:
if resp[0] == '\x06':
return 'ack'
elif resp[0] == '\x15':
res = 'nak'
self.h[index]['rx-error-cnt'] += 1
return res
def __getitem__ (self, item):
return getattr (self, item)
def __setitem__ (self, item, value):
return setattr (self, item, value)
if __name__ == '__main__':
import getopt, sys
letters = 'p:b:h' # the : means an argument needs to be passed after the letter
keywords = ['port = ', 'baud = '] # the = means that a value is expected after the keyword
try:
# starts at the second element of argv since the first one is the script name
# extraparms are extra arguments passed after all option/keywords are assigned
# opts is a list containing the pair 'option'/'value'
opts, extraparam = getopt.getopt (sys.argv[1:], letters, keywords)
except getopt.GetoptError as err:
# print help information and exit:
print str(err) # will print something like "option -a not recognized"
print ('Use --help option to get help')
sys.exit(0)
port = '/dev/ttyUSB0'
device_ids = []
baud = 9600
for o, p in opts:
if o in ['-p', '--port']:
port = p
elif o in ['-b', '--baud']:
baud = p
elif o in ['-h', '--help']:
print ('Hanning VFD computer-link interface')
print (' LinuxCNC user space component for controlling a Hanning Varicon inverter')
print (' over the serial port using RS485 standard;')
print (' this inverter is used in Wabeco lathes and mills using 2kW motors')
print ('')
print (''' Sample LinuxCNC code (in <filename>.hal file)
loadusr -Wn spindle hanning_vfd -p /dev/tty0 spindle=32
# ********** mill spindle vfd setup (slave 32) *************
net spindle-vel-cmd-rpm-abs <= motion.spindle-speed-out-abs
net spindle-vel-cmd-rpm-abs => spindle.motor-speed-rpm
net spindle-enable <= motion.spindle-on
net spindle-enable => spindle.run
net spindle-cw <= motion.spindle-forward
net spindle-cw => spindle.fwd
net spindle-brake <= motion.spindle-brake
net spindle-position => motion.spindle-revs
net spindle-at-speed <= spindle.sts-speed-ok
net spindle-at-speed => motion.spindle-at-speed
net spindle-speed-rps <= spindle.motor-fb-rps
net spindle-speed-rps => motion.spindle-speed-in
net spindle-index-enable <=> motion.spindle-index-enable
net machine-is-enabled <= motion.motion-enabled
net machine-is-enabled => spindle.monitor
net machine-is-enabled => spindle.enabled
net estop-out <= iocontrol.0.user-enable-out
net estop-out => iocontrol.0.emc-enable-in
net estop-out => spindle.estop
net spindle-error-condition <= spindle.sts-gen-error
net spindle-error-code <= spindle.error-code
net spindle-current <= spindle.motor-amps
# --- config mill spindle pins ---
setp spindle.speed-scale 1.46154
setp spindle.debug 0
setp spindle.motor-amps-enab 0
setp spindle.motion-reversed 1
''')
sys.exit (0)
if extraparam:
for dids in extraparam:
device_ids.append (dids.split ('='))
else:
device_ids = [['hanning_vfd', '32']]
# info gathered, now use them
try:
app = hanning_serial (vfd_names = device_ids, baudrate = int (baud), port = port)
app.loop ()
except KeyboardInterrupt:
sys.exit (0)
else:
exc_type, exc_value, exc_traceback = sys.exc_info ()
formatted_lines = traceback.format_exc ().splitlines ()
print ('')
print ('**** hanning_vfd debugging:', formatted_lines[0])
traceback.print_tb (exc_traceback, limit = 1, file = sys.stdout)
print (formatted_lines[-1])
P.S. nelle foto il filo giallo collegato al terminale 12 della morsettiera, deve essere in effetti collegato al terminale 13. Era collegato al 12 di fabbrica per errore: l'ho segnalato alla Wabeco che mi ha confermato che deve in effetti essere collegato al 13!
Sono ovviamente a disposizione per chiarimenti e dettagli