diff --git a/Grbl_Esp32/Machines/4axis_external_driver.h b/Grbl_Esp32/Machines/4axis_external_driver.h
index f88204ad..f093925c 100644
--- a/Grbl_Esp32/Machines/4axis_external_driver.h
+++ b/Grbl_Esp32/Machines/4axis_external_driver.h
@@ -43,9 +43,10 @@
#define SPINDLE_PWM_PIN GPIO_NUM_25
#define SPINDLE_ENABLE_PIN GPIO_NUM_22
-#define MODBUS_TX GPIO_NUM_17
-#define MODBUS_RX GPIO_NUM_4
-#define MODBUS_CTRL GPIO_NUM_16
+#define HUANYANG_TXD_PIN GPIO_NUM_17
+#define HUANYANG_RXD_PIN GPIO_NUM_4
+#define HUANYANG_RTS_PIN GPIO_NUM_16
+
#define X_LIMIT_PIN GPIO_NUM_34
#define Y_LIMIT_PIN GPIO_NUM_35
diff --git a/Grbl_Esp32/gcode.cpp b/Grbl_Esp32/gcode.cpp
index 6c8e8a66..ec3856bf 100644
--- a/Grbl_Esp32/gcode.cpp
+++ b/Grbl_Esp32/gcode.cpp
@@ -304,14 +304,10 @@ uint8_t gc_execute_line(char* line, uint8_t client) {
gc_block.modal.spindle = SPINDLE_ENABLE_CW;
break;
case 4: // Supported if SPINDLE_DIR_PIN is defined or laser mode is on.
-#ifndef SPINDLE_DIR_PIN
- // if laser mode is not on then this is an unsupported command
- if bit_isfalse(settings.flags, BITFLAG_LASER_MODE) {
+ if (my_spindle->is_reversable || bit_istrue(settings.flags, BITFLAG_LASER_MODE))
+ gc_block.modal.spindle = SPINDLE_ENABLE_CCW;
+ else
FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND);
- break;
- }
-#endif
- gc_block.modal.spindle = SPINDLE_ENABLE_CCW;
break;
case 5:
gc_block.modal.spindle = SPINDLE_DISABLE;
diff --git a/Grbl_Esp32/machine.h b/Grbl_Esp32/machine.h
index 1d49672e..d9bcc42f 100644
--- a/Grbl_Esp32/machine.h
+++ b/Grbl_Esp32/machine.h
@@ -8,7 +8,7 @@
// !!! For initial testing, start with test_drive.h which disables
// all I/O pins
-#include "Machines/3axis_v4.h"
+#include "Machines/4axis_external_driver.h"
// !!! For actual use, change the line above to select a board
// from Machines/, for example:
diff --git a/Grbl_Esp32/report.cpp b/Grbl_Esp32/report.cpp
index af9a02d0..5d69eed2 100644
--- a/Grbl_Esp32/report.cpp
+++ b/Grbl_Esp32/report.cpp
@@ -775,4 +775,23 @@ void report_gcode_comment(char* comment) {
msg[index - offset] = 0; // null terminate
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "GCode Comment...%s", msg);
}
+}
+
+
+/*
+ Print a message in hex formate
+ Ex: report_hex_msg(msg, "Rx:", 6);
+ Would could print [MSG Rx: 0x01 0x03 0x01 0x08 0x31 0xbf]
+*/
+void report_hex_msg(char* buf, const char *prefix, int len) {
+ char report[200];
+ char temp[20];
+ sprintf(report, "%s", prefix);
+ for (int i = 0; i < len; i++) {
+ sprintf(temp, " 0x%02X", buf[i]);
+ strcat(report, temp);
+ }
+
+ grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "%s", report);
+
}
\ No newline at end of file
diff --git a/Grbl_Esp32/report.h b/Grbl_Esp32/report.h
index 5b546413..8f0042ea 100644
--- a/Grbl_Esp32/report.h
+++ b/Grbl_Esp32/report.h
@@ -170,6 +170,6 @@ void report_gcode_comment(char* comment);
void report_realtime_debug();
#endif
-
+void report_hex_msg(char* buf, const char *prefix, int len);
#endif
diff --git a/Grbl_Esp32/system.h b/Grbl_Esp32/system.h
index d282e4d4..6cf6758f 100644
--- a/Grbl_Esp32/system.h
+++ b/Grbl_Esp32/system.h
@@ -74,6 +74,7 @@ extern system_t sys;
#define EXEC_ALARM_HOMING_FAIL_DOOR 7
#define EXEC_ALARM_HOMING_FAIL_PULLOFF 8
#define EXEC_ALARM_HOMING_FAIL_APPROACH 9
+#define EXEC_ALARM_SPINDLE_CONTROL 10
// Override bit maps. Realtime bitflags to control feed, rapid, spindle, and coolant overrides.
// Spindle/coolant and feed/rapids are separated into two controlling flag variables.
diff --git a/Grbl_Esp32/tools/DacSpindle.cpp b/Grbl_Esp32/tools/DacSpindle.cpp
index 1410262d..2467080c 100644
--- a/Grbl_Esp32/tools/DacSpindle.cpp
+++ b/Grbl_Esp32/tools/DacSpindle.cpp
@@ -46,8 +46,11 @@ void DacSpindle :: init() {
if (_enable_pin != UNDEFINED_PIN)
pinMode(_enable_pin, OUTPUT);
- if (_direction_pin != UNDEFINED_PIN)
+ if (_direction_pin != UNDEFINED_PIN) {
pinMode(_direction_pin, OUTPUT);
+ }
+ is_reversable = (_direction_pin != UNDEFINED_PIN);
+
config_message();
}
diff --git a/Grbl_Esp32/tools/HuanyangSpindle.cpp b/Grbl_Esp32/tools/HuanyangSpindle.cpp
new file mode 100644
index 00000000..028a9edc
--- /dev/null
+++ b/Grbl_Esp32/tools/HuanyangSpindle.cpp
@@ -0,0 +1,243 @@
+/*
+ HuanyangSpindle.cpp
+
+ This is for a Huanyang VFD based spindle via RS485 Modbus.
+
+ Part of Grbl_ESP32
+ 2020 - Bart Dring
+
+ Grbl is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ Grbl is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+ along with Grbl. If not, see .
+
+ // VFD frequencies are in Hz. Multiple by 60 for RPM
+
+ // before using spindle, VFD must be setup for RS485 and match your spindle
+ PD001 2 RS485 Control of run commands
+ PD002 2 RS485 Control of operating frequency
+ PD005 400 Maximum frequency Hz
+ PD011 120 Min Speed (Recommend Aircooled=120 Water=100)
+ PD014 10 Acceleration time (Test to optimize)
+ PD015 10 Deceleration time (Test to optimize)
+ PD023 1 Reverse run enabled
+ PD142 3.7 Max current Amps (0.8kw=3.7 1.5kw=7.0)
+ PD163 1 RS485 Address: 1
+ PD164 1 RS485 Baud rate: 9600
+ PD165 3 RS485 Mode: RTU, 8N1
+
+ Some references....
+ Manual: http://www.hy-electrical.com/bf/inverter.pdf
+ Reference: https://github.com/Smoothieware/Smoothieware/blob/edge/src/modules/tools/spindle/HuanyangSpindleControl.cpp
+ Refernece: https://gist.github.com/Bouni/803492ed0aab3f944066
+ VFD settings: https://www.hobbytronics.co.za/Content/external/1159/Spindle_Settings.pdf
+
+ TODO
+ Returning errors to Grbl and handling them in Grbl.
+ What happens if the VFD does not respond
+ Add periodic pinging of VFD in run mode to see if it is still at correct RPM
+*/
+
+#include "grbl.h"
+#include "SpindleClass.h"
+#include "driver/uart.h"
+
+#define HUANYANG_ADDR 0x01
+#define HUANYANG_UART_PORT UART_NUM_2 // hard coded for this port right now
+#define ECHO_TEST_CTS UART_PIN_NO_CHANGE // CTS pin is not used
+#define HUANYANG_BAUD_RATE 9600 // PD164 setting
+#define HUANYANG_BUF_SIZE 127
+#define RESPONSE_WAIT_TICKS 80 // how long to wait for a response
+
+void HuanyangSpindle :: init() {
+
+ // fail if numbers are not defined
+ if (!get_pin_numbers()) {
+ grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors");
+ return;
+ }
+
+ uart_driver_delete(HUANYANG_UART_PORT); // this allows us to init() more than once if settings have changed.
+
+ uart_config_t uart_config = {
+ .baud_rate = HUANYANG_BAUD_RATE,
+ .data_bits = UART_DATA_8_BITS,
+ .parity = UART_PARITY_DISABLE,
+ .stop_bits = UART_STOP_BITS_1,
+ .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
+ .rx_flow_ctrl_thresh = 122,
+ };
+
+ uart_param_config(HUANYANG_UART_PORT, &uart_config);
+
+ uart_set_pin(HUANYANG_UART_PORT,
+ _txd_pin,
+ _rxd_pin,
+ _rts_pin,
+ UART_PIN_NO_CHANGE);
+
+ uart_driver_install(HUANYANG_UART_PORT,
+ HUANYANG_BUF_SIZE * 2,
+ 0,
+ 0,
+ NULL,
+ 0);
+
+ uart_set_mode(HUANYANG_UART_PORT, UART_MODE_RS485_HALF_DUPLEX);
+
+ is_reversable = true;
+
+ config_message();
+}
+
+// Checks for all the required pin definitions
+// It returns a message for each missing pin
+// Returns true if all pins are defined.
+bool HuanyangSpindle :: get_pin_numbers() {
+ bool pins_ok = true;
+
+#ifdef HUANYANG_TXD_PIN
+ _txd_pin = HUANYANG_TXD_PIN;
+#else
+ grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Missing HUANYANG_TXD_PIN");
+ pins_ok = false;
+#endif
+
+#ifdef HUANYANG_RXD_PIN
+ _rxd_pin = HUANYANG_RXD_PIN;
+#else
+ grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No HUANYANG_RXD_PIN");
+ pins_ok = false;
+#endif
+
+#ifdef HUANYANG_RTS_PIN
+ _rts_pin = HUANYANG_RTS_PIN;
+#else
+ grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No HUANYANG_RTS_PIN");
+ pins_ok = false;
+#endif
+
+ return pins_ok;
+}
+
+void HuanyangSpindle :: config_message() {
+ grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang Spindle Tx:%d Rx:%d RTS:%d", _txd_pin, _rxd_pin, _rts_pin);
+}
+
+/*
+ ADDR CMD LEN DATA CRC
+ 0x01 0x03 0x01 0x01 0x31 0x88 Start spindle clockwise
+ 0x01 0x03 0x01 0x08 0xF1 0x8E Stop spindle
+ 0x01 0x03 0x01 0x11 0x30 0x44 Start spindle counter-clockwise
+*/
+void HuanyangSpindle :: set_state(uint8_t state, float rpm) {
+ if (sys.abort)
+ return; // Block during abort.
+
+ _state = state; // store locally for faster get_state()
+
+ if (!set_mode(state)) { // try to set state. If it fails there is no need to try to set RPM
+ system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL);
+ return;
+ }
+
+ if (state == SPINDLE_DISABLE) {
+ sys.spindle_speed = 0.0;
+ return;
+ }
+
+ set_rpm(rpm);
+ sys.report_ovr_counter = 0; // Set to report change immediately
+}
+
+bool HuanyangSpindle :: set_mode(uint8_t mode) {
+ char msg[6] = {HUANYANG_ADDR, 0x03, 0x01, 0x00, 0x00, 0x00};
+
+ if (mode == SPINDLE_ENABLE_CW)
+ msg[3] = 0x01;
+ else if (mode == SPINDLE_ENABLE_CCW)
+ msg[3] = 0x11;
+ else //SPINDLE_DISABLE
+ msg[3] = 0x08;
+
+ add_ModRTU_CRC(msg, sizeof(msg));
+
+ //report_hex_msg(msg, "To VFD:", sizeof(msg)); // TODO for debugging comment out
+
+ uart_write_bytes(HUANYANG_UART_PORT, msg, sizeof(msg));
+
+ return get_response(6);
+}
+
+
+bool HuanyangSpindle :: get_response(uint16_t length) {
+ uint8_t rx_message[10];
+
+ uint16_t read_length = uart_read_bytes(HUANYANG_UART_PORT, rx_message, length, RESPONSE_WAIT_TICKS);
+ if (read_length < length) {
+ grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive");
+ return false;
+ }
+ // check CRC?
+ // Check address?
+
+ return true;
+}
+
+/*
+ ADDR CMD LEN DATA CRC
+ 0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ)
+*/
+float HuanyangSpindle :: set_rpm(float rpm) {
+ char msg[7] = {HUANYANG_ADDR, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00};
+
+ // add data (rpm) bytes
+ uint16_t data = uint16_t(rpm / 60.0 * 100.0); // send Hz * 10 (Ex:1500 RPM = 25Hz .... Send 2500)
+ msg[3] = (data & 0xFF00) >> 8;
+ msg[4] = (data & 0xFF);
+
+ add_ModRTU_CRC(msg, sizeof(msg));
+
+ //report_hex_msg(msg, "To VFD:", sizeof(msg)); // TODO for debugging comment out
+
+ uart_write_bytes(HUANYANG_UART_PORT, msg, sizeof(msg));
+ get_response(6);
+
+ return rpm;
+}
+
+void HuanyangSpindle ::stop() {
+ set_mode(SPINDLE_DISABLE);
+}
+
+// state is cached rather than read right now to prevent delays
+uint8_t HuanyangSpindle :: get_state() {
+ return _state;
+}
+
+// Calculate the CRC on all of the byte except the last 2
+// It then added the CRC to those last 2 bytes
+// full_msg_len This is the length of the message including the 2 crc bytes
+// Source: https://ctlsys.com/support/how_to_compute_the_modbus_rtu_message_crc/
+void HuanyangSpindle :: add_ModRTU_CRC(char* buf, int full_msg_len) {
+ uint16_t crc = 0xFFFF;
+ for (int pos = 0; pos < full_msg_len - 2; pos++) {
+ crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc
+ for (int i = 8; i != 0; i--) { // Loop over each bit
+ if ((crc & 0x0001) != 0) { // If the LSB is set
+ crc >>= 1; // Shift right and XOR 0xA001
+ crc ^= 0xA001;
+ } else // Else LSB is not set
+ crc >>= 1; // Just shift right
+ }
+ }
+ // add the calculated Crc to the message
+ buf[full_msg_len - 1] = (crc & 0xFF00) >> 8;
+ buf[full_msg_len - 2] = (crc & 0xFF);
+}
diff --git a/Grbl_Esp32/tools/NullSpindle.cpp b/Grbl_Esp32/tools/NullSpindle.cpp
index 8be38ca6..73b94f23 100644
--- a/Grbl_Esp32/tools/NullSpindle.cpp
+++ b/Grbl_Esp32/tools/NullSpindle.cpp
@@ -25,6 +25,7 @@
// ======================= NullSpindle ==============================
// NullSpindle is just bunch of do nothing (ignore) methods to be used when you don't want a spindle
void NullSpindle :: init() {
+ is_reversable = false;
config_message();
}
float NullSpindle :: set_rpm(float rpm) {
diff --git a/Grbl_Esp32/tools/PWMSpindle.cpp b/Grbl_Esp32/tools/PWMSpindle.cpp
index f4448dbe..0af0e490 100644
--- a/Grbl_Esp32/tools/PWMSpindle.cpp
+++ b/Grbl_Esp32/tools/PWMSpindle.cpp
@@ -67,6 +67,8 @@ void PWMSpindle::init() {
if (_direction_pin != UNDEFINED_PIN)
pinMode(_direction_pin, OUTPUT);
+ is_reversable = (_direction_pin != UNDEFINED_PIN);
+
config_message();
}
@@ -87,7 +89,7 @@ void PWMSpindle :: get_pin_numbers() {
#endif
#ifdef SPINDLE_DIR_PIN
- _direction_pin = SPINDLE_DIR_PIN;
+ _direction_pin = SPINDLE_DIR_PIN;
#else
_direction_pin = UNDEFINED_PIN;
#endif
@@ -148,7 +150,6 @@ float PWMSpindle::set_rpm(float rpm) {
}
void PWMSpindle::set_state(uint8_t state, float rpm) {
-
if (sys.abort)
return; // Block during abort.
diff --git a/Grbl_Esp32/tools/RelaySpindle.cpp b/Grbl_Esp32/tools/RelaySpindle.cpp
index 1b7bfc3f..f517e817 100644
--- a/Grbl_Esp32/tools/RelaySpindle.cpp
+++ b/Grbl_Esp32/tools/RelaySpindle.cpp
@@ -39,6 +39,8 @@ void RelaySpindle::init() {
if (_direction_pin != UNDEFINED_PIN)
pinMode(_direction_pin, OUTPUT);
+ is_reversable = (_direction_pin != UNDEFINED_PIN);
+
config_message();
}
diff --git a/Grbl_Esp32/tools/SpindleClass.cpp b/Grbl_Esp32/tools/SpindleClass.cpp
index 208c4049..21623ba6 100644
--- a/Grbl_Esp32/tools/SpindleClass.cpp
+++ b/Grbl_Esp32/tools/SpindleClass.cpp
@@ -39,12 +39,14 @@
#include "DacSpindle.cpp"
#include "RelaySpindle.cpp"
#include "Laser.cpp"
+#include "HuanyangSpindle.cpp"
NullSpindle null_spindle;
PWMSpindle pwm_spindle;
RelaySpindle relay_spindle;
Laser laser;
DacSpindle dac_spindle;
+HuanyangSpindle huanyang_spindle;
void spindle_select(uint8_t spindle_type) {
@@ -61,6 +63,9 @@ void spindle_select(uint8_t spindle_type) {
case SPINDLE_TYPE_DAC:
my_spindle = &dac_spindle;
break;
+ case SPINDLE_TYPE_HUANYANG:
+ my_spindle = &huanyang_spindle;
+ break;
case SPINDLE_TYPE_NONE:
default:
my_spindle = &null_spindle;
diff --git a/Grbl_Esp32/tools/SpindleClass.h b/Grbl_Esp32/tools/SpindleClass.h
index 2ad3c669..444ff42a 100644
--- a/Grbl_Esp32/tools/SpindleClass.h
+++ b/Grbl_Esp32/tools/SpindleClass.h
@@ -26,17 +26,29 @@
#define SPINDLE_STATE_CW bit(0)
#define SPINDLE_STATE_CCW bit(1)
-#define SPINDLE_TYPE_NONE 0
-#define SPINDLE_TYPE_PWM 1
-#define SPINDLE_TYPE_RELAY 2
-#define SPINDLE_TYPE_LASER 4
-#define SPINDLE_TYPE_DAC 5
+#define SPINDLE_TYPE_NONE 0
+#define SPINDLE_TYPE_PWM 1
+#define SPINDLE_TYPE_RELAY 2
+#define SPINDLE_TYPE_LASER 3
+#define SPINDLE_TYPE_DAC 4
+#define SPINDLE_TYPE_HUANYANG 5
+/*
+typedef enum {
+ SPINDLE_TYPE_NONE = 0,
+ SPINDLE_TYPE_PWM,
+ SPINDLE_TYPE_RELAY,
+ SPINDLE_TYPE_LASER,
+ SPINDLE_TYPE_DAC,
+ SPINDLE_TYPE_HUANGYANG,
+} spindle_type_t;
+*/
#ifndef SPINDLE_CLASS_H
#define SPINDLE_CLASS_H
#include "grbl.h"
#include
+#include "driver/uart.h"
@@ -51,6 +63,8 @@ class Spindle {
virtual void config_message();
virtual bool isRateAdjusted();
virtual void spindle_sync(uint8_t state, float rpm);
+
+ bool is_reversable;
};
// This is a dummy spindle that has no I/O.
@@ -128,15 +142,38 @@ class DacSpindle : public PWMSpindle {
void set_pwm(uint32_t duty); // sets DAC instead of PWM
};
-extern Spindle *my_spindle;
+class HuanyangSpindle : public Spindle {
+ public:
+ void init();
+ void config_message();
+ virtual void set_state(uint8_t state, float rpm);
+ uint8_t get_state();
+ float set_rpm(float rpm);
+ void stop();
+
+ private:
+ bool get_response(uint16_t length);
+ uint16_t ModRTU_CRC(char* buf, int len);
+ void add_ModRTU_CRC(char* buf, int full_msg_len);
+ bool set_mode(uint8_t mode);
+ bool get_pin_numbers();
+
+ uint8_t _txd_pin;
+ uint8_t _rxd_pin;
+ uint8_t _rts_pin;
+ uint8_t _state;
+};
+
+extern Spindle* my_spindle;
extern NullSpindle null_spindle;
extern PWMSpindle pwm_spindle;
extern RelaySpindle relay_spindle;
extern Laser laser;
extern DacSpindle dac_spindle;
+extern HuanyangSpindle huanyang_spindle;
void spindle_select(uint8_t spindle_type);
-void spindle_read_prefs(Preferences &prefs);
+void spindle_read_prefs(Preferences& prefs);
#endif
diff --git a/Grbl_Esp32/wmb_settings.cpp b/Grbl_Esp32/wmb_settings.cpp
deleted file mode 100644
index 095925a0..00000000
--- a/Grbl_Esp32/wmb_settings.cpp
+++ /dev/null
@@ -1,715 +0,0 @@
-#include
-#include