1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-08-24 15:13:02 +02:00

Updated style of Spindles according to style doc. Added namespace Spindles. (#529)

* Renamed spindleclass file.

* Added (empty) header files; renamed SpindleClass.cpp

* Moved spindle classes, introduced namespaces

* Fixed forward declaration.

* Removed obsolete commented out code

* Fixed overrides and constructors

* Applied clang-format

* Fixed 10vspindle set_spindle_dir_pin and set_enable_pin.

Co-authored-by: Stefan de Bruijn <stefan@nubilosoft.com>
This commit is contained in:
Stefan de Bruijn
2020-08-09 22:22:14 +02:00
committed by GitHub
parent 3e15fb3409
commit 898edc7f95
22 changed files with 1352 additions and 1044 deletions

View File

@@ -34,7 +34,7 @@ volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bi
volatile uint8_t sys_rt_exec_debug;
#endif
Spindle* spindle;
Spindles::Spindle* spindle;
void setup() {
#ifdef USE_I2S_OUT
@@ -87,7 +87,7 @@ void setup() {
if (homing_enable->get())
sys.state = STATE_ALARM;
#endif
spindle_select();
Spindles::Spindle::spindle_select();
#ifdef ENABLE_WIFI
wifi_config.begin();
#endif

View File

@@ -23,143 +23,142 @@
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/>.
*/
#include "SpindleClass.h"
#include "10vSpindle.h"
void _10vSpindle ::init() {
get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC
namespace Spindles {
void _10vSpindle::init() {
get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC
// a couple more pins not inherited from PWM Spindle
// a couple more pins not inherited from PWM Spindle
#ifdef SPINDLE_FORWARD_PIN
_forward_pin = SPINDLE_FORWARD_PIN;
_forward_pin = SPINDLE_FORWARD_PIN;
#else
_forward_pin = UNDEFINED_PIN;
_forward_pin = UNDEFINED_PIN;
#endif
#ifdef SPINDLE_REVERSE_PIN
_reverse_pin = SPINDLE_REVERSE_PIN;
_reverse_pin = SPINDLE_REVERSE_PIN;
#else
_reverse_pin = UNDEFINED_PIN;
_reverse_pin = UNDEFINED_PIN;
#endif
if (_output_pin == UNDEFINED_PIN) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined");
return; // We cannot continue without the output pin
if (_output_pin == UNDEFINED_PIN) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined");
return; // We cannot continue without the output pin
}
ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel
ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
pinMode(_forward_pin, OUTPUT);
pinMode(_reverse_pin, OUTPUT);
set_rpm(0);
config_message();
is_reversable = true; // these VFDs are always reversable
use_delays = true;
}
ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel
ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin
// prints the startup message of the spindle config
void _10vSpindle::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"0-10V spindle Out:%s Enbl:%s, Dir:%s, Fwd:%s, Rev:%s, Freq:%dHz Res:%dbits",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str(),
pinName(_forward_pin).c_str(),
pinName(_reverse_pin).c_str(),
_pwm_freq,
_pwm_precision);
}
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
pinMode(_forward_pin, OUTPUT);
pinMode(_reverse_pin, OUTPUT);
uint32_t _10vSpindle::set_rpm(uint32_t rpm) {
uint32_t pwm_value;
set_rpm(0);
if (_output_pin == UNDEFINED_PIN)
return rpm;
config_message();
// apply speed overrides
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent)
is_reversable = true; // these VFDs are always reversable
use_delays = true;
}
// apply limits limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
// prints the startup message of the spindle config
void _10vSpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"0-10V spindle Out:%s Enbl:%s, Dir:%s, Fwd:%s, Rev:%s, Freq:%dHz Res:%dbits",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str(),
pinName(_forward_pin).c_str(),
pinName(_reverse_pin).c_str(),
_pwm_freq,
_pwm_precision);
}
// determine the pwm value
if (rpm == 0)
pwm_value = _pwm_off_value;
else
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
uint32_t _10vSpindle::set_rpm(uint32_t rpm) {
uint32_t pwm_value;
if (_output_pin == UNDEFINED_PIN)
set_output(pwm_value);
return rpm;
}
/*
void _10vSpindle::set_state(uint8_t state, uint32_t rpm) {
if (sys.abort)
return; // Block during abort.
// apply speed overrides
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent)
if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm.
sys.spindle_speed = 0;
stop();
} else {
set_spindle_dir_pin(state == SPINDLE_ENABLE_CW);
set_rpm(rpm);
}
// apply limits limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
set_enable_pin(state != SPINDLE_DISABLE);
// determine the pwm value
if (rpm == 0)
pwm_value = _pwm_off_value;
else
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
sys.report_ovr_counter = 0; // Set to report change immediately
}
set_output(pwm_value);
return rpm;
}
/*
void _10vSpindle::set_state(uint8_t state, uint32_t rpm) {
if (sys.abort)
return; // Block during abort.
*/
if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm.
sys.spindle_speed = 0;
stop();
} else {
set_spindle_dir_pin(state == SPINDLE_ENABLE_CW);
set_rpm(rpm);
uint8_t _10vSpindle::get_state() {
if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN)
return (SPINDLE_STATE_DISABLE);
if (_direction_pin != UNDEFINED_PIN)
return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW;
return (SPINDLE_STATE_CW);
}
set_enable_pin(state != SPINDLE_DISABLE);
void _10vSpindle::stop() {
// inverts are delt with in methods
set_enable_pin(false);
set_output(_pwm_off_value);
}
sys.report_ovr_counter = 0; // Set to report change immediately
}
*/
uint8_t _10vSpindle::get_state() {
if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN)
return (SPINDLE_STATE_DISABLE);
if (_direction_pin != UNDEFINED_PIN)
return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW;
return (SPINDLE_STATE_CW);
}
void _10vSpindle::stop() {
// inverts are delt with in methods
set_enable_pin(false);
set_output(_pwm_off_value);
}
void _10vSpindle::set_enable_pin(bool enable) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "_10vSpindle::set_enable_pin");
if (_off_with_zero_speed && sys.spindle_speed == 0)
enable = false;
void _10vSpindle::set_enable_pin(bool enable) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "_10vSpindle::set_enable_pin");
if (_off_with_zero_speed && sys.spindle_speed == 0)
enable = false;
#ifdef INVERT_SPINDLE_ENABLE_PIN
enable = !enable;
enable = !enable;
#endif
digitalWrite(_enable_pin, enable);
digitalWrite(_enable_pin, enable);
// turn off anything that acts like an enable
if (!enable) {
digitalWrite(_direction_pin, enable);
digitalWrite(_forward_pin, enable);
digitalWrite(_reverse_pin, enable);
// turn off anything that acts like an enable
if (!enable) {
digitalWrite(_direction_pin, enable);
digitalWrite(_forward_pin, enable);
digitalWrite(_reverse_pin, enable);
}
}
void _10vSpindle::set_spindle_dir_pin(bool Clockwise) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "_10vSpindle::set_spindle_dir_pin");
digitalWrite(_direction_pin, Clockwise);
digitalWrite(_forward_pin, Clockwise);
digitalWrite(_reverse_pin, !Clockwise);
}
}
void _10vSpindle::set_spindle_dir_pin(bool Clockwise) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "_10vSpindle::set_spindle_dir_pin");
digitalWrite(_direction_pin, Clockwise);
digitalWrite(_forward_pin, Clockwise);
digitalWrite(_reverse_pin, !Clockwise);
}

View File

@@ -0,0 +1,58 @@
#pragma once
/*
10vSpindle.h
This is basically a PWM spindle with some changes, so a separate forward and
reverse signal can be sent.
The direction pins will act as enables for the 2 directions. There is usually
a min RPM with VFDs, that speed will remain even if speed is 0. You
must turn off both direction pins when enable is off.
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/>.
*/
#include "PWMSpindle.h"
namespace Spindles {
class _10vSpindle : public PWMSpindle {
public:
_10vSpindle() = default;
_10vSpindle(const _10vSpindle&) = delete;
_10vSpindle(_10vSpindle&&) = delete;
_10vSpindle& operator=(const _10vSpindle&) = delete;
_10vSpindle& operator=(_10vSpindle&&) = delete;
void init() override;
void config_message() override;
uint32_t set_rpm(uint32_t rpm) override;
//void set_state(uint8_t state, uint32_t rpm);
uint8_t get_state() override;
void stop() override;
virtual ~_10vSpindle() {}
uint8_t _forward_pin;
uint8_t _reverse_pin;
protected:
void set_enable_pin(bool enable_pin) override;
void set_spindle_dir_pin(bool Clockwise) override;
};
}

View File

@@ -30,7 +30,7 @@
BESC_MAX_PULSE_SECS is typically 2ms (0.002 sec) or more
*/
#include "SpindleClass.h"
#include "BESCSpindle.h"
// don't change these
#define BESC_PWM_FREQ 50.0f // Hz
@@ -51,70 +51,72 @@
#define BESC_MIN_PULSE_CNT (uint16_t)(BESC_MIN_PULSE_SECS / BESC_PULSE_PERIOD * 65535.0)
#define BESC_MAX_PULSE_CNT (uint16_t)(BESC_MAX_PULSE_SECS / BESC_PULSE_PERIOD * 65535.0)
void BESCSpindle ::init() {
get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC
namespace Spindles {
void BESCSpindle::init() {
get_pins_and_settings(); // these gets the standard PWM settings, but many need to be changed for BESC
if (_output_pin == UNDEFINED_PIN) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined");
return; // We cannot continue without the output pin
if (_output_pin == UNDEFINED_PIN) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: BESC output pin not defined");
return; // We cannot continue without the output pin
}
// override some settings to what is required for a BESC
_pwm_freq = (uint32_t)BESC_PWM_FREQ;
_pwm_precision = 16;
// override these settings
_pwm_off_value = BESC_MIN_PULSE_CNT;
_pwm_min_value = _pwm_off_value;
_pwm_max_value = BESC_MAX_PULSE_CNT;
ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel
ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin
pinMode(_enable_pin, OUTPUT);
set_rpm(0);
use_delays = true;
config_message();
}
// override some settings to what is required for a BESC
_pwm_freq = (uint32_t)BESC_PWM_FREQ;
_pwm_precision = 16;
// prints the startup message of the spindle config
void BESCSpindle::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"BESC spindle on Pin:%s Min:%0.2fms Max:%0.2fms Freq:%dHz Res:%dbits",
pinName(_output_pin).c_str(),
BESC_MIN_PULSE_SECS * 1000.0, // convert to milliseconds
BESC_MAX_PULSE_SECS * 1000.0, // convert to milliseconds
_pwm_freq,
_pwm_precision);
}
// override these settings
_pwm_off_value = BESC_MIN_PULSE_CNT;
_pwm_min_value = _pwm_off_value;
_pwm_max_value = BESC_MAX_PULSE_CNT;
uint32_t BESCSpindle::set_rpm(uint32_t rpm) {
uint32_t pwm_value;
ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel
ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin
if (_output_pin == UNDEFINED_PIN)
return rpm;
pinMode(_enable_pin, OUTPUT);
// apply speed overrides
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent)
set_rpm(0);
// apply limits limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
use_delays = true;
// determine the pwm value
if (rpm == 0) {
pwm_value = _pwm_off_value;
} else {
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
}
config_message();
}
// prints the startup message of the spindle config
void BESCSpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"BESC spindle on Pin:%s Min:%0.2fms Max:%0.2fms Freq:%dHz Res:%dbits",
pinName(_output_pin).c_str(),
BESC_MIN_PULSE_SECS * 1000.0, // convert to milliseconds
BESC_MAX_PULSE_SECS * 1000.0, // convert to milliseconds
_pwm_freq,
_pwm_precision);
}
uint32_t BESCSpindle::set_rpm(uint32_t rpm) {
uint32_t pwm_value;
if (_output_pin == UNDEFINED_PIN)
set_output(pwm_value);
return rpm;
// apply speed overrides
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent)
// apply limits limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
// determine the pwm value
if (rpm == 0) {
pwm_value = _pwm_off_value;
} else {
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
}
set_output(pwm_value);
return rpm;
}

View File

@@ -0,0 +1,54 @@
#pragma once
/*
BESCSpindle.cpp
This a special type of PWM spindle for RC type Brushless DC Speed
controllers. They use a short pulse for off and a longer pulse for
full on. The pulse is always a small portion of the full cycle.
Some BESCs have a special turn on procedure. This may be a one time
procedure or must be done every time. The user must do that via gcode.
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/>.
Important ESC Settings
50 Hz this is a typical frequency for an ESC
Some ESCs can handle higher frequencies, but there is no advantage to changing it.
Determine the typical min and max pulse length of your ESC
BESC_MIN_PULSE_SECS is typically 1ms (0.001 sec) or less
BESC_MAX_PULSE_SECS is typically 2ms (0.002 sec) or more
*/
#include "PWMSpindle.h"
namespace Spindles {
class BESCSpindle : public PWMSpindle {
public:
BESCSpindle() = default;
BESCSpindle(const BESCSpindle&) = delete;
BESCSpindle(BESCSpindle&&) = delete;
BESCSpindle& operator=(const BESCSpindle&) = delete;
BESCSpindle& operator=(BESCSpindle&&) = delete;
void init() override;
void config_message() override;
uint32_t set_rpm(uint32_t rpm) override;
virtual ~BESCSpindle() {}
};
}

View File

@@ -21,84 +21,86 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SpindleClass.h"
#include "DacSpindle.h"
// ======================================== DacSpindle ======================================
void DacSpindle ::init() {
get_pins_and_settings();
namespace Spindles {
// ======================================== DacSpindle ======================================
void DacSpindle::init() {
get_pins_and_settings();
if (_output_pin == UNDEFINED_PIN)
return;
if (_output_pin == UNDEFINED_PIN)
return;
_min_rpm = rpm_min->get();
_max_rpm = rpm_max->get();
_pwm_min_value = 0; // not actually PWM...DAC counts
_pwm_max_value = 255; // not actually PWM...DAC counts
_gpio_ok = true;
_min_rpm = rpm_min->get();
_max_rpm = rpm_max->get();
_pwm_min_value = 0; // not actually PWM...DAC counts
_pwm_max_value = 255; // not actually PWM...DAC counts
_gpio_ok = true;
if (_output_pin != GPIO_NUM_25 && _output_pin != GPIO_NUM_26) { // DAC can only be used on these pins
_gpio_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "DAC spindle pin invalid GPIO_NUM_%d (pin 25 or 26 only)", _output_pin);
return;
}
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
is_reversable = (_direction_pin != UNDEFINED_PIN);
use_delays = true;
config_message();
}
void DacSpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"DAC spindle Output:%s, Enbl:%s, Dir:%s, Res:8bits",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str());
}
uint32_t DacSpindle::set_rpm(uint32_t rpm) {
if (_output_pin == UNDEFINED_PIN)
return rpm;
uint32_t pwm_value;
// apply overrides and limits
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent)
// Calculate PWM register value based on rpm max/min settings and programmed rpm.
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) {
// No PWM range possible. Set simple on/off spindle control pin state.
sys.spindle_speed = _max_rpm;
pwm_value = _pwm_max_value;
} else if (rpm <= _min_rpm) {
if (rpm == 0) { // S0 disables spindle
sys.spindle_speed = 0;
pwm_value = 0;
} else { // Set minimum PWM output
rpm = _min_rpm;
sys.spindle_speed = rpm;
pwm_value = 0;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RPM less than min RPM:%5.2f %d", rpm, pwm_value);
if (_output_pin != GPIO_NUM_25 && _output_pin != GPIO_NUM_26) { // DAC can only be used on these pins
_gpio_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "DAC spindle pin invalid GPIO_NUM_%d (pin 25 or 26 only)", _output_pin);
return;
}
} else {
// Compute intermediate PWM value with linear spindle speed model.
// NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight.
sys.spindle_speed = rpm;
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
is_reversable = (_direction_pin != UNDEFINED_PIN);
use_delays = true;
config_message();
}
set_output(pwm_value);
void DacSpindle::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"DAC spindle Output:%s, Enbl:%s, Dir:%s, Res:8bits",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str());
}
return rpm;
}
uint32_t DacSpindle::set_rpm(uint32_t rpm) {
if (_output_pin == UNDEFINED_PIN)
return rpm;
void DacSpindle ::set_output(uint32_t duty) {
if (_gpio_ok) {
dacWrite(_output_pin, (uint8_t)duty);
uint32_t pwm_value;
// apply overrides and limits
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (percent)
// Calculate PWM register value based on rpm max/min settings and programmed rpm.
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm)) {
// No PWM range possible. Set simple on/off spindle control pin state.
sys.spindle_speed = _max_rpm;
pwm_value = _pwm_max_value;
} else if (rpm <= _min_rpm) {
if (rpm == 0) { // S0 disables spindle
sys.spindle_speed = 0;
pwm_value = 0;
} else { // Set minimum PWM output
rpm = _min_rpm;
sys.spindle_speed = rpm;
pwm_value = 0;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RPM less than min RPM:%5.2f %d", rpm, pwm_value);
}
} else {
// Compute intermediate PWM value with linear spindle speed model.
// NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight.
sys.spindle_speed = rpm;
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
}
set_output(pwm_value);
return rpm;
}
void DacSpindle::set_output(uint32_t duty) {
if (_gpio_ok) {
dacWrite(_output_pin, (uint8_t)duty);
}
}
}

View File

@@ -0,0 +1,51 @@
#pragma once
/*
DacSpindle.cpp
This uses the Analog DAC in the ESP32 to generate a voltage
proportional to the GCode S value desired. Some spindle uses
a 0-5V or 0-10V value to control the spindle. You would use
an Op Amp type circuit to get from the 0.3.3V of the ESP32 to that voltage.
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/>.
*/
#include "PWMSpindle.h"
namespace Spindles {
// This uses one of the (2) DAC pins on ESP32 to output a voltage
class DacSpindle : public PWMSpindle {
public:
DacSpindle() = default;
DacSpindle(const DacSpindle&) = delete;
DacSpindle(DacSpindle&&) = delete;
DacSpindle& operator=(const DacSpindle&) = delete;
DacSpindle& operator=(DacSpindle&&) = delete;
void init() override;
void config_message() override;
uint32_t set_rpm(uint32_t rpm) override;
virtual ~DacSpindle() {}
private:
bool _gpio_ok; // DAC is on a valid pin
protected:
void set_output(uint32_t duty); // sets DAC instead of PWM
};
}

View File

@@ -109,9 +109,9 @@
*/
#include "SpindleClass.h"
#include "HuanyangSpindle.h"
#include "driver/uart.h"
#include <driver/uart.h>
#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
@@ -131,337 +131,335 @@
# define HUANYANG_BAUD_RATE 9600 // PD164 setting
#endif
// communication task and queue stuff
typedef struct {
uint8_t tx_length;
uint8_t rx_length;
bool critical;
char msg[HUANYANG_MAX_MSG_SIZE];
} hy_command_t;
namespace Spindles {
// communication task and queue stuff
typedef struct {
uint8_t tx_length;
uint8_t rx_length;
bool critical;
char msg[HUANYANG_MAX_MSG_SIZE];
} hy_command_t;
typedef enum : uint8_t {
READ_SET_FREQ = 0, // The set frequency
READ_OUTPUT_FREQ = 1, // The current operating frequency
READ_OUTPUT_AMPS = 2, //
READ_SET_RPM = 3, // This is the last requested freq even in off mode
READ_DC_VOLTAGE = 4, //
READ_AC_VOLTAGE = 5, //
READ_CONT = 6, // counting value???
READ_TEMP = 7, //
} read_register_t;
typedef enum : uint8_t {
READ_SET_FREQ = 0, // The set frequency
READ_OUTPUT_FREQ = 1, // The current operating frequency
READ_OUTPUT_AMPS = 2, //
READ_SET_RPM = 3, // This is the last requested freq even in off mode
READ_DC_VOLTAGE = 4, //
READ_AC_VOLTAGE = 5, //
READ_CONT = 6, // counting value???
READ_TEMP = 7, //
} read_register_t;
QueueHandle_t hy_cmd_queue;
QueueHandle_t hy_cmd_queue;
static TaskHandle_t vfd_cmdTaskHandle = 0;
static TaskHandle_t vfd_cmdTaskHandle = 0;
bool hy_spindle_ok = true;
bool hy_spindle_ok = true;
// The communications task
void vfd_cmd_task(void* pvParameters) {
static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive
uint8_t reg_item = 0x00;
hy_command_t next_cmd;
uint8_t rx_message[HUANYANG_MAX_MSG_SIZE];
// The communications task
void vfd_cmd_task(void* pvParameters) {
static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive
uint8_t reg_item = 0x00;
hy_command_t next_cmd;
uint8_t rx_message[HUANYANG_MAX_MSG_SIZE];
while (true) {
if (xQueueReceive(hy_cmd_queue, &next_cmd, 0) == pdTRUE) {
uart_flush(HUANYANG_UART_PORT);
//report_hex_msg(next_cmd.msg, "Tx: ", next_cmd.tx_length);
uart_write_bytes(HUANYANG_UART_PORT, next_cmd.msg, next_cmd.tx_length);
while (true) {
if (xQueueReceive(hy_cmd_queue, &next_cmd, 0) == pdTRUE) {
uart_flush(HUANYANG_UART_PORT);
//report_hex_msg(next_cmd.msg, "Tx: ", next_cmd.tx_length);
uart_write_bytes(HUANYANG_UART_PORT, next_cmd.msg, next_cmd.tx_length);
uint16_t read_length = uart_read_bytes(HUANYANG_UART_PORT, rx_message, next_cmd.rx_length, RESPONSE_WAIT_TICKS);
uint16_t read_length = uart_read_bytes(HUANYANG_UART_PORT, rx_message, next_cmd.rx_length, RESPONSE_WAIT_TICKS);
if (read_length < next_cmd.rx_length) {
if (!unresponsive) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive %d", read_length);
if (next_cmd.critical) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Critical Spindle RS485 Unresponsive");
system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL);
if (read_length < next_cmd.rx_length) {
if (!unresponsive) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive %d", read_length);
if (next_cmd.critical) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Critical Spindle RS485 Unresponsive");
system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL);
}
unresponsive = true;
}
unresponsive = true;
} else {
// success
unresponsive = false;
//report_hex_msg(rx_message, "Rx: ", read_length);
uint32_t ret_value = ((uint32_t)rx_message[4] << 8) + rx_message[5];
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Item:%d value:%05d ", rx_message[3], ret_value);
}
} else {
// success
unresponsive = false;
//report_hex_msg(rx_message, "Rx: ", read_length);
uint32_t ret_value = ((uint32_t)rx_message[4] << 8) + rx_message[5];
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Item:%d value:%05d ", rx_message[3], ret_value);
}
} else {
HuanyangSpindle ::read_value(reg_item); // only this appears to work all the time. Other registers are flakey.
if (reg_item < 0x03)
reg_item++;
else {
reg_item = 0x00;
HuanyangSpindle::read_value(reg_item); // only this appears to work all the time. Other registers are flakey.
if (reg_item < 0x03)
reg_item++;
else {
reg_item = 0x00;
}
}
vTaskDelay(HUANYANG_POLL_RATE); // TODO: What is the best value here?
}
vTaskDelay(HUANYANG_POLL_RATE); // TODO: What is the best value here?
}
}
// ================== Class methods ==================================
void HuanyangSpindle ::init() {
hy_spindle_ok = true; // initialize
// fail if required items are not defined
if (!get_pins_and_settings()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors");
return;
}
if (!_task_running) { // init can happen many times, we only want to start one task
hy_cmd_queue = xQueueCreate(HUANYANG_QUEUE_SIZE, sizeof(hy_command_t));
xTaskCreatePinnedToCore(vfd_cmd_task, // task
"vfd_cmdTaskHandle", // name for task
2048, // size of task stack
NULL, // parameters
1, // priority
&vfd_cmdTaskHandle,
0 // core
);
_task_running = true;
// ================== Class methods ==================================
void HuanyangSpindle::init() {
hy_spindle_ok = true; // initialize
// fail if required items are not defined
if (!get_pins_and_settings()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors");
return;
}
if (!_task_running) { // init can happen many times, we only want to start one task
hy_cmd_queue = xQueueCreate(HUANYANG_QUEUE_SIZE, sizeof(hy_command_t));
xTaskCreatePinnedToCore(vfd_cmd_task, // task
"vfd_cmdTaskHandle", // name for task
2048, // size of task stack
NULL, // parameters
1, // priority
&vfd_cmdTaskHandle,
0 // core
);
_task_running = true;
}
// this allows us to init() again later.
// If you change certain settings, init() gets called agian
uart_driver_delete(HUANYANG_UART_PORT);
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; // these VFDs are always reversable
use_delays = true;
//
_current_rpm = 0;
_state = SPINDLE_DISABLE;
config_message();
}
// this allows us to init() again later.
// If you change certain settings, init() gets called agian
uart_driver_delete(HUANYANG_UART_PORT);
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; // these VFDs are always reversable
use_delays = true;
//
_current_rpm = 0;
_state = SPINDLE_DISABLE;
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_pins_and_settings() {
// 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_pins_and_settings() {
#ifdef HUANYANG_TXD_PIN
_txd_pin = HUANYANG_TXD_PIN;
_txd_pin = HUANYANG_TXD_PIN;
#else
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_TXD_PIN");
hy_spindle_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_TXD_PIN");
hy_spindle_ok = false;
#endif
#ifdef HUANYANG_RXD_PIN
_rxd_pin = HUANYANG_RXD_PIN;
_rxd_pin = HUANYANG_RXD_PIN;
#else
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RXD_PIN");
hy_spindle_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RXD_PIN");
hy_spindle_ok = false;
#endif
#ifdef HUANYANG_RTS_PIN
_rts_pin = HUANYANG_RTS_PIN;
_rts_pin = HUANYANG_RTS_PIN;
#else
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RTS_PIN");
hy_spindle_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RTS_PIN");
hy_spindle_ok = false;
#endif
if (laser_mode->get()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle disabled in laser mode. Set $GCode/LaserMode=Off and restart");
hy_spindle_ok = false;
if (laser_mode->get()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle disabled in laser mode. Set $GCode/LaserMode=Off and restart");
hy_spindle_ok = false;
}
_min_rpm = rpm_min->get();
_max_rpm = rpm_max->get();
return hy_spindle_ok;
}
_min_rpm = rpm_min->get();
_max_rpm = rpm_max->get();
void HuanyangSpindle::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"Huanyang Spindle Tx:%s Rx:%s RTS:%s",
pinName(_txd_pin).c_str(),
pinName(_rxd_pin).c_str(),
pinName(_rts_pin).c_str());
}
return hy_spindle_ok;
}
void HuanyangSpindle::set_state(uint8_t state, uint32_t rpm) {
if (sys.abort)
return; // Block during abort.
void HuanyangSpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"Huanyang Spindle Tx:%s Rx:%s RTS:%s",
pinName(_txd_pin).c_str(),
pinName(_rxd_pin).c_str(),
pinName(_rts_pin).c_str());
}
bool critical = (sys.state == STATE_CYCLE || state != SPINDLE_DISABLE);
void HuanyangSpindle ::set_state(uint8_t state, uint32_t rpm) {
if (sys.abort)
return; // Block during abort.
bool critical = (sys.state == STATE_CYCLE || state != SPINDLE_DISABLE);
if (_current_state != state) { // already at the desired state. This function gets called a lot.
set_mode(state, critical); // critical if we are in a job
set_rpm(rpm);
if (state == SPINDLE_DISABLE) {
sys.spindle_speed = 0;
if (_current_state != state)
mc_dwell(spindle_delay_spindown->get());
} else {
if (_current_state != state)
mc_dwell(spindle_delay_spinup->get());
}
} else {
if (_current_rpm != rpm)
if (_current_state != state) { // already at the desired state. This function gets called a lot.
set_mode(state, critical); // critical if we are in a job
set_rpm(rpm);
}
_current_state = state; // store locally for faster get_state()
sys.report_ovr_counter = 0; // Set to report change immediately
return;
}
bool HuanyangSpindle ::set_mode(uint8_t mode, bool critical) {
if (!hy_spindle_ok)
return false;
hy_command_t mode_cmd;
mode_cmd.tx_length = 6;
mode_cmd.rx_length = 6;
mode_cmd.msg[0] = HUANYANG_ADDR;
mode_cmd.msg[1] = 0x03;
mode_cmd.msg[2] = 0x01;
if (mode == SPINDLE_ENABLE_CW)
mode_cmd.msg[3] = 0x01;
else if (mode == SPINDLE_ENABLE_CCW)
mode_cmd.msg[3] = 0x11;
else { //SPINDLE_DISABLE
mode_cmd.msg[3] = 0x08;
if (!xQueueReset(hy_cmd_queue)) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD spindle off, queue could not be reset");
if (state == SPINDLE_DISABLE) {
sys.spindle_speed = 0;
if (_current_state != state)
mc_dwell(spindle_delay_spindown->get());
} else {
if (_current_state != state)
mc_dwell(spindle_delay_spinup->get());
}
} else {
if (_current_rpm != rpm)
set_rpm(rpm);
}
_current_state = state; // store locally for faster get_state()
sys.report_ovr_counter = 0; // Set to report change immediately
return;
}
add_ModRTU_CRC(mode_cmd.msg, mode_cmd.rx_length);
bool HuanyangSpindle::set_mode(uint8_t mode, bool critical) {
if (!hy_spindle_ok)
return false;
mode_cmd.critical = critical;
hy_command_t mode_cmd;
if (xQueueSend(hy_cmd_queue, &mode_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
mode_cmd.tx_length = 6;
mode_cmd.rx_length = 6;
return true;
}
mode_cmd.msg[0] = HUANYANG_ADDR;
mode_cmd.msg[1] = 0x03;
mode_cmd.msg[2] = 0x01;
uint32_t HuanyangSpindle ::set_rpm(uint32_t rpm) {
if (!hy_spindle_ok)
return 0;
if (mode == SPINDLE_ENABLE_CW)
mode_cmd.msg[3] = 0x01;
else if (mode == SPINDLE_ENABLE_CCW)
mode_cmd.msg[3] = 0x11;
else { //SPINDLE_DISABLE
mode_cmd.msg[3] = 0x08;
hy_command_t rpm_cmd;
if (!xQueueReset(hy_cmd_queue)) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD spindle off, queue could not be reset");
}
}
// apply override
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent)
add_ModRTU_CRC(mode_cmd.msg, mode_cmd.rx_length);
// apply limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
mode_cmd.critical = critical;
sys.spindle_speed = rpm;
if (xQueueSend(hy_cmd_queue, &mode_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
return true;
}
uint32_t HuanyangSpindle::set_rpm(uint32_t rpm) {
if (!hy_spindle_ok)
return 0;
hy_command_t rpm_cmd;
// apply override
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent)
// apply limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
if (rpm == _current_rpm) // prevent setting same RPM twice
return rpm;
_current_rpm = rpm;
// TODO add the speed modifiers override, linearization, etc.
rpm_cmd.tx_length = 7;
rpm_cmd.rx_length = 6;
rpm_cmd.msg[0] = HUANYANG_ADDR;
rpm_cmd.msg[1] = 0x05;
rpm_cmd.msg[2] = 0x02;
uint16_t data = (uint16_t)(rpm * 100 / 60); // send Hz * 10 (Ex:1500 RPM = 25Hz .... Send 2500)
rpm_cmd.msg[3] = (data & 0xFF00) >> 8;
rpm_cmd.msg[4] = (data & 0xFF);
add_ModRTU_CRC(rpm_cmd.msg, rpm_cmd.tx_length);
rpm_cmd.critical = false;
if (xQueueSend(hy_cmd_queue, &rpm_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
if (rpm == _current_rpm) // prevent setting same RPM twice
return rpm;
_current_rpm = rpm;
// TODO add the speed modifiers override, linearization, etc.
rpm_cmd.tx_length = 7;
rpm_cmd.rx_length = 6;
rpm_cmd.msg[0] = HUANYANG_ADDR;
rpm_cmd.msg[1] = 0x05;
rpm_cmd.msg[2] = 0x02;
uint16_t data = (uint16_t)(rpm * 100 / 60); // send Hz * 10 (Ex:1500 RPM = 25Hz .... Send 2500)
rpm_cmd.msg[3] = (data & 0xFF00) >> 8;
rpm_cmd.msg[4] = (data & 0xFF);
add_ModRTU_CRC(rpm_cmd.msg, rpm_cmd.tx_length);
rpm_cmd.critical = false;
if (xQueueSend(hy_cmd_queue, &rpm_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
return rpm;
}
// This appears to read the control register and will return an RPM running or not.
void HuanyangSpindle ::read_value(uint8_t reg) {
uint16_t ret_value = 0;
hy_command_t read_cmd;
uint8_t rx_message[HUANYANG_MAX_MSG_SIZE];
read_cmd.tx_length = 8;
read_cmd.rx_length = 8;
read_cmd.msg[0] = HUANYANG_ADDR;
read_cmd.msg[1] = 0x04;
read_cmd.msg[2] = 0x03;
read_cmd.msg[3] = reg;
read_cmd.msg[4] = 0x00;
read_cmd.msg[5] = 0x00;
read_cmd.critical = (sys.state == STATE_CYCLE); // only critical if running a job TBD.... maybe spindle on?
add_ModRTU_CRC(read_cmd.msg, read_cmd.tx_length);
if (xQueueSend(hy_cmd_queue, &read_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
}
void HuanyangSpindle ::stop() {
set_mode(SPINDLE_DISABLE, false);
}
// 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);
// This appears to read the control register and will return an RPM running or not.
void HuanyangSpindle::read_value(uint8_t reg) {
uint16_t ret_value = 0;
hy_command_t read_cmd;
uint8_t rx_message[HUANYANG_MAX_MSG_SIZE];
read_cmd.tx_length = 8;
read_cmd.rx_length = 8;
read_cmd.msg[0] = HUANYANG_ADDR;
read_cmd.msg[1] = 0x04;
read_cmd.msg[2] = 0x03;
read_cmd.msg[3] = reg;
read_cmd.msg[4] = 0x00;
read_cmd.msg[5] = 0x00;
read_cmd.critical = (sys.state == STATE_CYCLE); // only critical if running a job TBD.... maybe spindle on?
add_ModRTU_CRC(read_cmd.msg, read_cmd.tx_length);
if (xQueueSend(hy_cmd_queue, &read_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
}
void HuanyangSpindle::stop() { set_mode(SPINDLE_DISABLE, false); }
// 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

@@ -0,0 +1,62 @@
#pragma once
/*
HuanyangSpindle.h
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/>.
*/
#include "Spindle.h"
namespace Spindles {
class HuanyangSpindle : public Spindle {
private:
uint16_t ModRTU_CRC(char* buf, int len);
bool set_mode(uint8_t mode, bool critical);
bool get_pins_and_settings();
uint32_t _current_rpm;
uint8_t _txd_pin;
uint8_t _rxd_pin;
uint8_t _rts_pin;
uint8_t _state;
bool _task_running;
public:
HuanyangSpindle() : _task_running(false) {}
HuanyangSpindle(const HuanyangSpindle&) = delete;
HuanyangSpindle(HuanyangSpindle&&) = delete;
HuanyangSpindle& operator=(const HuanyangSpindle&) = delete;
HuanyangSpindle& operator=(HuanyangSpindle&&) = delete;
void init();
void config_message();
void set_state(uint8_t state, uint32_t rpm);
uint8_t get_state();
uint32_t set_rpm(uint32_t rpm);
void stop();
static void read_value(uint8_t reg);
static void add_ModRTU_CRC(char* buf, int full_msg_len);
virtual ~HuanyangSpindle() {}
protected:
uint32_t _min_rpm;
uint32_t _max_rpm;
};
}

View File

@@ -19,22 +19,24 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SpindleClass.h"
#include "Laser.h"
// ===================================== Laser ==============================================
bool Laser ::isRateAdjusted() {
return true; // can use M4 (CCW) laser mode.
}
namespace Spindles {
bool Laser::isRateAdjusted() {
return true; // can use M4 (CCW) laser mode.
}
void Laser ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"Laser spindle on Pin:%s, Freq:%.2fHz, Res:%dbits Laser mode:$32=%d",
pinName(_output_pin).c_str(),
_pwm_freq,
_pwm_precision,
isRateAdjusted()); // the current mode
void Laser::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"Laser spindle on Pin:%s, Freq:%.2fHz, Res:%dbits Laser mode:$32=%d",
pinName(_output_pin).c_str(),
_pwm_freq,
_pwm_precision,
isRateAdjusted()); // the current mode
use_delays = false; // this will override the value set in PWMSpindle intit()
use_delays = false; // this will override the value set in PWMSpindle intit()
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
/*
Laser.h
This is similar the the PWM Spindle except that it allows the
M4 speed vs. power copensation.
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/>.
*/
#include "PWMSpindle.h"
namespace Spindles {
// this is the same as a PWM spindle but the M4 compensation is supported.
class Laser : public PWMSpindle {
public:
Laser() = default;
Laser(const Laser&) = delete;
Laser(Laser&&) = delete;
Laser& operator=(const Laser&) = delete;
Laser& operator=(Laser&&) = delete;
bool isRateAdjusted() override;
void config_message() override;
virtual ~Laser() {}
};
}

View File

@@ -19,24 +19,20 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SpindleClass.h"
#include "NullSpindle.h"
// ======================= NullSpindle ==============================
// NullSpindle is just bunch of do nothing (ignore) methods to be used when you don't want a spindle
namespace Spindles {
// ======================= 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;
use_delays = false;
config_message();
}
uint32_t NullSpindle ::set_rpm(uint32_t rpm) {
return rpm;
}
void NullSpindle ::set_state(uint8_t state, uint32_t rpm) {}
uint8_t NullSpindle ::get_state() {
return (SPINDLE_STATE_DISABLE);
}
void NullSpindle ::stop() {}
void NullSpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No spindle");
void NullSpindle::init() {
is_reversable = false;
use_delays = false;
config_message();
}
uint32_t NullSpindle::set_rpm(uint32_t rpm) { return rpm; }
void NullSpindle::set_state(uint8_t state, uint32_t rpm) {}
uint8_t NullSpindle::get_state() { return (SPINDLE_STATE_DISABLE); }
void NullSpindle::stop() {}
void NullSpindle::config_message() { grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No spindle"); }
}

View File

@@ -0,0 +1,47 @@
#pragma once
/*
NullSpindle.h
This is used when you don't want to use a spindle No I/O will be used
and most methods don't do anything
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/>.
*/
#include "Spindle.h"
namespace Spindles {
// This is a dummy spindle that has no I/O.
// It is used to ignore spindle commands when no spinde is desired
class NullSpindle : public Spindle {
public:
NullSpindle() = default;
NullSpindle(const NullSpindle&) = delete;
NullSpindle(NullSpindle&&) = delete;
NullSpindle& operator=(const NullSpindle&) = delete;
NullSpindle& operator=(NullSpindle&&) = delete;
void init() override;
uint32_t set_rpm(uint32_t rpm) override;
void set_state(uint8_t state, uint32_t rpm) override;
uint8_t get_state() override;
void stop() override;
void config_message() override;
virtual ~NullSpindle() {}
};
}

View File

@@ -19,7 +19,7 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SpindleClass.h"
#include "PWMSpindle.h"
// ======================= PWMSpindle ==============================
/*
@@ -29,230 +29,230 @@
//#include "grbl.h"
void PWMSpindle ::init() {
get_pins_and_settings();
namespace Spindles {
void PWMSpindle::init() {
get_pins_and_settings();
if (_output_pin == UNDEFINED_PIN) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin not defined");
return; // We cannot continue without the output pin
if (_output_pin == UNDEFINED_PIN) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin not defined");
return; // We cannot continue without the output pin
}
if (_output_pin >= I2S_OUT_PIN_BASE) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin %s cannot do PWM", pinName(_output_pin).c_str());
return;
}
ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel
ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
use_delays = true;
config_message();
}
if (_output_pin >= I2S_OUT_PIN_BASE) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle output pin %s cannot do PWM", pinName(_output_pin).c_str());
return;
}
ledcSetup(_spindle_pwm_chan_num, (double)_pwm_freq, _pwm_precision); // setup the channel
ledcAttachPin(_output_pin, _spindle_pwm_chan_num); // attach the PWM to the pin
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
use_delays = true;
config_message();
}
// Get the GPIO from the machine definition
void PWMSpindle ::get_pins_and_settings() {
// setup all the pins
// Get the GPIO from the machine definition
void PWMSpindle::get_pins_and_settings() {
// setup all the pins
#ifdef SPINDLE_OUTPUT_PIN
_output_pin = SPINDLE_OUTPUT_PIN;
_output_pin = SPINDLE_OUTPUT_PIN;
#else
_output_pin = UNDEFINED_PIN;
_output_pin = UNDEFINED_PIN;
#endif
#ifdef INVERT_SPINDLE_OUTPUT_PIN
_invert_pwm = true;
_invert_pwm = true;
#else
_invert_pwm = false;
_invert_pwm = false;
#endif
#ifdef SPINDLE_ENABLE_PIN
_enable_pin = SPINDLE_ENABLE_PIN;
_enable_pin = SPINDLE_ENABLE_PIN;
# ifdef SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED
_off_with_zero_speed = true;
_off_with_zero_speed = true;
# endif
#else
_enable_pin = UNDEFINED_PIN;
_off_with_zero_speed = false;
_enable_pin = UNDEFINED_PIN;
_off_with_zero_speed = false;
#endif
#ifdef SPINDLE_DIR_PIN
_direction_pin = SPINDLE_DIR_PIN;
_direction_pin = SPINDLE_DIR_PIN;
#else
_direction_pin = UNDEFINED_PIN;
_direction_pin = UNDEFINED_PIN;
#endif
is_reversable = (_direction_pin != UNDEFINED_PIN);
is_reversable = (_direction_pin != UNDEFINED_PIN);
_pwm_freq = spindle_pwm_freq->get();
_pwm_precision = calc_pwm_precision(_pwm_freq); // detewrmine the best precision
_pwm_period = (1 << _pwm_precision);
_pwm_freq = spindle_pwm_freq->get();
_pwm_precision = calc_pwm_precision(_pwm_freq); // detewrmine the best precision
_pwm_period = (1 << _pwm_precision);
if (spindle_pwm_min_value->get() > spindle_pwm_min_value->get())
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle min pwm is greater than max. Check $35 and $36");
if (spindle_pwm_min_value->get() > spindle_pwm_min_value->get())
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Spindle min pwm is greater than max. Check $35 and $36");
// pre-caculate some PWM count values
_pwm_off_value = (_pwm_period * spindle_pwm_off_value->get() / 100.0);
_pwm_min_value = (_pwm_period * spindle_pwm_min_value->get() / 100.0);
_pwm_max_value = (_pwm_period * spindle_pwm_max_value->get() / 100.0);
// pre-caculate some PWM count values
_pwm_off_value = (_pwm_period * spindle_pwm_off_value->get() / 100.0);
_pwm_min_value = (_pwm_period * spindle_pwm_min_value->get() / 100.0);
_pwm_max_value = (_pwm_period * spindle_pwm_max_value->get() / 100.0);
#ifdef ENABLE_PIECEWISE_LINEAR_SPINDLE
_min_rpm = RPM_MIN;
_max_rpm = RPM_MAX;
_piecewide_linear = true;
_min_rpm = RPM_MIN;
_max_rpm = RPM_MAX;
_piecewide_linear = true;
#else
_min_rpm = rpm_min->get();
_max_rpm = rpm_max->get();
_piecewide_linear = false;
_min_rpm = rpm_min->get();
_max_rpm = rpm_max->get();
_piecewide_linear = false;
#endif
// The pwm_gradient is the pwm duty cycle units per rpm
// _pwm_gradient = (_pwm_max_value - _pwm_min_value) / (_max_rpm - _min_rpm);
// The pwm_gradient is the pwm duty cycle units per rpm
// _pwm_gradient = (_pwm_max_value - _pwm_min_value) / (_max_rpm - _min_rpm);
_spindle_pwm_chan_num = 0; // Channel 0 is reserved for spindle use
}
uint32_t PWMSpindle::set_rpm(uint32_t rpm) {
uint32_t pwm_value;
if (_output_pin == UNDEFINED_PIN)
return rpm;
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_rpm(%d)", rpm);
// apply override
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent)
// apply limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
if (_piecewide_linear) {
//pwm_value = piecewise_linear_fit(rpm); TODO
pwm_value = 0;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Linear fit not implemented yet.");
} else {
if (rpm == 0)
pwm_value = _pwm_off_value;
else
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
_spindle_pwm_chan_num = 0; // Channel 0 is reserved for spindle use
}
set_output(pwm_value);
uint32_t PWMSpindle::set_rpm(uint32_t rpm) {
uint32_t pwm_value;
return 0;
}
if (_output_pin == UNDEFINED_PIN)
return rpm;
void PWMSpindle::set_state(uint8_t state, uint32_t rpm) {
if (sys.abort)
return; // Block during abort.
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_rpm(%d)", rpm);
if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm.
sys.spindle_speed = 0;
stop();
if (use_delays && (_current_state != state)) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Start ");
mc_dwell(spindle_delay_spindown->get());
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Done");
}
} else {
set_spindle_dir_pin(state == SPINDLE_ENABLE_CW);
set_rpm(rpm);
set_enable_pin(state != SPINDLE_DISABLE); // must be done after setting rpm for enable features to work
if (use_delays && (_current_state != state)) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Start %d", rpm);
mc_dwell(spindle_delay_spinup->get());
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Done");
// apply override
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent)
// apply limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
if (_piecewide_linear) {
//pwm_value = piecewise_linear_fit(rpm); TODO
pwm_value = 0;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Warning: Linear fit not implemented yet.");
} else {
if (rpm == 0)
pwm_value = _pwm_off_value;
else
pwm_value = map_uint32_t(rpm, _min_rpm, _max_rpm, _pwm_min_value, _pwm_max_value);
}
set_output(pwm_value);
return 0;
}
_current_state = state;
void PWMSpindle::set_state(uint8_t state, uint32_t rpm) {
if (sys.abort)
return; // Block during abort.
sys.report_ovr_counter = 0; // Set to report change immediately
}
if (state == SPINDLE_DISABLE) { // Halt or set spindle direction and rpm.
sys.spindle_speed = 0;
stop();
if (use_delays && (_current_state != state)) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Start ");
mc_dwell(spindle_delay_spindown->get());
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinDown Done");
}
} else {
set_spindle_dir_pin(state == SPINDLE_ENABLE_CW);
set_rpm(rpm);
set_enable_pin(state != SPINDLE_DISABLE); // must be done after setting rpm for enable features to work
if (use_delays && (_current_state != state)) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Start %d", rpm);
mc_dwell(spindle_delay_spinup->get());
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "SpinUp Done");
}
}
uint8_t PWMSpindle::get_state() {
if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN)
return (SPINDLE_STATE_DISABLE);
if (_direction_pin != UNDEFINED_PIN)
return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW;
return (SPINDLE_STATE_CW);
}
_current_state = state;
void PWMSpindle::stop() {
// inverts are delt with in methods
set_enable_pin(false);
set_output(_pwm_off_value);
}
sys.report_ovr_counter = 0; // Set to report change immediately
}
// prints the startup message of the spindle config
void PWMSpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"PWM spindle Output:%s, Enbl:%s, Dir:%s, Freq:%dHz, Res:%dbits",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str(),
_pwm_freq,
_pwm_precision);
}
uint8_t PWMSpindle::get_state() {
if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN)
return (SPINDLE_STATE_DISABLE);
if (_direction_pin != UNDEFINED_PIN)
return digitalRead(_direction_pin) ? SPINDLE_STATE_CW : SPINDLE_STATE_CCW;
return (SPINDLE_STATE_CW);
}
void PWMSpindle::set_output(uint32_t duty) {
if (_output_pin == UNDEFINED_PIN)
return;
void PWMSpindle::stop() {
// inverts are delt with in methods
set_enable_pin(false);
set_output(_pwm_off_value);
}
// to prevent excessive calls to ledcWrite, make sure duty hass changed
if (duty == _current_pwm_duty)
return;
// prints the startup message of the spindle config
void PWMSpindle::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"PWM spindle Output:%s, Enbl:%s, Dir:%s, Freq:%dHz, Res:%dbits",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str(),
_pwm_freq,
_pwm_precision);
}
_current_pwm_duty = duty;
void PWMSpindle::set_output(uint32_t duty) {
if (_output_pin == UNDEFINED_PIN)
return;
if (_invert_pwm)
duty = (1 << _pwm_precision) - duty;
// to prevent excessive calls to ledcWrite, make sure duty hass changed
if (duty == _current_pwm_duty)
return;
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_output(%d)", duty);
_current_pwm_duty = duty;
ledcWrite(_spindle_pwm_chan_num, duty);
}
if (_invert_pwm)
duty = (1 << _pwm_precision) - duty;
void PWMSpindle::set_enable_pin(bool enable) {
if (_enable_pin == UNDEFINED_PIN)
return;
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "set_output(%d)", duty);
if (_off_with_zero_speed && sys.spindle_speed == 0)
enable = false;
ledcWrite(_spindle_pwm_chan_num, duty);
}
void PWMSpindle::set_enable_pin(bool enable) {
if (_enable_pin == UNDEFINED_PIN)
return;
if (_off_with_zero_speed && sys.spindle_speed == 0)
enable = false;
#ifndef INVERT_SPINDLE_ENABLE_PIN
digitalWrite(_enable_pin, enable);
digitalWrite(_enable_pin, enable);
#else
digitalWrite(_enable_pin, !enable);
digitalWrite(_enable_pin, !enable);
#endif
digitalWrite(_enable_pin, enable);
}
void PWMSpindle::set_spindle_dir_pin(bool Clockwise) {
digitalWrite(_direction_pin, Clockwise);
}
/*
Calculate the highest precision of a PWM based on the frequency in bits
80,000,000 / freq = period
determine the highest precision where (1 << precision) < period
*/
uint8_t PWMSpindle ::calc_pwm_precision(uint32_t freq) {
uint8_t precision = 0;
// increase the precision (bits) until it exceeds allow by frequency the max or is 16
while ((1 << precision) < (uint32_t)(80000000 / freq) && precision <= 16)
precision++;
return precision - 1;
digitalWrite(_enable_pin, enable);
}
void PWMSpindle::set_spindle_dir_pin(bool Clockwise) { digitalWrite(_direction_pin, Clockwise); }
/*
Calculate the highest precision of a PWM based on the frequency in bits
80,000,000 / freq = period
determine the highest precision where (1 << precision) < period
*/
uint8_t PWMSpindle::calc_pwm_precision(uint32_t freq) {
uint8_t precision = 0;
// increase the precision (bits) until it exceeds allow by frequency the max or is 16
while ((1 << precision) < (uint32_t)(80000000 / freq) && precision <= 16)
precision++;
return precision - 1;
}
}

View File

@@ -0,0 +1,72 @@
#pragma once
/*
PWMSpindle.h
This is a full featured TTL PWM spindle This does not include speed/power
compensation. Use the Laser class for that.
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/>.
*/
#include "Spindle.h"
namespace Spindles {
// This adds support for PWM
class PWMSpindle : public Spindle {
public:
PWMSpindle() = default;
PWMSpindle(const PWMSpindle&) = delete;
PWMSpindle(PWMSpindle&&) = delete;
PWMSpindle& operator=(const PWMSpindle&) = delete;
PWMSpindle& operator=(PWMSpindle&&) = delete;
void init() override;
virtual uint32_t set_rpm(uint32_t rpm) override;
void set_state(uint8_t state, uint32_t rpm) override;
uint8_t get_state() override;
void stop() override;
void config_message() override;
virtual ~PWMSpindle() {}
protected:
int32_t _current_pwm_duty;
uint32_t _min_rpm;
uint32_t _max_rpm;
uint32_t _pwm_off_value;
uint32_t _pwm_min_value;
uint32_t _pwm_max_value;
uint8_t _output_pin;
uint8_t _enable_pin;
uint8_t _direction_pin;
uint8_t _spindle_pwm_chan_num;
uint32_t _pwm_freq;
uint32_t _pwm_period; // how many counts in 1 period
uint8_t _pwm_precision;
bool _piecewide_linear;
bool _off_with_zero_speed;
bool _invert_pwm;
//uint32_t _pwm_gradient; // Precalulated value to speed up rpm to PWM conversions.
virtual void set_spindle_dir_pin(bool Clockwise);
virtual void set_output(uint32_t duty);
virtual void set_enable_pin(bool enable_pin);
void get_pins_and_settings();
uint8_t calc_pwm_precision(uint32_t freq);
};
}

View File

@@ -19,51 +19,54 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SpindleClass.h"
#include "RelaySpindle.h"
// ========================= RelaySpindle ==================================
/*
namespace Spindles {
/*
This is a sub class of PWMSpindle but is a digital rather than PWM output
*/
void RelaySpindle::init() {
get_pins_and_settings();
void RelaySpindle::init() {
get_pins_and_settings();
if (_output_pin == UNDEFINED_PIN)
return;
if (_output_pin == UNDEFINED_PIN)
return;
pinMode(_output_pin, OUTPUT);
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
pinMode(_output_pin, OUTPUT);
pinMode(_enable_pin, OUTPUT);
pinMode(_direction_pin, OUTPUT);
is_reversable = (_direction_pin != UNDEFINED_PIN);
use_delays = true;
is_reversable = (_direction_pin != UNDEFINED_PIN);
use_delays = true;
config_message();
}
config_message();
}
// prints the startup message of the spindle config
void RelaySpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"Relay spindle Output:%s, Enbl:%s, Dir:%s",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str());
}
// prints the startup message of the spindle config
void RelaySpindle ::config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"Relay spindle Output:%s, Enbl:%s, Dir:%s",
pinName(_output_pin).c_str(),
pinName(_enable_pin).c_str(),
pinName(_direction_pin).c_str());
}
uint32_t RelaySpindle::set_rpm(uint32_t rpm) {
if (_output_pin == UNDEFINED_PIN)
return rpm;
sys.spindle_speed = rpm;
set_output(rpm != 0);
uint32_t RelaySpindle::set_rpm(uint32_t rpm) {
if (_output_pin == UNDEFINED_PIN)
return rpm;
}
sys.spindle_speed = rpm;
set_output(rpm != 0);
return rpm;
}
void RelaySpindle::set_output(uint32_t duty) {
void RelaySpindle::set_output(uint32_t duty) {
#ifdef INVERT_SPINDLE_PWM
duty = (duty == 0); // flip duty
duty = (duty == 0); // flip duty
#endif
digitalWrite(_output_pin, duty > 0); // anything greater
digitalWrite(_output_pin, duty > 0); // anything greater
}
}

View File

@@ -0,0 +1,46 @@
#pragma once
/*
RelaySpindle.h
This is used for a basic on/off spindle All S Values above 0
will turn the spindle on.
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/>.
*/
#include "PWMSpindle.h"
namespace Spindles {
// This is for an on/off spindle all RPMs above 0 are on
class RelaySpindle : public PWMSpindle {
public:
RelaySpindle() = default;
RelaySpindle(const RelaySpindle&) = delete;
RelaySpindle(RelaySpindle&&) = delete;
RelaySpindle& operator=(const RelaySpindle&) = delete;
RelaySpindle& operator=(RelaySpindle&&) = delete;
void init() override;
void config_message() override;
uint32_t set_rpm(uint32_t rpm) override;
virtual ~RelaySpindle() {}
protected:
void set_output(uint32_t duty);
};
}

View File

@@ -0,0 +1,81 @@
/*
SpindleClass.cpp
A Base class for spindles and spinsle like things such as lasers
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/>.
TODO
Add Spindle spin up/down delays
Get rid of dependance on machine definition #defines
SPINDLE_OUTPUT_PIN
SPINDLE_ENABLE_PIN
SPINDLE_DIR_PIN
*/
#include "Spindle.h"
#include "NullSpindle.h"
#include "PWMSpindle.h"
#include "RelaySpindle.h"
#include "Laser.h"
#include "DacSpindle.h"
#include "HuanyangSpindle.h"
#include "BESCSpindle.h"
#include "10vSpindle.h"
namespace Spindles {
// An instance of each type of spindle is created here.
// This allows the spindle to be dynamicly switched
NullSpindle null_spindle;
PWMSpindle pwm_spindle;
RelaySpindle relay_spindle;
Laser laser;
DacSpindle dac_spindle;
HuanyangSpindle huanyang_spindle;
BESCSpindle besc_spindle;
_10vSpindle _10v_spindle;
void Spindle::spindle_select() {
switch (spindle_type->get()) {
case SPINDLE_TYPE_PWM: spindle = &pwm_spindle; break;
case SPINDLE_TYPE_RELAY: spindle = &relay_spindle; break;
case SPINDLE_TYPE_LASER: spindle = &laser; break;
case SPINDLE_TYPE_DAC: spindle = &dac_spindle; break;
case SPINDLE_TYPE_HUANYANG: spindle = &huanyang_spindle; break;
case SPINDLE_TYPE_BESC: spindle = &besc_spindle; break;
case SPINDLE_TYPE_10V: spindle = &_10v_spindle; break;
case SPINDLE_TYPE_NONE:
default: spindle = &null_spindle; break;
}
spindle->init();
}
// ========================= Spindle ==================================
bool Spindle::isRateAdjusted() {
return false; // default for basic spindle is false
}
void Spindle::spindle_sync(uint8_t state, uint32_t rpm) {
if (sys.state == STATE_CHECK_MODE)
return;
protocol_buffer_synchronize(); // Empty planner buffer to ensure spindle is set when programmed.
set_state(state, rpm);
}
}

View File

@@ -0,0 +1,80 @@
#pragma once
/*
Spindle.h
Header file for a Spindle Class
See SpindleClass.cpp for more details
Part of Grbl_ESP32
2020 - Bart Dring This file was modified for use on the ESP32
CPU. Do not use this with Grbl for atMega328P
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/>.
See SpindleClass.cpp for more info and references
*/
#define SPINDLE_STATE_DISABLE 0 // Must be zero.
#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 3
#define SPINDLE_TYPE_DAC 4
#define SPINDLE_TYPE_HUANYANG 5
#define SPINDLE_TYPE_BESC 6
#define SPINDLE_TYPE_10V 7
#include "../grbl.h"
#include <driver/dac.h>
#include <driver/uart.h>
// =============== No floats! ===========================
// ================ NO FLOATS! ==========================
namespace Spindles {
// This is the base class. Do not use this as your spindle
class Spindle {
public:
Spindle() = default;
Spindle(const Spindle&) = delete;
Spindle(Spindle&&) = delete;
Spindle& operator=(const Spindle&) = delete;
Spindle& operator=(Spindle&&) = delete;
virtual void init() = 0; // not in constructor because this also gets called when $$ settings change
virtual uint32_t set_rpm(uint32_t rpm) = 0;
virtual void set_state(uint8_t state, uint32_t rpm) = 0;
virtual uint8_t get_state() = 0;
virtual void stop() = 0;
virtual void config_message() = 0;
virtual bool isRateAdjusted();
virtual void spindle_sync(uint8_t state, uint32_t rpm);
virtual ~Spindle() {}
bool is_reversable;
bool use_delays; // will SpinUp and SpinDown delays be used.
uint8_t _current_state;
static void spindle_select();
};
}
extern Spindles::Spindle* spindle;

View File

@@ -1,70 +0,0 @@
/*
SpindleClass.cpp
A Base class for spindles and spinsle like things such as lasers
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/>.
TODO
Add Spindle spin up/down delays
Get rid of dependance on machine definition #defines
SPINDLE_OUTPUT_PIN
SPINDLE_ENABLE_PIN
SPINDLE_DIR_PIN
*/
#include "SpindleClass.h"
// An instance of each type of spindle is created here.
// This allows the spindle to be dynamicly switched
NullSpindle null_spindle;
PWMSpindle pwm_spindle;
RelaySpindle relay_spindle;
Laser laser;
DacSpindle dac_spindle;
HuanyangSpindle huanyang_spindle;
BESCSpindle besc_spindle;
_10vSpindle _10v_spindle;
void spindle_select() {
switch (spindle_type->get()) {
case SPINDLE_TYPE_PWM: spindle = &pwm_spindle; break;
case SPINDLE_TYPE_RELAY: spindle = &relay_spindle; break;
case SPINDLE_TYPE_LASER: spindle = &laser; break;
case SPINDLE_TYPE_DAC: spindle = &dac_spindle; break;
case SPINDLE_TYPE_HUANYANG: spindle = &huanyang_spindle; break;
case SPINDLE_TYPE_BESC: spindle = &besc_spindle; break;
case SPINDLE_TYPE_10V: spindle = &_10v_spindle; break;
case SPINDLE_TYPE_NONE:
default: spindle = &null_spindle; break;
}
spindle->init();
}
// ========================= Spindle ==================================
bool Spindle::isRateAdjusted() {
return false; // default for basic spindle is false
}
void Spindle ::spindle_sync(uint8_t state, uint32_t rpm) {
if (sys.state == STATE_CHECK_MODE)
return;
protocol_buffer_synchronize(); // Empty planner buffer to ensure spindle is set when programmed.
set_state(state, rpm);
}

View File

@@ -1,217 +0,0 @@
#pragma once
/*
SpindleClass.h
Header file for a Spindle Class
See SpindleClass.cpp for more details
Part of Grbl_ESP32
2020 - Bart Dring This file was modified for use on the ESP32
CPU. Do not use this with Grbl for atMega328P
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/>.
See SpindleClass.cpp for more info and references
*/
#define SPINDLE_STATE_DISABLE 0 // Must be zero.
#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 3
#define SPINDLE_TYPE_DAC 4
#define SPINDLE_TYPE_HUANYANG 5
#define SPINDLE_TYPE_BESC 6
#define SPINDLE_TYPE_10V 7
#include "../grbl.h"
#include <driver/dac.h>
#include "driver/uart.h"
// =============== No floats! ===========================
// ================ NO FLOATS! ==========================
// This is the base class. Do not use this as your spindle
class Spindle {
public:
virtual void init(); // not in constructor because this also gets called when $$ settings change
virtual uint32_t set_rpm(uint32_t rpm);
virtual void set_state(uint8_t state, uint32_t rpm);
virtual uint8_t get_state();
virtual void stop();
virtual void config_message();
virtual bool isRateAdjusted();
virtual void spindle_sync(uint8_t state, uint32_t rpm);
bool is_reversable;
bool use_delays; // will SpinUp and SpinDown delays be used.
uint8_t _current_state;
};
// This is a dummy spindle that has no I/O.
// It is used to ignore spindle commands when no spinde is desired
class NullSpindle : public Spindle {
public:
void init();
uint32_t set_rpm(uint32_t rpm);
void set_state(uint8_t state, uint32_t rpm);
uint8_t get_state();
void stop();
void config_message();
};
// This adds support for PWM
class PWMSpindle : public Spindle {
public:
void init();
virtual uint32_t set_rpm(uint32_t rpm);
void set_state(uint8_t state, uint32_t rpm);
uint8_t get_state();
void stop();
void config_message();
private:
void set_spindle_dir_pin(bool Clockwise);
protected:
int32_t _current_pwm_duty;
uint32_t _min_rpm;
uint32_t _max_rpm;
uint32_t _pwm_off_value;
uint32_t _pwm_min_value;
uint32_t _pwm_max_value;
uint8_t _output_pin;
uint8_t _enable_pin;
uint8_t _direction_pin;
uint8_t _spindle_pwm_chan_num;
uint32_t _pwm_freq;
uint32_t _pwm_period; // how many counts in 1 period
uint8_t _pwm_precision;
bool _piecewide_linear;
bool _off_with_zero_speed;
bool _invert_pwm;
//uint32_t _pwm_gradient; // Precalulated value to speed up rpm to PWM conversions.
virtual void set_output(uint32_t duty);
void set_enable_pin(bool enable_pin);
void get_pins_and_settings();
uint8_t calc_pwm_precision(uint32_t freq);
};
// This is for an on/off spindle all RPMs above 0 are on
class RelaySpindle : public PWMSpindle {
public:
void init();
void config_message();
uint32_t set_rpm(uint32_t rpm);
protected:
void set_output(uint32_t duty);
};
// this is the same as a PWM spindle but the M4 compensation is supported.
class Laser : public PWMSpindle {
public:
bool isRateAdjusted();
void config_message();
};
// This uses one of the (2) DAC pins on ESP32 to output a voltage
class DacSpindle : public PWMSpindle {
public:
void init();
void config_message();
uint32_t set_rpm(uint32_t rpm);
private:
bool _gpio_ok; // DAC is on a valid pin
protected:
void set_output(uint32_t duty); // sets DAC instead of PWM
};
class HuanyangSpindle : public Spindle {
private:
uint16_t ModRTU_CRC(char* buf, int len);
bool set_mode(uint8_t mode, bool critical);
bool get_pins_and_settings();
uint32_t _current_rpm;
uint8_t _txd_pin;
uint8_t _rxd_pin;
uint8_t _rts_pin;
uint8_t _state;
bool _task_running;
public:
HuanyangSpindle() { _task_running = false; }
void init();
void config_message();
void set_state(uint8_t state, uint32_t rpm);
uint8_t get_state();
uint32_t set_rpm(uint32_t rpm);
void stop();
static void read_value(uint8_t reg);
static void add_ModRTU_CRC(char* buf, int full_msg_len);
protected:
uint32_t _min_rpm;
uint32_t _max_rpm;
};
class BESCSpindle : public PWMSpindle {
public:
void init();
void config_message();
uint32_t set_rpm(uint32_t rpm);
};
class _10vSpindle : public PWMSpindle {
public:
void init();
void config_message();
uint32_t set_rpm(uint32_t rpm);
uint8_t _forward_pin;
uint8_t _reverse_pin;
//void set_state(uint8_t state, uint32_t rpm);
uint8_t get_state();
void stop();
protected:
void set_enable_pin(bool enable_pin);
void set_spindle_dir_pin(bool Clockwise);
};
extern Spindle* spindle;
extern NullSpindle null_spindle;
extern PWMSpindle pwm_spindle;
extern RelaySpindle relay_spindle;
extern Laser laser;
extern DacSpindle dac_spindle;
extern HuanyangSpindle huanyang_spindle;
extern BESCSpindle besc_spindle;
extern _10vSpindle _10v_spindle;
void spindle_select();
// in HuanyangSpindle.cpp
void vfd_cmd_task(void* pvParameters);

View File

@@ -56,7 +56,7 @@
#include "report.h"
#include "serial.h"
#include "Pins.h"
#include "Spindles/SpindleClass.h"
#include "Spindles/Spindle.h"
#include "Motors/Motors.h"
#include "stepper.h"
#include "jog.h"