Add servo mode support for spindle PWM control
- Implemented USE_SPINDLE_SERVO_MODE configuration option - Changed Timer2 prescaler from 1/8 to 1/1024 for ~50Hz servo frequency - Added configurable servo pulse width range (SPINDLE_SERVO_MIN_PULSE/MAX_PULSE) - M3 S0-S1000 now controls servo position (0-180 degrees) - M5 positions servo to maximum position (pen up) instead of disabling PWM - Default configuration supports pen plotter with reversed pulse values (MIN=31, MAX=16) - Only supports ATmega328p (Arduino Uno) processors Based on: https://www.buildlog.net/blog/2017/08/using-grbls-spindle-pwm-to-control-a-servo/
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,5 +3,6 @@
|
||||
*.elf
|
||||
*.DS_Store
|
||||
*.d
|
||||
|
||||
.pio/
|
||||
.vscode/
|
||||
README.md
|
||||
|
||||
51
debug.md
Normal file
51
debug.md
Normal file
@@ -0,0 +1,51 @@
|
||||
this is my curennt grbl state
|
||||
|
||||
when homing the first limit switch encoutnered always retrun from homing.
|
||||
it can be either the x or y
|
||||
how to fix
|
||||
```
|
||||
*** Connecting to jserialcomm://ttyUSB0:115200
|
||||
*** Fetching device status
|
||||
>>> ?
|
||||
<Alarm,MPos:0.000,0.000,0.000,WPos:1.002,-209.000,0.000>
|
||||
*** Fetching device version
|
||||
*** Fetching device settings
|
||||
>>> $$
|
||||
$0 = 10 (step pulse, usec)
|
||||
$1 = 25 (step idle delay, msec)
|
||||
$2 = 0 (step port invert mask:00000000)
|
||||
$3 = 2 (dir port invert mask:00000010)
|
||||
$4 = 0 (step enable invert, bool)
|
||||
$5 = 0 (limit pins invert, bool)
|
||||
$6 = 0 (probe pin invert, bool)
|
||||
$10 = 3 (status report mask:00000011)
|
||||
$11 = 0.010 (junction deviation, mm)
|
||||
$12 = 0.002 (arc tolerance, mm)
|
||||
$13 = 0 (report inches, bool)
|
||||
$20 = 1 (soft limits, bool)
|
||||
$21 = 0 (hard limits, bool)
|
||||
$22 = 1 (homing cycle, bool)
|
||||
$23 = 6 (homing dir invert mask:00000110)
|
||||
$24 = 25.000 (homing feed, mm/min)
|
||||
$25 = 500.000 (homing seek, mm/min)
|
||||
$26 = 250 (homing debounce, msec)
|
||||
$27 = 1.000 (homing pull-off, mm)
|
||||
$100 = 113.821 (x, step/mm)
|
||||
$101 = 100.000 (y, step/mm)
|
||||
$102 = 100.000 (z, step/mm)
|
||||
$110 = 8000.000 (x max rate, mm/min)
|
||||
$111 = 8000.000 (y max rate, mm/min)
|
||||
$112 = 8000.000 (z max rate, mm/min)
|
||||
$120 = 1200.000 (x accel, mm/sec^2)
|
||||
$121 = 1200.000 (y accel, mm/sec^2)
|
||||
$122 = 3800.000 (z accel, mm/sec^2)
|
||||
$130 = 310.000 (x max travel, mm)
|
||||
$131 = 210.000 (y max travel, mm)
|
||||
$132 = 100.000 (z max travel, mm)
|
||||
ok
|
||||
*** Fetching device state
|
||||
*** Connected to GRBL 0.9i
|
||||
>>> $G
|
||||
[G0 G54 G17 G21 G90 G94 M0 M5 M9 T0 F0. S0.]
|
||||
ok
|
||||
```
|
||||
@@ -73,8 +73,10 @@
|
||||
// will not be affected by pin sharing.
|
||||
// NOTE: Defaults are set for a traditional 3-axis CNC machine. Z-axis first to clear, followed by X & Y.
|
||||
#define HOMING_CYCLE_0 (1<<Z_AXIS) // REQUIRED: First move Z to clear workspace.
|
||||
#define HOMING_CYCLE_1 ((1<<X_AXIS)|(1<<Y_AXIS)) // OPTIONAL: Then move X,Y at the same time.
|
||||
// #define HOMING_CYCLE_1 ((1<<X_AXIS)|(1<<Y_AXIS)) // OPTIONAL: Then move X,Y at the same time.
|
||||
// #define HOMING_CYCLE_2 // OPTIONAL: Uncomment and add axes mask to enable
|
||||
#define HOMING_CYCLE_0 (1<<X_AXIS)
|
||||
#define HOMING_CYCLE_1 (1<<Y_AXIS)
|
||||
|
||||
// Number of homing cycles performed after when the machine initially jogs to limit switches.
|
||||
// This help in preventing overshoot and should improve repeatability. This value should be one or
|
||||
@@ -149,7 +151,7 @@
|
||||
// defined at (http://corexy.com/theory.html). Motors are assumed to positioned and wired exactly as
|
||||
// described, if not, motions may move in strange directions. Grbl requires the CoreXY A and B motors
|
||||
// have the same steps per mm internally.
|
||||
// #define COREXY // Default disabled. Uncomment to enable.
|
||||
#define COREXY // Default disabled. Uncomment to enable.
|
||||
|
||||
// Inverts pin logic of the control command pins. This essentially means when this option is enabled
|
||||
// you can use normally-closed switches, rather than the default normally-open switches.
|
||||
@@ -251,7 +253,7 @@
|
||||
// equally divided voltage bins between the maximum and minimum spindle speeds. So for a 5V pin, 1000
|
||||
// max rpm, and 250 min rpm, the spindle output voltage would be set for the following "S" commands:
|
||||
// "S1000" @ 5V, "S250" @ 0.02V, and "S625" @ 2.5V (mid-range). The pin outputs 0V when disabled.
|
||||
#define SPINDLE_MAX_RPM 1000.0 // Max spindle RPM. This value is equal to 100% duty cycle on the PWM.
|
||||
#define SPINDLE_MAX_RPM 1200.0 // Max spindle RPM. This value is equal to 100% duty cycle on the PWM.
|
||||
#define SPINDLE_MIN_RPM 0.0 // Min spindle RPM. This value is equal to (1/256) duty cycle on the PWM.
|
||||
|
||||
// Used by variable spindle output only. This forces the PWM output to a minimum duty cycle when enabled.
|
||||
@@ -260,6 +262,23 @@
|
||||
// spindle RPM output lower than this value will be set to this value.
|
||||
// #define MINIMUM_SPINDLE_PWM 5 // Default disabled. Uncomment to enable. Integer (0-255)
|
||||
|
||||
// Enable servo mode for spindle PWM output. This changes the PWM frequency from ~1kHz to ~50Hz
|
||||
// and adjusts the duty cycle range to control hobby servos (1-2ms pulses). When enabled, the
|
||||
// spindle RPM values are mapped to servo positions (S0-S180 or configured min/max).
|
||||
// This is useful for pen plotters, drag knives, or other applications requiring servo control.
|
||||
// Based on: https://www.buildlog.net/blog/2017/08/using-grbls-spindle-pwm-to-control-a-servo/
|
||||
// NOTE: Only works with VARIABLE_SPINDLE enabled and ATmega328p (Uno) processors.
|
||||
// NOTE: This changes Timer2 prescaler from 1/8 to 1/1024, resulting in ~61Hz PWM frequency.
|
||||
// NOTE: Connect servo signal wire to Arduino Pin D11 (same as spindle PWM output).
|
||||
// Servo power (5V) and ground must be connected to appropriate power source.
|
||||
#define USE_SPINDLE_SERVO_MODE // Default disabled. Uncomment to enable.
|
||||
|
||||
// Servo pulse width range (in timer ticks). Only used with USE_SPINDLE_SERVO_MODE enabled.
|
||||
// At 1/1024 prescaler on 16MHz, each tick = 64μs. Default values: 16 ticks = 1.024ms, 31 ticks = 1.984ms
|
||||
// Adjust these if your servo requires different pulse widths for 0° and 180° positions.
|
||||
#define SPINDLE_SERVO_MIN_PULSE 31 // Servo position at minimum RPM (S0) - pen DOWN - ~2ms pulse
|
||||
#define SPINDLE_SERVO_MAX_PULSE 16 // Servo position at maximum RPM (S1000/M5) - pen UP - ~1ms pulse
|
||||
|
||||
// By default on a 328p(Uno), Grbl combines the variable spindle PWM and the enable into one pin to help
|
||||
// preserve I/O pins. For certain setups, these may need to be separate pins. This configure option uses
|
||||
// the spindle direction pin(D13) as a separate spindle enable pin along with spindle speed PWM on pin D11.
|
||||
@@ -407,6 +426,18 @@
|
||||
#error "USE_SPINDLE_DIR_AS_ENABLE_PIN may only be used with a 328p processor"
|
||||
#endif
|
||||
|
||||
#if defined(USE_SPINDLE_SERVO_MODE) && !defined(VARIABLE_SPINDLE)
|
||||
#error "USE_SPINDLE_SERVO_MODE may only be used with VARIABLE_SPINDLE enabled"
|
||||
#endif
|
||||
|
||||
#if defined(USE_SPINDLE_SERVO_MODE) && !defined(CPU_MAP_ATMEGA328P)
|
||||
#error "USE_SPINDLE_SERVO_MODE may only be used with a 328p processor"
|
||||
#endif
|
||||
|
||||
#if defined(USE_SPINDLE_SERVO_MODE) && (SPINDLE_SERVO_MIN_PULSE == SPINDLE_SERVO_MAX_PULSE)
|
||||
#error "SPINDLE_SERVO_MIN_PULSE and SPINDLE_SERVO_MAX_PULSE cannot be equal"
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
@@ -45,22 +45,38 @@ void spindle_init()
|
||||
|
||||
void spindle_stop()
|
||||
{
|
||||
// On the Uno, spindle enable and PWM are shared. Other CPUs have seperate enable pin.
|
||||
#ifdef VARIABLE_SPINDLE
|
||||
TCCRA_REGISTER &= ~(1<<COMB_BIT); // Disable PWM. Output voltage is zero.
|
||||
#ifdef USE_SPINDLE_SERVO_MODE
|
||||
// Servo mode: M5 moves servo to maximum position (e.g., pen up) instead of disabling PWM
|
||||
// This keeps the servo powered and in position
|
||||
TCCRA_REGISTER = (1<<COMB_BIT) | (1<<WAVE1_REGISTER) | (1<<WAVE0_REGISTER);
|
||||
TCCRB_REGISTER = (TCCRB_REGISTER & 0b11111000) | 0x07; // 1/1024 prescaler
|
||||
OCR_REGISTER = SPINDLE_SERVO_MAX_PULSE; // Set to maximum position
|
||||
|
||||
#if defined(CPU_MAP_ATMEGA2560) || defined(USE_SPINDLE_DIR_AS_ENABLE_PIN)
|
||||
#ifdef INVERT_SPINDLE_ENABLE_PIN
|
||||
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT);
|
||||
#else
|
||||
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT);
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
// Standard spindle mode: Disable PWM output
|
||||
#ifdef VARIABLE_SPINDLE
|
||||
TCCRA_REGISTER &= ~(1<<COMB_BIT); // Disable PWM. Output voltage is zero.
|
||||
#if defined(CPU_MAP_ATMEGA2560) || defined(USE_SPINDLE_DIR_AS_ENABLE_PIN)
|
||||
#ifdef INVERT_SPINDLE_ENABLE_PIN
|
||||
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT); // Set pin to high
|
||||
#else
|
||||
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT); // Set pin to low
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef INVERT_SPINDLE_ENABLE_PIN
|
||||
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT); // Set pin to high
|
||||
#else
|
||||
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT); // Set pin to low
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#ifdef INVERT_SPINDLE_ENABLE_PIN
|
||||
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT); // Set pin to high
|
||||
#else
|
||||
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT); // Set pin to low
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -83,7 +99,6 @@ void spindle_set_state(uint8_t state, float rpm)
|
||||
#endif
|
||||
|
||||
#ifdef VARIABLE_SPINDLE
|
||||
// TODO: Install the optional capability for frequency-based output for servos.
|
||||
#ifdef CPU_MAP_ATMEGA2560
|
||||
TCCRA_REGISTER = (1<<COMB_BIT) | (1<<WAVE1_REGISTER) | (1<<WAVE0_REGISTER);
|
||||
TCCRB_REGISTER = (TCCRB_REGISTER & 0b11111000) | 0x02 | (1<<WAVE2_REGISTER) | (1<<WAVE3_REGISTER); // set to 1/8 Prescaler
|
||||
@@ -91,33 +106,69 @@ void spindle_set_state(uint8_t state, float rpm)
|
||||
uint16_t current_pwm;
|
||||
#else
|
||||
TCCRA_REGISTER = (1<<COMB_BIT) | (1<<WAVE1_REGISTER) | (1<<WAVE0_REGISTER);
|
||||
TCCRB_REGISTER = (TCCRB_REGISTER & 0b11111000) | 0x02; // set to 1/8 Prescaler
|
||||
#ifdef USE_SPINDLE_SERVO_MODE
|
||||
// Servo mode: Set to 1/1024 prescaler for ~50Hz frequency (actually ~61Hz on 16MHz)
|
||||
// Timer formula: Freq = F_CPU / (Prescaler * 256) = 16MHz / (1024 * 256) = 61.04Hz
|
||||
// This provides the ~20ms period needed for servo control (50Hz ideal, 61Hz acceptable)
|
||||
TCCRB_REGISTER = (TCCRB_REGISTER & 0b11111000) | 0x07; // CS22=1, CS21=1, CS20=1 = 1/1024 prescaler
|
||||
#else
|
||||
// Standard spindle mode: Set to 1/8 prescaler for ~7.8kHz frequency
|
||||
TCCRB_REGISTER = (TCCRB_REGISTER & 0b11111000) | 0x02; // CS21=1 = 1/8 prescaler
|
||||
#endif
|
||||
uint8_t current_pwm;
|
||||
#endif
|
||||
|
||||
if (rpm <= 0.0) { spindle_stop(); } // RPM should never be negative, but check anyway.
|
||||
else {
|
||||
#define SPINDLE_RPM_RANGE (SPINDLE_MAX_RPM-SPINDLE_MIN_RPM)
|
||||
if ( rpm < SPINDLE_MIN_RPM ) { rpm = 0; }
|
||||
#ifdef USE_SPINDLE_SERVO_MODE
|
||||
// Servo mode: Allow rpm=0 as valid position (0 degrees), only stop on negative
|
||||
if (rpm < 0.0) { spindle_stop(); }
|
||||
else {
|
||||
// Servo mode: Map RPM range to servo pulse width (16-31 ticks = ~1-2ms pulses)
|
||||
// Each tick at 1/1024 prescaler = 64μs, so 16 ticks ≈ 1.024ms, 31 ticks ≈ 1.984ms
|
||||
#define SPINDLE_SERVO_RANGE (SPINDLE_SERVO_MAX_PULSE - SPINDLE_SERVO_MIN_PULSE)
|
||||
#define SPINDLE_RPM_RANGE (SPINDLE_MAX_RPM-SPINDLE_MIN_RPM)
|
||||
if ( rpm < SPINDLE_MIN_RPM ) { rpm = SPINDLE_MIN_RPM; }
|
||||
if ( rpm > SPINDLE_MAX_RPM ) { rpm = SPINDLE_MAX_RPM; }
|
||||
rpm -= SPINDLE_MIN_RPM;
|
||||
if ( rpm > SPINDLE_RPM_RANGE ) { rpm = SPINDLE_RPM_RANGE; } // Prevent integer overflow
|
||||
}
|
||||
current_pwm = floor( rpm*(PWM_MAX_VALUE/SPINDLE_RPM_RANGE) + 0.5);
|
||||
#ifdef MINIMUM_SPINDLE_PWM
|
||||
if (current_pwm < MINIMUM_SPINDLE_PWM) { current_pwm = MINIMUM_SPINDLE_PWM; }
|
||||
#endif
|
||||
OCR_REGISTER = current_pwm; // Set PWM pin output
|
||||
// Map RPM to servo pulse range
|
||||
current_pwm = floor( rpm * (SPINDLE_SERVO_RANGE / SPINDLE_RPM_RANGE) + SPINDLE_SERVO_MIN_PULSE + 0.5);
|
||||
OCR_REGISTER = current_pwm; // Set PWM pin output
|
||||
|
||||
// On the Uno, spindle enable and PWM are shared, unless otherwise specified.
|
||||
#if defined(CPU_MAP_ATMEGA2560) || defined(USE_SPINDLE_DIR_AS_ENABLE_PIN)
|
||||
#ifdef INVERT_SPINDLE_ENABLE_PIN
|
||||
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT);
|
||||
#else
|
||||
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT);
|
||||
// On the Uno, spindle enable and PWM are shared, unless otherwise specified.
|
||||
#if defined(CPU_MAP_ATMEGA2560) || defined(USE_SPINDLE_DIR_AS_ENABLE_PIN)
|
||||
#ifdef INVERT_SPINDLE_ENABLE_PIN
|
||||
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT);
|
||||
#else
|
||||
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Standard spindle mode: Stop on zero or negative RPM
|
||||
if (rpm <= 0.0) { spindle_stop(); }
|
||||
else {
|
||||
// Standard spindle mode: Map RPM range to full PWM range (0-255)
|
||||
#define SPINDLE_RPM_RANGE (SPINDLE_MAX_RPM-SPINDLE_MIN_RPM)
|
||||
if ( rpm < SPINDLE_MIN_RPM ) { rpm = 0; }
|
||||
else {
|
||||
rpm -= SPINDLE_MIN_RPM;
|
||||
if ( rpm > SPINDLE_RPM_RANGE ) { rpm = SPINDLE_RPM_RANGE; } // Prevent integer overflow
|
||||
}
|
||||
current_pwm = floor( rpm*(PWM_MAX_VALUE/SPINDLE_RPM_RANGE) + 0.5);
|
||||
#ifdef MINIMUM_SPINDLE_PWM
|
||||
if (current_pwm < MINIMUM_SPINDLE_PWM) { current_pwm = MINIMUM_SPINDLE_PWM; }
|
||||
#endif
|
||||
OCR_REGISTER = current_pwm; // Set PWM pin output
|
||||
|
||||
// On the Uno, spindle enable and PWM are shared, unless otherwise specified.
|
||||
#if defined(CPU_MAP_ATMEGA2560) || defined(USE_SPINDLE_DIR_AS_ENABLE_PIN)
|
||||
#ifdef INVERT_SPINDLE_ENABLE_PIN
|
||||
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT);
|
||||
#else
|
||||
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
// NOTE: Without variable spindle, the enable bit should just turn on or off, regardless
|
||||
|
||||
41
platformio.ini
Normal file
41
platformio.ini
Normal file
@@ -0,0 +1,41 @@
|
||||
; PlatformIO Project Configuration File for Grbl
|
||||
;
|
||||
; This configuration builds Grbl v0.9j for Arduino Uno (ATmega328P)
|
||||
; Based on the original Makefile settings
|
||||
;
|
||||
; Please visit documentation for options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
src_dir = src
|
||||
lib_dir = .
|
||||
include_dir = grbl
|
||||
|
||||
[env:uno]
|
||||
platform = atmelavr
|
||||
board = uno
|
||||
framework = arduino
|
||||
|
||||
; Build configuration matching Makefile
|
||||
build_flags =
|
||||
-DF_CPU=16000000L
|
||||
-DBAUD_RATE=115200
|
||||
-Wall
|
||||
-Os
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
-Wl,--gc-sections
|
||||
-lm
|
||||
-Igrbl
|
||||
-Igrbl/cpu_map
|
||||
-Igrbl/defaults
|
||||
|
||||
; Add Grbl source files to build
|
||||
build_src_filter =
|
||||
+<*>
|
||||
+<../grbl/*.c>
|
||||
-<../grbl/examples/>
|
||||
|
||||
; Upload configuration
|
||||
upload_speed = 115200
|
||||
monitor_speed = 115200
|
||||
Reference in New Issue
Block a user