MeatPack gcode compression support
This commit is contained in:
parent
3b9d1b8980
commit
3cd125c566
|
|
@ -142,6 +142,7 @@ set(FW_SOURCES
|
|||
lcd.cpp
|
||||
Marlin_main.cpp
|
||||
MarlinSerial.cpp
|
||||
meatpack.cpp
|
||||
menu.cpp
|
||||
mesh_bed_calibration.cpp
|
||||
mesh_bed_leveling.cpp
|
||||
|
|
@ -177,6 +178,7 @@ set(FW_SOURCES
|
|||
spi.c
|
||||
SpoolJoin.cpp
|
||||
stepper.cpp
|
||||
strtod.c
|
||||
swi2c.c
|
||||
Tcodes.cpp
|
||||
temperature.cpp
|
||||
|
|
|
|||
|
|
@ -94,6 +94,9 @@ extern const char _sPrinterMmuName[] PROGMEM;
|
|||
// This determines the communication speed of the printer
|
||||
#define BAUDRATE 115200
|
||||
|
||||
// Enable g-code compression (see https://github.com/scottmudge/OctoPrint-MeatPack)
|
||||
#define ENABLE_MEATPACK
|
||||
|
||||
// This enables the serial port associated to the Bluetooth interface
|
||||
//#define BTENABLED // Enable BT interface on AT90USB devices
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "cardreader.h"
|
||||
#include "ultralcd.h"
|
||||
#include "Prusa_farm.h"
|
||||
#include "meatpack.h"
|
||||
|
||||
// Reserve BUFSIZE lines of length MAX_CMD_SIZE plus CMDBUFFER_RESERVE_FRONT.
|
||||
char cmdbuffer[BUFSIZE * (MAX_CMD_SIZE + 1) + CMDBUFFER_RESERVE_FRONT];
|
||||
|
|
@ -365,7 +366,20 @@ void get_command()
|
|||
// start of serial line processing loop
|
||||
while (((MYSERIAL.available() > 0 && !saved_printing) || (MYSERIAL.available() > 0 && isPrintPaused)) && !cmdqueue_serial_disabled) { //is print is saved (crash detection or filament detection), dont process data from serial line
|
||||
|
||||
#ifdef ENABLE_MEATPACK
|
||||
// MeatPack Changes
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
const int rec = MYSERIAL.read();
|
||||
if (rec < 0) continue;
|
||||
|
||||
mp_handle_rx_char((uint8_t)rec);
|
||||
char c_res[2] = {0, 0};
|
||||
const uint8_t char_count = mp_get_result_char(c_res);
|
||||
// Note -- Paired bracket in preproc switch below
|
||||
for (uint8_t i = 0; i < char_count; ++i) { char serial_char = c_res[i];
|
||||
#else
|
||||
char serial_char = MYSERIAL.read();
|
||||
#endif
|
||||
|
||||
serialTimeoutTimer.start();
|
||||
|
||||
|
|
@ -526,6 +540,9 @@ void get_command()
|
|||
if(serial_char == ';') comment_mode = true;
|
||||
if(!comment_mode) cmdbuffer[bufindw+CMDHDRSIZE+serial_count++] = serial_char;
|
||||
}
|
||||
#ifdef ENABLE_MEATPACK
|
||||
}
|
||||
#endif
|
||||
} // end of serial line processing loop
|
||||
|
||||
if (serial_count > 0 && serialTimeoutTimer.expired(farm_mode ? 800 : 2000)) {
|
||||
|
|
|
|||
|
|
@ -71,10 +71,19 @@ extern void repeatcommand_front();
|
|||
extern void get_command();
|
||||
extern uint16_t cmdqueue_calc_sd_length();
|
||||
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
extern double strtod_noE(const char* nptr, char** endptr);
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Return True if a character was found
|
||||
static inline bool code_seen(char code) { return (strchr_pointer = strchr(CMDBUFFER_CURRENT_STRING, code)) != NULL; }
|
||||
static inline bool code_seen_P(const char *code_PROGMEM) { return (strchr_pointer = strstr_P(CMDBUFFER_CURRENT_STRING, code_PROGMEM)) != NULL; }
|
||||
static inline float code_value() { return strtod(strchr_pointer+1, NULL);}
|
||||
static inline float code_value() { return strtod_noE(strchr_pointer+1, NULL);}
|
||||
static inline long code_value_long() { return strtol(strchr_pointer+1, NULL, 10); }
|
||||
static inline int16_t code_value_short() { return int16_t(strtol(strchr_pointer+1, NULL, 10)); };
|
||||
static inline uint8_t code_value_uint8() { return uint8_t(strtol(strchr_pointer+1, NULL, 10)); };
|
||||
|
|
|
|||
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* MeatPack G-Code Compression
|
||||
*
|
||||
* Algorithm & Implementation: Scott Mudge - mail@scottmudge.com
|
||||
* Date: Dec. 2020
|
||||
*/
|
||||
|
||||
#include "meatpack.h"
|
||||
|
||||
#ifdef ENABLE_MEATPACK
|
||||
|
||||
#include "language.h"
|
||||
#include "Marlin.h"
|
||||
|
||||
//#define MP_DEBUG
|
||||
|
||||
// Utility definitions
|
||||
#define MeatPack_CommandByte 0b11111111
|
||||
#define MeatPack_NextPackedFirst 0b00000001
|
||||
#define MeatPack_NextPackedSecond 0b00000010
|
||||
|
||||
#define MeatPack_SpaceCharIdx 11U
|
||||
#define MeatPack_SpaceCharReplace 'E'
|
||||
|
||||
#define MeatPack_ProtocolVersion "PV01"
|
||||
|
||||
/*
|
||||
|
||||
Character Frequencies from ~30 MB of comment-stripped gcode:
|
||||
|
||||
'1' -> 4451136
|
||||
'0' -> 4253577
|
||||
' ' -> 3053297
|
||||
'.' -> 3035310
|
||||
'2' -> 1523296
|
||||
'8' -> 1366812
|
||||
'4' -> 1353273
|
||||
'9' -> 1352147
|
||||
'3' -> 1262929
|
||||
'5' -> 1189871
|
||||
'6' -> 1127900
|
||||
'7' -> 1112908
|
||||
'\n' -> 1087683
|
||||
'G' -> 1075806
|
||||
'X' -> 975742
|
||||
'E' -> 965275
|
||||
'Y' -> 965274
|
||||
'F' -> 99416
|
||||
'-' -> 90242
|
||||
'Z' -> 34109
|
||||
'M' -> 11879
|
||||
'S' -> 9910
|
||||
|
||||
If spaces are omitted, we add 'E'
|
||||
|
||||
*/
|
||||
|
||||
// Note:
|
||||
// I've tried both a switch/case method and a lookup table. The disassembly is exactly the same after compilation, byte-to-byte.
|
||||
// Thus, performance is identical.
|
||||
#define USE_LOOKUP_TABLE
|
||||
|
||||
// State variables
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
enum MeatPack_ConfigStateFlags {
|
||||
MPConfig_None = 0,
|
||||
MPConfig_Active = (1 << 0),
|
||||
MPConfig_NoSpaces = (1 << 1)
|
||||
};
|
||||
|
||||
uint8_t mp_config = MPConfig_None; // Configuration state
|
||||
uint8_t mp_cmd_active = 0; // Is a command is pending
|
||||
uint8_t mp_char_buf = 0; // Buffers a character if dealing with out-of-sequence pairs
|
||||
uint8_t mp_cmd_count = 0; // Counts how many command bytes are received (need 2)
|
||||
uint8_t mp_full_char_queue = 0; // Counts how many full-width characters are to be received
|
||||
uint8_t mp_char_out_buf[2]; // Output buffer for caching up to 2 characters
|
||||
uint8_t mp_char_out_count = 0; // Stores number of characters to be read out.
|
||||
|
||||
|
||||
#ifdef USE_LOOKUP_TABLE
|
||||
// The 15 most-common characters used in G-code, ~90-95% of all g-code uses these characters
|
||||
// NOT storing this with PROGMEM, given how frequently this table will be accessed.
|
||||
uint8_t MeatPackLookupTbl[16] = {
|
||||
'0', // 0000
|
||||
'1', // 0001
|
||||
'2', // 0010
|
||||
'3', // 0011
|
||||
'4', // 0100
|
||||
'5', // 0101
|
||||
'6', // 0110
|
||||
'7', // 0111
|
||||
'8', // 1000
|
||||
'9', // 1001
|
||||
'.', // 1010
|
||||
' ', // 1011
|
||||
'\n', // 1100
|
||||
'G', // 1101
|
||||
'X', // 1110
|
||||
'\0' // never used, 0b1111 is used to indicate next 8-bits is a full character
|
||||
};
|
||||
#else
|
||||
inline uint8_t get_char(const uint8_t in) {
|
||||
switch (in) {
|
||||
case 0b0000:
|
||||
return '0';
|
||||
break;
|
||||
case 0b0001:
|
||||
return '1';
|
||||
break;
|
||||
case 0b0010:
|
||||
return '2';
|
||||
break;
|
||||
case 0b0011:
|
||||
return '3';
|
||||
break;
|
||||
case 0b0100:
|
||||
return '4';
|
||||
break;
|
||||
case 0b0101:
|
||||
return '5';
|
||||
break;
|
||||
case 0b0110:
|
||||
return '6';
|
||||
break;
|
||||
case 0b0111:
|
||||
return '7';
|
||||
break;
|
||||
case 0b1000:
|
||||
return '8';
|
||||
break;
|
||||
case 0b1001:
|
||||
return '9';
|
||||
break;
|
||||
case 0b1010:
|
||||
return '.';
|
||||
break;
|
||||
case 0b1011:
|
||||
return (mp_config & MPConfig_NoSpaces) ? MeatPack_SpaceCharReplace : ' ';
|
||||
break;
|
||||
case 0b1100:
|
||||
return '\n';
|
||||
break;
|
||||
case 0b1101:
|
||||
return 'G';
|
||||
break;
|
||||
case 0b1110:
|
||||
return 'X';
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// #DEBUGGING
|
||||
#ifdef MP_DEBUG
|
||||
uint32_t mp_chars_decoded = 0;
|
||||
#endif
|
||||
|
||||
void FORCE_INLINE mp_handle_output_char(const uint8_t c) {
|
||||
mp_char_out_buf[mp_char_out_count++] = c;
|
||||
|
||||
#ifdef MP_DEBUG
|
||||
if (mp_chars_decoded < 4096) {
|
||||
++mp_chars_decoded;
|
||||
SERIAL_ECHOPGM("RB: ");
|
||||
MYSERIAL.print((char)c);
|
||||
SERIAL_ECHOLNPGM("");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Storing
|
||||
// packed = ((low & 0xF) << 4) | (high & 0xF);
|
||||
|
||||
// Unpacking
|
||||
// low = (packed >> 4) & 0xF;
|
||||
// high = (packed & 0xF);
|
||||
|
||||
//==========================================================================
|
||||
uint8_t FORCE_INLINE mp_unpack_chars(const uint8_t pk, uint8_t* __restrict const chars_out) {
|
||||
uint8_t out = 0;
|
||||
|
||||
#ifdef USE_LOOKUP_TABLE
|
||||
// If lower 4 bytes is 0b1111, the higher 4 are unused, and next char is full.
|
||||
if ((pk & MeatPack_FirstNotPacked) == MeatPack_FirstNotPacked) out |= MeatPack_NextPackedFirst;
|
||||
else chars_out[0] = MeatPackLookupTbl[(pk & 0xF)]; // Assign lower char
|
||||
|
||||
// Check if upper 4 bytes is 0b1111... if so, we don't need the second char.
|
||||
if ((pk & MeatPack_SecondNotPacked) == MeatPack_SecondNotPacked) out |= MeatPack_NextPackedSecond;
|
||||
else chars_out[1] = MeatPackLookupTbl[((pk >> 4) & 0xf)]; // Assign upper char
|
||||
#else
|
||||
// If lower 4 bytes is 0b1111, the higher 4 are unused, and next char is full.
|
||||
if ((pk & MeatPack_FirstNotPacked) == MeatPack_FirstNotPacked) out |= MeatPack_NextPackedFirst;
|
||||
else chars_out[0] = get_char(pk & 0xF); // Assign lower char
|
||||
|
||||
// Check if upper 4 bytes is 0b1111... if so, we don't need the second char.
|
||||
if ((pk & MeatPack_SecondNotPacked) == MeatPack_SecondNotPacked) out |= MeatPack_NextPackedSecond;
|
||||
else chars_out[1] = get_char((pk >> 4) & 0xf); // Assign upper char
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FORCE_INLINE mp_reset_state() {
|
||||
mp_char_out_count = 0;
|
||||
mp_cmd_active = MPCommand_None;
|
||||
mp_config = MPConfig_None;
|
||||
mp_char_buf = 0;
|
||||
mp_cmd_count = 0;
|
||||
mp_cmd_active = 0;
|
||||
mp_full_char_queue = 0;
|
||||
|
||||
#ifdef MP_DEBUG
|
||||
mp_chars_decoded = 0;
|
||||
SERIAL_ECHOLNPGM("MP Reset");
|
||||
#endif
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
void FORCE_INLINE mp_handle_rx_char_inner(const uint8_t c) {
|
||||
|
||||
// Packing enabled, handle character and re-arrange them appropriately.
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if (mp_config & MPConfig_Active) {
|
||||
if (mp_full_char_queue > 0) {
|
||||
mp_handle_output_char(c);
|
||||
if (mp_char_buf > 0) {
|
||||
mp_handle_output_char(mp_char_buf);
|
||||
mp_char_buf = 0;
|
||||
}
|
||||
--mp_full_char_queue;
|
||||
}
|
||||
else {
|
||||
uint8_t buf[2] = { 0,0 };
|
||||
const uint8_t res = mp_unpack_chars(c, buf);
|
||||
|
||||
if (res & MeatPack_NextPackedFirst) {
|
||||
++mp_full_char_queue;
|
||||
if (res & MeatPack_NextPackedSecond) ++mp_full_char_queue;
|
||||
else mp_char_buf = buf[1];
|
||||
}
|
||||
else {
|
||||
mp_handle_output_char(buf[0]);
|
||||
if (buf[0] != '\n') {
|
||||
if (res & MeatPack_NextPackedSecond) ++mp_full_char_queue;
|
||||
else mp_handle_output_char(buf[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Packing not enabled, just copy character to output
|
||||
mp_handle_output_char(c);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
void FORCE_INLINE mp_echo_config_state() {
|
||||
SERIAL_ECHOPGM(" [MP] "); // Add space at idx 0 just in case first character is dropped due to timing/sync issues.
|
||||
|
||||
// NOTE: if any configuration vars are added below, the outgoing sync text for host plugin
|
||||
// should not contain the "PV' substring, as this is used to indicate protocol version
|
||||
SERIAL_ECHOPGM(MeatPack_ProtocolVersion);
|
||||
|
||||
// Echo current state
|
||||
if (mp_config & MPConfig_Active)
|
||||
SERIAL_ECHOPGM(" ON");
|
||||
else
|
||||
SERIAL_ECHOPGM(" OFF");
|
||||
|
||||
if (mp_config & MPConfig_NoSpaces)
|
||||
SERIAL_ECHOPGM(" NSP"); // [N]o [SP]aces
|
||||
else
|
||||
SERIAL_ECHOPGM(" ESP"); // [E]nabled [SP]aces
|
||||
|
||||
SERIAL_ECHOLNPGM("");
|
||||
|
||||
// Validate config vars
|
||||
#ifdef USE_LOOKUP_TABLE
|
||||
if (mp_config & MPConfig_NoSpaces)
|
||||
MeatPackLookupTbl[MeatPack_SpaceCharIdx] = (uint8_t)(MeatPack_SpaceCharReplace);
|
||||
else
|
||||
MeatPackLookupTbl[MeatPack_SpaceCharIdx] = ' ';
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
void FORCE_INLINE mp_handle_cmd(const MeatPack_Command c) {
|
||||
switch (c) {
|
||||
case MPCommand_EnablePacking: {
|
||||
mp_config |= MPConfig_Active;
|
||||
#ifdef MP_DEBUG
|
||||
SERIAL_ECHOLNPGM("[MPDBG] ENABL REC");
|
||||
#endif
|
||||
} break;
|
||||
case MPCommand_DisablePacking: {
|
||||
mp_config &= ~(MPConfig_Active);
|
||||
#ifdef MP_DEBUG
|
||||
SERIAL_ECHOLNPGM("[MPDBG] DISBL REC");
|
||||
#endif
|
||||
} break;
|
||||
case MPCommand_ResetAll: {
|
||||
mp_reset_state();
|
||||
#ifdef MP_DEBUG
|
||||
SERIAL_ECHOLNPGM("[MPDBG] RESET REC");
|
||||
#endif
|
||||
} break;
|
||||
case MPCommand_EnableNoSpaces: {
|
||||
mp_config |= MPConfig_NoSpaces;
|
||||
#ifdef MP_DEBUG
|
||||
SERIAL_ECHOLNPGM("[MPDBG] ENABL NSP");
|
||||
#endif
|
||||
} break;
|
||||
case MPCommand_DisableNoSpaces: {
|
||||
mp_config &= ~(MPConfig_NoSpaces);
|
||||
#ifdef MP_DEBUG
|
||||
SERIAL_ECHOLNPGM("[MPDBG] DISBL NSP");
|
||||
#endif
|
||||
} break;
|
||||
default: {
|
||||
#ifdef MP_DEBUG
|
||||
SERIAL_ECHOLN("[MPDBG] UNK CMD REC");
|
||||
#endif
|
||||
}
|
||||
case MPCommand_QueryConfig:
|
||||
break;
|
||||
}
|
||||
|
||||
mp_echo_config_state();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
void mp_handle_rx_char(const uint8_t c) {
|
||||
|
||||
// Check for commit complete
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if (c == (uint8_t)(MeatPack_CommandByte)) {
|
||||
if (mp_cmd_count > 0) {
|
||||
mp_cmd_active = 1;
|
||||
mp_cmd_count = 0;
|
||||
}
|
||||
else
|
||||
++mp_cmd_count;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mp_cmd_active > 0) {
|
||||
mp_handle_cmd((MeatPack_Command)c);
|
||||
mp_cmd_active = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mp_cmd_count > 0) {
|
||||
mp_handle_rx_char_inner((uint8_t)(MeatPack_CommandByte));
|
||||
mp_cmd_count = 0;
|
||||
}
|
||||
|
||||
mp_handle_rx_char_inner(c);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
uint8_t mp_get_result_char(char* const __restrict out) {
|
||||
if (mp_char_out_count > 0) {
|
||||
const uint8_t res = mp_char_out_count;
|
||||
for (uint8_t i = 0; i < mp_char_out_count; ++i)
|
||||
out[i] = (char)mp_char_out_buf[i];
|
||||
mp_char_out_count = 0;
|
||||
return res;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void mp_trigger_cmd(const MeatPack_Command cmd)
|
||||
{
|
||||
mp_handle_cmd(cmd);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* MeatPack G-Code Compression
|
||||
*
|
||||
* Algorithm & Implementation: Scott Mudge - mail@scottmudge.com
|
||||
* Date: Dec. 2020
|
||||
*
|
||||
* Specifically optimized for 3D printing G-Code, this is a zero-cost data compression method
|
||||
* which packs ~180-190% more data into the same amount of bytes going to the CNC controller.
|
||||
* As a majority of G-Code can be represented by a restricted alphabet, I performed histogram
|
||||
* analysis on a wide variety of 3D printing gcode samples, and found ~93% of all gcode could
|
||||
* be represented by the same 15-character alphabet.
|
||||
*
|
||||
* This allowed me to design a system of packing 2 8-bit characters into a single byte, assuming
|
||||
* they fall within this limited 15-character alphabet. Using a 4-bit lookup table, these 8-bit
|
||||
* characters can be represented by a 4-bit index.
|
||||
*
|
||||
* Combined with some logic to allow commingling of full-width characters outside of this 15-
|
||||
* character alphabet (at the cost of an extra 8-bits per full-width character), and by stripping
|
||||
* out unnecessary comments, the end result is gcode which is roughly half the original size.
|
||||
*
|
||||
* Why did I do this? I noticed micro-stuttering and other data-bottleneck issues while printing
|
||||
* objects with high curvature, especially at high speeds. There is also the issue of the limited
|
||||
* baud rate provided by Prusa's Atmega2560-based boards, over the USB serial connection. So soft-
|
||||
* ware like OctoPrint would also suffer this same micro-stuttering and poor print quality issue.
|
||||
*
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include "Configuration.h"
|
||||
|
||||
#ifndef MEATPACK_H_
|
||||
#define MEATPACK_H_
|
||||
|
||||
#ifdef ENABLE_MEATPACK
|
||||
|
||||
#define MeatPack_SecondNotPacked 0b11110000
|
||||
#define MeatPack_FirstNotPacked 0b00001111
|
||||
|
||||
// These are commands sent to MeatPack to control its behavior.
|
||||
// They are sent by first sending 2x MeatPack_CommandByte (0xFF) in sequence,
|
||||
// followed by one of the command bytes below.
|
||||
// Provided that 0xFF is an exceedingly rare character that is virtually never
|
||||
// present in g-code naturally, it is safe to assume 2 in sequence should never
|
||||
// happen naturally, and so it is used as a signal here.
|
||||
//
|
||||
// 0xFF *IS* used in "packed" g-code (used to denote that the next 2 characters are
|
||||
// full-width), however 2 in a row will never occur, as the next 2 bytes will always
|
||||
// some non-0xFF character.
|
||||
enum MeatPack_Command {
|
||||
MPCommand_None = 0U,
|
||||
// MPCommand_TogglePacking = 253U, -- Unused, byte 253 can be re-used later.
|
||||
MPCommand_EnablePacking = 251U,
|
||||
MPCommand_DisablePacking = 250U,
|
||||
MPCommand_ResetAll = 249U,
|
||||
MPCommand_QueryConfig = 248U,
|
||||
MPCommand_EnableNoSpaces = 247U,
|
||||
MPCommand_DisableNoSpaces = 246U
|
||||
};
|
||||
|
||||
// Pass in a character rx'd by SD card or serial. Automatically parses command/ctrl sequences,
|
||||
// and will control state internally.
|
||||
extern void mp_handle_rx_char(const uint8_t c);
|
||||
|
||||
// After passing in rx'd char using above method, call this to get characters out. Can return
|
||||
// from 0 to 2 characters at once.
|
||||
// @param out [in] Output pointer for unpacked/processed data.
|
||||
// @return Number of characters returned. Range from 0 to 2.
|
||||
extern uint8_t mp_get_result_char(char* const __restrict out);
|
||||
#endif
|
||||
|
||||
#endif // MEATPACK_H_
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
// Note -- This is a modified stdtod() method, to prevent the catching of uppercase "E", used in 3D printing g-code.
|
||||
|
||||
|
||||
#if !defined(__AVR_TINY__)
|
||||
|
||||
#include <avr/pgmspace.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h> /* INFINITY, NAN */
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Only GCC 4.2 calls the library function to convert an unsigned long
|
||||
to float. Other GCC-es (including 4.3) use a signed long to float
|
||||
conversion along with a large inline code to correct the result. */
|
||||
extern double __floatunsisf(unsigned long);
|
||||
|
||||
PROGMEM static const float pwr_p10[6] = {
|
||||
1e+1, 1e+2, 1e+4, 1e+8, 1e+16, 1e+32
|
||||
};
|
||||
PROGMEM static const float pwr_m10[6] = {
|
||||
1e-1, 1e-2, 1e-4, 1e-8, 1e-16, 1e-32
|
||||
};
|
||||
|
||||
/* PSTR() is not used to save 1 byte per string: '\0' at the tail. */
|
||||
PROGMEM static const char pstr_inf[] = { 'I','N','F' };
|
||||
PROGMEM static const char pstr_inity[] = { 'I','N','I','T','Y' };
|
||||
PROGMEM static const char pstr_nan[] = { 'N','A','N' };
|
||||
|
||||
|
||||
double strtod_noE(const char* nptr, char** endptr)
|
||||
{
|
||||
union {
|
||||
unsigned long u32;
|
||||
float flt;
|
||||
} x;
|
||||
unsigned char c;
|
||||
int exp;
|
||||
|
||||
unsigned char flag;
|
||||
#define FL_MINUS 0x01 /* number is negative */
|
||||
#define FL_ANY 0x02 /* any digit was readed */
|
||||
#define FL_OVFL 0x04 /* overflow was */
|
||||
#define FL_DOT 0x08 /* decimal '.' was */
|
||||
#define FL_MEXP 0x10 /* exponent 'e' is neg. */
|
||||
|
||||
if (endptr)
|
||||
*endptr = (char*)nptr;
|
||||
|
||||
do {
|
||||
c = *nptr++;
|
||||
} while (isspace(c));
|
||||
|
||||
flag = 0;
|
||||
if (c == '-') {
|
||||
flag = FL_MINUS;
|
||||
c = *nptr++;
|
||||
}
|
||||
else if (c == '+') {
|
||||
c = *nptr++;
|
||||
}
|
||||
|
||||
if (!strncasecmp_P(nptr - 1, pstr_inf, 3)) {
|
||||
nptr += 2;
|
||||
if (!strncasecmp_P(nptr, pstr_inity, 5))
|
||||
nptr += 5;
|
||||
if (endptr)
|
||||
*endptr = (char*)nptr;
|
||||
return flag & FL_MINUS ? -INFINITY : +INFINITY;
|
||||
}
|
||||
|
||||
/* NAN() construction is not realised.
|
||||
Length would be 3 characters only. */
|
||||
if (!strncasecmp_P(nptr - 1, pstr_nan, 3)) {
|
||||
if (endptr)
|
||||
*endptr = (char*)nptr + 2;
|
||||
return NAN;
|
||||
}
|
||||
|
||||
x.u32 = 0;
|
||||
exp = 0;
|
||||
while (1) {
|
||||
|
||||
c -= '0';
|
||||
|
||||
if (c <= 9) {
|
||||
flag |= FL_ANY;
|
||||
if (flag & FL_OVFL) {
|
||||
if (!(flag & FL_DOT))
|
||||
exp += 1;
|
||||
}
|
||||
else {
|
||||
if (flag & FL_DOT)
|
||||
exp -= 1;
|
||||
/* x.u32 = x.u32 * 10 + c */
|
||||
x.u32 = (((x.u32 << 2) + x.u32) << 1) + c;
|
||||
if (x.u32 >= (ULONG_MAX - 9) / 10)
|
||||
flag |= FL_OVFL;
|
||||
}
|
||||
|
||||
}
|
||||
else if (c == (('.' - '0') & 0xff) && !(flag & FL_DOT)) {
|
||||
flag |= FL_DOT;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
c = *nptr++;
|
||||
}
|
||||
|
||||
// Check for exponent "E", but disable capital E
|
||||
if (c == (('e' - '0') & 0xff) /*|| c == (('E' - '0') & 0xff)*/)
|
||||
{
|
||||
int i;
|
||||
c = *nptr++;
|
||||
i = 2;
|
||||
if (c == '-') {
|
||||
flag |= FL_MEXP;
|
||||
c = *nptr++;
|
||||
}
|
||||
else if (c == '+') {
|
||||
c = *nptr++;
|
||||
}
|
||||
else {
|
||||
i = 1;
|
||||
}
|
||||
c -= '0';
|
||||
if (c > 9) {
|
||||
nptr -= i;
|
||||
}
|
||||
else {
|
||||
i = 0;
|
||||
do {
|
||||
if (i < 3200)
|
||||
i = (((i << 2) + i) << 1) + c; /* i = 10*i + c */
|
||||
c = *nptr++ - '0';
|
||||
} while (c <= 9);
|
||||
if (flag & FL_MEXP)
|
||||
i = -i;
|
||||
exp += i;
|
||||
}
|
||||
}
|
||||
|
||||
if ((flag & FL_ANY) && endptr)
|
||||
*endptr = (char*)nptr - 1;
|
||||
|
||||
x.flt = __floatunsisf(x.u32); /* manually */
|
||||
if ((flag & FL_MINUS) && (flag & FL_ANY))
|
||||
x.flt = -x.flt;
|
||||
|
||||
if (x.flt != 0) {
|
||||
int pwr;
|
||||
if (exp < 0) {
|
||||
nptr = (void*)(pwr_m10 + 5);
|
||||
exp = -exp;
|
||||
}
|
||||
else {
|
||||
nptr = (void*)(pwr_p10 + 5);
|
||||
}
|
||||
for (pwr = 32; pwr; pwr >>= 1) {
|
||||
for (; exp >= pwr; exp -= pwr) {
|
||||
union {
|
||||
unsigned long u32;
|
||||
float flt;
|
||||
} y;
|
||||
y.u32 = pgm_read_dword((float*)nptr);
|
||||
x.flt *= y.flt;
|
||||
}
|
||||
nptr -= sizeof(float);
|
||||
}
|
||||
if (!isfinite(x.flt) || x.flt == 0)
|
||||
errno = ERANGE;
|
||||
}
|
||||
|
||||
return x.flt;
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue