Частотник и Modbus. Универсальная заготовка.

Обсуждение установки, настройки и использования LinuxCNC. Вопросы по Gкоду.
Аватара пользователя
aekhv
Мастер
Сообщения: 393
Зарегистрирован: 17 окт 2014, 15:03
Репутация: 218
Настоящее имя: Александр
Откуда: г.Хабаровск
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение aekhv »

Валерий писал(а):Как со временем у Вас ?
Валерий, переоценил я свои силёнки, вообще не до этого сейчас. Делайте проще, как посоветовал UAVpilot выше. Или попытайтесь в выложенных в этой теме исходниках разобраться, код с комментариями, допиливайте под себя.
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

Ну вот и у меня начался забег по граблям. Часть собрал, часть еще нет :)
Вводная: ПЧ: MiCNO KE300, USB-RS485 самодельный изолированный. По сигналам претензий нет пока (проверял в на столе осциллографом, возможно еще сниму реальный обмен с шины, но пока что по логике проблема не здесь.)

Расскажу по порядку, может кому еще полезно будет, потом задам свои вопросы :)
Итак, первое:
Скомпилировал, настроил, получил пробему как у MGG вот тут: Re: Частотник и Modbus. Универсальная заготовка. #45
Только у меня картинка чуть другая:
IMG_20170919_204858.jpg (3554 просмотра) <a class='original' href='./download/file.php?id=120576&sid=1f8b028be58ea17f0772d96d47658311&mode=view' target=_blank>Загрузить оригинал (1.95 МБ)</a>
Прошу прощения за блондинистый скриншот, так было проще )
Как видим мастер пытается читать по адресу регистра 7001, в ответ получаем 01 03 80 01 00 04 3C
а в следующей попытке получаем просто 09
Так вот, это особенность частотника, как выше BentScrew и предполагал:
Это ответ частотинка о том, что ему не нравится в запросе. А т.к. он отсылает 80 01, то похоже мастер 80 игнорирует, т.к. ждет там 00, и читая дальше 01 считает что идет 1 байт данных, соответственно принимает пакет не полностью, а на байт меньше, 09 остается в буфере приемника и прилетает при следующей транзакции...
В общем если собрать пакет: "01 03 80 01 00 04 3C 09" то получаем нормальную CRC 3C 09.
А суть пакета похоже такая: 80 01 - ошибка связи; 00 04 - Неверный адрес
Вот табличка из мануала:

Код: Выделить всё

9.6.5 Description data of communication fault information (fault code)
Communication Fault
Address   Fault function description
8001      0000: No fault
          0001: Password error
          0002: Command error
          0003: CRC check error
          0004: Invalid address
          0005: Invalid parameter
          0006: Parameter changing invalid
          0007: System locked
          0008: EEPROM operating
Теперь дальше. На текущих граблях я еще топчусь. Поправил адреса параметров, частотник начал отвечать, НО:
Первая пара вопрос-ответ проходит нормально, а за ней вопрос-таймаут
Попробовал в коде компонента после первого тайм-аута вставить повтор запроса - прошло.
Т.е. сразу после своего ответа мастеру частотник пропускает мимо ушей следующий запрос.

Код: Выделить всё

spindle-vfd : updating frequency register [0.000000 RPM, 0x29] ... 
[01][06][10][00][00][29][4C][D4]
Waiting for response (8)...
<01><06><10><00><00><29><4C><D4>
Write OK!
spindle-vfd : updating command register [0x5] ... 
[01][06][20][00][00][05][42][09]
Waiting for response (8)...


ERROR Communication time out

Write FAIL!

[01][03][10][00][00][01][80][CA]
Waiting for response (7)...
<01><03><02><00><29><79><9A>

[01][03][10][00][00][01][80][CA]
Waiting for response (7)...


ERROR Communication time out

spindle-vfd : data reading error!
spindle-vfd : updating command register [0x5] ... 
[01][06][20][00][00][05][42][09]
Waiting for response (8)...
<01><06><20><00><00><05><42><09>
Write OK!
task: main loop took 0.013080 seconds
task: main loop took 0.013085 seconds

[01][03][10][00][00][01][80][CA]
Waiting for response (7)...
<01><03><02><00><29><79><9A>

[01][03][10][00][00][01][80][CA]
Waiting for response (7)...


ERROR Communication time out

spindle-vfd : data reading error!
Я сначала думал, что это мой конвертер не успевает быстро включиться на передачу, но первый то байт тоже передается из режима "прием" включением в "передачу", да и на осциллографе посмотрел - переключение происходит четко.
Все выше описанное глухо не работает до скоростей от 300 до 19200 бод включительно, на 38400 картина меняется - связь начинает проходить, но с перебоями.
Т.е. индикатор на панельке даже зеленеет периодически... Больше, чем 38400 частотник не умеет. Еще с кол-вом стоповых битов поиграться хочу, по умолчанию в частотнике их 2 стоит, вроде правил, но еще раз перепроверить нужно. Ну и сегодня заберу лог. анализатор, на работе оставил, попробую обмен снять если что, чтобы понять как там что передается на самом деле...

Пост написал сейчас, т.к. думаю может кто решал уже такую проблему и подскажет что... Все же пока складывается ощущение, что это очередная особенность китайских вариантов протоколов... Была мысль, что может пауза в 3,5 символа между кадрами не выдержана и частотник глотает начало кадра, но это до снятия времянок с шины не понять, может сегодня успею глянуть...
Аватара пользователя
aekhv
Мастер
Сообщения: 393
Зарегистрирован: 17 окт 2014, 15:03
Репутация: 218
Настоящее имя: Александр
Откуда: г.Хабаровск
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение aekhv »

Наверное скорость обмена как-то связана с тем, как быстро частотник готов принять новый запрос после того как ответил на предыдущий. Возможно после отправки ответа частник хочет присесть, подумать о жизни, вспомнить как он когда-то крутил с одним шпинделем... а тут бац, новый запрос, слишком быстро :hehehe: Что если перед каждым вызовом read_holding_registers() и preset_single_register() добавить задержку? Просто для эксперимента.
Аватара пользователя
Serg
Мастер
Сообщения: 21923
Зарегистрирован: 17 апр 2012, 14:58
Репутация: 5182
Заслуга: c781c134843e0c1a3de9
Настоящее имя: Сергей
Откуда: Москва
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение Serg »

Modbus RTU — компактный двоичный вариант. Сообщения разделяются по паузе в линии. Сообщение должно начинаться и заканчиваться интервалом тишины, длительностью не менее 3,5 символов при данной скорости передачи. Во время передачи сообщения не должно быть пауз длительностью более 1,5 символов. Для скоростей более 19200 бод допускается использовать интервалы 1,75 и 0,75 мс, соответственно. Проверка целостности осуществляется с помощью CRC.
Я не Христос, рыбу не раздаю, но могу научить, как сделать удочку...
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

Вот и я пришел к выводу что отсюда ноги растут, бегло посмотрел modbus.c - никакого контроля пауз там нет. Добавил ручками nanosleep (350мкс) после каждой транзакции, это для 9600 бод и выше должно быть достаточно, вечером постараюсь успеть проверить.
Аватара пользователя
Serg
Мастер
Сообщения: 21923
Зарегистрирован: 17 апр 2012, 14:58
Репутация: 5182
Заслуга: c781c134843e0c1a3de9
Настоящее имя: Сергей
Откуда: Москва
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение Serg »

N1X писал(а):Добавил ручками nanosleep (350мкс)
Там должны быть милисекунды.
Я не Христос, рыбу не раздаю, но могу научить, как сделать удочку...
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

Да, 3.5 символа, а я решил, что 3.5 бита, спасибо, сразу потенциальный косяк ушел )
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

В общем с добавлением задержки обмен пошел устойчиво.
Вот получившийся код модуля:

Код: Выделить всё

/*
    spindle-vfd.c
    Universal LinuxCNC component for MODBUS RTU VFD's by BentScrew.
    Special for <cnc-club.ru>, September 2015.
    http://www.cnc-club.ru/forum/viewtopic.php?f=15&t=9406

    Based on gs2_vfd.c
    Copyright (C) 2007, 2008 Stephen Wille Padnos, Thoth Systems, Inc.

    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.


    This is a userspace HAL program, which may be loaded using the halcmd "loadusr" command:
        loadusr spindle-vfd
    There are several command-line options.  Options that have a set list of possible values may
        be set by using any number of characters that are unique.  For example, --rate 5 will use
        a baud rate of 57600, since no other available baud rates start with "5"
    -b or --bits <n> (default 8)
        Set number of data bits to <n>, where n must be from 5 to 8 inclusive
    -d or --device <path> (default /dev/ttyUSB0)
        Set the name of the serial device node to use
    -g or --debug
        Turn on debugging messages.  This will also set the verbose flag.  Debug mode will cause
        all modbus messages to be printed in hex on the terminal.
    -n or --name <string> (default spindle-vfd)
        Set the name of the HAL module.  The HAL comp name will be set to <string>, and all pin
        and parameter names will begin with <string>.
    -p or --parity {even,odd,none} (defalt none)
        Set serial parity to even, odd, or none.
    -r or --rate <n> (default 38400)
        Set baud rate to <n>.  It is an error if the rate is not one of the following:
        110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
    -s or --stopbits {1,2} (default 1)
        Set serial stop bits to 1 or 2
    -t or --target <n> (default 1)
        Set MODBUS target (slave) number. This must match the device number you set on the VFD.
    -v or --verbose
        Turn on debug messages.  Note that if there are serial errors, this may become annoying.
        At the moment, it doesn't make much difference most of the time.    
*/

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <getopt.h>
#include <math.h>
#include "rtapi.h"
#include "hal.h"
#include "modbus.h"

/* Настройки связи по умолчанию, если в custom.hal параметры не были указаны явно */
#define DEFAULT_Slave		1		// адрес преобразователя частоты
#define DEFAULT_Device		"/dev/ttyUSB0"	// последовательный порт
#define DEFAULT_Baud		19200		// скорость, бод
#define DEFAULT_Bits		8		// количество бит: 5 .. 8
#define DEFAULT_Parity		"none"		// чётность: even, odd, none
#define DEFAULT_Stopbits	1		// стоп-биты: 1, 2
#define DEFAULT_Debug		0		// показывать запросы/ответы modbus? 0 - нет , 1 - да
#define DEFAULT_Verbose		0		// показывать сервисные сообщения? 0 - нет , 1 - да

/* Пауза между циклами чтения-записи, не изменяйте без необходимости */
#define DEFAULT_Looptime	0.1		// 0.001 .. 2.0

/* Регистры для чтения. Укажите значения из мануала на свой ПЧ! */
#define STATUS_Command_Frequency	0x1000	// заданная (опорная) частота
#define STATUS_Output_Frequency		0x1001	// выходная частота
#define STATUS_Output_Current		0x1004	// выходной ток
#define STATUS_Output_Voltage		0x1003	// выходное напряжение
#define STATUS_Output_Power		0x1005	// выходная мощность
#define STATUS_Output_Torque		0x1006	// выходной момент
#define STATUS_Motor_RPM		0x1007	// скорость вращения
#define STATUS_DC_Bus_Voltage		0x1002	// напряжение на шине DC
#define STATUS_Temperature		0x1011	// температура
#define STATUS_Uptime			0x101B	// время наработки
#define STATUS_Fault_Code		0x8000	// код ошибки ПЧ

/* Регистры для записи. Укажите значения из мануала на свой ПЧ! */
#define COMMAND_REGISTER		0x2000	// команда вперёд/назад/стоп
#define SET_FREQUENCY_REGISTER		0x1000	// задание частоты

/* Управляющие значения командного регистра. Аналогично, см. мануал! */
#define CONTROL_Run_Fwd			0x0001	// вперёд
#define CONTROL_Run_Rev			0x0002	// назад
#define CONTROL_Stop			0x0005	// стоп
#define CONTROL_Fault_Reset		0x0007  // сброс ошибки

/* В зависимости от модели ПЧ скорость вращения может задаваться либо заданием частоты (Гц), либо
   диапазоном 0 ... 100%, где 100% - максимальная скорость. Значения: 0 - частота, 1 - проценты */
#define SET_FREQUENCY_MODE		1

/* Минимальная безопасная скорость вращения, об/мин */
#define MIN_SPEED			00000.0

/* Максимальная скорость, об/мин. Если SET_FREQUENCY_MODE = 1, значение ниже будет принято за 100% */
#define MAX_SPEED			24000.0

/* Допустимое отклонение от заданной скорости, 0.01 = 1% */
#define AT_SPEED_TOLERANCE		0.01

/* Количество непрерывных успешных транзакций для уверенности в наличии связи по Modbus */
#define MIN_MODBUS_OK			10
 
/* HAL пины */
typedef struct {
  /* выходы для мониторинга параметров ПЧ */
  hal_float_t	*command_frequency;	// заданная (опорная) частота
  hal_float_t	*output_frequency;	// выходная частота
  hal_float_t	*output_current;	// выходной ток
  hal_float_t	*output_voltage;	// выходное напряжение
  hal_float_t	*output_power;		// выходная мощность
  hal_float_t	*output_torque;		// выходной момент
  hal_float_t	*motor_rpm;		// скорость вращения
  hal_float_t	*dc_bus_voltage;	// напряжение на шине DC
  hal_float_t	*temperature;		// температура
  hal_s32_t	*uptime;		// время наработки
  hal_s32_t	*fault_code;		// код ошибки ПЧ

  /* служебные выходы */
  hal_bit_t	*at_speed;		// заданная скорость достигнута
  hal_bit_t	*modbus_ok;		// связь установлена
  hal_s32_t	*error_count;		// количество ошибок приёма-передачи
  hal_s32_t	*error_code;		// код последней ошибки связи

  /* входы */
  hal_float_t	*command_speed;		// задание скорости вращения, об/мин
  hal_bit_t	*spindle_on;		// команда на запуск
  hal_bit_t	*spindle_fwd;		// направление - вперёд
  hal_bit_t	*spindle_rev;		// направление - назад
  hal_bit_t	*fault_reset;		// сброс ошибки

} haldata_t;

/* Имя компонента по умолчанию,
   при вызове из командной строки может быть изменено при помощи ключа "-n" */
char *modname = "spindle-vfd";

#undef DEBUG

unsigned short int data_ok_count;	// Число непрерывных успешных транзакций, при ошибке обнуляется
unsigned short int error_count;		// Количество ошибок приёма-передачи
int verbose;				// Флаг расширенного логирования
int old_control;			// Для контроля необходимости обновить командное слово
hal_float_t old_speed;			// Для контроля необходимости обновить заданную скорость

static struct option long_options[] = {
    {"bits", 1, 0, 'b'},
    {"device", 1, 0, 'd'},
    {"debug", 0, 0, 'g'},
    {"help", 0, 0, 'h'},
    {"name", 1, 0, 'n'},
    {"parity", 1, 0, 'p'},
    {"rate", 1, 0, 'r'},
    {"stopbits", 1, 0, 's'},
    {"target", 1, 0, 't'},
    {"verbose", 0, 0, 'v'},
    {0,0,0,0}
};

/* Массивы со списком возможных значений параметров связи */
static char *option_string = "b:d:ghn:p:r:s:t:v";
static char *bitstrings[] = {"5", "6", "7", "8", NULL};
static char *paritystrings[] = {"even", "odd", "none", NULL};
static char *ratestrings[] = {"110", "300", "600", "1200", "2400", "4800", "9600",
    "19200", "38400", "57600", "115200", NULL};
static char *stopstrings[] = {"1", "2", NULL};

static int done;

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;        // множественные совпадения
            match=which;
        }
        ++which;
    }
    return match;
}

/*===============Функция задержки перед кадром===================*/
void framestart_delay(void) {
	struct timespec fs_dt, fs_rm;
	fs_dt.tv_sec = 0;
	fs_dt.tv_nsec = (long) 350000;
	nanosleep(&fs_dt, &fs_rm);
}

/* Функция читает параметры из ПЧ */
int read_data(modbus_param_t *param, int slave, haldata_t *hal_data_block) {
    int receive_data[MAX_READ_HOLD_REGS]; // массив слов для принятых данных, MAX_READ_HOLD_REGS = 100
    int tmp, cnt;

    /* Проверка на корректность вызова функции */
    if (hal_data_block == NULL)
        return -1;
    if (param == NULL)
        return -1;

    /* ЧТЕНИЕ РЕГИСТРОВ: СПОСОБ 1. Один регистр - один запрос. Универсальный способ. */

    // Функция возвращает число прочитанных 16-ти битных регистров, запрашиваем содержимое ОДНОГО регистра
	
	framestart_delay();
	
    tmp = read_holding_registers(param, slave, STATUS_Command_Frequency, 1, receive_data);
    // Если один регистр был прочитан, то передаём его соответствующему пину HAL
    if (tmp == 1) {
        /* Т.к. содержимое регистра - это 16-ти битное целое беззнаковое число,
        требуется дополнительный множитель, чтобы получить корректное значение.
        В данном случае это 0.1. Уточните верный множитель в мануале своего ПЧ. */ 
        *(hal_data_block->command_frequency) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

    // Повторяем столько раз, сколько регистров требуется прочитать
	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Frequency, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_frequency) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Current, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_current) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Voltage, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_voltage) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Power, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_power) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Torque, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_torque) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Motor_RPM, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->motor_rpm) = receive_data[0] * 10.0;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_DC_Bus_Voltage, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->dc_bus_voltage) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Temperature, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->temperature) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Uptime, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->uptime) = receive_data[0];
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Fault_Code, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->fault_code) = receive_data[0];
        data_ok_count++;
    } else goto failed;

/*
    ЧТЕНИЕ РЕГИСТРОВ: СПОСОБ 2. ПОСЛЕДОВАТЕЛЬНАЯ цепочка регистров читается ОДНИМ запросом.
    Работает на всех ПЧ, поддерживающих функцию чтения 03, но возможны ограничения.
    Например, Tecorp HC1C+ не может прочитать более 4-х регистров одной командой, поэтому для большего числа
    регистров требуется несколько запросов. Чтобы узнать сколько регистров способен прочитать ваш ПЧ,
    включите флаг отладки (--debug или -g) и запросите максимальное требуемое число. В логе будет видно
    сколько регистров фактически возвращается в ответ на запрос.
       
    ПРИМЕР ЛОГА:

    spindle-vfd : requested 11 registers, see below how much registers returned

    [01][03][01][00][00][0B][55][CD]				// Запрос содержимого 11-ти [0x00][0x0B] регистров,
								//   начиная с регистра 256 [0x01][0x00]
    Waiting for response (27)...				// Ожидаемая длина ответного пакета в байтах, включая CRC и др.
    <01><03><08><01><F4><00><00><00><00><00><00><E1><D4>	// Фактически пакет содержит <0x08> байт данных (4 слова),
								//   хотя должно быть <0x16> байт (11 слов)
    ERROR Communication time out				// Время ожидания истекло
*/

/*
    // Функция возвращает число прочитанных 16-ти битных регистров, запрашиваем cnt регистров
    cnt = 11;
    if (param->debug == 1) printf("%s : requested %d registers, see below how much registers returned\n", modname, cnt);
    tmp = read_holding_registers(param, slave, STATUS_Command_Frequency, cnt, receive_data);
    if (tmp == cnt) {
        *(hal_data_block->command_frequency) = receive_data[0] * 0.1;
        *(hal_data_block->output_frequency) = receive_data[1] * 0.1;
        *(hal_data_block->output_current) = receive_data[2] * 0.1;
        *(hal_data_block->output_voltage) = receive_data[3] * 0.1;
        *(hal_data_block->output_power) = receive_data[4] * 0.1;
        *(hal_data_block->output_torque) = receive_data[5] * 0.1;
        *(hal_data_block->motor_rpm) = receive_data[6] * 10;
        *(hal_data_block->dc_bus_voltage) = receive_data[7] * 0.1;
        *(hal_data_block->temperature) = receive_data[8] * 0.1;
        *(hal_data_block->uptime) = receive_data[9];
        *(hal_data_block->fault_code) = receive_data[10];
        data_ok_count++;
    } else goto failed;
*/

    // всё хорошо, ошибок нет :)
    return 0;

    // всё плохо, данные не получены :-/
    failed:

    error_count++; // увеличиваем счётчик ошибок
    data_ok_count = 0; // сбрасываем счётчик успешных транзакций
    *(hal_data_block->error_count) = error_count;
    *(hal_data_block->error_code) = tmp;
    *(hal_data_block->modbus_ok) = 0;
    if (verbose == 1) printf("%s : data reading error!\n", modname);
    return -1;
}

/* Функция передаёт в ПЧ задание скорости и команду на запуск/останов */
int write_data(modbus_param_t *param, int slave, haldata_t *hal_data_block) {
    hal_float_t speed_cmd;
    int control, tmp;
    
    /* Корректируем заданную скорость, если она внезапно (!) отрицательна */
    speed_cmd = abs(*hal_data_block->command_speed);
    /* Проверка на выход за допустимые лимиты */
    if (speed_cmd > MAX_SPEED)
          speed_cmd = MAX_SPEED;
    if (speed_cmd < MIN_SPEED)
          speed_cmd = MIN_SPEED;

    if (SET_FREQUENCY_MODE == 1) {
        // Переводим заданную скорость (об/мин) в проценты (%)
        // Пример: command_speed = MAX_SPEED = 24000,
        // тогда speed_cmd = 24000 / (0.01 * 24000) = 100%
        speed_cmd /= 0.01 * MAX_SPEED;
        // Корректируем значение перед записью (см. мануал на свой ПЧ!)
        // Пример: 100% = 100.0% = 1000
        speed_cmd *= 10;
    } else {
        // Переводим заданную скорость (об/мин) в частоту (Гц)
        // Пример: command_speed = MAX_SPEED = 24000,
        // тогда speed_cmd = 24000 / 60 = 400Гц
        speed_cmd /= 60.0;
        // Корректируем значение перед записью (см. мануал на свой ПЧ!)
        // Пример: 400Гц = 400.0Гц = 4000
        speed_cmd *= 10; 
    }

    // Если заданная скорость изменилась...
    if (*hal_data_block->command_speed != old_speed) {
        if (verbose == 1) printf("%s : updating frequency register [%f RPM, 0x%X] ... ",
                              modname, *hal_data_block->command_speed, (int)(speed_cmd));
        // ...то записываем в ПЧ значение частоты (всего - один 16-ти битный регистр)
		framestart_delay();
        tmp = preset_single_register(param, slave, SET_FREQUENCY_REGISTER, (int)(speed_cmd));
        // Проверка результата - сколько регистров записано?
        if (tmp == 1) {
            if (verbose == 1) printf("Write OK!\n");
            old_speed = *hal_data_block->command_speed;
            data_ok_count++;
        } else {
            goto failed;
        }
    }

    /* Подготовка управляющего слова */
    control = CONTROL_Stop;			// Начальное состояние - "стоп"
    if (*hal_data_block->spindle_on) {		// Если подан сигнал на запуск...
        if (*hal_data_block->spindle_fwd) {
            control = CONTROL_Run_Fwd;		// ...то "крутим вперёд"
        }
        if (*hal_data_block->spindle_rev) {
            control = CONTROL_Run_Rev;		// ...или "крутим назад"
        }
    }

    if (*hal_data_block->fault_reset == 1) {	// Кнопка "сбросить ошибку" нажата?
        control = CONTROL_Fault_Reset;
    }

    // Если управляющее слово изменилось...
    if (control != old_control) {
        if (verbose == 1) printf("%s : updating command register [0x%X] ... ", modname, control);
        // ...то записываем его в ПЧ
		framestart_delay();
        tmp = preset_single_register(param, slave, COMMAND_REGISTER, control);
        // Проверка результата - сколько регистров записано?
        if (tmp == 1) {
            if (control == CONTROL_Fault_Reset) {	// Если команда "сбросить ошибку" записана успешно, то...
                *hal_data_block->fault_reset = 0;	// ...пин деактивируем
            }
            if (verbose == 1) printf("Write OK!\n");
            old_control = control;
            data_ok_count++;
        } else {
            goto failed;
        }
    }

    // всё хорошо, ошибок нет :)
    return 0;

    // всё плохо, данные не получены :-/
    failed:

    error_count++; // увеличиваем счётчик ошибок
    data_ok_count = 0; // сбрасываем счётчик успешных транзакций
    *(hal_data_block->error_count) = error_count;
    *(hal_data_block->error_code) = tmp;
    *(hal_data_block->modbus_ok) = 0;
    if (verbose == 1) printf("Write FAIL!\n");
    return -1;
}

/* Справка, вызывается ключом "-h", а также при любом неверном аргументе */
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 spindle-vfd\n"
    "There are several command-line options.  Options that have a set list of possible values may\n"
    "    be set by using any number of characters that are unique.  For example, --rate 5 will use\n"
    "    a baud rate of 57600, since no other available baud rates start with \"5\"\n"
    "-b or --bits <n> (default %d)\n"
    "    Set number of data bits to <n>, where n must be from 5 to 8 inclusive\n"
    "-d or --device <path> (default %s)\n"
    "    Set the name of the serial device node to use\n"
    "-g or --debug\n"
    "    Turn on debugging messages.  This will also set the verbose flag.  Debug mode will cause\n"
    "    all modbus messages to be printed in hex on the terminal.\n"
    "-n or --name <string> (default %s)\n"
    "    Set the name of the HAL module.  The HAL comp name will be set to <string>, and all pin\n"
    "    and parameter names will begin with <string>.\n"
    "-p or --parity {even,odd,none} (defalt %s)\n"
    "    Set serial parity to even, odd, or none.\n"
    "-r or --rate <n> (default %d)\n"
    "    Set baud rate to <n>.  It is an error if the rate is not one of the following:\n"
    "    110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200\n"
    "-s or --stopbits {1,2} (default %d)\n"
    "    Set serial stop bits to 1 or 2\n"
    "-t or --target <n> (default %d)\n"
    "    Set MODBUS target (slave) number.  This must match the device number you set on the VFD.\n"
    "-v or --verbose\n"
    "    Turn on debug messages.  Note that if there are serial errors, this may become annoying.\n"
    "    At the moment, it doesn't make much difference most of the time.\n",
    DEFAULT_Bits, DEFAULT_Device, modname, DEFAULT_Parity,
    DEFAULT_Baud, DEFAULT_Stopbits, DEFAULT_Slave );
}

/* Основная функция */
int main(int argc, char **argv)
{
    int retval;
    modbus_param_t mb_param;
    haldata_t *haldata;
    int hal_comp_id;
    struct timespec loop_timespec, remaining;
    int slave, baud, bits, stopbits, debug;
    char *device, *parity, *endarg;
    int opt;
    int argindex, argvalue;
    float looptime;
    double diff;

    done = 0;

    /* Применение настроек связи по умолчанию */
    slave = DEFAULT_Slave;
    device = DEFAULT_Device;
    baud = DEFAULT_Baud;
    bits = DEFAULT_Bits;
    parity = DEFAULT_Parity;
    stopbits = DEFAULT_Stopbits;
    debug = DEFAULT_Debug;
    verbose = DEFAULT_Verbose;
    looptime = DEFAULT_Looptime;

    /* Разбор параметров командной строки */
    while ((opt=getopt_long(argc, argv, option_string, long_options, NULL)) != -1) {
        switch(opt) {
            case 'b':   // число бит данных
                argindex=match_string(optarg, bitstrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid number of bits: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                bits = atoi(bitstrings[argindex]);
                break;
            case 'd':   // последовательный порт
                if (strlen(optarg) > FILENAME_MAX) {
                    printf("%s: ERROR - device node name is too long: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                device = strdup(optarg);
                break;
            case 'g':   // флаг отладки
                debug = 1;
                verbose = 1;
                break;
            case 'n':   // имя компонента
                if (strlen(optarg) > HAL_NAME_LEN-20) {
                    printf("%s: ERROR - HAL module name too long: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                modname = strdup(optarg);
                break;
            case 'p':   // чётность
                argindex=match_string(optarg, paritystrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid parity: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                parity = paritystrings[argindex];
                break;
            case 'r':   // скорость связи
                argindex=match_string(optarg, ratestrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid baud rate: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                baud = atoi(ratestrings[argindex]);
                break;
            case 's':   // число стоп-бит
                argindex=match_string(optarg, stopstrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid number of stop bits: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                stopbits = atoi(stopstrings[argindex]);
                break;
            case 't':   // адрес устройства (slave)
                argvalue = strtol(optarg, &endarg, 10);
                if ((*endarg != '\0') || (argvalue < 1) || (argvalue > 254)) {
                    printf("%s: ERROR - invalid slave number: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                slave = argvalue;
                break;
            case 'v':   // флаг расширенного логирования
                verbose = 1;
                break;
            case 'h':   // вызов справки
            default:
                usage(argc, argv);
                exit(0);
                break;
        }
    }

    printf("%s: device='%s', baud=%d, bits=%d, parity='%s', stopbits=%d, slave=%d, verbose=%d, debug=%d\n",
           modname, device, baud, bits, parity, stopbits, slave, verbose, debug);
    /* point TERM and INT signals at our quit function */
    /* if a signal is received between here and the main loop, it should prevent
            some initialization from happening */
    signal(SIGINT, quit);
    signal(SIGTERM, quit);

    /* Инициализация последовательного порта */
    modbus_init_rtu(&mb_param, device, baud, parity, bits, stopbits, debug);
    mb_param.debug = debug;
    if (((retval = modbus_connect(&mb_param))!=0) || done) {
        printf("%s: ERROR - couldn't open serial device\n", modname);
        goto out_noclose;
    }

    /* Создаём компонент HAL */
    hal_comp_id = hal_init(modname);
    if ((hal_comp_id < 0) || done) {
        printf("%s: ERROR - hal init failed\n", modname);
        retval = hal_comp_id;
        goto out_close;
    }

    /* Выделяем память для размещения пинов HAL */
    haldata = (haldata_t *)hal_malloc(sizeof(haldata_t));
    if ((haldata == 0) || done) {
        printf("%s: ERROR - unable to allocate shared memory\n", modname);
        retval = -1;
        goto out_close;
    }

    /* Создаём выходные пины */
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->command_frequency), hal_comp_id, "%s.command-frequency", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_frequency), hal_comp_id, "%s.output-frequency", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_current), hal_comp_id, "%s.output-current", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_voltage), hal_comp_id, "%s.output-voltage", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_power), hal_comp_id, "%s.output-power", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_torque), hal_comp_id, "%s.output-torque", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->motor_rpm), hal_comp_id, "%s.motor-rpm", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->dc_bus_voltage), hal_comp_id, "%s.dc-bus-voltage", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->temperature), hal_comp_id, "%s.temperature", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->uptime), hal_comp_id, "%s.uptime", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->fault_code), hal_comp_id, "%s.fault-code", 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_OUT, &(haldata->modbus_ok), hal_comp_id, "%s.modbus-ok", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->error_count), hal_comp_id, "%s.error-count", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->error_code), hal_comp_id, "%s.error-code", modname);
    if (retval!=0) goto out_closeHAL;

    /* Создаём входные пины */
    retval = hal_pin_float_newf(HAL_IN, &(haldata->command_speed), hal_comp_id, "%s.command-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_bit_newf(HAL_IN, &(haldata->spindle_fwd), hal_comp_id, "%s.spindle-fwd", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_bit_newf(HAL_IN, &(haldata->spindle_rev), hal_comp_id, "%s.spindle-rev", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_bit_newf(HAL_IN, &(haldata->fault_reset), hal_comp_id, "%s.fault-reset", modname);
    if (retval!=0) goto out_closeHAL;

    /* Инициализация пинов HAL и прочих переменных */
    *haldata->command_frequency = 0;
    *haldata->output_frequency = 0;
    *haldata->output_current = 0;
    *haldata->output_voltage = 0;
    *haldata->output_power = 0;
    *haldata->output_torque = 0;
    *haldata->motor_rpm = 0;
    *haldata->dc_bus_voltage = 0;
    *haldata->temperature = 0;
    *haldata->uptime = 0;
    *haldata->fault_code = 0;

    *haldata->at_speed = 0;
    *haldata->modbus_ok = 0;
    *haldata->error_count = 0;
    *haldata->error_code = 0;

    *haldata->spindle_on = 0;
    *haldata->spindle_fwd = 0;
    *haldata->spindle_rev = 0;
    *haldata->fault_reset = 0;

    hal_ready(hal_comp_id);
    
    data_ok_count = 0;
    error_count = 0;
    old_control = -1;
    old_speed = -1;

    /* Сердце программы - в бесконечном цикле читаем и пишем параметры */
    while (done==0) {
        read_data(&mb_param, slave, haldata);
        write_data(&mb_param, slave, haldata);

        /* Сравнение заданной и текущей скорости */
        if ((*haldata->command_speed != 0) && (*haldata->spindle_on))
            diff = fabs(1. - (*haldata->motor_rpm / abs(*haldata->command_speed)));
        else
            diff = 999.9;

        /* Заданная скорость достигнута? */
        if (diff > AT_SPEED_TOLERANCE)
            *haldata->at_speed = 0;
        else
            *haldata->at_speed = 1;
        
        // Зажигаем лампочку Modbus OK
        if (data_ok_count > MIN_MODBUS_OK)
            *haldata->modbus_ok = 1;

        /* Пауза */
        if (looptime < 0.001) looptime = 0.001;
        if (looptime > 2.0) looptime = 2.0;
        loop_timespec.tv_sec = (time_t)(looptime);
        loop_timespec.tv_nsec = (long)((looptime - loop_timespec.tv_sec) * 1000000000l);
        nanosleep(&loop_timespec, &remaining);
    }
    
    retval = 0;	/* Конец программы */
out_closeHAL:
    hal_exit(hal_comp_id);
out_close:
    modbus_close(&mb_param);
out_noclose:
    return retval;
}
Добавленная функция называется "framestart_delay". Можно будет допилить расчет задержки в зависимости от выбранной скорости, пока просто для праверки задержка грубо посчитана на 9600. На этой скорости и проверялось...
Аватара пользователя
Alexsh
Опытный
Сообщения: 100
Зарегистрирован: 25 дек 2015, 22:28
Репутация: 15
Настоящее имя: Алексей
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение Alexsh »

Приветствую!
Подскажите пожалуйста сконфигурировать под этот частотник.
Буду весьма благодарен за помощь.
Вложения
Manual User 9000 series converter_rus.pdf
(2.85 МБ) 1199 скачиваний
Аватара пользователя
aekhv
Мастер
Сообщения: 393
Зарегистрирован: 17 окт 2014, 15:03
Репутация: 218
Настоящее имя: Александр
Откуда: г.Хабаровск
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение aekhv »

Подсказать тут всегда рады. Только что конкретно подсказать-то? Дайте больше конкретики. Что уже пробовали сделать, и что не получилось?
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

Под него сконфигурировать не получится. Эта заготовка под Modbus RTU, а данный частотник работаетв формате ASCII...
Аватара пользователя
Alexsh
Опытный
Сообщения: 100
Зарегистрирован: 25 дек 2015, 22:28
Репутация: 15
Настоящее имя: Алексей
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение Alexsh »

Проблема со значениями регистров- не понятно где взять значения регистров чтения и записи.
Управляющие значения командного регистра нашел (кажется), но выглядят они примерно так:
СТОП сс=00
СБРО сс=01
ХОД ВПЕРЕД сс=01
и т.д.

В мануале читаю и не могу понять ничего..
Помогите разобраться на примере хотя бы одной-двух строчек.
В мануале по RS485 страницы 92-96.
Аватара пользователя
Alexsh
Опытный
Сообщения: 100
Зарегистрирован: 25 дек 2015, 22:28
Репутация: 15
Настоящее имя: Алексей
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение Alexsh »

N1X писал(а):Под него сконфигурировать не получится. Эта заготовка под Modbus RTU, а данный частотник работаетв формате ASCII...
Спасибо, а под этот формат есть какие нибудь заготовки на форуме ?
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

Не попадалось, по крайней мере мне.
Аватара пользователя
Alexsh
Опытный
Сообщения: 100
Зарегистрирован: 25 дек 2015, 22:28
Репутация: 15
Настоящее имя: Алексей
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение Alexsh »

Очень жаль. будет пока аналоговое управление.
MGG
Мастер
Сообщения: 3673
Зарегистрирован: 08 фев 2016, 16:33
Репутация: 1010
Настоящее имя: Манн Геннадий Геннадьевич
Откуда: Москва
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение MGG »

Товарищи, у меня тут в сявзи с поправками товарища N1X, стало работать стабильней, но появились новые приколы, не могу найти множитель частоты, а именно, в лцнц показывает заданная 50, по факту выдает 20, на часотнике 50, в лцнц заданная 125.
И еще момент, почему то останавливается своим ходом.
http://www.cnc-club.ru/forum/viewtopic. ... 76#p304076 Поставки оборудования для ваших станков
https://www.instagram.com/dtw.moscow/
dtw.moscow@gmail.com
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

Есть такое.
#define CONTROL_Stop 0x0006 // стоп
Вот так должно быть, ибо 5 - coast to stop, остановка выбегом, а 6 - deceleration to stop, соответственно с торможением
MGG писал(а): в лцнц показывает заданная 50, по факту выдает 20, на часотнике 50,

Код: Выделить всё

if (SET_FREQUENCY_MODE == 1) {
        // Переводим заданную скорость (об/мин) в проценты (%)
        // Пример: command_speed = MAX_SPEED = 24000,
        // тогда speed_cmd = 24000 / (0.01 * 24000) = 100%
        speed_cmd /= 0.01 * MAX_SPEED;
        // Корректируем значение перед записью (см. мануал на свой ПЧ!)
        // Пример: 100% = 100.0% = 1000
        speed_cmd *= 100;d_cmd *= 100;
Ну и

Код: Выделить всё

#define SET_FREQUENCY_MODE		1
т.к. частотник работает с заданичем по частоте в % от максимальных оборотов.

Вот полный текст модуля:

Код: Выделить всё

/*
    spindle-vfd.c
    Universal LinuxCNC component for MODBUS RTU VFD's by BentScrew.
    Special for <cnc-club.ru>, September 2015.
    http://www.cnc-club.ru/forum/viewtopic.php?f=15&t=9406

    Based on gs2_vfd.c
    Copyright (C) 2007, 2008 Stephen Wille Padnos, Thoth Systems, Inc.

    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.


    This is a userspace HAL program, which may be loaded using the halcmd "loadusr" command:
        loadusr spindle-vfd
    There are several command-line options.  Options that have a set list of possible values may
        be set by using any number of characters that are unique.  For example, --rate 5 will use
        a baud rate of 57600, since no other available baud rates start with "5"
    -b or --bits <n> (default 8)
        Set number of data bits to <n>, where n must be from 5 to 8 inclusive
    -d or --device <path> (default /dev/ttyUSB0)
        Set the name of the serial device node to use
    -g or --debug
        Turn on debugging messages.  This will also set the verbose flag.  Debug mode will cause
        all modbus messages to be printed in hex on the terminal.
    -n or --name <string> (default spindle-vfd)
        Set the name of the HAL module.  The HAL comp name will be set to <string>, and all pin
        and parameter names will begin with <string>.
    -p or --parity {even,odd,none} (defalt none)
        Set serial parity to even, odd, or none.
    -r or --rate <n> (default 38400)
        Set baud rate to <n>.  It is an error if the rate is not one of the following:
        110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
    -s or --stopbits {1,2} (default 1)
        Set serial stop bits to 1 or 2
    -t or --target <n> (default 1)
        Set MODBUS target (slave) number. This must match the device number you set on the VFD.
    -v or --verbose
        Turn on debug messages.  Note that if there are serial errors, this may become annoying.
        At the moment, it doesn't make much difference most of the time.    
*/

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <getopt.h>
#include <math.h>
#include "rtapi.h"
#include "hal.h"
#include "modbus.h"

/* Настройки связи по умолчанию, если в custom.hal параметры не были указаны явно */
#define DEFAULT_Slave		1		// адрес преобразователя частоты
#define DEFAULT_Device		"/dev/ttyUSB0"	// последовательный порт
#define DEFAULT_Baud		19200		// скорость, бод
#define DEFAULT_Bits		8		// количество бит: 5 .. 8
#define DEFAULT_Parity		"none"		// чётность: even, odd, none
#define DEFAULT_Stopbits	1		// стоп-биты: 1, 2
#define DEFAULT_Debug		0		// показывать запросы/ответы modbus? 0 - нет , 1 - да
#define DEFAULT_Verbose		0		// показывать сервисные сообщения? 0 - нет , 1 - да

/* Пауза между циклами чтения-записи, не изменяйте без необходимости */
#define DEFAULT_Looptime	0.1		// 0.001 .. 2.0

/* Регистры для чтения. Укажите значения из мануала на свой ПЧ! */
#define STATUS_Command_Frequency	0x1000	// заданная (опорная) частота
#define STATUS_Output_Frequency		0x1001	// выходная частота
#define STATUS_Output_Current		0x1004	// выходной ток
#define STATUS_Output_Voltage		0x1003	// выходное напряжение
#define STATUS_Output_Power		0x1005	// выходная мощность
#define STATUS_Output_Torque		0x1006	// выходной момент
#define STATUS_Motor_RPM		0x1007	// скорость вращения
#define STATUS_DC_Bus_Voltage		0x1002	// напряжение на шине DC
#define STATUS_Temperature		0x1011	// температура
#define STATUS_Uptime			0x101B	// время наработки
#define STATUS_Fault_Code		0x8000	// код ошибки ПЧ

/* Регистры для записи. Укажите значения из мануала на свой ПЧ! */
#define COMMAND_REGISTER		0x2000	// команда вперёд/назад/стоп
#define SET_FREQUENCY_REGISTER		0x1000	// задание частоты

/* Управляющие значения командного регистра. Аналогично, см. мануал! */
#define CONTROL_Run_Fwd			0x0001	// вперёд
#define CONTROL_Run_Rev			0x0002	// назад
#define CONTROL_Stop			0x0006	// стоп
#define CONTROL_Fault_Reset		0x0007  // сброс ошибки

/* В зависимости от модели ПЧ скорость вращения может задаваться либо заданием частоты (Гц), либо
   диапазоном 0 ... 100%, где 100% - максимальная скорость. Значения: 0 - частота, 1 - проценты */
#define SET_FREQUENCY_MODE		1

/* Минимальная безопасная скорость вращения, об/мин */
#define MIN_SPEED			00000.0

/* Максимальная скорость, об/мин. Если SET_FREQUENCY_MODE = 1, значение ниже будет принято за 100% */
#define MAX_SPEED			24000.0

/* Допустимое отклонение от заданной скорости, 0.01 = 1% */
#define AT_SPEED_TOLERANCE		0.01

/* Количество непрерывных успешных транзакций для уверенности в наличии связи по Modbus */
#define MIN_MODBUS_OK			10
 
/* HAL пины */
typedef struct {
  /* выходы для мониторинга параметров ПЧ */
  hal_float_t	*command_frequency;	// заданная (опорная) частота
  hal_float_t	*output_frequency;	// выходная частота
  hal_float_t	*output_current;	// выходной ток
  hal_float_t	*output_voltage;	// выходное напряжение
  hal_float_t	*output_power;		// выходная мощность
  hal_float_t	*output_torque;		// выходной момент
  hal_float_t	*motor_rpm;		// скорость вращения
  hal_float_t	*dc_bus_voltage;	// напряжение на шине DC
  hal_float_t	*temperature;		// температура
  hal_s32_t	*uptime;		// время наработки
  hal_s32_t	*fault_code;		// код ошибки ПЧ

  /* служебные выходы */
  hal_bit_t	*at_speed;		// заданная скорость достигнута
  hal_bit_t	*modbus_ok;		// связь установлена
  hal_s32_t	*error_count;		// количество ошибок приёма-передачи
  hal_s32_t	*error_code;		// код последней ошибки связи

  /* входы */
  hal_float_t	*command_speed;		// задание скорости вращения, об/мин
  hal_bit_t	*spindle_on;		// команда на запуск
  hal_bit_t	*spindle_fwd;		// направление - вперёд
  hal_bit_t	*spindle_rev;		// направление - назад
  hal_bit_t	*fault_reset;		// сброс ошибки

} haldata_t;

/* Имя компонента по умолчанию,
   при вызове из командной строки может быть изменено при помощи ключа "-n" */
char *modname = "spindle-vfd";

#undef DEBUG

unsigned short int data_ok_count;	// Число непрерывных успешных транзакций, при ошибке обнуляется
unsigned short int error_count;		// Количество ошибок приёма-передачи
int verbose;				// Флаг расширенного логирования
int old_control;			// Для контроля необходимости обновить командное слово
hal_float_t old_speed;			// Для контроля необходимости обновить заданную скорость

static struct option long_options[] = {
    {"bits", 1, 0, 'b'},
    {"device", 1, 0, 'd'},
    {"debug", 0, 0, 'g'},
    {"help", 0, 0, 'h'},
    {"name", 1, 0, 'n'},
    {"parity", 1, 0, 'p'},
    {"rate", 1, 0, 'r'},
    {"stopbits", 1, 0, 's'},
    {"target", 1, 0, 't'},
    {"verbose", 0, 0, 'v'},
    {0,0,0,0}
};

/* Массивы со списком возможных значений параметров связи */
static char *option_string = "b:d:ghn:p:r:s:t:v";
static char *bitstrings[] = {"5", "6", "7", "8", NULL};
static char *paritystrings[] = {"even", "odd", "none", NULL};
static char *ratestrings[] = {"110", "300", "600", "1200", "2400", "4800", "9600",
    "19200", "38400", "57600", "115200", NULL};
static char *stopstrings[] = {"1", "2", NULL};

static int done;

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;        // множественные совпадения
            match=which;
        }
        ++which;
    }
    return match;
}

/*===============Функция задержки перед кадром===================*/
void framestart_delay(void) {
	struct timespec fs_dt, fs_rm;
	fs_dt.tv_sec = 0;
	fs_dt.tv_nsec = (long) 3500000;
	nanosleep(&fs_dt, &fs_rm);
}

/* Функция читает параметры из ПЧ */
int read_data(modbus_param_t *param, int slave, haldata_t *hal_data_block) {
    int receive_data[MAX_READ_HOLD_REGS]; // массив слов для принятых данных, MAX_READ_HOLD_REGS = 100
    int tmp, cnt;

    /* Проверка на корректность вызова функции */
    if (hal_data_block == NULL)
        return -1;
    if (param == NULL)
        return -1;

    /* ЧТЕНИЕ РЕГИСТРОВ: СПОСОБ 1. Один регистр - один запрос. Универсальный способ. */

    // Функция возвращает число прочитанных 16-ти битных регистров, запрашиваем содержимое ОДНОГО регистра
	
	framestart_delay();
	
    tmp = read_holding_registers(param, slave, STATUS_Command_Frequency, 1, receive_data);
    // Если один регистр был прочитан, то передаём его соответствующему пину HAL
    if (tmp == 1) {
        /* Т.к. содержимое регистра - это 16-ти битное целое беззнаковое число,
        требуется дополнительный множитель, чтобы получить корректное значение.
        В данном случае это 0.1. Уточните верный множитель в мануале своего ПЧ. */ 
        *(hal_data_block->command_frequency) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

    // Повторяем столько раз, сколько регистров требуется прочитать
	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Frequency, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_frequency) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Current, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_current) = receive_data[0] * 0.01;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Voltage, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_voltage) = receive_data[0];
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Power, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_power) = receive_data[0] * 0.01;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Output_Torque, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->output_torque) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Motor_RPM, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->motor_rpm) = receive_data[0] * 60.0;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_DC_Bus_Voltage, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->dc_bus_voltage) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Temperature, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->temperature) = receive_data[0] * 0.1;
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Uptime, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->uptime) = receive_data[0];
        data_ok_count++;
    } else goto failed;

	framestart_delay();
    tmp = read_holding_registers(param, slave, STATUS_Fault_Code, 1, receive_data);
    if (tmp == 1) {
        *(hal_data_block->fault_code) = receive_data[0];
        data_ok_count++;
    } else goto failed;

/*
    ЧТЕНИЕ РЕГИСТРОВ: СПОСОБ 2. ПОСЛЕДОВАТЕЛЬНАЯ цепочка регистров читается ОДНИМ запросом.
    Работает на всех ПЧ, поддерживающих функцию чтения 03, но возможны ограничения.
    Например, Tecorp HC1C+ не может прочитать более 4-х регистров одной командой, поэтому для большего числа
    регистров требуется несколько запросов. Чтобы узнать сколько регистров способен прочитать ваш ПЧ,
    включите флаг отладки (--debug или -g) и запросите максимальное требуемое число. В логе будет видно
    сколько регистров фактически возвращается в ответ на запрос.
       
    ПРИМЕР ЛОГА:

    spindle-vfd : requested 11 registers, see below how much registers returned

    [01][03][01][00][00][0B][55][CD]				// Запрос содержимого 11-ти [0x00][0x0B] регистров,
								//   начиная с регистра 256 [0x01][0x00]
    Waiting for response (27)...				// Ожидаемая длина ответного пакета в байтах, включая CRC и др.
    <01><03><08><01><F4><00><00><00><00><00><00><E1><D4>	// Фактически пакет содержит <0x08> байт данных (4 слова),
								//   хотя должно быть <0x16> байт (11 слов)
    ERROR Communication time out				// Время ожидания истекло
*/

/*
    // Функция возвращает число прочитанных 16-ти битных регистров, запрашиваем cnt регистров
    cnt = 11;
    if (param->debug == 1) printf("%s : requested %d registers, see below how much registers returned\n", modname, cnt);
    tmp = read_holding_registers(param, slave, STATUS_Command_Frequency, cnt, receive_data);
    if (tmp == cnt) {
        *(hal_data_block->command_frequency) = receive_data[0] * 0.1;
        *(hal_data_block->output_frequency) = receive_data[1] * 0.1;
        *(hal_data_block->output_current) = receive_data[2] * 0.1;
        *(hal_data_block->output_voltage) = receive_data[3] * 0.1;
        *(hal_data_block->output_power) = receive_data[4] * 0.1;
        *(hal_data_block->output_torque) = receive_data[5] * 0.1;
        *(hal_data_block->motor_rpm) = receive_data[6] * 10;
        *(hal_data_block->dc_bus_voltage) = receive_data[7] * 0.1;
        *(hal_data_block->temperature) = receive_data[8] * 0.1;
        *(hal_data_block->uptime) = receive_data[9];
        *(hal_data_block->fault_code) = receive_data[10];
        data_ok_count++;
    } else goto failed;
*/

    // всё хорошо, ошибок нет :)
    return 0;

    // всё плохо, данные не получены :-/
    failed:

    error_count++; // увеличиваем счётчик ошибок
    data_ok_count = 0; // сбрасываем счётчик успешных транзакций
    *(hal_data_block->error_count) = error_count;
    *(hal_data_block->error_code) = tmp;
    *(hal_data_block->modbus_ok) = 0;
    if (verbose == 1) printf("%s : data reading error!\n", modname);
    return -1;
}

/* Функция передаёт в ПЧ задание скорости и команду на запуск/останов */
int write_data(modbus_param_t *param, int slave, haldata_t *hal_data_block) {
    hal_float_t speed_cmd;
    int control, tmp;
    
    /* Корректируем заданную скорость, если она внезапно (!) отрицательна */
    speed_cmd = abs(*hal_data_block->command_speed);
    /* Проверка на выход за допустимые лимиты */
    if (speed_cmd > MAX_SPEED)
          speed_cmd = MAX_SPEED;
    if (speed_cmd < MIN_SPEED)
          speed_cmd = MIN_SPEED;

    if (SET_FREQUENCY_MODE == 1) {
        // Переводим заданную скорость (об/мин) в проценты (%)
        // Пример: command_speed = MAX_SPEED = 24000,
        // тогда speed_cmd = 24000 / (0.01 * 24000) = 100%
        speed_cmd /= 0.01 * MAX_SPEED;
        // Корректируем значение перед записью (см. мануал на свой ПЧ!)
        // Пример: 100% = 100.0% = 1000
        speed_cmd *= 100;
    } else {
        // Переводим заданную скорость (об/мин) в частоту (Гц)
        // Пример: command_speed = MAX_SPEED = 24000,
        // тогда speed_cmd = 24000 / 60 = 400Гц
        speed_cmd /= 60.0;
        // Корректируем значение перед записью (см. мануал на свой ПЧ!)
        // Пример: 400Гц = 400.0Гц = 4000
        speed_cmd *= 10; 
    }

    // Если заданная скорость изменилась...
    if (*hal_data_block->command_speed != old_speed) {
        if (verbose == 1) printf("%s : updating frequency register [%f RPM, 0x%X] ... ",
                              modname, *hal_data_block->command_speed, (int)(speed_cmd));
        // ...то записываем в ПЧ значение частоты (всего - один 16-ти битный регистр)
		framestart_delay();
        tmp = preset_single_register(param, slave, SET_FREQUENCY_REGISTER, (int)(speed_cmd));
        // Проверка результата - сколько регистров записано?
        if (tmp == 1) {
            if (verbose == 1) printf("Write OK!\n");
            old_speed = *hal_data_block->command_speed;
            data_ok_count++;
        } else {
            goto failed;
        }
    }

    /* Подготовка управляющего слова */
    control = CONTROL_Stop;			// Начальное состояние - "стоп"
    if (*hal_data_block->spindle_on) {		// Если подан сигнал на запуск...
        if (*hal_data_block->spindle_fwd) {
            control = CONTROL_Run_Fwd;		// ...то "крутим вперёд"
        }
        if (*hal_data_block->spindle_rev) {
            control = CONTROL_Run_Rev;		// ...или "крутим назад"
        }
    }

    if (*hal_data_block->fault_reset == 1) {	// Кнопка "сбросить ошибку" нажата?
        control = CONTROL_Fault_Reset;
    }

    // Если управляющее слово изменилось...
    if (control != old_control) {
        if (verbose == 1) printf("%s : updating command register [0x%X] ... ", modname, control);
        // ...то записываем его в ПЧ
		framestart_delay();
        tmp = preset_single_register(param, slave, COMMAND_REGISTER, control);
        // Проверка результата - сколько регистров записано?
        if (tmp == 1) {
            if (control == CONTROL_Fault_Reset) {	// Если команда "сбросить ошибку" записана успешно, то...
                *hal_data_block->fault_reset = 0;	// ...пин деактивируем
            }
            if (verbose == 1) printf("Write OK!\n");
            old_control = control;
            data_ok_count++;
        } else {
            goto failed;
        }
    }

    // всё хорошо, ошибок нет :)
    return 0;

    // всё плохо, данные не получены :-/
    failed:

    error_count++; // увеличиваем счётчик ошибок
    data_ok_count = 0; // сбрасываем счётчик успешных транзакций
    *(hal_data_block->error_count) = error_count;
    *(hal_data_block->error_code) = tmp;
    *(hal_data_block->modbus_ok) = 0;
    if (verbose == 1) printf("Write FAIL!\n");
    return -1;
}

/* Справка, вызывается ключом "-h", а также при любом неверном аргументе */
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 spindle-vfd\n"
    "There are several command-line options.  Options that have a set list of possible values may\n"
    "    be set by using any number of characters that are unique.  For example, --rate 5 will use\n"
    "    a baud rate of 57600, since no other available baud rates start with \"5\"\n"
    "-b or --bits <n> (default %d)\n"
    "    Set number of data bits to <n>, where n must be from 5 to 8 inclusive\n"
    "-d or --device <path> (default %s)\n"
    "    Set the name of the serial device node to use\n"
    "-g or --debug\n"
    "    Turn on debugging messages.  This will also set the verbose flag.  Debug mode will cause\n"
    "    all modbus messages to be printed in hex on the terminal.\n"
    "-n or --name <string> (default %s)\n"
    "    Set the name of the HAL module.  The HAL comp name will be set to <string>, and all pin\n"
    "    and parameter names will begin with <string>.\n"
    "-p or --parity {even,odd,none} (defalt %s)\n"
    "    Set serial parity to even, odd, or none.\n"
    "-r or --rate <n> (default %d)\n"
    "    Set baud rate to <n>.  It is an error if the rate is not one of the following:\n"
    "    110, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200\n"
    "-s or --stopbits {1,2} (default %d)\n"
    "    Set serial stop bits to 1 or 2\n"
    "-t or --target <n> (default %d)\n"
    "    Set MODBUS target (slave) number.  This must match the device number you set on the VFD.\n"
    "-v or --verbose\n"
    "    Turn on debug messages.  Note that if there are serial errors, this may become annoying.\n"
    "    At the moment, it doesn't make much difference most of the time.\n",
    DEFAULT_Bits, DEFAULT_Device, modname, DEFAULT_Parity,
    DEFAULT_Baud, DEFAULT_Stopbits, DEFAULT_Slave );
}

/* Основная функция */
int main(int argc, char **argv)
{
    int retval;
    modbus_param_t mb_param;
    haldata_t *haldata;
    int hal_comp_id;
    struct timespec loop_timespec, remaining;
    int slave, baud, bits, stopbits, debug;
    char *device, *parity, *endarg;
    int opt;
    int argindex, argvalue;
    float looptime;
    double diff;

    done = 0;

    /* Применение настроек связи по умолчанию */
    slave = DEFAULT_Slave;
    device = DEFAULT_Device;
    baud = DEFAULT_Baud;
    bits = DEFAULT_Bits;
    parity = DEFAULT_Parity;
    stopbits = DEFAULT_Stopbits;
    debug = DEFAULT_Debug;
    verbose = DEFAULT_Verbose;
    looptime = DEFAULT_Looptime;

    /* Разбор параметров командной строки */
    while ((opt=getopt_long(argc, argv, option_string, long_options, NULL)) != -1) {
        switch(opt) {
            case 'b':   // число бит данных
                argindex=match_string(optarg, bitstrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid number of bits: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                bits = atoi(bitstrings[argindex]);
                break;
            case 'd':   // последовательный порт
                if (strlen(optarg) > FILENAME_MAX) {
                    printf("%s: ERROR - device node name is too long: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                device = strdup(optarg);
                break;
            case 'g':   // флаг отладки
                debug = 1;
                verbose = 1;
                break;
            case 'n':   // имя компонента
                if (strlen(optarg) > HAL_NAME_LEN-20) {
                    printf("%s: ERROR - HAL module name too long: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                modname = strdup(optarg);
                break;
            case 'p':   // чётность
                argindex=match_string(optarg, paritystrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid parity: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                parity = paritystrings[argindex];
                break;
            case 'r':   // скорость связи
                argindex=match_string(optarg, ratestrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid baud rate: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                baud = atoi(ratestrings[argindex]);
                break;
            case 's':   // число стоп-бит
                argindex=match_string(optarg, stopstrings);
                if (argindex<0) {
                    printf("%s: ERROR - invalid number of stop bits: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                stopbits = atoi(stopstrings[argindex]);
                break;
            case 't':   // адрес устройства (slave)
                argvalue = strtol(optarg, &endarg, 10);
                if ((*endarg != '\0') || (argvalue < 1) || (argvalue > 254)) {
                    printf("%s: ERROR - invalid slave number: %s\n", modname, optarg);
                    retval = -1;
                    goto out_noclose;
                }
                slave = argvalue;
                break;
            case 'v':   // флаг расширенного логирования
                verbose = 1;
                break;
            case 'h':   // вызов справки
            default:
                usage(argc, argv);
                exit(0);
                break;
        }
    }

    printf("%s: device='%s', baud=%d, bits=%d, parity='%s', stopbits=%d, slave=%d, verbose=%d, debug=%d\n",
           modname, device, baud, bits, parity, stopbits, slave, verbose, debug);
    /* point TERM and INT signals at our quit function */
    /* if a signal is received between here and the main loop, it should prevent
            some initialization from happening */
    signal(SIGINT, quit);
    signal(SIGTERM, quit);

    /* Инициализация последовательного порта */
    modbus_init_rtu(&mb_param, device, baud, parity, bits, stopbits, debug);
    mb_param.debug = debug;
    if (((retval = modbus_connect(&mb_param))!=0) || done) {
        printf("%s: ERROR - couldn't open serial device\n", modname);
        goto out_noclose;
    }

    /* Создаём компонент HAL */
    hal_comp_id = hal_init(modname);
    if ((hal_comp_id < 0) || done) {
        printf("%s: ERROR - hal init failed\n", modname);
        retval = hal_comp_id;
        goto out_close;
    }

    /* Выделяем память для размещения пинов HAL */
    haldata = (haldata_t *)hal_malloc(sizeof(haldata_t));
    if ((haldata == 0) || done) {
        printf("%s: ERROR - unable to allocate shared memory\n", modname);
        retval = -1;
        goto out_close;
    }

    /* Создаём выходные пины */
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->command_frequency), hal_comp_id, "%s.command-frequency", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_frequency), hal_comp_id, "%s.output-frequency", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_current), hal_comp_id, "%s.output-current", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_voltage), hal_comp_id, "%s.output-voltage", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_power), hal_comp_id, "%s.output-power", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->output_torque), hal_comp_id, "%s.output-torque", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->motor_rpm), hal_comp_id, "%s.motor-rpm", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->dc_bus_voltage), hal_comp_id, "%s.dc-bus-voltage", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_float_newf(HAL_OUT, &(haldata->temperature), hal_comp_id, "%s.temperature", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->uptime), hal_comp_id, "%s.uptime", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->fault_code), hal_comp_id, "%s.fault-code", 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_OUT, &(haldata->modbus_ok), hal_comp_id, "%s.modbus-ok", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->error_count), hal_comp_id, "%s.error-count", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_s32_newf(HAL_OUT, &(haldata->error_code), hal_comp_id, "%s.error-code", modname);
    if (retval!=0) goto out_closeHAL;

    /* Создаём входные пины */
    retval = hal_pin_float_newf(HAL_IN, &(haldata->command_speed), hal_comp_id, "%s.command-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_bit_newf(HAL_IN, &(haldata->spindle_fwd), hal_comp_id, "%s.spindle-fwd", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_bit_newf(HAL_IN, &(haldata->spindle_rev), hal_comp_id, "%s.spindle-rev", modname);
    if (retval!=0) goto out_closeHAL;
    retval = hal_pin_bit_newf(HAL_IN, &(haldata->fault_reset), hal_comp_id, "%s.fault-reset", modname);
    if (retval!=0) goto out_closeHAL;

    /* Инициализация пинов HAL и прочих переменных */
    *haldata->command_frequency = 0;
    *haldata->output_frequency = 0;
    *haldata->output_current = 0;
    *haldata->output_voltage = 0;
    *haldata->output_power = 0;
    *haldata->output_torque = 0;
    *haldata->motor_rpm = 0;
    *haldata->dc_bus_voltage = 0;
    *haldata->temperature = 0;
    *haldata->uptime = 0;
    *haldata->fault_code = 0;

    *haldata->at_speed = 0;
    *haldata->modbus_ok = 0;
    *haldata->error_count = 0;
    *haldata->error_code = 0;

    *haldata->spindle_on = 0;
    *haldata->spindle_fwd = 0;
    *haldata->spindle_rev = 0;
    *haldata->fault_reset = 0;

    hal_ready(hal_comp_id);
    
    data_ok_count = 0;
    error_count = 0;
    old_control = -1;
    old_speed = -1;

    /* Сердце программы - в бесконечном цикле читаем и пишем параметры */
    while (done==0) {
        read_data(&mb_param, slave, haldata);
        write_data(&mb_param, slave, haldata);

        /* Сравнение заданной и текущей скорости */
        if ((*haldata->command_speed != 0) && (*haldata->spindle_on))
            diff = fabs(1. - (*haldata->motor_rpm / abs(*haldata->command_speed)));
        else
            diff = 999.9;

        /* Заданная скорость достигнута? */
        if (diff > AT_SPEED_TOLERANCE)
            *haldata->at_speed = 0;
        else
            *haldata->at_speed = 1;
        
        // Зажигаем лампочку Modbus OK
        if (data_ok_count > MIN_MODBUS_OK)
            *haldata->modbus_ok = 1;

        /* Пауза */
        if (looptime < 0.001) looptime = 0.001;
        if (looptime > 2.0) looptime = 2.0;
        loop_timespec.tv_sec = (time_t)(looptime);
        loop_timespec.tv_nsec = (long)((looptime - loop_timespec.tv_sec) * 1000000000l);
        nanosleep(&loop_timespec, &remaining);
    }
    
    retval = 0;	/* Конец программы */
out_closeHAL:
    hal_exit(hal_comp_id);
out_close:
    modbus_close(&mb_param);
out_noclose:
    return retval;
}
С твоим частотником должен нормально работать.
MGG
Мастер
Сообщения: 3673
Зарегистрирован: 08 фев 2016, 16:33
Репутация: 1010
Настоящее имя: Манн Геннадий Геннадьевич
Откуда: Москва
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение MGG »

Не пойму куда мне множитель в данном слуаче впихнуть, заданная и фактическая с разницей в 2.5 раза. Т.е. то что выдает лцнц в 2.5 раза меньше факта
Вложения
modbus1.png (3301 просмотр) <a class='original' href='./download/file.php?id=121475&sid=1f8b028be58ea17f0772d96d47658311&mode=view' target=_blank>Загрузить оригинал (162.73 КБ)</a>
http://www.cnc-club.ru/forum/viewtopic. ... 76#p304076 Поставки оборудования для ваших станков
https://www.instagram.com/dtw.moscow/
dtw.moscow@gmail.com
MGG
Мастер
Сообщения: 3673
Зарегистрирован: 08 фев 2016, 16:33
Репутация: 1010
Настоящее имя: Манн Геннадий Геннадьевич
Откуда: Москва
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение MGG »

Разобрался!!!
#define STATUS_Command_Frequency 0x1001 // заданная (опорная) частота - тут стояло 1000, поменял, взлетело, хотя не думаю что это правильно.
#define STATUS_Output_Frequency 0x1001 // выходная частота


Но с рабочего конфига скорость не регулируется :(


Дорогой дневник, победил, это был регистр чтения

Код: Выделить всё

tmp = read_holding_registers(param, slave, STATUS_Command_Frequency, 1, receive_data);
        // Если один регистр был прочитан, то передаём его соответствующему пину HAL
        if (tmp == 1) {
            /* Т.к. содержимое регистра - это 16-ти битное целое беззнаковое число,
            требуется дополнительный множитель, чтобы получить корректное значение.
            В данном случае это 0.1. Уточните верный множитель в мануале своего ПЧ. */ 
            *(hal_data_block->command_frequency) = receive_data[0] * 0.04;
            data_ok_count++;
        } else goto failed;
Вот с рабочего конфига скорость так и не регулируется. нет даже кнопок + и -, ну и в принципе ошибок много валится. Понять бы с чем это связано, с ошибками думаю надо землить, из за серв, только вот ГНД свистка на планету или в ближайшую землю?
http://www.cnc-club.ru/forum/viewtopic. ... 76#p304076 Поставки оборудования для ваших станков
https://www.instagram.com/dtw.moscow/
dtw.moscow@gmail.com
Аватара пользователя
N1X
Мастер
Сообщения: 3653
Зарегистрирован: 16 фев 2015, 21:19
Репутация: 1645
Настоящее имя: Владимир
Откуда: Беларусь, Гомель
Контактная информация:

Re: Частотник и Modbus. Универсальная заготовка.

Сообщение N1X »

)))
modbus1.png (3283 просмотра) <a class='original' href='./download/file.php?id=121489&sid=1f8b028be58ea17f0772d96d47658311&mode=view' target=_blank>Загрузить оригинал (162.52 КБ)</a>
Ответить

Вернуться в «LinuxCNC»