diff --git a/Makefile b/Makefile
index 36073c0..d2ac8a0 100644
--- a/Makefile
+++ b/Makefile
@@ -30,12 +30,14 @@
DEVICE = atmega168
CLOCK = 16000000
PROGRAMMER = -c avrisp2 -P usb
-OBJECTS = main.o motion_control.o gcode.o spindle_control.o wiring_serial.o serial_protocol.o stepper.o
-FUSES = -U hfuse:w:0xd9:m -U lfuse:w:0x24:m
+OBJECTS = main.o motion_control.o gcode.o spindle_control.o wiring_serial.o serial_protocol.o stepper.o \
+ eeprom.o config.o
+# FUSES = -U hfuse:w:0xd9:m -U lfuse:w:0x24:m
+FUSES = -U hfuse:w:0xd2:m -U lfuse:w:0xff:m
# Tune the lines below only if you know what you are doing:
-AVRDUDE = avrdude $(PROGRAMMER) -p $(DEVICE) -B 10 -F
+AVRDUDE = avrdude $(PROGRAMMER) -p $(DEVICE) -B 10 -F
COMPILE = avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE) -I.
# symbolic targets:
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..e349005
--- /dev/null
+++ b/config.c
@@ -0,0 +1,95 @@
+/*
+ config.c - eeprom and compile time configuration handling
+ Part of Grbl
+
+ Copyright (c) 2009 Simen Svale Skogsrud
+
+ 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 .
+*/
+
+#include
+#include
+#include "nuts_bolts.h"
+#include "config.h"
+#include "eeprom.h"
+#include "wiring_serial.h"
+
+void reset_settings() {
+ settings.steps_per_mm[0] = X_STEPS_PER_MM;
+ settings.steps_per_mm[1] = Y_STEPS_PER_MM;
+ settings.steps_per_mm[2] = Z_STEPS_PER_MM;
+ settings.pulse_microseconds = STEP_PULSE_MICROSECONDS;
+ settings.default_feed_rate = DEFAULT_FEEDRATE;
+ settings.default_seek_rate = RAPID_FEEDRATE;
+ settings.mm_per_arc_segment = MM_PER_ARC_SEGMENT;
+ settings.invert_mask = STEPPING_INVERT_MASK;
+}
+
+void dump_settings() {
+ printString("$0 = "); printFloat(settings.steps_per_mm[0]);
+ printString(" (steps/mm x)\r\n$1 = "); printFloat(settings.steps_per_mm[1]);
+ printString(" (steps/mm y)\r\n$2 = "); printFloat(settings.steps_per_mm[2]);
+ printString(" (steps/mm z)\r\n$3 = "); printInteger(settings.pulse_microseconds);
+ printString(" (microseconds step pulse)\r\n$4 = "); printFloat(settings.default_feed_rate);
+ printString(" (mm/sec default feed rate)\r\n$5 = "); printFloat(settings.default_seek_rate);
+ printString(" (mm/sec default seek rate)\r\n$6 = "); printFloat(settings.mm_per_arc_segment);
+ printString(" (mm/arc segment)\r\n$7 = "); printInteger(settings.invert_mask);
+ printString(" (step port invert mask. binary = "); printIntegerInBase(settings.invert_mask, 2);
+ printString(")\r\n\r\n'$x=value' to set parameter or just '$' to dump current settings\r\n");
+}
+
+int read_settings() {
+ // Check version-byte of eeprom
+ uint8_t version = eeprom_get_char(0);
+ if (version != SETTINGS_VERSION) { return(FALSE); }
+ // Read settings-record and check checksum
+ if (!(memcpy_from_eeprom_with_checksum((char*)&settings, 1, sizeof(struct Settings)))) {
+ return(FALSE);
+ }
+ return(TRUE);
+}
+
+void write_settings() {
+ eeprom_put_char(0, SETTINGS_VERSION);
+ memcpy_to_eeprom_with_checksum(1, (char*)&settings, sizeof(struct Settings));
+}
+
+// A helper method to set settings from command line
+void store_setting(int parameter, double value) {
+ switch(parameter) {
+ case 0: case 1: case 2:
+ settings.steps_per_mm[parameter] = value; break;
+ case 3: settings.pulse_microseconds = round(value); break;
+ case 4: settings.default_feed_rate = value; break;
+ case 5: settings.default_seek_rate = value; break;
+ case 6: settings.mm_per_arc_segment = value; break;
+ case 7: settings.invert_mask = trunc(value); break;
+ default:
+ printString("Unknown parameter\r\n");
+ return;
+ }
+ write_settings();
+ printString("Stored new setting\r\n");
+}
+
+void config_init() {
+ if(read_settings()) {
+ printString("'$' to dump current settings\r\n");
+ } else {
+ printString("EEPROM blank. Rewrote default settings:\r\n");
+ reset_settings();
+ write_settings();
+ dump_settings();
+ }
+}
diff --git a/config.h b/config.h
index f772f37..4d9ce08 100644
--- a/config.h
+++ b/config.h
@@ -1,5 +1,5 @@
/*
- config.h - configuration data for Grbl
+ config.h - eeprom and compile time configuration handling
Part of Grbl
Copyright (c) 2009 Simen Svale Skogsrud
@@ -21,22 +21,11 @@
#ifndef config_h
#define config_h
-#define VERSION "0.5"
+#define VERSION "0.51"
-#define MICROSTEPS 8
-#define X_STEPS_PER_MM (94.488188976378*MICROSTEPS)
-#define Y_STEPS_PER_MM (94.488188976378*MICROSTEPS)
-#define Z_STEPS_PER_MM (94.488188976378*MICROSTEPS)
+// Settings that can only be set at compile-time:
-#define STEP_PULSE_MICROSECONDS 30
-
-#define INCHES_PER_MM (1.0/25.4)
-#define X_STEPS_PER_INCH X_STEPS_PER_MM*INCHES_PER_MM
-#define Y_STEPS_PER_INCH Y_STEPS_PER_MM*INCHES_PER_MM
-#define Z_STEPS_PER_INCH Z_STEPS_PER_MM*INCHES_PER_MM
-
-#define RAPID_FEEDRATE 480.0 // in millimeters per minute
-#define DEFAULT_FEEDRATE 480.0
+#define BAUD_RATE 9600
#define STEPPERS_ENABLE_DDR DDRD
#define STEPPERS_ENABLE_PORT PORTD
@@ -65,14 +54,44 @@
#define SPINDLE_DIRECTION_PORT PORTD
#define SPINDLE_DIRECTION_BIT 7
+
+// Version of the EEPROM data. Will be used to migrate existing data from older versions of Grbl
+// when firmware is upgraded. Always stored in byte 0 of eeprom
+#define SETTINGS_VERSION 1
+
+// Current global settings (persisted in EEPROM from byte 1 onwards)
+struct Settings {
+ double steps_per_mm[3];
+ uint8_t microsteps;
+ uint8_t pulse_microseconds;
+ double default_feed_rate;
+ double default_seek_rate;
+ uint8_t invert_mask;
+ double mm_per_arc_segment;
+};
+struct Settings settings;
+
+// Initialize the configuration subsystem (load settings from EEPROM)
+void config_init();
+
+// Print current settings
+void dump_settings();
+
+// A helper method to set new settings from command line
+void store_setting(int parameter, double value);
+
+
+// Default settings (used when resetting eeprom-settings)
+#define MICROSTEPS 8
+#define X_STEPS_PER_MM (94.488188976378*MICROSTEPS)
+#define Y_STEPS_PER_MM (94.488188976378*MICROSTEPS)
+#define Z_STEPS_PER_MM (94.488188976378*MICROSTEPS)
+#define STEP_PULSE_MICROSECONDS 30
+
#define MM_PER_ARC_SEGMENT 0.1
-#define BAUD_RATE 9600
-
-#define STEP_MASK ((1<
+#include
+
+/* These EEPROM bits have different names on different devices. */
+#ifndef EEPE
+ #define EEPE EEWE //!< EEPROM program/write enable.
+ #define EEMPE EEMWE //!< EEPROM master program/write enable.
+#endif
+
+/* These two are unfortunately not defined in the device include files. */
+#define EEPM1 5 //!< EEPROM Programming Mode Bit 1.
+#define EEPM0 4 //!< EEPROM Programming Mode Bit 0.
+
+/* Define to reduce code size. */
+#define EEPROM_IGNORE_SELFPROG //!< Remove SPM flag polling.
+
+/*! \brief Read byte from EEPROM.
+ *
+ * This function reads one byte from a given EEPROM address.
+ *
+ * \note The CPU is halted for 4 clock cycles during EEPROM read.
+ *
+ * \param addr EEPROM address to read from.
+ * \return The byte read from the EEPROM address.
+ */
+unsigned char eeprom_get_char( unsigned int addr )
+{
+ do {} while( EECR & (1< 0; size--) {
+ checksum = (checksum << 1) || (checksum >> 7);
+ checksum += *source;
+ eeprom_put_char(destination++, *(source++));
+ }
+ eeprom_put_char(destination, checksum);
+}
+
+int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size) {
+ unsigned char data, checksum = 0;
+ for(; size > 0; size--) {
+ data = eeprom_get_char(source++);
+ checksum = (checksum << 1) || (checksum >> 7);
+ checksum += data;
+ *(destination++) = data;
+ }
+ return(checksum == eeprom_get_char(source));
+}
+
+// end of file
diff --git a/eeprom.h b/eeprom.h
new file mode 100644
index 0000000..1769903
--- /dev/null
+++ b/eeprom.h
@@ -0,0 +1,9 @@
+#ifndef eeprom_h
+#define eeprom_h
+
+char eeprom_get_char(unsigned int addr);
+void eeprom_put_char(unsigned int addr, char new_value);
+void memcpy_to_eeprom_with_checksum(unsigned int destination, char *source, unsigned int size);
+int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size);
+
+#endif
diff --git a/gcode.c b/gcode.c
index 0f313f9..1931a58 100644
--- a/gcode.c
+++ b/gcode.c
@@ -83,7 +83,7 @@ struct ParserState {
uint8_t absolute_mode; /* 0 = relative motion, 1 = absolute motion {G90, G91} */
uint8_t program_flow;
int spindle_direction;
- double feed_rate; /* Millimeters/second */
+ double feed_rate, seek_rate; /* Millimeters/second */
double position[3]; /* Where the interpreter considers the tool to be at this point in the code */
uint8_t tool;
int16_t spindle_speed; /* RPM/100 */
@@ -110,7 +110,8 @@ void select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
void gc_init() {
memset(&gc, 0, sizeof(gc));
- gc.feed_rate = DEFAULT_FEEDRATE/60;
+ gc.feed_rate = settings.default_feed_rate/60;
+ gc.seek_rate = settings.default_seek_rate/60;
select_plane(X_AXIS, Y_AXIS, Z_AXIS);
gc.absolute_mode = TRUE;
}
@@ -163,6 +164,16 @@ uint8_t gc_execute_line(char *line) {
if (line[0] == '(') { return(gc.status_code); }
if (line[0] == '/') { counter++; } // ignore block delete
+ if (line[0] == '$') { // This is a parameter line intended to change EEPROM-settings
+ // Parameter lines are on the form '$4=374.3' or '$' to dump current settings
+ counter = 1;
+ if(line[counter] == 0) { dump_settings(); return(GCSTATUS_OK); }
+ read_double(line, &counter, &p);
+ if(line[counter++] != '=') { return(GCSTATUS_UNSUPPORTED_STATEMENT); }
+ read_double(line, &counter, &value);
+ if(line[counter] != 0) { return(GCSTATUS_UNSUPPORTED_STATEMENT); }
+ store_setting(p, value);
+ }
// Pass 1: Commands
while(next_statement(&letter, &value, line, &counter)) {
@@ -256,7 +267,8 @@ uint8_t gc_execute_line(char *line) {
case NEXT_ACTION_DEFAULT:
switch (gc.motion_mode) {
case MOTION_MODE_CANCEL: break;
- case MOTION_MODE_RAPID_LINEAR: case MOTION_MODE_LINEAR:
+ case MOTION_MODE_RAPID_LINEAR:
+ case MOTION_MODE_LINEAR:
mc_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS],
(gc.inverse_feed_rate_mode) ? inverse_feed_rate : gc.feed_rate, gc.inverse_feed_rate_mode);
break;
diff --git a/main.c b/main.c
index 518fa1b..36fd6fe 100644
--- a/main.c
+++ b/main.c
@@ -33,12 +33,12 @@
int main(void)
{
beginSerial(BAUD_RATE);
+ config_init();
st_init(); // initialize the stepper subsystem
mc_init(); // initialize motion control subsystem
spindle_init(); // initialize spindle controller
gc_init(); // initialize gcode-parser
sp_init(); // initialize the serial protocol
-// sd_raw_init());
DDRD |= (1<<3)|(1<<4)|(1<<5);
diff --git a/motion_control.c b/motion_control.c
index 3cdfad5..5b1dd34 100644
--- a/motion_control.c
+++ b/motion_control.c
@@ -51,9 +51,9 @@ void mc_line(double x, double y, double z, float feed_rate, int invert_feed_rate
int32_t target[3]; // The target position in absolute steps
int32_t steps[3]; // The target line in relative steps
- target[X_AXIS] = lround(x*X_STEPS_PER_MM);
- target[Y_AXIS] = lround(y*Y_STEPS_PER_MM);
- target[Z_AXIS] = lround(z*Z_STEPS_PER_MM);
+ target[X_AXIS] = lround(x*settings.steps_per_mm[0]);
+ target[Y_AXIS] = lround(y*settings.steps_per_mm[1]);
+ target[Z_AXIS] = lround(z*settings.steps_per_mm[2]);
for(axis = X_AXIS; axis <= Z_AXIS; axis++) {
steps[axis] = target[axis]-position[axis];
@@ -64,9 +64,9 @@ void mc_line(double x, double y, double z, float feed_rate, int invert_feed_rate
} else {
// Ask old Phytagoras to estimate how many mm our next move is going to take us
double millimeters_of_travel = sqrt(
- square(steps[X_AXIS]/X_STEPS_PER_MM) +
- square(steps[Y_AXIS]/Y_STEPS_PER_MM) +
- square(steps[Z_AXIS]/Z_STEPS_PER_MM));
+ square(steps[X_AXIS]/settings.steps_per_mm[0]) +
+ square(steps[Y_AXIS]/settings.steps_per_mm[1]) +
+ square(steps[Z_AXIS]/settings.steps_per_mm[2]));
st_buffer_line(steps[X_AXIS], steps[Y_AXIS], steps[Z_AXIS],
lround((millimeters_of_travel/feed_rate)*1000000));
}
@@ -80,14 +80,12 @@ void mc_line(double x, double y, double z, float feed_rate, int invert_feed_rate
// The arc is approximated by generating a huge number of tiny, linear segments. The length of each
// segment is configured in config.h by setting MM_PER_ARC_SEGMENT.
-
-// ISSUE: The arc interpolator assumes all axes have the same steps/mm as the X axis.
void mc_arc(double theta, double angular_travel, double radius, double linear_travel, int axis_1, int axis_2,
int axis_linear, double feed_rate, int invert_feed_rate)
{
double millimeters_of_travel = hypot(angular_travel*radius, labs(linear_travel));
if (millimeters_of_travel == 0.0) { return; }
- uint16_t segments = ceil(millimeters_of_travel/MM_PER_ARC_SEGMENT);
+ uint16_t segments = ceil(millimeters_of_travel/settings.mm_per_arc_segment);
// Multiply inverse feed_rate to compensate for the fact that this movement is approximated
// by a number of discrete segments. The inverse feed_rate should be correct for the sum of
// all segments.
@@ -97,8 +95,8 @@ void mc_arc(double theta, double angular_travel, double radius, double linear_tr
// The linear motion for each segment
double linear_per_segment = linear_travel/segments;
// Compute the center of this circle
- double center_x = (position[axis_1]/X_STEPS_PER_MM)-sin(theta)*radius;
- double center_y = (position[axis_2]/Y_STEPS_PER_MM)-cos(theta)*radius;
+ double center_x = (position[axis_1]/settings.steps_per_mm[axis_1])-sin(theta)*radius;
+ double center_y = (position[axis_2]/settings.steps_per_mm[axis_2])-cos(theta)*radius;
// a vector to track the end point of each segment
double target[3];
int i;
diff --git a/readme.txt b/readme.txt
index 4c498d7..6395bc0 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
-Grbl - An embedded rs274/ngc (g-code) integrater interpreter and motion-controller for the Arduino/AVR328 microcontroller
+Grbl - An embedded rs274/ngc (g-code) interpreter and motion-controller for the Arduino/AVR328 microcontroller
Goal: A no-compromise, high performance, low cost alternative to parallel-port based motion control for CNC milling
@@ -10,12 +10,12 @@ Status:
* Standards-compliant g-code arcs/circles fully supported
* Buffered, non blocking, asynchronous step generation so the rest of the system is free to process
g-code while the steppers are steppin'
+* Configuration parameters stored in EEPROM and set via simple commands
* Tested on very few (two) CNC rigs
Pending:
* Battle hardening in the field
* Documentation and web-site
-* Simpler configuration (w/o recompilation)
* Optional support for a alphanumeric LCD readout, a joystick and a few buttons for program control
* Support "headless" fabrication by buffering all code to SD-card or similar
* Easing of feed rate
diff --git a/stepper.c b/stepper.c
index 22e5996..25e3be4 100644
--- a/stepper.c
+++ b/stepper.c
@@ -95,8 +95,8 @@ SIGNAL(SIG_OUTPUT_COMPARE1A)
// Then pulse the stepping pins
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | out_bits;
// Reset step pulse reset timer so that SIG_OVERFLOW2 can reset the signal after
- // exactly STEP_PULSE_MICROSECONDS microseconds.
- TCNT2 = -(((STEP_PULSE_MICROSECONDS-2)*TICKS_PER_MICROSECOND)/8);
+ // exactly settings.pulse_microseconds microseconds.
+ TCNT2 = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND)/8);
busy = TRUE;
sei(); // Re enable interrupts (normally disabled while inside an interrupt handler)
@@ -150,17 +150,17 @@ SIGNAL(SIG_OUTPUT_COMPARE1A)
} else {
out_bits = 0;
}
- out_bits ^= STEPPING_INVERT_MASK;
+ out_bits ^= settings.invert_mask;
busy=FALSE;
PORTD &= ~(1<<3);
}
// This interrupt is set up by SIG_OUTPUT_COMPARE1A when it sets the motor port bits. It resets
-// the motor port after a short period (STEP_PULSE_MICROSECONDS) completing one step cycle.
+// the motor port after a short period (settings.pulse_microseconds) completing one step cycle.
SIGNAL(SIG_OVERFLOW2)
{
// reset stepping pins (leave the direction pins)
- STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (STEPPING_INVERT_MASK & STEP_MASK);
+ STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.invert_mask & STEP_MASK);
}
// Initialize and start the stepper motor subsystem
@@ -168,7 +168,7 @@ void st_init()
{
// Configure directions of interface pins
STEPPING_DDR |= STEPPING_MASK;
- STEPPING_PORT = (STEPPING_PORT & ~STEPPING_MASK); //| STEPPING_INVERT_MASK;
+ STEPPING_PORT = (STEPPING_PORT & ~STEPPING_MASK) | settings.invert_mask;
LIMIT_DDR &= ~(LIMIT_MASK);
STEPPERS_ENABLE_DDR |= 1<