mirror of
https://github.com/grblHAL/core.git
synced 2026-02-06 00:52:35 +08:00
Added ADC/DAC resolution to $pinstate command, changed reported values to integer for ADC/DAC devices and float formatted for PWM devices. Numeric settings can now be set via G65P1Q<n>S<value>, <n> is the setting number, <value> is the new value. Changed alarm code for Modbus exceptions (communication errors) from 14 to 19. Refactored MPG stream code to allow plugins to hook into MPG streams (via event handler). Added _free memory system parameter, returns value in KBytes or -1 if not available from driver. Changed basic stream data type from char to uint8_t, added HAL function and core API for releasing/closing UART streams.
1268 lines
46 KiB
C
1268 lines
46 KiB
C
/*
|
|
spindle_control.c - spindle control methods
|
|
|
|
Part of grblHAL
|
|
|
|
Copyright (c) 2017-2025 Terje Io
|
|
Copyright (c) 2012-2015 Sungeun K. Jeon
|
|
Copyright (c) 2009-2011 Simen Svale Skogsrud
|
|
|
|
grblHAL 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.
|
|
|
|
grblHAL 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 grblHAL. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#if ENABLE_SPINDLE_LINEARIZATION
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#include "hal.h"
|
|
#include "protocol.h"
|
|
#include "state_machine.h"
|
|
#include "settings.h"
|
|
|
|
#ifndef UNUSED
|
|
#define UNUSED(x) (void)(x)
|
|
#endif
|
|
|
|
/*! \brief Structure for holding spindle registration data */
|
|
typedef struct {
|
|
const spindle_ptrs_t *cfg;
|
|
spindle_ptrs_t hal;
|
|
const char *name;
|
|
bool init_ok;
|
|
} spindle_reg_t;
|
|
|
|
/*! \brief Structure for holding data about an enabled spindle */
|
|
typedef struct {
|
|
spindle_param_t param;
|
|
spindle_ptrs_t hal;
|
|
bool enabled;
|
|
} spindle_sys_t;
|
|
|
|
static uint8_t n_spindle = 0;
|
|
static spindle_sys_t sys_spindle[N_SYS_SPINDLE] = {0};
|
|
static spindle_reg_t spindles[N_SPINDLE] = {0}, *pwm_spindle = NULL;
|
|
static const spindle_data_ptrs_t *encoder;
|
|
|
|
static void spindle_init (void *data)
|
|
{
|
|
spindle_reg_t *spindle = (spindle_reg_t *)data;
|
|
|
|
if((spindle->init_ok = spindle->hal.config == NULL || spindle->hal.config(&spindle->hal)))
|
|
spindle->hal.set_state(&spindle->hal, (spindle_state_t){0}, 0.0f);
|
|
}
|
|
|
|
/*! \internal \brief Activates and registers a spindle as enabled with a specific spindle number.
|
|
\param spindle_id spindle id of spindle to activate as a \ref spindle_id_t.
|
|
\param spindle_num spindle number to set as enabled as a \ref spindle_num_t.
|
|
\returns \a true if succsesful, \a false if not.
|
|
*/
|
|
static bool spindle_activate (spindle_id_t spindle_id, spindle_num_t spindle_num)
|
|
{
|
|
bool ok;
|
|
spindle_reg_t *spindle;
|
|
|
|
// Always configure PWM spindle on startup to ensure outputs are set correctly.
|
|
if(pwm_spindle && pwm_spindle->cfg->config && pwm_spindle != &spindles[spindle_id]) {
|
|
|
|
if(!pwm_spindle->hal.cap.rpm_range_locked) {
|
|
pwm_spindle->hal.rpm_min = settings.pwm_spindle.rpm_min;
|
|
pwm_spindle->hal.rpm_max = settings.pwm_spindle.rpm_max;
|
|
}
|
|
|
|
spindle_init(pwm_spindle);
|
|
}
|
|
pwm_spindle = NULL;
|
|
|
|
if((ok = spindle_id >= 0 && spindle_id < n_spindle && !!spindles[spindle_id].cfg)) {
|
|
|
|
spindle = &spindles[spindle_id];
|
|
|
|
if(sys_spindle[spindle_num].enabled && sys_spindle[spindle_num].hal.id != spindle_id && sys_spindle[spindle_num].hal.set_state)
|
|
gc_spindle_off(); // TODO: switch off only the default spindle?
|
|
|
|
if(!spindle->hal.cap.rpm_range_locked) {
|
|
spindle->hal.rpm_min = settings.pwm_spindle.rpm_min;
|
|
spindle->hal.rpm_max = settings.pwm_spindle.rpm_max;
|
|
}
|
|
|
|
if(!spindle->init_ok)
|
|
ok = spindle->init_ok = spindle->hal.config == NULL || spindle->hal.config(&spindle->hal);
|
|
|
|
if(ok) {
|
|
|
|
spindle_ptrs_t spindle_hal;
|
|
|
|
memcpy(&spindle_hal, &spindle->hal, sizeof(spindle_ptrs_t));
|
|
|
|
spindle_hal.cap.laser &= settings.mode == Mode_Laser;
|
|
|
|
if(grbl.on_spindle_select)
|
|
ok = grbl.on_spindle_select(&spindle_hal);
|
|
|
|
if(ok) {
|
|
sys_spindle[spindle_num].enabled = true;
|
|
sys_spindle[spindle_num].param.hal = &sys_spindle[spindle_num].hal;
|
|
if(sys_spindle[spindle_num].param.override_pct == 0)
|
|
sys_spindle[spindle_num].param.override_pct = DEFAULT_SPINDLE_RPM_OVERRIDE;
|
|
spindle_hal.param = &sys_spindle[spindle_num].param;
|
|
memcpy(&sys_spindle[spindle_num].hal, &spindle_hal, sizeof(spindle_ptrs_t));
|
|
if(grbl.on_spindle_selected)
|
|
grbl.on_spindle_selected(&sys_spindle[spindle_num].hal);
|
|
#if N_SPINDLE > 1
|
|
system_add_rt_report(Report_SpindleId);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*! \brief Register a spindle with the core.
|
|
\param spindle pointer to a \a spindle_ptrs_t structure.
|
|
\param name pointer to a null terminated string.
|
|
\returns assigned \a spindle id as a \ref spindle_id_t if successful, -1 if not.
|
|
|
|
__NOTE:__ The first spindle registered will become the default active spindle.
|
|
__NOTE:__ up to \ref N_SPINDLE spindles can be registered at a time.
|
|
*/
|
|
spindle_id_t spindle_register (const spindle_ptrs_t *spindle, const char *name)
|
|
{
|
|
if(n_spindle == 1 && spindles[0].cfg->type == SpindleType_Null)
|
|
n_spindle = 0;
|
|
|
|
if(n_spindle < N_SPINDLE && settings_add_spindle_type(name)) {
|
|
|
|
spindles[n_spindle].cfg = spindle;
|
|
spindles[n_spindle].name = name;
|
|
memcpy(&spindles[n_spindle].hal, spindles[n_spindle].cfg, sizeof(spindle_ptrs_t));
|
|
spindles[n_spindle].hal.id = n_spindle;
|
|
|
|
if(spindle->type == SpindleType_PWM) {
|
|
if(pwm_spindle == NULL) {
|
|
pwm_spindle = &spindles[n_spindle];
|
|
hal.driver_cap.pwm_spindle = On;
|
|
} else
|
|
task_run_on_startup(spindle_init, &spindles[n_spindle]);
|
|
}
|
|
|
|
if(n_spindle == 0)
|
|
memcpy(&sys_spindle[0].hal, spindle, sizeof(spindle_ptrs_t));
|
|
|
|
return n_spindle++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*! \brief Enable a spindle and make it available for use by gcode.
|
|
\param spindle_id spindle id as a \ref spindle_id_t.
|
|
\returns assigned spindle number as a \a spindle_num_t if successful \a -1 if not.
|
|
|
|
__NOTE:__ up to \ref N_SYS_SPINDLE spindles can be enabled at a time.
|
|
*/
|
|
spindle_num_t spindle_enable (spindle_id_t spindle_id)
|
|
{
|
|
uint_fast8_t idx = 0;
|
|
spindle_num_t spindle_num = -1;
|
|
|
|
if(spindle_id >= 0 && spindle_id < n_spindle) do {
|
|
if(!sys_spindle[idx].enabled && spindle_activate(spindle_id, idx))
|
|
spindle_num = idx;
|
|
} while(++idx < N_SYS_SPINDLE && spindle_num == -1);
|
|
|
|
return spindle_num;
|
|
}
|
|
|
|
/*! \brief Enables a spindle and sets it as default spindle (spindle number 0).
|
|
\param spindle_id spindle id as a \ref spindle_id_t.
|
|
\returns \a true if succsesful, \a false if not.
|
|
*/
|
|
bool spindle_select (spindle_id_t spindle_id)
|
|
{
|
|
if(n_spindle == 0 && spindle_id >= 0) {
|
|
spindle_id = 0;
|
|
spindle_add_null();
|
|
}
|
|
|
|
return (sys_spindle[0].enabled && sys_spindle[0].hal.id == spindle_id) || spindle_activate(spindle_id, 0);
|
|
}
|
|
|
|
|
|
/*! \brief Get the handlers (function pointers) etc. associated with the spindle.
|
|
\param spindle_id spindle id as a \ref spindle_id_t.
|
|
\param hal a \ref spindle_hal_t enum value:
|
|
<br>\ref SpindleHAL_Raw - get the read only version as supplied at registration
|
|
<br>\ref SpindleHAL_Configured - get the version with run-time modifications applied by the spindle driver.
|
|
<br>\ref SpindleHAL_Active - get the enabled version available from gcode. Can be overriden by event handlers prior to activation.
|
|
\returns pointer to a \ref spindle_ptrs_t structure if successful, \a NULL if not.
|
|
|
|
__NOTE:__ do not modify the returned structure!
|
|
*/
|
|
spindle_ptrs_t *spindle_get_hal (spindle_id_t spindle_id, spindle_hal_t hal)
|
|
{
|
|
spindle_ptrs_t *spindle = NULL;
|
|
|
|
if(hal == SpindleHAL_Active) {
|
|
|
|
uint_fast8_t idx = N_SYS_SPINDLE;
|
|
|
|
do {
|
|
idx--;
|
|
if(sys_spindle[idx].hal.id == spindle_id && sys_spindle[idx].enabled)
|
|
spindle = &sys_spindle[idx].hal;
|
|
} while(idx && spindle == NULL);
|
|
|
|
} else if(spindle_id >= 0 && spindle_id < n_spindle && spindles[spindle_id].cfg)
|
|
spindle = hal == SpindleHAL_Raw ? (spindle_ptrs_t *)spindles[spindle_id].cfg : &spindles[spindle_id].hal;
|
|
|
|
return spindle;
|
|
}
|
|
|
|
/*! \brief Get the spindle id of the default spindle (spindle number 0).
|
|
\returns spindle id as a \ref spindle_id_t if successful, \a -2 if not (no spindle available).
|
|
*/
|
|
spindle_id_t spindle_get_default (void)
|
|
{
|
|
return sys_spindle[0].enabled ? sys_spindle[0].hal.id : -2;
|
|
}
|
|
|
|
/*! \brief Get the merged spindle capabilities of all registered spindles.
|
|
\param active true to return active capabilities, false to return default capabilities.
|
|
\returns capabilities in a \ref spindle_cap_t structure.
|
|
*/
|
|
spindle_cap_t spindle_get_caps (bool active)
|
|
{
|
|
spindle_cap_t caps = {0};
|
|
uint_fast8_t idx = n_spindle;
|
|
|
|
if(n_spindle) do {
|
|
--idx;
|
|
caps.value |= (active ? spindles[idx].hal.cap.value : spindles[idx].cfg->cap.value);
|
|
} while(idx);
|
|
|
|
return caps;
|
|
}
|
|
|
|
/*! \brief Get the registered name of a spindle.
|
|
\param spindle_id spindle id as a \ref spindle_id_t.
|
|
\returns pointer to a null terminated string if succesful, \a NULL if not.
|
|
*/
|
|
const char *spindle_get_name (spindle_id_t spindle_id)
|
|
{
|
|
return spindle_id >= 0 && spindle_id < n_spindle && spindles[spindle_id].cfg ? spindles[spindle_id].name : NULL;
|
|
}
|
|
|
|
/*! \brief Update the capabilities of a registered PWM spindle.
|
|
May be used by the driver on spindle initialization or when spindle settings has been changed.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param pwm_caps pointer to a \ref spindle_pwm_t structure.
|
|
*/
|
|
void spindle_update_caps (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_caps)
|
|
{
|
|
uint_fast8_t idx = N_SYS_SPINDLE;
|
|
|
|
spindle->type = pwm_caps ? SpindleType_PWM : SpindleType_Basic;
|
|
spindle->cap.laser = !!pwm_caps && !pwm_caps->flags.laser_mode_disable && !!spindle->update_pwm && settings.mode == Mode_Laser;
|
|
spindle->pwm_off_value = pwm_caps ? pwm_caps->off_value : 0;
|
|
|
|
do {
|
|
idx--;
|
|
if(sys_spindle[idx].enabled && spindle->id == sys_spindle[idx].hal.id) {
|
|
sys_spindle[idx].hal.type = spindle->type;
|
|
sys_spindle[idx].hal.cap.laser = spindle->cap.laser;
|
|
sys_spindle[idx].hal.rpm_min = spindle->rpm_min;
|
|
sys_spindle[idx].hal.rpm_max = spindle->rpm_max;
|
|
sys_spindle[idx].hal.pwm_off_value = spindle->pwm_off_value;
|
|
break;
|
|
}
|
|
} while(idx);
|
|
}
|
|
|
|
/*! \brief Get number of registered spindles.
|
|
\returns number of registered spindles.
|
|
*/
|
|
uint8_t spindle_get_count (void)
|
|
{
|
|
if(n_spindle == 0)
|
|
spindle_select(0);
|
|
|
|
return n_spindle == 1 && spindles[0].cfg->type == SpindleType_Null ? 0 : n_spindle;
|
|
}
|
|
|
|
bool spindle_get_id (uint8_t ref_id, spindle_id_t *spindle_id)
|
|
{
|
|
bool ok = false;
|
|
uint_fast8_t idx;
|
|
|
|
*spindle_id = 0;
|
|
|
|
for(idx = 0; idx < n_spindle; idx++) {
|
|
if((ok = spindles[idx].cfg->ref_id == ref_id)) {
|
|
*spindle_id = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
static spindle_num_t spindle_get_num (spindle_id_t spindle_id)
|
|
{
|
|
spindle_num_t spindle_num;
|
|
|
|
if((spindle_num = spindle_get_count() == 1 ? 0 : -1) == -1) {
|
|
|
|
const setting_detail_t *setting;
|
|
uint_fast8_t idx = N_SPINDLE_SELECTABLE;
|
|
|
|
do {
|
|
idx--;
|
|
if((setting = setting_get_details(idx == 0 ? Setting_SpindleType : (setting_id_t)(Setting_SpindleEnable0 + idx), NULL))) {
|
|
if(setting_get_int_value(setting, setting->flags.increment ? idx : 0) - (idx == 0 ? 0 : 1) == spindle_id)
|
|
spindle_num = idx;
|
|
}
|
|
} while(idx && spindle_num == -1);
|
|
}
|
|
|
|
return spindle_num;
|
|
}
|
|
|
|
void spindle_bind_encoder (const spindle_data_ptrs_t *encoder_data)
|
|
{
|
|
uint_fast8_t idx;
|
|
spindle_ptrs_t *spindle;
|
|
|
|
encoder = encoder_data;
|
|
|
|
for(idx = 0; idx < n_spindle; idx++) {
|
|
|
|
spindle = spindle_get(spindle_get_num(idx));
|
|
|
|
if(encoder_data && spindles[idx].hal.ref_id == settings.spindle.encoder_spindle) {
|
|
spindles[idx].hal.get_data = encoder_data->get;
|
|
spindles[idx].hal.reset_data = encoder_data->reset;
|
|
spindles[idx].hal.cap.at_speed = spindles[idx].hal.cap.variable;
|
|
} else {
|
|
spindles[idx].hal.get_data = spindles[idx].cfg->get_data;
|
|
spindles[idx].hal.reset_data = spindles[idx].cfg->reset_data;
|
|
spindles[idx].hal.cap.at_speed = spindles[idx].cfg->cap.at_speed;
|
|
}
|
|
|
|
if(spindle) {
|
|
spindle->get_data = spindles[idx].hal.get_data;
|
|
spindle->reset_data = spindles[idx].hal.reset_data;
|
|
spindle->cap.at_speed = spindles[idx].hal.cap.at_speed;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool spindle_set_at_speed_range (spindle_ptrs_t *spindle, spindle_data_t *spindle_data, float rpm)
|
|
{
|
|
spindle_data->rpm_programmed = rpm;
|
|
spindle_data->state_programmed.at_speed = false;
|
|
|
|
if((spindle_data->at_speed_enabled = spindle->at_speed_tolerance > 0.0f)) {
|
|
spindle_data->rpm_low_limit = rpm * (1.0f - (spindle->at_speed_tolerance / 100.0f));
|
|
spindle_data->rpm_high_limit = rpm * (1.0f + (spindle->at_speed_tolerance / 100.0f));
|
|
}
|
|
|
|
return spindle_data->at_speed_enabled;
|
|
}
|
|
|
|
/*! \brief Enumerate registered spindles by calling a callback function for each of them.
|
|
\param callback pointer to a \ref spindle_enumerate_callback_ptr type function.
|
|
\param data pointer to optional data to pass to the callback function.
|
|
\returns \a true if spindles are registered and a callback function was provided, \a false otherwise.
|
|
*/
|
|
bool spindle_enumerate_spindles (spindle_enumerate_callback_ptr callback, void *data)
|
|
{
|
|
if(callback == NULL || n_spindle == 0)
|
|
return false;
|
|
|
|
uint_fast8_t idx;
|
|
spindle_info_t spindle;
|
|
|
|
for(idx = 0; idx < n_spindle; idx++) {
|
|
|
|
spindle.id = idx;
|
|
spindle.ref_id = spindles[idx].cfg->ref_id;
|
|
spindle.name = spindles[idx].name;
|
|
spindle.num = spindle_get_num(idx);
|
|
spindle.enabled = spindle.num != -1;
|
|
#if N_SYS_SPINDLE == 1
|
|
spindle.hal = spindle.enabled && sys_spindle[0].hal.id == spindle.id ? &sys_spindle[0].hal : &spindles[idx].hal;
|
|
#else
|
|
spindle.hal = spindle.enabled && sys_spindle[spindle.num].hal.id == spindle.id ? &sys_spindle[spindle.num].hal : &spindles[idx].hal;
|
|
#endif
|
|
spindle.is_current = spindle.enabled && sys_spindle[0].hal.id == idx;
|
|
|
|
if(callback(&spindle, data))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// The following calls uses logical spindle numbers pointing into the sys_spindle array
|
|
// containing enabled spindles (spindle number as used by the $ gcode word)
|
|
|
|
/*! \brief Check if a spindle is enabled and available or not.
|
|
\param spindle_num spindle number as a \ref spindle_num_t.
|
|
\returns \a true if the spindle is enabled, \a false otherwise.
|
|
*/
|
|
bool spindle_is_enabled (spindle_num_t spindle_num)
|
|
{
|
|
if(spindle_num == -1)
|
|
spindle_num = 0;
|
|
|
|
return spindle_num >= 0 && spindle_num < N_SYS_SPINDLE && sys_spindle[spindle_num].enabled;
|
|
}
|
|
|
|
/*! \brief Get the handlers (function pointers) etc. associated with an enabled spindle.
|
|
\param spindle_num spindle number as a \ref spindle_num_t.
|
|
\returns pointer to a \ref spindle_ptrs_t structure if successful, \a NULL if not.
|
|
|
|
__NOTE:__ do not modify the returned structure!
|
|
*/
|
|
spindle_ptrs_t *spindle_get (spindle_num_t spindle_num)
|
|
{
|
|
return spindle_num >= 0 && spindle_num < N_SYS_SPINDLE && sys_spindle[spindle_num].enabled ? &sys_spindle[spindle_num].hal : NULL;
|
|
}
|
|
|
|
//
|
|
|
|
//
|
|
// Null (dummy) spindle, automatically installed if no spindles are registered.
|
|
//
|
|
|
|
static void null_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm)
|
|
{
|
|
UNUSED(spindle);
|
|
UNUSED(state);
|
|
UNUSED(rpm);
|
|
}
|
|
|
|
static spindle_state_t null_get_state (spindle_ptrs_t *spindle)
|
|
{
|
|
UNUSED(spindle);
|
|
|
|
return (spindle_state_t){0};
|
|
}
|
|
|
|
// Sets spindle speed
|
|
static void null_update_pwm (spindle_ptrs_t *spindle, uint_fast16_t pwm_value)
|
|
{
|
|
UNUSED(spindle);
|
|
UNUSED(pwm_value);
|
|
}
|
|
|
|
static uint_fast16_t null_get_pwm (spindle_ptrs_t *spindle, float rpm)
|
|
{
|
|
UNUSED(spindle);
|
|
UNUSED(rpm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void null_update_rpm (spindle_ptrs_t *spindle, float rpm)
|
|
{
|
|
UNUSED(spindle);
|
|
UNUSED(rpm);
|
|
}
|
|
|
|
#ifdef GRBL_ESP32
|
|
static void null_esp32_off (spindle_ptrs_t *spindle)
|
|
{
|
|
UNUSED(spindle);
|
|
}
|
|
#endif
|
|
|
|
/*! \brief Register a null spindle that has no connection to the outside world.
|
|
This is done automatically on startup if no spindle can be succesfully enabled.
|
|
\returns assigned spindle id as a \ref spindle_id_t if successful, \a -1 if not.
|
|
*/
|
|
spindle_id_t spindle_add_null (void)
|
|
{
|
|
static const spindle_ptrs_t spindle = {
|
|
.type = SpindleType_Null,
|
|
.cap.variable = Off,
|
|
.cap.at_speed = Off,
|
|
.cap.direction = Off,
|
|
.set_state = null_set_state,
|
|
#ifdef GRBL_ESP32
|
|
.esp32_off = null_esp32_off,
|
|
#endif
|
|
.get_state = null_get_state,
|
|
.get_pwm = null_get_pwm,
|
|
.update_pwm = null_update_pwm,
|
|
.update_rpm = null_update_rpm
|
|
};
|
|
|
|
bool registered = false;
|
|
uint_fast8_t idx = n_spindle;
|
|
|
|
if(idx) do {
|
|
if((registered = spindles[--idx].hal.type == SpindleType_Null))
|
|
break;
|
|
} while(idx);
|
|
|
|
if(!registered)
|
|
return spindle_register(&spindle, "NULL");
|
|
|
|
return idx;
|
|
}
|
|
|
|
// End null (dummy) spindle.
|
|
|
|
/*! \brief Set spindle speed override.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param speed_override override as a percentage of the programmed RPM.
|
|
\returns overridden RPM
|
|
__NOTE:__ Unlike motion overrides, spindle overrides do not require a planner reinitialization.
|
|
*/
|
|
float spindle_set_override (spindle_ptrs_t *spindle, override_t speed_override)
|
|
{
|
|
if(speed_override == DEFAULT_SPINDLE_RPM_OVERRIDE || !spindle->param->state.override_disable) {
|
|
|
|
speed_override = constrain(speed_override, MIN_SPINDLE_RPM_OVERRIDE, MAX_SPINDLE_RPM_OVERRIDE);
|
|
|
|
if((uint8_t)speed_override != spindle->param->override_pct) {
|
|
|
|
spindle_set_rpm(spindle, spindle->param->rpm, speed_override);
|
|
|
|
if(state_get() == STATE_IDLE) {
|
|
if(spindle->get_pwm && spindle->update_pwm)
|
|
spindle->update_pwm(spindle, spindle->get_pwm(spindle, spindle->param->rpm_overridden));
|
|
else if(spindle->update_rpm)
|
|
spindle->update_rpm(spindle, spindle->param->rpm_overridden);
|
|
} else
|
|
sys.step_control.update_spindle_rpm = On;
|
|
|
|
system_add_rt_report(Report_Overrides); // Set to report change immediately
|
|
|
|
// if(grbl.on_spindle_programmed)
|
|
// grbl.on_spindle_programmed(spindle, spindle->param->state, spindle->param->rpm, spindle->param->rpm_mode);
|
|
|
|
if(grbl.on_override_changed)
|
|
grbl.on_override_changed(OverrideChanged_SpindleRPM);
|
|
}
|
|
}
|
|
|
|
return spindle->param->rpm_overridden;
|
|
}
|
|
|
|
bool spindle_override_disable (spindle_ptrs_t *spindle, bool disable)
|
|
{
|
|
if(disable && !spindle->param->state.override_disable)
|
|
spindle_set_override(spindle, DEFAULT_SPINDLE_RPM_OVERRIDE);
|
|
|
|
spindle->param->state.override_disable = disable;
|
|
|
|
return disable;
|
|
}
|
|
|
|
/*! \brief Checks actual spindle state against given state.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param state a \ref spindle_state_t structure.
|
|
\returns \a true if on and ccw fields are equal, \a false otherwise.
|
|
*/
|
|
bool spindle_check_state (spindle_ptrs_t *spindle, spindle_state_t state)
|
|
{
|
|
static const spindle_state_t mask = {
|
|
.on = On,
|
|
.ccw = On
|
|
};
|
|
|
|
return (state.value & mask.value) == (spindle->get_state(spindle).value & mask.value);
|
|
}
|
|
|
|
/*! \internal \brief Immediately sets spindle running state with direction and spindle rpm, if enabled.
|
|
Called by g-code parser spindle_set_state_synced(), parking retract and restore, g-code program end,
|
|
sleep, and spindle stop override.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param state a \ref spindle_state_t structure.
|
|
\param rpm the spindle RPM to set.
|
|
\returns \a true if successful, \a false if the current controller state is \ref ABORTED.
|
|
*/
|
|
bool spindle_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm)
|
|
{
|
|
if (!ABORTED) { // Block during abort.
|
|
|
|
if (!state.on) { // Halt or set spindle direction and rpm.
|
|
rpm = 0.0f;
|
|
spindle->set_state(spindle, (spindle_state_t){0}, 0.0f);
|
|
} else {
|
|
// NOTE: Assumes all calls to this function is when grblHAL is not moving or must remain off.
|
|
// TODO: alarm/interlock if going from CW to CCW directly in non-laser mode?
|
|
if (spindle->cap.laser && state.ccw)
|
|
rpm = 0.0f; // TODO: May need to be rpm_min*(100/MAX_SPINDLE_RPM_OVERRIDE);
|
|
|
|
spindle->set_state(spindle, state, spindle_set_rpm(spindle, rpm, spindle->param->override_pct));
|
|
}
|
|
|
|
spindle->param->rpm = rpm;
|
|
spindle->param->state.on = state.on;
|
|
spindle->param->state.ccw = state.ccw;
|
|
|
|
system_add_rt_report(Report_Spindle); // Set to report change immediately
|
|
|
|
st_rpm_changed(rpm);
|
|
}
|
|
|
|
return !ABORTED;
|
|
}
|
|
|
|
/*! \brief If the spindle supports at speed functionality it will wait
|
|
for it to reach the speed and raise an alarm if the speed is not reached within the timeout period.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param state a \ref spindle_state_t structure.
|
|
\param rpm the spindle RPM to set.
|
|
\returns \a true if successful, \a false if the current controller state is \ref ABORTED.
|
|
*/
|
|
static bool spindle_set_state_wait (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, uint16_t delay_ms, delaymode_t delay_mode)
|
|
{
|
|
bool ok;
|
|
|
|
if(!(ok = state_get() == STATE_CHECK_MODE)) {
|
|
|
|
if((ok = spindle_set_state(spindle, state, rpm))) {
|
|
|
|
if(sys.override.control.spindle_wait_disable) {
|
|
sys.override.control.spindle_wait_disable = Off;
|
|
} else {
|
|
|
|
bool at_speed = !spindle->cap.at_speed || spindle->cap.torch || spindle->at_speed_tolerance <= 0.0f;
|
|
|
|
if(at_speed)
|
|
ok = delay_ms == 0 || spindle->cap.torch || delay_sec((float)delay_ms / 1000.0f, delay_mode);
|
|
else {
|
|
uint16_t delay = 0;
|
|
if(delay_ms == 0)
|
|
delay_ms = 60000; // one minute...
|
|
while(!(at_speed = spindle->get_state(spindle).at_speed)) {
|
|
if(!delay_sec(0.2f, delay_mode))
|
|
break;
|
|
delay += 200;
|
|
if(delay > delay_ms) {
|
|
gc_spindle_off();
|
|
system_raise_alarm(Alarm_Spindle);
|
|
break;
|
|
}
|
|
}
|
|
ok &= at_speed;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ok && grbl.on_spindle_at_speed)
|
|
grbl.on_spindle_at_speed(spindle, state);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*! \brief G-code parser entry-point for setting spindle state. Forces a planner buffer sync and bails
|
|
if an abort or check-mode is active. If the spindle supports at speed functionality it will wait
|
|
for it to reach the speed and raise an alarm if the speed is not reached within the timeout period.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param state a \ref spindle_state_t structure.
|
|
\param rpm the spindle RPM to set.
|
|
\returns \a true if successful, \a false if the current controller state is \ref ABORTED.
|
|
*/
|
|
bool spindle_set_state_synced (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, spindle_rpm_mode_t mode)
|
|
{
|
|
bool ok;
|
|
|
|
if((ok = protocol_buffer_synchronize())) { // Empty planner buffer to ensure spindle is set when programmed.
|
|
|
|
if(grbl.on_spindle_programmed)
|
|
grbl.on_spindle_programmed(spindle, state, rpm, mode);
|
|
|
|
ok = spindle_set_state_wait(spindle, state, rpm, state.on ? settings.spindle.on_delay : settings.spindle.off_delay, DelayMode_Dwell);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*! \brief Restore spindle running state with direction, enable, spindle RPM and appropriate delay.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param state a \ref spindle_state_t structure.
|
|
\param rpm the spindle RPM to set.
|
|
\returns \a true if successful, \a false if the current controller state is \ref ABORTED.
|
|
*/
|
|
bool spindle_restore (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, uint16_t delay_ms)
|
|
{
|
|
bool ok;
|
|
|
|
if(spindle->cap.laser) // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts.
|
|
ok = (sys.step_control.update_spindle_rpm = spindle_set_state(spindle, state, 0.0f));
|
|
else if(!(ok = spindle_check_state(spindle, state) && spindle->param->rpm == rpm))
|
|
ok = spindle_set_state_wait(spindle, state, rpm, delay_ms, DelayMode_SysSuspend);
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*! \brief Calculate and set programmed RPM according to override and max/min limits
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param rpm the programmed RPM.
|
|
\param override_pct override value in percent.
|
|
\returns the calulated RPM.
|
|
*/
|
|
float spindle_set_rpm (spindle_ptrs_t *spindle, float rpm, override_t override_pct)
|
|
{
|
|
if(override_pct != 100)
|
|
rpm *= 0.01f * (float)override_pct; // Scale RPM by override value.
|
|
|
|
rpm = rpm <= 0.0f ? 0.0f : constrain(rpm, spindle->rpm_min, spindle->rpm_max);
|
|
|
|
spindle->param->rpm_overridden = rpm;
|
|
spindle->param->override_pct = override_pct;
|
|
|
|
return spindle->param->rpm_overridden;
|
|
}
|
|
|
|
/*! \brief Turn off all enabled spindles.
|
|
*/
|
|
void spindle_all_off (void)
|
|
{
|
|
spindle_ptrs_t *spindle;
|
|
uint_fast8_t spindle_num = N_SYS_SPINDLE;
|
|
|
|
do {
|
|
if((spindle = spindle_get(--spindle_num))) {
|
|
spindle->param->rpm = spindle->param->rpm_overridden = 0.0f;
|
|
spindle->param->state.value = 0;
|
|
#ifdef GRBL_ESP32
|
|
spindle->esp32_off(spindle);
|
|
#else
|
|
spindle->set_state(spindle, (spindle_state_t){0}, 0.0f);
|
|
#endif
|
|
}
|
|
} while(spindle_num);
|
|
|
|
system_add_rt_report(Report_Spindle);
|
|
}
|
|
|
|
/*! \brief Check if any of the enabled spindles is running.
|
|
\returns \a true if a spindle is running, \a false otherwise.
|
|
*/
|
|
bool spindle_is_on (void)
|
|
{
|
|
bool on = false;
|
|
|
|
spindle_ptrs_t *spindle;
|
|
uint_fast8_t spindle_num = N_SYS_SPINDLE;
|
|
do {
|
|
if((spindle = spindle_get(--spindle_num)))
|
|
on = spindle->get_state(spindle).on;
|
|
} while(spindle_num && !on);
|
|
|
|
return on;
|
|
}
|
|
|
|
//
|
|
// The following functions are not called by the core, may be called by driver code.
|
|
//
|
|
|
|
/*! \brief calculate inverted pwm value if configured
|
|
\param pwm_data pointer t a \a spindle_pwm_t structure.
|
|
\param pwm_value non inverted PWM value.
|
|
\returns the inverted PWM value to use.
|
|
*/
|
|
static inline uint_fast16_t invert_pwm (spindle_pwm_t *pwm_data, uint_fast16_t pwm_value)
|
|
{
|
|
return pwm_data->flags.invert_pwm ? pwm_data->period - pwm_value : pwm_value;
|
|
}
|
|
|
|
/*! \brief Spindle RPM to PWM conversion.
|
|
\param pwm_data pointer t a \a spindle_pwm_t structure.
|
|
\param rpm spindle RPM.
|
|
\param pid_limit boolean, \a true if PID based spindle sync is used, \a false otherwise.
|
|
\returns the PWM value to use.
|
|
|
|
__NOTE:__ \a spindle_precompute_pwm_values() must be called to precompute values before this function is called.
|
|
Typically this is done by the spindle initialization code.
|
|
*/
|
|
static uint_fast16_t spindle_compute_pwm_value (spindle_pwm_t *pwm_data, float rpm, bool pid_limit)
|
|
{
|
|
uint_fast16_t pwm_value;
|
|
|
|
if(rpm > pwm_data->rpm_min) {
|
|
|
|
#if ENABLE_SPINDLE_LINEARIZATION
|
|
// Compute intermediate PWM value with linear spindle speed model via piecewise linear fit model.
|
|
uint_fast8_t idx = pwm_data->n_pieces;
|
|
|
|
if(idx) {
|
|
do {
|
|
idx--;
|
|
if(idx == 0 || rpm > pwm_data->piece[idx].rpm) {
|
|
pwm_value = floorf((pwm_data->piece[idx].start * rpm - pwm_data->piece[idx].end) * pwm_data->pwm_gradient);
|
|
break;
|
|
}
|
|
} while(idx);
|
|
} else
|
|
#endif
|
|
// Compute intermediate PWM value with linear spindle speed model.
|
|
pwm_value = (uint_fast16_t)floorf((rpm - pwm_data->rpm_min) * pwm_data->pwm_gradient) + pwm_data->min_value;
|
|
|
|
if(pwm_value >= (pid_limit ? pwm_data->period : pwm_data->max_value))
|
|
pwm_value = pid_limit ? pwm_data->period - 1 : pwm_data->max_value;
|
|
else if(pwm_value < pwm_data->min_value)
|
|
pwm_value = pwm_data->min_value;
|
|
|
|
if(pwm_data->flags.laser_off_overdrive) {
|
|
int32_t pwm_overdrive;
|
|
pwm_overdrive = (uint32_t)pwm_value + (uint32_t)((float)pwm_value * pwm_data->off_overdrive_pct) / 100.0f;
|
|
pwm_data->pwm_overdrive = invert_pwm(pwm_data, constrain((uint_fast16_t)pwm_overdrive, pwm_data->min_value, pwm_data->max_value));
|
|
}
|
|
|
|
pwm_value = invert_pwm(pwm_data, pwm_value);
|
|
|
|
} else
|
|
pwm_value = rpm == 0.0f ? pwm_data->off_value : invert_pwm(pwm_data, pwm_data->min_value);
|
|
|
|
return pwm_value;
|
|
}
|
|
|
|
/*! \internal \brief Dummy spindle RPM to PWM conversion, used if precompute fails.
|
|
\param pwm_data pointer t a \a spindle_pwm_t structure.
|
|
\param rpm spindle RPM.
|
|
\param pid_limit boolean, \a true if PID based spindle sync is used, \a false otherwise.
|
|
\returns the PWM value to use.
|
|
*/
|
|
static uint_fast16_t compute_dummy_pwm_value (spindle_pwm_t *pwm_data, float rpm, bool pid_limit)
|
|
{
|
|
return pwm_data->off_value;
|
|
}
|
|
|
|
static void set_laser_overdrive (struct spindle_pwm *pwm_data, float overdrive_pct)
|
|
{
|
|
pwm_data->flags.laser_off_overdrive = pwm_data->flags.rpm_controlled && (pwm_data->off_overdrive_pct = overdrive_pct) != 0.0f;
|
|
}
|
|
|
|
/*! \brief Precompute PWM values for faster conversion.
|
|
\param spindle pointer to a \ref spindle_ptrs_t structure.
|
|
\param pwm_data pointer to a \a spindle_pwm_t structure, to hold the precomputed values.
|
|
\param clock_hz timer clock frequency used for PWM generation.
|
|
\returns \a true if successful, \a false if no PWM range possible - driver should then revert to simple on/off spindle control.
|
|
*/
|
|
bool spindle_precompute_pwm_values (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_data, spindle_pwm_settings_t *settings, uint32_t clock_hz)
|
|
{
|
|
pwm_data->settings = settings;
|
|
pwm_data->off_value = pwm_data->pwm_overdrive = 0;
|
|
pwm_data->set_laser_overdrive = set_laser_overdrive;
|
|
|
|
spindle->rpm_min = pwm_data->rpm_min = settings->rpm_min;
|
|
spindle->rpm_max = settings->rpm_max;
|
|
spindle->at_speed_tolerance = settings->at_speed_tolerance;
|
|
spindle->cap.rpm_range_locked = On;
|
|
|
|
if((spindle->cap.variable = !settings->flags.pwm_disable && spindle->rpm_max > spindle->rpm_min)) {
|
|
pwm_data->f_clock = clock_hz;
|
|
pwm_data->period = (uint_fast16_t)((float)clock_hz / settings->pwm_freq);
|
|
pwm_data->flags.always_on = settings->pwm_off_value != 0.0f;
|
|
pwm_data->flags.invert_pwm = settings->invert.pwm & spindle->cap.pwm_invert;
|
|
pwm_data->flags.rpm_controlled = settings->flags.enable_rpm_controlled;
|
|
pwm_data->flags.laser_mode_disable = settings->flags.laser_mode_disable;
|
|
if(settings->pwm_off_value == 0.0f)
|
|
pwm_data->off_value = pwm_data->flags.invert_pwm ? pwm_data->period - pwm_data->offset : 0;
|
|
else
|
|
pwm_data->off_value = invert_pwm(pwm_data, (uint_fast16_t)(pwm_data->period * settings->pwm_off_value / 100.0f));
|
|
pwm_data->max_value = (uint_fast16_t)(pwm_data->period * settings->pwm_max_value / 100.0f) + pwm_data->offset;
|
|
if((pwm_data->min_value = (uint_fast16_t)(pwm_data->period * settings->pwm_min_value / 100.0f)) == 0 && spindle->rpm_min > 0.0f)
|
|
pwm_data->min_value = (uint_fast16_t)((float)pwm_data->max_value * 0.004f);
|
|
|
|
pwm_data->pwm_gradient = (float)(pwm_data->max_value - pwm_data->min_value) / (spindle->rpm_max - spindle->rpm_min);
|
|
pwm_data->compute_value = spindle_compute_pwm_value;
|
|
} else {
|
|
pwm_data->flags.value &= ((spindle_pwm_flags_t){ .cloned = pwm_data->flags.cloned }).value;
|
|
pwm_data->compute_value = compute_dummy_pwm_value;
|
|
}
|
|
|
|
spindle->context.pwm = pwm_data;
|
|
|
|
#if ENABLE_SPINDLE_LINEARIZATION
|
|
uint_fast8_t idx;
|
|
|
|
pwm_data->n_pieces = 0;
|
|
|
|
for(idx = 0; idx < SPINDLE_NPWM_PIECES; idx++) {
|
|
if(!isnan(settings->pwm_piece[idx].rpm) && settings->pwm_piece[idx].start != 0.0f)
|
|
memcpy(&pwm_data->piece[pwm_data->n_pieces++], &settings->pwm_piece[idx], sizeof(pwm_piece_t));
|
|
}
|
|
|
|
spindle->cap.pwm_linearization = pwm_data->n_pieces > 0;
|
|
#endif
|
|
|
|
return spindle->cap.variable;
|
|
}
|
|
|
|
#if N_SPINDLE > 1
|
|
|
|
#include "grbl/nvs_buffer.h"
|
|
|
|
static spindle1_pwm_settings_t sp1_settings;
|
|
static uint32_t nvs_address;
|
|
static char spindle_signals[] = "Spindle enable,Spindle direction,PWM";
|
|
static bool ports_ok = false;
|
|
static char max_aport[4], max_dport[4];
|
|
static spindle_cap_t spindle_cap;
|
|
static settings_changed_ptr on_settings_changed;
|
|
static spindle1_settings_changed_ptr on_spindle1_settings_changed;
|
|
|
|
#if ENABLE_SPINDLE_LINEARIZATION
|
|
|
|
static status_code_t set_linear_piece (setting_id_t id, char *svalue)
|
|
{
|
|
uint32_t idx = id - Setting_LinearSpindle1Piece1;
|
|
float rpm, start, end;
|
|
|
|
if(*svalue == '\0' || (svalue[0] == '0' && svalue[1] == '\0')) {
|
|
sp1_settings.cfg.pwm_piece[idx].rpm = NAN;
|
|
sp1_settings.cfg.pwm_piece[idx].start =
|
|
sp1_settings.cfg.pwm_piece[idx].end = 0.0f;
|
|
} else if(sscanf(svalue, "%f,%f,%f", &rpm, &start, &end) == 3) {
|
|
sp1_settings.cfg.pwm_piece[idx].rpm = rpm;
|
|
sp1_settings.cfg.pwm_piece[idx].start = start;
|
|
sp1_settings.cfg.pwm_piece[idx].end = end;
|
|
//?? if(idx == 0)
|
|
// sp1_settings.cfg.rpm_min = rpm;
|
|
} else
|
|
return Status_SettingValueOutOfRange;
|
|
|
|
return Status_OK;
|
|
}
|
|
|
|
static char *get_linear_piece (setting_id_t id)
|
|
{
|
|
static char buf[40];
|
|
|
|
uint32_t idx = id - Setting_LinearSpindle1Piece1;
|
|
|
|
if(isnan(sp1_settings.cfg.pwm_piece[idx].rpm))
|
|
*buf = '\0';
|
|
else
|
|
snprintf(buf, sizeof(buf), "%g,%g,%g", sp1_settings.cfg.pwm_piece[idx].rpm, sp1_settings.cfg.pwm_piece[idx].start, sp1_settings.cfg.pwm_piece[idx].end);
|
|
|
|
return buf;
|
|
}
|
|
|
|
#endif
|
|
|
|
static status_code_t set_spindle_invert (setting_id_t id, uint_fast16_t int_value)
|
|
{
|
|
sp1_settings.cfg.invert.mask = int_value;
|
|
if(sp1_settings.cfg.invert.pwm && !spindle_cap.pwm_invert) {
|
|
sp1_settings.cfg.invert.pwm = Off;
|
|
return Status_SettingDisabled;
|
|
}
|
|
|
|
return Status_OK;
|
|
}
|
|
|
|
static status_code_t set_pwm_options (setting_id_t id, uint_fast16_t int_value)
|
|
{
|
|
if(int_value & 0x001) {
|
|
if(int_value > 0b111)
|
|
return Status_SettingValueOutOfRange;
|
|
sp1_settings.cfg.flags.pwm_disable = Off;
|
|
sp1_settings.cfg.flags.enable_rpm_controlled = !!(int_value & 0b010);
|
|
sp1_settings.cfg.flags.laser_mode_disable = !!(int_value & 0b100);
|
|
} else {
|
|
sp1_settings.cfg.flags.pwm_disable = On;
|
|
sp1_settings.cfg.flags.enable_rpm_controlled = sp1_settings.cfg.flags.laser_mode_disable = Off;
|
|
}
|
|
|
|
return Status_OK;
|
|
}
|
|
|
|
static uint32_t get_int (setting_id_t id)
|
|
{
|
|
uint32_t value = 0;
|
|
|
|
switch(id) {
|
|
|
|
case Setting_SpindlePWMOptions1:
|
|
value = sp1_settings.cfg.flags.pwm_disable
|
|
? 0
|
|
: (0b001 |
|
|
(sp1_settings.cfg.flags.enable_rpm_controlled ? 0b010 : 0) |
|
|
(sp1_settings.cfg.flags.laser_mode_disable ? 0b100 : 0));
|
|
break;
|
|
|
|
case Setting_SpindleInvertMask1:
|
|
value = sp1_settings.cfg.invert.value;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static float get_port (setting_id_t id)
|
|
{
|
|
uint8_t port;
|
|
|
|
switch(id) {
|
|
|
|
case Setting_Spindle_OnPort:
|
|
port = sp1_settings.port_on;
|
|
break;
|
|
|
|
case Setting_Spindle_DirPort:
|
|
port = sp1_settings.port_dir;
|
|
break;
|
|
|
|
default: // Setting_Spindle_PWMPort:
|
|
port = sp1_settings.port_pwm;
|
|
break;
|
|
}
|
|
|
|
return port == IOPORT_UNASSIGNED ? -1.0f : (float)port;
|
|
}
|
|
|
|
bool pwm_port_validate (xbar_t *properties, uint8_t port, void *data)
|
|
{
|
|
return port == *(uint8_t *)data;
|
|
}
|
|
|
|
static status_code_t set_port (setting_id_t id, float value)
|
|
{
|
|
bool ok = true;
|
|
uint8_t port = value < 0.0f ? IOPORT_UNASSIGNED : (uint8_t)value;
|
|
|
|
switch(id) {
|
|
|
|
case Setting_Spindle_OnPort:
|
|
sp1_settings.port_on = port;
|
|
break;
|
|
|
|
case Setting_Spindle_DirPort:
|
|
sp1_settings.port_dir = port;
|
|
break;
|
|
|
|
default: // Setting_Spindle_PWMPort:
|
|
if((ok = port == sp1_settings.port_pwm ||
|
|
ioports_enumerate(Port_Analog, Port_Output, (pin_cap_t){ .claimable = On, .pwm = On }, pwm_port_validate, &port)))
|
|
sp1_settings.port_pwm = port;
|
|
break;
|
|
}
|
|
|
|
return ok ? Status_OK : Status_SettingValueOutOfRange;
|
|
}
|
|
|
|
static bool has_pwm (const setting_detail_t *setting, uint_fast16_t offset)
|
|
{
|
|
return spindle_cap.variable;
|
|
}
|
|
|
|
static bool has_freq (const setting_detail_t *setting, uint_fast16_t offset)
|
|
{
|
|
return spindle_cap.variable && !spindle_cap.cloned;
|
|
}
|
|
|
|
static bool has_ports (const setting_detail_t *setting, uint_fast16_t offset)
|
|
{
|
|
return ports_ok;
|
|
}
|
|
|
|
static const setting_detail_t spindle1_settings[] = {
|
|
{ Setting_Spindle_OnPort, Group_AuxPorts, "PWM2 spindle on port", NULL, Format_Decimal, "-#0", "0", max_dport, Setting_NonCoreFn, set_port, get_port, has_ports, { .reboot_required = On } },
|
|
{ Setting_Spindle_DirPort, Group_AuxPorts, "PWM2 spindle direction port", NULL, Format_Decimal, "-#0", "-1", max_dport, Setting_NonCoreFn, set_port, get_port, has_ports, { .reboot_required = On } },
|
|
{ Setting_SpindleInvertMask1, Group_Spindle, "PWM2 spindle signals invert", NULL, Format_Bitfield, spindle_signals, NULL, NULL, Setting_IsExtendedFn, set_spindle_invert, get_int, NULL, { .reboot_required = On } },
|
|
{ Setting_Spindle_PWMPort, Group_AuxPorts, "PWM2 spindle PWM port", NULL, Format_Decimal, "-#0", "0", max_aport, Setting_NonCoreFn, set_port, get_port, has_ports, { .reboot_required = On } },
|
|
{ Setting_SpindlePWMOptions1, Group_Spindle, "PWM2 spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability", NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, has_pwm },
|
|
{ Setting_RpmMax1, Group_Spindle, "PWM2 spindle max speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_max, NULL, has_pwm },
|
|
{ Setting_RpmMin1, Group_Spindle, "PWM2 spindle min speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_min, NULL, has_pwm },
|
|
{ Setting_PWMFreq1, Group_Spindle, "PWM2 spindle PWM frequency", "Hz", Format_Decimal, "#####0", NULL, NULL, Setting_IsExtended, &sp1_settings.cfg.pwm_freq, NULL, has_freq },
|
|
{ Setting_PWMOffValue1, Group_Spindle, "PWM2 spindle PWM off value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &sp1_settings.cfg.pwm_off_value, NULL, has_pwm },
|
|
{ Setting_PWMMinValue1, Group_Spindle, "PWM2 spindle PWM min value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &sp1_settings.cfg.pwm_min_value, NULL, has_pwm },
|
|
{ Setting_PWMMaxValue1, Group_Spindle, "PWM2 spindle PWM max value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &sp1_settings.cfg.pwm_max_value, NULL, has_pwm }
|
|
#if xENABLE_SPINDLE_LINEARIZATION
|
|
{ Setting_LinearSpindle1Piece1, Group_Spindle, "PWM2 spindle linearisation, 1st point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
|
|
#if SPINDLE_NPWM_PIECES > 1
|
|
{ Setting_LinearSpindle1Piece2, Group_Spindle, "PWM2 spindle linearisation, 2nd point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 2
|
|
{ Setting_LinearSpindle1Piece3, Group_Spindle, "PWM2 spindle linearisation, 3rd point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 3
|
|
{ Setting_LinearSpindle1Piece4, Group_Spindle, "PWM2 spindle linearisation, 4th point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL },
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
static const setting_descr_t spindle1_settings_descr[] = {
|
|
{ Setting_Spindle_OnPort, "On/off aux port." },
|
|
{ Setting_Spindle_DirPort, "Direction aux port, set to -1 if not required." },
|
|
{ Setting_SpindleInvertMask1, "Inverts the spindle on, counterclockwise and PWM signals (active low)." },
|
|
{ Setting_Spindle_PWMPort, "Spindle analog aux port. Must be PWM capable!" },
|
|
{ Setting_SpindlePWMOptions1, "Enable controls PWM output availability.\\n"
|
|
"When `RPM controls spindle enable signal` is checked and M3 or M4 is active S0 switches it off and S > 0 switches it on."
|
|
},
|
|
{ Setting_RpmMax1, "Maximum spindle speed." },
|
|
{ Setting_RpmMin1, "Minimum spindle speed." },
|
|
{ Setting_PWMFreq1, "PWM frequency." },
|
|
{ Setting_PWMOffValue1, "PWM off value in percent (duty cycle)." },
|
|
{ Setting_PWMMinValue1, "PWM min value in percent (duty cycle)." },
|
|
{ Setting_PWMMaxValue1, "PWM max value in percent (duty cycle)." }
|
|
#if xENABLE_SPINDLE_LINEARIZATION
|
|
{ Setting_LinearSpindle1Piece1, "Comma separated list of values: RPM_MIN, RPM_LINE_A1, RPM_LINE_B1, set to blank to disable." },
|
|
#if SPINDLE_NPWM_PIECES > 1
|
|
{ Setting_LinearSpindle1Piece2, "Comma separated list of values: RPM_POINT12, RPM_LINE_A2, RPM_LINE_B2, set to blank to disable." },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 2
|
|
{ Setting_LinearSpindle1Piece3, "Comma separated list of values: RPM_POINT23, RPM_LINE_A3, RPM_LINE_B3, set to blank to disable." },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 3
|
|
{ Setting_LinearSpindle1Piece4, "Comma separated list of values: RPM_POINT34, RPM_LINE_A4, RPM_LINE_B4, set to blank to disable." },
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
static void spindle1_settings_changed (settings_t *settings, settings_changed_flags_t changed)
|
|
{
|
|
UNUSED(changed);
|
|
|
|
if(on_spindle1_settings_changed)
|
|
on_spindle1_settings_changed(&sp1_settings);
|
|
}
|
|
|
|
static void onSettingsChanged (settings_t *settings, settings_changed_flags_t changed)
|
|
{
|
|
if(on_settings_changed)
|
|
on_settings_changed(settings, changed);
|
|
|
|
if(changed.spindle)
|
|
spindle1_settings_changed(settings, changed);
|
|
}
|
|
|
|
static void spindle1_settings_save (void)
|
|
{
|
|
hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&sp1_settings, sizeof(spindle1_pwm_settings_t), true);
|
|
}
|
|
|
|
static void spindle1_settings_restore (void)
|
|
{
|
|
static const spindle_pwm_settings_t defaults = {
|
|
.rpm_max = DEFAULT_SPINDLE1_RPM_MAX,
|
|
.rpm_min = DEFAULT_SPINDLE1_RPM_MIN,
|
|
.flags.pwm_disable = false,
|
|
.flags.enable_rpm_controlled = 0, //DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED, TODO: add setting?
|
|
.flags.laser_mode_disable = 0, // TODO: add setting? Not possible?
|
|
.invert.on = DEFAULT_INVERT_SPINDLE1_ENABLE_PIN,
|
|
.invert.ccw = DEFAULT_INVERT_SPINDLE1_CCW_PIN,
|
|
.invert.pwm = DEFAULT_INVERT_SPINDLE1_PWM_PIN,
|
|
.pwm_freq = DEFAULT_SPINDLE1_PWM_FREQ,
|
|
.pwm_off_value = DEFAULT_SPINDLE1_PWM_OFF_VALUE,
|
|
.pwm_min_value = DEFAULT_SPINDLE1_PWM_MIN_VALUE,
|
|
.pwm_max_value = DEFAULT_SPINDLE1_PWM_MAX_VALUE,
|
|
.at_speed_tolerance = DEFAULT_SPINDLE_AT_SPEED_TOLERANCE,
|
|
#if ENABLE_SPINDLE_LINEARIZATION
|
|
#if SPINDLE_NPWM_PIECES > 0
|
|
.pwm_piece[0] = { .rpm = DEFAULT_RPM_POINT01, .start = DEFAULT_RPM_LINE_A1, .end = DEFAULT_RPM_LINE_B1 },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 1
|
|
.pwm_piece[1] = { .rpm = DEFAULT_RPM_POINT12, .start = DEFAULT_RPM_LINE_A2, .end = DEFAULT_RPM_LINE_B2 },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 2
|
|
.pwm_piece[2] = { .rpm = DEFAULT_RPM_POINT23, .start = DEFAULT_RPM_LINE_A3, .end = DEFAULT_RPM_LINE_B3 },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 3
|
|
.pwm_piece[3] = { .rpm = DEFAULT_RPM_POINT34, .start = DEFAULT_RPM_LINE_A4, .end = DEFAULT_RPM_LINE_B4 },
|
|
#endif
|
|
#else
|
|
#if SPINDLE_NPWM_PIECES > 0
|
|
.pwm_piece[0] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 1
|
|
.pwm_piece[1] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 2
|
|
.pwm_piece[2] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
|
|
#endif
|
|
#if SPINDLE_NPWM_PIECES > 3
|
|
.pwm_piece[3] = { .rpm = NAN, .start = 0.0f, .end = 0.0f },
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
memcpy(&sp1_settings.cfg, &defaults, sizeof(spindle_pwm_settings_t));
|
|
|
|
sp1_settings.port_pwm = ioport_find_free(Port_Analog, Port_Output, (pin_cap_t){.claimable = On, .pwm = On }, NULL);
|
|
sp1_settings.port_on = ioport_find_free(Port_Digital, Port_Output, (pin_cap_t){ .claimable = On }, NULL);
|
|
sp1_settings.port_dir = sp1_settings.port_on != IOPORT_UNASSIGNED && sp1_settings.port_on > 0 ? sp1_settings.port_on - 1 : IOPORT_UNASSIGNED;
|
|
|
|
hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&sp1_settings, sizeof(spindle1_pwm_settings_t), true);
|
|
}
|
|
|
|
static void spindle1_settings_load (void)
|
|
{
|
|
if((hal.nvs.memcpy_from_nvs((uint8_t *)&sp1_settings, nvs_address, sizeof(spindle1_pwm_settings_t), true) != NVS_TransferResult_OK))
|
|
spindle1_settings_restore();
|
|
}
|
|
|
|
spindle1_pwm_settings_t *spindle1_settings_add (bool claim_ports)
|
|
{
|
|
uint8_t a_out = IOPORT_UNASSIGNED;
|
|
|
|
if((ports_ok = claim_ports &&
|
|
ioports_available(Port_Digital, Port_Output) &&
|
|
(a_out = ioport_find_free(Port_Analog, Port_Output, (pin_cap_t){ .claimable = On, .pwm = On }, NULL)) != IOPORT_UNASSIGNED)) {
|
|
|
|
strcpy(max_aport, uitoa(a_out));
|
|
strcpy(max_dport, uitoa(ioport_find_free(Port_Digital, Port_Output, (pin_cap_t){ .claimable = On }, NULL)));
|
|
}
|
|
|
|
return nvs_address == 0 && (!claim_ports || ports_ok) && (nvs_address = nvs_alloc(sizeof(spindle1_pwm_settings_t))) ? &sp1_settings : NULL;
|
|
}
|
|
|
|
void spindle1_settings_register (spindle_cap_t cap, spindle1_settings_changed_ptr on_changed)
|
|
{
|
|
static setting_details_t spindle1_setting_details = {
|
|
.is_core = true,
|
|
.settings = spindle1_settings,
|
|
.n_settings = sizeof(spindle1_settings) / sizeof(setting_detail_t),
|
|
.descriptions = spindle1_settings_descr,
|
|
.n_descriptions = sizeof(spindle1_settings_descr) / sizeof(setting_descr_t),
|
|
.load = spindle1_settings_load,
|
|
.restore = spindle1_settings_restore,
|
|
.save = spindle1_settings_save,
|
|
.on_changed = spindle1_settings_changed
|
|
};
|
|
|
|
if((on_spindle1_settings_changed = on_changed)) {
|
|
on_settings_changed = grbl.on_settings_changed;
|
|
grbl.on_settings_changed = onSettingsChanged;
|
|
}
|
|
|
|
settings_register(&spindle1_setting_details);
|
|
|
|
spindle_cap = cap;
|
|
|
|
spindle_state_t spindle_state = { .on = On };
|
|
spindle_state.ccw = cap.direction;
|
|
spindle_state.pwm = cap.pwm_invert;
|
|
|
|
setting_remove_elements(Setting_SpindleInvertMask1, spindle_state.mask);
|
|
}
|
|
|
|
#endif // N_SPINDLE > 1
|