diff --git a/FluidNC/src/Report.cpp b/FluidNC/src/Report.cpp index 4ddb94fba..683a57368 100644 --- a/FluidNC/src/Report.cpp +++ b/FluidNC/src/Report.cpp @@ -628,7 +628,7 @@ void hex_msg(uint8_t* buf, const char* prefix, int len) { char temp[20]; sprintf(report, "%s", prefix); for (int i = 0; i < len; i++) { - sprintf(temp, " 0x%02X", buf[i]); + sprintf(temp, " %02X", buf[i]); strcat(report, temp); } diff --git a/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h b/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h index cd85d1471..df2d54464 100644 --- a/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h +++ b/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h @@ -60,7 +60,9 @@ namespace Spindles { void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) { return get_status_ok_and_init(data, true); } + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { + return get_status_ok_and_init(data, true); + } response_parser get_current_speed(ModbusCommand& data) override; response_parser get_current_direction(ModbusCommand& data) override { return nullptr; }; response_parser get_status_ok(ModbusCommand& data) override { return get_status_ok_and_init(data, false); } diff --git a/FluidNC/src/Spindles/VFD/GenericProtocol.cpp b/FluidNC/src/Spindles/VFD/GenericProtocol.cpp new file mode 100644 index 000000000..758d4fc33 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/GenericProtocol.cpp @@ -0,0 +1,268 @@ +// Copyright (c) 2021 - Marco Wagner +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#include "GenericProtocol.h" + +#include "../VFDSpindle.h" + +#include "src/string_util.h" +#include + +namespace Spindles { + namespace VFD { + bool split(std::string_view& input, std::string_view& token, const char* delims) { + if (input.size() == 0) { + return false; + } + auto pos = input.find_first_of(delims); + if (pos != std::string_view::npos) { + token = input.substr(0, pos); + input = input.substr(pos + 1); + } else { + token = input; + input = ""; + } + return true; + } + + bool from_decimal(std::string_view str, uint32_t& value) { + value = 0; + if (str.size() == 0) { + return false; + } + while (str.size()) { + if (!isdigit(str[0])) { + return false; + } + value = value * 10 + str[0] - '0'; + str = str.substr(1); + } + return true; + } + + void scale(uint32_t& n, std::string_view scale_str, uint32_t maxRPM) { + if (scale_str.empty()) { + return; + } + if (scale_str[0] == '%') { + scale_str.remove_prefix(1); + n *= 100; + n /= maxRPM; + } + if (scale_str[0] == '*') { + std::string_view numerator_str; + scale_str = scale_str.substr(1); + split(scale_str, numerator_str, "/"); + uint32_t numerator; + if (from_decimal(numerator_str, numerator)) { + n *= numerator; + } else { + log_error(spindle->name() << ": bad decimal number " << numerator_str); + return; + } + if (!scale_str.empty()) { + uint32_t denominator; + if (from_decimal(scale_str, denominator)) { + n /= denominator; + } else { + log_error(spindle->name() << ": bad decimal number " << scale_str); + return; + } + } + } else if (scale_str[0] == '/') { + std::string_view denominator_str(scale_str.substr(1)); + uint32_t denominator; + if (from_decimal(denominator_str, denominator)) { + n /= denominator; + } else { + log_error(spindle->name() << ": bad decimal number " << scale_str); + return; + } + } + } + + bool from_xdigit(char c, uint8_t& value) { + if (isdigit(c)) { + value = c - '0'; + return true; + } + c = tolower(c); + if (c >= 'a' && c <= 'f') { + value = 10 + c - 'a'; + return true; + } + return false; + } + + bool from_hex(std::string_view str, uint8_t& value) { + value = 0; + if (str.size() == 0 || str.size() > 2) { + return false; + } + uint8_t x; + while (str.size()) { + value <<= 4; + if (!from_xdigit(str[0], x)) { + return false; + } + value += x; + str = str.substr(1); + } + return true; + } + bool set_data(std::string_view token, std::basic_string_view& response_view, const char* name, uint32_t& data) { + if (string_util::starts_with_ignore_case(token, name)) { + uint32_t rval = (response_view[0] << 8) + (response_view[1] & 0xff); + uint32_t orval = rval; + scale(rval, token.substr(strlen(name)), 1); + data = rval; + response_view.remove_prefix(2); + return true; + } + return false; + } + bool GenericProtocol::parser(const uint8_t* response, VFDSpindle* spindle, GenericProtocol* instance) { + // This routine does not know the actual length of the response array + std::basic_string_view response_view(response, VFD_RS485_MAX_MSG_SIZE); + response_view.remove_prefix(1); // Remove the modbus ID which has already been checked + + std::string_view token; + std::string_view format(_response_format); + while (split(format, token, " ")) { + uint8_t val; + if (token == "") { + // Ignore repeated blanks + continue; + } + if (set_data(token, response_view, "rpm", spindle->_sync_dev_speed)) { + continue; + } + if (set_data(token, response_view, "minrpm", instance->_minRPM)) { + log_debug(spindle->name() << ": got minRPM " << instance->_minRPM); + continue; + } + if (set_data(token, response_view, "maxrpm", instance->_maxRPM)) { + log_debug(spindle->name() << ": got maxRPM " << instance->_maxRPM); + continue; + } + if (from_hex(token, val)) { + if (val != response_view[0]) { + log_debug(spindle->name() << ": response mismatch - expected " << to_hex(val) << " got " << to_hex(response_view[0])); + return false; + } + response_view.remove_prefix(1); + continue; + } + log_error(spindle->name() << ": bad response token " << token); + return false; + } + return true; + } + void GenericProtocol::send_vfd_command(const std::string cmd, ModbusCommand& data, uint32_t out) { + data.tx_length = 1; + data.rx_length = 1; + if (cmd.empty()) { + return; + } + + std::string_view out_view; + std::string_view in_view(cmd); + split(in_view, out_view, ">"); + _response_format = in_view; // Remember the response format for the parser + + std::string_view token; + while (data.tx_length < (VFD_RS485_MAX_MSG_SIZE - 3) && split(out_view, token, " ")) { + if (token == "") { + // Ignore repeated blanks + continue; + } + if (string_util::starts_with_ignore_case(token, "rpm")) { + uint32_t oout = out; + scale(out, token.substr(strlen("rpm")), _maxRPM); + data.msg[data.tx_length++] = out >> 8; + data.msg[data.tx_length++] = out & 0xff; + } else if (from_hex(token, data.msg[data.tx_length])) { + ++data.tx_length; + } else { + log_error(spindle->name() << ":Bad hex number " << token); + return; + } + } + while (data.rx_length < (VFD_RS485_MAX_MSG_SIZE - 3) && split(in_view, token, " ")) { + if (token == "") { + // Ignore repeated spaces + continue; + } + uint8_t x; + if (string_util::equal_ignore_case(token, "echo")) { + data.rx_length = data.tx_length; + break; + } + if (string_util::starts_with_ignore_case(token, "rpm") || string_util::starts_with_ignore_case(token, "minrpm") || + string_util::starts_with_ignore_case(token, "maxrpm")) { + data.rx_length += 2; + } else if (from_hex(token, x)) { + ++data.rx_length; + } else { + log_error(spindle->name() << ": bad hex number " << token); + } + } + } + void GenericProtocol::direction_command(SpindleState mode, ModbusCommand& data) { + switch (mode) { + case SpindleState::Cw: + send_vfd_command(_cw_cmd, data, 0); + break; + case SpindleState::Ccw: + send_vfd_command(_ccw_cmd, data, 0); + break; + default: // SpindleState::Disable + send_vfd_command(_off_cmd, data, 0); + break; + } + } + + void GenericProtocol::set_speed_command(uint32_t speed, ModbusCommand& data) { + send_vfd_command(_set_rpm_cmd, data, speed); + } + + VFDProtocol::response_parser GenericProtocol::get_current_speed(ModbusCommand& data) { + send_vfd_command(_get_rpm_cmd, data, 0); + return [](const uint8_t* response, VFDSpindle* spindle, VFDProtocol* protocol) -> bool { + auto instance = static_cast(protocol); + return instance->parser(response, spindle, instance); + }; + } + + void GenericProtocol::setup_speeds(VFDSpindle* vfd) { + vfd->shelfSpeeds(_minRPM, _maxRPM); + vfd->setupSpeeds(_maxRPM); + vfd->_slop = 300; + } + VFDProtocol::response_parser GenericProtocol::initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { + if (_maxRPM == 0xffffffff && !_get_max_rpm_cmd.empty()) { + send_vfd_command(_get_max_rpm_cmd, data, 0); + return [](const uint8_t* response, VFDSpindle* spindle, VFDProtocol* protocol) -> bool { + auto instance = static_cast(protocol); + return instance->parser(response, spindle, instance); + }; + } + if (_minRPM == 0xffffffff && !_get_min_rpm_cmd.empty()) { + send_vfd_command(_get_min_rpm_cmd, data, 0); + return [](const uint8_t* response, VFDSpindle* spindle, VFDProtocol* protocol) -> bool { + auto instance = static_cast(protocol); + return instance->parser(response, spindle, instance); + }; + } + if (vfd->_speeds.size() == 0) { + setup_speeds(vfd); + } + return nullptr; + } + + // Configuration registration + namespace { + SpindleFactory::DependentInstanceBuilder registration("ModbusVFD"); + } + } +} diff --git a/FluidNC/src/Spindles/VFD/GenericProtocol.h b/FluidNC/src/Spindles/VFD/GenericProtocol.h new file mode 100644 index 000000000..816adfc66 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/GenericProtocol.h @@ -0,0 +1,57 @@ +// Copyright (c) 2024 - Mitch Bradley +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class GenericProtocol : public VFDProtocol, Configuration::Configurable { + protected: + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t dev_speed, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) override; + response_parser get_current_speed(ModbusCommand& data) override; + response_parser get_current_direction(ModbusCommand& data) override { return nullptr; }; + response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } + + std::string _cw_cmd; + std::string _ccw_cmd; + std::string _off_cmd; + std::string _set_rpm_cmd; + std::string _get_min_rpm_cmd; + std::string _get_max_rpm_cmd; + std::string _get_rpm_cmd; + + bool use_delay_settings() const override { return _get_rpm_cmd.empty(); } + bool safety_polling() const override { return false; } + + private: + std::string _model; // VFD Model name + uint32_t* _response_data; + uint32_t _minRPM = 0xffffffff; + uint32_t _maxRPM = 0xffffffff; + bool parser(const uint8_t* response, VFDSpindle* spindle, GenericProtocol* protocol); + void send_vfd_command(const std::string cmd, ModbusCommand& data, uint32_t out); + std::string _response_format; + void setup_speeds(VFDSpindle* vfd); + + public: + void group(Configuration::HandlerBase& handler) override { + handler.item("model", _model); + handler.item("min_RPM", _minRPM, 0xffffffff); + handler.item("max_RPM", _maxRPM, 0xffffffff); + handler.item("cw_cmd", _cw_cmd); + handler.item("ccw_cmd", _ccw_cmd); + handler.item("off_cmd", _off_cmd); + handler.item("set_rpm_cmd", _set_rpm_cmd); + handler.item("get_min_rpm_cmd", _get_min_rpm_cmd); + handler.item("get_max_rpm_cmd", _get_max_rpm_cmd); + handler.item("get_rpm_cmd", _get_rpm_cmd); + } + }; + + } +} diff --git a/FluidNC/src/Spindles/VFD/H100Protocol.cpp b/FluidNC/src/Spindles/VFD/H100Protocol.cpp index 4bd2389bb..7744c23e2 100644 --- a/FluidNC/src/Spindles/VFD/H100Protocol.cpp +++ b/FluidNC/src/Spindles/VFD/H100Protocol.cpp @@ -70,7 +70,7 @@ namespace Spindles { } // This gets data from the VFD. It does not set any values - VFDProtocol::response_parser H100Protocol::initialization_sequence(int index, ModbusCommand& data) { + VFDProtocol::response_parser H100Protocol::initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { // NOTE: data length is excluding the CRC16 checksum. data.tx_length = 6; data.rx_length = 5; diff --git a/FluidNC/src/Spindles/VFD/H100Protocol.h b/FluidNC/src/Spindles/VFD/H100Protocol.h index d6a339d17..2bbca1427 100644 --- a/FluidNC/src/Spindles/VFD/H100Protocol.h +++ b/FluidNC/src/Spindles/VFD/H100Protocol.h @@ -20,7 +20,7 @@ namespace Spindles { void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) override; response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } response_parser get_current_speed(ModbusCommand& data) override; diff --git a/FluidNC/src/Spindles/VFD/H2AProtocol.cpp b/FluidNC/src/Spindles/VFD/H2AProtocol.cpp index 3345a957b..9f355c69e 100644 --- a/FluidNC/src/Spindles/VFD/H2AProtocol.cpp +++ b/FluidNC/src/Spindles/VFD/H2AProtocol.cpp @@ -56,7 +56,7 @@ namespace Spindles { data.msg[5] = speed & 0xFF; } - VFDProtocol::response_parser H2AProtocol::initialization_sequence(int index, ModbusCommand& data) { + VFDProtocol::response_parser H2AProtocol::initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { if (index == -1) { data.tx_length = 6; data.rx_length = 8; diff --git a/FluidNC/src/Spindles/VFD/H2AProtocol.h b/FluidNC/src/Spindles/VFD/H2AProtocol.h index 921daf3ac..694efb402 100644 --- a/FluidNC/src/Spindles/VFD/H2AProtocol.h +++ b/FluidNC/src/Spindles/VFD/H2AProtocol.h @@ -12,7 +12,7 @@ namespace Spindles { void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t dev_speed, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) override; response_parser get_current_speed(ModbusCommand& data) override; response_parser get_current_direction(ModbusCommand& data) override; response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } diff --git a/FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp b/FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp index c8e3469a7..087c70543 100644 --- a/FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp +++ b/FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp @@ -210,7 +210,7 @@ namespace Spindles { } // This gets data from the VFS. It does not set any values - VFDProtocol::response_parser HuanyangProtocol::initialization_sequence(int index, ModbusCommand& data) { + VFDProtocol::response_parser HuanyangProtocol::initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { // NOTE: data length is excluding the CRC16 checksum. data.tx_length = 6; data.rx_length = 6; diff --git a/FluidNC/src/Spindles/VFD/HuanyangProtocol.h b/FluidNC/src/Spindles/VFD/HuanyangProtocol.h index e11696d7c..073aaabbe 100644 --- a/FluidNC/src/Spindles/VFD/HuanyangProtocol.h +++ b/FluidNC/src/Spindles/VFD/HuanyangProtocol.h @@ -23,7 +23,7 @@ namespace Spindles { void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) override; response_parser get_status_ok(ModbusCommand& data) override; response_parser get_current_speed(ModbusCommand& data) override; }; diff --git a/FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp b/FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp index b02732dcd..3a5006410 100644 --- a/FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp +++ b/FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp @@ -69,7 +69,7 @@ namespace Spindles { log_debug("VFD: Set speed: " << hz / 100 << "hz or" << (hz * 60 / 100) << "rpm"); } - VFDProtocol::response_parser NowForeverProtocol::initialization_sequence(int index, ModbusCommand& data) { + VFDProtocol::response_parser NowForeverProtocol::initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { if (index == -1) { data.tx_length = 6; data.rx_length = 7; diff --git a/FluidNC/src/Spindles/VFD/NowForeverProtocol.h b/FluidNC/src/Spindles/VFD/NowForeverProtocol.h index fcb5763c8..7f8068d12 100644 --- a/FluidNC/src/Spindles/VFD/NowForeverProtocol.h +++ b/FluidNC/src/Spindles/VFD/NowForeverProtocol.h @@ -14,7 +14,7 @@ namespace Spindles { void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t hz, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) override; response_parser get_current_speed(ModbusCommand& data) override; response_parser get_current_direction(ModbusCommand& data) override; response_parser get_status_ok(ModbusCommand& data) override; diff --git a/FluidNC/src/Spindles/VFD/SiemensV20Protocol.cpp b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.cpp index 2152b4341..5edc56674 100644 --- a/FluidNC/src/Spindles/VFD/SiemensV20Protocol.cpp +++ b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.cpp @@ -169,7 +169,7 @@ namespace Spindles { data.msg[5] = ScaledFreq & 0xFF; } - VFDProtocol::response_parser SiemensV20Protocol::initialization_sequence(int index, ModbusCommand& data) { + VFDProtocol::response_parser SiemensV20Protocol::initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { // NOTE: data length is excluding the CRC16 checksum. data.tx_length = 6; data.rx_length = 5; diff --git a/FluidNC/src/Spindles/VFD/SiemensV20Protocol.h b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.h index b7eabd39f..4417c4fc9 100644 --- a/FluidNC/src/Spindles/VFD/SiemensV20Protocol.h +++ b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.h @@ -21,7 +21,7 @@ namespace Spindles { void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) override; response_parser get_current_speed(ModbusCommand& data) override; response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } diff --git a/FluidNC/src/Spindles/VFD/VFDProtocol.cpp b/FluidNC/src/Spindles/VFD/VFDProtocol.cpp index 84458bb4b..2d2b52605 100644 --- a/FluidNC/src/Spindles/VFD/VFDProtocol.cpp +++ b/FluidNC/src/Spindles/VFD/VFDProtocol.cpp @@ -7,26 +7,20 @@ #include #include -namespace Spindles -{ - namespace VFD - { - const int VFD_RS485_BUF_SIZE = 127; - const int RESPONSE_WAIT_MS = 1000; // how long to wait for a response - const int VFD_RS485_POLL_RATE = 250; // in milliseconds between commands - const TickType_t response_ticks = RESPONSE_WAIT_MS / portTICK_PERIOD_MS; // in milliseconds between commands +namespace Spindles { + namespace VFD { + const int VFD_RS485_BUF_SIZE = 127; + const int RESPONSE_WAIT_MS = 1000; // how long to wait for a response + const TickType_t response_ticks = RESPONSE_WAIT_MS / portTICK_PERIOD_MS; // in milliseconds between commands QueueHandle_t VFDProtocol::vfd_cmd_queue = nullptr; TaskHandle_t VFDProtocol::vfd_cmdTaskHandle = nullptr; void VFDProtocol::reportParsingErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length) { - #ifdef DEBUG_VFD hex_msg(cmd.msg, "RS485 Tx: ", cmd.tx_length); hex_msg(rx_message, "RS485 Rx: ", read_length); - #endif } void VFDProtocol::reportCmdErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length, uint8_t id) { - #ifdef DEBUG_VFD hex_msg(cmd.msg, "RS485 Tx: ", cmd.tx_length); hex_msg(rx_message, "RS485 Rx: ", read_length); @@ -41,9 +35,8 @@ namespace Spindles } else { log_info("RS485 No response"); } - #endif } - + // The communications task void VFDProtocol::vfd_cmd_task(void* pvParameters) { static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive @@ -56,14 +49,15 @@ namespace Spindles uint8_t rx_message[VFD_RS485_MAX_MSG_SIZE]; bool safetyPollingEnabled = impl->safety_polling(); - for (; true; delay_ms(VFD_RS485_POLL_RATE)) { + for (; true; delay_ms(instance->_poll_ms)) { std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // read fence for settings response_parser parser = nullptr; // First check if we should ask the VFD for the speed parameters as part of the initialization. - if (pollidx < 0 && (parser = impl->initialization_sequence(pollidx, next_cmd)) != nullptr) { - } else { - pollidx = 1; // Done with initialization. Main sequence. + if (pollidx < 0) { + if ((parser = impl->initialization_sequence(pollidx, next_cmd, instance)) == nullptr) { + pollidx = 1; // Done with initialization. Main sequence. + } } next_cmd.critical = false; @@ -82,7 +76,6 @@ namespace Spindles next_cmd.critical = action.critical; break; case actionSetMode: - log_debug("vfd_cmd_task mode:" << action.action); if (!impl->prepareSetModeCommand(SpindleState(action.arg), next_cmd, instance)) { continue; // main loop } @@ -143,16 +136,16 @@ namespace Spindles next_cmd.msg[next_cmd.tx_length++] = (crc16 & 0xFF00) >> 8; next_cmd.rx_length += 2; - #ifdef DEBUG_VFD_ALL - if (parser == nullptr) { + if (instance->_debug > 2) { + // if (parser == nullptr) { hex_msg(next_cmd.msg, "RS485 Tx: ", next_cmd.tx_length); + // } } - #endif } // Assume for the worst, and retry... int retry_count = 0; - for (; retry_count < MAX_RETRIES; ++retry_count) { + for (; retry_count < instance->_retries; ++retry_count) { // Flush the UART and write the data: uart.flush(); uart.write(next_cmd.msg, next_cmd.tx_length); @@ -166,6 +159,7 @@ namespace Spindles // Apparently some Huanyang report modbus errors in the correct way, and the rest not. Sigh. // Let's just check for the condition, and truncate the first byte. if (read_length > 0 && instance->_modbus_id != 0 && rx_message[0] == 0) { + log_debug("Huanyang workaround"); memmove(rx_message + 1, rx_message, read_length - 1); } @@ -185,8 +179,10 @@ namespace Spindles // Success unresponsive = false; - retry_count = MAX_RETRIES + 1; // stop retry'ing - + retry_count = instance->_retries + 1; // stop retry'ing + if (instance->_debug > 2) { + hex_msg(rx_message, "RS485 Rx: ", read_length); + } // Should we parse this? if (parser != nullptr) { if (parser(rx_message, instance, impl)) { @@ -196,7 +192,9 @@ namespace Spindles } } else { // Parsing failed - reportParsingErrors(next_cmd, rx_message, read_length); + if (instance->_debug) { + reportParsingErrors(next_cmd, rx_message, read_length); + } // If we were initializing, move back to where we started. unresponsive = true; @@ -205,20 +203,21 @@ namespace Spindles } } } else { - reportCmdErrors(next_cmd, rx_message, read_length, instance->_modbus_id); + if (instance->_debug) { + reportCmdErrors(next_cmd, rx_message, read_length, instance->_modbus_id); + } - // Wait a bit before we retry. Set the delay to poll-rate. Not sure - // if we should use a different value... - delay_ms(VFD_RS485_POLL_RATE); + // Wait a bit before we retry. + delay_ms(instance->_poll_ms); - #ifdef DEBUG_TASK_STACK +#ifdef DEBUG_TASK_STACK static UBaseType_t uxHighWaterMark = 0; reportTaskStackSize(uxHighWaterMark); - #endif +#endif } } - if (retry_count == MAX_RETRIES) { + if (retry_count == instance->_retries) { if (!unresponsive) { log_info("VFD RS485 Unresponsive"); unresponsive = true; @@ -247,15 +246,11 @@ namespace Spindles } bool VFDProtocol::prepareSetSpeedCommand(uint32_t speed, ModbusCommand& data, VFDSpindle* spindle) { - log_debug("prep speed " << speed << " curr " << spindle->_current_dev_speed); if (speed == spindle->_current_dev_speed) { // prevent setting same speed twice return false; } spindle->_current_dev_speed = speed; - #ifdef DEBUG_VFD_ALL - log_debug("Setting spindle speed to:" << int(speed)); - #endif // Do variant-specific command preparation set_speed_command(speed, data); diff --git a/FluidNC/src/Spindles/VFD/VFDProtocol.h b/FluidNC/src/Spindles/VFD/VFDProtocol.h index 70a14c434..ee2af258c 100644 --- a/FluidNC/src/Spindles/VFD/VFDProtocol.h +++ b/FluidNC/src/Spindles/VFD/VFDProtocol.h @@ -5,7 +5,7 @@ namespace Spindles { class VFDSpindle; - + namespace VFD { // VFDProtocol resides in a separate class because it doesn't need to be in IRAM. This contains all the // VFD specific code, which is called from a separate task. @@ -14,7 +14,6 @@ namespace Spindles { using response_parser = bool (*)(const uint8_t* response, VFDSpindle* spindle, VFDProtocol* detail); static const int VFD_RS485_MAX_MSG_SIZE = 16; // more than enough for a modbus message - static const int MAX_RETRIES = 5; // otherwise the spindle is marked 'unresponsive' struct ModbusCommand { bool critical; // TODO SdB: change into `uint8_t critical : 1;`: We want more flags... @@ -23,6 +22,7 @@ namespace Spindles { uint8_t rx_length; uint8_t msg[VFD_RS485_MAX_MSG_SIZE]; }; + virtual void group(Configuration::HandlerBase& handler) {}; protected: // Enable spindown / spinup settings: @@ -34,7 +34,7 @@ namespace Spindles { // Commands that return the status. Returns nullptr if unavailable by this VFD (default): - virtual response_parser initialization_sequence(int index, ModbusCommand& data) { return nullptr; } + virtual response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { return nullptr; } virtual response_parser get_current_speed(ModbusCommand& data) { return nullptr; } virtual response_parser get_current_direction(ModbusCommand& data) { return nullptr; } virtual response_parser get_status_ok(ModbusCommand& data) = 0; @@ -51,11 +51,11 @@ namespace Spindles { uint32_t arg; }; - // Careful observers will notice that these *shouldn't* be static, but they are. The reason is - // hard to track down. In the spindle class, you can find: + // Careful observers will notice that these *shouldn't* be static, but they are. The reason is + // hard to track down. In the spindle class, you can find: // // 'virtual void init() = 0; // not in constructor because this also gets called when $$ settings change' - // + // // With init being called multiple times, static suddenly makes more sense - especially since there is // no de-init. Oh well... @@ -72,10 +72,10 @@ namespace Spindles { public: VFDProtocol() {} - VFDProtocol(const VFDProtocol&) = delete; - VFDProtocol(VFDProtocol&&) = delete; + VFDProtocol(const VFDProtocol&) = delete; + VFDProtocol(VFDProtocol&&) = delete; VFDProtocol& operator=(const VFDProtocol&) = delete; - VFDProtocol& operator=(VFDProtocol&&) = delete; + VFDProtocol& operator=(VFDProtocol&&) = delete; }; } } diff --git a/FluidNC/src/Spindles/VFD/YL620Protocol.cpp b/FluidNC/src/Spindles/VFD/YL620Protocol.cpp index a4643cf5d..2dc50d057 100644 --- a/FluidNC/src/Spindles/VFD/YL620Protocol.cpp +++ b/FluidNC/src/Spindles/VFD/YL620Protocol.cpp @@ -115,7 +115,7 @@ namespace Spindles { data.msg[5] = speed & 0xFF; } - VFDProtocol::response_parser YL620Protocol::initialization_sequence(int index, ModbusCommand& data) { + VFDProtocol::response_parser YL620Protocol::initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) { if (index == -1) { data.tx_length = 6; data.rx_length = 5; diff --git a/FluidNC/src/Spindles/VFD/YL620Protocol.h b/FluidNC/src/Spindles/VFD/YL620Protocol.h index ac4686fd8..88c7591fc 100644 --- a/FluidNC/src/Spindles/VFD/YL620Protocol.h +++ b/FluidNC/src/Spindles/VFD/YL620Protocol.h @@ -15,7 +15,7 @@ namespace Spindles { void direction_command(SpindleState mode, ModbusCommand& data) override; void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser initialization_sequence(int index, ModbusCommand& data, VFDSpindle* vfd) override; response_parser get_current_speed(ModbusCommand& data) override; response_parser get_current_direction(ModbusCommand& data) override; response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } diff --git a/FluidNC/src/Spindles/VFDSpindle.cpp b/FluidNC/src/Spindles/VFDSpindle.cpp index a71240cc1..b503298cc 100644 --- a/FluidNC/src/Spindles/VFDSpindle.cpp +++ b/FluidNC/src/Spindles/VFDSpindle.cpp @@ -22,8 +22,8 @@ #include "VFD/VFDProtocol.h" #include "../Machine/MachineConfig.h" -#include "../Protocol.h" // rtAlarm -#include "../Report.h" // hex message +#include "../Protocol.h" // rtAlarm +#include "../Report.h" // hex message #include "../Configuration/HandlerType.h" #include "../Platform.h" @@ -48,7 +48,7 @@ namespace Spindles { } else { _uart = config->_uarts[_uart_num]; if (!_uart) { - log_error("VFDSpindle: Missing uart" << _uart_num << "section"); + log_error("VFDSpindle: Missing uart" << _uart_num << " section"); return; } } @@ -69,10 +69,10 @@ namespace Spindles { if (!VFD::VFDProtocol::vfd_cmd_queue) { // init can happen many times, we only want to start one task VFD::VFDProtocol::vfd_cmd_queue = xQueueCreate(VFD_RS485_QUEUE_SIZE, sizeof(VFD::VFDProtocol::VFDaction)); xTaskCreatePinnedToCore(VFD::VFDProtocol::vfd_cmd_task, // task - "vfd_cmdTaskHandle", // name for task - 2048, // size of task stack - this, // parameters - 1, // priority + "vfd_cmdTaskHandle", // name for task + 2048, // size of task stack + this, // parameters + 1, // priority &VFD::VFDProtocol::vfd_cmdTaskHandle, SUPPORT_TASK_CORE // core ); @@ -104,7 +104,7 @@ namespace Spindles { } void VFDSpindle::setState(SpindleState state, SpindleSpeed speed) { - log_debug("VFD setState:" << uint8_t(state) << " SpindleSpeed:" << speed); + log_debug(name() << ": setState:" << uint8_t(state) << " SpindleSpeed:" << speed); if (sys.abort) { return; // Block during abort. } @@ -112,7 +112,6 @@ namespace Spindles { bool critical = (state_is(State::Cycle) || state != SpindleState::Disable); uint32_t dev_speed = mapSpeed(speed); - log_debug("RPM:" << speed << " mapped to device units:" << dev_speed); if (_current_state != state) { // Changing state @@ -144,16 +143,16 @@ namespace Spindles { while ((_last_override_value == sys.spindle_speed_ovr) && // skip if the override changes ((_sync_dev_speed < minSpeedAllowed || _sync_dev_speed > maxSpeedAllowed) && unchanged < limit)) { -#ifdef DEBUG_VFD - log_debug("Syncing speed. Requested: " << int(dev_speed) << " current:" << int(_sync_dev_speed)); -#endif + delay_ms(_poll_ms); + if (_debug > 1) { + log_debug("Syncing speed. Requested: " << int(dev_speed) << " current:" << int(_sync_dev_speed)); + } // if (!mc_dwell(500)) { // // Something happened while we were dwelling, like a safety door. // unchanged = limit; // last = _sync_dev_speed; // break; // } - delay_ms(500); // unchanged counts the number of consecutive times that we see the same speed unchanged = (_sync_dev_speed == last) ? unchanged + 1 : 0; @@ -161,13 +160,13 @@ namespace Spindles { } _last_override_value = sys.spindle_speed_ovr; -#ifdef DEBUG_VFD - log_debug("Synced speed. Requested:" << int(dev_speed) << " current:" << int(_sync_dev_speed)); -#endif + if (_debug > 1) { + log_debug("Synced speed. Requested:" << int(dev_speed) << " current:" << int(_sync_dev_speed)); + } if (unchanged == limit) { mc_critical(ExecAlarm::SpindleControl); - log_error(name() << " spindle did not reach device units " << dev_speed << ". Reported value is " << _sync_dev_speed); + log_error(name() << ": spindle did not reach device units " << dev_speed << ". Reported value is " << _sync_dev_speed); } _syncing = false; @@ -225,7 +224,11 @@ namespace Spindles { handler.item("uart_num", _uart_num); } handler.item("modbus_id", _modbus_id, 0, 247); // per https://modbus.org/docs/PI_MBUS_300.pdf + handler.item("debug", _debug, 0, 5); + handler.item("poll_ms", _poll_ms, 250, 20000); + handler.item("retries", _retries); Spindle::group(handler); + detail_->group(handler); } } diff --git a/FluidNC/src/Spindles/VFDSpindle.h b/FluidNC/src/Spindles/VFDSpindle.h index 5ca45cd55..3827c6535 100644 --- a/FluidNC/src/Spindles/VFDSpindle.h +++ b/FluidNC/src/Spindles/VFDSpindle.h @@ -9,13 +9,9 @@ #include "../Uart.h" -// #define DEBUG_VFD -// #define DEBUG_VFD_ALL - namespace Spindles { extern Uart _uart; - - + namespace VFD { // VFDProtocol resides in a separate class because it doesn't need to be in IRAM. This contains all the // VFD specific code, which is called from a separate task. @@ -37,9 +33,12 @@ namespace Spindles { protected: // The constructor sets these - int _uart_num = -1; - Uart* _uart = nullptr; - uint8_t _modbus_id = 1; + int _uart_num = -1; + Uart* _uart = nullptr; + uint8_t _modbus_id = 1; + uint8_t _debug = 0; + uint32_t _poll_ms = 250; + uint32_t _retries = 5; void setSpeed(uint32_t dev_speed); @@ -47,18 +46,19 @@ namespace Spindles { public: VFDSpindle(const char* name, VFD::VFDProtocol* detail) : Spindle(name), detail_(detail) {} - VFDSpindle(const VFDSpindle&) = delete; - VFDSpindle(VFDSpindle&&) = delete; + VFDSpindle(const VFDSpindle&) = delete; + VFDSpindle(VFDSpindle&&) = delete; VFDSpindle& operator=(const VFDSpindle&) = delete; - VFDSpindle& operator=(VFDSpindle&&) = delete; + VFDSpindle& operator=(VFDSpindle&&) = delete; void init(); void config_message(); void setState(SpindleState state, SpindleSpeed speed); void setSpeedfromISR(uint32_t dev_speed) override; - volatile uint32_t _sync_dev_speed; - SpindleSpeed _slop; + // volatile uint32_t _sync_dev_speed; + uint32_t _sync_dev_speed; + SpindleSpeed _slop; // Configuration handlers: void validate() override;