Firmware/FiveD on Arduino

From RepRap
Revision as of 18:09, 3 July 2010 by Traumflug (talk | contribs) (Add patch.)
Jump to: navigation, search

Rewrite of FiveD firmware for the atmega168-based Arduino by Triffid Hunter

This firmware is loosely based on the official FiveD Firmware, however it uses 100% integer math, and works very hard to minimize/eliminate long math operations in interrupt context.

Constant Acceleration

The official FiveD firmware changes speed by a fixed amount each step, however this also alters the step time. acceleration = dv/dt, so with a fixed dv but a changing dt, acceleration changes during a move. This makes high speeds quite unattainable as the acceleration quickly outstrips the motor's ability to keep up.

FiveD on Arduino contains a constant acceleration implementation.

Transmission of Multiple Commands

FiveD on Arduino has the capability to receive additional commands while working on the first one. This avoids short stops between commands and will allow for even more smooth transitions from one movement to the next in the future.

Patches, Enhancements

As Triffid Hunter is apparently out of town currently (2010-07-03), the Git Repository doesn't allow to upload patches and I don't want to do yet another fork, I'll upload patches here.

To apply those patches, type git apply in your terminal, inside your local copy of git repo, then copy the text given her into the terminal and hit ctrl-d. Well, I hope it works that way.

If you work on FiveD on Arduino as well, please do commits in small pieces, one topic/bug at a time. This helps a lot in reviewing patches. That done, you can create a set of patches easily:

git format-patch --keep-subject -o out origin

You'll find each of your patches in the out/ directory, nicely equipped with a mail header and a proper file name.

Make sources buildable with the Arduino IDE.

---
 mendel/mendel.pde |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)
 create mode 100644 mendel/mendel.pde

diff --git a/mendel/mendel.pde b/mendel/mendel.pde
new file mode 100644
index 0000000..1c3f190
--- /dev/null
+++ b/mendel/mendel.pde
@@ -0,0 +1,4 @@
+
+/*
+	The existence of this file makes sources buildable with the Arduino IDE.
+*/
--

Achieve C89 compatibility, Arduino IDE's default.

---
 mendel/serial.c |   12 ++++++++----
 1 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/mendel/serial.c b/mendel/serial.c
index bce947b..767bdeb 100644
--- a/mendel/serial.c
+++ b/mendel/serial.c
@@ -146,7 +146,9 @@ void serial_writechar(uint8_t data)
 
 void serial_writeblock(void *data, int datalen)
 {
-	for (int i = 0; i < datalen; i++)
+	int i;
+
+	for (i = 0; i < datalen; i++)
 		serial_writechar(((uint8_t *) data)[i]);
 }
 
@@ -170,15 +172,17 @@ void serial_writechar_P(PGM_P data)
 
 void serial_writeblock_P(PGM_P data, int datalen)
 {
-	for (int i = 0; i < datalen; i++)
+	int i;
+
+	for (i = 0; i < datalen; i++)
 		serial_writechar_P(&data[i]);
 }
 
 void serial_writestr_P(PGM_P data)
 {
-	uint8_t i = 0;
+	uint8_t r, i = 0;
 	// yes, this is *supposed* to be assignment rather than comparison, so we break when r is assigned zero
-	for (uint8_t r; (r = pgm_read_byte(&data[i])); i++)
+	for (r; (r = pgm_read_byte(&data[i])); i++)
 		serial_writechar(r);
 }

--

RepRap host software expects a lowercase "ok" for confirmation.

---
 mendel/gcode.c |    4 ++++
 mendel/gcode.h |    3 +++
 2 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index 6d06e08..04a7e71 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -312,7 +312,11 @@ void scan_char(uint8_t c) {
 					) {
 					process_gcode_command(&next_target);
 
+					#ifdef	LOWERCASE_OK
+					serial_writestr_P(PSTR("ok\n"));
+					#else
 					serial_writestr_P(PSTR("OK\n"));
+					#endif
 
 					// expect next line number
 					next_target.N_expected++;
diff --git a/mendel/gcode.h b/mendel/gcode.h
index bfc9061..4805501 100644
--- a/mendel/gcode.h
+++ b/mendel/gcode.h
@@ -5,6 +5,9 @@
 
 #include	"dda.h"
 
+// host software expectations
+#define	LOWERCASE_OK
+
 // this is a very crude decimal-based floating point structure. a real floating point would at least have signed exponent
 typedef struct {
 	uint32_t	sign			:1;
--

Moved REQUIRE_LINENUMBER and REQUIRE_CHECKSUM #defines to gcode.h as well.

---
 mendel/Makefile |    4 +---
 mendel/gcode.h  |    2 ++
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/mendel/Makefile b/mendel/Makefile
index fcc78f6..3dfd4b6 100644
--- a/mendel/Makefile
+++ b/mendel/Makefile
@@ -38,8 +38,6 @@ OBJCOPY = $(ARCH)objcopy
 
 DEFS = -DF_CPU=$(F_CPU)
 # DEFS += "-DDEBUG=1"
-# DEFS += "-DREQUIRE_LINENUMBER"
-# DEFS += "-DREQUIRE_CHECKSUM"
 
 OPTIMIZE = -Os -ffunction-sections -finline-functions-called-once -DDEBUG
 # OPTIMIZE = -O0
@@ -103,4 +101,4 @@ size: $(PROGRAM).hex
 
 %.sym: %.elf
 	@echo "  SYM       $@"
-	@$(OBJDUMP) -t $< | perl -ne 'BEGIN { printf "  ADDR  NAME                  SIZE\n"; } /([0-9a-f]+)\s+(\w+)\s+O\s+\.(bss|data)\s+([0-9a-f]+)\s+(\w+)/ && printf "0x%04x  %-20s +%d\n", eval("0x$$1") & 0xFFFF, $$5, eval("0x$$4")' | sort -k1 > $@
\ No newline at end of file
+	@$(OBJDUMP) -t $< | perl -ne 'BEGIN { printf "  ADDR  NAME                  SIZE\n"; } /([0-9a-f]+)\s+(\w+)\s+O\s+\.(bss|data)\s+([0-9a-f]+)\s+(\w+)/ && printf "0x%04x  %-20s +%d\n", eval("0x$$1") & 0xFFFF, $$5, eval("0x$$4")' | sort -k1 > $@
diff --git a/mendel/gcode.h b/mendel/gcode.h
index 4805501..9fd1795 100644
--- a/mendel/gcode.h
+++ b/mendel/gcode.h
@@ -7,6 +7,8 @@
 
 // host software expectations
 #define	LOWERCASE_OK
+//#define	REQUIRE_LINENUMBER
+//#define	REQUIRE_CHECKSUM
 
 // this is a very crude decimal-based floating point structure. a real floating point would at least have signed exponent
 typedef struct {
--

An M-code stuck when receiving G commands and vice-versa.

Testcase:

M105
G1 F100

--> Temperatures would be sent the second time as well.

---
 mendel/gcode.c |    7 ++++++-
 1 files changed, 6 insertions(+), 1 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index 04a7e71..c05c598 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -335,7 +335,12 @@ void scan_char(uint8_t c) {
 		}
 
 		// reset variables
-		next_target.seen_X = next_target.seen_Y = next_target.seen_Z = next_target.seen_E = next_target.seen_F = next_target.seen_S = next_target.seen_P = next_target.seen_N = next_target.seen_checksum = next_target.seen_comment = next_target.checksum_read = next_target.checksum_calculated = 0;
+		next_target.seen_X = next_target.seen_Y = next_target.seen_Z = \
+			next_target.seen_E = next_target.seen_F = next_target.seen_G = \
+			next_target.seen_S = next_target.seen_P = next_target.seen_N = \
+			next_target.seen_M = next_target.seen_checksum = \
+			next_target.seen_comment = next_target.checksum_read = \
+			next_target.checksum_calculated = 0;
 		last_field = 0;
 		read_digit.sign = 0;
 		read_digit.mantissa = 0;
--

If line numbers aren't required, don't bother about their actual value either.

Failing case was RepRap host software, sending

N0 T8 *18
N0 G21 *58

on each build start.

Also saves a whopping 124 bytes of memory on ignoring line numbers. next_target.N_expected is never used and it's handling should be optimized out.

---
 mendel/gcode.c |   10 +++++-----
 1 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index c05c598..86259b0 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -297,11 +297,11 @@ void scan_char(uint8_t c) {
 		// process
 		if (next_target.seen_G || next_target.seen_M) {
 			if (
-					#ifdef	REQUIRE_LINENUMBER
-					((next_target.N_expected == next_target.N) && (next_target.seen_N == 1))
-					#else
-					((next_target.N_expected == next_target.N) || (next_target.seen_N == 0))
-					#endif
+				#ifdef	REQUIRE_LINENUMBER
+				((next_target.N_expected == next_target.N) && (next_target.seen_N == 1))
+				#else
+				1
+				#endif
 				) {
 				if (
 					#ifdef	REQUIRE_CHECKSUM
--

Allow non-consecutive line numbers.

There isn't much point in insisting on consecutive line numbers. Some CNC softwares even do steps of 5 or 10 intentionally.

---
 mendel/gcode.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index 86259b0..a9cb139 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -298,7 +298,7 @@ void scan_char(uint8_t c) {
 		if (next_target.seen_G || next_target.seen_M) {
 			if (
 				#ifdef	REQUIRE_LINENUMBER
-				((next_target.N_expected == next_target.N) && (next_target.seen_N == 1))
+				((next_target.N >= next_target.N_expected) && (next_target.seen_N == 1))
 				#else
 				1
 				#endif
@@ -319,7 +319,7 @@ void scan_char(uint8_t c) {
 					#endif
 
 					// expect next line number
-					next_target.N_expected++;
+					next_target.N_expected = next_target.N  + 1;
 				}
 				else {
 					serial_writestr_P(PSTR("RESEND: BAD CHECKSUM: EXPECTED "));
--

Count only line numbers with N seen.

This allows to issue manual commands in midst of a (paused) build.

---
 mendel/gcode.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index a9cb139..1622b51 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -319,7 +319,8 @@ void scan_char(uint8_t c) {
 					#endif
 
 					// expect next line number
-					next_target.N_expected = next_target.N  + 1;
+					if (next_target.seen_N == 1)
+						next_target.N_expected = next_target.N + 1;
 				}
 				else {
 					serial_writestr_P(PSTR("RESEND: BAD CHECKSUM: EXPECTED "));
--

Don't fail on unknown commands.

For example, RepRap host sends T commands, which are not (yet) known.

Previously, the firmware would fail silently, which was even worse.

---
 mendel/gcode.c |   54 +++++++++++++++++++++++++++++++-----------------------
 1 files changed, 31 insertions(+), 23 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index 1622b51..d75e18e 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -221,7 +221,7 @@ void scan_char(uint8_t c) {
 
 		// process character
 		switch (c) {
-			// each command is either G or M, so preserve previous G/M unless a new one has appeared
+			// each currently known command is either G or M, so preserve previous G/M unless a new one has appeared
 			case 'G':
 				next_target.seen_G = 1;
 				next_target.seen_M = 0;
@@ -294,46 +294,54 @@ void scan_char(uint8_t c) {
 	if ((c == 10) || (c == 13)) {
 		if (debug_flags & DEBUG_ECHO)
 			serial_writechar(c);
-		// process
-		if (next_target.seen_G || next_target.seen_M) {
+
+		if (
+			#ifdef	REQUIRE_LINENUMBER
+			((next_target.N >= next_target.N_expected) && (next_target.seen_N == 1))
+			#else
+			1
+			#endif
+			) {
 			if (
-				#ifdef	REQUIRE_LINENUMBER
-				((next_target.N >= next_target.N_expected) && (next_target.seen_N == 1))
+				#ifdef	REQUIRE_CHECKSUM
+				((next_target.checksum_calculated == next_target.checksum_read) && (next_target.seen_checksum == 1))
 				#else
-				1
+				((next_target.checksum_calculated == next_target.checksum_read) || (next_target.seen_checksum == 0))
 				#endif
 				) {
-				if (
-					#ifdef	REQUIRE_CHECKSUM
-					((next_target.checksum_calculated == next_target.checksum_read) && (next_target.seen_checksum == 1))
-					#else
-					((next_target.checksum_calculated == next_target.checksum_read) || (next_target.seen_checksum == 0))
-					#endif
-					) {
+				// process
+				if (next_target.seen_G || next_target.seen_M) {
 					process_gcode_command(&next_target);
-
 					#ifdef	LOWERCASE_OK
 					serial_writestr_P(PSTR("ok\n"));
 					#else
 					serial_writestr_P(PSTR("OK\n"));
 					#endif
-
-					// expect next line number
-					if (next_target.seen_N == 1)
-						next_target.N_expected = next_target.N + 1;
 				}
 				else {
-					serial_writestr_P(PSTR("RESEND: BAD CHECKSUM: EXPECTED "));
-					serwrite_uint8(next_target.checksum_calculated);
-					serial_writestr_P(PSTR("\n"));
+					// write "OK" for invalid/unknown commands as well
+					#ifdef	LOWERCASE_OK
+					serial_writestr_P(PSTR("ok huh?\n"));
+					#else
+					serial_writestr_P(PSTR("OK Huh?\n"));
+					#endif
 				}
+
+				// expect next line number
+				if (next_target.seen_N == 1)
+					next_target.N_expected = next_target.N  + 1;
 			}
 			else {
-				serial_writestr_P(PSTR("RESEND: BAD LINE NUMBER: EXPECTED "));
-				serwrite_uint32(next_target.N_expected);
+				serial_writestr_P(PSTR("RESEND: BAD CHECKSUM: EXPECTED "));
+				serwrite_uint8(next_target.checksum_calculated);
 				serial_writestr_P(PSTR("\n"));
 			}
 		}
+		else {
+			serial_writestr_P(PSTR("RESEND: BAD LINE NUMBER: EXPECTED "));
+			serwrite_uint32(next_target.N_expected);
+			serial_writestr_P(PSTR("\n"));
+		}
 
 		// reset variables
 		next_target.seen_X = next_target.seen_Y = next_target.seen_Z = \
--

Make resend requests RepRap host compatible, too.

Elaborate on the various #defines related to host expectations in gcode.h

---
 mendel/gcode.c |   25 +++++++++++++++++++++++--
 mendel/gcode.h |   12 ++++++++++++
 2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index d75e18e..8460737 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -332,15 +332,17 @@ void scan_char(uint8_t c) {
 					next_target.N_expected = next_target.N + 1;
 			}
 			else {
-				serial_writestr_P(PSTR("RESEND: BAD CHECKSUM: EXPECTED "));
+				serial_writestr_P(PSTR("Expected checksum "));
 				serwrite_uint8(next_target.checksum_calculated);
 				serial_writestr_P(PSTR("\n"));
+				request_resend();
 			}
 		}
 		else {
-			serial_writestr_P(PSTR("RESEND: BAD LINE NUMBER: EXPECTED "));
+			serial_writestr_P(PSTR("Expected line number "));
 			serwrite_uint32(next_target.N_expected);
 			serial_writestr_P(PSTR("\n"));
+			request_resend();
 		}
 
 		// reset variables
@@ -725,3 +727,22 @@ void process_gcode_command(GCODE_COMMAND *gcmd) {
 		}
 	}
 }
+
+/****************************************************************************
+*                                                                           *
+* Request a resend of the current line - used from various places.          *
+*                                                                           *
+* Relies on the global variable next_target.N being valid.                  *
+*                                                                           *
+****************************************************************************/
+
+void request_resend() {
+	#ifdef	LOWERCASE_OK
+	serial_writestr_P(PSTR("Resend:"));
+	#else
+	serial_writestr_P(PSTR("RESEND: "));
+	#endif
+	serwrite_uint8(next_target.N);
+	serial_writestr_P(PSTR("\n"));
+}
+
diff --git a/mendel/gcode.h b/mendel/gcode.h
index 9fd1795..8b37e46 100644
--- a/mendel/gcode.h
+++ b/mendel/gcode.h
@@ -6,10 +6,19 @@
 #include	"dda.h"
 
 // host software expectations
+
+// wether host software expects all caps or (mostly) lowercase responses
+// keeping it defined makes us matching RepRap host software's expectations
 #define	LOWERCASE_OK
+
+// wether to insist on N line numbers
+// if not defined, N's are completely ignored
 //#define	REQUIRE_LINENUMBER
+
+// wether to insist on a checksum
 //#define	REQUIRE_CHECKSUM
 
+
 // this is a very crude decimal-based floating point structure. a real floating point would at least have signed exponent
 typedef struct {
 	uint32_t	sign			:1;
@@ -68,4 +77,7 @@ void scan_char(uint8_t c);
 // when we have a whole line, feed it to this
 void process_gcode_command(GCODE_COMMAND *gcmd);
 
+// uses the global variable next_target.N
+void request_resend();
+
 #endif	/* _GCODE_H */
--

Add the option to not include the asterisk in checksum calculation.

RepRap host software does it this way.

---
 mendel/gcode.c |   11 +++++++++--
 mendel/gcode.h |    4 ++++
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index 8460737..c1065f1 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -17,6 +17,8 @@ uint8_t last_field = 0;
 
 const char alphabet[] = "GMXYZEFSPN*";
 
+#define crc(a, b)		(a ^ b)
+
decfloat read_digit					__attribute__ ((__section__ (".bss")));
  
GCODE_COMMAND next_target		__attribute__ ((__section__ (".bss")));
@@ -103,10 +105,10 @@ void SpecialMoveE(int32_t e, uint32_t f) {
 ****************************************************************************/
 
 void scan_char(uint8_t c) {
-	// move this below switch(c) if the asterisk isn't included in the checksum
-	#define crc(a, b)		(a ^ b)
+	#ifdef ASTERISK_IN_CHECKSUM_INCLUDED
 	if (next_target.seen_checksum == 0)
 		next_target.checksum_calculated = crc(next_target.checksum_calculated, c);
+	#endif
 
 	// uppercase
 	if (c >= 'a' && c <= 'z')
@@ -290,6 +292,11 @@ void scan_char(uint8_t c) {
 		}
 	}
 
+	#ifndef ASTERISK_IN_CHECKSUM_INCLUDED
+	if (next_target.seen_checksum == 0)
+		next_target.checksum_calculated = crc(next_target.checksum_calculated, c);
+	#endif
+
 	// end of line
 	if ((c == 10) || (c == 13)) {
 		if (debug_flags & DEBUG_ECHO)
diff --git a/mendel/gcode.h b/mendel/gcode.h
index 8b37e46..c4bc440 100644
--- a/mendel/gcode.h
+++ b/mendel/gcode.h
@@ -11,6 +11,10 @@
 // keeping it defined makes us matching RepRap host software's expectations
 #define	LOWERCASE_OK
 
+// wether the asterisk (checksum-command) is included for checksum calculation
+// undefined for RepRap host software
+//#define ASTERISK_IN_CHECKSUM_INCLUDED
+
 // wether to insist on N line numbers
 // if not defined, N's are completely ignored
 //#define	REQUIRE_LINENUMBER
--

Every character, plus *, starts a new field.

Previously, unknown commands would mess up decimals handling. Failing testcase was "N11 T22", which was interpreted as "N1122".

The new code now also compiles to 56 bytes less.

---
 mendel/gcode.c |   16 ++++++----------
 1 files changed, 6 insertions(+), 10 deletions(-)

diff --git a/mendel/gcode.c b/mendel/gcode.c
index c1065f1..ed0db29 100644
--- a/mendel/gcode.c
+++ b/mendel/gcode.c
@@ -15,8 +15,6 @@
 
 uint8_t last_field = 0;
 
-const char alphabet[] = "GMXYZEFSPN*";
-
 #define crc(a, b)		(a ^ b)
 
 decfloat read_digit					__attribute__ ((__section__ (".bss")));
@@ -117,7 +115,8 @@ void scan_char(uint8_t c) {
 	// process previous field
 	if (last_field) {
 		// check if we're seeing a new field or end of line
-		if ((indexof(c, alphabet) >= 0) || (c == 10) || (c ==13)) {
+		// any character will start a new field, even invalid/unknown ones
+		if ((c >= 'A' && c <= 'Z') || c == '*' || (c == 10) || (c == 13)) {
 			switch (last_field) {
 				case 'G':
 					next_target.G = read_digit.mantissa;
@@ -206,16 +205,14 @@ void scan_char(uint8_t c) {
 			}
 			// reset for next field
 			last_field = 0;
-			read_digit.sign = 0;
-			read_digit.mantissa = 0;
-			read_digit.exponent = 0;
+			read_digit.sign = read_digit.mantissa = read_digit.exponent = 0;
 		}
 	}
 
 	// skip comments
 	if (next_target.seen_comment == 0) {
 		// new field?
-		if (indexof(c, alphabet) >= 0) {
+		if ((c >= 'A' && c <= 'Z') || c == '*') {
 			last_field = c;
 			if (debug_flags & DEBUG_ECHO)
 				serial_writechar(c);
@@ -289,6 +286,7 @@ void scan_char(uint8_t c) {
 					if (read_digit.exponent)
 						read_digit.exponent++;
 				}
+				// everything else is ignored
 		}
 	}
 
@@ -360,9 +358,7 @@ void scan_char(uint8_t c) {
 			next_target.seen_comment = next_target.checksum_read = \
 			next_target.checksum_calculated = 0;
 		last_field = 0;
-		read_digit.sign = 0;
-		read_digit.mantissa = 0;
-		read_digit.exponent = 0;
+		read_digit.sign = read_digit.mantissa = read_digit.exponent = 0;
 	}
 }
 
--