Sterowanie cyfrowe przez port RS-485

Podłączenie falownika z komputerem jest widoczne na zdjęciach. Wystarczą do tego zadania dwa przewody. Następnie musimy zadbać o takie samo ustawienie parametrów transmisji po obu stronach.

ParametrWartośćOpis
PD001 2 Niezbędne do starowania, ale jeśli chcemy tylko monitorować prace to możemy sterować inaczej
PD002 2 Kto kontroluje obroty
PD163 1 Ustalamy adres urządzenia
PD164 2 Prędkość 19200 b/s
PD164 3 Tryb RTU

Konieczne może okazać się również ręczne doinstalowanie zależności

pip install crcmod
pip install pyserial

Moduł do budowania komunikatów

Poniżej przykład użycia prostego modułu do komunikacji z inverter. Ten kod jest dostępny w dedykowanym repozytorium GitHub.

import serial
import time
import HuanyangDev

dev = HuanyangDev.HuanyangDev({"port": "COM5", "rate": 19200, "parity": serial.PARITY_NONE, "address": 1, "timeout": 0.1})
dev.open()

dev.write_function_data(8, 2200)
pd008 = dev.read_function_data(8)
print("PD008 = {}".format(pd008))

dev.write_freq(200)
dev.write_control_data(0x03)  # start

# 0: target frequency,  1: output frequency, 2:output current, 3: rpm, 4: DC voltage, 5: AC voltage,6:cont, 7:temp
f1, f2 = dev.read_control_data(0), dev.read_control_data(1)
print("target frequency = {}   output frequency = {}".format(f1, f2))
time.sleep(5)

dev.write_control_data(0x08)  # stop
dev.close()
import crcmod
import serial
import struct

_crc16 = crcmod.predefined.mkPredefinedCrcFun('modbus')


class HuanyangDev:
    def __init__(self, config):
        self.config = config
        self.conn = None

    def open(self):
        self.conn = serial.Serial(
            port=self.config["port"],
            baudrate=self.config["rate"],
            parity=self.config["parity"],
            timeout=self.config["timeout"]
        )

    def close(self):
        self.conn.close()

    def _build_packet(self, function, data):
        packet = [self.config["address"], function, len(data)]
        packet.extend(data)
        crc = self._hy_crc(packet)
        packet.extend(crc)
        return packet

    @staticmethod
    def _hy_crc(message):
        return list(struct.pack('<H', _crc16(bytes(message))))

    def _check_crc(self, message):
        if len(message) > 5:
            calc_crc = self._hy_crc(message[:-2])
            msg_crc = list(message[-2:])
            return calc_crc == msg_crc
        return False

    def _check_msg(self, message):
        if self._check_crc(message):
            if int(message[1]) & 0xF0 > 0:
                return False
            return int(message[2]) == len(message[3:-2])
        return False

    def read_function_data(self, parameter):
        packet = self._build_packet(0x01, [parameter])
        self.conn.write(bytes(packet))
        ans = self.conn.read(8)
        if not self._check_msg(ans):
            return None

        data = ans[3:-2]
        value = int.from_bytes(bytes(data[1:]), byteorder='big')
        return value

    def write_function_data(self, parameter, value):
        value = int(value)
        value_length = max(1, (value.bit_length() + 7) // 8)
        pdata = [parameter]
        pdata.extend(list(value.to_bytes(value_length, 'big')))
        packet = self._build_packet(0x02, pdata)
        self.conn.write(bytes(packet))
        ans = self.conn.read(8)

        if self._check_msg(ans):
            rdata = ans[3:-2]
            value = int.from_bytes(bytes(rdata[1:]), byteorder='big')
            return value
        return None

    def write_control_data(self, data):
        packet = self._build_packet(0x03, [data])
        self.conn.write(bytes(packet))
        ans = self.conn.read(6)
        if self._check_msg(ans):
            value = int.from_bytes(bytes(ans[3:-2]), byteorder='big')
            return value
        return None

    def read_control_data(self, parameter):
        packet = self._build_packet(0x04, [parameter])
        self.conn.write(bytes(packet))
        ans = self.conn.read(8)
        if not self._check_msg(ans):
            return None

        rdata = ans[3:-2]
        value = int.from_bytes(bytes(rdata[1:]), byteorder='big')
        return value

    def write_freq(self, freq):
        pdata = list(int(freq * 100).to_bytes(2, 'big'))
        packet = self._build_packet(0x05, pdata)
        self.conn.write(bytes(packet))
        ans = self.conn.read(8)
        if self._check_msg(ans):
            value = int.from_bytes(bytes(ans[3:-2]), byteorder='big')
            ret = value / 100
            return ret
        return None

Wykorzystanie pyHYControl

Do komunikacji z inverterem możemy wykorzystać moduł pythona pyHYControl, jego wykorzystanie znacznie ułatwi nam życie. Przed przystąpieniem do pracy musimy dołączyć moduł pyHYControl do naszego projektu, niestety pakiet Pip nie jest na chwilę obecną dostępny. Moduł pyHYControl jest przydatny, ale nie idealny.

Praca z konsoli

Sprawdzenie statusu falownika i parametrów chwilowych takich jak:

  • obroty chwilowe
  • częstotliwość oczekiwana i aktualnie generowana
  • napięcie wyjściowe i wejściowe
  • prąd pobierany przez silnik

Jeśli parametr PD002 będzie miał wartość równą 2 wówczas możemy zdalnie sterować urządzeniem, zapisywać wartości parametrów konfiguracyjnych oraz wydawać polecenia START, STOP.

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 status rpm
rpm: 1980.00 rpm

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 status vac
vac: 89.30 V

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 status all
fset: 33.00 Hz
fout: 33.00 Hz
aout: 4.70 A
rpm: 1980.00 rpm
vdc: 322.20 V
vac: 23.40 V
cont: 0.00 h
temp: 0.00 C

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 frequency 20
Set frequency command: 20.0

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 frequency 40
Set frequency command: 40.0

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 read 008
read: PD008: 220.0 V

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 write 008 190
written: PD008: 190.0 V

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 read 008
read: PD008: 190.0 V
D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 cmd run_fwd
Command: ['run', 'forward']
Status: []

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 cmd run_rev
Command: ['run', 'reverse']
Status: ['cmd_run', 'running']

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 cmd status
Command: []
Status: ['cmd_run', 'cmd_reverse', 'running', 'run_reverse']

D:\Huanyang>python -m hycontrol -d COM5 -p N -b 19200 -a 1 cmd stop
Command: ['stop']
Status: ['cmd_run', 'running']

Przykładowe skrypty

Przykładowe skrypty python do komunikacji z falownikiem Huanyang. Pierwszy dokonuje zrzutu parametrów, kolejny uruchamia falownik, ustawia generowaną częstotliwość, a następnie wyświetla aktualne obroty oraz pobierany prąd.

from hycontrol import VFDConf, RegisterMap, VFDDevice
import os

conf = VFDConf(port="COM5", rate=19200, parity="N", address=1, timeout=0.1)

regmap = RegisterMap(
    os.path.join(os.path.dirname(os.path.realpath(__file__)), 'hycontrol', 'registers.yml'))

d = VFDDevice(conf, regmap)

d.connect()

data_to_read = []
data_to_read.extend(range(1, 12))
data_to_read.extend(range(70, 75))

data = {}
for a in data_to_read:
    data[a] = d.read_function_data(a)


for k in data:
    print("PD{:03d} = {}".format(k, data[k]))


d.close()
from hycontrol import VFDConf, RegisterMap, VFDDevice
import os
import _thread

conf = VFDConf(port="COM5", rate=19200, parity="N", address=1, timeout=0.1)

regmap = RegisterMap(
    os.path.join(os.path.dirname(os.path.realpath(__file__)), 'hycontrol', 'registers.yml'))

d = VFDDevice(conf, regmap)
d.connect()

commands = {'status': 0x00, 'run_fwd': 0x03, 'run_rev': 0x11, 'stop': 0x08 }

def get_status(dev, cmd):
    return dev.read_control_data(cmd)

def report_thread(dev):
    import time

    while 1:
        time.sleep(0.5)
        rpm = get_status(dev, 3)
        ocur = get_status(dev, 2)
        print("speed = {} rpm      output current = {:2.2f} A".format(rpm[0], ocur[0]))


def run_spindle(dev):
    dev.write_freq(200)
    dev.write_control_data(commands['run_fwd'])

    _thread.start_new_thread(report_thread, (dev,))
    input('Press ENTER to exit\n')

try:
    run_spindle(d)
finally:
    d.write_control_data(commands['stop'])
    d.close()
Set frequency command: 200.0
Command: ['run', 'forward']
Status: []
Press ENTER to exit
speed = 2526 rpm      output current = 3.80 A
speed = 5118 rpm      output current = 3.50 A
speed = 7710 rpm      output current = 3.40 A
speed = 10302 rpm      output current = 3.30 A
speed = 12000 rpm      output current = 3.20 A
speed = 12000 rpm      output current = 3.20 A
speed = 12000 rpm      output current = 3.20 A
speed = 12000 rpm      output current = 3.20 A
speed = 12000 rpm      output current = 3.20 A

Command: ['stop']
Status: ['cmd_run', 'running']

Process finished with exit code 0

Dodatkowe informacje