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

Restored Spindle files

This commit is contained in:
Mitch Bradley
2020-05-07 11:19:34 -10:00
parent 2baaac726d
commit 4c3cf7ba9e
9 changed files with 1259 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
/*
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 "SpindleClass.h"
#include "SpindleClass.h"
// don't change these
#define BESC_PWM_FREQ 50.0f // Hz
#define BESC_PWM_BIT_PRECISION 16 // bits
#define BESC_PULSE_PERIOD (1.0 / BESC_PWM_FREQ)
// Ok to tweak. These are the pulse lengths in seconds
// #define them in your machine definition file if you want different values
#ifndef BESC_MIN_PULSE_SECS
#define BESC_MIN_PULSE_SECS 0.0009f // in seconds
#endif
#ifndef BESC_MAX_PULSE_SECS
#define BESC_MAX_PULSE_SECS 0.0022f // in seconds
#endif
//calculations...don't change
#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
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
if (_enable_pin != UNDEFINED_PIN)
pinMode(_enable_pin, OUTPUT);
set_rpm(0);
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:%d Min:%0.2fms Max:%0.2fms Freq:%dHz Res:%dbits",
report_pin_number(_output_pin),
BESC_MIN_PULSE_SECS * 1000.0, // convert to milliseconds
BESC_MAX_PULSE_SECS * 1000.0, // convert to milliseconds
report_pin_number(_pwm_freq),
report_pin_number(_pwm_precision));
}
uint32_t BESCSpindle::set_rpm(uint32_t rpm) {
uint32_t pwm_value;
if (_output_pin == UNDEFINED_PIN)
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,111 @@
/*
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 "SpindleClass.h"
// ======================================== DacSpindle ======================================
void DacSpindle :: init() {
get_pins_and_settings();
if (_output_pin == UNDEFINED_PIN)
return;
_min_rpm = settings.rpm_min;
_max_rpm = settings.rpm_max;
_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;
}
if (_enable_pin != UNDEFINED_PIN)
pinMode(_enable_pin, OUTPUT);
if (_direction_pin != UNDEFINED_PIN) {
pinMode(_direction_pin, OUTPUT);
}
is_reversable = (_direction_pin != UNDEFINED_PIN);
config_message();
}
void DacSpindle :: config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"DAC spindle Output:%d, Enbl:%d, Dir:%d, Res:8bits",
report_pin_number(_output_pin),
report_pin_number(_enable_pin), // 255 means pin not defined
report_pin_number(_direction_pin)); // 255 means pin not defined
}
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);
}
} 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);
}
if (_off_with_zero_speed) {
set_enable_pin(rpm != 0);
}
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,321 @@
/*
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/>.
WARNING!!!!
VFDs are very dangerous. They have high voltages and are very powerful
Remove power before changing bits.
VFD frequencies are in Hz. Multiply 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 (Typical for spindles)
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, 2.2kw=??)
PD163 1 RS485 Address: 1 (Typical. OK to change...see below)
PD164 1 RS485 Baud rate: 9600 (Typical. OK to change...see below)
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 "SpindleClass.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
#define HUANYANG_BUF_SIZE 127
#define RESPONSE_WAIT_TICKS 50 // how long to wait for a response
#define HUANYANG_MAX_MSG_SIZE 10 // more than enough for a modbus message
// OK to change these
// #define them in your machine definition file if you want different values
#ifndef HUANYANG_ADDR
#define HUANYANG_ADDR 0x01
#endif
#ifndef HUANYANG_BAUD_RATE
#define HUANYANG_BAUD_RATE 9600 // PD164 setting
#endif
// communication task and queue stuff
typedef struct {
uint8_t tx_length;
uint8_t rx_length;
char msg[HUANYANG_MAX_MSG_SIZE];
} hy_command_t;
QueueHandle_t hy_cmd_queue;
static TaskHandle_t vfd_cmdTaskHandle = 0;
// The communications task
void vfd_cmd_task(void* pvParameters) {
hy_command_t next_cmd;
uint8_t rx_message[HUANYANG_MAX_MSG_SIZE];
while (true) {
if (xQueueReceive(hy_cmd_queue, &next_cmd, 0) == pdTRUE) {
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "queue send %d !!!", next_cmd.tx_length);
//report_hex_msg(next_cmd.msg, "To VFD:", next_cmd.tx_length); // TODO for debugging comment out
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);
if (read_length < next_cmd.rx_length) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive");
// TODO Do something with this error
// system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL);
}
} else {
// TODO: Should we ping the spindle here to make sure it does not go off line?
}
vTaskDelay(500); // TODO: What is the best value here?
}
}
// ================== Class methods ==================================
void HuanyangSpindle :: init() {
if (! _task_running) { // init can happen many times, we only want to start one task
xTaskCreatePinnedToCore(vfd_cmd_task, // task
"vfd_cmdTaskHandle", // name for task
2048, // size of task stack
NULL, // parameters
1, // priority
&vfd_cmdTaskHandle,
0 // core
);
hy_cmd_queue = xQueueCreate(5, sizeof(hy_command_t));
_task_running = true;
}
// fail if required items are not defined
if (!get_pins_and_settings()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors");
return;
}
// 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
//
_current_pwm_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() {
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",
report_pin_number(_txd_pin),
report_pin_number(_rxd_pin),
report_pin_number(_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, uint32_t rpm) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Set state");
if (sys.abort)
return; // Block during abort.
if (state != _state) { // already at the desired state. This function gets called a lot.
_state = state; // store locally for faster get_state()
set_mode(state);
if (state == SPINDLE_DISABLE) {
sys.spindle_speed = 0;
return;
}
}
set_rpm(rpm);
sys.report_ovr_counter = 0; // Set to report change immediately
return;
}
bool HuanyangSpindle :: set_mode(uint8_t mode) {
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;
add_ModRTU_CRC(mode_cmd.msg, mode_cmd.rx_length);
if (xQueueSend(hy_cmd_queue, &mode_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
return true;
}
/*
ADDR CMD LEN DATA CRC
0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ)
*/
uint32_t HuanyangSpindle :: set_rpm(uint32_t rpm) {
hy_command_t rpm_cmd;
if (rpm == _current_pwm_rpm) // prevent setting same RPM twice
return rpm;
_current_pwm_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);
if (xQueueSend(hy_cmd_queue, &rpm_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
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

@@ -0,0 +1,39 @@
/*
Laser.cpp
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 "SpindleClass.h"
// ===================================== Laser ==============================================
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 GPIO:%d, Freq:%.2fHz, Res:%dbits Laser mode:$32=%d",
report_pin_number(_output_pin),
_pwm_freq,
_pwm_precision,
isRateAdjusted()); // the current mode
}

View File

@@ -0,0 +1,41 @@
/*
NullSpindle.cpp
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 "SpindleClass.h"
// ======================= 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();
}
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,257 @@
/*
PWMSpindle.cpp
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 "SpindleClass.h"
// ======================= PWMSpindle ==============================
/*
This gets called at startup or whenever a spindle setting changes
If the spindle is running it will stop and need to be restarted with M3Snnnn
*/
//#include "grbl.h"
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
}
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 (_enable_pin != UNDEFINED_PIN)
pinMode(_enable_pin, OUTPUT);
if (_direction_pin != UNDEFINED_PIN)
pinMode(_direction_pin, OUTPUT);
config_message();
}
// 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;
#else
_output_pin = UNDEFINED_PIN;
#endif
#ifdef INVERT_SPINDLE_ENABLE_PIN
_invert_pwm = true;
#else
_invert_pwm = false;
#endif
#ifdef SPINDLE_ENABLE_PIN
_enable_pin = SPINDLE_ENABLE_PIN;
#ifdef SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED
_off_with_zero_speed = true;
#endif
#else
_enable_pin = UNDEFINED_PIN;
_off_with_zero_speed = false;
#endif
#ifdef SPINDLE_DIR_PIN
_direction_pin = SPINDLE_DIR_PIN;
#else
_direction_pin = UNDEFINED_PIN;
#endif
is_reversable = (_direction_pin != UNDEFINED_PIN);
_pwm_freq = (uint32_t)settings.spindle_pwm_freq;
_pwm_precision = calc_pwm_precision(_pwm_freq); // detewrmine the best precision
_pwm_period = (1 << _pwm_precision);
if (settings.spindle_pwm_min_value > settings.spindle_pwm_min_value)
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 * (uint32_t)settings.spindle_pwm_off_value / 100.0);
_pwm_min_value = (_pwm_period * (uint32_t)settings.spindle_pwm_min_value / 100.0);
_pwm_max_value = (_pwm_period * (uint32_t)settings.spindle_pwm_max_value / 100.0);
#ifdef ENABLE_PIECEWISE_LINEAR_SPINDLE
_min_rpm = RPM_MIN;
_max_rpm = RPM_MAX;
_piecewide_linear = true;
#else
_min_rpm = (uint32_t)settings.rpm_min;
_max_rpm = (uint32_t)settings.rpm_max;
_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);
_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);
}
if (_off_with_zero_speed)
set_enable_pin(rpm != 0);
set_output(pwm_value);
return 0;
}
void PWMSpindle::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);
}
set_enable_pin(state == SPINDLE_DISABLE);
sys.report_ovr_counter = 0; // Set to report change immediately
}
uint8_t PWMSpindle::get_state() {
if (_current_pwm_duty == 0 || _output_pin == UNDEFINED_PIN)
return (SPINDLE_STATE_DISABLE);
else {
if (_direction_pin != UNDEFINED_PIN) {
if (digitalRead(_direction_pin))
return (SPINDLE_STATE_CW);
else
return (SPINDLE_STATE_CCW);
} else
return (SPINDLE_STATE_CW);
}
}
void PWMSpindle::stop() {
// inverts are delt with in methods
set_enable_pin(false);
set_output(_pwm_off_value);
}
// prints the startup message of the spindle config
void PWMSpindle :: config_message() {
grbl_msg_sendf(CLIENT_SERIAL,
MSG_LEVEL_INFO,
"PWM spindle Output:%d, Enbl:%d, Dir:%d, Freq:%dHz, Res:%dbits",
report_pin_number(_output_pin),
report_pin_number(_enable_pin), // 255 means pin not defined
report_pin_number(_direction_pin), // 255 means pin not defined
_pwm_freq,
_pwm_precision);
}
void PWMSpindle::set_output(uint32_t duty) {
if (_output_pin == UNDEFINED_PIN)
return;
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Set output %d", duty);
// to prevent excessive calls to ledcWrite, make sure duty hass changed
if (duty == _current_pwm_duty)
return;
_current_pwm_duty = duty;
if (_invert_pwm)
duty = (1 << _pwm_precision) - duty;
ledcWrite(_spindle_pwm_chan_num, duty);
}
void PWMSpindle::set_enable_pin(bool enable) {
if (_enable_pin == UNDEFINED_PIN)
return;
#ifndef INVERT_SPINDLE_ENABLE_PIN
digitalWrite(_enable_pin, enable);
#else
digitalWrite(_enable_pin, !enable);
#endif
}
void PWMSpindle::set_spindle_dir_pin(bool Clockwise) {
if (_direction_pin != UNDEFINED_PIN)
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,80 @@
/*
RelaySpindle.cpp
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 "SpindleClass.h"
// ========================= RelaySpindle ==================================
/*
This is a sub class of PWMSpindle but is a digital rather than PWM output
*/
void RelaySpindle::init() {
get_pins_and_settings();
if (_output_pin == UNDEFINED_PIN)
return;
pinMode(_output_pin, OUTPUT);
if (_enable_pin != UNDEFINED_PIN)
pinMode(_enable_pin, OUTPUT);
if (_direction_pin != UNDEFINED_PIN)
pinMode(_direction_pin, OUTPUT);
is_reversable = (_direction_pin != UNDEFINED_PIN);
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:%d, Enbl:%d, Dir:%d",
report_pin_number(_output_pin),
report_pin_number(_enable_pin), // 255 means pin not defined
report_pin_number(_direction_pin)); // 255 means pin not defined
}
uint32_t RelaySpindle::set_rpm(uint32_t rpm) {
if (_output_pin == UNDEFINED_PIN)
return rpm;
sys.spindle_speed = rpm;
if (rpm == 0) {
set_output(0);
} else {
set_output(1);
}
if (_off_with_zero_speed)
set_enable_pin(rpm != 0);
return rpm;
}
void RelaySpindle::set_output(uint32_t duty) {
#ifdef INVERT_SPINDLE_PWM
duty = (duty == 0); // flip duty
#endif
digitalWrite(_output_pin, duty > 0); // anything greater
}

View File

@@ -0,0 +1,91 @@
/*
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 "NullSpindle.cpp"
#include "PWMSpindle.cpp"
#include "DacSpindle.cpp"
#include "RelaySpindle.cpp"
#include "Laser.cpp"
#include "HuanyangSpindle.cpp"
#include "BESCSpindle.cpp"
// 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;
void spindle_select(uint8_t spindle_type) {
switch (spindle_type) {
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_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,197 @@
/*
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
#ifndef SPINDLE_CLASS_H
#define SPINDLE_CLASS_H
#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;
};
// 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:
int32_t _current_pwm_duty;
void set_spindle_dir_pin(bool Clockwise);
protected:
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);
void add_ModRTU_CRC(char* buf, int full_msg_len);
bool set_mode(uint8_t mode);
bool get_pins_and_settings();
uint32_t _current_pwm_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();
};
class BESCSpindle : public PWMSpindle {
public:
void init();
void config_message();
uint32_t set_rpm(uint32_t rpm);
};
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;
void spindle_select(uint8_t spindletype);
// in HuanyangSpindle.cpp
void vfd_cmd_task(void* pvParameters);
#endif