1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-09-02 19:02:35 +02:00

Added a basic HuangyangSpinlde class

- just basic features (CW, CCW and RPM)
- no continuous monitoring during run yet.
This commit is contained in:
bdring
2020-04-18 11:24:04 -05:00
parent 1a8ff1f3a4
commit 2a0cf73d21
14 changed files with 331 additions and 737 deletions

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

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

View File

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

View File

@@ -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();
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
// 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);
}

View File

@@ -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) {

View File

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

View File

@@ -39,6 +39,8 @@ void RelaySpindle::init() {
if (_direction_pin != UNDEFINED_PIN)
pinMode(_direction_pin, OUTPUT);
is_reversable = (_direction_pin != UNDEFINED_PIN);
config_message();
}

View File

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

View File

@@ -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 <driver/dac.h>
#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

View File

@@ -1,715 +0,0 @@
#include <iostream>
#include <map>
#include <string>
#include <string.h>
#include <typeinfo>
#include <iterator>
#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<typename T>
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<float> FloatSetting;
typedef TypedSetting<int> 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<<MAX_N_AXIS)-1);
IntSetting dir_invert_mask("DIR_INVERT_MASK", DEFAULT_DIRECTION_INVERT_MASK, 0, (1<<MAX_N_AXIS)-1);
// XXX need to call st_generate_step_invert_masks()
IntSetting homing_dir_mask("HOMING_DIR_INVERT_MASK", DEFAULT_HOMING_DIR_MASK, 0, (1<<MAX_N_AXIS)-1);
class FlagSetting : public Setting {
private:
bool defaultValue;
bool storedValue;
bool currentValue;
uint32_t bitMask;
err_t (*checker)();
public:
FlagSetting(const char* name, bool defVal, uint32_t mask, err_t (*f)() = NULL) :
Setting(name),
defaultValue(defVal),
bitMask(mask),
checker(f)
{ }
void load() {
//p storedValue = preferences.getBool(displayName, defaultValue);
currentValue = storedValue;
}
void commit() {
if (storedValue != currentValue) {
if (storedValue == defaultValue) {
//p preferences.remove(displayName);
} else {
//p preferences.putBool(displayName, currentValue);
}
}
}
bool get() { return currentValue; }
err_t set(bool value) {
currentValue = value;
if (value) {
settings.flags |= bitMask;
} else {
settings.flags |= ~bitMask;
}
return STATUS_OK;
}
err_t string_to_value(string s, bool &value) {
int n;
try {
n = stoi(s);
}
catch (int e) {
return STATUS_INVALID_VALUE;
}
value = !!n;
return STATUS_OK;
}
err_t setStringValue(string s) {
bool value;
err_t ret = string_to_value(s, value);
if (checker && (ret = checker())) return ret;
return ret ? ret : set(value);
}
string value_to_string (bool value) {
return to_string(value);
}
string getStringValue() { return value_to_string(get()); }
};
FlagSetting step_enable_invert("STEP_ENABLE_INVERT", DEFAULT_INVERT_ST_ENABLE, BITFLAG_INVERT_ST_ENABLE);
FlagSetting limit_invert("LIMIT_INVERT", DEFAULT_INVERT_LIMIT_PINS, BITFLAG_INVERT_LIMIT_PINS);
FlagSetting probe_invert("PROBE_INVERT", DEFAULT_INVERT_PROBE_PIN, BITFLAG_INVERT_PROBE_PIN);
FlagSetting report_inches("REPORT_INCHES", DEFAULT_REPORT_INCHES, BITFLAG_REPORT_INCHES);
err_t check_homing_enable() {
return STATUS_OK;
}
FlagSetting soft_limits("SOFT_LIMITS", DEFAULT_SOFT_LIMIT_ENABLE, BITFLAG_SOFT_LIMIT_ENABLE, check_homing_enable);
// XXX need to check for HOMING_ENABLE
FlagSetting hard_limits("HARD_LIMITS", DEFAULT_HARD_LIMIT_ENABLE, BITFLAG_HARD_LIMIT_ENABLE, limits_init);
// XXX need to call limits_init();
FlagSetting homing_enable("HOMING_ENABLE", DEFAULT_HOMING_ENABLE, BITFLAG_HOMING_ENABLE, also_soft_limit);
// XXX also need to clear, but not set, BITFLAG_SOFT_LIMIT_ENABLE
FlagSetting laser_mode("LASER_MODE", DEFAULT_LASER_MODE, BITFLAG_LASER_MODE, my_spindle_init);
// XXX also need to call my_spindle->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<const char*, Setting*> 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<const char*, Command_t> 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<const char*, Setting*>::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<const char*, Command_t>::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<const char *, Setting*>::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<const char*, Setting*>::iterator it = numberedSettings.begin();
it != numberedSettings.end();
it++) {
cout << it->first << ": " << it->second->getStringValue() << '\n';
}
}
int main()
{
list_settings();
}