948 lines
27 KiB
C++
948 lines
27 KiB
C++
|
/*
|
||
|
This file is part of Repetier-Firmware.
|
||
|
|
||
|
Repetier-Firmware 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.
|
||
|
|
||
|
Repetier-Firmware 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 Repetier-Firmware. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
This firmware is a nearly complete rewrite of the sprinter firmware
|
||
|
by kliment (https://github.com/kliment/Sprinter)
|
||
|
which based on Tonokip RepRap firmware rewrite based off of Hydra-mmm firmware.
|
||
|
|
||
|
Functions in this file are used to communicate using ascii or repetier protocol.
|
||
|
*/
|
||
|
|
||
|
#include "Repetier.h"
|
||
|
|
||
|
#ifndef FEATURE_CHECKSUM_FORCED
|
||
|
#define FEATURE_CHECKSUM_FORCED false
|
||
|
#endif
|
||
|
|
||
|
GCode GCode::commandsBuffered[GCODE_BUFFER_SIZE]; ///< Buffer for received commands.
|
||
|
uint8_t GCode::bufferReadIndex = 0; ///< Read position in gcode_buffer.
|
||
|
uint8_t GCode::bufferWriteIndex = 0; ///< Write position in gcode_buffer.
|
||
|
uint8_t GCode::commandReceiving[MAX_CMD_SIZE]; ///< Current received command.
|
||
|
uint8_t GCode::commandsReceivingWritePosition = 0; ///< Writing position in gcode_transbuffer.
|
||
|
uint8_t GCode::sendAsBinary; ///< Flags the command as binary input.
|
||
|
uint8_t GCode::wasLastCommandReceivedAsBinary = 0; ///< Was the last successful command in binary mode?
|
||
|
uint8_t GCode::commentDetected = false; ///< Flags true if we are reading the comment part of a command.
|
||
|
uint8_t GCode::binaryCommandSize; ///< Expected size of the incoming binary command.
|
||
|
bool GCode::waitUntilAllCommandsAreParsed = false; ///< Don't read until all commands are parsed. Needed if gcode_buffer is misused as storage for strings.
|
||
|
uint32_t GCode::lastLineNumber = 0; ///< Last line number received.
|
||
|
uint32_t GCode::actLineNumber; ///< Line number of current command.
|
||
|
int8_t GCode::waitingForResend = -1; ///< Waiting for line to be resend. -1 = no wait.
|
||
|
volatile uint8_t GCode::bufferLength = 0; ///< Number of commands stored in gcode_buffer
|
||
|
millis_t GCode::timeOfLastDataPacket = 0; ///< Time, when we got the last data packet. Used to detect missing uint8_ts.
|
||
|
uint8_t GCode::formatErrors = 0;
|
||
|
|
||
|
/** \page Repetier-protocol
|
||
|
|
||
|
\section Introduction
|
||
|
|
||
|
The repetier-protocol was developed, to overcome some shortcommings in the standard
|
||
|
RepRap communication method, while remaining backward compatible. To use the improved
|
||
|
features of this protocal, you need a host which speaks it. On Windows the recommended
|
||
|
host software is Repetier-Host. It is developed in parallel to this firmware and supports
|
||
|
all implemented features.
|
||
|
|
||
|
\subsection Improvements
|
||
|
|
||
|
- With higher speeds, the serial connection is more likely to produce communication failures.
|
||
|
The standard method is to transfer a checksum at the end of the line. This checksum is the
|
||
|
XORd value of all characters send. The value is limited to a range between 0 and 127. It can
|
||
|
not detect two identical missing characters or a wrong order. Therefore the new protocol
|
||
|
uses Fletchers checksum, which overcomes these shortcommings.
|
||
|
- The new protocol send data in binary format. This reduces the data size to less then 50% and
|
||
|
it speeds up decoding the command. No slow conversion from string to floats are needed.
|
||
|
|
||
|
*/
|
||
|
|
||
|
/** \brief Computes size of binary data from bitfield.
|
||
|
|
||
|
In the repetier-protocol in binary mode, the first 2 uint8_ts define the
|
||
|
data. From this bitfield, this function computes the size of the command
|
||
|
including the 2 uint8_ts of the bitfield and the 2 uint8_ts for the checksum.
|
||
|
|
||
|
Gcode Letter to Bit and Datatype:
|
||
|
|
||
|
- N : Bit 0 : 16-Bit Integer
|
||
|
- M : Bit 1 : 8-Bit unsigned uint8_t
|
||
|
- G : Bit 2 : 8-Bit unsigned uint8_t
|
||
|
- X : Bit 3 : 32-Bit Float
|
||
|
- Y : Bit 4 : 32-Bit Float
|
||
|
- Z : Bit 5 : 32-Bit Float
|
||
|
- E : Bit 6 : 32-Bit Float
|
||
|
- : Bit 7 : always set to distinguish binary from ASCII line.
|
||
|
- F : Bit 8 : 32-Bit Float
|
||
|
- T : Bit 9 : 8 Bit Integer
|
||
|
- S : Bit 10 : 32 Bit Value
|
||
|
- P : Bit 11 : 32 Bit Integer
|
||
|
- V2 : Bit 12 : Version 2 command for additional commands/sizes
|
||
|
- Ext : Bit 13 : There are 2 more uint8_ts following with Bits, only for future versions
|
||
|
- Int :Bit 14 : Marks it as internal command,
|
||
|
- Text : Bit 15 : 16 Byte ASCII String terminated with 0
|
||
|
Second word if V2:
|
||
|
- I : Bit 0 : 32-Bit float
|
||
|
- J : Bit 1 : 32-Bit float
|
||
|
- R : Bit 2 : 32-Bit float
|
||
|
- D : Bit 3 : 32-Bit float
|
||
|
- C : Bit 4 : 32-Bit float
|
||
|
- H : Bit 5 : 32-Bit float
|
||
|
- A : Bit 6 : 32-Bit float
|
||
|
- B : Bit 7 : 32-Bit float
|
||
|
- K : Bit 8 : 32-Bit float
|
||
|
- L : Bit 9 : 32-Bit float
|
||
|
- O : Bit 0 : 32-Bit float
|
||
|
*/
|
||
|
uint8_t GCode::computeBinarySize(char *ptr) // unsigned int bitfield) {
|
||
|
{
|
||
|
uint8_t s = 4; // include checksum and bitfield
|
||
|
uint16_t bitfield = *(uint16_t*)ptr;
|
||
|
if(bitfield & 1) s += 2;
|
||
|
if(bitfield & 8) s += 4;
|
||
|
if(bitfield & 16) s += 4;
|
||
|
if(bitfield & 32) s += 4;
|
||
|
if(bitfield & 64) s += 4;
|
||
|
if(bitfield & 256) s += 4;
|
||
|
if(bitfield & 512) s += 1;
|
||
|
if(bitfield & 1024) s += 4;
|
||
|
if(bitfield & 2048) s += 4;
|
||
|
if(bitfield & 4096) // Version 2 or later
|
||
|
{
|
||
|
s += 2; // for bitfield 2
|
||
|
uint16_t bitfield2 = *(uint16_t*)(ptr + 2);
|
||
|
if(bitfield & 2) s += 2;
|
||
|
if(bitfield & 4) s += 2;
|
||
|
if(bitfield2 & 1) s += 4;
|
||
|
if(bitfield2 & 2) s += 4;
|
||
|
if(bitfield2 & 4) s += 4;
|
||
|
if(bitfield2 & 8) s += 4;
|
||
|
if(bitfield2 & 16) s += 4;
|
||
|
if(bitfield2 & 32) s += 4;
|
||
|
if(bitfield2 & 64) s += 4;
|
||
|
if(bitfield2 & 128) s += 4;
|
||
|
if(bitfield2 & 256) s += 4;
|
||
|
if(bitfield2 & 512) s += 4;
|
||
|
if(bitfield2 & 1024) s += 4;
|
||
|
if(bitfield2 & 2048) s += 4;
|
||
|
if(bitfield2 & 4096) s += 4;
|
||
|
if(bitfield2 & 8192) s += 4;
|
||
|
if(bitfield2 & 16384) s += 4;
|
||
|
if(bitfield2 & 32768) s += 4;
|
||
|
if(bitfield & 32768) s += RMath::min(80,(uint8_t)ptr[4] + 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(bitfield & 2) s += 1;
|
||
|
if(bitfield & 4) s += 1;
|
||
|
if(bitfield & 32768) s += 16;
|
||
|
}
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
void GCode::requestResend()
|
||
|
{
|
||
|
HAL::serialFlush();
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
if(sendAsBinary)
|
||
|
waitingForResend = 30;
|
||
|
else
|
||
|
waitingForResend = 14;
|
||
|
Com::println();
|
||
|
Com::printFLN(Com::tResend,lastLineNumber + 1);
|
||
|
Com::printFLN(Com::tOk);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Check if result is plausible. If it is, an ok is send and the command is stored in queue.
|
||
|
If not, a resend and ok is send.
|
||
|
*/
|
||
|
void GCode::checkAndPushCommand()
|
||
|
{
|
||
|
if(hasM())
|
||
|
{
|
||
|
if(M == 110) // Reset line number
|
||
|
{
|
||
|
lastLineNumber = actLineNumber;
|
||
|
Com::printFLN(Com::tOk);
|
||
|
waitingForResend = -1;
|
||
|
return;
|
||
|
}
|
||
|
if(M == 112) // Emergency kill - freeze printer
|
||
|
{
|
||
|
Commands::emergencyStop();
|
||
|
}
|
||
|
#ifdef DEBUG_COM_ERRORS
|
||
|
if(M == 666) // force an communication error
|
||
|
{
|
||
|
lastLineNumber++;
|
||
|
return;
|
||
|
}
|
||
|
#endif // DEBUG_COM_ERRORS
|
||
|
}
|
||
|
if(hasN())
|
||
|
{
|
||
|
if((((lastLineNumber + 1) & 0xffff) != (actLineNumber & 0xffff)))
|
||
|
{
|
||
|
if(static_cast<uint16_t>(lastLineNumber - actLineNumber) < 40)
|
||
|
{
|
||
|
// we have seen that line already. So we assume it is a repeated resend and we ignore it
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
Com::printFLN(Com::tSkip,actLineNumber);
|
||
|
Com::printFLN(Com::tOk);
|
||
|
}
|
||
|
else if(waitingForResend < 0) // after a resend, we have to skip the garbage in buffers, no message for this
|
||
|
{
|
||
|
if(Printer::debugErrors())
|
||
|
{
|
||
|
Com::printF(Com::tExpectedLine, lastLineNumber + 1);
|
||
|
Com::printFLN(Com::tGot, actLineNumber);
|
||
|
}
|
||
|
requestResend(); // Line missing, force resend
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
--waitingForResend;
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
Com::printFLN(Com::tSkip, actLineNumber);
|
||
|
Com::printFLN(Com::tOk);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
lastLineNumber = actLineNumber;
|
||
|
}
|
||
|
pushCommand();
|
||
|
#if ACK_WITH_LINENUMBER
|
||
|
Com::printFLN(Com::tOkSpace, actLineNumber);
|
||
|
#else
|
||
|
Com::printFLN(Com::tOk);
|
||
|
#endif
|
||
|
wasLastCommandReceivedAsBinary = sendAsBinary;
|
||
|
waitingForResend = -1; // everything is ok.
|
||
|
}
|
||
|
|
||
|
void GCode::pushCommand()
|
||
|
{
|
||
|
#if !ECHO_ON_EXECUTE
|
||
|
commandsBuffered[bufferWriteIndex].echoCommand();
|
||
|
#endif
|
||
|
if(++bufferWriteIndex >= GCODE_BUFFER_SIZE) bufferWriteIndex = 0;
|
||
|
bufferLength++;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Get the next buffered command. Returns 0 if no more commands are buffered. For each
|
||
|
returned command, the gcode_command_finished() function must be called.
|
||
|
*/
|
||
|
GCode *GCode::peekCurrentCommand()
|
||
|
{
|
||
|
if(bufferLength == 0) return NULL; // No more data
|
||
|
return &commandsBuffered[bufferReadIndex];
|
||
|
}
|
||
|
|
||
|
/** \brief Removes the last returned command from cache. */
|
||
|
void GCode::popCurrentCommand()
|
||
|
{
|
||
|
if(!bufferLength) return; // Should not happen, but safety first
|
||
|
#if ECHO_ON_EXECUTE
|
||
|
echoCommand();
|
||
|
#endif
|
||
|
if(++bufferReadIndex == GCODE_BUFFER_SIZE) bufferReadIndex = 0;
|
||
|
bufferLength--;
|
||
|
}
|
||
|
|
||
|
void GCode::echoCommand()
|
||
|
{
|
||
|
if(Printer::debugEcho())
|
||
|
{
|
||
|
Com::printF(Com::tEcho);
|
||
|
printCommand();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void GCode::debugCommandBuffer()
|
||
|
{
|
||
|
Com::printF(PSTR("CommandBuffer"));
|
||
|
for(int i = 0; i < commandsReceivingWritePosition; i++)
|
||
|
Com::printF(Com::tColon,(int)commandReceiving[i]);
|
||
|
Com::println();
|
||
|
Com::printFLN(PSTR("Binary:"), (int)sendAsBinary);
|
||
|
if(!sendAsBinary)
|
||
|
{
|
||
|
Com::print((char*)commandReceiving);
|
||
|
Com::println();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** \brief Execute commands in progmem stored string. Multiple commands are seperated by \n */
|
||
|
void GCode::executeFString(FSTRINGPARAM(cmd))
|
||
|
{
|
||
|
char buf[80];
|
||
|
uint8_t buflen;
|
||
|
char c;
|
||
|
GCode code;
|
||
|
do
|
||
|
{
|
||
|
// Wait for a free place in command buffer
|
||
|
// Scan next command from string
|
||
|
uint8_t comment = 0;
|
||
|
buflen = 0;
|
||
|
do
|
||
|
{
|
||
|
c = HAL::readFlashByte(cmd++);
|
||
|
if(c == 0 || c == '\n') break;
|
||
|
if(c == ';') comment = 1;
|
||
|
if(comment) continue;
|
||
|
buf[buflen++] = c;
|
||
|
}
|
||
|
while(buflen < 79);
|
||
|
if(buflen == 0) // empty line ignore
|
||
|
continue;
|
||
|
buf[buflen] = 0;
|
||
|
// Send command into command buffer
|
||
|
if(code.parseAscii((char *)buf,false) && (code.params & 518)) // Success
|
||
|
{
|
||
|
#ifdef DEBUG_PRINT
|
||
|
debugWaitLoop = 7;
|
||
|
#endif
|
||
|
|
||
|
Commands::executeGCode(&code);
|
||
|
Printer::defaultLoopActions();
|
||
|
}
|
||
|
}
|
||
|
while(c);
|
||
|
}
|
||
|
|
||
|
/** \brief Read from serial console or sdcard.
|
||
|
|
||
|
This function is the main function to read the commands from serial console or from sdcard.
|
||
|
It must be called frequently to empty the incoming buffer.
|
||
|
*/
|
||
|
void GCode::readFromSerial()
|
||
|
{
|
||
|
if(bufferLength >= GCODE_BUFFER_SIZE) return; // all buffers full
|
||
|
if(waitUntilAllCommandsAreParsed && bufferLength) return;
|
||
|
waitUntilAllCommandsAreParsed = false;
|
||
|
millis_t time = HAL::timeInMilliseconds();
|
||
|
if(!HAL::serialByteAvailable())
|
||
|
{
|
||
|
if((waitingForResend >= 0 || commandsReceivingWritePosition > 0) && time - timeOfLastDataPacket > 200)
|
||
|
{
|
||
|
requestResend(); // Something is wrong, a started line was not continued in the last second
|
||
|
timeOfLastDataPacket = time;
|
||
|
}
|
||
|
#ifdef WAITING_IDENTIFIER
|
||
|
else if(bufferLength == 0 && time - timeOfLastDataPacket > 1000) // Don't do it if buffer is not empty. It may be a slow executing command.
|
||
|
{
|
||
|
Com::printFLN(Com::tWait); // Unblock communication in case the last ok was not received correct.
|
||
|
timeOfLastDataPacket = time;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
while(HAL::serialByteAvailable() && commandsReceivingWritePosition < MAX_CMD_SIZE) // consume data until no data or buffer full
|
||
|
{
|
||
|
timeOfLastDataPacket = time; //HAL::timeInMilliseconds();
|
||
|
commandReceiving[commandsReceivingWritePosition++] = HAL::serialReadByte();
|
||
|
// first lets detect, if we got an old type ascii command
|
||
|
if(commandsReceivingWritePosition == 1)
|
||
|
{
|
||
|
if(waitingForResend >= 0 && wasLastCommandReceivedAsBinary)
|
||
|
{
|
||
|
if(!commandReceiving[0])
|
||
|
waitingForResend--; // Skip 30 zeros to get in sync
|
||
|
else
|
||
|
waitingForResend = 30;
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
continue;
|
||
|
}
|
||
|
if(!commandReceiving[0]) // Ignore zeros
|
||
|
{
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
continue;
|
||
|
}
|
||
|
sendAsBinary = (commandReceiving[0] & 128) != 0;
|
||
|
}
|
||
|
if(sendAsBinary)
|
||
|
{
|
||
|
if(commandsReceivingWritePosition < 2 ) continue;
|
||
|
if(commandsReceivingWritePosition == 5 || commandsReceivingWritePosition == 4)
|
||
|
binaryCommandSize = computeBinarySize((char*)commandReceiving);
|
||
|
if(commandsReceivingWritePosition == binaryCommandSize)
|
||
|
{
|
||
|
GCode *act = &commandsBuffered[bufferWriteIndex];
|
||
|
if(act->parseBinary(commandReceiving, true)) // Success
|
||
|
act->checkAndPushCommand();
|
||
|
else
|
||
|
requestResend();
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else // Ascii command
|
||
|
{
|
||
|
char ch = commandReceiving[commandsReceivingWritePosition - 1];
|
||
|
if(ch == 0 || ch == '\n' || ch == '\r' || (!commentDetected && ch == ':')) // complete line read
|
||
|
{
|
||
|
//Com::printF(PSTR("Parse ascii"));Com::print((char*)commandReceiving);Com::println();
|
||
|
commandReceiving[commandsReceivingWritePosition - 1] = 0;
|
||
|
commentDetected = false;
|
||
|
if(commandsReceivingWritePosition == 1) // empty line ignore
|
||
|
{
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
continue;
|
||
|
}
|
||
|
GCode *act = &commandsBuffered[bufferWriteIndex];
|
||
|
if(act->parseAscii((char *)commandReceiving, true)) // Success
|
||
|
act->checkAndPushCommand();
|
||
|
else
|
||
|
requestResend();
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(ch == ';') commentDetected = true; // ignore new data until lineend
|
||
|
if(commentDetected) commandsReceivingWritePosition--;
|
||
|
}
|
||
|
}
|
||
|
if(commandsReceivingWritePosition == MAX_CMD_SIZE)
|
||
|
{
|
||
|
requestResend();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
#if SDSUPPORT
|
||
|
if(sd.sdmode == 0 || commandsReceivingWritePosition != 0) // not reading or incoming serial command
|
||
|
return;
|
||
|
while( sd.filesize > sd.sdpos && commandsReceivingWritePosition < MAX_CMD_SIZE) // consume data until no data or buffer full
|
||
|
{
|
||
|
timeOfLastDataPacket = HAL::timeInMilliseconds();
|
||
|
int n = sd.file.read();
|
||
|
if(n == -1)
|
||
|
{
|
||
|
Com::printFLN(Com::tSDReadError);
|
||
|
UI_ERROR("SD Read Error");
|
||
|
|
||
|
// Second try in case of recoverable errors
|
||
|
sd.file.seekSet(sd.sdpos);
|
||
|
n = sd.file.read();
|
||
|
if(n == -1)
|
||
|
{
|
||
|
Com::printErrorFLN(PSTR("SD error did not recover!"));
|
||
|
sd.sdmode = 0;
|
||
|
break;
|
||
|
}
|
||
|
UI_ERROR("SD error fixed");
|
||
|
}
|
||
|
sd.sdpos++; // = file.curPosition();
|
||
|
commandReceiving[commandsReceivingWritePosition++] = (uint8_t)n;
|
||
|
|
||
|
// first lets detect, if we got an old type ascii command
|
||
|
if(commandsReceivingWritePosition == 1)
|
||
|
{
|
||
|
sendAsBinary = (commandReceiving[0] & 128) != 0;
|
||
|
}
|
||
|
if(sendAsBinary)
|
||
|
{
|
||
|
if(commandsReceivingWritePosition < 2 ) continue;
|
||
|
if(commandsReceivingWritePosition == 4 || commandsReceivingWritePosition == 5)
|
||
|
binaryCommandSize = computeBinarySize((char*)commandReceiving);
|
||
|
if(commandsReceivingWritePosition == binaryCommandSize)
|
||
|
{
|
||
|
GCode *act = &commandsBuffered[bufferWriteIndex];
|
||
|
if(act->parseBinary(commandReceiving, false)) // Success, silently ignore illegal commands
|
||
|
pushCommand();
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
if(sd.sdmode == 2)
|
||
|
sd.sdmode = 0;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
char ch = commandReceiving[commandsReceivingWritePosition-1];
|
||
|
bool returnChar = ch == '\n' || ch == '\r';
|
||
|
if(returnChar || sd.filesize == sd.sdpos || (!commentDetected && ch == ':') || commandsReceivingWritePosition >= (MAX_CMD_SIZE - 1) ) // complete line read
|
||
|
{
|
||
|
if(returnChar || ch == ':')
|
||
|
commandReceiving[commandsReceivingWritePosition - 1] = 0;
|
||
|
else
|
||
|
commandReceiving[commandsReceivingWritePosition] = 0;
|
||
|
commentDetected = false;
|
||
|
if(commandsReceivingWritePosition == 1) // empty line ignore
|
||
|
{
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
continue;
|
||
|
}
|
||
|
GCode *act = &commandsBuffered[bufferWriteIndex];
|
||
|
if(act->parseAscii((char *)commandReceiving, false)) // Success
|
||
|
pushCommand();
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
if(sd.sdmode == 2)
|
||
|
sd.sdmode = 0;
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(ch == ';') commentDetected = true; // ignore new data until lineend
|
||
|
if(commentDetected) commandsReceivingWritePosition--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
sd.sdmode = 0;
|
||
|
Com::printFLN(Com::tDonePrinting);
|
||
|
commandsReceivingWritePosition = 0;
|
||
|
commentDetected = false;
|
||
|
Printer::setMenuMode(MENU_MODE_SD_PRINTING, false);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Converts a binary uint8_tfield containing one GCode line into a GCode structure.
|
||
|
Returns true if checksum was correct.
|
||
|
*/
|
||
|
bool GCode::parseBinary(uint8_t *buffer,bool fromSerial)
|
||
|
{
|
||
|
internalCommand = !fromSerial;
|
||
|
unsigned int sum1 = 0,sum2 = 0; // for fletcher-16 checksum
|
||
|
// first do fletcher-16 checksum tests see
|
||
|
// http://en.wikipedia.org/wiki/Fletcher's_checksum
|
||
|
uint8_t *p = buffer;
|
||
|
uint8_t len = binaryCommandSize - 2;
|
||
|
while (len)
|
||
|
{
|
||
|
uint8_t tlen = len > 21 ? 21 : len;
|
||
|
len -= tlen;
|
||
|
do
|
||
|
{
|
||
|
sum1 += *p++;
|
||
|
if(sum1 >= 255) sum1 -= 255;
|
||
|
sum2 += sum1;
|
||
|
if(sum2 >= 255) sum2 -= 255;
|
||
|
}
|
||
|
while (--tlen);
|
||
|
}
|
||
|
sum1 -= *p++;
|
||
|
sum2 -= *p;
|
||
|
if(sum1 | sum2)
|
||
|
{
|
||
|
if(Printer::debugErrors())
|
||
|
{
|
||
|
Com::printErrorFLN(Com::tWrongChecksum);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
p = buffer;
|
||
|
params = *(unsigned int *)p;
|
||
|
p += 2;
|
||
|
uint8_t textlen = 16;
|
||
|
if(isV2())
|
||
|
{
|
||
|
params2 = *(unsigned int *)p;
|
||
|
p+=2;
|
||
|
if(hasString())
|
||
|
textlen = *p++;
|
||
|
}
|
||
|
else params2 = 0;
|
||
|
if(params & 1)
|
||
|
{
|
||
|
actLineNumber = N = *(uint16_t *)p;
|
||
|
p+=2;
|
||
|
}
|
||
|
if(isV2()) // Read G,M as 16 bit value
|
||
|
{
|
||
|
if(params & 2)
|
||
|
{
|
||
|
M = *(uint16_t *)p;
|
||
|
p += 2;
|
||
|
}
|
||
|
if(params & 4)
|
||
|
{
|
||
|
G = *(uint16_t *)p;
|
||
|
p += 2;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(params & 2)
|
||
|
{
|
||
|
M = *p++;
|
||
|
}
|
||
|
if(params & 4)
|
||
|
{
|
||
|
G = *p++;
|
||
|
}
|
||
|
}
|
||
|
//if(code->params & 8) {memcpy(&code->X,p,4);p+=4;}
|
||
|
if(params & 8)
|
||
|
{
|
||
|
X = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(params & 16)
|
||
|
{
|
||
|
Y = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(params & 32)
|
||
|
{
|
||
|
Z = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(params & 64)
|
||
|
{
|
||
|
E = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(params & 256)
|
||
|
{
|
||
|
F = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(params & 512)
|
||
|
{
|
||
|
T = *p++;
|
||
|
}
|
||
|
if(params & 1024)
|
||
|
{
|
||
|
S = *(int32_t*)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(params & 2048)
|
||
|
{
|
||
|
P = *(int32_t*)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasI())
|
||
|
{
|
||
|
I = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasJ())
|
||
|
{
|
||
|
J = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasR())
|
||
|
{
|
||
|
R = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasD())
|
||
|
{
|
||
|
D = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasC())
|
||
|
{
|
||
|
C = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasH())
|
||
|
{
|
||
|
H = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasA())
|
||
|
{
|
||
|
A = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasB())
|
||
|
{
|
||
|
B = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasK())
|
||
|
{
|
||
|
K = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasL())
|
||
|
{
|
||
|
L = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasO())
|
||
|
{
|
||
|
O = *(float *)p;
|
||
|
p += 4;
|
||
|
}
|
||
|
if(hasString()) // set text pointer to string
|
||
|
{
|
||
|
text = (char*)p;
|
||
|
text[textlen] = 0; // Terminate string overwriting checksum
|
||
|
waitUntilAllCommandsAreParsed=true; // Don't destroy string until executed
|
||
|
}
|
||
|
formatErrors = 0;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Converts a ascii GCode line into a GCode structure.
|
||
|
*/
|
||
|
bool GCode::parseAscii(char *line,bool fromSerial)
|
||
|
{
|
||
|
char *pos = line;
|
||
|
params = 0;
|
||
|
params2 = 0;
|
||
|
internalCommand = !fromSerial;
|
||
|
char c;
|
||
|
while ( (c = *(pos++)) )
|
||
|
{
|
||
|
switch(c)
|
||
|
{
|
||
|
case 'N':
|
||
|
case 'n':
|
||
|
{
|
||
|
actLineNumber = parseLongValue(pos);
|
||
|
params |=1;
|
||
|
N = actLineNumber;
|
||
|
break;
|
||
|
}
|
||
|
case 'G':
|
||
|
case 'g':
|
||
|
{
|
||
|
G = parseLongValue(pos) & 0xffff;
|
||
|
params |= 4;
|
||
|
if(G > 255) params |= 4096;
|
||
|
break;
|
||
|
}
|
||
|
case 'M':
|
||
|
case 'm':
|
||
|
{
|
||
|
M = parseLongValue(pos) & 0xffff;
|
||
|
params |=2;
|
||
|
if(M > 255) params |= 4096;
|
||
|
// handle non standard text arguments that some M codes have
|
||
|
if (M == 23 || M == 28 || M == 29 || M == 30 || M == 32 || M == 117)
|
||
|
{
|
||
|
// after M command we got a filename or text
|
||
|
char digit;
|
||
|
while( (digit = *pos) )
|
||
|
{
|
||
|
if (digit < '0' || digit > '9') break;
|
||
|
pos++;
|
||
|
}
|
||
|
while( (digit = *pos) )
|
||
|
{
|
||
|
if (digit != ' ') break;
|
||
|
pos++;
|
||
|
// skip leading whitespaces (may be no white space)
|
||
|
}
|
||
|
text = pos;
|
||
|
while (*pos)
|
||
|
{
|
||
|
if((M != 117 && *pos==' ') || *pos=='*') break;
|
||
|
pos++; // find a space as file name end
|
||
|
}
|
||
|
*pos = 0; // truncate filename by erasing space with nul, also skips checksum
|
||
|
waitUntilAllCommandsAreParsed = true; // don't risk string be deleted
|
||
|
params |= 32768;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case 'X':
|
||
|
case 'x':
|
||
|
{
|
||
|
X = parseFloatValue(pos);
|
||
|
params |= 8;
|
||
|
break;
|
||
|
}
|
||
|
case 'Y':
|
||
|
case 'y':
|
||
|
{
|
||
|
Y = parseFloatValue(pos);
|
||
|
params |= 16;
|
||
|
break;
|
||
|
}
|
||
|
case 'Z':
|
||
|
case 'z':
|
||
|
{
|
||
|
Z = parseFloatValue(pos);
|
||
|
params |= 32;
|
||
|
break;
|
||
|
}
|
||
|
case 'E':
|
||
|
case 'e':
|
||
|
{
|
||
|
E = parseFloatValue(pos);
|
||
|
params |= 64;
|
||
|
break;
|
||
|
}
|
||
|
case 'F':
|
||
|
case 'f':
|
||
|
{
|
||
|
F = parseFloatValue(pos);
|
||
|
params |= 256;
|
||
|
break;
|
||
|
}
|
||
|
case 'T':
|
||
|
case 't':
|
||
|
{
|
||
|
T = parseLongValue(pos) & 0xff;
|
||
|
params |= 512;
|
||
|
break;
|
||
|
}
|
||
|
case 'S':
|
||
|
case 's':
|
||
|
{
|
||
|
S = parseLongValue(pos);
|
||
|
params |= 1024;
|
||
|
break;
|
||
|
}
|
||
|
case 'P':
|
||
|
case 'p':
|
||
|
{
|
||
|
P = parseLongValue(pos);
|
||
|
params |= 2048;
|
||
|
break;
|
||
|
}
|
||
|
case 'I':
|
||
|
case 'i':
|
||
|
{
|
||
|
I = parseFloatValue(pos);
|
||
|
params2 |= 1;
|
||
|
params |= 4096; // Needs V2 for saving
|
||
|
break;
|
||
|
}
|
||
|
case 'J':
|
||
|
case 'j':
|
||
|
{
|
||
|
J = parseFloatValue(pos);
|
||
|
params2 |= 2;
|
||
|
params |= 4096; // Needs V2 for saving
|
||
|
break;
|
||
|
}
|
||
|
case 'R':
|
||
|
case 'r':
|
||
|
{
|
||
|
R = parseFloatValue(pos);
|
||
|
params2 |= 4;
|
||
|
params |= 4096; // Needs V2 for saving
|
||
|
break;
|
||
|
}
|
||
|
case 'D':
|
||
|
case 'd':
|
||
|
{
|
||
|
D = parseFloatValue(pos);
|
||
|
params2 |= 8;
|
||
|
params |= 4096; // Needs V2 for saving
|
||
|
break;
|
||
|
}
|
||
|
case '*' : //checksum
|
||
|
{
|
||
|
uint8_t checksum_given = parseLongValue(pos);
|
||
|
uint8_t checksum = 0;
|
||
|
while(line != (pos-1)) checksum ^= *line++;
|
||
|
#if FEATURE_CHECKSUM_FORCED
|
||
|
Printer::flag0 |= PRINTER_FLAG0_FORCE_CHECKSUM;
|
||
|
#endif
|
||
|
if(checksum != checksum_given)
|
||
|
{
|
||
|
if(Printer::debugErrors())
|
||
|
{
|
||
|
Com::printErrorFLN(Com::tWrongChecksum);
|
||
|
}
|
||
|
return false; // mismatch
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}// end switch
|
||
|
}// end while
|
||
|
|
||
|
if(hasFormatError() || (params & 518)==0) // Must contain G, M or T command and parameter need to have variables!
|
||
|
{
|
||
|
formatErrors++;
|
||
|
if(Printer::debugErrors())
|
||
|
Com::printErrorFLN(Com::tFormatError);
|
||
|
if(formatErrors<3) return false;
|
||
|
}
|
||
|
else formatErrors = 0;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/** \brief Print command on serial console */
|
||
|
void GCode::printCommand()
|
||
|
{
|
||
|
if(hasN()) {
|
||
|
Com::print('N');
|
||
|
Com::print((long)N);
|
||
|
Com::print(' ');
|
||
|
}
|
||
|
if(hasM())
|
||
|
{
|
||
|
Com::print('M');
|
||
|
Com::print((int)M);
|
||
|
Com::print(' ');
|
||
|
}
|
||
|
if(hasG())
|
||
|
{
|
||
|
Com::print('G');
|
||
|
Com::print((int)G);
|
||
|
Com::print(' ');
|
||
|
}
|
||
|
if(hasT())
|
||
|
{
|
||
|
Com::print('T');
|
||
|
Com::print((int)T);
|
||
|
Com::print(' ');
|
||
|
}
|
||
|
if(hasX())
|
||
|
{
|
||
|
Com::printF(Com::tX,X);
|
||
|
}
|
||
|
if(hasY())
|
||
|
{
|
||
|
Com::printF(Com::tY,Y);
|
||
|
}
|
||
|
if(hasZ())
|
||
|
{
|
||
|
Com::printF(Com::tZ,Z);
|
||
|
}
|
||
|
if(hasE())
|
||
|
{
|
||
|
Com::printF(Com::tE,E,4);
|
||
|
}
|
||
|
if(hasF())
|
||
|
{
|
||
|
Com::printF(Com::tF,F);
|
||
|
}
|
||
|
if(hasS())
|
||
|
{
|
||
|
Com::printF(Com::tS,S);
|
||
|
}
|
||
|
if(hasP())
|
||
|
{
|
||
|
Com::printF(Com::tP,P);
|
||
|
}
|
||
|
if(hasI())
|
||
|
{
|
||
|
Com::printF(Com::tI,I);
|
||
|
}
|
||
|
if(hasJ())
|
||
|
{
|
||
|
Com::printF(Com::tJ,J);
|
||
|
}
|
||
|
if(hasR())
|
||
|
{
|
||
|
Com::printF(Com::tR,R);
|
||
|
}
|
||
|
if(hasString())
|
||
|
{
|
||
|
Com::print(text);
|
||
|
}
|
||
|
Com::println();
|
||
|
}
|