Example #1
0
void sersendf_P(PGM_P format, ...) {
	va_list args;
	va_start(args, format);

	uint16_t i = 0;
	uint8_t c = 1, j = 0;
	while ((c = pgm_read_byte(&format[i++]))) {
		if (j) {
			switch(c) {
				case 's':
					j = 1;
					break;
				case 'l':
					j = 4;
					break;
				case 'u':
					if (j == 4)
						serwrite_uint32(va_arg(args, uint32_t));
					else
						serwrite_uint16(va_arg(args, unsigned int));
					j = 0;
					break;
				case 'd':
					if (j == 4)
						serwrite_int32(va_arg(args, int32_t));
					else
						serwrite_int16(va_arg(args, int));
					j = 0;
					break;
/*				case 'x':
					serial_writestr_P(str_ox);
					if (j == 4)
						serwrite_hex32(va_arg(args, uint32_t));
					else if (j == 1)
						serwrite_hex8(va_arg(args, uint16_t));
					else
						serwrite_hex16(va_arg(args, uint16_t));
					j = 0;
					break;
				case 'c':
					serial_writechar(va_arg(args, uint16_t));
				case 'p':
					serwrite_hex16(va_arg(args, uint16_t));
				default:
					j = 0;
					break;*/
			}
		}
		else {
			if (c == '%') {
				j = 2;
			}
			else {
				serial_writechar(c);
			}
		}
	}
	va_end(args);
}
Example #2
0
void sersendf(char *format, ...) {
	va_list args;
	va_start(args, format);

	uint16_t i = 0;
	uint8_t c, j = 0;
	while ((c = format[i++])) {
		if (j) {
			switch(c) {
				case 'l':
					j = 4;
				case 'u':
					if (j == 4)
						serwrite_uint32(va_arg(args, uint32_t));
					else
						serwrite_uint16(va_arg(args, unsigned int));
					j = 0;
					break;
				case 'd':
					if (j == 4)
						serwrite_int32(va_arg(args, int32_t));
					else
						serwrite_int16(va_arg(args, int));
					j = 0;
					break;
				case 'p':
				case 'x':
					serial_writestr_P(str_ox);
					if (j == 4)
						serwrite_hex32(va_arg(args, uint32_t));
					else
						serwrite_hex16(va_arg(args, unsigned int));
					j = 0;
					break;
				case 'c':
					serial_writechar(va_arg(args, unsigned int));
					j = 0;
					break;
				case 's':
					serial_writestr(va_arg(args, uint8_t *));
					j = 0;
					break;
				default:
					j = 0;
					break;
			}
		}
		else {
			if (c == '%') {
				j = 2;
			}
			else {
				serial_writechar(c);
			}
		}
	}
	va_end(args);
}
void read_device_serial_number(void)
{
	  param_table[0] = READ_DEVICE_SERIAL;

	  IAP_entry(param_table,result_table);
	  if(result_table[0] != CMD_SUCCESS)
	  {
		  serial_writestr("Error: reading serial ");
	  }
	  else
	  {
		  serwrite_uint32(result_table[1]);
		  serial_writestr(" ");
		  serwrite_uint32(result_table[2]);
		  serial_writestr(" ");
		  serwrite_uint32(result_table[3]);
		  serial_writestr(" ");
		  serwrite_uint32(result_table[4]);
		  serial_writestr(" ");
	  }
}
Example #4
0
eParseResult gcode_parse_line (tLineBuffer *pLine) 
{
  int j;
  eParseResult result = PR_OK;

  for (j=0; j < pLine->len; j++)
  {
    gcode_parse_char (pLine->data [j]);
  }

  // end of line
  //if ((c == 10) || (c == 13))
  {
    if (
    #ifdef	REQUIRE_LINENUMBER
        (next_target.N >= next_target.N_expected) && (next_target.seen_N == 1)
    #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
              )
      {
        if (sd_writing_file)
        {
          if (next_target.seen_M && (next_target.M >= 20) && (next_target.M <= 29))
          {
            if (next_target.seen_M && next_target.M == 29)
            {
              // M29 - stop writing
              sd_writing_file = false;
              sd_close (&file);
              serial_writestr("Done saving file\r\n");
            }
            else
            {
              // else - do not write SD M-codes to file
              serial_writestr("ok\r\n");
            }
          }
          else
          {
            // lines in files must be LF terminated for sd_read_file to work
            if (pLine->data [pLine->len-1] == 13)
            {
              pLine->data [pLine->len-1] = 10;
            }

            if (sd_write_to_file(pLine->data, pLine->len))
            {
              serial_writestr("ok\r\n");
            }
            else
            {
              serial_writestr("error writing to file\r\n");
            }
          }
        }
        else
        {
          // process
          result = process_gcode_command();

          // expect next line number
          if (next_target.seen_N == 1)
          {
            next_target.N_expected = next_target.N + 1;
          }
        }
      }
      else
      {
        serial_writestr("Expected checksum ");
        serwrite_uint8(next_target.checksum_calculated);
        serial_writestr("\r\n");
        request_resend();
      }
    }
    else
    {
      serial_writestr("Expected line number ");
      serwrite_uint32(next_target.N_expected);
      serial_writestr("\r\n");
      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_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;
    next_target.chpos = 0;
    last_field = 0;
    read_digit.sign = read_digit.exponent = 0;
    value = 0;

    // dont assume a G1 by default
    next_target.seen_G = 0;
    next_target.G = 0;

    if (next_target.option_relative)
    {
      next_target.target.x = next_target.target.y = next_target.target.z = 0.0;
      next_target.target.e = 0.0;
    }
  }

  return result;
}
Example #5
0
void dda_create(DDA *dda, TARGET *target) {
    uint32_t	distance, c_limit, c_limit_calc;

    // initialise DDA to a known state
    dda->allflags = 0;

    if (debug_flags & DEBUG_DDA)
        serial_writestr_P(PSTR("\n{DDA_CREATE: ["));

    // we end at the passed target
    memcpy(&(dda->endpoint), target, sizeof(TARGET));

    dda->x_delta = ABS(target->X - startpoint.X);
    dda->y_delta = ABS(target->Y - startpoint.Y);
    dda->z_delta = ABS(target->Z - startpoint.Z);
    dda->e_delta = ABS(target->E - startpoint.E);

    dda->x_direction = (target->X >= startpoint.X)?1:0;
    dda->y_direction = (target->Y >= startpoint.Y)?1:0;
    dda->z_direction = (target->Z >= startpoint.Z)?1:0;
    dda->e_direction = (target->E >= startpoint.E)?1:0;

    if (debug_flags & DEBUG_DDA) {
        if (dda->x_direction == 0)
            serial_writechar('-');
        serwrite_uint32(dda->x_delta);
        serial_writechar(',');
        if (dda->y_direction == 0)
            serial_writechar('-');
        serwrite_uint32(dda->y_delta);
        serial_writechar(',');
        if (dda->z_direction == 0)
            serial_writechar('-');
        serwrite_uint32(dda->z_delta);
        serial_writechar(',');
        if (dda->e_direction == 0)
            serial_writechar('-');
        serwrite_uint32(dda->e_delta);

        serial_writestr_P(PSTR("] ["));
    }

    dda->total_steps = dda->x_delta;
    if (dda->y_delta > dda->total_steps)
        dda->total_steps = dda->y_delta;
    if (dda->z_delta > dda->total_steps)
        dda->total_steps = dda->z_delta;
    if (dda->e_delta > dda->total_steps)
        dda->total_steps = dda->e_delta;

    if (debug_flags & DEBUG_DDA) {
        serial_writestr_P(PSTR("ts:"));
        serwrite_uint32(dda->total_steps);
    }

    if (dda->total_steps == 0) {
        dda->nullmove = 1;
    }
    else {
        // get steppers ready to go
        steptimeout = 0;
        power_on();

        dda->x_counter = dda->y_counter = dda->z_counter = dda->e_counter =
                                              -(dda->total_steps >> 1);

        // since it's unusual to combine X, Y and Z changes in a single move on reprap, check if we can use simpler approximations before trying the full 3d approximation.
        if (dda->z_delta == 0)
            distance = approx_distance(dda->x_delta * UM_PER_STEP_X, dda->y_delta * UM_PER_STEP_Y);
        else if (dda->x_delta == 0 && dda->y_delta == 0)
            distance = dda->z_delta * UM_PER_STEP_Z;
        else
            distance = approx_distance_3(dda->x_delta * UM_PER_STEP_X, dda->y_delta * UM_PER_STEP_Y, dda->z_delta * UM_PER_STEP_Z);

        if (distance < 2)
            distance = dda->e_delta * UM_PER_STEP_E;

        if (debug_flags & DEBUG_DDA) {
            serial_writestr_P(PSTR(",ds:"));
            serwrite_uint32(distance);
        }

        // pre-calculate move speed in millimeter microseconds per step minute for less math in interrupt context
        // mm (distance) * 60000000 us/min / step (total_steps) = mm.us per step.min
        //   note: um (distance) * 60000 == mm * 60000000
        // so in the interrupt we must simply calculate
        // mm.us per step.min / mm per min (F) = us per step

        // break this calculation up a bit and lose some precision because 300,000um * 60000 is too big for a uint32
        // calculate this with a uint64 if you need the precision, but it'll take longer so routines with lots of short moves may suffer
        // 2^32/6000 is about 715mm which should be plenty

        // changed * 10 to * (F_CPU / 100000) so we can work in cpu_ticks rather than microseconds.
        // timer.c setTimer() routine altered for same reason

        // changed distance * 6000 .. * F_CPU / 100000 to
        //         distance * 2400 .. * F_CPU / 40000 so we can move a distance of up to 1800mm without overflowing
        uint32_t move_duration = ((distance * 2400) / dda->total_steps) * (F_CPU / 40000);

        // similarly, find out how fast we can run our axes.
        // do this for each axis individually, as the combined speed of two or more axes can be higher than the capabilities of a single one.
        c_limit = 0;
        c_limit_calc = ( (dda->x_delta * (UM_PER_STEP_X * 2400L)) / dda->total_steps * (F_CPU / 40000) / MAXIMUM_FEEDRATE_X) << 8;
        if (c_limit_calc > c_limit)
            c_limit = c_limit_calc;
        c_limit_calc = ( (dda->y_delta * (UM_PER_STEP_Y * 2400L)) / dda->total_steps * (F_CPU / 40000) / MAXIMUM_FEEDRATE_Y) << 8;
        if (c_limit_calc > c_limit)
            c_limit = c_limit_calc;
        c_limit_calc = ( (dda->z_delta * (UM_PER_STEP_Z * 2400L)) / dda->total_steps * (F_CPU / 40000) / MAXIMUM_FEEDRATE_Z) << 8;
        if (c_limit_calc > c_limit)
            c_limit = c_limit_calc;
        c_limit_calc = ( (dda->e_delta * (UM_PER_STEP_E * 2400L)) / dda->total_steps * (F_CPU / 40000) / MAXIMUM_FEEDRATE_E) << 8;
        if (c_limit_calc > c_limit)
            c_limit = c_limit_calc;

#ifdef ACCELERATION_REPRAP
        // c is initial step time in IOclk ticks
        dda->c = (move_duration / startpoint.F) << 8;
        if (dda->c < c_limit)
            dda->c = c_limit;
        dda->end_c = (move_duration / target->F) << 8;
        if (dda->end_c < c_limit)
            dda->end_c = c_limit;

        if (debug_flags & DEBUG_DDA) {
            serial_writestr_P(PSTR(",md:"));
            serwrite_uint32(move_duration);
            serial_writestr_P(PSTR(",c:"));
            serwrite_uint32(dda->c >> 8);
        }

        if (dda->c != dda->end_c) {
            uint32_t stF = startpoint.F / 4;
            uint32_t enF = target->F / 4;
            // now some constant acceleration stuff, courtesy of http://www.embedded.com/columns/technicalinsights/56800129?printable=true
            uint32_t ssq = (stF * stF);
            uint32_t esq = (enF * enF);
            int32_t dsq = (int32_t) (esq - ssq) / 4;

            uint8_t msb_ssq = msbloc(ssq);
            uint8_t msb_tot = msbloc(dda->total_steps);

            // the raw equation WILL overflow at high step rates, but 64 bit math routines take waay too much space
            // at 65536 mm/min (1092mm/s), ssq/esq overflows, and dsq is also close to overflowing if esq/ssq is small
            // but if ssq-esq is small, ssq/dsq is only a few bits
            // we'll have to do it a few different ways depending on the msb locations of each
            if ((msb_tot + msb_ssq) <= 30) {
                // we have room to do all the multiplies first
                if (debug_flags & DEBUG_DDA)
                    serial_writechar('A');
                dda->n = ((int32_t) (dda->total_steps * ssq) / dsq) + 1;
            }
            else if (msb_tot >= msb_ssq) {
                // total steps has more precision
                if (debug_flags & DEBUG_DDA)
                    serial_writechar('B');
                dda->n = (((int32_t) dda->total_steps / dsq) * (int32_t) ssq) + 1;
            }
            else {
                // otherwise
                if (debug_flags & DEBUG_DDA)
                    serial_writechar('C');
                dda->n = (((int32_t) ssq / dsq) * (int32_t) dda->total_steps) + 1;
            }

            if (debug_flags & DEBUG_DDA) {
                sersendf_P(PSTR("\n{DDA:CA end_c:%lu, n:%ld, md:%lu, ssq:%lu, esq:%lu, dsq:%lu, msbssq:%u, msbtot:%u}\n"),
                           (long unsigned int)dda->end_c >> 8,
                           (long int)dda->n,
                           (long unsigned int)move_duration,
                           (long unsigned int)ssq,
                           (long unsigned int)esq,
                           (long unsigned int)dsq,
                           msb_ssq,
                           msb_tot);
            }
/// 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, STEPS_PER_IN_X, 0);
					else
						next_target.target.X = decfloat_to_int(&read_digit, STEPS_PER_M_X, 1);
					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, STEPS_PER_IN_Y, 0);
					else
						next_target.target.Y = decfloat_to_int(&read_digit, STEPS_PER_M_Y, 1);
					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, STEPS_PER_IN_Z, 0);
					else
						next_target.target.Z = decfloat_to_int(&read_digit, STEPS_PER_M_Z, 1);
					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, STEPS_PER_IN_E, 0);
					else
						next_target.target.E = decfloat_to_int(&read_digit, STEPS_PER_M_E, 1);
					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
				#ifdef DEBUG
				sersendf_P(PSTR("received line %lu\n"),next_target.N);
				#endif
				serial_writestr_P(PSTR("ok "));
				process_gcode_command();
				serial_writechar('\n');
				#ifdef DEBUG
				print_queue();
				serial_writechar('\n');
				#endif

				// 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
	}
}
Example #7
0
/** Character received - add it to our command.

  \param c The next character to process.

  \return Whether end of line was reached.

  This parser operates character by character, so there's no need for a
  buffer holding the entire line of G-code.
*/
uint8_t gcode_parse_char(uint8_t c) {
	uint8_t checksum_char = c;

	// uppercase
	if (c >= 'a' && c <= 'z')
		c &= ~32;
#ifdef SIMULATOR
  sim_gcode_ch(c);
#endif

  // An asterisk is a quasi-EOL and always ends all fields.
  if (c == '*') {
    next_target.seen_semi_comment = next_target.seen_parens_comment =
    next_target.read_string = 0;
  }

  // Skip comments and strings.
  if (next_target.seen_semi_comment == 0 &&
      next_target.seen_parens_comment == 0 &&
      next_target.read_string == 0
     ) {
    // Check if the field has ended. Either by a new field, space or EOL.
    if (last_field && (c < '0' || c > '9') && c != '.') {
			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;
          #ifdef SD
            if (next_target.M == 23) {
              // SD card command with a filename.
              next_target.read_string = 1;  // Reset by string handler or EOL.
              str_buf_ptr = 0;
              last_field = 0;
            }
          #endif
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
						serwrite_uint8(next_target.M);
					break;
				case 'X':
					if (next_target.option_inches)
            next_target.target.axis[X] = decfloat_to_int(&read_digit, 25400);
					else
            next_target.target.axis[X] = decfloat_to_int(&read_digit, 1000);
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
            serwrite_int32(next_target.target.axis[X]);
					break;
				case 'Y':
					if (next_target.option_inches)
            next_target.target.axis[Y] = decfloat_to_int(&read_digit, 25400);
					else
            next_target.target.axis[Y] = decfloat_to_int(&read_digit, 1000);
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
            serwrite_int32(next_target.target.axis[Y]);
					break;
				case 'Z':
					if (next_target.option_inches)
            next_target.target.axis[Z] = decfloat_to_int(&read_digit, 25400);
					else
            next_target.target.axis[Z] = decfloat_to_int(&read_digit, 1000);
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
            serwrite_int32(next_target.target.axis[Z]);
					break;
				case 'E':
					if (next_target.option_inches)
            next_target.target.axis[E] = decfloat_to_int(&read_digit, 25400);
					else
            next_target.target.axis[E] = decfloat_to_int(&read_digit, 1000);
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
            serwrite_int32(next_target.target.axis[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);
					else
						next_target.target.F = decfloat_to_int(&read_digit, 1);
					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);
					// 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);
					else
						next_target.S = decfloat_to_int(&read_digit, 1);
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
						serwrite_int32(next_target.S);
					break;
				case 'P':
					next_target.P = decfloat_to_int(&read_digit, 1);
					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);
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
						serwrite_uint32(next_target.N);
					break;
				case '*':
					next_target.checksum_read = decfloat_to_int(&read_digit, 1);
					if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
						serwrite_uint8(next_target.checksum_read);
					break;
			}
		}

		// new field?
		if ((c >= 'A' && c <= 'Z') || c == '*') {
			last_field = c;
      read_digit.sign = read_digit.mantissa = read_digit.exponent = 0;
			if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
				serial_writechar(c);
		}

		// process character
    // Can't do ranges in switch..case, so process actual digits here.
    // Do it early, as there are many more digits than characters expected.
    if (c >= '0' && c <= '9') {
      if (read_digit.exponent < DECFLOAT_EXP_MAX + 1 &&
          ((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++;
      }
    }
    else {
      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;    // Reset by EOL.
          break;
        case '(':
          next_target.seen_parens_comment = 1;  // Reset by ')' or EOL.
          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:
          #ifdef	DEBUG
            // invalid
            serial_writechar('?');
            serial_writechar(c);
            serial_writechar('?');
          #endif
          break;
      }
		}
	} 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, checksum_char);

	// end of line
	if ((c == 10) || (c == 13)) {
		if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
			serial_writechar(c);

    // Assume G1 for unspecified movements.
    if ( ! next_target.seen_G && ! next_target.seen_M && ! next_target.seen_T &&
        (next_target.seen_X || next_target.seen_Y || next_target.seen_Z ||
         next_target.seen_E || next_target.seen_F)) {
      next_target.seen_G = 1;
      next_target.G = 1;
    }

		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
				process_gcode_command();

        // Acknowledgement ("ok") is sent in the main loop, in mendel.c.

				// 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_G = next_target.seen_M = next_target.seen_checksum = \
      next_target.seen_semi_comment = next_target.seen_parens_comment = \
      next_target.read_string = next_target.checksum_read = \
      next_target.checksum_calculated = 0;
      last_field = 0;
      read_digit.sign = read_digit.mantissa = read_digit.exponent = 0;

		if (next_target.option_all_relative) {
      next_target.target.axis[X] = next_target.target.axis[Y] = next_target.target.axis[Z] = 0;
		}
		if (next_target.option_all_relative || next_target.option_e_relative) {
      next_target.target.axis[E] = 0;
		}

    return 1;
	}

  #ifdef SD
  // Handle string reading. After checking for EOL.
  if (next_target.read_string) {
    if (c == ' ') {
      if (str_buf_ptr)
        next_target.read_string = 0;
    }
    else if (str_buf_ptr < STR_BUF_LEN) {
      gcode_str_buf[str_buf_ptr] = c;
      str_buf_ptr++;
      gcode_str_buf[str_buf_ptr] = '\0';
    }
  }
  #endif /* SD */

  return 0;
}