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 -#include -#include -#include -#include -#include "./defaults.h" -using namespace std; - -// These status values are just assigned at random, for testing -// they need to be synced with the rest of Grbl -typedef enum { - STATUS_OK = 0, - STATUS_INVALID_STATEMENT, - STATUS_NUMBER_RANGE, - STATUS_INVALID_VALUE, - STATUS_IDLE_ERROR, -} err_t; - -enum { - BITFLAG_INVERT_ST_ENABLE = 1, - BITFLAG_INVERT_LIMIT_PINS = 2, - BITFLAG_INVERT_PROBE_PIN = 4, - BITFLAG_REPORT_INCHES = 8, - BITFLAG_SOFT_LIMIT_ENABLE = 16, - BITFLAG_HARD_LIMIT_ENABLE = 32, - BITFLAG_HOMING_ENABLE = 64, - BITFLAG_LASER_MODE = 128, -}; -#define DEFAULT_SPINDLE_TYPE 0 -#define DEFAULT_SPINDLE_BIT_PRECISION 10 -enum { - STATE_IDLE = 0, - STATE_JOG, -}; -#define MAXLINE 128 -int client; - -struct { - int flags; -} settings; - -struct { - int state; -} sys; - -#define MAX_N_AXIS 6 -#define MESSAGE_RESTORE_DEFAULTS "restoring defaults" -enum { - SETTINGS_RESTORE_DEFAULTS, - SETTINGS_RESTORE_PARAMETERS, - SETTINGS_RESTORE_ALL, - SETTINGS_RESTORE_WIFI_SETTINGS, - SETTINGS_WIPE, -}; - -err_t my_spindle_init(); -err_t limits_init(); -err_t also_soft_limit(); -void settings_restore(int); -void report_feedback_message(const char *); -err_t report_normal_settings(uint8_t); -err_t report_extended_settings(uint8_t); -err_t report_gcode_modes(uint8_t); -err_t report_build_info(uint8_t); -err_t report_startup_lines(uint8_t); -err_t toggle_check_mode(uint8_t); -err_t disable_alarm_lock(uint8_t); -err_t report_ngc(uint8_t); -err_t home_all(uint8_t); -err_t home_x(uint8_t); -err_t home_y(uint8_t); -err_t home_z(uint8_t); -err_t home_a(uint8_t); -err_t home_b(uint8_t); -err_t home_c(uint8_t); -err_t sleep(uint8_t); -err_t gc_execute_line(char *line, uint8_t client); - -void mc_reset(); - -err_t check_motor_settings() { return STATUS_OK; } -err_t settings_spi_driver_init() { return STATUS_OK; } - -// SettingsList is a linked list of all settings, -// so common code can enumerate them. -class Setting; -Setting *SettingsList = NULL; - -// This abstract class defines the generic interface that -// is used to set and get values for all settings independent -// of their underlying data type. The values are always -// represented as human-readable strings. This generic -// interface is used for managing settings via the user interface. -class Setting { - public: - - // Add each constructed setting to the linked list - Setting *link; - Setting(const char* name) { - displayName = name; - link = SettingsList; - SettingsList = this; - } - const char* getName() { return displayName; }; - - // load() reads the backing store to get the current - // value of the setting. This could be slow so it - // should be done infrequently, typically once at startup. - virtual void load() =0; - - // commit() puts the current value of the setting into - // the backing store. - // virtual void commit() =0; - - virtual err_t setStringValue(string value) =0; - virtual string getStringValue() =0; - - private: - const char* displayName; -}; - -// This template class, derived from the generic abstract -// class, lets you store an individual setting as typed data. - -template -class TypedSetting : public Setting { - public: - TypedSetting(const char* name, T defVal, T minVal, T maxVal, err_t (*f)() = NULL) : - Setting(name), - defaultValue(defVal), - currentValue(defVal), - minValue(minVal), - maxValue(maxVal), - checker(f) - { } - void load() { - if (isfloat) { - //p storedValue = preferences.getFloat(displayName, defaultValue); - } else { - //p storedValue = preferences.getInt(displayName, defaultValue); - } // Read value from backing store - currentValue = storedValue; - } - void commit() { - if (storedValue != currentValue) { - if (storedValue == defaultValue) { - //p preferences.remove(displayName); - } else { - if (isfloat) { - //p preferences.putFloat(displayName, currentValue); - } else { - //p preferences.putInt(displayName, currentValue); - } - } - } - } - - T get() { return currentValue; } - err_t set(T value) { currentValue = value; return STATUS_OK; } - err_t string_to_value(string s, T &value) { - T convertedValue; - try { - convertedValue = stof(s); - } - catch (int e) { - return STATUS_INVALID_VALUE; - } - if (convertedValue < minValue || convertedValue > maxValue) { - return STATUS_NUMBER_RANGE; - } - value = convertedValue; - return STATUS_OK; - } - string value_to_string (T value) { - return to_string(value); - } - - // Standard methods of the abstract class "Setting" - err_t setStringValue(string s) { - T newValue; - err_t ret = string_to_value(s, newValue); - if (ret) return ret; - if (checker && (ret = checker())) return ret; - currentValue = newValue; - return ret; - } - string getStringValue () { - return to_string(get()); - } - - private: - T defaultValue; - T currentValue; - T storedValue; - T minValue; - T maxValue; - const bool isfloat = typeid(T) == typeid(float); - err_t (*checker)(); -}; - -typedef TypedSetting FloatSetting; -typedef TypedSetting IntSetting; - -class AxisSettings { - public: - string name; - FloatSetting steps_per_mm; - FloatSetting max_rate; - FloatSetting acceleration; - FloatSetting max_travel; - FloatSetting run_current; - FloatSetting hold_current; - IntSetting microsteps; - IntSetting stallguard; - AxisSettings(string axisName, float steps, float rate, float accel, float travel, - float run, float hold, int usteps, int stall) : - name(axisName), - steps_per_mm((axisName+"_STEPS_PER_MM").c_str(), steps, 1.0, 50000.0, check_motor_settings), - max_rate((axisName+"_MAX_RATE").c_str(), rate, 1.0, 1000000.0, check_motor_settings), - acceleration((axisName+"_ACCELERATION").c_str(), accel, 1.0, 100000.0), - max_travel((axisName+"_MAX_TRAVEL").c_str(), travel, 1.0, 100000.0), // Note! this values is entered as scaler but store negative - run_current((axisName+"_RUN_CURRENT").c_str(), run, 0.05, 20.0, settings_spi_driver_init), - hold_current((axisName+"_HOLD_CURRENT").c_str(), hold, 0.0, 100.0, settings_spi_driver_init), - microsteps((axisName+"_MICROSTEPS").c_str(), usteps, 1, 256, settings_spi_driver_init), - stallguard((axisName+"_STALLGUARD").c_str(), stall, 0, 100, settings_spi_driver_init) - {} -}; -AxisSettings x_axis_settings = { - "X", - DEFAULT_X_STEPS_PER_MM, - DEFAULT_X_MAX_RATE, - DEFAULT_X_ACCELERATION, - DEFAULT_X_MAX_TRAVEL, - DEFAULT_X_CURRENT, - DEFAULT_X_HOLD_CURRENT, - DEFAULT_X_MICROSTEPS, - DEFAULT_X_STALLGUARD -}; - -AxisSettings y_axis_settings = { - "Y", - DEFAULT_Y_STEPS_PER_MM, - DEFAULT_Y_MAX_RATE, - DEFAULT_Y_ACCELERATION, - DEFAULT_Y_MAX_TRAVEL, - DEFAULT_Y_CURRENT, - DEFAULT_Y_HOLD_CURRENT, - DEFAULT_Y_MICROSTEPS, - DEFAULT_Y_STALLGUARD -}; - -AxisSettings z_axis_settings = { - "Z", - DEFAULT_Z_STEPS_PER_MM, - DEFAULT_Z_MAX_RATE, - DEFAULT_Z_ACCELERATION, - DEFAULT_Z_MAX_TRAVEL, - DEFAULT_Z_CURRENT, - DEFAULT_Z_HOLD_CURRENT, - DEFAULT_Z_MICROSTEPS, - DEFAULT_Z_STALLGUARD -}; - -AxisSettings a_axis_settings = { - "A", - DEFAULT_A_STEPS_PER_MM, - DEFAULT_A_MAX_RATE, - DEFAULT_A_ACCELERATION, - DEFAULT_A_MAX_TRAVEL, - DEFAULT_A_CURRENT, - DEFAULT_A_HOLD_CURRENT, - DEFAULT_A_MICROSTEPS, - DEFAULT_A_STALLGUARD -}; - -AxisSettings b_axis_settings = { - "B", - DEFAULT_B_STEPS_PER_MM, - DEFAULT_B_MAX_RATE, - DEFAULT_B_ACCELERATION, - DEFAULT_B_MAX_TRAVEL, - DEFAULT_B_CURRENT, - DEFAULT_B_HOLD_CURRENT, - DEFAULT_B_MICROSTEPS, - DEFAULT_B_STALLGUARD -}; - -AxisSettings c_axis_settings = { - "C", - DEFAULT_C_STEPS_PER_MM, - DEFAULT_C_MAX_RATE, - DEFAULT_C_ACCELERATION, - DEFAULT_C_MAX_TRAVEL, - DEFAULT_C_CURRENT, - DEFAULT_C_HOLD_CURRENT, - DEFAULT_C_MICROSTEPS, - DEFAULT_C_STALLGUARD -}; - -AxisSettings axis_settings[] = { - x_axis_settings, - y_axis_settings, - z_axis_settings, - a_axis_settings, - b_axis_settings, - c_axis_settings, -}; - -class StringSetting : public Setting { - private: - string currentValue; - string storedValue; - string defaultValue; - - public: - StringSetting(const char* name, string defVal) : - Setting(name), - defaultValue(defVal), - currentValue(defVal) - { }; - void load() { - //p storedValue = preferences.getString(displayName, defaultValue); - currentValue = storedValue; - } - void commit() { - if (storedValue != currentValue) { - if (storedValue == defaultValue) { - //p preferences.remove(displayName); - } else { - //p preferences.putString(displayName, currentValue); - } - } - } - string get() { return currentValue; } - err_t set(string value) { currentValue = value; return STATUS_OK; } - err_t string_to_value(string s, string &value) { - value = s; - return STATUS_OK; - } - string value_to_string (string value) { return value; } - err_t setStringValue(string s) { - currentValue = s; - return STATUS_OK; - } - string getStringValue() { return currentValue; } -}; -StringSetting startup_line_0("N0", ""); -StringSetting startup_line_1("N1", ""); -StringSetting build_info("I", ""); - -// Single-use class for the $RST= command -// There is no underlying stored value -class SettingsReset : public Setting { - public: - SettingsReset(const char* name) : - Setting(name) - { } - void load() { - } - void commit() { - } - string get() { return 0; } - err_t set(int value) { - settings_restore(value); - report_feedback_message(MESSAGE_RESTORE_DEFAULTS); - mc_reset(); // Ensure settings are initialized correctly - return STATUS_OK; - } - err_t string_to_value(string s, int &value) { - switch (s[0]) { - case '$': value = SETTINGS_RESTORE_DEFAULTS; break; - case '#': value = SETTINGS_RESTORE_PARAMETERS; break; - case '*': value = SETTINGS_RESTORE_ALL; break; - case '@': value = SETTINGS_RESTORE_WIFI_SETTINGS; break; - case '!': value = SETTINGS_WIPE; break; - default: return STATUS_INVALID_VALUE; - } - return STATUS_OK; - } - err_t setStringValue(string s) { - int value; - err_t ret = string_to_value(s, value); - return ret ? ret : set(value); - } - string value_to_string (int value) { - switch(value) { - case SETTINGS_RESTORE_DEFAULTS: return "$"; break; - case SETTINGS_RESTORE_PARAMETERS: return "#"; break; - case SETTINGS_RESTORE_ALL: return "*"; break; - case SETTINGS_RESTORE_WIFI_SETTINGS: return "@"; break; - case SETTINGS_WIPE: return "!"; break; - default: return ""; - } - } - string getStringValue() { return ""; } -}; - -IntSetting pulse_microseconds("STEP_PULSE", DEFAULT_STEP_PULSE_MICROSECONDS, 3, 1000); -IntSetting stepper_idle_lock_time("STEPPER_IDLE_TIME", DEFAULT_STEPPER_IDLE_LOCK_TIME, 0, 255); - -IntSetting step_invert_mask("STEP_INVERT_MASK", DEFAULT_STEPPING_INVERT_MASK, 0, (1<init(); - -IntSetting status_mask("STATUS_MASK", DEFAULT_STATUS_REPORT_MASK, 0, 2); -FloatSetting junction_deviation("JUNCTION_DEVIATION", DEFAULT_JUNCTION_DEVIATION, 0, 10); -FloatSetting arc_tolerance("ARC_TOLERANCE", DEFAULT_ARC_TOLERANCE, 0, 1); - -FloatSetting homing_feed_rate("HOMING_FEED", DEFAULT_HOMING_FEED_RATE, 0, 10000); -FloatSetting homing_seek_rate("HOMING_SEEK", DEFAULT_HOMING_SEEK_RATE, 0, 10000); -FloatSetting homing_debounce("HOMING_DEBOUNCE", DEFAULT_HOMING_DEBOUNCE_DELAY, 0, 10000); -FloatSetting homing_pulloff("HOMING_PULLOFF", DEFAULT_HOMING_PULLOFF, 0, 1000); -FloatSetting spindle_pwm_freq("SPINDLE_PWM_FREQ", DEFAULT_SPINDLE_FREQ, 0, 100000); -FloatSetting rpm_max("RPM_MAX", DEFAULT_SPINDLE_RPM_MAX, 0, 100000); -FloatSetting rpm_min("RPM_MIN", DEFAULT_SPINDLE_RPM_MIN, 0, 100000); - -FloatSetting spindle_pwm_off_value("SPINDLE_PWM_OFF_VALUE", DEFAULT_SPINDLE_OFF_VALUE, 0.0, 100.0); // these are percentages -FloatSetting spindle_pwm_min_value("SPINDLE_PWM_MIN_VALUE", DEFAULT_SPINDLE_MIN_VALUE, 0.0, 100.0); -FloatSetting spindle_pwm_max_value("SPINDLE_PWM_MAX_VALUE", DEFAULT_SPINDLE_MAX_VALUE, 0.0, 100.0); -IntSetting spindle_pwm_bit_precision("SPINDLE_PWM_BIT_PRECISION", DEFAULT_SPINDLE_BIT_PRECISION, 1, 16); - -StringSetting spindle_type("SPINDLE_TYPE", DEFAULT_SPINDLE_TYPE); - -// The following table maps numbered settings to their settings -// objects, for backwards compatibility. It is used if the -// line is of the form "$key=value". If a key match is found, -// the object's setValueString method is invoked with the value -// as argument. If no match is found, the list of named settings -// is searched, with the same behavior on a match. - -// These are compatibily aliases for Classic GRBL -std::map numberedSettings = { - { "0", &pulse_microseconds }, - { "1", &stepper_idle_lock_time }, - { "2", &step_invert_mask }, - { "3", &dir_invert_mask }, - { "4", &step_enable_invert }, - { "5", &limit_invert }, - { "6", &probe_invert }, - { "10", &status_mask }, - { "11", &junction_deviation }, - { "12", &arc_tolerance }, - { "13", &report_inches }, - { "20", &soft_limits }, - { "21", &hard_limits }, - { "22", &homing_enable }, - { "23", &homing_dir_mask }, - { "24", &homing_feed_rate }, - { "25", &homing_seek_rate }, - { "26", &homing_debounce }, - { "27", &homing_pulloff }, - { "30", &rpm_max }, - { "31", &rpm_min }, - { "32", &laser_mode }, - { "100", &x_axis_settings.steps_per_mm }, - { "101", &y_axis_settings.steps_per_mm }, - { "102", &z_axis_settings.steps_per_mm }, - { "110", &x_axis_settings.max_rate }, - { "111", &y_axis_settings.max_rate }, - { "112", &z_axis_settings.max_rate }, - { "120", &x_axis_settings.acceleration }, - { "121", &y_axis_settings.acceleration }, - { "122", &z_axis_settings.acceleration }, - { "130", &x_axis_settings.max_travel }, - { "131", &y_axis_settings.max_travel }, - { "132", &z_axis_settings.max_travel }, -}; - -// FIXME - jog may need to be special-cased in the parser, since -// it is not really a setting and the entire line needs to be -// sent to gc_execute_line. It is probably also more time-critical -// than actual settings, which change infrequently, so handling -// it early is probably prudent. -uint8_t jog_set(uint8_t *value, uint8_t client) { - // Execute only if in IDLE or JOG states. - if (sys.state != STATE_IDLE && sys.state != STATE_JOG) return STATUS_IDLE_ERROR; - - // restore the $J= prefix because gc_execute_line() expects it - char line[MAXLINE]; - strcpy(line, "$J="); - strncat(line, (char *)value, MAXLINE-strlen("$J=")-1); - - return gc_execute_line(line, client); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions. -} - -// The following table is used if the line is of the form "$key\n" -// i.e. dollar commands without "=" -// The key value is matched against the string and the corresponding -// function is called with no arguments. -// If there is no key match an error is reported -typedef err_t (*Command_t)(uint8_t); -std::map dollarCommands = { - { "$", report_normal_settings }, - { "+", report_extended_settings }, - { "G", report_gcode_modes }, - { "C", toggle_check_mode }, - { "X", disable_alarm_lock }, - { "#", report_ngc }, - { "H", home_all }, - { "HX", home_x }, - { "HY", home_y }, - { "HZ", home_z }, - { "HA", home_a }, - { "HB", home_b }, - { "HC", home_c }, - { "SLP", sleep }, - { "I", report_build_info }, - { "N", report_startup_lines }, -}; - -// normalize_key puts a key string into canonical form - -// upper case without whitespace. -// start points to a null-terminated string. -// Returns the first substring that does not contain whitespace, -// converted to upper case. -char *normalize_key(char *start) { - char c; - - // In the usual case, this loop will exit on the very first test, - // because the first character is likely to be non-white. - // Null ('\0') is not considered to be a space character. - while (isspace(c = *start) && c != '\0') { - ++start; - } - - // start now points to either a printable character or end of string - if (c == '\0') { - return start; - } - - // Having found the beginning of the printable string, - // we now scan forward, converting lower to upper case, - // until we find a space character. - char *end; - for (end = start; (c = *end) != '\0' && !isspace(c); end++) { - if (islower(c)) { - *end = toupper(c); - } - } - - // end now points to either a whitespace character of end of string - // In either case it is okay to place a null there - *end = '\0'; - - return start; -} - -// This is for changing settings with $key=value . -// Lookup key in the numberedSettings map. If found, execute -// the corresponding object's "set" method. Otherwise fail. -err_t do_setting(const char *key, char *value, uint8_t client) { - - // First search this numberedSettings array - aliases - // for the underlying named settings. - map::iterator it = numberedSettings.find(key); - if (it != numberedSettings.end()) { - return it->second->setStringValue(value); - } - - // Then search the list of named settings. - for (Setting *s = SettingsList; s; s = s->link) { - if (strcmp(s->getName(), key) == 0) { - return s->setStringValue(value); - } - } - - return STATUS_INVALID_STATEMENT; -} - -// This is for bare commands like "$RST" - no equals sign. -// Lookup key in the dollarCommands map. If found, execute -// the corresponding command. -// As an enhancement to Classic GRBL, if the key is not found -// in the commands map, look it up in the lists of settings -// and display the current value. -err_t do_command(const char *key, uint8_t client) { - - map::iterator i = dollarCommands.find(key); - if (i != dollarCommands.end()) { - return i->second(client); - } - - // Enhancement - not in Classic GRBL: - // If it is not a command, look up the key - // as a setting and display the value. - - map::iterator it = numberedSettings.find(key); - if (it != numberedSettings.end()) { - Setting *s = it->second; - cout << key << "=" << s->getStringValue() << '\n'; - return STATUS_OK; - } - - for (Setting *s = SettingsList; s; s = s->link) { - // if (s->getName().compare(k)) { - if (strcmp(s->getName(), key) == 0) { - cout << key << "=" << s->getStringValue() << '\n'; - return STATUS_OK; - } - } - - return STATUS_INVALID_STATEMENT; -} - -uint8_t system_execute_line(char* line, uint8_t client) { - char *value = strchr(line, '='); - - if (value) { - // Equals was found; replace it with null and skip it - *value++ = '\0'; - do_setting(normalize_key(line), value, client); - } else { - // No equals, so it must be a command - do_command(normalize_key(line), client); - } -} - -void list_settings() -{ - for (Setting *s = SettingsList; s; s = s->link) { - cout << s->getName() << " " << s->getStringValue() << '\n'; - } -} - -void list_numbered_settings() -{ - for (map::iterator it = numberedSettings.begin(); - it != numberedSettings.end(); - it++) { - cout << it->first << ": " << it->second->getStringValue() << '\n'; - } -} - -int main() -{ - list_settings(); -}