Notatnik techniczny

Huanyang sterowanie przez RS-485

Czytasz jeden z serii wpisów traktujących o chińskim wrzecionie i falowniku Huanyang:

Poniżej pokazuje jak sterować falownikiem za pomocą portu komunikacyjnego.

Podłączenie falownika

RS485

usb to rs485

usb to rs485

virtualny port COM

Konfiguracja falownika

Parametry których ustawienie ma wpływ na pracę jeśli sterujemy urządzeniem przy pomocy portu komunikacyjnego.

Komunikacja z urządzeniem

Zanim uda nam sie skomunikować z urządzeniem musimy ustawić ręcznie parametry transmisji, w zamieszczonych przykładach zakładam ze konfiguracja jest następująca:

Parametr Wartość Komentarz
PD001 2 Aby odczytywać wartości ustawienie nie wymagane
PD002 2 Aby odczytywać wartości ustawienie nie wymagane
PD163 1 Ustalamy adres urządzenia
PD164 2 Prędkość 19200 b/s
PD165 3 Tryb RTU
port COM5  

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 inverterem. 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()

Oraz kod źródłowy modułu:

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 zycie. Przed przystąpieniem do pracy musimy dołączyć moduł pyHYControl do naszego projektu, niestety pakiet Pip nie jest na chwile obecną dostępny. Moduł pyHYControl jest przydatny ale nie idealny, w chwili pisania tego artykułu w kodzie znajdywało się wiele niepotrzebnych print. Aby zastosować ten moduł konieczne może okazać się ręczne usunięcie niepotrzebnych instrukcji.

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
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

Jeśli parametr PD002 będzie miał wartość równą 2 wówczas możemy ustawiać częstotliwość poprzez port komunikacyjny.

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

Możemy również zapisywać i odczytywać wartości parametrów konfiguracyjnych urządzenia.

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

Oraz wydawać polecenia dla uradzenia (start, stop) o ile parametr PD001 ma wartość 2.


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

Zrzut wybranych parametrów

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()

Monitoring stanu urządzenia

Skrypt uruchamia falownik, ustawia generowaną częstotliwość, a następnie wyświetla aktualne obroty oraz pobierany prąd. Python3

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()

Wynik działania:

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

Pamiętaj o repozytorium GitHub.

Jeśli widzisz jakieś błędy albo masz uwagi napisz to w komentarzu lub prywatnej wiadomości.