Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

384 lines (349 sloc) 12.109 kb
#include "gcode_parse.h"
/** \file
\brief Parse received G-Codes
*/
#include <string.h>
#include "serial.h"
#include "sermsg.h"
#include "dda_queue.h"
#include "debug.h"
#include "heater.h"
#include "sersendf.h"
#include "gcode_process.h"
/// current or previous gcode word
/// for working out what to do with data just received
uint8_t last_field = 0;
/// crude crc macro
#define crc(a, b) (a ^ b)
/// crude floating point data storage
decfloat read_digit __attribute__ ((__section__ (".bss")));
/// this is where we store all the data for the current command before we work out what to do with it
GCODE_COMMAND next_target __attribute__ ((__section__ (".bss")));
/*
decfloat_to_int() is the weakest subject to variable overflow. For evaluation, we assume a build room of +-1000 mm and STEPS_PER_MM_x between 1.000 and 4096. Accordingly for metric units:
df->mantissa: +-0..1048075 (20 bit - 500 for rounding)
df->exponent: 0, 2, 3 or 4 (10 bit)
multiplicand / denominator: 20..4194303 / 1000 (22 bit - 10 bit) or
0..4095 / 1 (12 bit - 0 bit)
imperial units:
df->mantissa: +-0..32267 (15 bit - 500 for rounding)
df->exponent: 0, 2, 3 or 4 (10 bit)
multiplicand: 1..105000 (17 bit)
denominator: 1 or 10 ( 4 bit)
*/
// accordingly:
#define DECFLOAT_EXP_MAX 4
#define DECFLOAT_MANT_MM_MAX 1048075
#define DECFLOAT_MANT_IN_MAX 32267
/*
utility functions
*/
extern const uint32_t powers[]; // defined in sermsg.c
// TODO: When the new approach to pass distances in micrometers instead of step
// numbers stays, this should be replaced by a simplified version.
/// convert a floating point input value into an integer with appropriate scaling.
/// \param *df pointer to floating point structure that holds fp value to convert
/// \param multiplicand multiply by this amount during conversion to integer
/// \param divide_by_1000 divide by 1000 during conversion to integer
///
/// lots of work has been done in exploring this function's limitations in terms of overflow and rounding
/// this work may not be finished
static int32_t decfloat_to_int(decfloat *df, uint32_t multiplicand, uint8_t divide_by_1000) {
uint32_t r = df->mantissa;
uint8_t e = df->exponent;
uint32_t rnew1, rnew2;
// e=1 means we've seen a decimal point but no digits after it, and e=2 means we've seen a decimal point with one digit so it's too high by one if not zero
if (e)
e--;
if (divide_by_1000) {
rnew1 = r * (multiplicand / 1000);
rnew2 = (r * (multiplicand % 1000) + (1000 / 2)) / 1000;
r = rnew1 + rnew2;
}
else {
r *= multiplicand;
}
if (e)
r = (r + powers[e] / 2) / powers[e];
return df->sign ? -(int32_t)r : (int32_t)r;
}
/// Character Received - add it to our command
/// \param c the next character to process
void gcode_parse_char(uint8_t c) {
// uppercase
if (c >= 'a' && c <= 'z')
c &= ~32;
// process previous field
if (last_field) {
// check if we're seeing a new field or end of line
// 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;
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint8(next_target.G);
break;
case 'M':
next_target.M = read_digit.mantissa;
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint8(next_target.M);
break;
case 'X':
if (next_target.option_inches)
next_target.target.X = decfloat_to_int(&read_digit, 25400, 1);
else
next_target.target.X = decfloat_to_int(&read_digit, 1000, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_int32(next_target.target.X);
break;
case 'Y':
if (next_target.option_inches)
next_target.target.Y = decfloat_to_int(&read_digit, 25400, 1);
else
next_target.target.Y = decfloat_to_int(&read_digit, 1000, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_int32(next_target.target.Y);
break;
case 'Z':
if (next_target.option_inches)
next_target.target.Z = decfloat_to_int(&read_digit, 25400, 1);
else
next_target.target.Z = decfloat_to_int(&read_digit, 1000, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_int32(next_target.target.Z);
break;
case 'E':
if (next_target.option_inches)
next_target.target.E = decfloat_to_int(&read_digit, 25400, 1);
else
next_target.target.E = decfloat_to_int(&read_digit, 1000, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint32(next_target.target.E);
break;
case 'F':
// just use raw integer, we need move distance and n_steps to convert it to a useful value, so wait until we have those to convert it
if (next_target.option_inches)
next_target.target.F = decfloat_to_int(&read_digit, 25400, 1);
else
next_target.target.F = decfloat_to_int(&read_digit, 1, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint32(next_target.target.F);
break;
case 'S':
// if this is temperature, multiply by 4 to convert to quarter-degree units
// cosmetically this should be done in the temperature section,
// but it takes less code, less memory and loses no precision if we do it here instead
if ((next_target.M == 104) || (next_target.M == 109) || (next_target.M == 140))
next_target.S = decfloat_to_int(&read_digit, 4, 0);
// if this is heater PID stuff, multiply by PID_SCALE because we divide by PID_SCALE later on
else if ((next_target.M >= 130) && (next_target.M <= 132))
next_target.S = decfloat_to_int(&read_digit, PID_SCALE, 0);
else
next_target.S = decfloat_to_int(&read_digit, 1, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint16(next_target.S);
break;
case 'P':
next_target.P = decfloat_to_int(&read_digit, 1, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint16(next_target.P);
break;
case 'T':
next_target.T = read_digit.mantissa;
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint8(next_target.T);
break;
case 'N':
next_target.N = decfloat_to_int(&read_digit, 1, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint32(next_target.N);
break;
case '*':
next_target.checksum_read = decfloat_to_int(&read_digit, 1, 0);
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serwrite_uint8(next_target.checksum_read);
break;
}
// reset for next field
last_field = 0;
read_digit.sign = read_digit.mantissa = read_digit.exponent = 0;
}
}
// skip comments
if (next_target.seen_semi_comment == 0 && next_target.seen_parens_comment == 0) {
// new field?
if ((c >= 'A' && c <= 'Z') || c == '*') {
last_field = c;
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serial_writechar(c);
}
// process character
switch (c) {
// each currently known command is either G or M, so preserve previous G/M unless a new one has appeared
// FIXME: same for T command
case 'G':
next_target.seen_G = 1;
next_target.seen_M = 0;
next_target.M = 0;
break;
case 'M':
next_target.seen_M = 1;
next_target.seen_G = 0;
next_target.G = 0;
break;
case 'X':
next_target.seen_X = 1;
break;
case 'Y':
next_target.seen_Y = 1;
break;
case 'Z':
next_target.seen_Z = 1;
break;
case 'E':
next_target.seen_E = 1;
break;
case 'F':
next_target.seen_F = 1;
break;
case 'S':
next_target.seen_S = 1;
break;
case 'P':
next_target.seen_P = 1;
break;
case 'T':
next_target.seen_T = 1;
break;
case 'N':
next_target.seen_N = 1;
break;
case '*':
next_target.seen_checksum = 1;
break;
// comments
case ';':
next_target.seen_semi_comment = 1;
break;
case '(':
next_target.seen_parens_comment = 1;
break;
// now for some numeracy
case '-':
read_digit.sign = 1;
// force sign to be at start of number, so 1-2 = -2 instead of -12
read_digit.exponent = 0;
read_digit.mantissa = 0;
break;
case '.':
if (read_digit.exponent == 0)
read_digit.exponent = 1;
break;
#ifdef DEBUG
case ' ':
case '\t':
case 10:
case 13:
// ignore
break;
#endif
default:
// can't do ranges in switch..case, so process actual digits here.
if (c >= '0' && c <= '9') {
if (read_digit.exponent < DECFLOAT_EXP_MAX &&
((next_target.option_inches == 0 &&
read_digit.mantissa < DECFLOAT_MANT_MM_MAX) ||
(next_target.option_inches &&
read_digit.mantissa < DECFLOAT_MANT_IN_MAX)))
{
// this is simply mantissa = (mantissa * 10) + atoi(c) in different clothes
read_digit.mantissa = (read_digit.mantissa << 3) + (read_digit.mantissa << 1) + (c - '0');
if (read_digit.exponent)
read_digit.exponent++;
}
}
#ifdef DEBUG
else {
// invalid
serial_writechar('?');
serial_writechar(c);
serial_writechar('?');
}
#endif
}
} else if ( next_target.seen_parens_comment == 1 && c == ')')
next_target.seen_parens_comment = 0; // recognize stuff after a (comment)
if (next_target.seen_checksum == 0)
next_target.checksum_calculated = crc(next_target.checksum_calculated, c);
// end of line
if ((c == 10) || (c == 13)) {
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
serial_writechar(c);
if (
#ifdef REQUIRE_LINENUMBER
((next_target.N >= next_target.N_expected) && (next_target.seen_N == 1)) ||
(next_target.seen_M && (next_target.M == 110))
#else
1
#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
serial_writestr_P(PSTR("ok "));
process_gcode_command();
serial_writechar('\n');
// expect next line number
if (next_target.seen_N == 1)
next_target.N_expected = next_target.N + 1;
}
else {
sersendf_P(PSTR("rs N%ld Expected checksum %d\n"), next_target.N_expected, next_target.checksum_calculated);
// request_resend();
}
}
else {
sersendf_P(PSTR("rs N%ld Expected line number %ld\n"), next_target.N_expected, next_target.N_expected);
// request_resend();
}
// 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_T = next_target.seen_N = \
next_target.seen_M = next_target.seen_checksum = next_target.seen_semi_comment = \
next_target.seen_parens_comment = next_target.checksum_read = \
next_target.checksum_calculated = 0;
// last_field and read_digit are reset above already
// assume a G1 by default
next_target.seen_G = 1;
next_target.G = 1;
if (next_target.option_relative) {
next_target.target.X = next_target.target.Y = next_target.target.Z = 0;
#ifdef E_ABSOLUTE
next_target.target.E = 0;
#endif
}
#ifndef E_ABSOLUTE
// E always relative
next_target.target.E = 0;
#endif
}
}
/***************************************************************************\
* *
* Request a resend of the current line - used from various places. *
* *
* Relies on the global variable next_target.N being valid. *
* *
\***************************************************************************/
void request_resend(void) {
serial_writestr_P(PSTR("rs "));
serwrite_uint8(next_target.N);
serial_writechar('\n');
}
Jump to Line
Something went wrong with that request. Please try again.