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<