Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/Platform')
-rw-r--r--src/Platform/Logger.cpp222
-rw-r--r--src/Platform/Logger.h69
-rw-r--r--src/Platform/MessageType.h100
-rw-r--r--src/Platform/OutputMemory.cpp669
-rw-r--r--src/Platform/OutputMemory.h184
-rw-r--r--src/Platform/Platform.cpp5227
-rw-r--r--src/Platform/Platform.h1098
-rw-r--r--src/Platform/PortControl.cpp145
-rw-r--r--src/Platform/PortControl.h41
-rw-r--r--src/Platform/RepRap.cpp2926
-rw-r--r--src/Platform/RepRap.h318
-rw-r--r--src/Platform/Roland.cpp267
-rw-r--r--src/Platform/Roland.h74
-rw-r--r--src/Platform/Scanner.cpp608
-rw-r--r--src/Platform/Scanner.h112
-rw-r--r--src/Platform/TaskPriorities.h39
-rw-r--r--src/Platform/Tasks.cpp394
-rw-r--r--src/Platform/Tasks.h35
18 files changed, 12528 insertions, 0 deletions
diff --git a/src/Platform/Logger.cpp b/src/Platform/Logger.cpp
new file mode 100644
index 00000000..937132e2
--- /dev/null
+++ b/src/Platform/Logger.cpp
@@ -0,0 +1,222 @@
+/*
+ * Logger.cpp
+ *
+ * Created on: 17 Sep 2017
+ * Author: David
+ */
+
+#include "Logger.h"
+
+#if HAS_MASS_STORAGE
+
+#include "OutputMemory.h"
+#include "RepRap.h"
+#include "Platform.h"
+#include "Version.h"
+
+// Simple lock class that sets a variable true when it is created and makes sure it gets set false when it falls out of scope
+class Lock
+{
+public:
+ Lock(bool& pb) : b(pb) { b = true; }
+ ~Lock() { b = false; }
+
+private:
+ bool& b;
+};
+
+Logger::Logger(LogLevel logLvl) noexcept : logFile(), lastFlushTime(0), lastFlushFileSize(0), dirty(false), inLogger(false), logLevel(logLvl)
+{
+}
+
+void Logger::Start(time_t time, const StringRef& filename) noexcept
+{
+ if (!inLogger && logLevel > LogLevel::off)
+ {
+ Lock loggerLock(inLogger);
+ FileStore * const f = reprap.GetPlatform().OpenSysFile(filename.c_str(), OpenMode::append);
+ if (f != nullptr)
+ {
+ logFile.Set(f);
+ lastFlushFileSize = logFile.Length();
+ logFile.Seek(lastFlushFileSize);
+ logFileName.copy(filename.c_str());
+ String<StringLength50> startMessage;
+ startMessage.printf("Event logging started at level %s\n", logLevel.ToString());
+ InternalLogMessage(time, startMessage.c_str(), MessageLogLevel::info);
+ LogFirmwareInfo(time);
+ reprap.StateUpdated();
+ }
+ }
+}
+
+// TODO: Move this to a more sensible location ?
+void Logger::LogFirmwareInfo(time_t time) noexcept
+{
+ const size_t versionStringLength = 80
+#if 0 && SUPPORT_CAN_EXPANSION // TODO enable this once the part below is cleaned up
+ + reprap.GetExpansion().GetNumExpansionBoards() + 1 * 80
+#endif
+ ;
+ char buffer[versionStringLength];
+ StringRef firmwareInfo(buffer, ARRAY_SIZE(buffer)); // This is huge but should accommodate around 20 boards
+ firmwareInfo.printf("Running: %s: %s (%s%s)", reprap.GetPlatform().GetElectronicsString(), VERSION, DATE, TIME_SUFFIX);
+
+#if 0 && SUPPORT_CAN_EXPANSION // TODO this needs some rework - for now the main board is used only
+ for (size_t i = 1; i < reprap.GetExpansion().GetNumExpansionBoards() + 1; ++i)
+ {
+ auto expansionBoardData = reprap.GetExpansion().FindIndexedBoard(i);
+ firmwareInfo.catf(" - %s", expansionBoardData.typeName);
+ }
+#endif
+ InternalLogMessage(time, firmwareInfo.c_str(), MessageLogLevel::info);
+}
+
+void Logger::Stop(time_t time) noexcept
+{
+ if (logFile.IsLive() && !inLogger)
+ {
+ Lock loggerLock(inLogger);
+ InternalLogMessage(time, "Event logging stopped\n", MessageLogLevel::info);
+ logFile.Close();
+ reprap.StateUpdated();
+ }
+}
+
+// This will not start the logger if it is currently stopped
+void Logger::SetLogLevel(LogLevel newLogLevel) noexcept
+{
+ if (logLevel != newLogLevel)
+ {
+ logLevel = newLogLevel;
+ reprap.StateUpdated();
+ }
+}
+
+#if 0 // Currently not needed but might be useful in the future
+bool Logger::IsLoggingEnabledFor(const MessageType mt) const noexcept
+{
+ const auto messageLogLevel = GetMessageLogLevel(mt);
+ return IsLoggingEnabledFor(messageLogLevel);
+}
+#endif
+
+void Logger::LogMessage(time_t time, const char *message, MessageType type) noexcept
+{
+
+ if (logFile.IsLive() && !inLogger && !IsEmptyMessage(message))
+ {
+ const auto messageLogLevel = GetMessageLogLevel(type);
+ if (!IsLoggingEnabledFor(messageLogLevel))
+ {
+ return;
+ }
+ Lock loggerLock(inLogger);
+ InternalLogMessage(time, message, messageLogLevel);
+ }
+}
+
+void Logger::LogMessage(time_t time, OutputBuffer *buf, MessageType type) noexcept
+{
+ if (logFile.IsLive() && !inLogger && !IsEmptyMessage(buf->Data()))
+ {
+ const auto messageLogLevel = GetMessageLogLevel(type);
+ if (!IsLoggingEnabledFor(messageLogLevel))
+ {
+ return;
+ }
+ Lock loggerLock(inLogger);
+ bool ok = WriteDateTimeAndLogLevelPrefix(time, messageLogLevel);
+ if (ok)
+ {
+ ok = buf->WriteToFile(logFile);
+ }
+
+ if (ok)
+ {
+ dirty = true;
+ }
+ else
+ {
+ logFile.Close();
+ reprap.StateUpdated();
+ }
+ }
+}
+
+// Version of LogMessage for when we already know we want to proceed and we have already set inLogger
+void Logger::InternalLogMessage(time_t time, const char *message, const MessageLogLevel messageLogLevel) noexcept
+{
+ bool ok = WriteDateTimeAndLogLevelPrefix(time, messageLogLevel);
+ if (ok)
+ {
+ const size_t len = strlen(message);
+ if (len != 0)
+ {
+ ok = logFile.Write(message, len);
+ }
+ if (ok && (len == 0 || message[len - 1] != '\n'))
+ {
+ ok = logFile.Write('\n');
+ }
+ }
+
+ if (ok)
+ {
+ dirty = true;
+ }
+ else
+ {
+ logFile.Close();
+ reprap.StateUpdated();
+ }
+}
+
+// This is called regularly by Platform to give the logger an opportunity to flush the file buffer
+void Logger::Flush(bool forced) noexcept
+{
+ if (logFile.IsLive() && dirty && !inLogger)
+ {
+ // Log file is dirty and can be flushed.
+ // To avoid excessive disk write operations, flush it only if one of the following is true:
+ // 1. We have possibly allocated a new cluster since the last flush. To avoid lost clusters if we power down before flushing,
+ // we should flush early in this case. Rather than determine the cluster size, we flush if we have started a new 512-byte sector.
+ // 2. If it hasn't been flushed for LogFlushInterval milliseconds.
+ const FilePosition currentPos = logFile.GetPosition();
+ const uint32_t now = millis();
+ if (forced || now - lastFlushTime >= LogFlushInterval || currentPos/512 != lastFlushFileSize/512)
+ {
+ Lock loggerLock(inLogger);
+ logFile.Flush();
+ lastFlushTime = millis();
+ lastFlushFileSize = currentPos;
+ dirty = false;
+ }
+ }
+}
+
+// Write the data, time and message log level to the file followed by a space.
+// Caller must already have checked and set inLogger.
+bool Logger::WriteDateTimeAndLogLevelPrefix(time_t time, MessageLogLevel messageLogLevel) noexcept
+{
+ String<StringLength50> bufferSpace;
+ const StringRef buf = bufferSpace.GetRef();
+ if (time == 0)
+ {
+ const uint32_t timeSincePowerUp = (uint32_t)(millis64()/1000u);
+ buf.printf("power up + %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 " ", timeSincePowerUp/3600u, (timeSincePowerUp % 3600u)/60u, timeSincePowerUp % 60u);
+ }
+ else
+ {
+ tm timeInfo;
+ gmtime_r(&time, &timeInfo);
+ buf.printf("%04u-%02u-%02u %02u:%02u:%02u ",
+ timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
+ }
+ buf.catf("[%s] ", messageLogLevel.ToString());
+ return logFile.Write(buf.c_str());
+}
+
+#endif
+
+// End
diff --git a/src/Platform/Logger.h b/src/Platform/Logger.h
new file mode 100644
index 00000000..26ed6287
--- /dev/null
+++ b/src/Platform/Logger.h
@@ -0,0 +1,69 @@
+/*
+ * Logger.h
+ *
+ * Created on: 17 Sep 2017
+ * Author: David
+ */
+
+#ifndef SRC_LOGGER_H_
+#define SRC_LOGGER_H_
+
+#include "RepRapFirmware.h"
+#include <General/NamedEnum.h>
+
+NamedEnum(LogLevel, uint8_t, off, warn, info, debug);
+
+#if HAS_MASS_STORAGE
+
+#include <ctime>
+#include "Storage/FileData.h"
+#include "MessageType.h"
+
+class OutputBuffer;
+
+
+class Logger
+{
+public:
+ Logger(LogLevel logLvl) noexcept;
+
+ void Start(time_t time, const StringRef& file) noexcept;
+ void Stop(time_t time) noexcept;
+ void LogMessage(time_t time, const char *message, MessageType type) noexcept;
+ void LogMessage(time_t time, OutputBuffer *buf, MessageType type) noexcept;
+ void Flush(bool forced) noexcept;
+ bool IsActive() const noexcept { return logFile.IsLive(); }
+ const char *GetFileName() const noexcept { return (IsActive()) ? logFileName.c_str() : nullptr; }
+ LogLevel GetLogLevel() const noexcept { return logLevel; }
+ void SetLogLevel(LogLevel newLogLevel) noexcept;
+#if 0 // Currently not needed but might be useful in the future
+ bool IsLoggingEnabledFor(const MessageType mt) const noexcept;
+ bool IsWarnEnabled() const noexcept { return logLevel >= LogLevel::warn; }
+ bool IsInfoEnabled() const noexcept { return logLevel >= LogLevel::info; }
+ bool IsDebugEnabled() const noexcept { return logLevel >= LogLevel::debug; }
+#endif
+
+private:
+ NamedEnum(MessageLogLevel, uint8_t, debug, info, warn, off);
+ MessageLogLevel GetMessageLogLevel(MessageType mt) const noexcept { return (MessageLogLevel) ((mt & MessageType::LogOff)>>30); }
+
+ static const uint8_t LogEnabledThreshold = 3;
+
+ bool WriteDateTimeAndLogLevelPrefix(time_t time, MessageLogLevel messageLogLevel) noexcept;
+ void InternalLogMessage(time_t time, const char *message, const MessageLogLevel messageLogLevel) noexcept;
+ bool IsLoggingEnabledFor(const MessageLogLevel mll) const noexcept { return (mll < MessageLogLevel::off) && (mll.ToBaseType() + logLevel.ToBaseType() >= LogEnabledThreshold); }
+ void LogFirmwareInfo(time_t time) noexcept;
+ bool IsEmptyMessage(const char * message) const noexcept { return message[0] == '\0' || (message[0] == '\n' && message[1] == '\0'); }
+
+ String<MaxFilenameLength> logFileName;
+ FileData logFile;
+ uint32_t lastFlushTime;
+ FilePosition lastFlushFileSize;
+ bool dirty;
+ bool inLogger;
+ LogLevel logLevel;
+};
+
+#endif
+
+#endif /* SRC_LOGGER_H_ */
diff --git a/src/Platform/MessageType.h b/src/Platform/MessageType.h
new file mode 100644
index 00000000..d1062542
--- /dev/null
+++ b/src/Platform/MessageType.h
@@ -0,0 +1,100 @@
+/*
+ * MessageType.h
+ *
+ * Created on: 21 May 2016
+ * Authors: David and Christian
+ */
+
+#ifndef MESSAGETYPE_H_
+#define MESSAGETYPE_H_
+
+#include <cstdint>
+
+// Supported message destinations. This is now a bitmap. Note that this type is used by the Linux service as well
+enum MessageType : uint32_t
+{
+ // Destinations (bytes 1-2)
+ // Keep the following in sync with the order of GCodeBuffers in the GCodes class
+ HttpMessage = 0x01, // A message that is to be sent to the web (HTTP)
+ TelnetMessage = 0x02, // A message that is to be sent to a Telnet client
+ FileMessage = 0x04, // A message that is to be sent to a file processor
+ UsbMessage = 0x08, // A message that is to be sent in non-blocking mode to the host via USB
+ AuxMessage = 0x10, // A message that is to be sent to an auxiliary device (PanelDue)
+ TriggerMessage = 0x20, // A message that is to be sent to a trigger processor
+ CodeQueueMessage = 0x40, // A message that is to be sent to the code queue channel
+ LcdMessage = 0x80, // A message that is to be sent to the panel
+ SbcMessage = 0x100, // A message that is to be sent to the SBC
+ DaemonMessage = 0x200, // A message that is sent to the daemon processor
+ Aux2Message = 0x400, // A message that is to be sent to the second aux device
+ AutoPauseMessage = 0x800, // A message that is to be sent to an auto-pause processor
+
+ // Special destinations (byte 3)
+ BlockingUsbMessage = 0x10000, // A message that is to be sent to USB in blocking mode
+ ImmediateAuxMessage = 0x20000, // A message that is to be sent to LCD in immediate mode
+
+ DestinationsMask = 0x308FF, // Mask for all the destinations
+
+ // Special indicators (byte 4)
+ // The first two are not processed when calling the version of Platform::Message that takes an OutputBuffer.
+ ErrorMessageFlag = 0x1000000, // This is an error message
+ WarningMessageFlag = 0x2000000, // This is a warning message
+ RawMessageFlag = 0x8000000, // Do not encapsulate this message
+ BinaryCodeReplyFlag = 0x10000000, // This message comes from a binary G-Code buffer
+ PushFlag = 0x20000000, // There is more to come; the message has been truncated
+ LogMessageLowBit = 0x40000000, // Log level consists of two bits this is the low bit
+ LogMessageHighBit = 0x80000000, // Log level consists of two bits this is the high bit
+
+ // Common combinations
+ NoDestinationMessage = 0, // A message that is going nowhere
+ GenericMessage = UsbMessage | AuxMessage | HttpMessage | TelnetMessage, // A message that is to be sent to the web, Telnet, USB and panel
+ LogOff = LogMessageLowBit | LogMessageHighBit, // Log level "off (3): do not log this message
+ LogWarn = LogMessageHighBit, // Log level "warn" (2): all messages of type Error and Warning are logged
+ LogInfo = LogMessageLowBit, // Log level "info" (1): all messages of level "warn" plus info messages
+ LoggedGenericMessage = GenericMessage | LogWarn, // A GenericMessage that is also logged
+ DirectAuxMessage = AuxMessage | RawMessageFlag, // Direct message to PanelDue
+ ErrorMessage = GenericMessage | LogWarn | ErrorMessageFlag, // An error message
+ WarningMessage = GenericMessage | LogWarn | WarningMessageFlag, // A warning message
+ FirmwareUpdateMessage = UsbMessage | ImmediateAuxMessage, // A message that conveys progress of a firmware update
+ FirmwareUpdateErrorMessage = FirmwareUpdateMessage | ErrorMessageFlag, // A message that reports an error during a firmware update
+ NetworkInfoMessage = UsbMessage | AuxMessage | LogWarn // A message that conveys information about the state of the network interface
+};
+
+inline constexpr MessageType AddLogDebug(MessageType mt) noexcept
+{
+ // Debug level has no flags set such that any non-flagged message automatically
+ // is part of this log level - force it by removing the existing flags
+ return (MessageType)(mt & ~(LogMessageLowBit | LogMessageHighBit));
+}
+
+inline constexpr MessageType AddLogWarn(MessageType mt) noexcept
+{
+ // Since increasing log levels have lower numbers we need to delete
+ // any existing log flags first - otherwise this could lead to MessageLogLevel
+ // rising to 3 which is equivalent to OFF
+ return (MessageType)(AddLogDebug(mt) | LogWarn);
+}
+
+inline constexpr MessageType AddLogInfo(MessageType mt) noexcept
+{
+ // Since increasing log levels have lower numbers we need to delete
+ // any existing log flags first - otherwise this could lead to MessageLogLevel
+ // rising to 3 which is equivalent to OFF
+ return (MessageType)(AddLogDebug(mt) | LogInfo);
+}
+
+inline constexpr MessageType RemoveLogging(MessageType mt) noexcept
+{
+ return (MessageType)(mt | LogOff);
+}
+
+inline constexpr MessageType AddError(MessageType mt) noexcept
+{
+ return AddLogWarn((MessageType)(mt | ErrorMessageFlag));
+}
+
+inline constexpr MessageType AddWarning(MessageType mt) noexcept
+{
+ return AddLogWarn((MessageType)(mt | WarningMessageFlag));
+}
+
+#endif /* MESSAGETYPE_H_ */
diff --git a/src/Platform/OutputMemory.cpp b/src/Platform/OutputMemory.cpp
new file mode 100644
index 00000000..2ef4329d
--- /dev/null
+++ b/src/Platform/OutputMemory.cpp
@@ -0,0 +1,669 @@
+/*
+ * OutputMemory.cpp
+ *
+ * Created on: 10 Jan 2016
+ * Authors: David and Christian
+ */
+
+#include "OutputMemory.h"
+#include "Platform.h"
+#include "RepRap.h"
+#include <cstdarg>
+
+/*static*/ OutputBuffer * volatile OutputBuffer::freeOutputBuffers = nullptr; // Messages may also be sent by ISRs,
+/*static*/ volatile size_t OutputBuffer::usedOutputBuffers = 0; // so make these volatile.
+/*static*/ volatile size_t OutputBuffer::maxUsedOutputBuffers = 0;
+
+//*************************************************************************************************
+// OutputBuffer class implementation
+
+void OutputBuffer::Append(OutputBuffer *other) noexcept
+{
+ if (other != nullptr)
+ {
+ last->next = other;
+ last = other->last;
+ if (other->hadOverflow)
+ {
+ hadOverflow = true;
+ }
+
+ for (OutputBuffer *item = Next(); item != other; item = item->Next())
+ {
+ item->last = last;
+ }
+ }
+}
+
+void OutputBuffer::IncreaseReferences(size_t refs) noexcept
+{
+ if (refs > 0)
+ {
+ TaskCriticalSectionLocker lock;
+
+ for(OutputBuffer *item = this; item != nullptr; item = item->Next())
+ {
+ item->references += refs;
+ item->isReferenced = true;
+ }
+ }
+}
+
+size_t OutputBuffer::Length() const noexcept
+{
+ size_t totalLength = 0;
+ for (const OutputBuffer *current = this; current != nullptr; current = current->Next())
+ {
+ totalLength += current->DataLength();
+ }
+ return totalLength;
+}
+
+char &OutputBuffer::operator[](size_t index) noexcept
+{
+ // Get the right buffer to access
+ OutputBuffer *itemToIndex = this;
+ while (index >= itemToIndex->DataLength())
+ {
+ index -= itemToIndex->DataLength();
+ itemToIndex = itemToIndex->Next();
+ }
+
+ // Return the char reference
+ return itemToIndex->data[index];
+}
+
+char OutputBuffer::operator[](size_t index) const noexcept
+{
+ // Get the right buffer to access
+ const OutputBuffer *itemToIndex = this;
+ while (index >= itemToIndex->DataLength())
+ {
+ index -= itemToIndex->DataLength();
+ itemToIndex = itemToIndex->Next();
+ }
+
+ // Return the char reference
+ return itemToIndex->data[index];
+}
+
+const char *OutputBuffer::Read(size_t len) noexcept
+{
+ size_t offset = bytesRead;
+ bytesRead += len;
+ return data + offset;
+}
+
+// Empty this buffer
+void OutputBuffer::Clear() noexcept
+{
+ if (next != nullptr)
+ {
+ ReleaseAll(next);
+ last = this;
+ }
+ dataLength = 0;
+}
+
+size_t OutputBuffer::vprintf(const char *fmt, va_list vargs) noexcept
+{
+ Clear();
+ return vcatf(fmt, vargs);
+}
+
+size_t OutputBuffer::printf(const char *fmt, ...) noexcept
+{
+ va_list vargs;
+ va_start(vargs, fmt);
+ size_t ret = vprintf(fmt, vargs);
+ va_end(vargs);
+ return ret;
+}
+
+size_t OutputBuffer::vcatf(const char *fmt, va_list vargs) noexcept
+{
+ return vuprintf([this](char c) noexcept -> bool
+ {
+ return c != 0 && cat(c) != 0;
+ },
+ fmt, vargs);
+}
+
+size_t OutputBuffer::catf(const char *fmt, ...) noexcept
+{
+ va_list vargs;
+ va_start(vargs, fmt);
+ size_t ret = vcatf(fmt, vargs);
+ va_end(vargs);
+ return ret;
+}
+
+size_t OutputBuffer::lcatf(const char *fmt, ...) noexcept
+{
+ size_t extra = 0;
+ if (Length() != 0 && operator[](Length() - 1) != '\n')
+ {
+ extra = cat('\n');
+ }
+
+ va_list vargs;
+ va_start(vargs, fmt);
+ const size_t ret = vcatf(fmt, vargs);
+ va_end(vargs);
+ return ret + extra;
+}
+
+size_t OutputBuffer::copy(const char c) noexcept
+{
+ Clear();
+ data[0] = c;
+ dataLength = 1;
+ return 1;
+}
+
+size_t OutputBuffer::copy(const char *src) noexcept
+{
+ return copy(src, strlen(src));
+}
+
+size_t OutputBuffer::copy(const char *src, size_t len) noexcept
+{
+ Clear();
+ return cat(src, len);
+}
+
+size_t OutputBuffer::cat(const char c) noexcept
+{
+ // See if we can append a char
+ if (last->dataLength == OUTPUT_BUFFER_SIZE)
+ {
+ // No - allocate a new item and copy the data
+ OutputBuffer *nextBuffer;
+ if (!Allocate(nextBuffer))
+ {
+ // We cannot store any more data
+ hadOverflow = true;
+ return 0;
+ }
+ nextBuffer->references = references;
+ nextBuffer->copy(c);
+
+ // Link the new item to this list
+ last->next = nextBuffer;
+ for (OutputBuffer *item = this; item != nextBuffer; item = item->Next())
+ {
+ item->last = nextBuffer;
+ }
+ }
+ else
+ {
+ // Yes - we have enough space left
+ last->data[last->dataLength++] = c;
+ }
+ return 1;
+}
+
+size_t OutputBuffer::cat(const char *src) noexcept
+{
+ return cat(src, strlen(src));
+}
+
+size_t OutputBuffer::lcat(const char *src) noexcept
+{
+ return lcat(src, strlen(src));
+}
+
+size_t OutputBuffer::cat(const char *src, size_t len) noexcept
+{
+ size_t copied = 0;
+ while (copied < len)
+ {
+ if (last->dataLength == OUTPUT_BUFFER_SIZE)
+ {
+ // The last buffer is full
+ OutputBuffer *nextBuffer;
+ if (!Allocate(nextBuffer))
+ {
+ // We cannot store any more data, stop here
+ hadOverflow = true;
+ break;
+ }
+ nextBuffer->references = references;
+ last->next = nextBuffer;
+ last = nextBuffer->last;
+ for (OutputBuffer *item = Next(); item != nextBuffer; item = item->Next())
+ {
+ item->last = last;
+ }
+ }
+ const size_t copyLength = min<size_t>(len - copied, OUTPUT_BUFFER_SIZE - last->dataLength);
+ memcpy(last->data + last->dataLength, src + copied, copyLength);
+ last->dataLength += copyLength;
+ copied += copyLength;
+ }
+ return copied;
+}
+
+size_t OutputBuffer::lcat(const char *src, size_t len) noexcept
+{
+ if (Length() != 0)
+ {
+ cat('\n');
+ }
+ return cat(src, len);
+}
+
+size_t OutputBuffer::cat(StringRef &str) noexcept
+{
+ return cat(str.c_str(), str.strlen());
+}
+
+// Encode a character in JSON format, and append it to the buffer and return the number of bytes written
+size_t OutputBuffer::EncodeChar(char c) noexcept
+{
+ char esc;
+ switch (c)
+ {
+ case '\r':
+ esc = 'r';
+ break;
+ case '\n':
+ esc = 'n';
+ break;
+ case '\t':
+ esc = 't';
+ break;
+ case '"':
+ case '\\':
+#if 1
+ // Escaping '/' is optional in JSON, although doing so so confuses PanelDue (fixed in PanelDue firmware version 1.15 and later). As it's optional, we don't do it.
+#else
+ case '/':
+#endif
+ esc = c;
+ break;
+ default:
+ esc = 0;
+ break;
+ }
+
+ if (esc != 0)
+ {
+ cat('\\');
+ cat(esc);
+ return 2;
+ }
+
+ cat(c);
+ return 1;
+}
+
+size_t OutputBuffer::EncodeReply(OutputBuffer *src) noexcept
+{
+ size_t bytesWritten = cat('"');
+
+ while (src != nullptr)
+ {
+ for (size_t index = 0; index < src->DataLength(); ++index)
+ {
+ bytesWritten += EncodeChar(src->Data()[index]);
+ }
+ src = Release(src);
+ }
+
+ bytesWritten += cat('"');
+ return bytesWritten;
+}
+
+#if HAS_MASS_STORAGE
+
+// Write all the data to file, but don't release the buffers
+// Returns true if successful
+bool OutputBuffer::WriteToFile(FileData& f) const noexcept
+{
+ bool endedInNewline = false;
+ const OutputBuffer *current = this;
+ do
+ {
+ if (current->dataLength != 0)
+ {
+ if (!f.Write(current->data, current->dataLength))
+ {
+ return false;
+ }
+ endedInNewline = current->data[current->dataLength - 1] == '\n';
+ }
+ current = current->Next();
+ } while (current != nullptr);
+
+ if (!endedInNewline)
+ {
+ return f.Write('\n');
+ }
+ return true;
+}
+
+#endif
+
+// Initialise the output buffers manager
+/*static*/ void OutputBuffer::Init() noexcept
+{
+ freeOutputBuffers = nullptr;
+ for (size_t i = 0; i < OUTPUT_BUFFER_COUNT; i++)
+ {
+ freeOutputBuffers = new OutputBuffer(freeOutputBuffers);
+ }
+}
+
+// Allocates an output buffer instance which can be used for (large) string outputs. This must be thread safe. Not safe to call from interrupts!
+/*static*/ bool OutputBuffer::Allocate(OutputBuffer *&buf) noexcept
+{
+ {
+ TaskCriticalSectionLocker lock;
+
+ buf = freeOutputBuffers;
+ if (buf != nullptr)
+ {
+ freeOutputBuffers = buf->next;
+ usedOutputBuffers++;
+ if (usedOutputBuffers > maxUsedOutputBuffers)
+ {
+ maxUsedOutputBuffers = usedOutputBuffers;
+ }
+
+ // Initialise the buffer before we release the lock in case another task uses it immediately
+ buf->next = nullptr;
+ buf->last = buf;
+ buf->dataLength = buf->bytesRead = 0;
+ buf->references = 1; // assume it's only used once by default
+ buf->isReferenced = false;
+ buf->hadOverflow = false;
+ buf->whenQueued = millis(); // use the time of allocation as the default when-used time
+
+ return true;
+ }
+ }
+
+ reprap.GetPlatform().LogError(ErrorCode::OutputStarvation);
+ return false;
+}
+
+// Get the number of bytes left for continuous writing
+/*static*/ size_t OutputBuffer::GetBytesLeft(const OutputBuffer *writingBuffer) noexcept
+{
+ const size_t freeBuffers = OUTPUT_BUFFER_COUNT - usedOutputBuffers;
+ const size_t bytesLeft = OUTPUT_BUFFER_SIZE - writingBuffer->last->DataLength();
+
+ if (freeBuffers < RESERVED_OUTPUT_BUFFERS)
+ {
+ // Keep some space left to encapsulate the responses (e.g. via an HTTP header)
+ return bytesLeft;
+ }
+
+ return bytesLeft + (freeBuffers - RESERVED_OUTPUT_BUFFERS) * OUTPUT_BUFFER_SIZE;
+}
+
+// Truncate an output buffer to free up more memory. Returns the number of released bytes.
+// This never releases the first buffer in the chain, so call it with a large value of bytesNeeded to release all buffers except the first.
+/*static */ size_t OutputBuffer::Truncate(OutputBuffer *buffer, size_t bytesNeeded) noexcept
+{
+ // Can we free up space from this chain? Don't break it up if it's referenced anywhere else
+ if (buffer == nullptr || buffer->Next() == nullptr || buffer->IsReferenced())
+ {
+ // No
+ return 0;
+ }
+
+ // Yes - free up the last entries
+ size_t releasedBytes = 0;
+ OutputBuffer *previousItem;
+ do {
+ // Get two the last entries from the chain
+ previousItem = buffer;
+ OutputBuffer *lastItem = previousItem->Next();
+ while (lastItem->Next() != nullptr)
+ {
+ previousItem = lastItem;
+ lastItem = lastItem->Next();
+ }
+
+ // Unlink and free the last entry
+ ReleaseAll(previousItem->next);
+ releasedBytes += OUTPUT_BUFFER_SIZE;
+ } while (previousItem != buffer && releasedBytes < bytesNeeded);
+
+ // Update all the references to the last item
+ for (OutputBuffer *item = buffer; item != nullptr; item = item->Next())
+ {
+ item->last = previousItem;
+ }
+ return releasedBytes;
+}
+
+// Releases an output buffer instance and returns the next entry from the chain
+/*static */ OutputBuffer *OutputBuffer::Release(OutputBuffer *buf) noexcept
+{
+ TaskCriticalSectionLocker lock;
+ OutputBuffer * const nextBuffer = buf->next;
+
+ // If this one is reused by another piece of code, don't free it up
+ if (buf->references > 1)
+ {
+ buf->references--;
+ buf->bytesRead = 0;
+ }
+ else
+ {
+ // Otherwise prepend it to the list of free output buffers again
+ buf->next = freeOutputBuffers;
+ freeOutputBuffers = buf;
+ usedOutputBuffers--;
+ }
+ return nextBuffer;
+}
+
+/*static */ void OutputBuffer::ReleaseAll(OutputBuffer * volatile &buf) noexcept
+{
+ while (buf != nullptr)
+ {
+ buf = Release(buf);
+ }
+}
+
+/*static*/ void OutputBuffer::Diagnostics(MessageType mtype) noexcept
+{
+ reprap.GetPlatform().MessageF(mtype, "Used output buffers: %d of %d (%d max)\n",
+ usedOutputBuffers, OUTPUT_BUFFER_COUNT, maxUsedOutputBuffers);
+}
+
+//*************************************************************************************************
+// OutputStack class implementation
+
+// Push an OutputBuffer chain. Return true if successful, else release the buffer and return false.
+bool OutputStack::Push(OutputBuffer *buffer, MessageType type) volatile noexcept
+{
+ {
+ TaskCriticalSectionLocker lock;
+
+ if (count < OUTPUT_STACK_DEPTH)
+ {
+ if (buffer != nullptr)
+ {
+ buffer->whenQueued = millis();
+ }
+ items[count] = buffer;
+ types[count] = type;
+ count++;
+ return true;
+ }
+ }
+ OutputBuffer::ReleaseAll(buffer);
+ reprap.GetPlatform().LogError(ErrorCode::OutputStackOverflow);
+ return false;
+}
+
+// Pop an OutputBuffer chain or return nullptr if none is available
+OutputBuffer *OutputStack::Pop() volatile noexcept
+{
+ TaskCriticalSectionLocker lock;
+
+ if (count == 0)
+ {
+ return nullptr;
+ }
+
+ OutputBuffer *item = items[0];
+ for (size_t i = 1; i < count; i++)
+ {
+ items[i - 1] = items[i];
+ types[i - 1] = types[i];
+ }
+ count--;
+
+ return item;
+}
+
+// Returns the first item from the stack or nullptr if none is available
+OutputBuffer *OutputStack::GetFirstItem() const volatile noexcept
+{
+ return (count == 0) ? nullptr : items[0];
+}
+
+// Returns the first item's type from the stack or NoDestinationMessage if none is available
+MessageType OutputStack::GetFirstItemType() const volatile noexcept
+{
+ return (count == 0) ? MessageType::NoDestinationMessage : types[0];
+}
+
+#if HAS_LINUX_INTERFACE
+
+// Update the first item of the stack
+void OutputStack::SetFirstItem(OutputBuffer *buffer) volatile noexcept
+{
+ if (count != 0)
+ {
+ if (buffer == nullptr)
+ {
+ (void)Pop();
+ }
+ else
+ {
+ items[0] = buffer;
+ buffer->whenQueued = millis();
+ }
+ }
+}
+
+#endif
+
+// Release the first item at the top of the stack
+void OutputStack::ReleaseFirstItem() volatile noexcept
+{
+ if (count != 0)
+ {
+ OutputBuffer * const buf = items[0]; // capture volatile variable
+ if (buf != nullptr)
+ {
+ items[0] = OutputBuffer::Release(buf);
+ }
+ if (items[0] == nullptr)
+ {
+ (void)Pop();
+ }
+ }
+}
+
+// Release the first item on the top of the stack if it is too old. Return true if the item was timed out or was null.
+bool OutputStack::ApplyTimeout(uint32_t ticks) volatile noexcept
+{
+ bool ret = false;
+ if (count != 0)
+ {
+ OutputBuffer * buf = items[0]; // capture volatile variable
+ while (buf != nullptr && millis() - buf->whenQueued >= ticks)
+ {
+ items[0] = buf = OutputBuffer::Release(buf);
+ ret = true;
+ }
+ if (items[0] == nullptr)
+ {
+ (void)Pop();
+ ret = true;
+ }
+ }
+ return ret;
+}
+
+// Returns the last item from the stack or nullptr if none is available
+OutputBuffer *OutputStack::GetLastItem() const volatile noexcept
+{
+ return (count == 0) ? nullptr : items[count - 1];
+}
+
+// Returns the type of the last item from the stack or NoDestinationMessage if none is available
+MessageType OutputStack::GetLastItemType() const volatile noexcept
+{
+ return (count == 0) ? MessageType::NoDestinationMessage : types[count - 1];
+}
+
+// Get the total length of all queued buffers
+size_t OutputStack::DataLength() const volatile noexcept
+{
+ size_t totalLength = 0;
+
+ TaskCriticalSectionLocker lock;
+ for (size_t i = 0; i < count; i++)
+ {
+ if (items[i] != nullptr)
+ {
+ totalLength += items[i]->Length();
+ }
+ }
+
+ return totalLength;
+}
+
+// Append another OutputStack to this instance. If no more space is available,
+// all OutputBuffers that can't be added are automatically released
+void OutputStack::Append(volatile OutputStack& stack) volatile noexcept
+{
+ for (size_t i = 0; i < stack.count; i++)
+ {
+ if (count < OUTPUT_STACK_DEPTH)
+ {
+ items[count] = stack.items[i];
+ types[count] = stack.types[i];
+ count++;
+ }
+ else
+ {
+ reprap.GetPlatform().LogError(ErrorCode::OutputStackOverflow);
+ OutputBuffer::ReleaseAll(stack.items[i]);
+ }
+ }
+}
+
+// Increase the number of references for each OutputBuffer on the stack
+void OutputStack::IncreaseReferences(size_t num) volatile noexcept
+{
+ TaskCriticalSectionLocker lock;
+ for (size_t i = 0; i < count; i++)
+ {
+ if (items[i] != nullptr)
+ {
+ items[i]->IncreaseReferences(num);
+ }
+ }
+}
+
+// Release all buffers and clean up
+void OutputStack::ReleaseAll() volatile noexcept
+{
+ for (size_t i = 0; i < count; i++)
+ {
+ OutputBuffer::ReleaseAll(items[i]);
+ }
+ count = 0;
+}
+
+// End
diff --git a/src/Platform/OutputMemory.h b/src/Platform/OutputMemory.h
new file mode 100644
index 00000000..b0f0368f
--- /dev/null
+++ b/src/Platform/OutputMemory.h
@@ -0,0 +1,184 @@
+/*
+ * OutputMemory.h
+ *
+ * Created on: 10 Jan 2016
+ * Authors: David and Christian
+ */
+
+#ifndef OUTPUTMEMORY_H_
+#define OUTPUTMEMORY_H_
+
+#include "RepRapFirmware.h"
+#include "MessageType.h"
+#include "Storage/FileData.h"
+
+#if HAS_LINUX_INTERFACE
+const size_t OUTPUT_STACK_DEPTH = 64; // Number of OutputBuffer chains that can be pushed onto one stack instance
+#else
+const size_t OUTPUT_STACK_DEPTH = 4; // Number of OutputBuffer chains that can be pushed onto one stack instance
+#endif
+
+class OutputStack;
+
+// This class is used to hold data for sending (either for Serial or Network destinations)
+class OutputBuffer
+{
+public:
+ friend class OutputStack;
+
+ OutputBuffer(OutputBuffer *n) noexcept : next(n) { }
+ OutputBuffer(const OutputBuffer&) = delete;
+
+ void Append(OutputBuffer *other) noexcept;
+ OutputBuffer *Next() const noexcept { return next; }
+ bool IsReferenced() const noexcept { return isReferenced; }
+ bool HadOverflow() const noexcept { return hadOverflow; }
+ void IncreaseReferences(size_t refs) noexcept;
+
+ const char *Data() const noexcept { return data; }
+ const char *UnreadData() const noexcept { return data + bytesRead; }
+ size_t DataLength() const noexcept { return dataLength; } // How many bytes have been written to this instance?
+ size_t Length() const noexcept; // How many bytes have been written to the whole chain?
+
+ char& operator[](size_t index) noexcept;
+ char operator[](size_t index) const noexcept;
+ const char *Read(size_t len) noexcept;
+ void Taken(size_t len) noexcept { bytesRead += len; }
+ size_t BytesLeft() const noexcept { return dataLength - bytesRead; } // How many bytes have not been sent yet?
+
+ size_t vprintf(const char *fmt, va_list vargs) noexcept;
+ size_t printf(const char *fmt, ...) noexcept __attribute__ ((format (printf, 2, 3)));
+ size_t vcatf(const char *fmt, va_list vargs) noexcept;
+ size_t catf(const char *fmt, ...) noexcept __attribute__ ((format (printf, 2, 3)));
+ size_t lcatf(const char *fmt, ...) noexcept __attribute__ ((format (printf, 2, 3)));
+
+ size_t copy(const char c) noexcept;
+ size_t copy(const char *src) noexcept;
+ size_t copy(const char *src, size_t len) noexcept;
+
+ size_t cat(const char c) noexcept;
+ size_t cat(const char *src) noexcept;
+ size_t lcat(const char *src) noexcept;
+ size_t cat(const char *src, size_t len) noexcept;
+ size_t lcat(const char *src, size_t len) noexcept;
+ size_t cat(StringRef &str) noexcept;
+
+ size_t EncodeChar(char c) noexcept;
+ size_t EncodeReply(OutputBuffer *src) noexcept;
+
+ uint32_t GetAge() const noexcept;
+
+#if HAS_MASS_STORAGE
+ // Write the buffer to file returning true if successful
+ bool WriteToFile(FileData& f) const noexcept;
+#endif
+ // Initialise the output buffers manager
+ static void Init() noexcept;
+
+ // Allocate an unused OutputBuffer instance. Returns true on success or false if no instance could be allocated.
+ static bool Allocate(OutputBuffer *&buf) noexcept;
+
+ // Get the number of bytes left for allocation. If writingBuffer is not NULL, this returns the number of free bytes for
+ // continuous writes, i.e. for writes that need to allocate an extra OutputBuffer instance to finish the message.
+ static size_t GetBytesLeft(const OutputBuffer *writingBuffer) noexcept;
+
+ // Truncate an OutputBuffer instance to free up more memory. Returns the number of released bytes.
+ static size_t Truncate(OutputBuffer *buffer, size_t bytesNeeded) noexcept;
+
+ // Release one OutputBuffer instance. Returns the next item from the chain or nullptr if this was the last instance.
+ __attribute((warn_unused_result)) static OutputBuffer *Release(OutputBuffer *buf) noexcept;
+
+ // Release all OutputBuffer objects in a chain
+ static void ReleaseAll(OutputBuffer * volatile &buf) noexcept;
+
+ static void Diagnostics(MessageType mtype) noexcept;
+
+ static unsigned int GetFreeBuffers() noexcept { return OUTPUT_BUFFER_COUNT - usedOutputBuffers; }
+
+private:
+ void Clear() noexcept;
+
+ OutputBuffer *next;
+ OutputBuffer *last;
+
+ uint32_t whenQueued;
+
+ char data[OUTPUT_BUFFER_SIZE];
+ size_t dataLength, bytesRead;
+
+ bool isReferenced;
+ bool hadOverflow;
+ volatile size_t references;
+
+ static OutputBuffer * volatile freeOutputBuffers; // Messages may be sent by multiple tasks
+ static volatile size_t usedOutputBuffers; // so make these volatile.
+ static volatile size_t maxUsedOutputBuffers;
+};
+
+inline uint32_t OutputBuffer::GetAge() const noexcept
+{
+ return millis() - whenQueued;
+}
+
+// This class is used to manage references to OutputBuffer chains for all output destinations.
+// Note that OutputStack objects should normally be declared volatile.
+class OutputStack
+{
+public:
+ OutputStack() noexcept : count(0) { }
+ OutputStack(const OutputStack&) = delete;
+
+ // Is there anything on this stack?
+ bool IsEmpty() const volatile noexcept { return count == 0; }
+
+ // Clear the reference list
+ void Clear() volatile noexcept { count = 0; }
+
+ // Push an OutputBuffer chain. Return true if successful, else release the buffer and return false.
+ bool Push(OutputBuffer *buffer, MessageType type = NoDestinationMessage) volatile noexcept;
+
+ // Pop an OutputBuffer chain or return NULL if none is available
+ OutputBuffer *Pop() volatile noexcept;
+
+ // Returns the first item from the stack or NULL if none is available
+ OutputBuffer *GetFirstItem() const volatile noexcept;
+
+ // Returns the first item's type from the stack or NoDestinationMessage if none is available
+ MessageType GetFirstItemType() const volatile noexcept;
+
+#if HAS_LINUX_INTERFACE
+ // Set the first item of the stack. If it's NULL, then the first item will be removed
+ void SetFirstItem(OutputBuffer *buffer) volatile noexcept;
+#endif
+ // Release the first item at the top of the stack
+ void ReleaseFirstItem() volatile noexcept;
+
+ // Apply a timeout to the first item at the top of the stack
+ bool ApplyTimeout(uint32_t ticks) volatile noexcept;
+
+ // Returns the last item from the stack or NULL if none is available
+ OutputBuffer *GetLastItem() const volatile noexcept;
+
+ // Returns the type of the last item from the stack or NoDestinationMessage if none is available
+ MessageType GetLastItemType() const volatile noexcept;
+
+ // Get the total length of all queued buffers
+ size_t DataLength() const volatile noexcept;
+
+ // Append another OutputStack to this instance. If no more space is available,
+ // all OutputBuffers that can't be added are automatically released
+ void Append(volatile OutputStack& stack) volatile noexcept;
+
+ // Increase the number of references for each OutputBuffer on the stack
+ void IncreaseReferences(size_t num) volatile noexcept;
+
+ // Release all buffers and clean up
+ void ReleaseAll() volatile noexcept;
+
+private:
+ size_t count;
+ OutputBuffer * items[OUTPUT_STACK_DEPTH];
+ MessageType types[OUTPUT_STACK_DEPTH];
+};
+
+#endif /* OUTPUTMEMORY_H_ */
diff --git a/src/Platform/Platform.cpp b/src/Platform/Platform.cpp
new file mode 100644
index 00000000..ded40ede
--- /dev/null
+++ b/src/Platform/Platform.cpp
@@ -0,0 +1,5227 @@
+/****************************************************************************************************
+
+ RepRapFirmware
+
+ Platform contains all the code and definitions to deal with machine-dependent things such as control
+ pins, bed area, number of extruders, tolerable accelerations and speeds and so on.
+
+ -----------------------------------------------------------------------------------------------------
+
+ Version 0.1
+
+ 18 November 2012
+
+ Adrian Bowyer
+ RepRap Professional Ltd
+ http://reprappro.com
+
+ Licence: GPL
+
+ ****************************************************************************************************/
+
+#include "Platform.h"
+
+#include <Heating/Heat.h>
+#include <Movement/DDA.h>
+#include <Movement/Move.h>
+#include <Movement/StepTimer.h>
+#include <Tools/Tool.h>
+#include <Endstops/ZProbe.h>
+#include <Networking/Network.h>
+#include <PrintMonitor/PrintMonitor.h>
+#include <FilamentMonitors/FilamentMonitor.h>
+#include "RepRap.h"
+#include "Scanner.h"
+#include <Version.h>
+#include "Logger.h"
+#include "Tasks.h"
+#include <Cache.h>
+#include <Hardware/SharedSpi/SharedSpiDevice.h>
+#include <Math/Isqrt.h>
+#include <Hardware/I2C.h>
+#include <Hardware/NonVolatileMemory.h>
+#include <Storage/CRC32.h>
+
+#if SAM4E || SAM4S || SAME70
+# include <Flash.h> // for flash_read_unique_id()
+#endif
+
+#if SAM4E || SAM4S || SAME70
+# include <AnalogIn.h>
+# include <DmacManager.h>
+using LegacyAnalogIn::AdcBits;
+# if SAME70
+static_assert(NumDmaChannelsUsed <= NumDmaChannelsSupported, "Need more DMA channels in CoreNG");
+# endif
+#elif SAME5x
+# include <AnalogIn.h>
+# include <DmacManager.h>
+using AnalogIn::AdcBits; // for compatibility with CoreNG, which doesn't have the AnalogIn namespace
+#elif defined(__LPC17xx__)
+# include "LPC/BoardConfig.h"
+#else
+# include "sam/drivers/tc/tc.h"
+# include "sam/drivers/hsmci/hsmci.h"
+#endif
+
+#include <Libraries/sd_mmc/sd_mmc.h>
+
+#if SUPPORT_TMC2660
+# include "Movement/StepperDrivers/TMC2660.h"
+#endif
+#if SUPPORT_TMC22xx
+# include "Movement/StepperDrivers/TMC22xx.h"
+#endif
+#if SUPPORT_TMC51xx
+# include "Movement/StepperDrivers/TMC51xx.h"
+#endif
+
+#if HAS_WIFI_NETWORKING
+# include <Comms/FirmwareUpdater.h>
+#endif
+
+#if SUPPORT_12864_LCD
+# include "Display/Display.h"
+#endif
+
+#if SUPPORT_IOBITS
+# include "PortControl.h"
+#endif
+
+#if HAS_LINUX_INTERFACE
+# include "Linux/LinuxInterface.h"
+# include "Linux/DataTransfer.h"
+#endif
+
+#if HAS_NETWORKING
+# include "Networking/HttpResponder.h"
+# include "Networking/FtpResponder.h"
+# include "Networking/TelnetResponder.h"
+#endif
+
+#if SUPPORT_CAN_EXPANSION
+# include "CAN/CanMessageGenericConstructor.h"
+# include "CAN/CanInterface.h"
+#endif
+
+#if SUPPORT_REMOTE_COMMANDS
+# include <CanMessageGenericParser.h>
+#endif
+
+#include <climits>
+#include <utility> // for std::swap
+
+#if !defined(HAS_LWIP_NETWORKING) || !defined(HAS_WIFI_NETWORKING) || !defined(HAS_CPU_TEMP_SENSOR) || !defined(HAS_HIGH_SPEED_SD) \
+ || !defined(HAS_SMART_DRIVERS) || !defined(HAS_STALL_DETECT) || !defined(HAS_VOLTAGE_MONITOR) || !defined(HAS_12V_MONITOR) || !defined(HAS_VREF_MONITOR) \
+ || !defined(SUPPORT_NONLINEAR_EXTRUSION) || !defined(SUPPORT_ASYNC_MOVES) || !defined(HAS_MASS_STORAGE)
+# error Missing feature definition
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+
+inline constexpr float AdcReadingToPowerVoltage(uint16_t adcVal)
+{
+ return adcVal * (PowerMonitorVoltageRange/(1u << AdcBits));
+}
+
+inline constexpr uint16_t PowerVoltageToAdcReading(float voltage)
+{
+ return (uint16_t)(voltage * ((1u << AdcBits)/PowerMonitorVoltageRange));
+}
+
+constexpr uint16_t driverPowerOnAdcReading = PowerVoltageToAdcReading(10.0); // minimum voltage at which we initialise the drivers
+constexpr uint16_t driverPowerOffAdcReading = PowerVoltageToAdcReading(9.5); // voltages below this flag the drivers as unusable
+
+# if ENFORCE_MAX_VIN
+constexpr uint16_t driverOverVoltageAdcReading = PowerVoltageToAdcReading(29.0); // voltages above this cause driver shutdown
+constexpr uint16_t driverNormalVoltageAdcReading = PowerVoltageToAdcReading(27.5); // voltages at or below this are normal
+# endif
+
+#endif
+
+#if HAS_12V_MONITOR
+
+inline constexpr float AdcReadingToV12Voltage(uint16_t adcVal)
+{
+ return adcVal * (V12MonitorVoltageRange/(1u << AdcBits));
+}
+
+inline constexpr uint16_t V12VoltageToAdcReading(float voltage)
+{
+ return (uint16_t)(voltage * ((1u << AdcBits)/V12MonitorVoltageRange));
+}
+
+constexpr uint16_t driverV12OnAdcReading = V12VoltageToAdcReading(10.0); // minimum voltage at which we initialise the drivers
+constexpr uint16_t driverV12OffAdcReading = V12VoltageToAdcReading(9.5); // voltages below this flag the drivers as unusable
+
+#endif
+
+const float MinStepPulseTiming = 0.2; // we assume that we always generate step high and low times at least this wide without special action
+
+// Global variable for debugging in tricky situations e.g. within ISRs
+int debugLine = 0;
+
+// Global functions
+
+DriversBitmap AxisDriversConfig::GetDriversBitmap() const noexcept
+{
+ DriversBitmap rslt;
+ for (size_t i = 0; i < numDrivers; ++i)
+ {
+#if SUPPORT_CAN_EXPANSION
+ if (driverNumbers[i].IsLocal())
+#endif
+ {
+ rslt.SetBit(driverNumbers[i].localDriver);
+ }
+ }
+ return rslt;
+}
+
+//*************************************************************************************************
+// Platform class
+
+#if SUPPORT_OBJECT_MODEL
+
+// Object model table and functions
+// Note: if using GCC version 7.3.1 20180622 and lambda functions are used in this table, you must compile this file with option -std=gnu++17.
+// Otherwise the table will be allocate in RAM instead of flash, which wastes too much RAM.
+
+// Macro to build a standard lambda function that includes the necessary type conversions
+#define OBJECT_MODEL_FUNC(...) OBJECT_MODEL_FUNC_BODY(Platform, __VA_ARGS__)
+
+constexpr ObjectModelArrayDescriptor Platform::axisDriversArrayDescriptor =
+{
+ nullptr, // no lock needed
+ [] (const ObjectModel *self, const ObjectExplorationContext& context) noexcept -> size_t { return ((const Platform*)self)->axisDrivers[context.GetLastIndex()].numDrivers; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue
+ { return ExpressionValue(((const Platform*)self)->axisDrivers[context.GetIndex(1)].driverNumbers[context.GetLastIndex()]); }
+};
+
+constexpr ObjectModelArrayDescriptor Platform::workplaceOffsetsArrayDescriptor =
+{
+ nullptr, // no lock needed
+ [] (const ObjectModel *self, const ObjectExplorationContext& context) noexcept -> size_t { return NumCoordinateSystems; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue
+ { return ExpressionValue(reprap.GetGCodes().GetWorkplaceOffset(context.GetIndex(1), context.GetIndex(0)), 3); }
+};
+
+static inline const char* GetFilamentName(size_t extruder) noexcept
+{
+ const Filament *fil = Filament::GetFilamentByExtruder(extruder);
+ return (fil == nullptr) ? "" : fil->GetName();
+}
+
+constexpr ObjectModelTableEntry Platform::objectModelTable[] =
+{
+ // 0. boards[0] members
+#if SUPPORT_CAN_EXPANSION
+ { "canAddress", OBJECT_MODEL_FUNC_NOSELF((int32_t)CanInterface::GetCanAddress()), ObjectModelEntryFlags::none },
+#endif
+#if SUPPORT_12864_LCD
+ { "directDisplay", OBJECT_MODEL_FUNC_IF_NOSELF(reprap.GetDisplay().IsPresent(), &reprap.GetDisplay()), ObjectModelEntryFlags::none },
+#endif
+ { "firmwareDate", OBJECT_MODEL_FUNC_NOSELF(DATE), ObjectModelEntryFlags::none },
+ { "firmwareFileName", OBJECT_MODEL_FUNC_NOSELF(IAP_FIRMWARE_FILE), ObjectModelEntryFlags::none },
+ { "firmwareName", OBJECT_MODEL_FUNC_NOSELF(FIRMWARE_NAME), ObjectModelEntryFlags::none },
+ { "firmwareVersion", OBJECT_MODEL_FUNC_NOSELF(VERSION), ObjectModelEntryFlags::none },
+#if HAS_LINUX_INTERFACE
+ { "iapFileNameSBC", OBJECT_MODEL_FUNC_NOSELF(IAP_UPDATE_FILE_SBC), ObjectModelEntryFlags::none },
+#endif
+#if HAS_MASS_STORAGE
+ { "iapFileNameSD", OBJECT_MODEL_FUNC_NOSELF(IAP_UPDATE_FILE), ObjectModelEntryFlags::none },
+#endif
+ { "maxHeaters", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxHeaters), ObjectModelEntryFlags::verbose },
+ { "maxMotors", OBJECT_MODEL_FUNC_NOSELF((int32_t)NumDirectDrivers), ObjectModelEntryFlags::verbose },
+ { "mcuTemp", OBJECT_MODEL_FUNC(self, 1), ObjectModelEntryFlags::live },
+# ifdef DUET_NG
+ { "name", OBJECT_MODEL_FUNC(self->GetBoardName()), ObjectModelEntryFlags::none },
+ { "shortName", OBJECT_MODEL_FUNC(self->GetBoardShortName()), ObjectModelEntryFlags::none },
+# else
+ { "name", OBJECT_MODEL_FUNC_NOSELF(BOARD_NAME), ObjectModelEntryFlags::none },
+ { "shortName", OBJECT_MODEL_FUNC_NOSELF(BOARD_SHORT_NAME), ObjectModelEntryFlags::none },
+# endif
+ { "supportsDirectDisplay", OBJECT_MODEL_FUNC_NOSELF(SUPPORT_12864_LCD ? true : false), ObjectModelEntryFlags::verbose },
+#if MCU_HAS_UNIQUE_ID
+ { "uniqueId", OBJECT_MODEL_FUNC(self->GetUniqueIdString()), ObjectModelEntryFlags::none },
+#endif
+#if HAS_12V_MONITOR
+ { "v12", OBJECT_MODEL_FUNC(self, 6), ObjectModelEntryFlags::live },
+#endif
+ { "vIn", OBJECT_MODEL_FUNC(self, 2), ObjectModelEntryFlags::live },
+
+#if HAS_CPU_TEMP_SENSOR
+ // 1. mcuTemp members
+ { "current", OBJECT_MODEL_FUNC(self->GetMcuTemperatures().current, 1), ObjectModelEntryFlags::live },
+ { "max", OBJECT_MODEL_FUNC(self->GetMcuTemperatures().max, 1), ObjectModelEntryFlags::none },
+ { "min", OBJECT_MODEL_FUNC(self->GetMcuTemperatures().min, 1), ObjectModelEntryFlags::none },
+#endif
+
+ // 2. vIn members
+#if HAS_VOLTAGE_MONITOR
+ { "current", OBJECT_MODEL_FUNC(self->GetCurrentPowerVoltage(), 1), ObjectModelEntryFlags::live },
+ { "max", OBJECT_MODEL_FUNC(self->GetPowerVoltages().max, 1), ObjectModelEntryFlags::none },
+ { "min", OBJECT_MODEL_FUNC(self->GetPowerVoltages().min, 1), ObjectModelEntryFlags::none },
+#endif
+
+ // 3. move.axes[] members
+ { "acceleration", OBJECT_MODEL_FUNC(self->Acceleration(context.GetLastIndex()), 1), ObjectModelEntryFlags::none },
+ { "babystep", OBJECT_MODEL_FUNC_NOSELF(reprap.GetGCodes().GetTotalBabyStepOffset(context.GetLastIndex()), 3), ObjectModelEntryFlags::none },
+ { "current", OBJECT_MODEL_FUNC((int32_t)lrintf(self->GetMotorCurrent(context.GetLastIndex(), 906))), ObjectModelEntryFlags::none },
+ { "drivers", OBJECT_MODEL_FUNC_NOSELF(&axisDriversArrayDescriptor), ObjectModelEntryFlags::none },
+ { "homed", OBJECT_MODEL_FUNC_NOSELF(reprap.GetGCodes().IsAxisHomed(context.GetLastIndex())), ObjectModelEntryFlags::none },
+ { "jerk", OBJECT_MODEL_FUNC(MinutesToSeconds * self->GetInstantDv(context.GetLastIndex()), 1), ObjectModelEntryFlags::none },
+ { "letter", OBJECT_MODEL_FUNC_NOSELF(reprap.GetGCodes().GetAxisLetters()[context.GetLastIndex()]), ObjectModelEntryFlags::none },
+ { "machinePosition", OBJECT_MODEL_FUNC_NOSELF(reprap.GetMove().LiveCoordinate(context.GetLastIndex(), reprap.GetCurrentTool()), 3), ObjectModelEntryFlags::live },
+ { "max", OBJECT_MODEL_FUNC(self->AxisMaximum(context.GetLastIndex()), 2), ObjectModelEntryFlags::none },
+ { "maxProbed", OBJECT_MODEL_FUNC(self->axisMaximaProbed.IsBitSet(context.GetLastIndex())), ObjectModelEntryFlags::none },
+ { "microstepping", OBJECT_MODEL_FUNC(self, 7), ObjectModelEntryFlags::none },
+ { "min", OBJECT_MODEL_FUNC(self->AxisMinimum(context.GetLastIndex()), 2), ObjectModelEntryFlags::none },
+ { "minProbed", OBJECT_MODEL_FUNC(self->axisMinimaProbed.IsBitSet(context.GetLastIndex())), ObjectModelEntryFlags::none },
+ { "speed", OBJECT_MODEL_FUNC(MinutesToSeconds * self->MaxFeedrate(context.GetLastIndex()), 1), ObjectModelEntryFlags::none },
+ { "stepsPerMm", OBJECT_MODEL_FUNC(self->driveStepsPerUnit[context.GetLastIndex()], 2), ObjectModelEntryFlags::none },
+ { "userPosition", OBJECT_MODEL_FUNC_NOSELF(reprap.GetGCodes().GetUserCoordinate(context.GetLastIndex()), 3), ObjectModelEntryFlags::live },
+ { "visible", OBJECT_MODEL_FUNC_NOSELF(context.GetLastIndex() < (int32_t)reprap.GetGCodes().GetVisibleAxes()), ObjectModelEntryFlags::none },
+ { "workplaceOffsets", OBJECT_MODEL_FUNC_NOSELF(&workplaceOffsetsArrayDescriptor), ObjectModelEntryFlags::none },
+
+ // 4. move.extruders[] members
+ { "acceleration", OBJECT_MODEL_FUNC(self->Acceleration(ExtruderToLogicalDrive(context.GetLastIndex())), 1), ObjectModelEntryFlags::none },
+ { "current", OBJECT_MODEL_FUNC((int32_t)lrintf(self->GetMotorCurrent(ExtruderToLogicalDrive(context.GetLastIndex()), 906))), ObjectModelEntryFlags::none },
+ { "driver", OBJECT_MODEL_FUNC(self->extruderDrivers[context.GetLastIndex()]), ObjectModelEntryFlags::none },
+ { "factor", OBJECT_MODEL_FUNC_NOSELF(reprap.GetGCodes().GetExtrusionFactor(context.GetLastIndex()), 2), ObjectModelEntryFlags::none },
+ { "filament", OBJECT_MODEL_FUNC_NOSELF(GetFilamentName(context.GetLastIndex())), ObjectModelEntryFlags::none },
+ { "jerk", OBJECT_MODEL_FUNC(MinutesToSeconds * self->GetInstantDv(ExtruderToLogicalDrive(context.GetLastIndex())), 1), ObjectModelEntryFlags::none },
+ { "microstepping", OBJECT_MODEL_FUNC(self, 8), ObjectModelEntryFlags::none },
+ { "nonlinear", OBJECT_MODEL_FUNC(self, 5), ObjectModelEntryFlags::none },
+ { "position", OBJECT_MODEL_FUNC_NOSELF(ExpressionValue(reprap.GetMove().LiveCoordinate(ExtruderToLogicalDrive(context.GetLastIndex()), reprap.GetCurrentTool()), 1)), ObjectModelEntryFlags::live },
+ { "pressureAdvance", OBJECT_MODEL_FUNC(self->GetPressureAdvance(context.GetLastIndex()), 2), ObjectModelEntryFlags::none },
+ { "rawPosition", OBJECT_MODEL_FUNC_NOSELF(ExpressionValue(reprap.GetGCodes().GetRawExtruderTotalByDrive(context.GetLastIndex()), 1)), ObjectModelEntryFlags::live },
+ { "speed", OBJECT_MODEL_FUNC(MinutesToSeconds * self->MaxFeedrate(ExtruderToLogicalDrive(context.GetLastIndex())), 1), ObjectModelEntryFlags::none },
+ { "stepsPerMm", OBJECT_MODEL_FUNC(self->driveStepsPerUnit[ExtruderToLogicalDrive(context.GetLastIndex())], 2), ObjectModelEntryFlags::none },
+
+ // 5. move.extruders[].nonlinear members
+ { "a", OBJECT_MODEL_FUNC(self->nonlinearExtrusionA[context.GetLastIndex()], 3), ObjectModelEntryFlags::none },
+ { "b", OBJECT_MODEL_FUNC(self->nonlinearExtrusionB[context.GetLastIndex()], 3), ObjectModelEntryFlags::none },
+ { "upperLimit", OBJECT_MODEL_FUNC(self->nonlinearExtrusionLimit[context.GetLastIndex()], 2), ObjectModelEntryFlags::none },
+
+#if HAS_12V_MONITOR
+ // 6. v12 members
+ { "current", OBJECT_MODEL_FUNC(self->GetV12Voltages().current, 1), ObjectModelEntryFlags::live },
+ { "max", OBJECT_MODEL_FUNC(self->GetV12Voltages().max, 1), ObjectModelEntryFlags::none },
+ { "min", OBJECT_MODEL_FUNC(self->GetV12Voltages().min, 1), ObjectModelEntryFlags::none },
+#endif
+
+ // 7. move.axes[].microstepping members
+ { "interpolated", OBJECT_MODEL_FUNC((self->microstepping[context.GetLastIndex()] & 0x8000) != 0), ObjectModelEntryFlags::none },
+ { "value", OBJECT_MODEL_FUNC((int32_t)(self->microstepping[context.GetLastIndex()] & 0x7FFF)), ObjectModelEntryFlags::none },
+
+ // 8. move.extruders[].microstepping members
+ { "interpolated", OBJECT_MODEL_FUNC((self->microstepping[ExtruderToLogicalDrive(context.GetLastIndex())] & 0x8000) != 0), ObjectModelEntryFlags::none },
+ { "value", OBJECT_MODEL_FUNC((int32_t)(self->microstepping[ExtruderToLogicalDrive(context.GetLastIndex())] & 0x7FFF)), ObjectModelEntryFlags::none },
+};
+
+constexpr uint8_t Platform::objectModelTableDescriptor[] =
+{
+ 9, // number of sections
+ 11 + HAS_LINUX_INTERFACE + HAS_MASS_STORAGE + HAS_12V_MONITOR + SUPPORT_CAN_EXPANSION + SUPPORT_12864_LCD + MCU_HAS_UNIQUE_ID, // section 0: boards[0]
+#if HAS_CPU_TEMP_SENSOR
+ 3, // section 1: mcuTemp
+#else
+ 0,
+#endif
+#if HAS_VOLTAGE_MONITOR
+ 3, // section 2: vIn
+#else
+ 0, // section 2: vIn
+#endif
+ 18, // section 3: move.axes[]
+ 13, // section 4: move.extruders[]
+ 3, // section 5: move.extruders[].nonlinear
+#if HAS_12V_MONITOR
+ 3, // section 6: v12
+#else
+ 0, // section 6: v12
+#endif
+ 2, // section 7: move.axes[].microstepping
+ 2, // section 8: move.extruders[].microstepping
+};
+
+DEFINE_GET_OBJECT_MODEL_TABLE(Platform)
+
+size_t Platform::GetNumGpInputsToReport() const noexcept
+{
+ size_t ret = MaxGpInPorts;
+ while (ret != 0 && gpinPorts[ret - 1].IsUnused())
+ {
+ --ret;
+ }
+ return ret;
+}
+
+size_t Platform::GetNumGpOutputsToReport() const noexcept
+{
+ size_t ret = MaxGpOutPorts;
+ while (ret != 0 && gpoutPorts[ret - 1].IsUnused())
+ {
+ --ret;
+ }
+ return ret;
+}
+
+#endif
+
+bool Platform::deliberateError = false; // true if we deliberately caused an exception for testing purposes
+
+Platform::Platform() noexcept :
+#if HAS_MASS_STORAGE
+ logger(nullptr),
+#endif
+ board(DEFAULT_BOARD_TYPE), active(false), errorCodeBits(0),
+#if HAS_SMART_DRIVERS
+ nextDriveToPoll(0),
+#endif
+ lastFanCheckTime(0),
+#if HAS_AUX_DEVICES
+ panelDueUpdater(nullptr),
+#endif
+#if HAS_MASS_STORAGE
+ sysDir(nullptr),
+#endif
+ tickState(0), debugCode(0),
+ lastWarningMillis(0),
+#if SUPPORT_LASER
+ lastLaserPwm(0.0),
+#endif
+ deferredPowerDown(false)
+{
+}
+
+// Initialise the Platform. Note: this is the first module to be initialised, so don't call other modules from here!
+void Platform::Init() noexcept
+{
+#if defined(DUET3) || defined(DUET3MINI)
+ pinMode(EthernetPhyResetPin, OUTPUT_LOW); // hold the Ethernet Phy chip in reset, hopefully this will prevent it being too noisy if Ethernet is not enabled
+#endif
+
+ // Deal with power first (we assume this doesn't depend on identifying the board type)
+ pinMode(ATX_POWER_PIN, OUTPUT_LOW);
+
+ // Make sure the on-board drivers are disabled
+#if defined(DUET_NG) || defined(PCCB_10) || defined(PCCB_08_X5)
+ pinMode(GlobalTmc2660EnablePin, OUTPUT_HIGH);
+#elif defined(DUET_M) || defined(PCCB_08) || defined(PCCB_08_X5) || defined(DUET3MINI)
+ pinMode(GlobalTmc22xxEnablePin, OUTPUT_HIGH);
+#endif
+
+ // Sort out which board we are running on (some firmware builds support more than one board variant)
+ SetBoardType(BoardType::Auto);
+
+#if SAME70
+ DmacManager::Init();
+#endif
+
+#if MCU_HAS_UNIQUE_ID
+ ReadUniqueId();
+#endif
+
+ // Real-time clock
+ realTime = 0;
+
+ // Comms
+ baudRates[0] = MAIN_BAUD_RATE;
+ commsParams[0] = 0;
+ usbMutex.Create("USB");
+#if SAME5x
+ SERIAL_MAIN_DEVICE.Start();
+#elif defined(__LPC17xx__)
+ SERIAL_MAIN_DEVICE.begin(baudRates[0]);
+#else
+ SERIAL_MAIN_DEVICE.Start(UsbVBusPin);
+#endif
+
+#if HAS_AUX_DEVICES
+ auxDevices[0].Init(&SERIAL_AUX_DEVICE);
+ baudRates[1] = AUX_BAUD_RATE;
+ commsParams[1] = 1; // by default we require a checksum on data from the aux port, to guard against overrun errors
+#endif
+
+#if defined(SERIAL_AUX2_DEVICE) && !defined(DUET3_ATE)
+ auxDevices[1].Init(&SERIAL_AUX2_DEVICE);
+ baudRates[2] = AUX2_BAUD_RATE;
+ commsParams[2] = 0;
+#endif
+
+ // Initialise the IO port subsystem
+ IoPort::Init();
+
+ // Shared SPI subsystem
+ SharedSpiDevice::Init();
+
+ // File management and SD card interfaces
+ for (size_t i = 0; i < NumSdCards; ++i)
+ {
+ pinMode(SdCardDetectPins[i], INPUT_PULLUP);
+ }
+
+#if HAS_MASS_STORAGE || HAS_LINUX_INTERFACE
+ MassStorage::Init();
+#endif
+
+#ifdef __LPC17xx__
+ // Load HW pin assignments from sdcard
+ BoardConfig::Init();
+ pinMode(ATX_POWER_PIN,(ATX_POWER_INVERTED==false)?OUTPUT_LOW:OUTPUT_HIGH);
+#else
+ // Deal with power first (we assume this doesn't depend on identifying the board type)
+ pinMode(ATX_POWER_PIN,OUTPUT_LOW);
+#endif
+
+ // Ethernet networking defaults
+ ipAddress = DefaultIpAddress;
+ netMask = DefaultNetMask;
+ gateWay = DefaultGateway;
+
+ // Do hardware dependent initialisation
+#if VARIABLE_NUM_DRIVERS
+ numActualDirectDrivers = NumDirectDrivers; // assume they are all available until we know otherwise
+#endif
+
+#if HAS_SMART_DRIVERS
+# if defined(DUET_NG)
+ // Test for presence of a DueX2 or DueX5 expansion board and work out how many TMC2660 drivers we have
+ expansionBoard = DuetExpansion::DueXnInit();
+
+ switch (expansionBoard)
+ {
+ case ExpansionBoardType::DueX2:
+ numSmartDrivers = 7;
+ break;
+ case ExpansionBoardType::DueX5:
+ numSmartDrivers = 10;
+ break;
+ case ExpansionBoardType::none:
+ case ExpansionBoardType::DueX0:
+ default:
+ numSmartDrivers = 5; // assume that any additional drivers are dumb enable/step/dir ones
+ break;
+ }
+
+ DuetExpansion::AdditionalOutputInit();
+
+# elif defined(DUET_M)
+ numSmartDrivers = MaxSmartDrivers; // for now we assume that expansion drivers are smart too
+# elif defined(PCCB)
+ numSmartDrivers = MaxSmartDrivers;
+# elif defined(DUET3)
+ numSmartDrivers = MaxSmartDrivers;
+# elif defined(DUET3MINI)
+ numSmartDrivers = MaxSmartDrivers; // support the expansion board, but don't mind if it's missing
+# endif
+#endif
+
+#if defined(DUET_06_085)
+ ARRAY_INIT(defaultMacAddress, DefaultMacAddress);
+
+ // Motor current setting on Duet 0.6 and 0.8.5
+ I2C::Init();
+ mcpExpansion.setMCP4461Address(0x2E); // not required for mcpDuet, as this uses the default address
+ ARRAY_INIT(potWipes, POT_WIPES);
+ senseResistor = SENSE_RESISTOR;
+ maxStepperDigipotVoltage = MAX_STEPPER_DIGIPOT_VOLTAGE;
+ stepperDacVoltageRange = STEPPER_DAC_VOLTAGE_RANGE;
+ stepperDacVoltageOffset = STEPPER_DAC_VOLTAGE_OFFSET;
+#elif defined(__ALLIGATOR__)
+ pinMode(EthernetPhyResetPin, INPUT); // Init Ethernet Phy Reset Pin
+
+ // Alligator Init DAC for motor current vref
+ ARRAY_INIT(spiDacCS, SPI_DAC_CS);
+ dacAlligator.Init(spiDacCS[0]);
+ dacPiggy.Init(spiDacCS[1]);
+ // Get macaddress from EUI48 eeprom
+ eui48MacAddress.Init(Eui48csPin);
+ if (!eui48MacAddress.getEUI48(defaultMacAddress))
+ {
+ ARRAY_INIT(defaultMacAddress, DefaultMacAddress);
+ }
+
+ Microstepping::Init(); // Init Motor FAULT detect Pin
+ pinMode(ExpansionVoltageLevelPin, ExpansionVoltageLevel==3 ? OUTPUT_LOW : OUTPUT_HIGH); // Init Expansion Voltage Level Pin
+ pinMode(MotorFaultDetectPin,INPUT); // Init Motor FAULT detect Pin
+ pinMode(ExpansionPiggyDetectPin,INPUT); // Init Expansion Piggy module presence Pin
+ pinMode(FTDIconverterResetPin,INPUT); // Init FTDI Serial Converter Reset Pin
+ pinMode(SpiEEPROMcsPin,OUTPUT_HIGH); // Init Spi EEPROM Cs pin, not implemented, default unselected
+ pinMode(SpiFLASHcsPin,OUTPUT_HIGH); // Init Spi FLASH Cs pin, not implemented, default unselected
+#endif
+
+#if defined(__LPC17xx__)
+ if (hasDriverCurrentControl)
+ {
+ mcp4451.begin();
+ }
+ Microstepping::Init(); // basic class to remember the Microstepping.
+#endif
+
+ // Initialise endstops. On Duet 2 this must be done after testing for an expansion board.
+ endstops.Init();
+
+ // Axes
+ for (size_t axis = 0; axis < MaxAxes; ++axis)
+ {
+ axisMinima[axis] = DefaultAxisMinimum;
+ axisMaxima[axis] = DefaultAxisMaximum;
+
+ maxFeedrates[axis] = DefaultXYMaxFeedrate;
+ accelerations[axis] = DefaultXYAcceleration;
+ driveStepsPerUnit[axis] = DefaultXYDriveStepsPerUnit;
+ instantDvs[axis] = DefaultXYInstantDv;
+ }
+
+ // We use different defaults for the Z axis
+ maxFeedrates[Z_AXIS] = DefaultZMaxFeedrate;
+ accelerations[Z_AXIS] = DefaultZAcceleration;
+ driveStepsPerUnit[Z_AXIS] = DefaultZDriveStepsPerUnit;
+ instantDvs[Z_AXIS] = DefaultZInstantDv;
+
+ // Extruders
+ for (size_t drive = MaxAxes; drive < MaxAxesPlusExtruders; ++drive)
+ {
+ maxFeedrates[drive] = DefaultEMaxFeedrate;
+ accelerations[drive] = DefaultEAcceleration;
+ driveStepsPerUnit[drive] = DefaultEDriveStepsPerUnit;
+ instantDvs[drive] = DefaultEInstantDv;
+ }
+
+ minimumMovementSpeed = DefaultMinFeedrate;
+ axisMaximaProbed.Clear();
+ axisMinimaProbed.Clear();
+ idleCurrentFactor = DefaultIdleCurrentFactor;
+
+ // Motors
+
+ // Clear out the axis and extruder driver bitmaps
+ for (size_t i = 0; i < MaxAxesPlusExtruders; ++i)
+ {
+ driveDriverBits[i] = 0;
+ }
+
+ // Set up the bitmaps for direct driver access
+ for (size_t driver = 0; driver < NumDirectDrivers; ++driver)
+ {
+ driveDriverBits[driver + MaxAxesPlusExtruders] = StepPins::CalcDriverBitmap(driver);
+ }
+
+ // Set up the local drivers
+ for (size_t driver = 0; driver < NumDirectDrivers; ++driver)
+ {
+ directions[driver] = true; // drive moves forwards by default
+ enableValues[driver] = 0; // assume active low enable signal
+
+#if SUPPORT_REMOTE_COMMANDS
+ remotePressureAdvance[driver] = 0.0;
+#endif
+ // Set up the control pins
+ pinMode(STEP_PINS[driver], OUTPUT_LOW);
+ pinMode(DIRECTION_PINS[driver], OUTPUT_LOW);
+#if !defined(DUET3) && !defined(DUET3MINI)
+ pinMode(ENABLE_PINS[driver], OUTPUT_HIGH); // this is OK for the TMC2660 CS pins too
+#endif
+ }
+
+ // Set up the axis+extruder arrays
+ for (size_t drive = 0; drive < MaxAxesPlusExtruders; drive++)
+ {
+ driverState[drive] = DriverStatus::disabled;
+ driveDriverBits[drive] = 0;
+ motorCurrents[drive] = 0.0;
+ motorCurrentFraction[drive] = 1.0;
+ standstillCurrentPercent[drive] = DefaultStandstillCurrentPercent;
+ microstepping[drive] = 16 | 0x8000; // x16 with interpolation
+ }
+
+ // Set up default axis mapping
+ for (size_t axis = 0; axis < MinAxes; ++axis)
+ {
+#ifdef PCCB
+ const size_t driver = (axis + 1) % 3; // on PCCB we map axes X Y Z to drivers 1 2 0
+#else
+ const size_t driver = axis; // on most boards we map axes straight through to drives
+#endif
+ axisDrivers[axis].numDrivers = 1;
+ axisDrivers[axis].driverNumbers[0].SetLocal(driver);
+ driveDriverBits[axis] = StepPins::CalcDriverBitmap(driver); // overwrite the default value set up earlier
+ }
+ linearAxes = AxesBitmap::MakeLowestNBits(3); // XYZ axes are linear
+
+ for (size_t axis = MinAxes; axis < MaxAxes; ++axis)
+ {
+ axisDrivers[axis].numDrivers = 0;
+ }
+
+ // Set up default extruders
+ for (size_t extr = 0; extr < MaxExtruders; ++extr)
+ {
+ extruderDrivers[extr].SetLocal(extr + MinAxes); // set up default extruder drive mapping
+ driveDriverBits[ExtruderToLogicalDrive(extr)] = StepPins::CalcDriverBitmap(extr + MinAxes);
+ pressureAdvance[extr] = 0.0;
+#if SUPPORT_NONLINEAR_EXTRUSION
+ nonlinearExtrusionA[extr] = nonlinearExtrusionB[extr] = 0.0;
+ nonlinearExtrusionLimit[extr] = DefaultNonlinearExtrusionLimit;
+#endif
+ }
+
+ for (uint32_t& entry : slowDriverStepTimingClocks)
+ {
+ entry = 0; // reset all to zero as we have no known slow drivers yet
+ }
+ slowDriversBitmap = 0; // assume no drivers need extended step pulse timing
+ EnableAllSteppingDrivers(); // no drivers disabled
+
+ driversPowered = false;
+
+#if HAS_SMART_DRIVERS
+ // Initialise TMC driver module
+# if SUPPORT_TMC51xx
+ SmartDrivers::Init();
+# elif SUPPORT_TMC22xx
+# if TMC22xx_VARIABLE_NUM_DRIVERS
+ SmartDrivers::Init(numSmartDrivers);
+# else
+ SmartDrivers::Init();
+# endif
+# else
+ SmartDrivers::Init(ENABLE_PINS, numSmartDrivers);
+# endif
+ temperatureShutdownDrivers.Clear();
+ temperatureWarningDrivers.Clear();
+ shortToGroundDrivers.Clear();
+ openLoadADrivers.Clear();
+ openLoadBDrivers.Clear();
+ notOpenLoadADrivers.Clear();
+ notOpenLoadBDrivers.Clear();
+#endif
+
+#if HAS_STALL_DETECT
+ stalledDrivers.Clear();
+ logOnStallDrivers.Clear();
+ pauseOnStallDrivers.Clear();
+ rehomeOnStallDrivers.Clear();
+ stalledDriversToLog.Clear();
+ stalledDriversToPause.Clear();
+ stalledDriversToRehome.Clear();
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+ autoSaveEnabled = false;
+ autoSaveState = AutoSaveState::starting;
+#endif
+
+#if HAS_SMART_DRIVERS && (HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR)
+ warnDriversNotPowered = false;
+#endif
+
+ extrusionAncilliaryPwmValue = 0.0;
+
+ // Initialise the configured heaters to just the default bed heater (there are no default chamber heaters)
+ configuredHeaters.Clear();
+
+#if !defined(DUET3) && !defined(DUET3MINI)
+ if (DefaultBedHeater >= 0)
+ {
+ configuredHeaters.SetBit(DefaultBedHeater);
+ }
+#endif
+
+ // Enable pullups on all the SPI CS pins. This is required if we are using more than one device on the SPI bus.
+ // Otherwise, when we try to initialise the first device, the other devices may respond as well because their CS lines are not high.
+ for (Pin p : SpiTempSensorCsPins)
+ {
+ pinMode(p, INPUT_PULLUP);
+ }
+
+ // If MISO from a MAX31856 board breaks after initialising the MAX31856 then if MISO floats low and reads as all zeros, this looks like a temperature of 0C and no error.
+ // Enable the pullup resistor, with luck this will make it float high instead.
+#if SAM3XA
+ pinMode(APIN_SHARED_SPI_MISO, INPUT_PULLUP);
+#elif defined(__LPC17xx__) || SAME5x
+ // nothing to do here
+#else
+ pinMode(APIN_USART_SSPI_MISO, INPUT_PULLUP);
+#endif
+
+#ifdef PCCB
+ // Setup the LED ports as GPIO ports
+ for (size_t i = 0; i < ARRAY_SIZE(DefaultGpioPinNames); ++i)
+ {
+ gpoutPorts[i].Assign(DefaultGpioPinNames[i]);
+ }
+#endif
+
+ for (size_t thermistor = 0; thermistor < NumThermistorInputs; thermistor++)
+ {
+ // TODO use ports for these?
+ pinMode(TEMP_SENSE_PINS[thermistor], AIN);
+ filteredAdcChannels[thermistor] = PinToAdcChannel(TEMP_SENSE_PINS[thermistor]); // translate the pin number to the SAM ADC channel number;
+ }
+
+#if HAS_VREF_MONITOR
+ // Set up the VSSA and VREF measurement channels
+ pinMode(VssaSensePin, AIN);
+ filteredAdcChannels[VssaFilterIndex] = PinToAdcChannel(VssaSensePin); // translate the pin number to the SAM ADC channel number
+ pinMode(VrefSensePin, AIN);
+ filteredAdcChannels[VrefFilterIndex] = PinToAdcChannel(VrefSensePin); // translate the pin number to the SAM ADC channel number
+#endif
+
+#if HAS_CPU_TEMP_SENSOR
+# if SAME5x
+ tpFilter.Init(0);
+ AnalogIn::EnableTemperatureSensor(0, tpFilter.CallbackFeedIntoFilter, &tpFilter, 1, 0);
+ tcFilter.Init(0);
+ AnalogIn::EnableTemperatureSensor(1, tcFilter.CallbackFeedIntoFilter, &tcFilter, 1, 0);
+ TemperatureCalibrationInit();
+# else
+ filteredAdcChannels[CpuTempFilterIndex] =
+#if SAM4E || SAM4S || SAME70
+ LegacyAnalogIn::
+#endif
+ GetTemperatureAdcChannel();
+# endif
+#endif
+
+ // Initialise all the ADC filters and enable the corresponding ADC channels
+ for (size_t filter = 0; filter < NumAdcFilters; ++filter)
+ {
+ adcFilters[filter].Init(0);
+ AnalogInEnableChannel(filteredAdcChannels[filter], true);
+ }
+
+ // Hotend configuration
+ nozzleDiameter = NOZZLE_DIAMETER;
+ filamentWidth = FILAMENT_WIDTH;
+
+#if HAS_CPU_TEMP_SENSOR
+ // MCU temperature monitoring
+ highestMcuTemperature = -273.0; // the highest temperature we have seen
+ lowestMcuTemperature = 2000.0; // the lowest temperature we have seen
+ mcuTemperatureAdjust = 0.0;
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+ // Power monitoring
+ vInMonitorAdcChannel = PinToAdcChannel(PowerMonitorVinDetectPin);
+ pinMode(PowerMonitorVinDetectPin, AIN);
+ AnalogInEnableChannel(vInMonitorAdcChannel, true);
+ currentVin = highestVin = 0;
+ lowestVin = 9999;
+ numVinUnderVoltageEvents = previousVinUnderVoltageEvents = numVinOverVoltageEvents = previousVinOverVoltageEvents = 0;
+#endif
+
+#if HAS_12V_MONITOR
+ // Power monitoring
+ v12MonitorAdcChannel = PinToAdcChannel(PowerMonitorV12DetectPin);
+ pinMode(PowerMonitorV12DetectPin, AIN);
+ AnalogInEnableChannel(v12MonitorAdcChannel, true);
+ currentV12 = highestV12 = 0;
+ lowestV12 = 9999;
+ numV12UnderVoltageEvents = previousV12UnderVoltageEvents = 0;
+#endif
+
+ // Kick everything off
+ InitialiseInterrupts();
+
+#ifdef DUET_NG
+ DuetExpansion::DueXnTaskInit(); // must initialise interrupt priorities before calling this
+#endif
+ active = true;
+}
+
+#if MCU_HAS_UNIQUE_ID
+
+// Read the unique ID of the MCU, if it has one
+void Platform::ReadUniqueId()
+{
+# if SAME5x
+ for (size_t i = 0; i < 4; ++i)
+ {
+ uniqueId[i] = *reinterpret_cast<const uint32_t*>(SerialNumberAddresses[i]);
+ }
+# else
+ memset(uniqueId, 0, sizeof(uniqueId));
+
+ const bool cacheWasEnabled = Cache::Disable();
+#if SAM4E || SAM4S || SAME70
+ const bool success = Flash::ReadUniqueId(uniqueId);
+#else
+ const bool success = flash_read_unique_id(uniqueId) == 0;
+#endif
+ if (cacheWasEnabled)
+ {
+ Cache::Enable();
+ }
+
+ if (success)
+ {
+# endif
+ // Put the checksum at the end
+ // We only print 30 5-bit characters = 128 data bits + 22 checksum bits. So compress the 32 checksum bits into 22.
+ uniqueId[4] = uniqueId[0] ^ uniqueId[1] ^ uniqueId[2] ^ uniqueId[3];
+ uniqueId[4] ^= (uniqueId[4] >> 10);
+
+ // On the Duet Ethernet and SAM E70, use the unique chip ID as most of the MAC address.
+ // The unique ID is 128 bits long whereas the whole MAC address is only 48 bits,
+ // so we can't guarantee that each Duet will get a unique MAC address this way.
+ memset(defaultMacAddress.bytes, 0, sizeof(defaultMacAddress.bytes));
+ defaultMacAddress.bytes[0] = 0xBE; // use a fixed first byte with the locally-administered bit set
+ const uint8_t * const idBytes = reinterpret_cast<const uint8_t *>(uniqueId);
+ for (size_t i = 0; i < 15; ++i)
+ {
+ defaultMacAddress.bytes[(i % 5) + 1] ^= idBytes[i];
+ }
+
+ // Convert the unique ID and checksum to a string as 30 base5 alphanumeric digits
+ char *digitPtr = uniqueIdChars;
+ for (size_t i = 0; i < 30; ++i)
+ {
+ if ((i % 5) == 0 && i != 0)
+ {
+ *digitPtr++ = '-';
+ }
+ const size_t index = (i * 5) / 32;
+ const size_t shift = (i * 5) % 32;
+ uint32_t val = uniqueId[index] >> shift;
+ if (shift > 32 - 5)
+ {
+ // We need some bits from the next dword too
+ val |= uniqueId[index + 1] << (32 - shift);
+ }
+ val &= 31;
+ char c;
+ if (val < 10)
+ {
+ c = val + '0';
+ }
+ else
+ {
+ c = val + ('A' - 10);
+ // We have 26 letters in the usual A-Z alphabet and we only need 22 of them plus 0-9.
+ // So avoid using letters C, E, I and O which are easily mistaken for G, F, 1 and 0.
+ if (c >= 'C')
+ {
+ ++c;
+ }
+ if (c >= 'E')
+ {
+ ++c;
+ }
+ if (c >= 'I')
+ {
+ ++c;
+ }
+ if (c >= 'O')
+ {
+ ++c;
+ }
+ }
+ *digitPtr++ = c;
+ }
+ *digitPtr = 0;
+
+# if !SAME5x
+ }
+ else
+ {
+ defaultMacAddress.SetDefault();
+ strcpy(uniqueIdChars, "unknown");
+ }
+# endif
+}
+
+#endif
+
+// Send the beep command to the aux channel. There is no flow control on this port, so it can't block for long.
+void Platform::PanelDueBeep(int freq, int ms) noexcept
+{
+ MessageF(AuxMessage, "{\"beep_freq\":%d,\"beep_length\":%d}\n", freq, ms);
+}
+
+// Send a short message to the aux channel. There is no flow control on this port, so it can't block for long.
+void Platform::SendPanelDueMessage(size_t auxNumber, const char* msg) noexcept
+{
+#if HAS_AUX_DEVICES
+ // Don't send anything to PanelDue while we are flashing it
+ if (!reprap.GetGCodes().IsFlashingPanelDue())
+ {
+ auxDevices[auxNumber].SendPanelDueMessage(msg);
+ }
+#endif
+}
+
+void Platform::Exit() noexcept
+{
+ StopLogging();
+#if HAS_MASS_STORAGE
+ MassStorage::CloseAllFiles();
+#endif
+#if HAS_SMART_DRIVERS
+ SmartDrivers::Exit();
+#endif
+
+ // Stop processing data. Don't try to send a message because it will probably never get there.
+ active = false;
+
+ // Close down USB and serial ports and release output buffers
+ SERIAL_MAIN_DEVICE.end();
+ usbOutput.ReleaseAll();
+
+#if HAS_AUX_DEVICES
+ for (AuxDevice& dev : auxDevices)
+ {
+ dev.Disable();
+ }
+#endif
+}
+
+void Platform::SetIPAddress(IPAddress ip) noexcept
+{
+ ipAddress = ip;
+ reprap.GetNetwork().SetEthernetIPAddress(ipAddress, gateWay, netMask);
+}
+
+void Platform::SetGateWay(IPAddress gw) noexcept
+{
+ gateWay = gw;
+ reprap.GetNetwork().SetEthernetIPAddress(ipAddress, gateWay, netMask);
+}
+
+void Platform::SetNetMask(IPAddress nm) noexcept
+{
+ netMask = nm;
+ reprap.GetNetwork().SetEthernetIPAddress(ipAddress, gateWay, netMask);
+}
+
+// Flush messages to USB and aux, returning true if there is more to send
+bool Platform::FlushMessages() noexcept
+{
+ bool auxHasMore = false;
+#if HAS_AUX_DEVICES
+ for (AuxDevice& dev : auxDevices)
+ {
+ if (dev.Flush())
+ {
+ auxHasMore = true;
+ }
+ }
+#endif
+
+ // Write non-blocking data to the USB line
+ bool usbHasMore = !usbOutput.IsEmpty(); // test first to see if we can avoid getting the mutex
+ if (usbHasMore)
+ {
+ MutexLocker lock(usbMutex);
+ OutputBuffer *usbOutputBuffer = usbOutput.GetFirstItem();
+ if (usbOutputBuffer == nullptr)
+ {
+ (void) usbOutput.Pop();
+ }
+ else if (!SERIAL_MAIN_DEVICE.IsConnected())
+ {
+ // If the USB port is not opened, free the data left for writing
+ OutputBuffer::ReleaseAll(usbOutputBuffer);
+ (void) usbOutput.Pop();
+ }
+ else
+ {
+ // Write as much data as we can...
+ const size_t bytesToWrite = min<size_t>(SERIAL_MAIN_DEVICE.canWrite(), usbOutputBuffer->BytesLeft());
+ if (bytesToWrite != 0)
+ {
+ SERIAL_MAIN_DEVICE.write(usbOutputBuffer->Read(bytesToWrite), bytesToWrite);
+ }
+
+ if (usbOutputBuffer->BytesLeft() == 0)
+ {
+ usbOutput.ReleaseFirstItem();
+ }
+ else
+ {
+ usbOutput.ApplyTimeout(SERIAL_MAIN_TIMEOUT);
+ }
+ }
+ usbHasMore = !usbOutput.IsEmpty();
+ }
+
+ return auxHasMore || usbHasMore;
+}
+
+void Platform::Spin() noexcept
+{
+ if (!active)
+ {
+ return;
+ }
+
+#if defined(DUET3) || defined(DUET3MINI) || defined(__LPC17xx__)
+# if SUPPORT_REMOTE_COMMANDS
+ if (CanInterface::InExpansionMode())
+ {
+ if (StepTimer::IsSynced())
+ {
+ digitalWrite(DiagPin, XNor(DiagOnPolarity, StepTimer::GetMasterTime() & (1u << 19)) != 0);
+ }
+ else
+ {
+ digitalWrite(DiagPin, XNor(DiagOnPolarity, StepTimer::GetTimerTicks() & (1u << 17)) != 0);
+ }
+ }
+ else
+# endif
+ {
+ // Blink the LED at about 2Hz. Duet 3 expansion boards will blink in sync when they have established clock sync with us.
+ digitalWrite(DiagPin, XNor(DiagOnPolarity, StepTimer::GetTimerTicks() & (1u << 19)) != 0);
+ }
+#endif
+
+#if HAS_MASS_STORAGE
+ MassStorage::Spin();
+#endif
+
+ // Try to flush messages to serial ports
+ (void)FlushMessages();
+
+ // Check the MCU max and min temperatures
+#if HAS_CPU_TEMP_SENSOR
+# if SAME5x
+ if (tcFilter.IsValid() && tpFilter.IsValid())
+# else
+ if (adcFilters[CpuTempFilterIndex].IsValid())
+# endif
+ {
+ const float currentMcuTemperature = GetCpuTemperature();
+ if (currentMcuTemperature > highestMcuTemperature)
+ {
+ highestMcuTemperature= currentMcuTemperature;
+ }
+ if (currentMcuTemperature < lowestMcuTemperature)
+ {
+ lowestMcuTemperature = currentMcuTemperature;
+ }
+ }
+#endif
+
+ // Diagnostics test
+ if (debugCode == (unsigned int)DiagnosticTestType::TestSpinLockup)
+ {
+ for (;;) {}
+ }
+
+ // Check whether the TMC drivers need to be initialised.
+ // The tick ISR also looks for over-voltage events, but it just disables the driver without changing driversPowerd or numVinOverVoltageEvents
+ if (driversPowered)
+ {
+#if HAS_VOLTAGE_MONITOR
+ if (currentVin < driverPowerOffAdcReading)
+ {
+ driversPowered = false;
+ ++numVinUnderVoltageEvents;
+ lastVinUnderVoltageValue = currentVin; // save this because the voltage may have changed by the time we report it
+ reprap.GetGCodes().SetAllAxesNotHomed();
+ }
+# if ENFORCE_MAX_VIN
+ else if (currentVin > driverOverVoltageAdcReading)
+ {
+ driversPowered = false;
+ ++numVinOverVoltageEvents;
+ lastVinOverVoltageValue = currentVin; // save this because the voltage may have changed by the time we report it
+ reprap.GetGCodes().SetAllAxesNotHomed();
+ }
+# endif
+ else
+#endif
+
+#if HAS_12V_MONITOR
+ if (currentV12 < driverV12OffAdcReading)
+ {
+ driversPowered = false;
+ ++numV12UnderVoltageEvents;
+ lastV12UnderVoltageValue = currentV12; // save this because the voltage may have changed by the time we report it
+ reprap.GetGCodes().SetAllAxesNotHomed();
+ }
+ else
+#endif
+ {
+#if HAS_SMART_DRIVERS
+ // Check one TMC2660 or TMC2224 for temperature warning or temperature shutdown
+ if (enableValues[nextDriveToPoll] >= 0) // don't poll driver if it is flagged "no poll"
+ {
+ const uint32_t stat = SmartDrivers::GetAccumulatedStatus(nextDriveToPoll, 0);
+ const DriversBitmap mask = DriversBitmap::MakeFromBits(nextDriveToPoll);
+ if (stat & TMC_RR_OT)
+ {
+ temperatureShutdownDrivers |= mask;
+ }
+ else if (stat & TMC_RR_OTPW)
+ {
+ temperatureWarningDrivers |= mask;
+ }
+ if (stat & TMC_RR_S2G)
+ {
+ shortToGroundDrivers |= mask;
+ }
+ else
+ {
+ shortToGroundDrivers &= ~mask;
+ }
+
+ // The driver often produces a transient open-load error, especially in stealthchop mode, so we require the condition to persist before we report it.
+ // Also, false open load indications persist when in standstill, if the phase has zero current in that position
+ if ((stat & TMC_RR_OLA) != 0)
+ {
+ if (!openLoadATimer.IsRunning())
+ {
+ openLoadATimer.Start();
+ openLoadADrivers.Clear();
+ notOpenLoadADrivers.Clear();
+ }
+ openLoadADrivers |= mask;
+ }
+ else if (openLoadATimer.IsRunning())
+ {
+ notOpenLoadADrivers |= mask;
+ if (openLoadADrivers.Disjoint(~notOpenLoadADrivers))
+ {
+ openLoadATimer.Stop();
+ }
+ }
+
+ if ((stat & TMC_RR_OLB) != 0)
+ {
+ if (!openLoadBTimer.IsRunning())
+ {
+ openLoadBTimer.Start();
+ openLoadBDrivers.Clear();
+ notOpenLoadBDrivers.Clear();
+ }
+ openLoadBDrivers |= mask;
+ }
+ else if (openLoadBTimer.IsRunning())
+ {
+ notOpenLoadBDrivers |= mask;
+ if (openLoadBDrivers.Disjoint(~notOpenLoadBDrivers))
+ {
+ openLoadBTimer.Stop();
+ }
+ }
+
+# if HAS_STALL_DETECT
+ if ((stat & TMC_RR_SG) != 0)
+ {
+ if (stalledDrivers.Disjoint(mask))
+ {
+ // This stall is new so check whether we need to perform some action in response to the stall
+ if (rehomeOnStallDrivers.Intersects(mask))
+ {
+ stalledDriversToRehome |= mask;
+ }
+ else if (pauseOnStallDrivers.Intersects(mask))
+ {
+ stalledDriversToPause |= mask;
+ }
+ else if (logOnStallDrivers.Intersects(mask))
+ {
+ stalledDriversToLog |= mask;
+ }
+ }
+ stalledDrivers |= mask;
+ }
+ else
+ {
+ stalledDrivers &= ~mask;
+ }
+# endif
+ }
+
+# if HAS_STALL_DETECT
+ // Action any pause or rehome actions due to motor stalls. This may have to be done more than once.
+ if (stalledDriversToRehome.IsNonEmpty())
+ {
+ if (reprap.GetGCodes().ReHomeOnStall(stalledDriversToRehome))
+ {
+ stalledDriversToRehome.Clear();
+ }
+ }
+ else if (stalledDriversToPause.IsNonEmpty())
+ {
+ if (reprap.GetGCodes().PauseOnStall(stalledDriversToPause))
+ {
+ stalledDriversToPause.Clear();
+ }
+ }
+# endif
+ // Advance drive number ready for next time
+ ++nextDriveToPoll;
+ if (nextDriveToPoll == numSmartDrivers)
+ {
+ nextDriveToPoll = 0;
+ }
+#endif // HAS_SMART_DRIVERS
+ }
+ }
+#if HAS_VOLTAGE_MONITOR && HAS_12V_MONITOR
+ else if (currentVin >= driverPowerOnAdcReading && currentV12 >= driverV12OnAdcReading
+# if ENFORCE_MAX_VIN
+ && currentVin <= driverNormalVoltageAdcReading
+# endif
+ )
+#elif HAS_VOLTAGE_MONITOR
+ else if (currentVin >= driverPowerOnAdcReading
+# if ENFORCE_MAX_VIN
+ && currentVin <= driverNormalVoltageAdcReading
+# endif
+ )
+#elif HAS_12V_MONITOR
+ else if (currentV12 >= driverV12OnAdcReading)
+#else
+ else
+#endif
+ {
+ driversPowered = true;
+#if HAS_SMART_DRIVERS
+ openLoadATimer.Stop();
+ openLoadBTimer.Stop();
+ temperatureShutdownDrivers.Clear();
+ temperatureWarningDrivers.Clear();
+ shortToGroundDrivers.Clear();
+ openLoadADrivers.Clear();
+ openLoadBDrivers.Clear();
+ notOpenLoadADrivers.Clear();
+ notOpenLoadBDrivers.Clear();
+#endif
+ }
+
+#if HAS_SMART_DRIVERS
+ SmartDrivers::Spin(driversPowered);
+#endif
+
+ const uint32_t now = millis();
+
+ // Update the time
+ if (IsDateTimeSet() && now - timeLastUpdatedMillis >= 1000)
+ {
+ ++realTime; // this assumes that time_t is a seconds-since-epoch counter, which is not guaranteed by the C standard
+ timeLastUpdatedMillis += 1000;
+ }
+
+ // Thermostatically-controlled fans (do this after getting TMC driver status)
+ // We should call CheckFans frequently so that blip time is terminated at the right time, but we don't need or want to check sensors that often
+ const bool checkFanSensors = (now - lastFanCheckTime >= FanCheckInterval);
+ const bool thermostaticFanRunning = reprap.GetFansManager().CheckFans(checkFanSensors);
+ if (checkFanSensors)
+ {
+ lastFanCheckTime = now;
+
+ if (deferredPowerDown && !thermostaticFanRunning)
+ {
+ AtxPowerOff(false);
+ }
+
+ // Check whether it is time to report any faults (do this after checking fans in case driver cooling fans are turned on)
+ if (now - lastWarningMillis > MinimumWarningInterval)
+ {
+ bool reported = false;
+#if HAS_SMART_DRIVERS
+ ReportDrivers(ErrorMessage, shortToGroundDrivers, "short-to-ground", reported);
+ ReportDrivers(ErrorMessage, temperatureShutdownDrivers, "over temperature shutdown", reported);
+ if (openLoadATimer.CheckAndStop(OpenLoadTimeout))
+ {
+ ReportDrivers(WarningMessage, openLoadADrivers, "motor phase A may be disconnected", reported);
+ }
+ if (openLoadBTimer.CheckAndStop(OpenLoadTimeout))
+ {
+ ReportDrivers(WarningMessage, openLoadBDrivers, "motor phase B may be disconnected", reported);
+ }
+
+ // Don't warn about a hot driver if we recently turned on a fan to cool it
+ if (temperatureWarningDrivers.IsNonEmpty())
+ {
+ const DriversBitmap driversMonitored[NumTmcDriversSenseChannels] =
+# ifdef DUET_NG
+ { DriversBitmap::MakeLowestNBits(5), DriversBitmap::MakeLowestNBits(5).ShiftUp(5) }; // first channel is Duet, second is DueX5
+# elif defined(DUET_M)
+ { DriversBitmap::MakeLowestNBits(5), DriversBitmap::MakeLowestNBits(2).ShiftUp(5) }; // first channel is Duet, second is daughter board
+# else
+ { DriversBitmap::MakeLowestNBits(NumDirectDrivers) };
+# endif
+ for (unsigned int i = 0; i < NumTmcDriversSenseChannels; ++i)
+ {
+ if (driversFanTimers[i].IsRunning())
+ {
+ const bool timedOut = driversFanTimers[i].CheckAndStop(DriverCoolingTimeout);
+ if (!timedOut)
+ {
+ temperatureWarningDrivers &= ~driversMonitored[i];
+ }
+ }
+ }
+ ReportDrivers(WarningMessage, temperatureWarningDrivers, "high temperature", reported);
+ }
+#endif
+
+#if HAS_STALL_DETECT
+ // Check for stalled drivers that need to be reported and logged
+ if (stalledDriversToLog.IsNonEmpty() && reprap.GetGCodes().IsReallyPrinting())
+ {
+ String<StringLength100> scratchString;
+ ListDrivers(scratchString.GetRef(), stalledDriversToLog);
+ stalledDriversToLog.Clear();
+ MessageF(WarningMessage, "Driver(s)%s stalled at Z height %.2f", scratchString.c_str(), (double)reprap.GetMove().LiveCoordinate(Z_AXIS, reprap.GetCurrentTool()));
+ reported = true;
+ }
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+ if (numVinOverVoltageEvents != previousVinOverVoltageEvents)
+ {
+ MessageF(WarningMessage, "VIN over-voltage event (%.1fV)", (double)AdcReadingToPowerVoltage(lastVinOverVoltageValue));
+ previousVinOverVoltageEvents = numVinOverVoltageEvents;
+ reported = true;
+ }
+ if (numVinUnderVoltageEvents != previousVinUnderVoltageEvents)
+ {
+ MessageF(WarningMessage, "VIN under-voltage event (%.1fV)", (double)AdcReadingToPowerVoltage(lastVinUnderVoltageValue));
+ previousVinUnderVoltageEvents = numVinUnderVoltageEvents;
+ reported = true;
+ }
+#endif
+
+#if HAS_12V_MONITOR
+ if (numV12UnderVoltageEvents != previousV12UnderVoltageEvents)
+ {
+ MessageF(WarningMessage, "12V under-voltage event (%.1fV)", (double)AdcReadingToPowerVoltage(lastV12UnderVoltageValue));
+ previousV12UnderVoltageEvents = numV12UnderVoltageEvents;
+ reported = true;
+ }
+#endif
+
+ // Check for a VSSA fault
+#if HAS_VREF_MONITOR
+ constexpr uint32_t MaxVssaFilterSum = (15 * (1u << AdcBits) * ThermistorAverageReadings * 4)/2200; // VSSA fuse should have <= 15 ohms resistance
+ if (adcFilters[VssaFilterIndex].GetSum() > MaxVssaFilterSum)
+ {
+ Message(ErrorMessage, "VSSA fault, check thermistor wiring\n");
+ reported = true;
+ }
+#elif defined(DUET_NG)
+ if ( (board == BoardType::DuetWiFi_102 || board == BoardType::DuetEthernet_102)
+ && digitalRead(VssaSensePin)
+ )
+ {
+ Message(ErrorMessage, "VSSA fault, check thermistor wiring\n");
+ reported = true;
+ }
+#endif
+
+#if HAS_SMART_DRIVERS && (HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR)
+ // Check for attempts to move motors when not powered
+ if (warnDriversNotPowered)
+ {
+ Message(ErrorMessage, "Attempt to move motors when VIN is not in range\n");
+ warnDriversNotPowered = false;
+ reported = true;
+ }
+#endif
+ if (reported)
+ {
+ lastWarningMillis = now;
+ }
+ }
+ }
+
+#if HAS_VOLTAGE_MONITOR
+ // Check for auto-pause, shutdown or resume
+ if (autoSaveEnabled)
+ {
+ switch (autoSaveState)
+ {
+ case AutoSaveState::starting:
+ // Some users set the auto resume threshold high to disable auto resume, so prime auto save at the auto save threshold plus half a volt
+ if (currentVin >= autoResumeReading || currentVin > autoPauseReading + PowerVoltageToAdcReading(0.5))
+ {
+ autoSaveState = AutoSaveState::normal;
+ }
+ break;
+
+ case AutoSaveState::normal:
+ if (currentVin < autoPauseReading)
+ {
+ if (reprap.GetGCodes().LowVoltagePause())
+ {
+ autoSaveState = AutoSaveState::autoPaused;
+ }
+ }
+ break;
+
+ case AutoSaveState::autoPaused:
+ if (currentVin >= autoResumeReading)
+ {
+ if (reprap.GetGCodes().LowVoltageResume())
+ {
+ autoSaveState = AutoSaveState::normal;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+#endif
+
+#if HAS_MASS_STORAGE
+ // Flush the log file it it is time. This may take some time, so do it last.
+ if (logger != nullptr)
+ {
+ logger->Flush(false);
+ }
+#endif
+
+}
+
+#if HAS_SMART_DRIVERS
+
+// Report driver status conditions that require attention.
+// Sets 'reported' if we reported anything, else leaves 'reported' alone.
+void Platform::ReportDrivers(MessageType mt, DriversBitmap& whichDrivers, const char* text, bool& reported) noexcept
+{
+ if (whichDrivers.IsNonEmpty())
+ {
+ String<StringLength100> scratchString;
+ scratchString.printf("%s reported by driver(s)", text);
+ whichDrivers.Iterate([&scratchString](unsigned int drive, unsigned int) noexcept { scratchString.catf(" %u", drive); });
+ MessageF(mt, "%s\n", scratchString.c_str());
+ reported = true;
+ whichDrivers.Clear();
+ }
+}
+
+#endif
+
+#if HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR
+
+bool Platform::HasVinPower() const noexcept
+{
+# if HAS_SMART_DRIVERS
+ return driversPowered; // not quite right because drivers are disabled if we get over-voltage too, or if the 12V rail is low, but OK for the status report
+# else
+ return true;
+# endif
+}
+
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+
+void Platform::DisableAutoSave() noexcept
+{
+ autoSaveEnabled = false;
+}
+
+bool Platform::IsPowerOk() const noexcept
+{
+ // FIXME Implement auto-save for the SBC
+ return ( !autoSaveEnabled
+#if HAS_LINUX_INTERFACE
+ || reprap.UsingLinuxInterface()
+#endif
+ )
+ || currentVin > autoPauseReading;
+}
+
+void Platform::EnableAutoSave(float saveVoltage, float resumeVoltage) noexcept
+{
+ autoPauseReading = PowerVoltageToAdcReading(saveVoltage);
+ autoResumeReading = PowerVoltageToAdcReading(resumeVoltage);
+ autoSaveEnabled = true;
+}
+
+bool Platform::GetAutoSaveSettings(float& saveVoltage, float&resumeVoltage) noexcept
+{
+ if (autoSaveEnabled)
+ {
+ saveVoltage = AdcReadingToPowerVoltage(autoPauseReading);
+ resumeVoltage = AdcReadingToPowerVoltage(autoResumeReading);
+ }
+ return autoSaveEnabled;
+}
+
+#endif
+
+#if HAS_CPU_TEMP_SENSOR
+
+float Platform::GetCpuTemperature() const noexcept
+{
+#if SAME5x
+ // From the datasheet:
+ // T = (tl * vph * tc - th * vph * tc - tl * tp *vch + th * tp * vcl)/(tp * vcl - tp * vch - tc * vpl * tc * vph)
+ const uint16_t tc_result = tcFilter.GetSum()/(tcFilter.NumAveraged() << (AnalogIn::AdcBits - 12));
+ const uint16_t tp_result = tpFilter.GetSum()/(tpFilter.NumAveraged() << (AnalogIn::AdcBits - 12));
+
+ int32_t result = (tempCalF1 * tc_result - tempCalF2 * tp_result);
+ const int32_t divisor = (tempCalF3 * tp_result - tempCalF4 * tc_result);
+ result = (divisor == 0) ? 0 : result/divisor;
+ return (float)result/16 + mcuTemperatureAdjust;
+#else
+ const float voltage = (float)adcFilters[CpuTempFilterIndex].GetSum() * (3.3/(float)((1u << AdcBits) * ThermistorAverageReadings));
+# if SAM4E || SAM4S
+ return (voltage - 1.44) * (1000.0/4.7) + 27.0 + mcuTemperatureAdjust; // accuracy at 27C is +/-13C
+# elif SAM3XA
+ return (voltage - 0.8) * (1000.0/2.65) + 27.0 + mcuTemperatureAdjust; // accuracy at 27C is +/-45C
+# elif SAME70
+ return (voltage - 0.72) * (1000.0/2.33) + 25.0 + mcuTemperatureAdjust; // accuracy at 25C is +/-34C
+# else
+# error undefined CPU temp conversion
+# endif
+#endif
+}
+
+#endif
+
+//*****************************************************************************************************************
+// Interrupts
+
+#if SAME5x
+// Set a contiguous range of interrupts to the specified priority
+static void SetInterruptPriority(IRQn base, unsigned int num, uint32_t prio)
+{
+ do
+ {
+ NVIC_SetPriority(base, prio);
+ base = (IRQn)(base + 1);
+ --num;
+ }
+ while (num != 0);
+}
+#endif
+
+void Platform::InitialiseInterrupts() noexcept
+{
+ // Watchdog interrupt priority if applicable has already been set up in RepRap::Init
+
+#if HAS_HIGH_SPEED_SD
+ NVIC_SetPriority(SdhcIRQn, NvicPriorityHSMCI); // set priority for SD interface interrupts
+#endif
+
+ // Set PanelDue UART interrupt priority is set in AuxDevice::Init
+ // WiFi UART interrupt priority is now set in module WiFiInterface
+
+#if SUPPORT_TMC22xx && !SAME5x // SAME5x uses a DMA interrupt instead of the UART interrupt
+# if TMC22xx_HAS_MUX
+ NVIC_SetPriority(TMC22xx_UART_IRQn, NvicPriorityDriversSerialTMC); // set priority for TMC2660 SPI interrupt
+# else
+ NVIC_SetPriority(TMC22xxUartIRQns[0], NvicPriorityDriversSerialTMC);
+ NVIC_SetPriority(TMC22xxUartIRQns[1], NvicPriorityDriversSerialTMC);
+# endif
+#endif
+
+#if SUPPORT_TMC2660
+ NVIC_SetPriority(TMC2660_SPI_IRQn, NvicPriorityDriversSerialTMC); // set priority for TMC2660 SPI interrupt
+#endif
+
+#if HAS_LWIP_NETWORKING
+ // Set up the Ethernet interface priority here to because we have access to the priority definitions
+# if SAME70 || SAME5x
+ NVIC_SetPriority(GMAC_IRQn, NvicPriorityEthernet);
+# else
+ NVIC_SetPriority(EMAC_IRQn, NvicPriorityEthernet);
+# endif
+#endif
+
+#if SAME5x
+ SetInterruptPriority(DMAC_0_IRQn, 5, NvicPriorityDMA); // SAME5x DMAC has 5 contiguous IRQ numbers
+#elif SAME70
+ NVIC_SetPriority(XDMAC_IRQn, NvicPriorityDMA);
+#endif
+
+#ifdef __LPC17xx__
+ // Interrupt for GPIO pins. Only port 0 and 2 support interrupts and both share EINT3
+ NVIC_SetPriority(EINT3_IRQn, NvicPriorityPins);
+#elif SAME5x
+ SetInterruptPriority(EIC_0_IRQn, 16, NvicPriorityPins); // SAME5x EXINT has 16 contiguous IRQ numbers
+#else
+ NVIC_SetPriority(PIOA_IRQn, NvicPriorityPins);
+ NVIC_SetPriority(PIOB_IRQn, NvicPriorityPins);
+ NVIC_SetPriority(PIOC_IRQn, NvicPriorityPins);
+# ifdef ID_PIOD
+ NVIC_SetPriority(PIOD_IRQn, NvicPriorityPins);
+# endif
+# ifdef ID_PIOE
+ NVIC_SetPriority(PIOE_IRQn, NvicPriorityPins);
+# endif
+#endif
+
+#if SAME5x
+ SetInterruptPriority(USB_0_IRQn, 4, NvicPriorityUSB); // SAME5x USB has 4 contiguous IRQ numbers
+#elif SAME70
+ NVIC_SetPriority(USBHS_IRQn, NvicPriorityUSB);
+#elif SAM4E || SAM4S
+ NVIC_SetPriority(UDP_IRQn, NvicPriorityUSB);
+#elif SAM3XA
+ NVIC_SetPriority(UOTGHS_IRQn, NvicPriorityUSB);
+#elif defined(__LPC17xx__)
+ NVIC_SetPriority(USB_IRQn, NvicPriorityUSB);
+#else
+# error Unsupported processor
+#endif
+
+#if defined(DUET_NG) || defined(DUET_M) || defined(DUET_06_085)
+ NVIC_SetPriority(I2C_IRQn, NvicPriorityTwi);
+#elif defined(__LPC17xx__)
+ NVIC_SetPriority(I2C0_IRQn, NvicPriorityTwi);
+ NVIC_SetPriority(I2C1_IRQn, NvicPriorityTwi);
+#endif
+
+#if SUPPORT_CAN_EXPANSION
+# if SAME5x
+ NVIC_SetPriority(CAN0_IRQn, NvicPriorityCan);
+ NVIC_SetPriority(CAN1_IRQn, NvicPriorityCan);
+# elif SAME70
+ NVIC_SetPriority(MCAN0_INT0_IRQn, NvicPriorityCan); // we don't use INT1
+ NVIC_SetPriority(MCAN1_INT0_IRQn, NvicPriorityCan); // we don't use INT1
+# endif
+#endif
+
+#if defined(__LPC17xx__)
+ // set rest of the Timer Interrupt priorities
+ // Timer 0 is used for step generation (set elsewhere)
+ NVIC_SetPriority(TIMER1_IRQn, 8); //Timer 1 is currently unused
+ NVIC_SetPriority(TIMER2_IRQn, NvicPriorityTimerServo); //Timer 2 runs the PWM for Servos at 50hz
+ NVIC_SetPriority(TIMER3_IRQn, NvicPriorityTimerPWM); //Timer 3 runs the microsecond free running timer to generate heater/fan PWM
+#endif
+
+ StepTimer::Init(); // initialise the step pulse timer
+
+ // Tick interrupt for ADC conversions
+ tickState = 0;
+ currentFilterNumber = 0;
+}
+
+//*************************************************************************************************
+
+// Debugging variables
+//extern "C" uint32_t longestWriteWaitTime, shortestWriteWaitTime, longestReadWaitTime, shortestReadWaitTime;
+//extern uint32_t maxRead, maxWrite;
+
+// Return diagnostic information
+void Platform::Diagnostics(MessageType mtype) noexcept
+{
+#if USE_CACHE && (SAM4E || SAME5x)
+ // Get the cache statistics before we start messing around with the cache
+ const uint32_t cacheCount = Cache::GetHitCount();
+#endif
+
+ Message(mtype, "=== Platform ===\n");
+
+ // Debugging support
+ if (debugLine != 0)
+ {
+ MessageF(mtype, "Debug line %d\n", debugLine);
+ }
+
+ // Show the up time and reason for the last reset
+ const uint32_t now = (uint32_t)(millis64()/1000u); // get up time in seconds
+
+#if SAME5x
+ {
+ String<StringLength100> resetString;
+ resetString.printf("Last reset %02d:%02d:%02d ago, cause", (unsigned int)(now/3600), (unsigned int)((now % 3600)/60), (unsigned int)(now % 60));
+ const uint8_t resetReason = RSTC->RCAUSE.reg;
+ // The datasheet says only one of these bits will be set, but we don't assume that
+ if (resetReason & RSTC_RCAUSE_POR) { resetString.cat(": power up"); }
+ if (resetReason & RSTC_RCAUSE_BODCORE) { resetString.cat(": core brownout"); }
+ if (resetReason & RSTC_RCAUSE_BODVDD) { resetString.cat(": Vdd brownout"); }
+ if (resetReason & RSTC_RCAUSE_WDT) { resetString.cat(": watchdog"); }
+ if (resetReason & RSTC_RCAUSE_NVM) { resetString.cat(": NVM"); }
+ if (resetReason & RSTC_RCAUSE_EXT) { resetString.cat(": reset button"); }
+ if (resetReason & RSTC_RCAUSE_SYST) { resetString.cat(": software"); }
+ if (resetReason & RSTC_RCAUSE_BACKUP) { resetString.cat(": backup/hibernate"); }
+ resetString.cat('\n');
+ Message(mtype, resetString.c_str());
+ }
+#elif defined(__LPC17xx__)
+ // Reset Reason
+ MessageF(mtype, "Last reset %02d:%02d:%02d ago, cause: ",
+ (unsigned int)(now/3600), (unsigned int)((now % 3600)/60), (unsigned int)(now % 60));
+
+ if (LPC_SYSCTL->RSID & RSID_POR) { Message(mtype, "[power up]"); }
+ if (LPC_SYSCTL->RSID & RSID_EXTR) { Message(mtype, "[reset button]"); }
+ if (LPC_SYSCTL->RSID & RSID_WDTR) { Message(mtype, "[watchdog]"); }
+ if (LPC_SYSCTL->RSID & RSID_BODR) { Message(mtype, "[brownout]"); }
+ if (LPC_SYSCTL->RSID & RSID_SYSRESET) { Message(mtype, "[software]"); }
+ if (LPC_SYSCTL->RSID & RSID_LOCKUP) { Message(mtype, "[lockup]"); }
+
+ Message(mtype, "\n");
+#else
+ const char* resetReasons[8] = { "power up", "backup", "watchdog", "software",
+# ifdef DUET_NG
+ // On the SAM4E a watchdog reset may be reported as a user reset because of the capacitor on the NRST pin.
+ // The SAM4S is the same but the Duet M has a diode in the reset circuit to avoid this problem.
+ "reset button or watchdog",
+# else
+ "reset button",
+# endif
+ "?", "?", "?" };
+ MessageF(mtype, "Last reset %02d:%02d:%02d ago, cause: %s\n",
+ (unsigned int)(now/3600), (unsigned int)((now % 3600)/60), (unsigned int)(now % 60),
+ resetReasons[(REG_RSTC_SR & RSTC_SR_RSTTYP_Msk) >> RSTC_SR_RSTTYP_Pos]);
+#endif
+
+ // Show the reset code stored at the last software reset
+ {
+ NonVolatileMemory mem;
+ unsigned int slot;
+ const SoftwareResetData * const srd = mem.GetLastWrittenResetData(slot);
+ if (srd == nullptr)
+ {
+ Message(mtype, "Last software reset details not available\n");
+ }
+ else
+ {
+ srd->Print(mtype, slot);
+ }
+ }
+
+ // Show the current error codes
+ MessageF(mtype, "Error status: 0x%02" PRIx32 "\n", errorCodeBits); // we only use the bottom 5 bits at present, so print just 2 characters
+
+#if HAS_AUX_DEVICES
+ // Show the aux port status
+ for (size_t i = 0; i < ARRAY_SIZE(auxDevices); ++i)
+ {
+ auxDevices[i].Diagnostics(mtype, i);
+ }
+#endif
+
+#if HAS_CPU_TEMP_SENSOR
+ // Show the MCU temperatures
+ const float currentMcuTemperature = GetCpuTemperature();
+ MessageF(mtype, "MCU temperature: min %.1f, current %.1f, max %.1f\n",
+ (double)lowestMcuTemperature, (double)currentMcuTemperature, (double)highestMcuTemperature);
+ lowestMcuTemperature = highestMcuTemperature = currentMcuTemperature;
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+ // Show the supply voltage
+ MessageF(mtype, "Supply voltage: min %.1f, current %.1f, max %.1f, under voltage events: %" PRIu32 ", over voltage events: %" PRIu32 ", power good: %s\n",
+ (double)AdcReadingToPowerVoltage(lowestVin), (double)AdcReadingToPowerVoltage(currentVin), (double)AdcReadingToPowerVoltage(highestVin),
+ numVinUnderVoltageEvents, numVinOverVoltageEvents,
+ (HasVinPower()) ? "yes" : "no");
+ lowestVin = highestVin = currentVin;
+#endif
+
+#if HAS_12V_MONITOR
+ // Show the 12V rail voltage
+ MessageF(mtype, "12V rail voltage: min %.1f, current %.1f, max %.1f, under voltage events: %" PRIu32 "\n",
+ (double)AdcReadingToPowerVoltage(lowestV12), (double)AdcReadingToPowerVoltage(currentV12), (double)AdcReadingToPowerVoltage(highestV12), numV12UnderVoltageEvents);
+ lowestV12 = highestV12 = currentV12;
+#endif
+
+ // Show the motor position and stall status
+ for (size_t drive = 0; drive < NumDirectDrivers; ++drive)
+ {
+ String<StringLength256> driverStatus;
+ driverStatus.printf("Driver %u: position %" PRIi32, drive, reprap.GetMove().GetEndPoint(drive));
+#if HAS_SMART_DRIVERS
+ if (drive < numSmartDrivers)
+ {
+ driverStatus.cat(", ");
+ SmartDrivers::AppendDriverStatus(drive, driverStatus.GetRef());
+ }
+#endif
+ driverStatus.cat('\n');
+ Message(mtype, driverStatus.c_str());
+ }
+
+ // Show current RTC time
+ Message(mtype, "Date/time: ");
+ struct tm timeInfo;
+ if (gmtime_r(&realTime, &timeInfo) != nullptr)
+ {
+ MessageF(mtype, "%04u-%02u-%02u %02u:%02u:%02u\n",
+ timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
+ timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
+ }
+ else
+ {
+ Message(mtype, "not set\n");
+ }
+
+#if USE_CACHE && (SAM4E || SAME5x)
+ MessageF(mtype, "Cache data hit count %" PRIu32 "\n", cacheCount);
+#endif
+
+ reprap.Timing(mtype);
+
+#if 0
+ // Debugging temperature readings
+ const uint32_t div = ThermistorAveragingFilter::NumAveraged() >> 2; // 2 oversample bits
+ MessageF(mtype, "Vssa %" PRIu32 " Vref %" PRIu32 " Temp0 %" PRIu32 " Temp1 %" PRIu32 "\n",
+ adcFilters[VssaFilterIndex].GetSum()/div, adcFilters[VrefFilterIndex].GetSum()/div, adcFilters[0].GetSum()/div, adcFilters[1].GetSum()/div);
+#endif
+
+#ifdef SOFT_TIMER_DEBUG
+ MessageF(mtype, "Soft timer interrupts executed %u, next %u scheduled at %u, now %u\n",
+ numSoftTimerInterruptsExecuted, STEP_TC->TC_CHANNEL[STEP_TC_CHAN].TC_RB, lastSoftTimerInterruptScheduledAt, GetTimerTicks());
+#endif
+
+#ifdef I2C_IFACE
+ const TwoWire::ErrorCounts errs = I2C_IFACE.GetErrorCounts(true);
+ MessageF(mtype, "I2C nak errors %" PRIu32 ", send timeouts %" PRIu32 ", receive timeouts %" PRIu32 ", finishTimeouts %" PRIu32 ", resets %" PRIu32 "\n",
+ errs.naks, errs.sendTimeouts, errs.recvTimeouts, errs.finishTimeouts, errs.resets);
+#endif
+}
+
+// Execute a timed square root that takes less than one millisecond
+static uint32_t TimedSqrt(uint64_t arg, uint32_t& timeAcc) noexcept
+{
+ cpu_irq_disable();
+ asm volatile("":::"memory");
+ uint32_t now1 = SysTick->VAL;
+ const uint32_t ret = isqrt64(arg);
+ uint32_t now2 = SysTick->VAL;
+ asm volatile("":::"memory");
+ cpu_irq_enable();
+ now1 &= 0x00FFFFFF;
+ now2 &= 0x00FFFFFF;
+ timeAcc += ((now1 > now2) ? now1 : now1 + (SysTick->LOAD & 0x00FFFFFF) + 1) - now2;
+ return ret;
+}
+
+GCodeResult Platform::DiagnosticTest(GCodeBuffer& gb, const StringRef& reply, OutputBuffer*& buf, unsigned int d) THROWS(GCodeException)
+{
+ switch (d)
+ {
+ case (unsigned int)DiagnosticTestType::PrintTestReport:
+ {
+ bool testFailed = false;
+ if (!OutputBuffer::Allocate(buf))
+ {
+ reply.copy("No output buffer");
+ return GCodeResult::error;
+ }
+
+#if HAS_MASS_STORAGE
+ // Check the SD card detect and speed
+ if (!MassStorage::IsCardDetected(0))
+ {
+ buf->copy("SD card 0 not detected");
+ testFailed = true;
+ }
+# if HAS_HIGH_SPEED_SD
+ else if (sd_mmc_get_interface_speed(0) != ExpectedSdCardSpeed)
+ {
+ buf->printf("SD card speed %.2fMbytes/sec is unexpected", (double)((float)sd_mmc_get_interface_speed(0) * 0.000001));
+ testFailed = true;
+ }
+# endif
+ else
+ {
+ buf->copy("SD card interface OK");
+ }
+#endif
+
+#if HAS_CPU_TEMP_SENSOR
+ // Check the MCU temperature
+ {
+ gb.MustSee('T');
+ float tempMinMax[2];
+ size_t numTemps = 2;
+ gb.GetFloatArray(tempMinMax, numTemps, false);
+ const float currentMcuTemperature = GetCpuTemperature();
+ if (currentMcuTemperature < tempMinMax[0])
+ {
+ buf->lcatf("MCU temperature %.1f is lower than expected", (double)currentMcuTemperature);
+ testFailed = true;
+ }
+ else if (currentMcuTemperature > tempMinMax[1])
+ {
+ buf->lcatf("MCU temperature %.1f is higher than expected", (double)currentMcuTemperature);
+ testFailed = true;
+ }
+ else
+ {
+ buf->lcat("MCU temperature reading OK");
+ }
+ }
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+ // Check the supply voltage
+ {
+ gb.MustSee('V');
+ float voltageMinMax[2];
+ size_t numVoltages = 2;
+ gb.GetFloatArray(voltageMinMax, numVoltages, false);
+ const float voltage = AdcReadingToPowerVoltage(currentVin);
+ if (voltage < voltageMinMax[0])
+ {
+ buf->lcatf("VIN voltage reading %.1f is lower than expected", (double)voltage);
+ testFailed = true;
+ }
+ else if (voltage > voltageMinMax[1])
+ {
+ buf->lcatf("VIN voltage reading %.1f is higher than expected", (double)voltage);
+ testFailed = true;
+ }
+ else
+ {
+ buf->lcat("VIN voltage reading OK");
+ }
+ }
+#endif
+
+#if HAS_12V_MONITOR
+ // Check the 12V rail voltage
+ {
+ gb.MustSee('W');
+ float voltageMinMax[2];
+ size_t numVoltages = 2;
+ gb.GetFloatArray(voltageMinMax, numVoltages, false);
+
+ const float voltage = AdcReadingToPowerVoltage(currentV12);
+ if (voltage < voltageMinMax[0])
+ {
+ buf->lcatf("12V voltage reading %.1f is lower than expected", (double)voltage);
+ testFailed = true;
+ }
+ else if (voltage > voltageMinMax[1])
+ {
+ buf->lcatf("12V voltage reading %.1f is higher than expected", (double)voltage);
+ testFailed = true;
+ }
+ else
+ {
+ buf->lcat("12V voltage reading OK");
+ }
+ }
+#endif
+
+#if HAS_SMART_DRIVERS
+ // Check the stepper driver status
+ bool driversOK = true;
+ for (size_t driver = 0; driver < numSmartDrivers; ++driver)
+ {
+ const uint32_t stat = SmartDrivers::GetAccumulatedStatus(driver, 0xFFFFFFFF);
+ if ((stat & (TMC_RR_OT || TMC_RR_OTPW)) != 0)
+ {
+ buf->lcatf("Driver %u reports over temperature", driver);
+ driversOK = false;
+ }
+ if ((stat & TMC_RR_S2G) != 0)
+ {
+ buf->lcatf("Driver %u reports short-to-ground", driver);
+ driversOK = false;
+ }
+ }
+ if (driversOK)
+ {
+ buf->lcat("Driver status OK");
+ }
+ else
+ {
+ testFailed = true;
+ }
+#endif
+ buf->lcat((testFailed) ? "***** ONE OR MORE CHECKS FAILED *****" : "All checks passed");
+
+#if MCU_HAS_UNIQUE_ID
+ if (!testFailed)
+ {
+ buf->lcatf("Board ID: %s", GetUniqueIdString());
+ }
+#endif
+ }
+ break;
+
+ case (int)DiagnosticTestType::OutputBufferStarvation:
+ {
+ OutputBuffer *buf;
+ while (OutputBuffer::Allocate(buf)) { }
+ OutputBuffer::ReleaseAll(buf);
+ }
+ break;
+
+ case (int)DiagnosticTestType::SetWriteBuffer:
+#if SAME70
+ //TODO set cache to write-back instead
+ reply.copy("Write buffer not supported on this processor");
+ return GCodeResult::error;
+#else
+ if (gb.Seen('S'))
+ {
+ if (gb.GetUIValue() > 0)
+ {
+ SCnSCB->ACTLR &= ~SCnSCB_ACTLR_DISDEFWBUF_Msk; // enable write buffer
+ }
+ else
+ {
+ SCnSCB->ACTLR |= SCnSCB_ACTLR_DISDEFWBUF_Msk; // disable write buffer
+ }
+ }
+ else
+ {
+ reply.printf("Write buffer is %s", (SCnSCB->ACTLR & SCnSCB_ACTLR_DISDEFWBUF_Msk) ? "disabled" : "enabled");
+ }
+ break;
+#endif
+
+ case (unsigned int)DiagnosticTestType::TestWatchdog:
+ if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry
+ {
+ return GCodeResult::notFinished;
+ }
+ deliberateError = true;
+ SysTick->CTRL &= ~(SysTick_CTRL_TICKINT_Msk); // disable the system tick interrupt so that we get a watchdog timeout reset
+ break;
+
+ case (unsigned int)DiagnosticTestType::TestSpinLockup:
+ if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry
+ {
+ return GCodeResult::notFinished;
+ }
+ deliberateError = true;
+ debugCode = d; // tell the Spin function to loop
+ break;
+
+ case (unsigned int)DiagnosticTestType::TestSerialBlock: // write an arbitrary message via debugPrintf()
+ if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry
+ {
+ return GCodeResult::notFinished;
+ }
+ deliberateError = true;
+ debugPrintf("Diagnostic Test\n");
+ break;
+
+ case (unsigned int)DiagnosticTestType::DivideByZero: // do an integer divide by zero to test exception handling
+ if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry
+ {
+ return GCodeResult::notFinished;
+ }
+ deliberateError = true;
+ (void)RepRap::DoDivide(1, 0); // call function in another module so it can't be optimised away
+ break;
+
+ case (unsigned int)DiagnosticTestType::UnalignedMemoryAccess: // disable unaligned memory accesses
+ if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry
+ {
+ return GCodeResult::notFinished;
+ }
+ deliberateError = true;
+ SCB->CCR |= SCB_CCR_UNALIGN_TRP_Msk; // by default, unaligned memory accesses are allowed, so change that
+ // We don't actually generate a fault any more, instead we let this function identify existing unaligned accesses in the code
+ break;
+
+ case (unsigned int)DiagnosticTestType::BusFault:
+#if SAME70 && !USE_MPU
+ Message(WarningMessage, "There is no abort area on the SAME70 with MPU disabled");
+#else
+ if (!gb.DoDwellTime(1000)) // wait a second to allow the response to be sent back to the web server, otherwise it may retry
+ {
+ return GCodeResult::notFinished;
+ }
+ deliberateError = true;
+ RepRap::GenerateBusFault();
+#endif
+ break;
+
+ case (unsigned int)DiagnosticTestType::AccessMemory:
+ {
+ gb.MustSee('A');
+ const uint32_t address = gb.GetUIValue();
+ uint32_t val;
+ bool dummy;
+ deliberateError = true; // in case the memory access causes a fault
+ if (gb.TryGetUIValue('V', val, dummy))
+ {
+ *reinterpret_cast<uint32_t*>(address) = val;
+ __DSB(); // allow the write to complete in case it raises a fault
+ }
+ else
+ {
+ reply.printf("Address %08" PRIx32 " value %08" PRIx32, address, *reinterpret_cast<const uint32_t*>(address));
+ }
+ deliberateError = false;
+ }
+ break;
+
+ case (unsigned int)DiagnosticTestType::PrintMoves:
+ DDA::PrintMoves();
+ break;
+
+ case (unsigned int)DiagnosticTestType::TimeCalculations: // Show the square root calculation time. Caution: may disable interrupt for several tens of microseconds.
+ {
+ bool ok1 = true;
+ uint32_t tim1 = 0;
+ for (uint32_t i = 0; i < 100; ++i)
+ {
+ const uint32_t num1 = 0x7fffffff - (67 * i);
+ const uint64_t sq = (uint64_t)num1 * num1;
+ const uint32_t num1a = TimedSqrt(sq, tim1);
+ if (num1a != num1)
+ {
+ ok1 = false;
+ }
+ }
+
+ bool ok2 = true;
+ uint32_t tim2 = 0;
+ constexpr uint32_t iterations = 100; // use a value that divides into one million
+ for (uint32_t i = 0; i < iterations; ++i)
+ {
+ const uint32_t num2 = 0x0000ffff - (67 * i);
+ const uint64_t sq = (uint64_t)num2 * num2;
+ const uint32_t num2a = TimedSqrt(sq, tim2);
+ if (num2a != num2)
+ {
+ ok2 = false;
+ }
+ }
+
+ reply.printf("Square roots: 62-bit %.2fus %s, 32-bit %.2fus %s",
+ (double)((float)(tim1 * (1'000'000/iterations))/SystemCoreClock), (ok1) ? "ok" : "ERROR",
+ (double)((float)(tim2 * (1'000'000/iterations))/SystemCoreClock), (ok2) ? "ok" : "ERROR");
+ }
+
+ // We now also time sine and cosine in the same test
+ {
+ uint32_t tim1 = 0;
+ constexpr uint32_t iterations = 100; // use a value that divides into one million
+ for (unsigned int i = 0; i < iterations; ++i)
+ {
+ const float angle = 0.01 * i;
+
+ cpu_irq_disable();
+ asm volatile("":::"memory");
+ uint32_t now1 = SysTick->VAL;
+ (void)RepRap::SinfCosf(angle);
+ uint32_t now2 = SysTick->VAL;
+ asm volatile("":::"memory");
+ cpu_irq_enable();
+ now1 &= 0x00FFFFFF;
+ now2 &= 0x00FFFFFF;
+ tim1 += ((now1 > now2) ? now1 : now1 + (SysTick->LOAD & 0x00FFFFFF) + 1) - now2;
+ }
+
+ // We no longer calculate sin and cos for doubles because it pulls in those library functions, which we don't otherwise need
+ reply.lcatf("Float sine + cosine: %.2fus", (double)((float)(tim1 * (1'000'000/iterations))/SystemCoreClock));
+ }
+
+ // We also time floating point square root so we can compare it with sine/cosine in order to consider various optimisations
+ {
+ uint32_t tim1 = 0;
+ constexpr uint32_t iterations = 100; // use a value that divides into one million
+ float val = 10000.0;
+ for (unsigned int i = 0; i < iterations; ++i)
+ {
+
+ cpu_irq_disable();
+ asm volatile("":::"memory");
+ uint32_t now1 = SysTick->VAL;
+ val = RepRap::FastSqrtf(val);
+ uint32_t now2 = SysTick->VAL;
+ asm volatile("":::"memory");
+ cpu_irq_enable();
+ now1 &= 0x00FFFFFF;
+ now2 &= 0x00FFFFFF;
+ tim1 += ((now1 > now2) ? now1 : now1 + (SysTick->LOAD & 0x00FFFFFF) + 1) - now2;
+ }
+
+ // We no longer calculate sin and cos for doubles because it pulls in those library functions, which we don't otherwise need
+ reply.lcatf("Float sqrt: %.2fus", (double)((float)(tim1 * (1'000'000/iterations))/SystemCoreClock));
+ }
+ break;
+
+ case (unsigned int)DiagnosticTestType::TimeSDWrite:
+#if HAS_MASS_STORAGE
+ return reprap.GetGCodes().StartSDTiming(gb, reply);
+#else
+ reply.copy("No SD card interface available");
+ return GCodeResult::errorNotSupported;
+#endif
+
+ case (unsigned int)DiagnosticTestType::PrintObjectSizes:
+ reply.printf(
+ "DDA %u, DM %u, Tool %u, GCodeBuffer %u, heater %u"
+#if HAS_NETWORKING
+ ", HTTP resp %u, FTP resp %u, Telnet resp %u"
+#endif
+ , sizeof(DDA), sizeof(DriveMovement), sizeof(Tool), sizeof(GCodeBuffer), sizeof(Heater)
+#if HAS_NETWORKING
+ , sizeof(HttpResponder), sizeof(FtpResponder), sizeof(TelnetResponder)
+#endif
+ );
+ break;
+
+ case (unsigned int)DiagnosticTestType::PrintObjectAddresses:
+ MessageF(MessageType::GenericMessage,
+ "Platform %08" PRIx32 "-%08" PRIx32
+#if HAS_LINUX_INTERFACE
+ "\nLinuxInterface %08" PRIx32 "-%08" PRIx32
+#endif
+ "\nNetwork %08" PRIx32 "-%08" PRIx32
+ "\nGCodes %08" PRIx32 "-%08" PRIx32
+ "\nMove %08" PRIx32 "-%08" PRIx32
+ "\nHeat %08" PRIx32 "-%08" PRIx32
+ , reinterpret_cast<uint32_t>(this), reinterpret_cast<uint32_t>(this) + sizeof(Platform) - 1
+#if HAS_LINUX_INTERFACE
+ , reinterpret_cast<uint32_t>(&reprap.GetLinuxInterface())
+ , (reinterpret_cast<uint32_t>(&reprap.GetLinuxInterface()) == 0) ? 0 : reinterpret_cast<uint32_t>(&reprap.GetLinuxInterface()) + sizeof(LinuxInterface)
+#endif
+ , reinterpret_cast<uint32_t>(&reprap.GetNetwork()), reinterpret_cast<uint32_t>(&reprap.GetNetwork()) + sizeof(Network) - 1
+ , reinterpret_cast<uint32_t>(&reprap.GetGCodes()), reinterpret_cast<uint32_t>(&reprap.GetGCodes()) + sizeof(GCodes) - 1
+ , reinterpret_cast<uint32_t>(&reprap.GetMove()), reinterpret_cast<uint32_t>(&reprap.GetMove()) + sizeof(Move) - 1
+ , reinterpret_cast<uint32_t>(&reprap.GetHeat()), reinterpret_cast<uint32_t>(&reprap.GetHeat()) + sizeof(Heat) - 1
+ );
+
+ MessageF(MessageType::GenericMessage,
+ "\nPrintMonitor %08" PRIx32 "-%08" PRIx32
+ "\nFansManager %08" PRIx32 "-%08" PRIx32
+#if SUPPORT_ROLAND
+ "\nRoland %08" PRIx32 "-%08" PRIx32
+#endif
+#if SUPPORT_SCANNER
+ "\nScanner %08" PRIx32 "-%08" PRIx32
+#endif
+#if SUPPORT_IOBITS
+ "\nPortControl %08" PRIx32 "-%08" PRIx32
+#endif
+#if SUPPORT_12864_LCD
+ "\nDisplay %08" PRIx32 "-%08" PRIx32
+#endif
+#if SUPPORT_CAN_EXPANSION
+ "\nExpansionManager %08" PRIx32 "-%08" PRIx32
+#endif
+
+ , reinterpret_cast<uint32_t>(&reprap.GetPrintMonitor()), reinterpret_cast<uint32_t>(&reprap.GetPrintMonitor()) + sizeof(PrintMonitor) - 1
+ , reinterpret_cast<uint32_t>(&reprap.GetFansManager()), reinterpret_cast<uint32_t>(&reprap.GetFansManager()) + sizeof(FansManager) - 1
+#if SUPPORT_ROLAND
+ , reinterpret_cast<uint32_t>(&reprap.GetRoland()), reinterpret_cast<uint32_t>(&reprap.GetRoland()) + sizeof(Roland) - 1
+#endif
+#if SUPPORT_SCANNER
+ , reinterpret_cast<uint32_t>(&reprap.GetScanner()), reinterpret_cast<uint32_t>(&reprap.GetScanner()) + sizeof(Scanner) - 1
+#endif
+#if SUPPORT_IOBITS
+ , reinterpret_cast<uint32_t>(&reprap.GetPortControl()), reinterpret_cast<uint32_t>(&reprap.GetPortControl()) + sizeof(PortControl) - 1
+#endif
+#if SUPPORT_12864_LCD
+ , reinterpret_cast<uint32_t>(&reprap.GetDisplay()), reinterpret_cast<uint32_t>(&reprap.GetDisplay()) + sizeof(Display) - 1
+#endif
+#if SUPPORT_CAN_EXPANSION
+ , reinterpret_cast<uint32_t>(&reprap.GetExpansion()), reinterpret_cast<uint32_t>(&reprap.GetExpansion()) + sizeof(ExpansionManager) - 1
+#endif
+ );
+ break;
+
+ case (unsigned int)DiagnosticTestType::TimeCRC32:
+ {
+ const size_t length = (gb.Seen('S')) ? gb.GetUIValue() : 1024;
+ CRC32 crc;
+ cpu_irq_disable();
+ asm volatile("":::"memory");
+ uint32_t now1 = SysTick->VAL;
+ crc.Update(
+#if SAME5x
+ reinterpret_cast<const char*>(HSRAM_ADDR),
+#else
+ reinterpret_cast<const char*>(IRAM_ADDR), // for the SAME70 this is in the non-cacheable RAM, which is the usual case when computing a CRC
+#endif
+ length);
+ uint32_t now2 = SysTick->VAL;
+ asm volatile("":::"memory");
+ cpu_irq_enable();
+ now1 &= 0x00FFFFFF;
+ now2 &= 0x00FFFFFF;
+ uint32_t tim1 = ((now1 > now2) ? now1 : now1 + (SysTick->LOAD & 0x00FFFFFF) + 1) - now2;
+ reply.printf("CRC of %u bytes took %.2fus", length, (double)((1'000'000.0f * (float)tim1)/(float)SystemCoreClock));
+ }
+ break;
+
+ case (unsigned int)DiagnosticTestType::TimeGetTimerTicks:
+ {
+ unsigned int i = 100;
+ cpu_irq_disable();
+ asm volatile("":::"memory");
+ uint32_t now1 = SysTick->VAL;
+ do
+ {
+ --i;
+ (void)StepTimer::GetTimerTicks();
+ } while (i != 0);
+ uint32_t now2 = SysTick->VAL;
+ asm volatile("":::"memory");
+ cpu_irq_enable();
+ now1 &= 0x00FFFFFF;
+ now2 &= 0x00FFFFFF;
+ uint32_t tim1 = ((now1 > now2) ? now1 : now1 + (SysTick->LOAD & 0x00FFFFFF) + 1) - now2;
+ reply.printf("Reading step timer 100 times took %.2fus", (double)((1'000'000.0f * (float)tim1)/(float)SystemCoreClock));
+ }
+
+#if SUPPORT_CAN_EXPANSION
+ // Also check the correspondence between the CAN timestamp timer and the step clock
+ {
+ uint32_t startClocks, endClocks;
+ uint16_t startTimeStamp, endTimeStamp;
+ {
+ AtomicCriticalSectionLocker lock;
+ startClocks = StepTimer::GetTimerTicks();
+ startTimeStamp = CanInterface::GetTimeStampCounter();
+ }
+ delay(2);
+ {
+ AtomicCriticalSectionLocker lock;
+ endClocks = StepTimer::GetTimerTicks();
+ endTimeStamp = CanInterface::GetTimeStampCounter();
+ }
+# if SAME70
+ const uint32_t tsDiff = (endTimeStamp - startTimeStamp) & 0xFFFF;
+# else
+ const uint32_t tsDiff = (((endTimeStamp - startTimeStamp) & 0xFFFF) * CanInterface::GetTimeStampPeriod()) >> 6;
+# endif
+ reply.lcatf("Clock diff %" PRIu32 ", ts diff %" PRIu32, endClocks - startClocks, tsDiff);
+ }
+#endif
+ break;
+
+#ifdef DUET_NG
+ case (unsigned int)DiagnosticTestType::PrintExpanderStatus:
+ reply.printf("Expander status %04X\n", DuetExpansion::DiagnosticRead());
+ break;
+#endif
+
+#ifdef __LPC17xx__
+ // Diagnostic for LPC board configuration
+ case (int)DiagnosticTestType::PrintBoardConfiguration:
+ BoardConfig::Diagnostics(gb.GetResponseMessageType());
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ return GCodeResult::ok;
+}
+
+#if HAS_SMART_DRIVERS
+
+// This is called when a fan that monitors driver temperatures is turned on when it was off
+void Platform::DriverCoolingFansOnOff(DriverChannelsBitmap driverChannelsMonitored, bool on) noexcept
+{
+ driverChannelsMonitored.Iterate
+ ([this, on](unsigned int i, unsigned int) noexcept
+ {
+ if (on)
+ {
+ this->driversFanTimers[i].Start();
+ }
+ else
+ {
+ this->driversFanTimers[i].Stop();
+ }
+ }
+ );
+}
+
+#endif
+
+// Get the index of the averaging filter for an analog port.
+// Note, the Thermistor code assumes that this is also the thermistor input number
+int Platform::GetAveragingFilterIndex(const IoPort& port) const noexcept
+{
+ for (size_t i = 0; i < NumAdcFilters; ++i)
+ {
+ if (port.GetAnalogChannel() == filteredAdcChannels[i])
+ {
+ return (int)i;
+ }
+ }
+ return -1;
+}
+
+void Platform::UpdateConfiguredHeaters() noexcept
+{
+ configuredHeaters.Clear();
+
+ // Check bed heaters
+ for (size_t i = 0; i < MaxBedHeaters; i++)
+ {
+ const int8_t bedHeater = reprap.GetHeat().GetBedHeater(i);
+ if (bedHeater >= 0)
+ {
+ configuredHeaters.SetBit(bedHeater);
+ }
+ }
+
+ // Check chamber heaters
+ for (size_t i = 0; i < MaxChamberHeaters; i++)
+ {
+ const int8_t chamberHeater = reprap.GetHeat().GetChamberHeater(i);
+ if (chamberHeater >= 0)
+ {
+ configuredHeaters.SetBit(chamberHeater);
+ }
+ }
+
+ // Check tool heaters
+ for (size_t heater = 0; heater < MaxHeaters; heater++)
+ {
+ if (reprap.IsHeaterAssignedToTool(heater))
+ {
+ configuredHeaters.SetBit(heater);
+ }
+ }
+}
+
+#if HAS_MASS_STORAGE
+
+// Write the platform parameters to file
+bool Platform::WritePlatformParameters(FileStore *f, bool includingG31) const noexcept
+{
+ bool ok;
+ if (axisMinimaProbed.IsNonEmpty() || axisMaximaProbed.IsNonEmpty())
+ {
+ ok = f->Write("; Probed axis limits\n");
+ if (ok)
+ {
+ ok = WriteAxisLimits(f, axisMinimaProbed, axisMinima, 1);
+ }
+ if (ok)
+ {
+ ok = WriteAxisLimits(f, axisMaximaProbed, axisMaxima, 0);
+ }
+ }
+ else
+ {
+ ok = true;
+ }
+
+ if (ok && includingG31)
+ {
+ ok = endstops.WriteZProbeParameters(f, includingG31);
+ }
+
+ return ok;
+}
+
+bool Platform::WriteAxisLimits(FileStore *f, AxesBitmap axesProbed, const float limits[MaxAxes], int sParam) noexcept
+{
+ if (axesProbed.IsEmpty())
+ {
+ return true;
+ }
+
+ String<StringLength100> scratchString;
+ scratchString.printf("M208 S%d", sParam);
+ axesProbed.Iterate([&scratchString, limits](unsigned int axis, unsigned int) noexcept { scratchString.catf(" %c%.2f", reprap.GetGCodes().GetAxisLetters()[axis], (double)limits[axis]); });
+ scratchString.cat('\n');
+ return f->Write(scratchString.c_str());
+}
+
+#endif
+
+#if SUPPORT_CAN_EXPANSION
+
+// Function to identify and iterate through all drivers attached to an axis or extruder
+void Platform::IterateDrivers(size_t axisOrExtruder, stdext::inplace_function<void(uint8_t)> localFunc, stdext::inplace_function<void(DriverId)> remoteFunc) noexcept
+{
+ if (axisOrExtruder < reprap.GetGCodes().GetTotalAxes())
+ {
+ for (size_t i = 0; i < axisDrivers[axisOrExtruder].numDrivers; ++i)
+ {
+ const DriverId id = axisDrivers[axisOrExtruder].driverNumbers[i];
+ if (id.IsLocal())
+ {
+ localFunc(id.localDriver);
+ }
+ else
+ {
+ remoteFunc(id);
+ }
+ }
+ }
+ else if (axisOrExtruder < MaxAxesPlusExtruders)
+ {
+ const DriverId id = extruderDrivers[LogicalDriveToExtruder(axisOrExtruder)];
+ if (id.IsLocal())
+ {
+ localFunc(id.localDriver);
+ }
+ else
+ {
+ remoteFunc(id);
+ }
+ }
+}
+
+#else
+
+// Function to identify and iterate through all drivers attached to an axis or extruder
+void Platform::IterateDrivers(size_t axisOrExtruder, stdext::inplace_function<void(uint8_t)> localFunc) noexcept
+{
+ if (axisOrExtruder < reprap.GetGCodes().GetTotalAxes())
+ {
+ for (size_t i = 0; i < axisDrivers[axisOrExtruder].numDrivers; ++i)
+ {
+ const DriverId id = axisDrivers[axisOrExtruder].driverNumbers[i];
+ localFunc(id.localDriver);
+ }
+ }
+ else if (axisOrExtruder < MaxAxesPlusExtruders)
+ {
+ const DriverId id = extruderDrivers[LogicalDriveToExtruder(axisOrExtruder)];
+ localFunc(id.localDriver);
+ }
+}
+
+#endif
+
+// This is called from the step ISR as well as other places, so keep it fast
+// If drive >= DRIVES then we are setting an individual motor direction
+void Platform::SetDirection(size_t axisOrExtruder, bool direction) noexcept
+{
+ const bool isSlowDriver = (GetDriversBitmap(axisOrExtruder) & GetSlowDriversBitmap()) != 0;
+ if (isSlowDriver)
+ {
+ while (StepTimer::GetTimerTicks() - DDA::lastStepLowTime < GetSlowDriverDirHoldClocks()) { }
+ }
+
+ if (axisOrExtruder < MaxAxesPlusExtruders)
+ {
+ IterateLocalDrivers(axisOrExtruder, [this, direction](uint8_t driver) { this->SetDriverDirection(driver, direction); });
+ }
+ else if (axisOrExtruder < MaxAxesPlusExtruders + NumDirectDrivers)
+ {
+ SetDriverDirection(axisOrExtruder - MaxAxesPlusExtruders, direction);
+ }
+
+ if (isSlowDriver)
+ {
+ DDA::lastDirChangeTime = StepTimer::GetTimerTicks();
+ }
+}
+
+// Enable a driver. Must not be called from an ISR, or with interrupts disabled.
+void Platform::EnableOneLocalDriver(size_t driver, float requiredCurrent) noexcept
+{
+#if HAS_SMART_DRIVERS && (HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR)
+ if (driver < numSmartDrivers && !driversPowered)
+ {
+ warnDriversNotPowered = true;
+ }
+ else
+ {
+#endif
+ UpdateMotorCurrent(driver, requiredCurrent);
+
+#if defined(DUET3) && HAS_SMART_DRIVERS
+ SmartDrivers::EnableDrive(driver, true); // all drivers driven directly by the main board are smart
+#elif HAS_SMART_DRIVERS
+ if (driver < numSmartDrivers)
+ {
+ SmartDrivers::EnableDrive(driver, true);
+ }
+# if !defined(DUET3MINI) // no enable pins on 5LC
+ else
+ {
+ digitalWrite(ENABLE_PINS[driver], enableValues[driver] > 0);
+ }
+# endif
+#else
+ digitalWrite(ENABLE_PINS[driver], enableValues[driver] > 0);
+#endif
+#if HAS_SMART_DRIVERS && (HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR)
+ }
+#endif
+}
+
+// Disable a driver
+void Platform::DisableOneLocalDriver(size_t driver) noexcept
+{
+ if (driver < GetNumActualDirectDrivers())
+ {
+#if defined(DUET3) && HAS_SMART_DRIVERS
+ SmartDrivers::EnableDrive(driver, false); // all drivers driven directly by the main board are smart
+#elif HAS_SMART_DRIVERS
+ if (driver < numSmartDrivers)
+ {
+ SmartDrivers::EnableDrive(driver, false);
+ }
+# if !defined(DUET3MINI) // Duet 5LC has no enable pins
+ else
+ {
+ digitalWrite(ENABLE_PINS[driver], enableValues[driver] <= 0);
+ }
+# endif
+#else
+ digitalWrite(ENABLE_PINS[driver], enableValues[driver] <= 0);
+#endif
+ }
+}
+
+// Enable the local drivers for a drive. Must not be called from an ISR, or with interrupts disabled.
+void Platform::EnableDrivers(size_t axisOrExtruder) noexcept
+{
+ if (driverState[axisOrExtruder] != DriverStatus::enabled)
+ {
+ driverState[axisOrExtruder] = DriverStatus::enabled;
+ const float requiredCurrent = motorCurrents[axisOrExtruder] * motorCurrentFraction[axisOrExtruder];
+#if SUPPORT_CAN_EXPANSION
+ CanDriversList canDriversToEnable;
+ IterateDrivers(axisOrExtruder,
+ [this, requiredCurrent](uint8_t driver) { EnableOneLocalDriver(driver, requiredCurrent); },
+ [&canDriversToEnable](DriverId driver) { canDriversToEnable.AddEntry(driver); }
+ );
+ CanInterface::EnableRemoteDrivers(canDriversToEnable);
+#else
+ IterateDrivers(axisOrExtruder,
+ [this, requiredCurrent](uint8_t driver) { EnableOneLocalDriver(driver, requiredCurrent); }
+ );
+#endif
+ }
+}
+
+// Disable the drivers for a drive
+void Platform::DisableDrivers(size_t axisOrExtruder) noexcept
+{
+#if SUPPORT_CAN_EXPANSION
+ CanDriversList canDriversToDisable;
+
+ IterateDrivers(axisOrExtruder,
+ [this](uint8_t driver) { DisableOneLocalDriver(driver); },
+ [&canDriversToDisable](DriverId driver) { canDriversToDisable.AddEntry(driver); }
+ );
+ CanInterface::DisableRemoteDrivers(canDriversToDisable);
+#else
+ IterateDrivers(axisOrExtruder,
+ [this](uint8_t driver) { DisableOneLocalDriver(driver); }
+ );
+#endif
+ driverState[axisOrExtruder] = DriverStatus::disabled;
+}
+
+// Disable all drives in an emergency. Called from emergency stop and the tick ISR.
+// This is only called in an emergency, so we don't update the driver status
+void Platform::EmergencyDisableDrivers() noexcept
+{
+ for (size_t drive = 0; drive < GetNumActualDirectDrivers(); drive++)
+ {
+ if (!inInterrupt()) // on the Duet 06/085 we need interrupts running to send the I2C commands to set motor currents
+ {
+ UpdateMotorCurrent(drive, 0.0);
+ }
+ DisableOneLocalDriver(drive);
+ }
+}
+
+void Platform::DisableAllDrivers() noexcept
+{
+ for (size_t axisOrExtruder = 0; axisOrExtruder < MaxAxesPlusExtruders; axisOrExtruder++)
+ {
+ DisableDrivers(axisOrExtruder);
+ }
+}
+
+// Set drives to idle hold if they are enabled. If a drive is disabled, leave it alone.
+// Must not be called from an ISR, or with interrupts disabled.
+void Platform::SetDriversIdle() noexcept
+{
+ if (idleCurrentFactor == 0)
+ {
+ DisableAllDrivers();
+ reprap.GetGCodes().SetAllAxesNotHomed();
+ }
+ else
+ {
+#if SUPPORT_CAN_EXPANSION
+ CanDriversList canDriversToSetIdle;
+#endif
+ for (size_t axisOrExtruder = 0; axisOrExtruder < MaxAxesPlusExtruders; ++axisOrExtruder)
+ {
+ if (driverState[axisOrExtruder] == DriverStatus::enabled)
+ {
+ driverState[axisOrExtruder] = DriverStatus::idle;
+ const float current = motorCurrents[axisOrExtruder] * idleCurrentFactor;
+ IterateDrivers(axisOrExtruder,
+ [this, current](uint8_t driver) { UpdateMotorCurrent(driver, current); }
+#if SUPPORT_CAN_EXPANSION
+ , [&canDriversToSetIdle](DriverId driver) { canDriversToSetIdle.AddEntry(driver); }
+#endif
+ );
+ }
+ }
+#if SUPPORT_CAN_EXPANSION
+ CanInterface::SetRemoteDriversIdle(canDriversToSetIdle, idleCurrentFactor);
+#endif
+ }
+}
+
+// Set the current for all drivers on an axis or extruder. Current is in mA.
+GCodeResult Platform::SetMotorCurrent(size_t axisOrExtruder, float currentOrPercent, int code, const StringRef& reply) noexcept
+{
+ switch (code)
+ {
+ case 906:
+ motorCurrents[axisOrExtruder] = currentOrPercent;
+ break;
+
+ case 913:
+ motorCurrentFraction[axisOrExtruder] = constrain<float>(0.01 * currentOrPercent, 0.0, 1.0);
+ break;
+
+#if HAS_SMART_DRIVERS
+ case 917:
+ standstillCurrentPercent[axisOrExtruder] = constrain<float>(currentOrPercent, 0.0, 100.0);
+ break;
+#endif
+
+ default:
+ return GCodeResult::error;
+ }
+
+#if SUPPORT_CAN_EXPANSION
+ CanDriversData<float> canDriversToUpdate;
+
+ IterateDrivers(axisOrExtruder,
+ [this, axisOrExtruder, code](uint8_t driver)
+ {
+ if (code == 917)
+ {
+# if HAS_SMART_DRIVERS
+ SmartDrivers::SetStandstillCurrentPercent(driver, standstillCurrentPercent[axisOrExtruder]);
+# endif
+ }
+ else
+ {
+ UpdateMotorCurrent(driver, motorCurrents[axisOrExtruder] * motorCurrentFraction[axisOrExtruder]);
+ }
+ },
+ [this, axisOrExtruder, code, &canDriversToUpdate](DriverId driver)
+ {
+ if (code == 917)
+ {
+ canDriversToUpdate.AddEntry(driver, standstillCurrentPercent[axisOrExtruder]);
+ }
+ else
+ {
+ canDriversToUpdate.AddEntry(driver, motorCurrents[axisOrExtruder] * motorCurrentFraction[axisOrExtruder]);
+ }
+ }
+ );
+ if (code == 917)
+ {
+ return CanInterface::SetRemoteStandstillCurrentPercent(canDriversToUpdate, reply);
+ }
+ else
+ {
+ return CanInterface::SetRemoteDriverCurrents(canDriversToUpdate, reply);
+ }
+#else
+ IterateDrivers(axisOrExtruder,
+ [this, axisOrExtruder, code](uint8_t driver)
+ {
+ if (code == 917)
+ {
+# if HAS_SMART_DRIVERS
+ SmartDrivers::SetStandstillCurrentPercent(driver, standstillCurrentPercent[axisOrExtruder]);
+# endif
+ }
+ else
+ {
+ UpdateMotorCurrent(driver, motorCurrents[axisOrExtruder] * motorCurrentFraction[axisOrExtruder]);
+ }
+ }
+ );
+ return GCodeResult::ok;
+#endif
+}
+
+// This must not be called from an ISR, or with interrupts disabled.
+void Platform::UpdateMotorCurrent(size_t driver, float current) noexcept
+{
+ if (driver < GetNumActualDirectDrivers())
+ {
+#if HAS_SMART_DRIVERS
+ if (driver < numSmartDrivers)
+ {
+ SmartDrivers::SetCurrent(driver, current);
+ }
+#elif defined (DUET_06_085)
+ const uint16_t pot = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDigipotVoltage/2)/maxStepperDigipotVoltage);
+ if (driver < 4)
+ {
+ mcpDuet.setNonVolatileWiper(potWipes[driver], pot);
+ mcpDuet.setVolatileWiper(potWipes[driver], pot);
+ }
+ else
+ {
+ if (board == BoardType::Duet_085)
+ {
+ // Extruder 0 is on DAC channel 0
+ if (driver == 4)
+ {
+ const float dacVoltage = max<float>(current * 0.008 * senseResistor + stepperDacVoltageOffset, 0.0); // the voltage we want from the DAC relative to its minimum
+ const float dac = dacVoltage/stepperDacVoltageRange;
+ AnalogOut(DAC0, dac);
+ }
+ else
+ {
+ mcpExpansion.setNonVolatileWiper(potWipes[driver-1], pot);
+ mcpExpansion.setVolatileWiper(potWipes[driver-1], pot);
+ }
+ }
+ else if (driver < 8) // on a Duet 0.6 we have a maximum of 8 drives
+ {
+ mcpExpansion.setNonVolatileWiper(potWipes[driver], pot);
+ mcpExpansion.setVolatileWiper(potWipes[driver], pot);
+ }
+ }
+#elif defined(__ALLIGATOR__)
+ // Alligator SPI DAC current
+ if (driver < 4) // Onboard DAC
+ {
+ dacAlligator.setChannel(3-driver, current * 0.102);
+ }
+ else // Piggy module DAC
+ {
+ dacPiggy.setChannel(7-driver, current * 0.102);
+ }
+#elif defined(__LPC17xx__)
+ if (hasDriverCurrentControl)
+ {
+ //Has digipots to set current control for drivers
+ //Current is in mA
+ const uint16_t pot = (unsigned short) (current * digipotFactor / 1000);
+ if (driver < 4)
+ {
+ mcp4451.setMCP4461Address(0x2C); //A0 and A1 Grounded. (001011 00)
+ mcp4451.setVolatileWiper(POT_WIPES[driver], pot);
+ }
+ else
+ mcp4451.setMCP4461Address(0x2D); //A0 Vcc, A1 Grounded. (001011 01)
+ mcp4451.setVolatileWiper(POT_WIPES[driver-4], pot);
+ }
+ }
+#else
+ // otherwise we can't set the motor current
+#endif
+ }
+}
+
+// Get the configured motor current for an axis or extruder
+float Platform::GetMotorCurrent(size_t drive, int code) const noexcept
+{
+ switch (code)
+ {
+ case 906:
+ return motorCurrents[drive];
+
+ case 913:
+ return motorCurrentFraction[drive] * 100.0;
+
+#if HAS_SMART_DRIVERS
+ case 917:
+ return standstillCurrentPercent[drive];
+#endif
+ default:
+ return 0.0;
+ }
+}
+
+// Set the motor idle current factor
+void Platform::SetIdleCurrentFactor(float f) noexcept
+{
+ idleCurrentFactor = constrain<float>(f, 0.0, 1.0);
+ reprap.MoveUpdated();
+
+#if SUPPORT_CAN_EXPANSION
+ CanDriversData<float> canDriversToUpdate;
+#endif
+ for (size_t axisOrExtruder = 0; axisOrExtruder < MaxAxesPlusExtruders; ++axisOrExtruder)
+ {
+ if (driverState[axisOrExtruder] == DriverStatus::idle)
+ {
+ const float requiredCurrent = motorCurrents[axisOrExtruder] * idleCurrentFactor;
+ IterateDrivers(axisOrExtruder,
+ [this, requiredCurrent](uint8_t driver){ UpdateMotorCurrent(driver, requiredCurrent); }
+#if SUPPORT_CAN_EXPANSION
+ , [this, requiredCurrent, &canDriversToUpdate](DriverId driver) { canDriversToUpdate.AddEntry(driver, (uint16_t)requiredCurrent); }
+#endif
+ );
+ }
+ }
+#if SUPPORT_CAN_EXPANSION
+ String<1> dummy;
+ (void)CanInterface::SetRemoteDriverCurrents(canDriversToUpdate, dummy.GetRef());
+#endif
+}
+
+void Platform::SetDriveStepsPerUnit(size_t axisOrExtruder, float value, uint32_t requestedMicrostepping) noexcept
+{
+ if (requestedMicrostepping != 0)
+ {
+ const uint32_t currentMicrostepping = microstepping[axisOrExtruder] & 0x7FFF;
+ if (currentMicrostepping != requestedMicrostepping)
+ {
+ value = value * (float)currentMicrostepping / (float)requestedMicrostepping;
+ }
+ }
+ driveStepsPerUnit[axisOrExtruder] = max<float>(value, 1.0); // don't allow zero or negative
+ reprap.MoveUpdated();
+}
+
+// Set the microstepping for a driver, returning true if successful
+bool Platform::SetDriverMicrostepping(size_t driver, unsigned int microsteps, int mode) noexcept
+{
+ if (driver < GetNumActualDirectDrivers())
+ {
+#if HAS_SMART_DRIVERS
+ if (driver < numSmartDrivers)
+ {
+ return SmartDrivers::SetMicrostepping(driver, microsteps, mode);
+ }
+ else
+ {
+ // Other drivers only support x16 microstepping.
+ // We ignore the interpolation on/off parameter so that e.g. M350 I1 E16:128 won't give an error if E1 supports interpolation but E0 doesn't.
+ return microsteps == 16;
+ }
+#elif defined(__ALLIGATOR__)
+ return Microstepping::Set(driver, microsteps); // no mode in Alligator board
+#elif defined(__LPC17xx__)
+ return Microstepping::Set(driver, microsteps);
+#else
+ // Assume only x16 microstepping supported
+ return microsteps == 16;
+#endif
+ }
+ return false;
+}
+
+// Set the microstepping for local drivers, returning true if successful. All drivers for the same axis must use the same microstepping.
+// Caller must deal with remote drivers.
+bool Platform::SetMicrostepping(size_t axisOrExtruder, int microsteps, bool interp, const StringRef& reply) noexcept
+{
+ //TODO check that it is a valid microstep setting
+ microstepping[axisOrExtruder] = (interp) ? microsteps | 0x8000 : microsteps;
+ reprap.MoveUpdated();
+ bool ok = true;
+ IterateLocalDrivers(axisOrExtruder,
+ [this, microsteps, interp, &ok, reply](uint8_t driver) noexcept
+ {
+ if (!SetDriverMicrostepping(driver, microsteps, interp))
+ {
+ reply.lcatf("Driver %u does not support x%u microstepping", driver, microsteps);
+ if (interp)
+ {
+ reply.cat(" with interpolation");
+ }
+ ok = false;
+ }
+ }
+ );
+ return ok;
+}
+
+// Get the microstepping for an axis or extruder
+unsigned int Platform::GetMicrostepping(size_t axisOrExtruder, bool& interpolation) const noexcept
+{
+ interpolation = (microstepping[axisOrExtruder] & 0x8000) != 0;
+ return microstepping[axisOrExtruder] & 0x7FFF;
+}
+
+void Platform::SetEnableValue(size_t driver, int8_t eVal) noexcept
+{
+ if (driver < GetNumActualDirectDrivers())
+ {
+ enableValues[driver] = eVal;
+ DisableOneLocalDriver(driver); // disable the drive, because the enable polarity may have been wrong before
+#if HAS_SMART_DRIVERS
+ if (eVal == -1)
+ {
+ // User has asked to disable status monitoring for this driver, so clear its error bits
+ const DriversBitmap mask = ~DriversBitmap::MakeFromBits(driver);
+ temperatureShutdownDrivers &= mask;
+ temperatureWarningDrivers &= mask;
+ shortToGroundDrivers &= mask;
+ openLoadADrivers &= mask;
+ openLoadBDrivers &= mask;
+ }
+#endif
+ }
+}
+
+void Platform::SetAxisDriversConfig(size_t axis, size_t numValues, const DriverId driverNumbers[]) noexcept
+{
+ AxisDriversConfig& cfg = axisDrivers[axis];
+ cfg.numDrivers = numValues;
+ uint32_t bitmap = 0;
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ const DriverId id = driverNumbers[i];
+ cfg.driverNumbers[i] = id;
+ if (id.IsLocal())
+ {
+ bitmap |= StepPins::CalcDriverBitmap(id.localDriver);
+#if HAS_SMART_DRIVERS
+ SmartDrivers::SetAxisNumber(id.localDriver, axis);
+#endif
+ }
+ }
+ driveDriverBits[axis] = bitmap;
+}
+
+// Set the characteristics of an axis
+void Platform::SetAxisType(size_t axis, AxisWrapType wrapType, bool isNistRotational) noexcept
+{
+ if (isNistRotational)
+ {
+ rotationalAxes.SetBit(axis);
+ }
+ else
+ {
+ linearAxes.SetBit(axis);
+ }
+
+ switch (wrapType)
+ {
+#if 0 // shortcut axes not implemented yet
+ case AxisWrapType::wrapWithShortcut:
+ shortcutAxes.SetBit(axis);
+ // no break
+#endif
+ case AxisWrapType::wrapAt360:
+ continuousAxes.SetBit(axis);
+ break;
+
+ default:
+ break;
+ }
+}
+
+// Map an extruder to a driver
+void Platform::SetExtruderDriver(size_t extruder, DriverId driver) noexcept
+{
+ extruderDrivers[extruder] = driver;
+ if (driver.IsLocal())
+ {
+#if HAS_SMART_DRIVERS
+ SmartDrivers::SetAxisNumber(driver.localDriver, ExtruderToLogicalDrive(extruder));
+#endif
+ driveDriverBits[ExtruderToLogicalDrive(extruder)] = StepPins::CalcDriverBitmap(driver.localDriver);
+ }
+ else
+ {
+ driveDriverBits[ExtruderToLogicalDrive(extruder)] = 0;
+ }
+}
+
+void Platform::SetDriverStepTiming(size_t driver, const float microseconds[4]) noexcept
+{
+ const uint32_t bitmap = StepPins::CalcDriverBitmap(driver);
+ slowDriversBitmap &= ~bitmap; // start by assuming this drive does not need extended timing
+ if (slowDriversBitmap == 0)
+ {
+ for (uint32_t& entry : slowDriverStepTimingClocks)
+ {
+ entry = 0; // reset all to zero if we have no known slow drivers
+ }
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(slowDriverStepTimingClocks); ++i)
+ {
+ if (microseconds[i] > MinStepPulseTiming)
+ {
+ slowDriversBitmap |= StepPins::CalcDriverBitmap(driver); // this drive does need extended timing
+ const uint32_t clocks = (uint32_t)(((float)StepTimer::StepClockRate * microseconds[i] * 0.000001) + 0.99); // convert microseconds to step clocks, rounding up
+ if (clocks > slowDriverStepTimingClocks[i])
+ {
+ slowDriverStepTimingClocks[i] = clocks;
+ }
+ }
+ }
+}
+
+// Get the driver step timing, returning true if we are using slower timing than standard
+bool Platform::GetDriverStepTiming(size_t driver, float microseconds[4]) const noexcept
+{
+ const bool isSlowDriver = ((slowDriversBitmap & StepPins::CalcDriverBitmap(driver)) != 0);
+ for (size_t i = 0; i < 4; ++i)
+ {
+ microseconds[i] = (isSlowDriver)
+ ? (float)slowDriverStepTimingClocks[i] * 1000000.0/(float)StepTimer::StepClockRate
+ : 0.0;
+ }
+ return isSlowDriver;
+}
+
+//-----------------------------------------------------------------------------------------------------
+
+// USB port functions
+
+void Platform::AppendUsbReply(OutputBuffer *buffer) noexcept
+{
+ if ( !SERIAL_MAIN_DEVICE.IsConnected()
+#if SUPPORT_SCANNER
+ || (reprap.GetScanner().IsRegistered() && !reprap.GetScanner().DoingGCodes())
+#endif
+ )
+ {
+ // If the serial USB line is not open, discard the message right away
+ OutputBuffer::ReleaseAll(buffer);
+ }
+ else
+ {
+ // Else append incoming data to the stack
+ MutexLocker lock(usbMutex);
+ usbOutput.Push(buffer);
+ }
+}
+
+// Aux port functions
+
+bool Platform::IsAuxEnabled(size_t auxNumber) const noexcept
+{
+#if HAS_AUX_DEVICES
+ return auxNumber < ARRAY_SIZE(auxDevices) && auxDevices[auxNumber].IsEnabled();
+#else
+ return false;
+#endif
+}
+
+void Platform::EnableAux(size_t auxNumber) noexcept
+{
+#if HAS_AUX_DEVICES
+ if (auxNumber < ARRAY_SIZE(auxDevices) && !auxDevices[auxNumber].IsEnabled())
+ {
+ auxDevices[auxNumber].Enable(baudRates[auxNumber + 1]);
+ }
+#endif
+}
+
+bool Platform::IsAuxRaw(size_t auxNumber) const noexcept
+{
+#if HAS_AUX_DEVICES
+ return auxNumber >= ARRAY_SIZE(auxDevices) || auxDevices[auxNumber].IsRaw();
+#else
+ return true;
+#endif
+}
+
+void Platform::SetAuxRaw(size_t auxNumber, bool raw) noexcept
+{
+#if HAS_AUX_DEVICES
+ if (auxNumber < ARRAY_SIZE(auxDevices))
+ {
+ auxDevices[auxNumber].SetRaw(raw);
+ }
+#endif
+}
+
+#if HAS_AUX_DEVICES
+void Platform::InitPanelDueUpdater() noexcept
+{
+ if (panelDueUpdater == nullptr)
+ {
+ panelDueUpdater = new PanelDueUpdater();
+ }
+}
+#endif
+
+void Platform::AppendAuxReply(size_t auxNumber, const char *msg, bool rawMessage) noexcept
+{
+#if HAS_AUX_DEVICES
+ if (auxNumber < ARRAY_SIZE(auxDevices))
+ {
+ // Don't send anything to PanelDue while we are flashing it
+ if (auxNumber == 0 && reprap.GetGCodes().IsFlashingPanelDue())
+ {
+ return;
+ }
+ auxDevices[auxNumber].AppendAuxReply(msg, rawMessage);
+ }
+#endif
+}
+
+void Platform::AppendAuxReply(size_t auxNumber, OutputBuffer *reply, bool rawMessage) noexcept
+{
+#if HAS_AUX_DEVICES
+ if (auxNumber < ARRAY_SIZE(auxDevices))
+ {
+ // Don't send anything to PanelDue while we are flashing it
+ if (auxNumber == 0 && reprap.GetGCodes().IsFlashingPanelDue())
+ {
+ OutputBuffer::ReleaseAll(reply);
+ return;
+ }
+ auxDevices[auxNumber].AppendAuxReply(reply, rawMessage);
+ }
+ else
+#endif
+ {
+ OutputBuffer::ReleaseAll(reply);
+ }
+}
+
+// Send the specified message to the specified destinations. The Error and Warning flags have already been handled.
+void Platform::RawMessage(MessageType type, const char *message) noexcept
+{
+#if HAS_MASS_STORAGE
+ // Deal with logging
+ if (logger != nullptr)
+ {
+ logger->LogMessage(realTime, message, type);
+ }
+#endif
+
+ // Send the message to the destinations
+ if ((type & ImmediateAuxMessage) != 0)
+ {
+ SendPanelDueMessage(0, message);
+ }
+ else if ((type & AuxMessage) != 0)
+ {
+ AppendAuxReply(0, message, message[0] == '{' || (type & RawMessageFlag) != 0);
+ }
+
+ if ((type & HttpMessage) != 0)
+ {
+ reprap.GetNetwork().HandleHttpGCodeReply(message);
+ }
+
+ if ((type & TelnetMessage) != 0)
+ {
+ reprap.GetNetwork().HandleTelnetGCodeReply(message);
+ }
+
+ if ((type & Aux2Message) != 0)
+ {
+ AppendAuxReply(1, message, message[0] == '{' || (type & RawMessageFlag) != 0);
+ }
+
+ if ((type & BlockingUsbMessage) != 0)
+ {
+ // Debug output sends messages in blocking mode. We now give up sending if we are close to software watchdog timeout.
+ MutexLocker lock(usbMutex);
+ const char *p = message;
+ size_t len = strlen(p);
+ while (SERIAL_MAIN_DEVICE.IsConnected() && len != 0 && !reprap.SpinTimeoutImminent())
+ {
+ const size_t written = SERIAL_MAIN_DEVICE.write(p, len);
+ len -= written;
+ p += written;
+ }
+ // We no longer flush afterwards
+ }
+ else if ((type & UsbMessage) != 0)
+ {
+ // Message that is to be sent via the USB line (non-blocking)
+ MutexLocker lock(usbMutex);
+#if SUPPORT_SCANNER
+ if (!reprap.GetScanner().IsRegistered() || reprap.GetScanner().DoingGCodes())
+#endif
+ {
+ // Ensure we have a valid buffer to write to that isn't referenced for other destinations
+ OutputBuffer *usbOutputBuffer = usbOutput.GetLastItem();
+ if (usbOutputBuffer == nullptr || usbOutputBuffer->IsReferenced())
+ {
+ if (OutputBuffer::Allocate(usbOutputBuffer))
+ {
+ if (usbOutput.Push(usbOutputBuffer))
+ {
+ usbOutputBuffer->cat(message);
+ }
+ // else the message buffer has been released, so discard the message
+ }
+ }
+ else
+ {
+ usbOutputBuffer->cat(message); // append the message
+ }
+ }
+ }
+}
+
+// Note: this overload of Platform::Message does not process the special action flags in the MessageType.
+// Also it treats calls to send a blocking USB message the same as ordinary USB messages,
+// and calls to send an immediate LCD message the same as ordinary LCD messages
+void Platform::Message(const MessageType type, OutputBuffer *buffer) noexcept
+{
+#if HAS_MASS_STORAGE
+ // First deal with logging because it doesn't hang on to the buffer
+ if (logger != nullptr)
+ {
+ logger->LogMessage(realTime, buffer, type);
+ }
+#endif
+
+ // Now send the message to all the destinations
+ size_t numDestinations = 0;
+ if ((type & (AuxMessage | ImmediateAuxMessage)) != 0)
+ {
+ ++numDestinations;
+ }
+ if ((type & (UsbMessage | BlockingUsbMessage)) != 0)
+ {
+ ++numDestinations;
+ }
+ if ((type & HttpMessage) != 0)
+ {
+ ++numDestinations;
+ }
+ if ((type & TelnetMessage) != 0)
+ {
+ ++numDestinations;
+ }
+#if HAS_LINUX_INTERFACE
+ if (reprap.UsingLinuxInterface() && ((type & GenericMessage) == GenericMessage || (type & BinaryCodeReplyFlag) != 0))
+ {
+ ++numDestinations;
+ }
+#endif
+#ifdef SERIAL_AUX2_DEVICE
+ if ((type & Aux2Message) != 0)
+ {
+ ++numDestinations;
+ }
+#endif
+
+ if (numDestinations == 0)
+ {
+ OutputBuffer::ReleaseAll(buffer);
+ }
+ else
+ {
+ buffer->IncreaseReferences(numDestinations - 1);
+
+ if ((type & (AuxMessage | ImmediateAuxMessage)) != 0)
+ {
+ AppendAuxReply(0, buffer, ((*buffer)[0] == '{') || (type & RawMessageFlag) != 0);
+ }
+
+ if ((type & HttpMessage) != 0)
+ {
+ reprap.GetNetwork().HandleHttpGCodeReply(buffer);
+ }
+
+ if ((type & TelnetMessage) != 0)
+ {
+ reprap.GetNetwork().HandleTelnetGCodeReply(buffer);
+ }
+
+ if ((type & Aux2Message) != 0)
+ {
+ AppendAuxReply(1, buffer, ((*buffer)[0] == '{') || (type & RawMessageFlag) != 0);
+ }
+
+ if ((type & (UsbMessage | BlockingUsbMessage)) != 0)
+ {
+ AppendUsbReply(buffer);
+ }
+
+#if HAS_LINUX_INTERFACE
+ if (reprap.UsingLinuxInterface() && ((type & GenericMessage) == GenericMessage || (type & BinaryCodeReplyFlag) != 0))
+ {
+ reprap.GetLinuxInterface().HandleGCodeReply(type, buffer);
+ }
+#endif
+ }
+}
+
+void Platform::MessageF(MessageType type, const char *fmt, va_list vargs) noexcept
+{
+ String<FormatStringLength> formatString;
+#if HAS_LINUX_INTERFACE
+ if (reprap.UsingLinuxInterface() && ((type & GenericMessage) == GenericMessage || (type & BinaryCodeReplyFlag) != 0))
+ {
+ formatString.vprintf(fmt, vargs);
+ reprap.GetLinuxInterface().HandleGCodeReply(type, formatString.c_str());
+ if ((type & BinaryCodeReplyFlag) != 0)
+ {
+ return;
+ }
+ }
+#endif
+
+ if ((type & ErrorMessageFlag) != 0)
+ {
+ formatString.copy("Error: ");
+ formatString.vcatf(fmt, vargs);
+ }
+ else if ((type & WarningMessageFlag) != 0)
+ {
+ formatString.copy("Warning: ");
+ formatString.vcatf(fmt, vargs);
+ }
+ else
+ {
+ formatString.vprintf(fmt, vargs);
+ }
+
+ RawMessage((MessageType)(type & ~(ErrorMessageFlag | WarningMessageFlag)), formatString.c_str());
+}
+
+void Platform::MessageF(MessageType type, const char *fmt, ...) noexcept
+{
+ va_list vargs;
+ va_start(vargs, fmt);
+ MessageF(type, fmt, vargs);
+ va_end(vargs);
+}
+
+void Platform::Message(MessageType type, const char *message) noexcept
+{
+#if HAS_LINUX_INTERFACE
+ if (reprap.UsingLinuxInterface() &&
+ ((type & BinaryCodeReplyFlag) != 0 || (type & GenericMessage) == GenericMessage || (type & LogOff) != LogOff))
+ {
+ reprap.GetLinuxInterface().HandleGCodeReply(type, message);
+ if ((type & BinaryCodeReplyFlag) != 0)
+ {
+ return;
+ }
+ }
+#endif
+
+ if ((type & (ErrorMessageFlag | WarningMessageFlag)) == 0)
+ {
+ RawMessage(type, message);
+ }
+ else
+ {
+#ifdef DUET3_ATE
+ // FormatStringLength is too short for some ATE replies
+ OutputBuffer *buf;
+ if (OutputBuffer::Allocate(buf))
+ {
+ buf->copy(((type & ErrorMessageFlag) != 0) ? "Error: " : "Warning: ");
+ buf->cat(message);
+ Message(type, buf);
+ }
+ else
+#endif
+ {
+ String<FormatStringLength> formatString;
+ formatString.copy(((type & ErrorMessageFlag) != 0) ? "Error: " : "Warning: ");
+ formatString.cat(message);
+ RawMessage((MessageType)(type & ~(ErrorMessageFlag | WarningMessageFlag)), formatString.c_str());
+ }
+ }
+}
+
+// Send a debug message to USB using minimal stack
+void Platform::DebugMessage(const char *fmt, va_list vargs) noexcept
+{
+ MutexLocker lock(usbMutex);
+ vuprintf([](char c) -> bool
+ {
+ if (c != 0)
+ {
+ while (SERIAL_MAIN_DEVICE.IsConnected() && !reprap.SpinTimeoutImminent())
+ {
+ if (SERIAL_MAIN_DEVICE.canWrite() != 0)
+ {
+ SERIAL_MAIN_DEVICE.write(c);
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ fmt,
+ vargs
+ );
+}
+
+// Send a message box, which may require an acknowledgement
+// sParam = 0 Just display the message box, optional timeout
+// sParam = 1 As for 0 but display a Close button as well
+// sParam = 2 Display the message box with an OK button, wait for acknowledgement (waiting is set up by the caller)
+// sParam = 3 As for 2 but also display a Cancel button
+void Platform::SendAlert(MessageType mt, const char *message, const char *title, int sParam, float tParam, AxesBitmap controls) noexcept
+{
+ if ((mt & (HttpMessage | AuxMessage | LcdMessage | BinaryCodeReplyFlag)) != 0)
+ {
+ reprap.SetAlert(message, title, sParam, tParam, controls); // make the RepRap class cache this message until it's picked up by the HTTP clients and/or PanelDue
+ }
+
+ MessageF(MessageType::LogInfo, "M291: - %s - %s", (strlen(title) > 0 ? title : "[no title]"), message);
+
+ mt = (MessageType)(mt & (UsbMessage | TelnetMessage));
+ if (mt != 0)
+ {
+ if (strlen(title) > 0)
+ {
+ MessageF(mt, "- %s -\n", title);
+ }
+ MessageF(mt, "%s\n", message);
+ if (sParam == 2)
+ {
+ Message(mt, "Send M292 to continue\n");
+ }
+ else if (sParam == 3)
+ {
+ Message(mt, "Send M292 to continue or M292 P1 to cancel\n");
+ }
+ }
+}
+
+#if HAS_MASS_STORAGE
+
+// Configure logging according to the M929 command received, returning true if error
+GCodeResult Platform::ConfigureLogging(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException)
+{
+ if (gb.Seen('S'))
+ {
+ StopLogging();
+ const auto logLevel = (LogLevel) gb.GetLimitedUIValue('S', LogLevel::off, LogLevel::NumValues);
+ if (logLevel > LogLevel::off)
+ {
+ // Start logging
+ if (logger == nullptr)
+ {
+ logger = new Logger(logLevel);
+ }
+ else
+ {
+ logger->SetLogLevel(logLevel);
+ }
+
+ char buf[MaxFilenameLength + 1];
+ StringRef filename(buf, ARRAY_SIZE(buf));
+ if (gb.Seen('P'))
+ {
+ gb.GetQuotedString(filename);
+ }
+ else
+ {
+ filename.copy(DEFAULT_LOG_FILE);
+ }
+ logger->Start(realTime, filename);
+ }
+ }
+ else
+ {
+ if (logger == nullptr || !logger->IsActive())
+ {
+ reply.copy("Event logging is disabled");
+ }
+ else
+ {
+ const auto logLevel = logger->GetLogLevel();
+ reply.printf("Event logging is enabled at log level %s", logLevel.ToString());
+ }
+ }
+ return GCodeResult::ok;
+}
+
+// Return the log file name, or nullptr if logging is not active
+const char *Platform::GetLogFileName() const noexcept
+{
+ return (logger == nullptr) ? nullptr : logger->GetFileName();
+}
+
+#endif
+
+const char *Platform::GetLogLevel() const noexcept
+{
+ static const LogLevel off = LogLevel::off; // need to have an instance otherwise it will fail .ToString() below
+#if HAS_MASS_STORAGE
+ return (logger == nullptr) ? off.ToString() : logger->GetLogLevel().ToString();
+#else
+ return off.ToString();
+#endif
+}
+
+// This is called from EmergencyStop. It closes the log file and stops logging.
+void Platform::StopLogging() noexcept
+{
+#if HAS_MASS_STORAGE
+ if (logger != nullptr)
+ {
+ logger->Stop(realTime);
+ }
+#endif
+}
+
+bool Platform::AtxPower() const noexcept
+{
+#ifdef __LPC17xx__
+ const bool val = IoPort::ReadPin(ATX_POWER_PIN);
+ return (ATX_POWER_INVERTED) ? !val : val;
+#else
+ return IoPort::ReadPin(ATX_POWER_PIN);
+#endif
+}
+
+void Platform::AtxPowerOn() noexcept
+{
+ deferredPowerDown = false;
+ IoPort::WriteDigital(ATX_POWER_PIN, true);
+}
+
+void Platform::AtxPowerOff(bool defer) noexcept
+{
+ deferredPowerDown = defer;
+ if (!defer)
+ {
+#if HAS_MASS_STORAGE
+ if (logger != nullptr)
+ {
+ logger->LogMessage(realTime, "Power off commanded", LogWarn);
+ logger->Flush(true);
+ // We don't call logger->Stop() here because we don't know whether turning off the power will work
+ }
+#endif
+#ifdef __LPC17xx__
+ IoPort::WriteDigital(ATX_POWER_PIN, ATX_POWER_INVERTED);
+#else
+ IoPort::WriteDigital(ATX_POWER_PIN, false);
+#endif
+ }
+}
+
+GCodeResult Platform::SetPressureAdvance(float advance, GCodeBuffer& gb, const StringRef& reply)
+{
+ GCodeResult rslt = GCodeResult::ok;
+
+#if SUPPORT_CAN_EXPANSION
+ CanDriversData<float> canDriversToUpdate;
+#endif
+
+ if (gb.Seen('D'))
+ {
+ uint32_t eDrive[MaxExtruders];
+ size_t eCount = MaxExtruders;
+ gb.GetUnsignedArray(eDrive, eCount, false);
+ for (size_t i = 0; i < eCount; i++)
+ {
+ const uint32_t extruder = eDrive[i];
+ if (extruder >= reprap.GetGCodes().GetNumExtruders())
+ {
+ reply.printf("Invalid extruder number '%" PRIu32 "'", extruder);
+ rslt = GCodeResult::error;
+ break;
+ }
+ pressureAdvance[extruder] = advance;
+#if SUPPORT_CAN_EXPANSION
+ if (extruderDrivers[extruder].IsRemote())
+ {
+ canDriversToUpdate.AddEntry(extruderDrivers[extruder], advance);
+ }
+#endif
+ }
+ }
+ else
+ {
+ const Tool * const ct = reprap.GetCurrentTool();
+ if (ct == nullptr)
+ {
+ reply.copy("No tool selected");
+ rslt = GCodeResult::error;
+ }
+ else
+ {
+#if SUPPORT_CAN_EXPANSION
+ ct->IterateExtruders([this, advance, &canDriversToUpdate](unsigned int extruder)
+ {
+ pressureAdvance[extruder] = advance;
+ if (extruderDrivers[extruder].IsRemote())
+ {
+ canDriversToUpdate.AddEntry(extruderDrivers[extruder], advance);
+ }
+ }
+ );
+#else
+ ct->IterateExtruders([this, advance](unsigned int extruder)
+ {
+ pressureAdvance[extruder] = advance;
+ }
+ );
+#endif
+ }
+ }
+
+#if SUPPORT_CAN_EXPANSION
+ return max(rslt, CanInterface::SetRemotePressureAdvance(canDriversToUpdate, reply));
+#else
+ return rslt;
+#endif
+}
+
+#if SUPPORT_NONLINEAR_EXTRUSION
+
+bool Platform::GetExtrusionCoefficients(size_t extruder, float& a, float& b, float& limit) const noexcept
+{
+ if (extruder < MaxExtruders)
+ {
+ a = nonlinearExtrusionA[extruder];
+ b = nonlinearExtrusionB[extruder];
+ limit = nonlinearExtrusionLimit[extruder];
+ return true;
+ }
+ return false;
+}
+
+void Platform::SetNonlinearExtrusion(size_t extruder, float a, float b, float limit) noexcept
+{
+ if (extruder < MaxExtruders && nonlinearExtrusionLimit[extruder] > 0.0)
+ {
+ nonlinearExtrusionLimit[extruder] = limit;
+ nonlinearExtrusionA[extruder] = a;
+ nonlinearExtrusionB[extruder] = b;
+ }
+}
+
+#endif
+
+void Platform::SetBaudRate(size_t chan, uint32_t br) noexcept
+{
+ if (chan < NumSerialChannels)
+ {
+ baudRates[chan] = br;
+ }
+}
+
+uint32_t Platform::GetBaudRate(size_t chan) const noexcept
+{
+ return (chan < NumSerialChannels) ? baudRates[chan] : 0;
+}
+
+void Platform::SetCommsProperties(size_t chan, uint32_t cp) noexcept
+{
+ if (chan < NumSerialChannels)
+ {
+ commsParams[chan] = cp;
+ }
+}
+
+uint32_t Platform::GetCommsProperties(size_t chan) const noexcept
+{
+ return (chan < NumSerialChannels) ? commsParams[chan] : 0;
+}
+
+// Re-initialise a serial channel.
+void Platform::ResetChannel(size_t chan) noexcept
+{
+ if (chan == 0)
+ {
+ SERIAL_MAIN_DEVICE.end();
+#if SAME5x
+ SERIAL_MAIN_DEVICE.Start();
+#elif defined(__LPC17xx__)
+ SERIAL_MAIN_DEVICE.begin(baudRates[0]);
+#else
+ SERIAL_MAIN_DEVICE.Start(UsbVBusPin);
+#endif
+ }
+#if HAS_AUX_DEVICES
+ else if (chan < NumSerialChannels)
+ {
+ auxDevices[chan - 1].Disable();
+ auxDevices[chan - 1].Enable(baudRates[chan]);
+ }
+#endif
+}
+
+// Set the board type. This must be called quite early, because for some builds it relies on pins not having been programmed for their intended use yet.
+void Platform::SetBoardType(BoardType bt) noexcept
+{
+ if (bt == BoardType::Auto)
+ {
+#if defined(DUET3MINI_V02)
+ // Test whether this is a WiFi or an Ethernet board. Currently we do this based on the processor type.
+ const uint16_t deviceId = DSU->DID.reg >> 16;
+ board = (deviceId == 0x6184) // if SAME54P20A
+ ? BoardType::Duet3Mini_Ethernet
+ : (deviceId == 0x6006) // SAMD51P20A rev D
+ ? BoardType::Duet3Mini_WiFi
+ : BoardType::Duet3Mini_Unknown;
+#elif defined(DUET3MINI_V04)
+ // Test whether this is a WiFi or an Ethernet board by testing for a pulldown resistor on Dir1
+ pinMode(DIRECTION_PINS[1], INPUT_PULLUP);
+ delayMicroseconds(20); // give the pullup resistor time to work
+ board = (digitalRead(DIRECTION_PINS[1])) // if SAME54P20A
+ ? BoardType::Duet3Mini_WiFi
+ : BoardType::Duet3Mini_Ethernet;
+#elif defined(DUET3)
+ // Driver 0 direction has a pulldown resistor on v0.6 and v1.0 boards, but won't on v1.01 boards
+ pinMode(DIRECTION_PINS[0], INPUT_PULLUP);
+ delayMicroseconds(20); // give the pullup resistor time to work
+ board = (digitalRead(DIRECTION_PINS[0])) ? BoardType::Duet3_v101 : BoardType::Duet3_v06_100;
+#elif defined(SAME70XPLD)
+ board = BoardType::SAME70XPLD_0;
+#elif defined(DUET_NG)
+ // Get ready to test whether the Ethernet module is present, so that we avoid additional delays
+ pinMode(EspResetPin, OUTPUT_LOW); // reset the WiFi module or the W5500. We assume that this forces the ESP8266 UART output pin to high impedance.
+ pinMode(W5500ModuleSensePin, INPUT_PULLUP); // set our UART receive pin to be an input pin and enable the pullup
+
+ // Set up the VSSA sense pin. Older Duet WiFis don't have it connected, so we enable the pulldown resistor to keep it inactive.
+ pinMode(VssaSensePin, INPUT_PULLUP);
+ delayMicroseconds(10);
+ const bool vssaHighVal = digitalRead(VssaSensePin);
+ pinMode(VssaSensePin, INPUT_PULLDOWN);
+ delayMicroseconds(10);
+ const bool vssaLowVal = digitalRead(VssaSensePin);
+ const bool vssaSenseWorking = vssaLowVal || !vssaHighVal;
+ if (vssaSenseWorking)
+ {
+ pinMode(VssaSensePin, INPUT);
+ }
+
+# if defined(USE_SBC)
+ board = (vssaSenseWorking) ? BoardType::Duet2SBC_102 : BoardType::Duet2SBC_10;
+# else
+ // Test whether the Ethernet module is present
+ if (digitalRead(W5500ModuleSensePin)) // the Ethernet module has this pin grounded
+ {
+ board = (vssaSenseWorking) ? BoardType::DuetWiFi_102 : BoardType::DuetWiFi_10;
+ }
+ else
+ {
+ board = (vssaSenseWorking) ? BoardType::DuetEthernet_102 : BoardType::DuetEthernet_10;
+ }
+# endif
+#elif defined(DUET_M)
+ board = BoardType::DuetM_10;
+#elif defined(DUET_06_085)
+ // Determine whether this is a Duet 0.6 or a Duet 0.8.5 board.
+ // If it is a 0.85 board then DAC0 (AKA digital pin 67) is connected to ground via a diode and a 2.15K resistor.
+ // So we enable the pullup (value 100K-150K) on pin 67 and read it, expecting a LOW on a 0.8.5 board and a HIGH on a 0.6 board.
+ // This may fail if anyone connects a load to the DAC0 pin on a Duet 0.6, hence we implement board selection in M115 as well.
+ pinMode(Dac0DigitalPin, INPUT_PULLUP);
+ delayMicroseconds(10);
+ board = (digitalRead(Dac0DigitalPin)) ? BoardType::Duet_06 : BoardType::Duet_085;
+ pinMode(Dac0DigitalPin, INPUT); // turn pullup off
+#elif defined(__RADDS__)
+ board = BoardType::RADDS_15;
+#elif defined(__ALLIGATOR__)
+ board = BoardType::Alligator_2;
+#elif defined(PCCB_10)
+ board = BoardType::PCCB_v10;
+#elif defined(PCCB_08) || defined(PCCB_08_X5)
+ board = BoardType::PCCB_v08;
+#elif defined(__LPC17xx__)
+ board = BoardType::Lpc;
+#else
+# error Undefined board type
+#endif
+ }
+ else
+ {
+ board = bt;
+ }
+}
+
+// Get a string describing the electronics
+const char* Platform::GetElectronicsString() const noexcept
+{
+ switch (board)
+ {
+#if defined(DUET3MINI)
+ case BoardType::Duet3Mini_Unknown: return "Duet 3 " BOARD_SHORT_NAME " unknown variant";
+ case BoardType::Duet3Mini_WiFi: return "Duet 3 " BOARD_SHORT_NAME " WiFi";
+ case BoardType::Duet3Mini_Ethernet: return "Duet 3 " BOARD_SHORT_NAME " Ethernet";
+#elif defined(DUET3)
+ case BoardType::Duet3_v06_100: return "Duet 3 " BOARD_SHORT_NAME " v0.6 or 1.0";
+ case BoardType::Duet3_v101: return "Duet 3 " BOARD_SHORT_NAME " v1.01 or later";
+#elif defined(SAME70XPLD)
+ case BoardType::SAME70XPLD_0: return "SAME70-XPLD";
+#elif defined(DUET_NG)
+ case BoardType::DuetWiFi_10: return "Duet WiFi 1.0 or 1.01";
+ case BoardType::DuetWiFi_102: return "Duet WiFi 1.02 or later";
+ case BoardType::DuetEthernet_10: return "Duet Ethernet 1.0 or 1.01";
+ case BoardType::DuetEthernet_102: return "Duet Ethernet 1.02 or later";
+ case BoardType::Duet2SBC_10: return "Duet 2 1.0 or 1.01 + SBC";
+ case BoardType::Duet2SBC_102: return "Duet 2 1.02 or later + SBC";
+#elif defined(DUET_M)
+ case BoardType::DuetM_10: return "Duet Maestro 1.0";
+#elif defined(DUET_06_085)
+ case BoardType::Duet_06: return "Duet 0.6";
+ case BoardType::Duet_07: return "Duet 0.7";
+ case BoardType::Duet_085: return "Duet 0.85";
+#elif defined(__RADDS__)
+ case BoardType::RADDS_15: return "RADDS 1.5";
+#elif defined(__ALLIGATOR__)
+ case BoardType::Alligator_2: return "Alligator r2";
+#elif defined(PCCB_10)
+ case BoardType::PCCB_v10: return "PC001373";
+#elif defined(PCCB_08) || defined(PCCB_08_X5)
+ case BoardType::PCCB_v08: return "PCCB 0.8";
+#elif defined(__LPC17xx__)
+ case BoardType::Lpc: return LPC_ELECTRONICS_STRING;
+#else
+# error Undefined board type
+#endif
+ default: return "Unidentified";
+ }
+}
+
+// Get the board string
+const char* Platform::GetBoardString() const noexcept
+{
+ switch (board)
+ {
+#if defined(DUET3MINI)
+ case BoardType::Duet3Mini_Unknown: return "duet5lcunknown";
+ case BoardType::Duet3Mini_WiFi: return "duet5lcwifi";
+ case BoardType::Duet3Mini_Ethernet: return "duet5lcethernet";
+#elif defined(DUET3)
+ case BoardType::Duet3_v06_100: return "duet3mb6hc100";
+ case BoardType::Duet3_v101: return "duet3mb6hc101";
+#elif defined(SAME70XPLD)
+ case BoardType::SAME70XPLD_0: return "same70xpld";
+#elif defined(DUET_NG)
+ case BoardType::DuetWiFi_10: return "duetwifi10";
+ case BoardType::DuetWiFi_102: return "duetwifi102";
+ case BoardType::DuetEthernet_10: return "duetethernet10";
+ case BoardType::DuetEthernet_102: return "duetethernet102";
+ case BoardType::Duet2SBC_10: return "duet2sbc10";
+ case BoardType::Duet2SBC_102: return "duet2sbc102";
+#elif defined(DUET_M)
+ case BoardType::DuetM_10: return "duetmaestro100";
+#elif defined(DUET_06_085)
+ case BoardType::Duet_06: return "duet06";
+ case BoardType::Duet_07: return "duet07";
+ case BoardType::Duet_085: return "duet085";
+#elif defined(__RADDS__)
+ case BoardType::RADDS_15: return "radds15";
+#elif defined(__ALLIGATOR__)
+ case BoardType::Alligator_2: return "alligator2";
+#elif defined(PCCB_10)
+ case BoardType::PCCB_v10: return "pc001373";
+#elif defined(PCCB_08) || defined(PCCB_08_X5)
+ case BoardType::PCCB_v08: return "pccb08";
+#elif defined(__LPC17xx__)
+ case BoardType::Lpc: return LPC_BOARD_STRING;
+#else
+# error Undefined board type
+#endif
+ default: return "unknown";
+ }
+}
+
+#ifdef DUET_NG
+
+// Return true if this is a Duet WiFi, false if it is a Duet Ethernet
+bool Platform::IsDuetWiFi() const noexcept
+{
+ return board == BoardType::DuetWiFi_10 || board == BoardType::DuetWiFi_102;
+}
+
+const char *Platform::GetBoardName() const noexcept
+{
+ return (board == BoardType::Duet2SBC_10 || board == BoardType::Duet2SBC_102)
+ ? BOARD_NAME_SBC
+ : (IsDuetWiFi()) ? BOARD_NAME_WIFI : BOARD_NAME_ETHERNET;
+}
+
+const char *Platform::GetBoardShortName() const noexcept
+{
+ return (board == BoardType::Duet2SBC_10 || board == BoardType::Duet2SBC_102)
+ ? BOARD_SHORT_NAME_SBC
+ : (IsDuetWiFi()) ? BOARD_SHORT_NAME_WIFI : BOARD_SHORT_NAME_ETHERNET;
+}
+
+#endif
+
+#ifdef DUET3MINI
+
+// Return true if this is a Duet WiFi, false if it is a Duet Ethernet
+bool Platform::IsDuetWiFi() const noexcept
+{
+ return board == BoardType::Duet3Mini_WiFi || board == BoardType::Duet3Mini_Unknown;
+}
+
+#endif
+
+#if HAS_MASS_STORAGE || HAS_LINUX_INTERFACE
+
+// Open a file
+FileStore* Platform::OpenFile(const char* folder, const char* fileName, OpenMode mode, uint32_t preAllocSize) const noexcept
+{
+ String<MaxFilenameLength> location;
+ return (MassStorage::CombineName(location.GetRef(), folder, fileName))
+ ? MassStorage::OpenFile(location.c_str(), mode, preAllocSize)
+ : nullptr;
+}
+
+bool Platform::FileExists(const char* folder, const char *filename) const noexcept
+{
+ String<MaxFilenameLength> location;
+ return MassStorage::CombineName(location.GetRef(), folder, filename) && MassStorage::FileExists(location.c_str());
+}
+
+#endif
+
+#if HAS_MASS_STORAGE
+
+// Return a pointer to a string holding the directory where the system files are. Lock the sysdir lock before calling this.
+const char* Platform::InternalGetSysDir() const noexcept
+{
+ return (sysDir != nullptr) ? sysDir : DEFAULT_SYS_DIR;
+}
+
+bool Platform::Delete(const char* folder, const char *filename) const noexcept
+{
+ String<MaxFilenameLength> location;
+ return MassStorage::CombineName(location.GetRef(), folder, filename) && MassStorage::Delete(location.c_str(), true);
+}
+
+bool Platform::DirectoryExists(const char *folder, const char *dir) const noexcept
+{
+ String<MaxFilenameLength> location;
+ return MassStorage::CombineName(location.GetRef(), folder, dir) && MassStorage::DirectoryExists(location.c_str());
+}
+
+// Set the system files path
+GCodeResult Platform::SetSysDir(const char* dir, const StringRef& reply) noexcept
+{
+ String<MaxFilenameLength> newSysDir;
+ WriteLocker lock(sysDirLock);
+
+ if (!MassStorage::CombineName(newSysDir.GetRef(), InternalGetSysDir(), dir) || (!newSysDir.EndsWith('/') && newSysDir.cat('/')))
+ {
+ reply.copy("Path name too long");
+ return GCodeResult::error;
+ }
+
+ const size_t len = newSysDir.strlen() + 1;
+ char* const nsd = new char[len];
+ memcpy(nsd, newSysDir.c_str(), len);
+ const char *nsd2 = nsd;
+ std::swap(sysDir, nsd2);
+ delete nsd2;
+ reprap.DirectoriesUpdated();
+ return GCodeResult::ok;
+}
+
+bool Platform::SysFileExists(const char *filename) const noexcept
+{
+ String<MaxFilenameLength> location;
+ return MakeSysFileName(location.GetRef(), filename) && MassStorage::FileExists(location.c_str());
+}
+
+FileStore* Platform::OpenSysFile(const char *filename, OpenMode mode) const noexcept
+{
+ String<MaxFilenameLength> location;
+ return (MakeSysFileName(location.GetRef(), filename))
+ ? MassStorage::OpenFile(location.c_str(), mode, 0)
+ : nullptr;
+}
+
+bool Platform::DeleteSysFile(const char *filename) const noexcept
+{
+ String<MaxFilenameLength> location;
+ return MakeSysFileName(location.GetRef(), filename) && MassStorage::Delete(location.c_str(), true);
+}
+
+bool Platform::MakeSysFileName(const StringRef& result, const char *filename) const noexcept
+{
+ return MassStorage::CombineName(result, GetSysDir().Ptr(), filename);
+}
+
+void Platform::AppendSysDir(const StringRef & path) const noexcept
+{
+ path.cat(GetSysDir().Ptr());
+}
+
+ReadLockedPointer<const char> Platform::GetSysDir() const noexcept
+{
+ ReadLocker lock(sysDirLock);
+ return ReadLockedPointer<const char>(lock, InternalGetSysDir());
+}
+
+#endif
+
+#if SUPPORT_LASER
+
+// CNC and laser support
+
+void Platform::SetLaserPwm(Pwm_t pwm) noexcept
+{
+ lastLaserPwm = (float)pwm/65535;
+ laserPort.WriteAnalog(lastLaserPwm); // we don't currently have an function that accepts an integer PWM fraction
+}
+
+// Return laser PWM in 0..1
+float Platform::GetLaserPwm() const noexcept
+{
+ return lastLaserPwm;
+}
+
+bool Platform::AssignLaserPin(GCodeBuffer& gb, const StringRef& reply)
+{
+ const bool ok = laserPort.AssignPort(gb, reply, PinUsedBy::laser, PinAccess::pwm);
+ if (ok)
+ {
+ SetLaserPwm(0);
+ }
+ return ok;
+}
+
+void Platform::SetLaserPwmFrequency(PwmFrequency freq) noexcept
+{
+ laserPort.SetFrequency(freq);
+}
+
+#endif
+
+// Axis limits
+void Platform::SetAxisMaximum(size_t axis, float value, bool byProbing) noexcept
+{
+ axisMaxima[axis] = value;
+ if (byProbing)
+ {
+ axisMaximaProbed.SetBit(axis);
+ }
+ reprap.MoveUpdated();
+}
+
+void Platform::SetAxisMinimum(size_t axis, float value, bool byProbing) noexcept
+{
+ axisMinima[axis] = value;
+ if (byProbing)
+ {
+ axisMinimaProbed.SetBit(axis);
+ }
+ reprap.MoveUpdated();
+}
+
+void Platform::InitZProbeFilters() noexcept
+{
+ zProbeOnFilter.Init(0);
+ zProbeOffFilter.Init(0);
+}
+
+#if SUPPORT_INKJET
+
+// Fire the inkjet (if any) in the given pattern
+// If there is no inkjet, false is returned; if there is one this returns true
+// So you can test for inkjet presence with if(platform->Inkjet(0))
+bool Platform::Inkjet(int bitPattern) noexcept
+{
+ if (inkjetBits < 0)
+ return false;
+ if (!bitPattern)
+ return true;
+
+ for(int8_t i = 0; i < inkjetBits; i++)
+ {
+ if (bitPattern & 1)
+ {
+ digitalWrite(inkjetSerialOut, 1); // Write data to shift register
+
+ for(int8_t j = 0; j <= i; j++)
+ {
+ digitalWrite(inkjetShiftClock, HIGH);
+ digitalWrite(inkjetShiftClock, LOW);
+ digitalWrite(inkjetSerialOut, 0);
+ }
+
+ digitalWrite(inkjetStorageClock, HIGH); // Transfers data from shift register to output register
+ digitalWrite(inkjetStorageClock, LOW);
+
+ digitalWrite(inkjetOutputEnable, LOW); // Fire the droplet out
+ delayMicroseconds(inkjetFireMicroseconds);
+ digitalWrite(inkjetOutputEnable, HIGH);
+
+ digitalWrite(inkjetClear, LOW); // Clear to 0
+ digitalWrite(inkjetClear, HIGH);
+
+ delayMicroseconds(inkjetDelayMicroseconds); // Wait for the next bit
+ }
+
+ bitPattern >>= 1; // Put the next bit in the units column
+ }
+
+ return true;
+}
+#endif
+
+#if HAS_CPU_TEMP_SENSOR
+
+// CPU temperature
+MinMaxCurrent Platform::GetMcuTemperatures() const noexcept
+{
+ MinMaxCurrent result;
+ result.min = lowestMcuTemperature;
+ result.current = GetCpuTemperature();
+ result.max = highestMcuTemperature;
+ return result;
+}
+
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+
+// Power in voltage
+MinMaxCurrent Platform::GetPowerVoltages() const noexcept
+{
+ MinMaxCurrent result;
+ result.min = AdcReadingToPowerVoltage(lowestVin);
+ result.current = AdcReadingToPowerVoltage(currentVin);
+ result.max = AdcReadingToPowerVoltage(highestVin);
+ return result;
+}
+
+float Platform::GetCurrentPowerVoltage() const noexcept
+{
+ return AdcReadingToPowerVoltage(currentVin);
+}
+
+#endif
+
+#if HAS_12V_MONITOR
+
+MinMaxCurrent Platform::GetV12Voltages() const noexcept
+{
+ MinMaxCurrent result;
+ result.min = AdcReadingToPowerVoltage(lowestV12);
+ result.current = AdcReadingToPowerVoltage(currentV12);
+ result.max = AdcReadingToPowerVoltage(highestV12);
+ return result;
+}
+
+float Platform::GetCurrentV12Voltage() const noexcept
+{
+ return AdcReadingToPowerVoltage(currentV12);
+}
+
+#endif
+
+#if HAS_SMART_DRIVERS
+
+// TMC driver temperatures
+float Platform::GetTmcDriversTemperature(unsigned int boardNumber) const noexcept
+{
+#if defined(DUET3MINI)
+ const DriversBitmap mask = (boardNumber == 0)
+ ? DriversBitmap::MakeLowestNBits(5) // drivers 0-4 are on the main board
+ : DriversBitmap::MakeLowestNBits(3).ShiftUp(5); // drivers 5-7 are on the daughter board
+#elif defined(DUET3)
+ const DriversBitmap mask = DriversBitmap::MakeLowestNBits(6); // there are 6 drivers, only one board
+#elif defined(DUET_NG)
+ const DriversBitmap mask = DriversBitmap::MakeLowestNBits(5).ShiftUp(5 * boardNumber); // there are 5 drivers on each board
+#elif defined(DUET_M)
+ const DriversBitmap mask = (boardNumber == 0)
+ ? DriversBitmap::MakeLowestNBits(5) // drivers 0-4 are on the main board
+ : DriversBitmap::MakeLowestNBits(2).ShiftUp(5); // drivers 5-6 are on the daughter board
+#elif defined(PCCB_10)
+ const DriversBitmap mask = (boardNumber == 0)
+ ? DriversBitmap::MakeLowestNBits(2) // drivers 0,1 are on-board
+ : DriversBitmap::MakeLowestNBits(5).ShiftUp(2); // drivers 2-7 are on the DueX5
+#elif defined(PCCB_08_X5)
+ const DriversBitmap mask = DriversBitmap::MakeLowestNBits(5); // all drivers (0-4) are on the DueX, no further expansion supported
+#elif defined(PCCB_08)
+ const DriversBitmap mask = DriversBitmap::MakeLowestNBits(2); // drivers 0, 1 are on-board, no expansion supported
+#else
+# error Undefined board
+#endif
+ return (temperatureShutdownDrivers.Intersects(mask)) ? 150.0
+ : (temperatureWarningDrivers.Intersects(mask)) ? 100.0
+ : 0.0;
+}
+
+#endif
+
+#if HAS_STALL_DETECT
+
+// Configure the motor stall detection, returning true if an error was encountered
+GCodeResult Platform::ConfigureStallDetection(GCodeBuffer& gb, const StringRef& reply, OutputBuffer *& buf) THROWS(GCodeException)
+{
+ // Build a bitmap of all the drivers referenced
+ // First looks for explicit driver numbers
+ DriversBitmap drivers;
+#if SUPPORT_CAN_EXPANSION
+ CanDriversList canDrivers;
+#endif
+ if (gb.Seen('P'))
+ {
+ DriverId drives[NumDirectDrivers];
+ size_t dCount = NumDirectDrivers;
+ gb.GetDriverIdArray(drives, dCount);
+ for (size_t i = 0; i < dCount; i++)
+ {
+ if (drives[i].IsLocal())
+ {
+ if (drives[i].localDriver >= numSmartDrivers)
+ {
+ reply.printf("Invalid local drive number '%u'", drives[i].localDriver);
+ return GCodeResult::error;
+ }
+ drivers.SetBit(drives[i].localDriver);
+ }
+#if SUPPORT_CAN_EXPANSION
+ else
+ {
+ canDrivers.AddEntry(drives[i]);
+ }
+#endif
+ }
+ }
+
+ // Now look for axes
+ for (size_t axis = 0; axis < reprap.GetGCodes().GetTotalAxes(); ++axis)
+ {
+ if (gb.Seen(reprap.GetGCodes().GetAxisLetters()[axis]))
+ {
+ IterateDrivers(axis,
+ [&drivers](uint8_t localDriver){ drivers.SetBit(localDriver); }
+#if SUPPORT_CAN_EXPANSION
+ , [&canDrivers](DriverId driver){ canDrivers.AddEntry(driver); }
+#endif
+ );
+ }
+ }
+
+ // Look for extruders
+ if (gb.Seen('E'))
+ {
+ uint32_t extruderNumbers[MaxExtruders];
+ size_t numSeen = MaxExtruders;
+ gb.GetUnsignedArray(extruderNumbers, numSeen, false);
+ for (size_t i = 0; i < numSeen; ++i)
+ {
+ if (extruderNumbers[i] < MaxExtruders)
+ {
+ const DriverId driver = GetExtruderDriver(extruderNumbers[i]);
+ if (driver.IsLocal())
+ {
+ drivers.SetBit(driver.localDriver);
+ }
+#if SUPPORT_CAN_EXPANSION
+ else
+ {
+ canDrivers.AddEntry(driver);
+ }
+#endif
+ }
+ }
+ }
+
+ // Now check for values to change
+ bool seen = false;
+ if (gb.Seen('S'))
+ {
+ seen = true;
+ const int sgThreshold = gb.GetIValue();
+ drivers.Iterate([sgThreshold](unsigned int drive, unsigned int) noexcept { SmartDrivers::SetStallThreshold(drive, sgThreshold); });
+ }
+ if (gb.Seen('F'))
+ {
+ seen = true;
+ const bool sgFilter = (gb.GetIValue() == 1);
+ drivers.Iterate([sgFilter](unsigned int drive, unsigned int) noexcept { SmartDrivers::SetStallFilter(drive, sgFilter); });
+ }
+ if (gb.Seen('H'))
+ {
+ seen = true;
+ const unsigned int stepsPerSecond = gb.GetUIValue();
+ drivers.Iterate([stepsPerSecond](unsigned int drive, unsigned int) noexcept { SmartDrivers::SetStallMinimumStepsPerSecond(drive, stepsPerSecond); });
+ }
+ if (gb.Seen('T'))
+ {
+ seen = true;
+ const uint32_t coolStepConfig = gb.GetUIValue();
+ drivers.Iterate([coolStepConfig](unsigned int drive, unsigned int) noexcept { SmartDrivers::SetRegister(drive, SmartDriverRegister::coolStep, coolStepConfig); } );
+ }
+ if (gb.Seen('R'))
+ {
+ seen = true;
+ const int action = gb.GetIValue();
+ switch (action)
+ {
+ case 0:
+ default:
+ logOnStallDrivers &= ~drivers;
+ pauseOnStallDrivers &= ~drivers;
+ rehomeOnStallDrivers &= ~drivers;
+ break;
+
+ case 1:
+ rehomeOnStallDrivers &= ~drivers;
+ pauseOnStallDrivers &= ~drivers;
+ logOnStallDrivers |= drivers;
+ break;
+
+ case 2:
+ logOnStallDrivers &= ~drivers;
+ rehomeOnStallDrivers &= ~drivers;
+ pauseOnStallDrivers |= drivers;
+ break;
+
+ case 3:
+ logOnStallDrivers &= ~drivers;
+ pauseOnStallDrivers &= ~drivers;
+ rehomeOnStallDrivers |= drivers;
+ break;
+ }
+ }
+
+ if (seen)
+ {
+#if SUPPORT_CAN_EXPANSION
+ return CanInterface::GetSetRemoteDriverStallParameters(canDrivers, gb, reply, buf);
+#else
+ return GCodeResult::ok;
+#endif
+ }
+
+ // Print the stall status
+ if (!OutputBuffer::Allocate(buf))
+ {
+ return GCodeResult::notFinished;
+ }
+
+ if (drivers.IsEmpty()
+#if SUPPORT_CAN_EXPANSION
+ && canDrivers.IsEmpty()
+#endif
+ )
+ {
+ drivers = DriversBitmap::MakeLowestNBits(numSmartDrivers);
+ }
+
+ drivers.Iterate
+ ([buf, this, &reply](unsigned int drive, unsigned int) noexcept
+ {
+#if SUPPORT_CAN_EXPANSION
+ buf->lcatf("Driver 0.%u: ", drive);
+#else
+ buf->lcatf("Driver %u: ", drive);
+#endif
+ reply.Clear(); // we use 'reply' as a temporary buffer
+ SmartDrivers::AppendStallConfig(drive, reply);
+ buf->cat(reply.c_str());
+ buf->catf(", action on stall: %s",
+ (rehomeOnStallDrivers.IsBitSet(drive)) ? "rehome"
+ : (pauseOnStallDrivers.IsBitSet(drive)) ? "pause"
+ : (logOnStallDrivers.IsBitSet(drive)) ? "log"
+ : "none"
+ );
+ }
+ );
+
+# if SUPPORT_CAN_EXPANSION
+ return CanInterface::GetSetRemoteDriverStallParameters(canDrivers, gb, reply, buf);
+# else
+ return GCodeResult::ok;
+#endif
+}
+
+#endif
+
+// Real-time clock
+
+bool Platform::SetDateTime(time_t time) noexcept
+{
+ struct tm brokenDateTime;
+ const bool ok = (gmtime_r(&time, &brokenDateTime) != nullptr);
+ if (ok)
+ {
+ realTime = time; // set the date and time
+
+ // Write a log message, giving the time since power up in same format as the logger does
+ const uint32_t timeSincePowerUp = (uint32_t)(millis64()/1000u);
+ MessageF(LogWarn, "Date and time set at power up + %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 "\n", timeSincePowerUp/3600u, (timeSincePowerUp % 3600u)/60u, timeSincePowerUp % 60u);
+ timeLastUpdatedMillis = millis();
+ }
+ return ok;
+}
+
+// Configure an I/O port
+GCodeResult Platform::ConfigurePort(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException)
+{
+ // Exactly one of FHJPS is allowed
+ unsigned int charsPresent = 0;
+ for (char c : (const char[]){'R', 'J', 'F', 'H', 'P', 'S'})
+ {
+ charsPresent <<= 1;
+ if (gb.Seen(c))
+ {
+ charsPresent |= 1;
+ }
+ }
+
+ switch (charsPresent)
+ {
+ case 1:
+ {
+ const uint32_t gpioNumber = gb.GetLimitedUIValue('S', MaxGpOutPorts);
+ return gpoutPorts[gpioNumber].Configure(gpioNumber, true, gb, reply);
+ }
+
+ case 2:
+ {
+ const uint32_t gpioNumber = gb.GetLimitedUIValue('P', MaxGpOutPorts);
+ return gpoutPorts[gpioNumber].Configure(gpioNumber, false, gb, reply);
+ }
+
+ case 4:
+ return reprap.GetHeat().ConfigureHeater(gb, reply);
+
+ case 8:
+ return reprap.GetFansManager().ConfigureFanPort(gb, reply);
+
+ case 16:
+ {
+ const uint32_t gpinNumber = gb.GetLimitedUIValue('J', MaxGpInPorts);
+ return gpinPorts[gpinNumber].Configure(gpinNumber, gb, reply);
+ }
+ case 32:
+ {
+ const uint32_t slot = gb.GetLimitedUIValue('R', MaxSpindles);
+ return spindles[slot].Configure(gb, reply);
+ }
+
+ default:
+ reply.copy("exactly one of FHJPSR must be given");
+ return GCodeResult::error;
+ }
+}
+
+#if SUPPORT_CAN_EXPANSION
+
+void Platform::HandleRemoteGpInChange(CanAddress src, uint8_t handleMajor, uint8_t handleMinor, bool state) noexcept
+{
+ if (handleMajor < MaxGpInPorts)
+ {
+ gpinPorts[handleMajor].SetState(src, state);
+ }
+}
+
+GCodeResult Platform::UpdateRemoteStepsPerMmAndMicrostepping(AxesBitmap axesAndExtruders, const StringRef& reply) noexcept
+{
+ CanDriversData<StepsPerUnitAndMicrostepping> data;
+ axesAndExtruders.Iterate([this, &data](unsigned int axisOrExtruder, unsigned int count) noexcept
+ {
+ const StepsPerUnitAndMicrostepping driverData(this->driveStepsPerUnit[axisOrExtruder], this->microstepping[axisOrExtruder]);
+ this->IterateRemoteDrivers(axisOrExtruder,
+ [&data, &driverData](DriverId driver) noexcept
+ {
+ data.AddEntry(driver, driverData);
+ }
+ );
+ }
+ );
+ return CanInterface::SetRemoteDriverStepsPerMmAndMicrostepping(data, reply);
+}
+
+#endif
+
+// Configure the ancillary PWM
+GCodeResult Platform::GetSetAncillaryPwm(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException)
+{
+ bool seen = false;
+ if (gb.Seen('P'))
+ {
+ seen = true;
+ if (!extrusionAncilliaryPwmPort.AssignPort(gb, reply, PinUsedBy::gpout, PinAccess::pwm))
+ {
+ return GCodeResult::error;
+ }
+ const PwmFrequency freq = (gb.Seen('Q') || gb.Seen('F')) ? gb.GetPwmFrequency() : DefaultPinWritePwmFreq;
+ extrusionAncilliaryPwmPort.SetFrequency(freq);
+ }
+ if (gb.Seen('S'))
+ {
+ extrusionAncilliaryPwmValue = min<float>(gb.GetFValue(), 1.0); // negative values are OK, they mean don't set the output
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.copy("Extrusion ancillary PWM");
+ extrusionAncilliaryPwmPort.AppendDetails(reply);
+ }
+ return GCodeResult::ok;
+}
+
+#if MCU_HAS_UNIQUE_ID
+
+// Get a pseudo-random number (not a true random number)
+uint32_t Platform::Random() noexcept
+{
+ const uint32_t clocks = StepTimer::GetTimerTicks();
+ return clocks ^ uniqueId[0] ^ uniqueId[1] ^ uniqueId[2] ^ uniqueId[3];
+}
+
+#endif
+
+#if HAS_CPU_TEMP_SENSOR && SAME5x
+
+void Platform::TemperatureCalibrationInit() noexcept
+{
+ // Temperature sense stuff
+ constexpr uint32_t NVM_TEMP_CAL_TLI_POS = 0;
+ constexpr uint32_t NVM_TEMP_CAL_TLI_SIZE = 8;
+ constexpr uint32_t NVM_TEMP_CAL_TLD_POS = 8;
+ constexpr uint32_t NVM_TEMP_CAL_TLD_SIZE = 4;
+ constexpr uint32_t NVM_TEMP_CAL_THI_POS = 12;
+ constexpr uint32_t NVM_TEMP_CAL_THI_SIZE = 8;
+ constexpr uint32_t NVM_TEMP_CAL_THD_POS = 20;
+ constexpr uint32_t NVM_TEMP_CAL_THD_SIZE = 4;
+ constexpr uint32_t NVM_TEMP_CAL_VPL_POS = 40;
+ constexpr uint32_t NVM_TEMP_CAL_VPL_SIZE = 12;
+ constexpr uint32_t NVM_TEMP_CAL_VPH_POS = 52;
+ constexpr uint32_t NVM_TEMP_CAL_VPH_SIZE = 12;
+ constexpr uint32_t NVM_TEMP_CAL_VCL_POS = 64;
+ constexpr uint32_t NVM_TEMP_CAL_VCL_SIZE = 12;
+ constexpr uint32_t NVM_TEMP_CAL_VCH_POS = 76;
+ constexpr uint32_t NVM_TEMP_CAL_VCH_SIZE = 12;
+
+ const uint16_t temp_cal_vpl = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_VPL_POS / 32)) >> (NVM_TEMP_CAL_VPL_POS % 32))
+ & ((1u << NVM_TEMP_CAL_VPL_SIZE) - 1);
+ const uint16_t temp_cal_vph = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_VPH_POS / 32)) >> (NVM_TEMP_CAL_VPH_POS % 32))
+ & ((1u << NVM_TEMP_CAL_VPH_SIZE) - 1);
+ const uint16_t temp_cal_vcl = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_VCL_POS / 32)) >> (NVM_TEMP_CAL_VCL_POS % 32))
+ & ((1u << NVM_TEMP_CAL_VCL_SIZE) - 1);
+ const uint16_t temp_cal_vch = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_VCH_POS / 32)) >> (NVM_TEMP_CAL_VCH_POS % 32))
+ & ((1u << NVM_TEMP_CAL_VCH_SIZE) - 1);
+
+ const uint8_t temp_cal_tli = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_TLI_POS / 32)) >> (NVM_TEMP_CAL_TLI_POS % 32))
+ & ((1u << NVM_TEMP_CAL_TLI_SIZE) - 1);
+ const uint8_t temp_cal_tld = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_TLD_POS / 32)) >> (NVM_TEMP_CAL_TLD_POS % 32))
+ & ((1u << NVM_TEMP_CAL_TLD_SIZE) - 1);
+ const uint16_t temp_cal_tl = ((uint16_t)temp_cal_tli) << 4 | ((uint16_t)temp_cal_tld);
+
+ const uint8_t temp_cal_thi = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_THI_POS / 32)) >> (NVM_TEMP_CAL_THI_POS % 32))
+ & ((1u << NVM_TEMP_CAL_THI_SIZE) - 1);
+ const uint8_t temp_cal_thd = (*((uint32_t *)(NVMCTRL_TEMP_LOG) + (NVM_TEMP_CAL_THD_POS / 32)) >> (NVM_TEMP_CAL_THD_POS % 32))
+ & ((1u << NVM_TEMP_CAL_THD_SIZE) - 1);
+ const uint16_t temp_cal_th = ((uint16_t)temp_cal_thi) << 4 | ((uint16_t)temp_cal_thd);
+
+ tempCalF1 = (int32_t)temp_cal_tl * (int32_t)temp_cal_vph - (int32_t)temp_cal_th * (int32_t)temp_cal_vpl;
+ tempCalF2 = (int32_t)temp_cal_tl * (int32_t)temp_cal_vch - (int32_t)temp_cal_th * (int32_t)temp_cal_vcl;
+ tempCalF3 = (int32_t)temp_cal_vcl - (int32_t)temp_cal_vch;
+ tempCalF4 = (int32_t)temp_cal_vpl - (int32_t)temp_cal_vph;
+}
+
+#endif
+
+#if SUPPORT_REMOTE_COMMANDS
+
+GCodeResult Platform::EutHandleM950Gpio(const CanMessageGeneric& msg, const StringRef& reply) noexcept
+{
+ // Get and validate the port number
+ CanMessageGenericParser parser(msg, M950GpioParams);
+ uint16_t gpioNumber;
+ if (!parser.GetUintParam('P', gpioNumber))
+ {
+ reply.copy("Missing port number parameter in M950Gpio message");
+ return GCodeResult::error;
+ }
+ if (gpioNumber >= MaxGpOutPorts)
+ {
+ reply.printf("GPIO port number %u is too high for board %u", gpioNumber, CanInterface::GetCanAddress());
+ return GCodeResult::error;
+ }
+
+ return gpoutPorts[gpioNumber].AssignFromRemote(gpioNumber, parser, reply);
+}
+
+GCodeResult Platform::EutHandleGpioWrite(const CanMessageWriteGpio& msg, const StringRef& reply) noexcept
+{
+ if (msg.portNumber >= MaxGpOutPorts)
+ {
+ reply.printf("GPIO port# %u is too high for this board", msg.portNumber);
+ return GCodeResult::error;
+ }
+
+ gpoutPorts[msg.portNumber].WriteAnalog(msg.pwm);
+ return GCodeResult::ok;
+}
+
+GCodeResult Platform::EutSetMotorCurrents(const CanMessageMultipleDrivesRequest<float>& msg, size_t dataLength, const StringRef& reply) noexcept
+{
+# if HAS_SMART_DRIVERS
+ const auto drivers = Bitmap<uint16_t>::MakeFromRaw(msg.driversToUpdate);
+ if (dataLength < msg.GetActualDataLength(drivers.CountSetBits()))
+ {
+ reply.copy("bad data length");
+ return GCodeResult::error;
+ }
+
+ GCodeResult rslt = GCodeResult::ok;
+ drivers.Iterate([this, &msg, &reply, &rslt](unsigned int driver, unsigned int count) -> void
+ {
+ if (driver >= NumDirectDrivers)
+ {
+ reply.lcatf("No such driver %u.%u", CanInterface::GetCanAddress(), driver);
+ rslt = GCodeResult::error;
+ }
+ else
+ {
+ SetMotorCurrent(driver, msg.values[count], 906, reply);
+ }
+ }
+ );
+ return rslt;
+# else
+ reply.copy("Setting not available for external drivers");
+ return GCodeResult::error;
+# endif
+}
+
+GCodeResult Platform::EutSetStepsPerMmAndMicrostepping(const CanMessageMultipleDrivesRequest<StepsPerUnitAndMicrostepping>& msg, size_t dataLength, const StringRef& reply) noexcept
+{
+ const auto drivers = Bitmap<uint16_t>::MakeFromRaw(msg.driversToUpdate);
+ if (dataLength < msg.GetActualDataLength(drivers.CountSetBits()))
+ {
+ reply.copy("bad data length");
+ return GCodeResult::error;
+ }
+
+ GCodeResult rslt = GCodeResult::ok;
+ drivers.Iterate([this, &msg, &reply, &rslt](unsigned int driver, unsigned int count) -> void
+ {
+ if (driver >= NumDirectDrivers)
+ {
+ reply.lcatf("No such driver %u.%u", CanInterface::GetCanAddress(), driver);
+ rslt = GCodeResult::error;
+ }
+ else
+ {
+ SetDriveStepsPerUnit(driver, msg.values[count].GetStepsPerUnit(), 0);
+#if HAS_SMART_DRIVERS
+ microstepping[driver] = msg.values[count].GetMicrostepping();
+ const uint16_t microsteppingOnly = microstepping[driver] & 0x03FF;
+ const bool interpolate = (microstepping[driver] & 0x8000) != 0;
+ if (!SmartDrivers::SetMicrostepping(driver, microsteppingOnly, interpolate))
+ {
+ reply.lcatf("Driver %u.%u does not support x%u microstepping", CanInterface::GetCanAddress(), driver, microsteppingOnly);
+ if (interpolate)
+ {
+ reply.cat(" with interpolation");
+ }
+ rslt = GCodeResult::error;
+ }
+#endif
+ }
+ }
+ );
+ return rslt;
+}
+
+GCodeResult Platform::EutHandleSetDriverStates(const CanMessageMultipleDrivesRequest<DriverStateControl>& msg, const StringRef& reply) noexcept
+{
+ //TODO check message is long enough for the number of drivers specified
+ const auto drivers = Bitmap<uint16_t>::MakeFromRaw(msg.driversToUpdate);
+ drivers.Iterate([this, &msg](unsigned int driver, unsigned int count) -> void
+ {
+ switch (msg.values[count].mode)
+ {
+ case DriverStateControl::driverActive:
+ EnableOneLocalDriver(driver, motorCurrents[driver]);
+ driverState[driver] = DriverStatus::enabled;
+ break;
+
+ case DriverStateControl::driverIdle:
+ UpdateMotorCurrent(driver, motorCurrents[driver] * idleCurrentFactor);
+ driverState[driver] = DriverStatus::idle;
+ break;
+
+ case DriverStateControl::driverDisabled:
+ default:
+ DisableOneLocalDriver(driver);
+ driverState[driver] = DriverStatus::disabled;
+ break;
+ }
+ });
+ return GCodeResult::ok;
+}
+
+float Platform::EutGetRemotePressureAdvance(size_t driver) const noexcept
+{
+ return (driver < ARRAY_SIZE(remotePressureAdvance)) ? remotePressureAdvance[driver] : 0.0;
+}
+
+GCodeResult Platform::EutSetRemotePressureAdvance(const CanMessageMultipleDrivesRequest<float>& msg, size_t dataLength, const StringRef& reply) noexcept
+{
+ const auto drivers = Bitmap<uint16_t>::MakeFromRaw(msg.driversToUpdate);
+ if (dataLength < msg.GetActualDataLength(drivers.CountSetBits()))
+ {
+ reply.copy("bad data length");
+ return GCodeResult::error;
+ }
+
+ GCodeResult rslt = GCodeResult::ok;
+ drivers.Iterate([this, &msg, &reply, &rslt](unsigned int driver, unsigned int count) -> void
+ {
+ if (driver >= NumDirectDrivers)
+ {
+ reply.lcatf("No such driver %u.%u", CanInterface::GetCanAddress(), driver);
+ rslt = GCodeResult::error;
+ }
+ else
+ {
+ remotePressureAdvance[driver] = msg.values[count];
+ }
+ }
+ );
+ return rslt;
+}
+
+GCodeResult Platform::EutProcessM569(const CanMessageGeneric& msg, const StringRef& reply) noexcept
+{
+ CanMessageGenericParser parser(msg, M569Params);
+ uint8_t drive;
+ if (!parser.GetUintParam('P', drive))
+ {
+ reply.copy("Missing P parameter in CAN message");
+ return GCodeResult::error;
+ }
+
+ if (drive >= NumDirectDrivers)
+ {
+ reply.printf("Driver number %u.%u out of range", CanInterface::GetCanAddress(), drive);
+ return GCodeResult::error;
+ }
+
+ bool seen = false;
+ uint8_t direction;
+ if (parser.GetUintParam('S', direction))
+ {
+ seen = true;
+ SetDirectionValue(drive, direction != 0);
+ }
+ int8_t rValue;
+ if (parser.GetIntParam('R', rValue))
+ {
+ seen = true;
+ SetEnableValue(drive, rValue);
+ }
+
+ float timings[4];
+ size_t numTimings = 4;
+ if (parser.GetFloatArrayParam('T', numTimings, timings))
+ {
+ seen = true;
+ if (numTimings == 1)
+ {
+ timings[1] = timings[2] = timings[3] = timings[0];
+ }
+ else if (numTimings != 4)
+ {
+ reply.copy("bad timing parameter, expected 1 or 4 values");
+ return GCodeResult::error;
+ }
+ SetDriverStepTiming(drive, timings);
+ }
+
+#if HAS_SMART_DRIVERS
+ {
+ uint32_t val;
+ if (parser.GetUintParam('D', val)) // set driver mode
+ {
+ seen = true;
+ if (!SmartDrivers::SetDriverMode(drive, val))
+ {
+ reply.printf("Driver %u.%u does not support mode '%s'", CanInterface::GetCanAddress(), drive, TranslateDriverMode(val));
+ return GCodeResult::error;
+ }
+ }
+
+ if (parser.GetUintParam('F', val)) // set off time
+ {
+ seen = true;
+ if (!SmartDrivers::SetRegister(drive, SmartDriverRegister::toff, val))
+ {
+ reply.printf("Bad off time for driver %u", drive);
+ return GCodeResult::error;
+ }
+ }
+
+ if (parser.GetUintParam('B', val)) // set blanking time
+ {
+ seen = true;
+ if (!SmartDrivers::SetRegister(drive, SmartDriverRegister::tblank, val))
+ {
+ reply.printf("Bad blanking time for driver %u", drive);
+ return GCodeResult::error;
+ }
+ }
+
+ if (parser.GetUintParam('V', val)) // set microstep interval for changing from stealthChop to spreadCycle
+ {
+ seen = true;
+ if (!SmartDrivers::SetRegister(drive, SmartDriverRegister::tpwmthrs, val))
+ {
+ reply.printf("Bad mode change microstep interval for driver %u", drive);
+ return GCodeResult::error;
+ }
+ }
+
+#if SUPPORT_TMC51xx
+ if (parser.GetUintParam('H', val)) // set coolStep threshold
+ {
+ seen = true;
+ if (!SmartDrivers::SetRegister(drive, SmartDriverRegister::thigh, val))
+ {
+ reply.printf("Bad high speed microstep interval for driver %u", drive);
+ return GCodeResult::error;
+ }
+ }
+#endif
+ }
+
+ size_t numHvalues = 3;
+ const uint8_t *hvalues;
+ if (parser.GetArrayParam('Y', ParamDescriptor::ParamType::uint8_array, numHvalues, hvalues)) // set spread cycle hysteresis
+ {
+ seen = true;
+ if (numHvalues == 2 || numHvalues == 3)
+ {
+ // There is a constraint on the sum of HSTRT and HEND, so set HSTART then HEND then HSTART again because one may go up and the other down
+ (void)SmartDrivers::SetRegister(drive, SmartDriverRegister::hstart, hvalues[0]);
+ bool ok = SmartDrivers::SetRegister(drive, SmartDriverRegister::hend, hvalues[1]);
+ if (ok)
+ {
+ ok = SmartDrivers::SetRegister(drive, SmartDriverRegister::hstart, hvalues[0]);
+ }
+ if (ok && numHvalues == 3)
+ {
+ ok = SmartDrivers::SetRegister(drive, SmartDriverRegister::hdec, hvalues[2]);
+ }
+ if (!ok)
+ {
+ reply.printf("Bad hysteresis setting for driver %u", drive);
+ return GCodeResult::error;
+ }
+ }
+ else
+ {
+ reply.copy("Expected 2 or 3 Y values");
+ return GCodeResult::error;
+ }
+ }
+#endif
+ if (!seen)
+ {
+ reply.printf("Driver %u.%u runs %s, active %s enable",
+ CanInterface::GetCanAddress(),
+ drive,
+ (GetDirectionValue(drive)) ? "forwards" : "in reverse",
+ (GetEnableValue(drive)) ? "high" : "low");
+
+ float timings[4];
+ const bool isSlowDriver = GetDriverStepTiming(drive, timings);
+ if (isSlowDriver)
+ {
+ reply.catf(", step timing %.1f:%.1f:%.1f:%.1fus", (double)timings[0], (double)timings[1], (double)timings[2], (double)timings[3]);
+ }
+ else
+ {
+ reply.cat(", step timing fast");
+ }
+
+#if HAS_SMART_DRIVERS
+ // It's a smart driver, so print the parameters common to all modes, except for the position
+ reply.catf(", mode %s, ccr 0x%05" PRIx32 ", toff %" PRIu32 ", tblank %" PRIu32,
+ TranslateDriverMode(SmartDrivers::GetDriverMode(drive)),
+ SmartDrivers::GetRegister(drive, SmartDriverRegister::chopperControl),
+ SmartDrivers::GetRegister(drive, SmartDriverRegister::toff),
+ SmartDrivers::GetRegister(drive, SmartDriverRegister::tblank)
+ );
+
+# if SUPPORT_TMC51xx
+ {
+ const uint32_t thigh = SmartDrivers::GetRegister(drive, SmartDriverRegister::thigh);
+ bool bdummy;
+ const float mmPerSec = (12000000.0 * SmartDrivers::GetMicrostepping(drive, bdummy))/(256 * thigh * Platform::DriveStepsPerUnit(drive));
+ reply.catf(", thigh %" PRIu32 " (%.1f mm/sec)", thigh, (double)mmPerSec);
+ }
+# endif
+
+ // Print the additional parameters that are relevant in the current mode
+ if (SmartDrivers::GetDriverMode(drive) == DriverMode::spreadCycle)
+ {
+ reply.catf(", hstart/hend/hdec %" PRIu32 "/%" PRIu32 "/%" PRIu32,
+ SmartDrivers::GetRegister(drive, SmartDriverRegister::hstart),
+ SmartDrivers::GetRegister(drive, SmartDriverRegister::hend),
+ SmartDrivers::GetRegister(drive, SmartDriverRegister::hdec)
+ );
+ }
+
+# if SUPPORT_TMC22xx || SUPPORT_TMC51xx
+ if (SmartDrivers::GetDriverMode(drive) == DriverMode::stealthChop)
+ {
+ const uint32_t tpwmthrs = SmartDrivers::GetRegister(drive, SmartDriverRegister::tpwmthrs);
+ bool bdummy;
+ const float mmPerSec = (12000000.0 * SmartDrivers::GetMicrostepping(drive, bdummy))/(256 * tpwmthrs * Platform::DriveStepsPerUnit(drive));
+ const uint32_t pwmScale = SmartDrivers::GetRegister(drive, SmartDriverRegister::pwmScale);
+ const uint32_t pwmAuto = SmartDrivers::GetRegister(drive, SmartDriverRegister::pwmAuto);
+ const unsigned int pwmScaleSum = pwmScale & 0xFF;
+ const int pwmScaleAuto = (int)((((pwmScale >> 16) & 0x01FF) ^ 0x0100) - 0x0100);
+ const unsigned int pwmOfsAuto = pwmAuto & 0xFF;
+ const unsigned int pwmGradAuto = (pwmAuto >> 16) & 0xFF;
+ reply.catf(", tpwmthrs %" PRIu32 " (%.1f mm/sec), pwmScaleSum %u, pwmScaleAuto %d, pwmOfsAuto %u, pwmGradAuto %u",
+ tpwmthrs, (double)mmPerSec, pwmScaleSum, pwmScaleAuto, pwmOfsAuto, pwmGradAuto);
+ }
+# endif
+ // Finally, print the microstep position
+ {
+ const uint32_t mstepPos = SmartDrivers::GetRegister(drive, SmartDriverRegister::mstepPos);
+ if (mstepPos < 1024)
+ {
+ reply.catf(", pos %" PRIu32, mstepPos);
+ }
+ else
+ {
+ reply.cat(", pos unknown");
+ }
+ }
+#endif
+
+ }
+ return GCodeResult::ok;
+}
+
+#endif
+
+// Process a 1ms tick interrupt
+// This function must be kept fast so as not to disturb the stepper timing, so don't do any floating point maths in here.
+// This is what we need to do:
+// 1. Kick off a new ADC conversion.
+// 2. Fetch and process the result of the last ADC conversion.
+// 3a. If the last ADC conversion was for the Z probe, toggle the modulation output if using a modulated IR sensor.
+// 3b. If the last ADC reading was a thermistor reading, check for an over-temperature situation and turn off the heater if necessary.
+// We do this here because the usual polling loop sometimes gets stuck trying to send data to the USB port.
+
+void Platform::Tick() noexcept
+{
+#if !SAME5x
+ LegacyAnalogIn::AnalogInFinaliseConversion();
+#endif
+
+#if HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR
+ if (tickState != 0)
+ {
+# if HAS_VOLTAGE_MONITOR
+ // Read the power input voltage
+ currentVin = AnalogInReadChannel(vInMonitorAdcChannel);
+ if (currentVin > highestVin)
+ {
+ highestVin = currentVin;
+ }
+ if (currentVin < lowestVin)
+ {
+ lowestVin = currentVin;
+ }
+# endif
+
+# if HAS_12V_MONITOR
+ currentV12 = AnalogInReadChannel(v12MonitorAdcChannel);
+ if (currentV12 > highestV12)
+ {
+ highestV12 = currentV12;
+ }
+ if (currentV12 < lowestV12)
+ {
+ lowestV12 = currentV12;
+ }
+# endif
+
+# if HAS_SMART_DRIVERS && (ENFORCE_MAX_VIN || ENFORCE_MIN_V12)
+ if (driversPowered &&
+# if ENFORCE_MAX_VIN && ENFORCE_MIN_V12
+ (currentVin > driverOverVoltageAdcReading || currentV12 < driverV12OffAdcReading)
+# elif ENFORCE_MAX_VIN
+ currentVin > driverOverVoltageAdcReading
+# elif ENFORCE_MIN_V12
+ currentV12 < driverV12OffAdcReading
+# endif
+ )
+ {
+ SmartDrivers::TurnDriversOff();
+ // We deliberately do not clear driversPowered here or increase the over voltage event count - we let the spin loop handle that
+ }
+# endif
+ }
+#endif
+
+#if SAME70
+ // The SAME70 ADC is noisy, so read a thermistor on every tick so that we can average a greater number of readings
+ // Because we are in the tick ISR and no other ISR reads the averaging filter, we can cast away 'volatile' here.
+ if (tickState != 0)
+ {
+ ThermistorAveragingFilter& currentFilter = const_cast<ThermistorAveragingFilter&>(adcFilters[currentFilterNumber]); // cast away 'volatile'
+ currentFilter.ProcessReading(AnalogInReadChannel(filteredAdcChannels[currentFilterNumber]));
+
+ ++currentFilterNumber;
+ if (currentFilterNumber == NumAdcFilters)
+ {
+ currentFilterNumber = 0;
+ }
+ }
+#endif
+
+ const ZProbe& currentZProbe = endstops.GetDefaultZProbeFromISR();
+ switch (tickState)
+ {
+ case 1:
+ case 3:
+ {
+#if !SAME70
+ // We read a filtered ADC channel on alternate ticks
+ // Because we are in the tick ISR and no other ISR reads the averaging filter, we can cast away 'volatile' here.
+ ThermistorAveragingFilter& currentFilter = const_cast<ThermistorAveragingFilter&>(adcFilters[currentFilterNumber]); // cast away 'volatile'
+ currentFilter.ProcessReading(AnalogInReadChannel(filteredAdcChannels[currentFilterNumber]));
+
+ ++currentFilterNumber;
+ if (currentFilterNumber == NumAdcFilters)
+ {
+ currentFilterNumber = 0;
+ }
+#endif
+ // If we are not using a simple modulated IR sensor, process the Z probe reading on every tick for a faster response.
+ // If we are using a simple modulated IR sensor then we need to allow the reading to settle after turning the IR emitter on or off,
+ // so on alternate ticks we read it and switch the emitter
+ if (currentZProbe.GetProbeType() != ZProbeType::dumbModulated)
+ {
+ const_cast<ZProbeAveragingFilter&>((tickState == 1) ? zProbeOnFilter : zProbeOffFilter).ProcessReading(currentZProbe.GetRawReading());
+ }
+ ++tickState;
+ }
+ break;
+
+ case 2:
+ const_cast<ZProbeAveragingFilter&>(zProbeOnFilter).ProcessReading(currentZProbe.GetRawReading());
+ currentZProbe.SetIREmitter(false);
+ ++tickState;
+ break;
+
+ case 4: // last conversion started was the Z probe, with IR LED off if modulation is enabled
+ const_cast<ZProbeAveragingFilter&>(zProbeOffFilter).ProcessReading(currentZProbe.GetRawReading());
+ currentZProbe.SetIREmitter(true);
+ tickState = 1;
+ break;
+
+ case 0: // this is the state after initialisation, no conversion has been started
+ default:
+ currentZProbe.SetIREmitter(true);
+ tickState = 1;
+ break;
+ }
+
+#if SAME70
+ // On Duet 3, AFEC1 is used only for thermistors and associated Vref/Vssa monitoring. AFEC0 is used for everything else.
+ // To reduce noise, we use x16 hardware averaging on AFEC0 and x256 on AFEC1. This is hard coded in file AnalogIn.cpp in project CoreNG.
+ // There is enough time to convert all AFEC0 channels in one tick, but only one AFEC1 channel because of the higher averaging.
+ LegacyAnalogIn::AnalogInStartConversion(0x0FFF | (1u << (uint8_t) filteredAdcChannels[currentFilterNumber]));
+#elif !SAME5x
+ LegacyAnalogIn::AnalogInStartConversion();
+#endif
+}
+
+// Pragma pop_options is not supported on this platform
+//#pragma GCC pop_options
+
+// End
diff --git a/src/Platform/Platform.h b/src/Platform/Platform.h
new file mode 100644
index 00000000..89c41f45
--- /dev/null
+++ b/src/Platform/Platform.h
@@ -0,0 +1,1098 @@
+/****************************************************************************************************
+
+RepRapFirmware - Platform
+
+Platform contains all the code and definitions to deal with machine-dependent things such as control
+pins, bed area, number of extruders, tolerable accelerations and speeds and so on.
+
+No definitions that are system-independent should go in here. Put them in Configuration.h.
+
+-----------------------------------------------------------------------------------------------------
+
+Version 0.3
+
+28 August 2013
+
+Adrian Bowyer
+RepRap Professional Ltd
+http://reprappro.com
+
+Licence: GPL
+
+****************************************************************************************************/
+
+#ifndef PLATFORM_H
+#define PLATFORM_H
+
+#include "RepRapFirmware.h"
+#include "ObjectModel/ObjectModel.h"
+#include "Hardware/IoPorts.h"
+#include "Fans/FansManager.h"
+#include "Heating/TemperatureError.h"
+#include "OutputMemory.h"
+#include "Storage/FileStore.h"
+#include "Storage/FileData.h"
+#include "Storage/MassStorage.h" // must be after Pins.h because it needs NumSdCards defined
+#include "MessageType.h"
+#include "Tools/Spindle.h"
+#include "Endstops/EndstopsManager.h"
+#include <GPIO/GpInPort.h>
+#include <GPIO/GpOutPort.h>
+#include <Comms/AuxDevice.h>
+#include <Comms/PanelDueUpdater.h>
+#include <General/IPAddress.h>
+#include <General/inplace_function.h>
+
+#if defined(DUET_NG)
+# include "DueXn.h"
+#elif defined(DUET_06_085)
+# include "MCP4461/MCP4461.h"
+#elif defined(__ALLIGATOR__)
+# include "DAC/DAC084S085.h" // SPI DAC for motor current vref
+# include "EUI48/EUI48EEPROM.h" // SPI EUI48 mac address EEPROM
+# include "Microstepping.h"
+#elif defined(__LPC17xx__)
+# include "MCP4461/MCP4461.h"
+#endif
+
+#if SUPPORT_CAN_EXPANSION
+# include <RemoteInputHandle.h>
+#endif
+
+constexpr bool FORWARDS = true;
+constexpr bool BACKWARDS = !FORWARDS;
+
+// Define the number of ADC filters and the indices of the extra ones
+// Note, the thermistor code assumes that the first N filters are used by the TEMP0 to TEMP(N-1) thermistor inputs, where N = NumThermistorInputs
+#if HAS_VREF_MONITOR
+constexpr size_t VrefFilterIndex = NumThermistorInputs;
+constexpr size_t VssaFilterIndex = NumThermistorInputs + 1;
+# if HAS_CPU_TEMP_SENSOR && !SAME5x
+constexpr size_t CpuTempFilterIndex = NumThermistorInputs + 2;
+constexpr size_t NumAdcFilters = NumThermistorInputs + 3;
+# else
+constexpr size_t NumAdcFilters = NumThermistorInputs + 2;
+# endif
+#elif HAS_CPU_TEMP_SENSOR && !SAME5x
+constexpr size_t CpuTempFilterIndex = NumThermistorInputs;
+constexpr size_t NumAdcFilters = NumThermistorInputs + 1;
+#else
+constexpr size_t NumAdcFilters = NumThermistorInputs;
+#endif
+
+/**************************************************************************************************/
+
+#if SUPPORT_INKJET
+
+// Inkjet (if any - no inkjet is flagged by INKJET_BITS negative)
+
+const int8_t INKJET_BITS = 12; // How many nozzles? Set to -1 to disable this feature
+const int INKJET_FIRE_MICROSECONDS = 5; // How long to fire a nozzle
+const int INKJET_DELAY_MICROSECONDS = 800; // How long to wait before the next bit
+
+#endif
+
+// Z PROBE
+constexpr unsigned int ZProbeAverageReadings = 8; // We average this number of readings with IR on, and the same number with IR off
+
+// HEATERS - The bed is assumed to be the at index 0
+
+// Define the number of temperature readings we average for each thermistor. This should be a power of 2 and at least 4 ^ AD_OVERSAMPLE_BITS.
+#if SAME70
+// On the SAME70 we read a thermistor on every tick so that we can average a higher number of readings
+// Keep THERMISTOR_AVERAGE_READINGS * NUM_HEATERS * 1ms no greater than HEAT_SAMPLE_TIME or the PIDs won't work well.
+constexpr unsigned int ThermistorAverageReadings = 16;
+#else
+// We read a thermistor on alternate ticks
+// Keep THERMISTOR_AVERAGE_READINGS * NUM_HEATERS * 2ms no greater than HEAT_SAMPLE_TIME or the PIDs won't work well.
+constexpr unsigned int ThermistorAverageReadings = 16;
+#endif
+
+#if SAME5x
+constexpr unsigned int TempSenseAverageReadings = 16;
+#endif
+
+constexpr uint32_t maxPidSpinDelay = 5000; // Maximum elapsed time in milliseconds between successive temp samples by Pid::Spin() permitted for a temp sensor
+
+/****************************************************************************************************/
+
+enum class BoardType : uint8_t
+{
+ Auto = 0,
+#if defined(DUET3MINI) // we use the same values for both v0.2 and v0.4
+ Duet3Mini_Unknown,
+ Duet3Mini_WiFi,
+ Duet3Mini_Ethernet,
+#elif defined(DUET3)
+ Duet3_v06_100 = 1,
+ Duet3_v101 = 2,
+#elif defined(SAME70XPLD)
+ SAME70XPLD_0 = 1
+#elif defined(DUET_NG)
+ DuetWiFi_10 = 1,
+ DuetWiFi_102 = 2,
+ DuetEthernet_10 = 3,
+ DuetEthernet_102 = 4,
+ Duet2SBC_10 = 5,
+ Duet2SBC_102 = 6,
+#elif defined(DUET_M)
+ DuetM_10 = 1,
+#elif defined(DUET_06_085)
+ Duet_06 = 1,
+ Duet_07 = 2,
+ Duet_085 = 3
+#elif defined(__RADDS__)
+ RADDS_15 = 1
+#elif defined(PCCB_10)
+ PCCB_v10 = 1
+#elif defined(PCCB_08) || defined(PCCB_08_X5)
+ PCCB_v08 = 1
+#elif defined(DUET3MINI)
+ Duet_5LC = 1
+#elif defined(__LPC17xx__)
+ Lpc = 1
+#else
+# error Unknown board
+#endif
+};
+
+// Type of an axis. The values must correspond to values of the R parameter in the M584 command.
+enum class AxisWrapType : uint8_t
+{
+ noWrap = 0, // axis does not wrap
+ wrapAt360, // axis wraps, actual position are modulo 360deg
+#if 0 // shortcut axes not implemented yet
+ wrapWithShortcut, // axis wraps, G0 moves are allowed to take the shortest direction
+#endif
+ undefined // this one must be last
+};
+
+/***************************************************************************************************/
+
+// Enumeration to describe various tests we do in response to the M122 command
+enum class DiagnosticTestType : unsigned int
+{
+ PrintTestReport = 1, // run some tests and report the processor ID
+
+ PrintMoves = 100, // print summary of recent moves (only if recording moves was enabled in firmware)
+#ifdef DUET_NG
+ PrintExpanderStatus = 101, // print DueXn expander status
+#endif
+ TimeCalculations = 102, // do a timing test on the square root function and sine/cosine
+ unused1 = 103, // was TimeSinCos
+ TimeSDWrite = 104, // do a write timing test on the SD card
+ PrintObjectSizes = 105, // print the sizes of various objects
+ PrintObjectAddresses = 106, // print the addresses and sizes of various objects
+ TimeCRC32 = 107, // time how long it takes to calculate CRC32
+ TimeGetTimerTicks = 108, // time now long it takes to read the step clock
+
+#ifdef __LPC17xx__
+ PrintBoardConfiguration = 200, // Prints out all pin/values loaded from SDCard to configure board
+#endif
+
+ SetWriteBuffer = 500, // enable/disable the write buffer
+
+ OutputBufferStarvation = 900, // Allocate almost all output buffers to emulate starvation
+
+ TestWatchdog = 1001, // test that we get a watchdog reset if the tick interrupt stops
+ TestSpinLockup = 1002, // test that we get a software reset if a Spin() function takes too long
+ TestSerialBlock = 1003, // test what happens when we write a blocking message via debugPrintf()
+ DivideByZero = 1004, // do an integer divide by zero to test exception handling
+ UnalignedMemoryAccess = 1005, // do an unaligned memory access to test exception handling
+ BusFault = 1006, // generate a bus fault
+ AccessMemory = 1007 // read or write memory
+};
+
+/***************************************************************************************************************/
+
+// Class to perform averaging of values read from the ADC
+// numAveraged should be a power of 2 for best efficiency
+template<size_t numAveraged> class AveragingFilter
+{
+public:
+ AveragingFilter() noexcept
+ {
+ Init(0);
+ }
+
+ void Init(uint16_t val) volatile noexcept
+ {
+ const irqflags_t flags = cpu_irq_save();
+ sum = (uint32_t)val * (uint32_t)numAveraged;
+ index = 0;
+ isValid = false;
+ for (size_t i = 0; i < numAveraged; ++i)
+ {
+ readings[i] = val;
+ }
+ cpu_irq_restore(flags);
+ }
+
+ // Call this to put a new reading into the filter
+ // This is only called by the ISR, so it not declared volatile to make it faster
+ void ProcessReading(uint16_t r) noexcept
+ {
+ sum = sum - readings[index] + r;
+ readings[index] = r;
+ ++index;
+ if (index == numAveraged)
+ {
+ index = 0;
+ isValid = true;
+ }
+ }
+
+ // Return the raw sum
+ uint32_t GetSum() const volatile noexcept
+ {
+ return sum;
+ }
+
+ // Return true if we have a valid average
+ bool IsValid() const volatile noexcept
+ {
+ return isValid;
+ }
+
+ // Get the latest reading
+ uint16_t GetLatestReading() const volatile noexcept
+ {
+ size_t indexOfLastReading = index; // capture volatile variable
+ indexOfLastReading = (indexOfLastReading == 0) ? numAveraged - 1 : indexOfLastReading - 1;
+ return readings[indexOfLastReading];
+ }
+
+ static constexpr size_t NumAveraged() noexcept { return numAveraged; }
+
+ // Function used as an ADC callback to feed a result into an averaging filter
+ static void CallbackFeedIntoFilter(CallbackParameter cp, uint16_t val);
+
+private:
+ uint16_t readings[numAveraged];
+ size_t index;
+ uint32_t sum;
+ bool isValid;
+ //invariant(sum == + over readings)
+ //invariant(index < numAveraged)
+};
+
+template<size_t numAveraged> void AveragingFilter<numAveraged>::CallbackFeedIntoFilter(CallbackParameter cp, uint16_t val)
+{
+ static_cast<AveragingFilter<numAveraged>*>(cp.vp)->ProcessReading(val);
+}
+
+typedef AveragingFilter<ThermistorAverageReadings> ThermistorAveragingFilter;
+typedef AveragingFilter<ZProbeAverageReadings> ZProbeAveragingFilter;
+
+#if SAME5x
+typedef AveragingFilter<TempSenseAverageReadings> TempSenseAveragingFilter;
+#endif
+
+// Enumeration of error condition bits
+enum class ErrorCode : uint32_t
+{
+ BadTemp = 1u << 0,
+ BadMove = 1u << 1,
+ OutputStarvation = 1u << 2,
+ OutputStackOverflow = 1u << 3,
+ HsmciTimeout = 1u << 4
+};
+
+struct AxisDriversConfig
+{
+ AxisDriversConfig() noexcept { numDrivers = 0; }
+ DriversBitmap GetDriversBitmap() const noexcept;
+
+ uint8_t numDrivers; // Number of drivers assigned to each axis
+ DriverId driverNumbers[MaxDriversPerAxis]; // The driver numbers assigned - only the first numDrivers are meaningful
+};
+
+// The main class that defines the RepRap machine for the benefit of the other classes
+class Platform INHERIT_OBJECT_MODEL
+{
+public:
+ // Enumeration to describe the status of a drive
+ enum class DriverStatus : uint8_t { disabled, idle, enabled };
+
+ Platform() noexcept;
+ Platform(const Platform&) = delete;
+
+//-------------------------------------------------------------------------------------------------------------
+
+ // These are the functions that form the interface between Platform and the rest of the firmware.
+
+ void Init() noexcept; // Set the machine up after a restart. If called subsequently this should set the machine up as if
+ // it has just been restarted; it can do this by executing an actual restart if you like, but beware the loop of death...
+ void Spin() noexcept; // This gets called in the main loop and should do any housekeeping needed
+ void Exit() noexcept; // Shut down tidily. Calling Init after calling this should reset to the beginning
+
+ void Diagnostics(MessageType mtype) noexcept;
+ GCodeResult DiagnosticTest(GCodeBuffer& gb, const StringRef& reply, OutputBuffer*& buf, unsigned int d) THROWS(GCodeException);
+ static bool WasDeliberateError() noexcept { return deliberateError; }
+ void LogError(ErrorCode e) noexcept { errorCodeBits |= (uint32_t)e; }
+
+ bool AtxPower() const noexcept;
+ void AtxPowerOn() noexcept;
+ void AtxPowerOff(bool defer) noexcept;
+
+ BoardType GetBoardType() const noexcept { return board; }
+ void SetBoardType(BoardType bt) noexcept;
+ const char* GetElectronicsString() const noexcept;
+ const char* GetBoardString() const noexcept;
+
+#if SUPPORT_OBJECT_MODEL
+ size_t GetNumGpInputsToReport() const noexcept;
+ size_t GetNumGpOutputsToReport() const noexcept;
+#endif
+
+#if defined(DUET_NG) || defined(DUET3MINI)
+ bool IsDuetWiFi() const noexcept;
+#endif
+
+#ifdef DUET_NG
+ bool IsDueXPresent() const noexcept { return expansionBoard != ExpansionBoardType::none; }
+ const char *GetBoardName() const noexcept;
+ const char *GetBoardShortName() const noexcept;
+#endif
+
+ const MacAddress& GetDefaultMacAddress() const noexcept { return defaultMacAddress; }
+
+ // Timing
+ void Tick() noexcept SPEED_CRITICAL; // Process a systick interrupt
+
+ // Real-time clock
+ bool IsDateTimeSet() const noexcept { return realTime != 0; } // Has the RTC been set yet?
+ time_t GetDateTime() const noexcept { return realTime; } // Retrieves the current RTC datetime
+ bool GetDateTime(tm& rslt) const noexcept { return gmtime_r(&realTime, &rslt) != nullptr && realTime != 0; }
+ // Retrieves the broken-down current RTC datetime and returns true if it's valid
+ bool SetDateTime(time_t time) noexcept; // Sets the current RTC date and time or returns false on error
+
+ // Communications and data storage
+ void AppendUsbReply(OutputBuffer *buffer) noexcept;
+ void AppendAuxReply(size_t auxNumber, OutputBuffer *buf, bool rawMessage) noexcept;
+ void AppendAuxReply(size_t auxNumber, const char *msg, bool rawMessage) noexcept;
+
+ void ResetChannel(size_t chan) noexcept; // Re-initialise a serial channel
+ bool IsAuxEnabled(size_t auxNumber) const noexcept; // Any device on the AUX line?
+ void EnableAux(size_t auxNumber) noexcept;
+ bool IsAuxRaw(size_t auxNumber) const noexcept;
+ void SetAuxRaw(size_t auxNumber, bool raw) noexcept;
+#if HAS_AUX_DEVICES
+ PanelDueUpdater* GetPanelDueUpdater() noexcept { return panelDueUpdater; }
+ void InitPanelDueUpdater() noexcept;
+#endif
+
+ void SetIPAddress(IPAddress ip) noexcept;
+ IPAddress GetIPAddress() const noexcept;
+ void SetNetMask(IPAddress nm) noexcept;
+ IPAddress NetMask() const noexcept;
+ void SetGateWay(IPAddress gw) noexcept;
+ IPAddress GateWay() const noexcept;
+ void SetBaudRate(size_t chan, uint32_t br) noexcept;
+ uint32_t GetBaudRate(size_t chan) const noexcept;
+ void SetCommsProperties(size_t chan, uint32_t cp) noexcept;
+ uint32_t GetCommsProperties(size_t chan) const noexcept;
+
+#if defined(__ALLIGATOR__)
+ // Mac address from EUI48 EEPROM
+ EUI48EEPROM eui48MacAddress;
+#endif
+
+ // File functions
+#if HAS_MASS_STORAGE || HAS_LINUX_INTERFACE
+ FileStore* OpenFile(const char* folder, const char* fileName, OpenMode mode, uint32_t preAllocSize = 0) const noexcept;
+ bool FileExists(const char* folder, const char *filename) const noexcept;
+#endif
+#if HAS_MASS_STORAGE
+ bool Delete(const char* folder, const char *filename) const noexcept;
+ bool DirectoryExists(const char *folder, const char *dir) const noexcept;
+
+ const char* GetWebDir() const noexcept; // Where the html etc files are
+ const char* GetGCodeDir() const noexcept; // Where the gcodes are
+ const char* GetMacroDir() const noexcept; // Where the user-defined macros are
+
+ // Functions to work with the system files folder
+ GCodeResult SetSysDir(const char* dir, const StringRef& reply) noexcept; // Set the system files path
+ bool SysFileExists(const char *filename) const noexcept;
+ FileStore* OpenSysFile(const char *filename, OpenMode mode) const noexcept;
+ bool DeleteSysFile(const char *filename) const noexcept;
+ bool MakeSysFileName(const StringRef& result, const char *filename) const noexcept;
+ void AppendSysDir(const StringRef & path) const noexcept;
+ ReadLockedPointer<const char> GetSysDir() const noexcept; // where the system files are
+#endif
+
+ // Message output (see MessageType for further details)
+ void Message(MessageType type, const char *message) noexcept;
+ void Message(MessageType type, OutputBuffer *buffer) noexcept;
+ void MessageF(MessageType type, const char *fmt, ...) noexcept __attribute__ ((format (printf, 3, 4)));
+ void MessageF(MessageType type, const char *fmt, va_list vargs) noexcept;
+ void DebugMessage(const char *fmt, va_list vargs) noexcept;
+ bool FlushMessages() noexcept; // Flush messages to USB and aux, returning true if there is more to send
+ void SendAlert(MessageType mt, const char *message, const char *title, int sParam, float tParam, AxesBitmap controls) noexcept;
+ void StopLogging() noexcept;
+
+ // Movement
+ void EmergencyStop() noexcept;
+ size_t GetNumActualDirectDrivers() const noexcept;
+ void SetDirection(size_t axisOrExtruder, bool direction) noexcept;
+ void SetDirectionValue(size_t driver, bool dVal) noexcept;
+ bool GetDirectionValue(size_t driver) const noexcept;
+ void SetDriverAbsoluteDirection(size_t driver, bool dVal) noexcept;
+ void SetEnableValue(size_t driver, int8_t eVal) noexcept;
+ int8_t GetEnableValue(size_t driver) const noexcept;
+ void EnableDrivers(size_t axisOrExtruder) noexcept;
+ void EnableOneLocalDriver(size_t driver, float requiredCurrent) noexcept;
+ void DisableAllDrivers() noexcept;
+ void DisableDrivers(size_t axisOrExtruder) noexcept;
+ void DisableOneLocalDriver(size_t driver) noexcept;
+ void EmergencyDisableDrivers() noexcept;
+ void SetDriversIdle() noexcept;
+ GCodeResult SetMotorCurrent(size_t axisOrExtruder, float current, int code, const StringRef& reply) noexcept;
+ float GetMotorCurrent(size_t axisOrExtruder, int code) const noexcept;
+ void SetIdleCurrentFactor(float f) noexcept;
+ float GetIdleCurrentFactor() const noexcept { return idleCurrentFactor; }
+ bool SetDriverMicrostepping(size_t driver, unsigned int microsteps, int mode) noexcept;
+ bool SetMicrostepping(size_t axisOrExtruder, int microsteps, bool mode, const StringRef& reply) noexcept;
+ unsigned int GetMicrostepping(size_t axisOrExtruder, bool& interpolation) const noexcept;
+ void SetDriverStepTiming(size_t driver, const float microseconds[4]) noexcept;
+ bool GetDriverStepTiming(size_t driver, float microseconds[4]) const noexcept;
+ float DriveStepsPerUnit(size_t axisOrExtruder) const noexcept;
+ const float *GetDriveStepsPerUnit() const noexcept
+ { return driveStepsPerUnit; }
+ void SetDriveStepsPerUnit(size_t axisOrExtruder, float value, uint32_t requestedMicrostepping) noexcept;
+ float Acceleration(size_t axisOrExtruder) const noexcept;
+ const float* Accelerations() const noexcept;
+ void SetAcceleration(size_t axisOrExtruder, float value) noexcept;
+ float MaxFeedrate(size_t axisOrExtruder) const noexcept;
+ const float* MaxFeedrates() const noexcept { return maxFeedrates; }
+ void SetMaxFeedrate(size_t axisOrExtruder, float value) noexcept;
+ float MinMovementSpeed() const noexcept { return minimumMovementSpeed; }
+ void SetMinMovementSpeed(float value) noexcept { minimumMovementSpeed = max<float>(value, 0.01); }
+ float GetInstantDv(size_t axis) const noexcept;
+ void SetInstantDv(size_t axis, float value) noexcept;
+ float AxisMaximum(size_t axis) const noexcept;
+ void SetAxisMaximum(size_t axis, float value, bool byProbing) noexcept;
+ float AxisMinimum(size_t axis) const noexcept;
+ void SetAxisMinimum(size_t axis, float value, bool byProbing) noexcept;
+ float AxisTotalLength(size_t axis) const noexcept;
+
+ float GetPressureAdvance(size_t extruder) const noexcept;
+ GCodeResult SetPressureAdvance(float advance, GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException);
+
+ inline AxesBitmap GetLinearAxes() const noexcept { return linearAxes; }
+ inline AxesBitmap GetRotationalAxes() const noexcept { return rotationalAxes; }
+ inline bool IsAxisRotational(size_t axis) const noexcept { return rotationalAxes.IsBitSet(axis); }
+ inline bool IsAxisContinuous(size_t axis) const noexcept { return continuousAxes.IsBitSet(axis); }
+#if 0 // shortcut axes not implemented yet
+ inline bool IsAxisShortcutAllowed(size_t axis) const noexcept { return shortcutAxes.IsBitSet(axis); }
+#endif
+
+ void SetAxisType(size_t axis, AxisWrapType wrapType, bool isNistRotational) noexcept;
+
+ const AxisDriversConfig& GetAxisDriversConfig(size_t axis) const noexcept
+ pre(axis < MaxAxes)
+ { return axisDrivers[axis]; }
+ void SetAxisDriversConfig(size_t axis, size_t numValues, const DriverId driverNumbers[]) noexcept
+ pre(axis < MaxAxes);
+ DriverId GetExtruderDriver(size_t extruder) const noexcept
+ pre(extruder < MaxExtruders)
+ { return extruderDrivers[extruder]; }
+ void SetExtruderDriver(size_t extruder, DriverId driver) noexcept
+ pre(extruder < MaxExtruders);
+ uint32_t GetDriversBitmap(size_t axisOrExtruder) const noexcept // get the bitmap of driver step bits for this axis or extruder
+ pre(axisOrExtruder < MaxAxesPlusExtruders + NumLocalDrivers)
+ { return driveDriverBits[axisOrExtruder]; }
+ uint32_t GetSlowDriversBitmap() const noexcept { return slowDriversBitmap; }
+ uint32_t GetSlowDriverStepHighClocks() const noexcept { return slowDriverStepTimingClocks[0]; }
+ uint32_t GetSlowDriverStepLowClocks() const noexcept { return slowDriverStepTimingClocks[1]; }
+ uint32_t GetSlowDriverDirSetupClocks() const noexcept { return slowDriverStepTimingClocks[2]; }
+ uint32_t GetSlowDriverDirHoldClocks() const noexcept { return slowDriverStepTimingClocks[3]; }
+ uint32_t GetSteppingEnabledDrivers() const noexcept { return steppingEnabledDriversBitmap; }
+ void DisableSteppingDriver(uint8_t driver) noexcept { steppingEnabledDriversBitmap &= ~StepPins::CalcDriverBitmap(driver); }
+ void EnableAllSteppingDrivers() noexcept { steppingEnabledDriversBitmap = 0xFFFFFFFF; }
+
+#if SUPPORT_NONLINEAR_EXTRUSION
+ bool GetExtrusionCoefficients(size_t extruder, float& a, float& b, float& limit) const noexcept;
+ void SetNonlinearExtrusion(size_t extruder, float a, float b, float limit) noexcept;
+#endif
+
+ // Endstops and Z probe
+ EndstopsManager& GetEndstops() noexcept { return endstops; }
+ ReadLockedPointer<ZProbe> GetZProbeOrDefault(size_t probeNumber) noexcept { return endstops.GetZProbeOrDefault(probeNumber); }
+ void InitZProbeFilters() noexcept;
+ const volatile ZProbeAveragingFilter& GetZProbeOnFilter() const noexcept { return zProbeOnFilter; }
+ const volatile ZProbeAveragingFilter& GetZProbeOffFilter() const noexcept{ return zProbeOffFilter; }
+
+#if HAS_MASS_STORAGE
+ bool WritePlatformParameters(FileStore *f, bool includingG31) const noexcept;
+#endif
+
+ // Heat and temperature
+ volatile ThermistorAveragingFilter& GetAdcFilter(size_t channel) noexcept
+ pre(channel < ARRAY_SIZE(adcFilters))
+ {
+ return adcFilters[channel];
+ }
+
+ int GetAveragingFilterIndex(const IoPort&) const noexcept;
+
+ void UpdateConfiguredHeaters() noexcept;
+
+ // AUX device
+ void PanelDueBeep(int freq, int ms) noexcept;
+ void SendPanelDueMessage(size_t auxNumber, const char* msg) noexcept;
+
+ // Hotend configuration
+ float GetFilamentWidth() const noexcept;
+ void SetFilamentWidth(float width) noexcept;
+ float GetNozzleDiameter() const noexcept;
+ void SetNozzleDiameter(float diameter) noexcept;
+
+ // Fire the inkjet (if any) in the given pattern
+ // If there is no inkjet false is returned; if there is one this returns true
+ // So you can test for inkjet presence with if(platform->Inkjet(0))
+ bool Inkjet(int bitPattern) noexcept;
+
+ // MCU temperature
+#if HAS_CPU_TEMP_SENSOR
+ MinMaxCurrent GetMcuTemperatures() const noexcept;
+ void SetMcuTemperatureAdjust(float v) noexcept { mcuTemperatureAdjust = v; }
+ float GetMcuTemperatureAdjust() const noexcept { return mcuTemperatureAdjust; }
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+ // Power in voltage
+ MinMaxCurrent GetPowerVoltages() const noexcept;
+ float GetCurrentPowerVoltage() const noexcept;
+ bool IsPowerOk() const noexcept;
+ void DisableAutoSave() noexcept;
+ void EnableAutoSave(float saveVoltage, float resumeVoltage) noexcept;
+ bool GetAutoSaveSettings(float& saveVoltage, float&resumeVoltage) noexcept;
+#endif
+
+#if HAS_12V_MONITOR
+ // 12V rail voltage
+ MinMaxCurrent GetV12Voltages() const noexcept;
+ float GetCurrentV12Voltage() const noexcept;
+#endif
+
+#if HAS_SMART_DRIVERS
+ float GetTmcDriversTemperature(unsigned int boardNumber) const noexcept;
+ void DriverCoolingFansOnOff(DriverChannelsBitmap driverChannelsMonitored, bool on) noexcept;
+ unsigned int GetNumSmartDrivers() const noexcept { return numSmartDrivers; }
+#endif
+
+#if HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR
+ bool HasVinPower() const noexcept;
+#else
+ bool HasVinPower() const noexcept { return true; }
+#endif
+
+#if HAS_STALL_DETECT
+ GCodeResult ConfigureStallDetection(GCodeBuffer& gb, const StringRef& reply, OutputBuffer *& buf) THROWS(GCodeException);
+#endif
+
+ // Logging support
+ const char *GetLogLevel() const noexcept;
+#if HAS_MASS_STORAGE
+ GCodeResult ConfigureLogging(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException);
+ const char *GetLogFileName() const noexcept;
+#endif
+
+ // Ancillary PWM
+ GCodeResult GetSetAncillaryPwm(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException);
+ void ExtrudeOn() noexcept;
+ void ExtrudeOff() noexcept;
+
+ // CNC and laser support
+ Spindle& AccessSpindle(size_t slot) noexcept { return spindles[slot]; }
+
+#if SUPPORT_LASER
+ void SetLaserPwm(Pwm_t pwm) noexcept;
+ float GetLaserPwm() const noexcept; // return laser PWM in 0..1
+ bool AssignLaserPin(GCodeBuffer& gb, const StringRef& reply);
+ void SetLaserPwmFrequency(PwmFrequency freq) noexcept;
+#endif
+
+ // Misc
+ GCodeResult ConfigurePort(GCodeBuffer& gb, const StringRef& reply) THROWS(GCodeException);
+
+ GpOutputPort& GetGpOutPort(size_t gpoutPortNumber) noexcept
+ pre(gpioPortNumber < MaxGpOutPorts) { return gpoutPorts[gpoutPortNumber]; }
+ const GpInputPort& GetGpInPort(size_t gpinPortNumber) const noexcept
+ pre(gpinPortNumber < MaxGpInPorts) { return gpinPorts[gpinPortNumber]; }
+
+#if MCU_HAS_UNIQUE_ID
+ uint32_t Random() noexcept;
+ const char *GetUniqueIdString() const noexcept { return uniqueIdChars; }
+#endif
+
+#if SUPPORT_CAN_EXPANSION
+ void HandleRemoteGpInChange(CanAddress src, uint8_t handleMajor, uint8_t handleMinor, bool state) noexcept;
+ GCodeResult UpdateRemoteStepsPerMmAndMicrostepping(AxesBitmap axesAndExtruders, const StringRef& reply) noexcept;
+#endif
+
+#if SUPPORT_REMOTE_COMMANDS
+ GCodeResult EutHandleM950Gpio(const CanMessageGeneric& msg, const StringRef& reply) noexcept;
+ GCodeResult EutHandleGpioWrite(const CanMessageWriteGpio& msg, const StringRef& reply) noexcept;
+ GCodeResult EutSetMotorCurrents(const CanMessageMultipleDrivesRequest<float>& msg, size_t dataLength, const StringRef& reply) noexcept;
+ GCodeResult EutSetStepsPerMmAndMicrostepping(const CanMessageMultipleDrivesRequest<StepsPerUnitAndMicrostepping>& msg, size_t dataLength, const StringRef& reply) noexcept;
+ GCodeResult EutHandleSetDriverStates(const CanMessageMultipleDrivesRequest<DriverStateControl>& msg, const StringRef& reply) noexcept;
+ float EutGetRemotePressureAdvance(size_t driver) const noexcept;
+ GCodeResult EutSetRemotePressureAdvance(const CanMessageMultipleDrivesRequest<float>& msg, size_t dataLength, const StringRef& reply) noexcept;
+ GCodeResult EutProcessM569(const CanMessageGeneric& msg, const StringRef& reply) noexcept;
+#endif
+
+#if VARIABLE_NUM_DRIVERS
+ void AdjustNumDrivers(size_t numDriversNotAvailable) noexcept;
+#endif
+
+protected:
+ DECLARE_OBJECT_MODEL
+ OBJECT_MODEL_ARRAY(axisDrivers)
+ OBJECT_MODEL_ARRAY(workplaceOffsets)
+
+private:
+ const char* InternalGetSysDir() const noexcept; // where the system files are - not thread-safe!
+
+ void RawMessage(MessageType type, const char *message) noexcept; // called by Message after handling error/warning flags
+
+ float GetCpuTemperature() const noexcept;
+
+#if SUPPORT_CAN_EXPANSION
+ void IterateDrivers(size_t axisOrExtruder, stdext::inplace_function<void(uint8_t) /*noexcept*/ > localFunc, stdext::inplace_function<void(DriverId) /*noexcept*/ > remoteFunc) noexcept;
+ void IterateLocalDrivers(size_t axisOrExtruder, stdext::inplace_function<void(uint8_t) /*noexcept*/ > func) noexcept { IterateDrivers(axisOrExtruder, func, [](DriverId) noexcept {}); }
+ void IterateRemoteDrivers(size_t axisOrExtruder, stdext::inplace_function<void(DriverId) /*noexcept*/ > func) noexcept { IterateDrivers(axisOrExtruder, [](uint8_t) noexcept {}, func); }
+#else
+ void IterateDrivers(size_t axisOrExtruder, stdext::inplace_function<void(uint8_t) /*noexcept*/ > localFunc) noexcept;
+ void IterateLocalDrivers(size_t axisOrExtruder, stdext::inplace_function<void(uint8_t) /*noexcept*/ > func) noexcept { IterateDrivers(axisOrExtruder, func); }
+#endif
+
+#if SUPPORT_REMOTE_COMMANDS
+ float remotePressureAdvance[NumDirectDrivers];
+#endif
+
+#if HAS_SMART_DRIVERS
+ void ReportDrivers(MessageType mt, DriversBitmap& whichDrivers, const char* text, bool& reported) noexcept;
+#endif
+
+#if HAS_MASS_STORAGE
+ // Logging
+ Logger *logger;
+#endif
+
+ // Network
+ IPAddress ipAddress;
+ IPAddress netMask;
+ IPAddress gateWay;
+ MacAddress defaultMacAddress;
+
+ // Board and processor
+#if MCU_HAS_UNIQUE_ID
+ void ReadUniqueId();
+ uint32_t uniqueId[5];
+ char uniqueIdChars[30 + 5 + 1]; // 30 characters, 5 separators, 1 null terminator
+#endif
+
+ BoardType board;
+
+#ifdef DUET_NG
+ ExpansionBoardType expansionBoard;
+#endif
+
+ bool active;
+ uint32_t errorCodeBits;
+
+ void InitialiseInterrupts() noexcept;
+
+ // Drives
+ void UpdateMotorCurrent(size_t driver, float current) noexcept;
+ void SetDriverDirection(uint8_t driver, bool direction) noexcept
+ pre(driver < DRIVES);
+
+#if VARIABLE_NUM_DRIVERS && SUPPORT_12864_LCD
+ size_t numActualDirectDrivers;
+#endif
+
+ bool directions[NumDirectDrivers];
+ int8_t enableValues[NumDirectDrivers];
+
+ float motorCurrents[MaxAxesPlusExtruders]; // the normal motor current for each stepper driver
+ float motorCurrentFraction[MaxAxesPlusExtruders]; // the percentages of normal motor current that each driver is set to
+ float standstillCurrentPercent[MaxAxesPlusExtruders]; // the percentages of normal motor current that each driver uses when in standstill
+ uint16_t microstepping[MaxAxesPlusExtruders]; // the microstepping used for each axis or extruder, top bit is set if interpolation enabled
+
+ volatile DriverStatus driverState[MaxAxesPlusExtruders];
+ float maxFeedrates[MaxAxesPlusExtruders];
+ float accelerations[MaxAxesPlusExtruders];
+ float driveStepsPerUnit[MaxAxesPlusExtruders];
+ float instantDvs[MaxAxesPlusExtruders];
+ uint32_t driveDriverBits[MaxAxesPlusExtruders + NumDirectDrivers];
+ // the bitmap of local driver port bits for each axis or extruder, followed by the bitmaps for the individual Z motors
+ AxisDriversConfig axisDrivers[MaxAxes]; // the driver numbers assigned to each axis
+ AxesBitmap linearAxes; // axes that behave like linear axes w.r.t. feedrate handling
+ AxesBitmap rotationalAxes; // axes that behave like rotational axes w.r.t. feedrate handling
+ AxesBitmap continuousAxes; // axes that wrap modulo 360
+#if 0 // shortcut axes not implemented yet
+ AxesBitmap shortcutAxes; // axes that wrap modulo 360 and for which G0 may choose the shortest direction
+#endif
+
+ float pressureAdvance[MaxExtruders];
+#if SUPPORT_NONLINEAR_EXTRUSION
+ float nonlinearExtrusionA[MaxExtruders], nonlinearExtrusionB[MaxExtruders], nonlinearExtrusionLimit[MaxExtruders];
+#endif
+
+ DriverId extruderDrivers[MaxExtruders]; // the driver number assigned to each extruder
+ uint32_t slowDriverStepTimingClocks[4]; // minimum step high, step low, dir setup and dir hold timing for slow drivers
+ uint32_t slowDriversBitmap; // bitmap of driver port bits that need extended step pulse timing
+ uint32_t steppingEnabledDriversBitmap; // mask of driver bits that we haven't disabled temporarily
+ float idleCurrentFactor;
+ float minimumMovementSpeed;
+
+#if HAS_SMART_DRIVERS
+ size_t numSmartDrivers; // the number of TMC drivers we have, the remaining are simple enable/step/dir drivers
+ DriversBitmap temperatureShutdownDrivers, temperatureWarningDrivers, shortToGroundDrivers;
+ DriversBitmap openLoadADrivers, openLoadBDrivers, notOpenLoadADrivers, notOpenLoadBDrivers;
+ MillisTimer openLoadATimer, openLoadBTimer;
+ MillisTimer driversFanTimers[NumTmcDriversSenseChannels]; // driver cooling fan timers
+ uint8_t nextDriveToPoll;
+#endif
+
+ bool driversPowered;
+
+#if HAS_SMART_DRIVERS && (HAS_VOLTAGE_MONITOR || HAS_12V_MONITOR)
+ bool warnDriversNotPowered;
+#endif
+
+#if HAS_STALL_DETECT
+ DriversBitmap logOnStallDrivers, pauseOnStallDrivers, rehomeOnStallDrivers;
+ DriversBitmap stalledDrivers, stalledDriversToLog, stalledDriversToPause, stalledDriversToRehome;
+#endif
+
+#if defined(__LPC17xx__)
+ MCP4461 mcp4451;// works for 5561 (only volatile setting commands)
+#endif
+
+ // Endstops
+ EndstopsManager endstops;
+
+ // Z probe
+ volatile ZProbeAveragingFilter zProbeOnFilter; // Z probe readings we took with the IR turned on
+ volatile ZProbeAveragingFilter zProbeOffFilter; // Z probe readings we took with the IR turned off
+
+ // GPIO pins
+ GpOutputPort gpoutPorts[MaxGpOutPorts];
+ GpInputPort gpinPorts[MaxGpInPorts];
+
+ // Thermistors and temperature monitoring
+ volatile ThermistorAveragingFilter adcFilters[NumAdcFilters]; // ADC reading averaging filters
+
+#if HAS_CPU_TEMP_SENSOR
+ float highestMcuTemperature, lowestMcuTemperature;
+ float mcuTemperatureAdjust;
+# if SAME5x
+ TempSenseAveragingFilter tpFilter, tcFilter;
+ int32_t tempCalF1, tempCalF2, tempCalF3, tempCalF4; // temperature calibration factors
+ void TemperatureCalibrationInit() noexcept;
+# endif
+#endif
+
+ // Axes and endstops
+ float axisMaxima[MaxAxes];
+ float axisMinima[MaxAxes];
+ AxesBitmap axisMinimaProbed, axisMaximaProbed;
+
+#if HAS_MASS_STORAGE
+ static bool WriteAxisLimits(FileStore *f, AxesBitmap axesProbed, const float limits[MaxAxes], int sParam) noexcept;
+#endif
+
+ // Heaters
+ HeatersBitmap configuredHeaters; // bitmap of all real heaters in use
+
+ // Fans
+ uint32_t lastFanCheckTime;
+
+ // Serial/USB
+ uint32_t baudRates[NumSerialChannels];
+ uint8_t commsParams[NumSerialChannels];
+
+ volatile OutputStack usbOutput;
+ Mutex usbMutex;
+
+#if HAS_AUX_DEVICES
+ AuxDevice auxDevices[NumSerialChannels - 1];
+ PanelDueUpdater* panelDueUpdater;
+#endif
+
+ // Files
+#if HAS_MASS_STORAGE
+ const char *sysDir;
+ mutable ReadWriteLock sysDirLock;
+#endif
+
+ // Data used by the tick interrupt handler
+ AnalogChannelNumber filteredAdcChannels[NumAdcFilters];
+ AnalogChannelNumber zProbeAdcChannel;
+ uint8_t tickState;
+ size_t currentFilterNumber;
+ int debugCode;
+
+ // Hotend configuration
+ float filamentWidth;
+ float nozzleDiameter;
+
+ // Power monitoring
+#if HAS_VOLTAGE_MONITOR
+ AnalogChannelNumber vInMonitorAdcChannel;
+ volatile uint16_t currentVin, highestVin, lowestVin;
+ uint16_t lastVinUnderVoltageValue, lastVinOverVoltageValue;
+ uint16_t autoPauseReading, autoResumeReading;
+ uint32_t numVinUnderVoltageEvents, previousVinUnderVoltageEvents;
+ volatile uint32_t numVinOverVoltageEvents, previousVinOverVoltageEvents;
+ bool autoSaveEnabled;
+
+ enum class AutoSaveState : uint8_t
+ {
+ starting = 0,
+ normal,
+ autoPaused
+ };
+ AutoSaveState autoSaveState;
+#endif
+
+#if HAS_12V_MONITOR
+ AnalogChannelNumber v12MonitorAdcChannel;
+ volatile uint16_t currentV12, highestV12, lowestV12;
+ uint16_t lastV12UnderVoltageValue;
+ uint32_t numV12UnderVoltageEvents, previousV12UnderVoltageEvents;
+#endif
+
+ uint32_t lastWarningMillis; // When we last sent a warning message
+
+ // RTC
+ time_t realTime; // the current date/time, or zero if never set
+ uint32_t timeLastUpdatedMillis; // the milliseconds counter when we last incremented the time
+
+ // CNC and laser support
+ Spindle spindles[MaxSpindles];
+ float extrusionAncilliaryPwmValue;
+ PwmPort extrusionAncilliaryPwmPort;
+
+#if SUPPORT_LASER
+ PwmPort laserPort;
+ float lastLaserPwm;
+#endif
+
+ // Power on/off
+ bool deferredPowerDown;
+
+ // Misc
+ static bool deliberateError; // true if we deliberately caused an exception for testing purposes. Must be static in case of exception during startup.
+};
+
+#if HAS_MASS_STORAGE
+
+// Where the htm etc files are
+inline const char* Platform::GetWebDir() const noexcept
+{
+ return WEB_DIR;
+}
+
+// Where the gcodes are
+inline const char* Platform::GetGCodeDir() const noexcept
+{
+ return GCODE_DIR;
+}
+
+inline const char* Platform::GetMacroDir() const noexcept
+{
+ return MACRO_DIR;
+}
+
+#endif
+
+//*****************************************************************************************************************
+
+// Drive the RepRap machine - Movement
+
+inline float Platform::DriveStepsPerUnit(size_t drive) const noexcept
+{
+ return driveStepsPerUnit[drive];
+}
+
+inline float Platform::Acceleration(size_t drive) const noexcept
+{
+ return accelerations[drive];
+}
+
+inline const float* Platform::Accelerations() const noexcept
+{
+ return accelerations;
+}
+
+inline void Platform::SetAcceleration(size_t drive, float value) noexcept
+{
+ accelerations[drive] = max<float>(value, 1.0); // don't allow zero or negative
+}
+
+inline float Platform::MaxFeedrate(size_t drive) const noexcept
+{
+ return maxFeedrates[drive];
+}
+
+inline void Platform::SetMaxFeedrate(size_t drive, float value) noexcept
+{
+ maxFeedrates[drive] = max<float>(value, minimumMovementSpeed); // don't allow zero or negative, but do allow small values
+}
+
+inline float Platform::GetInstantDv(size_t drive) const noexcept
+{
+ return instantDvs[drive];
+}
+
+inline void Platform::SetInstantDv(size_t drive, float value) noexcept
+{
+ instantDvs[drive] = max<float>(value, 0.1); // don't allow zero or negative values, they causes Move to loop indefinitely
+}
+
+inline size_t Platform::GetNumActualDirectDrivers() const noexcept
+{
+#if VARIABLE_NUM_DRIVERS
+ return numActualDirectDrivers;
+#else
+ return NumDirectDrivers;
+#endif
+}
+
+#if VARIABLE_NUM_DRIVERS
+
+inline void Platform::AdjustNumDrivers(size_t numDriversNotAvailable) noexcept
+{
+ numActualDirectDrivers = NumDirectDrivers - numDriversNotAvailable;
+}
+
+#endif
+
+inline void Platform::SetDirectionValue(size_t drive, bool dVal) noexcept
+{
+ directions[drive] = dVal;
+}
+
+inline bool Platform::GetDirectionValue(size_t drive) const noexcept
+{
+ return directions[drive];
+}
+
+inline void Platform::SetDriverDirection(uint8_t driver, bool direction) noexcept
+{
+ if (driver < GetNumActualDirectDrivers())
+ {
+ const bool d = (direction == FORWARDS) ? directions[driver] : !directions[driver];
+#if SAME5x
+ IoPort::WriteDigital(DIRECTION_PINS[driver], d);
+#else
+ digitalWrite(DIRECTION_PINS[driver], d);
+#endif
+ }
+}
+
+inline void Platform::SetDriverAbsoluteDirection(size_t driver, bool direction) noexcept
+{
+ if (driver < GetNumActualDirectDrivers())
+ {
+#if SAME5x
+ IoPort::WriteDigital(DIRECTION_PINS[driver], direction);
+#else
+ digitalWrite(DIRECTION_PINS[driver], direction);
+#endif
+ }
+}
+
+inline int8_t Platform::GetEnableValue(size_t driver) const noexcept
+{
+ return enableValues[driver];
+}
+
+inline float Platform::AxisMaximum(size_t axis) const noexcept
+{
+ return axisMaxima[axis];
+}
+
+inline float Platform::AxisMinimum(size_t axis) const noexcept
+{
+ return axisMinima[axis];
+}
+
+inline float Platform::AxisTotalLength(size_t axis) const noexcept
+{
+ return axisMaxima[axis] - axisMinima[axis];
+}
+
+// For the Duet we use the fan output for this
+// DC 2015-03-21: To allow users to control the cooling fan via gcodes generated by slic3r etc.,
+// only turn the fan on/off if the extruder ancillary PWM has been set nonzero.
+// Caution: this is often called from an ISR, or with interrupts disabled!
+inline void Platform::ExtrudeOn() noexcept
+{
+ if (extrusionAncilliaryPwmValue > 0.0)
+ {
+ extrusionAncilliaryPwmPort.WriteAnalog(extrusionAncilliaryPwmValue);
+ }
+}
+
+// DC 2015-03-21: To allow users to control the cooling fan via gcodes generated by slic3r etc.,
+// only turn the fan on/off if the extruder ancilliary PWM has been set nonzero.
+// Caution: this is often called from an ISR, or with interrupts disabled!
+inline void Platform::ExtrudeOff() noexcept
+{
+ if (extrusionAncilliaryPwmValue > 0.0)
+ {
+ extrusionAncilliaryPwmPort.WriteAnalog(0.0);
+ }
+}
+
+//********************************************************************************************************
+
+// Drive the RepRap machine - Heat and temperature
+
+inline IPAddress Platform::GetIPAddress() const noexcept
+{
+ return ipAddress;
+}
+
+inline IPAddress Platform::NetMask() const noexcept
+{
+ return netMask;
+}
+
+inline IPAddress Platform::GateWay() const noexcept
+{
+ return gateWay;
+}
+
+inline float Platform::GetPressureAdvance(size_t extruder) const noexcept
+{
+ return (extruder < MaxExtruders) ? pressureAdvance[extruder] : 0.0;
+}
+
+inline float Platform::GetFilamentWidth() const noexcept
+{
+ return filamentWidth;
+}
+
+inline void Platform::SetFilamentWidth(float width) noexcept
+{
+ filamentWidth = width;
+}
+
+inline float Platform::GetNozzleDiameter() const noexcept
+{
+ return nozzleDiameter;
+}
+
+inline void Platform::SetNozzleDiameter(float diameter) noexcept
+{
+ nozzleDiameter = diameter;
+}
+
+#endif
diff --git a/src/Platform/PortControl.cpp b/src/Platform/PortControl.cpp
new file mode 100644
index 00000000..0632f0a5
--- /dev/null
+++ b/src/Platform/PortControl.cpp
@@ -0,0 +1,145 @@
+/*
+ * PortControl.cpp
+ *
+ * Created on: 15 Jun 2017
+ * Author: David
+ */
+
+#include "PortControl.h"
+#include "GCodes/GCodeBuffer/GCodeBuffer.h"
+#include "Movement/Move.h"
+#include "Movement/DDA.h"
+#include "Movement/StepTimer.h"
+
+#if SUPPORT_IOBITS
+
+PortControl::PortControl() noexcept
+{
+}
+
+void PortControl::Init() noexcept
+{
+ numConfiguredPorts = 0;
+ advanceMillis = 0;
+ advanceClocks = 0;
+ currentPortState = 0;
+}
+
+void PortControl::Exit() noexcept
+{
+ UpdatePorts(0);
+ numConfiguredPorts = 0;
+}
+
+// Update the IO bits. Return the number of milliseconds before we need to be called again, or 0 to be called when movement restarts.
+uint32_t PortControl::UpdatePorts() noexcept
+{
+ if (numConfiguredPorts == 0)
+ {
+ return 0;
+ }
+
+ SetBasePriority(NvicPriorityStep);
+ const DDA * cdda = reprap.GetMove().GetMainDDARing().GetCurrentDDA();
+ if (cdda == nullptr)
+ {
+ // Movement has stopped, so turn all ports off
+ SetBasePriority(0);
+ UpdatePorts(0);
+ return 0;
+ }
+
+ // Find the DDA whose IO port bits we should set now
+ const uint32_t now = StepTimer::GetTimerTicks() + advanceClocks;
+ uint32_t moveEndTime = cdda->GetMoveStartTime();
+ DDA::DDAState st = cdda->GetState();
+ do
+ {
+ moveEndTime += cdda->GetClocksNeeded();
+ if ((int32_t)(moveEndTime - now) >= 0)
+ {
+ SetBasePriority(0);
+ UpdatePorts(cdda->GetIoBits());
+ return (moveEndTime - now + StepTimer::StepClockRate/1000 - 1)/(StepTimer::StepClockRate/1000);
+ }
+ cdda = cdda->GetNext();
+ st = cdda->GetState();
+ } while (st == DDA::executing || st == DDA::frozen);
+
+ SetBasePriority(0);
+ UpdatePorts(0);
+ return 0;
+}
+
+// Set up the GPIO ports returning true if error
+bool PortControl::Configure(GCodeBuffer& gb, const StringRef& reply)
+{
+ bool seen = false;
+ if (gb.Seen('C'))
+ {
+ seen = true;
+ UpdatePorts(0);
+ IoPort * portAddresses[MaxPorts];
+ PinAccess access[MaxPorts];
+ for (size_t i = 0; i < MaxPorts; ++i)
+ {
+ portAddresses[i] = &portMap[i];
+ access[i] = PinAccess::write0;
+ }
+ numConfiguredPorts = IoPort::AssignPorts(gb, reply, PinUsedBy::gpout, MaxPorts, portAddresses, access);
+ if (numConfiguredPorts == 0)
+ {
+ return true;
+ }
+ }
+ if (gb.Seen('T'))
+ {
+ seen = true;
+ advanceMillis = (unsigned int)constrain<int>(gb.GetIValue(), 0, 1000);
+ advanceClocks = (advanceMillis * (uint64_t)StepTimer::StepClockRate)/1000;
+ }
+ if (!seen)
+ {
+ reply.printf("Advance %ums, ", advanceMillis);
+ if (numConfiguredPorts == 0)
+ {
+ reply.cat("no port mapping configured");
+ }
+ else
+ {
+ reply.cat("ports");
+ for (size_t i = 0; i < numConfiguredPorts; ++i)
+ {
+ reply.cat(' ');
+ portMap[i].AppendPinName(reply);
+ }
+ }
+ }
+ return false;
+}
+
+void PortControl::UpdatePorts(IoBits_t newPortState) noexcept
+{
+ if (newPortState != currentPortState)
+ {
+ const IoBits_t bitsToClear = currentPortState & ~newPortState;
+ const IoBits_t bitsToSet = newPortState & ~currentPortState;
+ for (size_t i = 0; i < numConfiguredPorts; ++i)
+ {
+ const IoBits_t mask = 1u << i;
+ if (bitsToClear & mask)
+ {
+ portMap[i].WriteDigital(false);
+ }
+ else if (bitsToSet & mask)
+ {
+ portMap[i].WriteDigital(true);
+ }
+ }
+ currentPortState = newPortState;
+ }
+}
+
+#endif
+
+// End
diff --git a/src/Platform/PortControl.h b/src/Platform/PortControl.h
new file mode 100644
index 00000000..8ea23c3b
--- /dev/null
+++ b/src/Platform/PortControl.h
@@ -0,0 +1,41 @@
+/*
+ * PortControl.h
+ *
+ * Created on: 15 Jun 2017
+ * Author: David
+ */
+
+#ifndef SRC_PORTCONTROL_H_
+#define SRC_PORTCONTROL_H_
+
+#include "RepRapFirmware.h"
+#include "Hardware/IoPorts.h"
+
+class GCodeBuffer;
+
+#if SUPPORT_IOBITS
+
+class PortControl
+{
+public:
+ PortControl() noexcept;
+ void Init() noexcept;
+ void Exit() noexcept;
+ uint32_t UpdatePorts() noexcept;
+ bool Configure(GCodeBuffer& gb, const StringRef& reply);
+
+private:
+ void UpdatePorts(IoBits_t newPortState) noexcept;
+
+ static const size_t MaxPorts = 16; // the port bitmap is currently a 16-bit word
+
+ IoPort portMap[MaxPorts];
+ size_t numConfiguredPorts;
+ unsigned int advanceMillis;
+ uint32_t advanceClocks;
+ IoBits_t currentPortState;
+};
+
+#endif
+
+#endif /* SRC_PORTCONTROL_H_ */
diff --git a/src/Platform/RepRap.cpp b/src/Platform/RepRap.cpp
new file mode 100644
index 00000000..007b2c3c
--- /dev/null
+++ b/src/Platform/RepRap.cpp
@@ -0,0 +1,2926 @@
+#include "RepRap.h"
+
+#include "Movement/Move.h"
+#include "Movement/StepTimer.h"
+#include "FilamentMonitors/FilamentMonitor.h"
+#include "GCodes/GCodes.h"
+#include "Heating/Heat.h"
+#include "Heating/Sensors/TemperatureSensor.h"
+#include "Networking/Network.h"
+#if SUPPORT_HTTP
+# include "Networking/HttpResponder.h"
+#endif
+#include "Platform.h"
+#include "Scanner.h"
+#include <PrintMonitor/PrintMonitor.h>
+#include "Tools/Tool.h"
+#include "Tools/Filament.h"
+#include "Endstops/ZProbe.h"
+#include "Tasks.h"
+#include <Cache.h>
+#include "Fans/FansManager.h"
+#include <Hardware/SoftwareReset.h>
+#include <Hardware/ExceptionHandlers.h>
+#include "Version.h"
+
+#ifdef DUET_NG
+# include "DueXn.h"
+#endif
+
+#if SUPPORT_TMC2660
+# include "Movement/StepperDrivers/TMC2660.h"
+#endif
+#if SUPPORT_TMC22xx
+# include "Movement/StepperDrivers/TMC22xx.h"
+#endif
+#if SUPPORT_TMC51xx
+# include "Movement/StepperDrivers/TMC51xx.h"
+#endif
+
+#if SUPPORT_IOBITS
+# include "PortControl.h"
+#endif
+
+#if SUPPORT_12864_LCD
+# include "Display/Display.h"
+#endif
+
+#if HAS_LINUX_INTERFACE
+# include "Linux/LinuxInterface.h"
+#endif
+
+#ifdef DUET3_ATE
+# include <Duet3Ate.h>
+#endif
+
+#if HAS_HIGH_SPEED_SD
+
+# if !SAME5x
+# include <hsmci/hsmci.h>
+# include <conf_sd_mmc.h>
+# endif
+
+# if SAME70
+// Check correct DMA channel assigned
+static_assert(CONF_HSMCI_XDMAC_CHANNEL == DmacChanHsmci, "mismatched DMA channel assignment");
+# endif
+
+#endif
+
+#if SUPPORT_CAN_EXPANSION
+# include <CAN/CanInterface.h>
+# include <CAN/ExpansionManager.h>
+#endif
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+#if SAME70
+# include <DmacManager.h>
+#endif
+
+#if SAM4S
+# include <wdt/wdt.h>
+#endif
+
+// We call vTaskNotifyGiveFromISR from various interrupts, so the following must be true
+static_assert(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY <= NvicPriorityHSMCI, "configMAX_SYSCALL_INTERRUPT_PRIORITY is set too high");
+
+// Builds that use CoreN2G now need a version string. Eventually, all builds of RRF3 will use CoreN2G.
+extern const char VersionText[] = FIRMWARE_NAME " version " VERSION;
+
+#if HAS_HIGH_SPEED_SD && !SAME5x // SAME5x uses CoreN2G which makes its own RTOS calls
+
+static TaskHandle hsmciTask = nullptr; // the task that is waiting for a HSMCI command to complete
+
+// HSMCI interrupt handler
+extern "C" void HSMCI_Handler() noexcept
+{
+ HSMCI->HSMCI_IDR = 0xFFFFFFFF; // disable all HSMCI interrupts
+#if SAME70
+ XDMAC->XDMAC_CHID[DmacChanHsmci].XDMAC_CID = 0xFFFFFFFF; // disable all DMA interrupts for this channel
+#endif
+ TaskBase::GiveFromISR(hsmciTask); // wake up the task
+}
+
+#if SAME70
+
+// HSMCI DMA complete callback
+void HsmciDmaCallback(CallbackParameter cb, DmaCallbackReason reason) noexcept
+{
+ HSMCI->HSMCI_IDR = 0xFFFFFFFF; // disable all HSMCI interrupts
+ XDMAC->XDMAC_CHID[DmacChanHsmci].XDMAC_CID = 0xFFFFFFFF; // disable all DMA interrupts for this channel
+ if (hsmciTask != nullptr)
+ {
+ TaskBase::GiveFromISR(hsmciTask);
+ hsmciTask = nullptr;
+ }
+}
+
+#endif
+
+// Callback function from the hsmci driver, called while it is waiting for an SD card operation to complete
+// 'stBits' is the set of bits in the HSMCI status register that the caller is interested in.
+// The caller keeps calling this function until at least one of those bits is set.
+extern "C" void hsmciIdle(uint32_t stBits, uint32_t dmaBits) noexcept
+{
+ if ( (HSMCI->HSMCI_SR & stBits) == 0
+#if SAME70
+ && (XDMAC->XDMAC_CHID[DmacChanHsmci].XDMAC_CIS & dmaBits) == 0
+#endif
+ )
+ {
+ // Suspend this task until we get an interrupt indicating that a status bit that we are interested in has been set
+ hsmciTask = TaskBase::GetCallerTaskHandle();
+ HSMCI->HSMCI_IER = stBits;
+#if SAME70
+ DmacManager::SetInterruptCallback(DmacChanHsmci, HsmciDmaCallback, CallbackParameter());
+ XDMAC->XDMAC_CHID[DmacChanHsmci].XDMAC_CIE = dmaBits;
+ XDMAC->XDMAC_GIE = 1u << DmacChanHsmci;
+#endif
+ if (!TaskBase::Take(200))
+ {
+ // We timed out waiting for the HSMCI operation to complete
+ reprap.GetPlatform().LogError(ErrorCode::HsmciTimeout);
+ }
+ }
+}
+
+#endif //end if HAS_HIGH_SPEED_SD && !SAME5x
+
+#if SUPPORT_OBJECT_MODEL
+
+// Object model table and functions
+// Note: if using GCC version 7.3.1 20180622 and lambda functions are used in this table, you must compile this file with option -std=gnu++17.
+// Otherwise the table will be allocate in RAM instead of flash, which wastes too much RAM.
+
+// Macro to build a standard lambda function that includes the necessary type conversions
+#define OBJECT_MODEL_FUNC(...) OBJECT_MODEL_FUNC_BODY(RepRap, __VA_ARGS__)
+#define OBJECT_MODEL_FUNC_IF(_condition,...) OBJECT_MODEL_FUNC_IF_BODY(RepRap, _condition,__VA_ARGS__)
+
+constexpr ObjectModelArrayDescriptor RepRap::boardsArrayDescriptor =
+{
+ nullptr, // no lock needed
+#if SUPPORT_CAN_EXPANSION
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return ((const RepRap*)self)->expansion->GetNumExpansionBoards() + 1; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue
+ { return (context.GetLastIndex() == 0)
+ ? ExpressionValue(((const RepRap*)self)->platform, 0)
+ : ExpressionValue(((const RepRap*)self)->expansion, 0); }
+#else
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return 1; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(((const RepRap*)self)->platform, 0); }
+#endif
+};
+
+constexpr ObjectModelArrayDescriptor RepRap::fansArrayDescriptor =
+{
+ &FansManager::fansLock,
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return ((const RepRap*)self)->fansManager->GetNumFansToReport(); },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(((const RepRap*)self)->fansManager->FindFan(context.GetLastIndex()).Ptr()); }
+};
+
+constexpr ObjectModelArrayDescriptor RepRap::inputsArrayDescriptor =
+{
+ nullptr,
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return ((const RepRap*)self)->gCodes->GetNumInputs(); },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(((const RepRap*)self)->gCodes->GetInput(context.GetLastIndex())); }
+};
+
+constexpr ObjectModelArrayDescriptor RepRap::gpoutArrayDescriptor =
+{
+ nullptr,
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return reprap.GetPlatform().GetNumGpOutputsToReport(); },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue
+ {
+ const GpOutputPort& port = reprap.GetPlatform().GetGpOutPort(context.GetLastIndex());
+ return (port.IsUnused()) ? ExpressionValue(nullptr) : ExpressionValue(&port);
+ }
+};
+
+constexpr ObjectModelArrayDescriptor RepRap::spindlesArrayDescriptor =
+{
+ nullptr, // no lock needed
+ [] (const ObjectModel *self, const ObjectExplorationContext& context) noexcept -> size_t { return MaxSpindles; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(&((const RepRap*)self)->platform->AccessSpindle(context.GetLastIndex())); }
+};
+
+constexpr ObjectModelArrayDescriptor RepRap::toolsArrayDescriptor =
+{
+ &toolListLock,
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return ((const RepRap*)self)->numToolsToReport; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(((const RepRap*)self)->GetTool(context.GetLastIndex()).Ptr()); }
+};
+
+constexpr ObjectModelArrayDescriptor RepRap::restorePointsArrayDescriptor =
+{
+ nullptr,
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return NumRestorePoints; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue
+ { return ExpressionValue(((const RepRap*)self)->gCodes->GetRestorePoint(context.GetLastIndex())); }
+};
+
+constexpr ObjectModelArrayDescriptor RepRap::volumesArrayDescriptor =
+{
+ nullptr,
+#if HAS_MASS_STORAGE
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return MassStorage::GetNumVolumes(); },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(MassStorage::GetVolume(context.GetLastIndex())); }
+#else
+ [] (const ObjectModel *self, const ObjectExplorationContext&) noexcept -> size_t { return 0; },
+ [] (const ObjectModel *self, ObjectExplorationContext& context) noexcept -> ExpressionValue { return ExpressionValue(nullptr); }
+#endif
+};
+
+constexpr ObjectModelTableEntry RepRap::objectModelTable[] =
+{
+ // Within each group, these entries must be in alphabetical order
+ // 0. MachineModel root
+ { "boards", OBJECT_MODEL_FUNC_NOSELF(&boardsArrayDescriptor), ObjectModelEntryFlags::live },
+#if HAS_MASS_STORAGE
+ { "directories", OBJECT_MODEL_FUNC(self, 1), ObjectModelEntryFlags::none },
+#endif
+ { "fans", OBJECT_MODEL_FUNC_NOSELF(&fansArrayDescriptor), ObjectModelEntryFlags::live },
+ { "heat", OBJECT_MODEL_FUNC(self->heat), ObjectModelEntryFlags::live },
+ { "inputs", OBJECT_MODEL_FUNC_NOSELF(&inputsArrayDescriptor), ObjectModelEntryFlags::live },
+ { "job", OBJECT_MODEL_FUNC(self->printMonitor), ObjectModelEntryFlags::live },
+ { "limits", OBJECT_MODEL_FUNC(self, 2), ObjectModelEntryFlags::none },
+ { "move", OBJECT_MODEL_FUNC(self->move), ObjectModelEntryFlags::live },
+ // Note, 'network' is needed even if there is no networking, because it contains the machine name
+ { "network", OBJECT_MODEL_FUNC(self->network), ObjectModelEntryFlags::none },
+#if SUPPORT_SCANNER
+ { "scanner", OBJECT_MODEL_FUNC(self->scanner), ObjectModelEntryFlags::none },
+#endif
+ { "sensors", OBJECT_MODEL_FUNC(&self->platform->GetEndstops()), ObjectModelEntryFlags::live },
+ { "seqs", OBJECT_MODEL_FUNC(self, 6), ObjectModelEntryFlags::live },
+ { "spindles", OBJECT_MODEL_FUNC_NOSELF(&spindlesArrayDescriptor), ObjectModelEntryFlags::live },
+ { "state", OBJECT_MODEL_FUNC(self, 3), ObjectModelEntryFlags::live },
+ { "tools", OBJECT_MODEL_FUNC_NOSELF(&toolsArrayDescriptor), ObjectModelEntryFlags::live },
+ { "volumes", OBJECT_MODEL_FUNC_NOSELF(&volumesArrayDescriptor), ObjectModelEntryFlags::none },
+
+ // 1. MachineModel.directories
+#if HAS_MASS_STORAGE
+ { "filaments", OBJECT_MODEL_FUNC_NOSELF(FILAMENTS_DIRECTORY), ObjectModelEntryFlags::verbose },
+ { "firmware", OBJECT_MODEL_FUNC_NOSELF(FIRMWARE_DIRECTORY), ObjectModelEntryFlags::verbose },
+ { "gCodes", OBJECT_MODEL_FUNC(self->platform->GetGCodeDir()), ObjectModelEntryFlags::verbose },
+ { "macros", OBJECT_MODEL_FUNC(self->platform->GetMacroDir()), ObjectModelEntryFlags::verbose },
+ { "menu", OBJECT_MODEL_FUNC_NOSELF(MENU_DIR), ObjectModelEntryFlags::verbose },
+ { "scans", OBJECT_MODEL_FUNC_NOSELF(SCANS_DIRECTORY), ObjectModelEntryFlags::verbose },
+ { "system", OBJECT_MODEL_FUNC_NOSELF(ExpressionValue::SpecialType::sysDir, 0), ObjectModelEntryFlags::none },
+ { "web", OBJECT_MODEL_FUNC(self->platform->GetWebDir()), ObjectModelEntryFlags::verbose },
+#endif
+
+ // 2. MachineModel.limits
+ { "axes", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxAxes), ObjectModelEntryFlags::verbose },
+ { "axesPlusExtruders", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxAxesPlusExtruders), ObjectModelEntryFlags::verbose },
+ { "bedHeaters", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxBedHeaters), ObjectModelEntryFlags::verbose },
+#if SUPPORT_CAN_EXPANSION
+ { "boards", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxCanBoards + 1), ObjectModelEntryFlags::verbose },
+#else
+ { "boards", OBJECT_MODEL_FUNC_NOSELF((int32_t)1), ObjectModelEntryFlags::verbose },
+#endif
+ { "chamberHeaters", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxChamberHeaters), ObjectModelEntryFlags::verbose },
+#if SUPPORT_CAN_EXPANSION
+ { "drivers", OBJECT_MODEL_FUNC_NOSELF((int32_t)(NumDirectDrivers + MaxCanDrivers)), ObjectModelEntryFlags::verbose },
+#else
+ { "drivers", OBJECT_MODEL_FUNC((int32_t)self->platform->GetNumActualDirectDrivers()), ObjectModelEntryFlags::verbose },
+#endif
+ { "driversPerAxis", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxDriversPerAxis), ObjectModelEntryFlags::verbose },
+ { "extruders", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxExtruders), ObjectModelEntryFlags::verbose },
+ { "extrudersPerTool", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxExtrudersPerTool), ObjectModelEntryFlags::verbose },
+ { "fans", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxFans), ObjectModelEntryFlags::verbose },
+ { "gpInPorts", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxGpInPorts), ObjectModelEntryFlags::verbose },
+ { "gpOutPorts", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxGpOutPorts), ObjectModelEntryFlags::verbose },
+ { "heaters", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxHeaters), ObjectModelEntryFlags::verbose },
+ { "heatersPerTool", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxHeatersPerTool), ObjectModelEntryFlags::verbose },
+ { "monitorsPerHeater", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxMonitorsPerHeater), ObjectModelEntryFlags::verbose },
+ { "restorePoints", OBJECT_MODEL_FUNC_NOSELF((int32_t)NumRestorePoints), ObjectModelEntryFlags::verbose },
+ { "sensors", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxSensors), ObjectModelEntryFlags::verbose },
+ { "spindles", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxSpindles), ObjectModelEntryFlags::verbose },
+ { "tools", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxTools), ObjectModelEntryFlags::verbose },
+ { "trackedObjects", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxTrackedObjects), ObjectModelEntryFlags::verbose },
+ { "triggers", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxTriggers), ObjectModelEntryFlags::verbose },
+ // TODO userVariables
+#if HAS_MASS_STORAGE
+ { "volumes", OBJECT_MODEL_FUNC_NOSELF((int32_t)NumSdCards), ObjectModelEntryFlags::verbose },
+#else
+ { "volumes", OBJECT_MODEL_FUNC_NOSELF((int32_t)0), ObjectModelEntryFlags::verbose },
+#endif
+ { "workplaces", OBJECT_MODEL_FUNC_NOSELF((int32_t)NumCoordinateSystems), ObjectModelEntryFlags::verbose },
+ { "zProbeProgramBytes", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxZProbeProgramBytes), ObjectModelEntryFlags::verbose },
+ { "zProbes", OBJECT_MODEL_FUNC_NOSELF((int32_t)MaxZProbes), ObjectModelEntryFlags::verbose },
+
+ // 3. MachineModel.state
+ { "atxPower", OBJECT_MODEL_FUNC_IF(self->gCodes->AtxPowerControlled(), self->platform->AtxPower()), ObjectModelEntryFlags::live },
+ { "beep", OBJECT_MODEL_FUNC_IF(self->beepDuration != 0, self, 4), ObjectModelEntryFlags::none },
+ { "currentTool", OBJECT_MODEL_FUNC((int32_t)self->GetCurrentToolNumber()), ObjectModelEntryFlags::live },
+ { "displayMessage", OBJECT_MODEL_FUNC(self->message.c_str()), ObjectModelEntryFlags::none },
+ { "gpOut", OBJECT_MODEL_FUNC_NOSELF(&gpoutArrayDescriptor), ObjectModelEntryFlags::live },
+#if SUPPORT_LASER
+ // 2020-04-24: return the configured laser PWM even if the laser is temporarily turned off
+ { "laserPwm", OBJECT_MODEL_FUNC_IF(self->gCodes->GetMachineType() == MachineType::laser, self->gCodes->GetLaserPwm(), 2), ObjectModelEntryFlags::live },
+#endif
+#if HAS_MASS_STORAGE
+ { "logFile", OBJECT_MODEL_FUNC(self->platform->GetLogFileName()), ObjectModelEntryFlags::none },
+#else
+ { "logFile", OBJECT_MODEL_FUNC_NOSELF(nullptr), ObjectModelEntryFlags::none },
+#endif
+ { "logLevel", OBJECT_MODEL_FUNC(self->platform->GetLogLevel()), ObjectModelEntryFlags::none },
+ { "machineMode", OBJECT_MODEL_FUNC(self->gCodes->GetMachineModeString()), ObjectModelEntryFlags::none },
+ { "messageBox", OBJECT_MODEL_FUNC_IF(self->mbox.active, self, 5), ObjectModelEntryFlags::none },
+ { "msUpTime", OBJECT_MODEL_FUNC_NOSELF((int32_t)(context.GetStartMillis() % 1000u)), ObjectModelEntryFlags::live },
+ { "nextTool", OBJECT_MODEL_FUNC((int32_t)self->gCodes->GetNewToolNumber()), ObjectModelEntryFlags::live },
+#if HAS_VOLTAGE_MONITOR
+ { "powerFailScript", OBJECT_MODEL_FUNC(self->gCodes->GetPowerFailScript()), ObjectModelEntryFlags::none },
+#endif
+ { "previousTool", OBJECT_MODEL_FUNC((int32_t)self->previousToolNumber), ObjectModelEntryFlags::live },
+ { "restorePoints", OBJECT_MODEL_FUNC_NOSELF(&restorePointsArrayDescriptor), ObjectModelEntryFlags::none },
+ { "status", OBJECT_MODEL_FUNC(self->GetStatusString()), ObjectModelEntryFlags::live },
+ { "time", OBJECT_MODEL_FUNC(DateTime(self->platform->GetDateTime())), ObjectModelEntryFlags::live },
+ { "upTime", OBJECT_MODEL_FUNC_NOSELF((int32_t)((context.GetStartMillis()/1000u) & 0x7FFFFFFF)), ObjectModelEntryFlags::live },
+
+ // 4. MachineModel.state.beep
+ { "duration", OBJECT_MODEL_FUNC((int32_t)self->beepDuration), ObjectModelEntryFlags::none },
+ { "frequency", OBJECT_MODEL_FUNC((int32_t)self->beepFrequency), ObjectModelEntryFlags::none },
+
+ // 5. MachineModel.state.messageBox (FIXME acquire MutexLocker when accessing the following)
+ { "axisControls", OBJECT_MODEL_FUNC((int32_t)self->mbox.controls.GetRaw()), ObjectModelEntryFlags::none },
+ { "message", OBJECT_MODEL_FUNC(self->mbox.message.c_str()), ObjectModelEntryFlags::none },
+ { "mode", OBJECT_MODEL_FUNC((int32_t)self->mbox.mode), ObjectModelEntryFlags::none },
+ { "seq", OBJECT_MODEL_FUNC((int32_t)self->mbox.seq), ObjectModelEntryFlags::none },
+ { "timeout", OBJECT_MODEL_FUNC((int32_t)self->mbox.timeout), ObjectModelEntryFlags::none },
+ { "title", OBJECT_MODEL_FUNC(self->mbox.title.c_str()), ObjectModelEntryFlags::none },
+
+ // 6. MachineModel.seqs
+ { "boards", OBJECT_MODEL_FUNC((int32_t)self->boardsSeq), ObjectModelEntryFlags::live },
+#if HAS_MASS_STORAGE
+ { "directories", OBJECT_MODEL_FUNC((int32_t)self->directoriesSeq), ObjectModelEntryFlags::live },
+#endif
+ { "fans", OBJECT_MODEL_FUNC((int32_t)self->fansSeq), ObjectModelEntryFlags::live },
+ { "heat", OBJECT_MODEL_FUNC((int32_t)self->heatSeq), ObjectModelEntryFlags::live },
+ { "inputs", OBJECT_MODEL_FUNC((int32_t)self->inputsSeq), ObjectModelEntryFlags::live },
+ { "job", OBJECT_MODEL_FUNC((int32_t)self->jobSeq), ObjectModelEntryFlags::live },
+ // no need for 'limits' because it never changes
+ { "move", OBJECT_MODEL_FUNC((int32_t)self->moveSeq), ObjectModelEntryFlags::live },
+ // Note, 'network' is needed even if there is no networking, because it contains the machine name
+ { "network", OBJECT_MODEL_FUNC((int32_t)self->networkSeq), ObjectModelEntryFlags::live },
+#if HAS_NETWORKING
+ { "reply", OBJECT_MODEL_FUNC_NOSELF((int32_t)HttpResponder::GetReplySeq()), ObjectModelEntryFlags::live },
+#endif
+#if SUPPORT_SCANNER
+ { "scanner", OBJECT_MODEL_FUNC((int32_t)self->scannerSeq), ObjectModelEntryFlags::live },
+#endif
+ { "sensors", OBJECT_MODEL_FUNC((int32_t)self->sensorsSeq), ObjectModelEntryFlags::live },
+ { "spindles", OBJECT_MODEL_FUNC((int32_t)self->spindlesSeq), ObjectModelEntryFlags::live },
+ { "state", OBJECT_MODEL_FUNC((int32_t)self->stateSeq), ObjectModelEntryFlags::live },
+ { "tools", OBJECT_MODEL_FUNC((int32_t)self->toolsSeq), ObjectModelEntryFlags::live },
+#if HAS_MASS_STORAGE
+ { "volumes", OBJECT_MODEL_FUNC((int32_t)self->volumesSeq), ObjectModelEntryFlags::live },
+#endif
+};
+
+constexpr uint8_t RepRap::objectModelTableDescriptor[] =
+{
+ 7, // number of sub-tables
+ 14 + SUPPORT_SCANNER + HAS_MASS_STORAGE, // root
+#if HAS_MASS_STORAGE
+ 8, // directories
+#else
+ 0, // directories
+#endif
+ 25, // limits
+ 16 + HAS_VOLTAGE_MONITOR + SUPPORT_LASER, // state
+ 2, // state.beep
+ 6, // state.messageBox
+ 11 + HAS_NETWORKING + SUPPORT_SCANNER + 2 * HAS_MASS_STORAGE // seqs
+};
+
+DEFINE_GET_OBJECT_MODEL_TABLE(RepRap)
+
+#endif
+
+ReadWriteLock RepRap::toolListLock;
+
+// RepRap member functions.
+
+// Do nothing more in the constructor; put what you want in RepRap:Init()
+
+RepRap::RepRap() noexcept
+ : boardsSeq(0), directoriesSeq(0), fansSeq(0), heatSeq(0), inputsSeq(0), jobSeq(0), moveSeq(0),
+ networkSeq(0), scannerSeq(0), sensorsSeq(0), spindlesSeq(0), stateSeq(0), toolsSeq(0), volumesSeq(0),
+ toolList(nullptr), currentTool(nullptr), lastWarningMillis(0),
+ activeExtruders(0), activeToolHeaters(0), numToolsToReport(0),
+ ticksInSpinState(0), heatTaskIdleTicks(0), debug(0),
+ beepFrequency(0), beepDuration(0), beepTimer(0),
+ previousToolNumber(-1),
+ diagnosticsDestination(MessageType::NoDestinationMessage), justSentDiagnostics(false),
+ spinningModule(noModule), stopped(false), active(false), processingConfig(true)
+#if HAS_LINUX_INTERFACE
+ , usingLinuxInterface(false) // default to not using the SBC interface until we have checked for config.g on an SD card,
+ // because a disconnected SBC interface can generate noise which may trigger interrupts and DMA
+#endif
+{
+ // Don't call constructors for other objects here
+}
+
+#if 0
+
+///DEBUG to catch memory corruption
+const size_t WatchSize = 32768;
+uint32_t *watchBuffer;
+
+static void InitWatchBuffer() noexcept
+{
+ watchBuffer = (uint32_t*)malloc(WatchSize);
+ memset(watchBuffer, 0x5A, WatchSize);
+}
+
+static void CheckWatchBuffer(unsigned int module) noexcept
+{
+ uint32_t *p = watchBuffer, *end = watchBuffer + 32768/sizeof(uint32_t);
+ while (p < end)
+ {
+ if (*p != 0x5A5A5A5A)
+ {
+ debugPrintf("Address %p data %08" PRIx32 " module %u\n", p, *p, module);
+ *p = 0x5A5A5A5A;
+ }
+ ++p;
+ }
+}
+
+#endif
+
+void RepRap::Init() noexcept
+{
+ OutputBuffer::Init();
+ platform = new Platform();
+#if HAS_LINUX_INTERFACE
+ linuxInterface = new LinuxInterface(); // needs to be allocated early on Duet 2 so as to avoid using any of the last 64K of RAM
+#endif
+ network = new Network(*platform);
+ gCodes = new GCodes(*platform);
+ move = new Move();
+ heat = new Heat();
+ printMonitor = new PrintMonitor(*platform, *gCodes);
+ fansManager = new FansManager;
+
+#if SUPPORT_ROLAND
+ roland = new Roland(*platform);
+#endif
+#if SUPPORT_SCANNER
+ scanner = new Scanner(*platform);
+#endif
+#if SUPPORT_IOBITS
+ portControl = new PortControl();
+#endif
+#if SUPPORT_12864_LCD
+ display = new Display();
+#endif
+#if SUPPORT_CAN_EXPANSION
+ expansion = new ExpansionManager();
+#endif
+
+ SetPassword(DEFAULT_PASSWORD);
+ message.Clear();
+#if SUPPORT_12864_LCD
+ messageSequence = 0;
+#endif
+
+ messageBoxMutex.Create("MessageBox");
+
+ platform->Init();
+ network->Init();
+ SetName(DEFAULT_MACHINE_NAME); // Network must be initialised before calling this because this calls SetHostName
+ gCodes->Init(); // must be called before Move::Init
+#if SUPPORT_CAN_EXPANSION
+ CanInterface::Init();
+#endif
+ move->Init();
+ heat->Init();
+ fansManager->Init();
+ printMonitor->Init();
+ FilamentMonitor::InitStatic();
+
+#if SUPPORT_ROLAND
+ roland->Init();
+#endif
+#if SUPPORT_SCANNER
+ scanner->Init();
+#endif
+#if SUPPORT_IOBITS
+ portControl->Init();
+#endif
+#if SUPPORT_12864_LCD
+ display->Init();
+#endif
+#ifdef DUET3_ATE
+ Duet3Ate::Init();
+#endif
+ // linuxInterface is not initialised until we know we are using it, to prevent a disconnected SBC interface generating interrupts and DMA
+
+ // Set up the timeout of the regular watchdog, and set up the backup watchdog if there is one.
+#if SAME5x
+ WatchdogInit();
+ NVIC_SetPriority(WDT_IRQn, NvicPriorityWatchdog); // set priority for watchdog interrupts
+ NVIC_ClearPendingIRQ(WDT_IRQn);
+ NVIC_EnableIRQ(WDT_IRQn); // enable the watchdog early warning interrupt
+#elif defined(__LPC17xx__)
+ wdt_init(1); // set wdt to 1 second. reset the processor on a watchdog fault
+#else
+ {
+ // The clock frequency for both watchdogs is about 32768/128 = 256Hz
+ // The watchdogs on the SAM4E seem to be very timing-sensitive. On the Duet WiFi/Ethernet they were going off spuriously depending on how long the DueX initialisation took.
+ // The documentation says you mustn't write to the mode register within 3 slow clocks after kicking the watchdog.
+ // I have a theory that the converse is also true, i.e. after enabling the watchdog you mustn't kick it within 3 slow clocks
+ // So I've added a delay call before we set 'active' true (which enables kicking the watchdog), and that seems to fix the problem.
+# if SAM4E || SAME70
+ const uint16_t mainTimeout = 49152/128; // set main (back stop) watchdog timeout to 1.5s second (max allowed value is 4095 = 16 seconds)
+ WDT->WDT_MR = WDT_MR_WDRSTEN | WDT_MR_WDDBGHLT | WDT_MR_WDV(mainTimeout) | WDT_MR_WDD(mainTimeout); // reset the processor on a watchdog fault, stop it when debugging
+
+ // The RSWDT must be initialised *after* the main WDT
+ const uint16_t rsTimeout = 32768/128; // set secondary watchdog timeout to 1 second (max allowed value is 4095 = 16 seconds)
+# if SAME70
+ RSWDT->RSWDT_MR = RSWDT_MR_WDFIEN | RSWDT_MR_WDDBGHLT | RSWDT_MR_WDV(rsTimeout) | RSWDT_MR_ALLONES_Msk; // generate an interrupt on a watchdog fault
+ NVIC_SetPriority(RSWDT_IRQn, NvicPriorityWatchdog); // set priority for watchdog interrupts
+ NVIC_ClearPendingIRQ(RSWDT_IRQn);
+ NVIC_EnableIRQ(RSWDT_IRQn); // enable the watchdog interrupt
+# else
+ RSWDT->RSWDT_MR = RSWDT_MR_WDFIEN | RSWDT_MR_WDDBGHLT | RSWDT_MR_WDV(rsTimeout) | RSWDT_MR_WDD(rsTimeout); // generate an interrupt on a watchdog fault
+ NVIC_SetPriority(WDT_IRQn, NvicPriorityWatchdog); // set priority for watchdog interrupts
+ NVIC_ClearPendingIRQ(WDT_IRQn);
+ NVIC_EnableIRQ(WDT_IRQn); // enable the watchdog interrupt
+# endif
+# else
+ // We don't have a RSWDT so set the main watchdog timeout to 1 second
+ const uint16_t timeout = 32768/128; // set watchdog timeout to 1 second (max allowed value is 4095 = 16 seconds)
+ wdt_init(WDT, WDT_MR_WDRSTEN | WDT_MR_WDDBGHLT, timeout, timeout); // reset the processor on a watchdog fault, stop it when debugging
+# endif
+ delayMicroseconds(200); // 200us is about 6 slow clocks
+ }
+#endif
+
+ active = true; // must do this after we initialise the watchdog but before we start the network or call Spin(), else the watchdog may time out
+
+ platform->MessageF(UsbMessage, "%s\n", VersionText);
+
+#if HAS_LINUX_INTERFACE && !HAS_MASS_STORAGE
+ linuxInterface->Init();
+ usingLinuxInterface = true;
+#endif
+
+#if HAS_MASS_STORAGE
+ {
+ // Try to mount the first SD card
+ GCodeResult rslt;
+ String<100> reply;
+ do
+ {
+ MassStorage::Spin(); // Spin() doesn't get called regularly until after this function completes, and we need it to update the card detect status
+ rslt = MassStorage::Mount(0, reply.GetRef(), false);
+ }
+ while (rslt == GCodeResult::notFinished);
+
+ if (rslt == GCodeResult::ok)
+ {
+# if HAS_LINUX_INTERFACE
+ delete linuxInterface; // free up the RAM for more tools etc.
+ linuxInterface = nullptr;
+# endif
+ // Run the configuration file
+ if (!RunStartupFile(GCodes::CONFIG_FILE) && !RunStartupFile(GCodes::CONFIG_BACKUP_FILE))
+ {
+ platform->Message(AddWarning(UsbMessage), "no configuration file found\n");
+ }
+ }
+# if HAS_LINUX_INTERFACE
+ else if (!MassStorage::IsCardDetected(0)) // if we failed to mount the SD card because there was no card in the slot
+ {
+ linuxInterface->Init();
+ usingLinuxInterface = true;
+ }
+# endif
+ else
+ {
+# if HAS_LINUX_INTERFACE
+ delete linuxInterface; // free up the RAM for more tools etc.
+ linuxInterface = nullptr;
+# endif
+ delay(3000); // Wait a few seconds so users have a chance to see this
+ platform->MessageF(AddWarning(UsbMessage), "%s\n", reply.c_str());
+ }
+ }
+#endif
+
+#if HAS_LINUX_INTERFACE
+ if (usingLinuxInterface)
+ {
+ // Keep spinning until the SBC connects
+ while (!linuxInterface->IsConnected())
+ {
+ Spin();
+ }
+
+ // Run config.g or config.g.bak
+ if (!RunStartupFile(GCodes::CONFIG_FILE))
+ {
+ RunStartupFile(GCodes::CONFIG_BACKUP_FILE);
+ }
+
+ // runonce.g is executed by the SBC as soon as processingConfig is set to false.
+ // As we are running the SBC, save RAM by not activating the network
+ }
+ else
+#endif
+ {
+ network->Activate(); // need to do this here, as the configuration GCodes may set IP address etc.
+#if HAS_MASS_STORAGE
+ // If we are running from SD card, run the runonce.g file if it exists, then delete it
+ if (RunStartupFile(GCodes::RUNONCE_G))
+ {
+ platform->DeleteSysFile(GCodes::RUNONCE_G);
+ }
+#endif
+ }
+ processingConfig = false;
+
+#if HAS_HIGH_SPEED_SD && !SAME5x
+ hsmci_set_idle_func(hsmciIdle);
+ HSMCI->HSMCI_IDR = 0xFFFFFFFF; // disable all HSMCI interrupts
+ NVIC_EnableIRQ(HSMCI_IRQn);
+#endif
+
+ platform->MessageF(UsbMessage, "%s is up and running.\n", FIRMWARE_NAME);
+
+ fastLoop = UINT32_MAX;
+ slowLoop = 0;
+}
+
+// Run a startup file
+bool RepRap::RunStartupFile(const char *filename) noexcept
+{
+ bool rslt = gCodes->RunConfigFile(filename);
+ if (rslt)
+ {
+ platform->MessageF(UsbMessage, "Executing %s... ", filename);
+ do
+ {
+ // GCodes::Spin will process the macro file and ensure IsDaemonBusy returns false when it's done
+ Spin();
+ } while (gCodes->IsTriggerBusy());
+ platform->Message(UsbMessage, "Done!\n");
+ }
+ return rslt;
+}
+
+void RepRap::Exit() noexcept
+{
+#if HAS_HIGH_SPEED_SD && !SAME5x // SAME5x MCI driver is RTOS_aware, so it doesn't need this
+ hsmci_set_idle_func(nullptr);
+#endif
+ active = false;
+ heat->Exit();
+ move->Exit();
+ gCodes->Exit();
+#if SUPPORT_SCANNER
+ scanner->Exit();
+#endif
+#if SUPPORT_IOBITS
+ portControl->Exit();
+#endif
+#if SUPPORT_12864_LCD
+ display->Exit();
+#endif
+ network->Exit();
+ platform->Exit();
+}
+
+void RepRap::Spin() noexcept
+{
+ if (!active)
+ {
+ return;
+ }
+
+ const uint32_t lastTime = StepTimer::GetTimerTicks();
+
+ ticksInSpinState = 0;
+ spinningModule = modulePlatform;
+ platform->Spin();
+
+ ticksInSpinState = 0;
+ spinningModule = moduleGcodes;
+ gCodes->Spin();
+
+ ticksInSpinState = 0;
+ spinningModule = moduleMove;
+ move->Spin();
+
+#if SUPPORT_ROLAND
+ ticksInSpinState = 0;
+ spinningModule = moduleRoland;
+ roland->Spin();
+#endif
+
+#if SUPPORT_SCANNER && !SCANNER_AS_SEPARATE_TASK
+ ticksInSpinState = 0;
+ spinningModule = moduleScanner;
+ scanner->Spin();
+#endif
+
+ ticksInSpinState = 0;
+ spinningModule = modulePrintMonitor;
+ printMonitor->Spin();
+
+ ticksInSpinState = 0;
+ spinningModule = moduleFilamentSensors;
+ FilamentMonitor::Spin();
+
+#if SUPPORT_12864_LCD
+ ticksInSpinState = 0;
+ spinningModule = moduleDisplay;
+ display->Spin();
+#endif
+
+ ticksInSpinState = 0;
+ spinningModule = noModule;
+
+ // Check if we need to send diagnostics
+ if (diagnosticsDestination != MessageType::NoDestinationMessage)
+ {
+ Diagnostics(diagnosticsDestination);
+ diagnosticsDestination = MessageType::NoDestinationMessage;
+ }
+
+ // Check if we need to display a cold extrusion warning
+ const uint32_t now = millis();
+ if (now - lastWarningMillis >= MinimumWarningInterval)
+ {
+ ReadLocker lock(toolListLock);
+ for (Tool *t = toolList; t != nullptr; t = t->Next())
+ {
+ if (t->DisplayColdExtrudeWarning())
+ {
+ platform->MessageF(WarningMessage, "Tool %d was not driven because its heater temperatures were not high enough or it has a heater fault\n", t->myNumber);
+ lastWarningMillis = now;
+ }
+ }
+ }
+
+ // Check if the beep request can be cleared
+ if (beepTimer != 0 && now - beepTimer >= beepDuration)
+ {
+ beepDuration = beepFrequency = beepTimer = 0;
+ StateUpdated();
+ }
+
+ // Check if the message box can be hidden
+ {
+ MutexLocker lock(messageBoxMutex);
+ if (mbox.active && mbox.timer != 0 && now - mbox.timer >= mbox.timeout)
+ {
+ mbox.active = false;
+ StateUpdated();
+ }
+ }
+
+ // Keep track of the loop time
+ if (justSentDiagnostics)
+ {
+ // Sending diagnostics increases the loop time, so don't count it
+ justSentDiagnostics = false;
+ }
+ else
+ {
+ const uint32_t now = StepTimer::GetTimerTicks();
+ const uint32_t dt = now - lastTime;
+#if 0 //DEBUG
+ if (dt > 1000000)
+ {
+ platform->MessageF(ErrorMessage, "dt %" PRIu32 " now %08" PRIx32 " last %08" PRIx32 "\n", dt, now, lastTime);
+ }
+#endif
+ if (dt < fastLoop)
+ {
+ fastLoop = dt;
+ }
+ if (dt > slowLoop)
+ {
+ slowLoop = dt;
+ }
+ }
+
+ RTOSIface::Yield();
+}
+
+void RepRap::Timing(MessageType mtype) noexcept
+{
+ platform->MessageF(mtype, "Slowest loop: %.2fms; fastest: %.2fms\n", (double)(slowLoop * StepTimer::StepClocksToMillis), (double)(fastLoop * StepTimer::StepClocksToMillis));
+ fastLoop = UINT32_MAX;
+ slowLoop = 0;
+}
+
+void RepRap::Diagnostics(MessageType mtype) noexcept
+{
+ platform->Message(mtype, "=== Diagnostics ===\n");
+
+// DEBUG print the module addresses
+// platform->MessageF(mtype, "platform %" PRIx32 ", network %" PRIx32 ", move %" PRIx32 ", heat %" PRIx32 ", gcodes %" PRIx32 ", scanner %" PRIx32 ", pm %" PRIx32 ", portc %" PRIx32 "\n",
+// (uint32_t)platform, (uint32_t)network, (uint32_t)move, (uint32_t)heat, (uint32_t)gCodes, (uint32_t)scanner, (uint32_t)printMonitor, (uint32_t)portControl);
+
+ // Print the firmware version and board type
+
+#ifdef DUET_NG
+# if HAS_LINUX_INTERFACE
+ platform->MessageF(mtype, "%s version %s running on %s (%s mode)", FIRMWARE_NAME, VERSION, platform->GetElectronicsString(),
+ (UsingLinuxInterface()) ? "SBC" : "standalone");
+# else
+ platform->MessageF(mtype, "%s version %s running on %s", FIRMWARE_NAME, VERSION, platform->GetElectronicsString());
+# endif
+ const char* const expansionName = DuetExpansion::GetExpansionBoardName();
+ platform->MessageF(mtype, (expansionName == nullptr) ? "\n" : " + %s\n", expansionName);
+#elif defined(__LPC17xx__)
+ platform->MessageF(mtype, "%s (%s) version %s running on %s at %dMhz\n", FIRMWARE_NAME, lpcBoardName, VERSION, platform->GetElectronicsString(), (int)SystemCoreClock/1000000);
+#elif HAS_LINUX_INTERFACE
+ platform->MessageF(mtype, "%s version %s running on %s (%s mode)\n", FIRMWARE_NAME, VERSION, platform->GetElectronicsString(),
+# if SUPPORT_REMOTE_COMMANDS
+ (CanInterface::InExpansionMode()) ? "expansion" :
+# endif
+ (UsingLinuxInterface()) ? "SBC" : "standalone"
+ );
+#else
+ platform->MessageF(mtype, "%s version %s running on %s\n", FIRMWARE_NAME, VERSION, platform->GetElectronicsString());
+#endif
+
+#if MCU_HAS_UNIQUE_ID
+ platform->MessageF(mtype, "Board ID: %s\n", platform->GetUniqueIdString());
+#endif
+
+ // Show the used and free buffer counts. Do this early in case we are running out of them and the diagnostics get truncated.
+ OutputBuffer::Diagnostics(mtype);
+
+ // Now print diagnostics for other modules
+ Tasks::Diagnostics(mtype);
+ platform->Diagnostics(mtype); // this includes a call to our Timing() function
+#if HAS_MASS_STORAGE
+ MassStorage::Diagnostics(mtype);
+#endif
+ move->Diagnostics(mtype);
+ heat->Diagnostics(mtype);
+ gCodes->Diagnostics(mtype);
+ FilamentMonitor::Diagnostics(mtype);
+#ifdef DUET_NG
+ DuetExpansion::Diagnostics(mtype);
+#endif
+#if SUPPORT_CAN_EXPANSION
+ CanInterface::Diagnostics(mtype);
+#endif
+#if HAS_LINUX_INTERFACE
+ if (usingLinuxInterface)
+ {
+ linuxInterface->Diagnostics(mtype);
+ }
+ else
+#endif
+ {
+ network->Diagnostics(mtype);
+ }
+
+ justSentDiagnostics = true;
+}
+
+// Turn off the heaters, disable the motors, and deactivate the Heat and Move classes. Leave everything else working.
+void RepRap::EmergencyStop() noexcept
+{
+ stopped = true; // a useful side effect of setting this is that it prevents Platform::Tick being called, which is needed when loading IAP into RAM
+
+ // Do not turn off ATX power here. If the nozzles are still hot, don't risk melting any surrounding parts by turning fans off.
+ //platform->SetAtxPower(false);
+
+ switch (gCodes->GetMachineType())
+ {
+ case MachineType::cnc:
+ for (size_t i = 0; i < MaxSpindles; i++)
+ {
+ platform->AccessSpindle(i).SetState(SpindleState::stopped);
+ }
+ break;
+
+#if SUPPORT_LASER
+ case MachineType::laser:
+ platform->SetLaserPwm(0);
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ heat->Exit(); // this also turns off all heaters
+ move->Exit(); // this stops the motors stepping
+
+#if SUPPORT_CAN_EXPANSION
+ expansion->EmergencyStop();
+#endif
+
+ gCodes->EmergencyStop();
+ platform->StopLogging();
+}
+
+void RepRap::SetDebug(Module m, bool enable) noexcept
+{
+ if (m < numModules)
+ {
+ if (enable)
+ {
+ debug |= (1u << m);
+ }
+ else
+ {
+ debug &= ~(1u << m);
+ }
+ }
+}
+
+void RepRap::ClearDebug() noexcept
+{
+ debug = 0;
+}
+
+void RepRap::PrintDebug(MessageType mt) noexcept
+{
+ platform->Message((MessageType)(mt | PushFlag), "Debugging enabled for modules:");
+ for (size_t i = 0; i < numModules; i++)
+ {
+ if ((debug & (1u << i)) != 0)
+ {
+ platform->MessageF((MessageType)(mt | PushFlag), " %s(%u)", GetModuleName(i), i);
+ }
+ }
+
+ platform->Message((MessageType)(mt | PushFlag), "\nDebugging disabled for modules:");
+ for (size_t i = 0; i < numModules; i++)
+ {
+ if ((debug & (1u << i)) == 0)
+ {
+ platform->MessageF((MessageType)(mt | PushFlag), " %s(%u)", GetModuleName(i), i);
+ }
+ }
+ platform->Message(mt, "\n");
+}
+
+// Add a tool.
+// Prior to calling this, delete any existing tool with the same number
+// The tool list is maintained in tool number order.
+void RepRap::AddTool(Tool* tool) noexcept
+{
+ WriteLocker lock(toolListLock);
+ Tool** t = &toolList;
+ while(*t != nullptr && (*t)->Number() < tool->Number())
+ {
+ t = &((*t)->next);
+ }
+ tool->next = *t;
+ *t = tool;
+ tool->UpdateExtruderAndHeaterCount(activeExtruders, activeToolHeaters, numToolsToReport);
+ platform->UpdateConfiguredHeaters();
+ ToolsUpdated();
+}
+
+void RepRap::DeleteTool(int toolNumber) noexcept
+{
+ WriteLocker lock(toolListLock);
+
+ // Deselect it if necessary
+ if (currentTool != nullptr && currentTool->Number() == toolNumber)
+ {
+ SelectTool(-1, false);
+ }
+
+ // Purge any references to this tool
+ Tool * tool = nullptr;
+ for (Tool **t = &toolList; *t != nullptr; t = &((*t)->next))
+ {
+ if ((*t)->Number() == toolNumber)
+ {
+ tool = *t;
+ *t = tool->next;
+
+ // Switch off any associated heaters
+ for (size_t i = 0; i < tool->HeaterCount(); i++)
+ {
+ heat->SwitchOff(tool->Heater(i));
+ }
+
+ break;
+ }
+ }
+
+ // Delete it
+ Tool::Delete(tool);
+
+ // Update the number of active heaters and extruder drives
+ activeExtruders = activeToolHeaters = numToolsToReport = 0;
+ for (Tool *t = toolList; t != nullptr; t = t->Next())
+ {
+ t->UpdateExtruderAndHeaterCount(activeExtruders, activeToolHeaters, numToolsToReport);
+ }
+ platform->UpdateConfiguredHeaters();
+ ToolsUpdated();
+}
+
+// Select the specified tool, putting the existing current tool into standby
+void RepRap::SelectTool(int toolNumber, bool simulating) noexcept
+{
+ ReadLockedPointer<Tool> const newTool = GetTool(toolNumber);
+ if (!simulating)
+ {
+ if (currentTool != nullptr && currentTool != newTool.Ptr())
+ {
+ currentTool->Standby();
+ }
+ if (newTool.IsNotNull())
+ {
+ newTool->Activate();
+ }
+ }
+ currentTool = newTool.Ptr();
+}
+
+void RepRap::PrintTool(int toolNumber, const StringRef& reply) const noexcept
+{
+ ReadLockedPointer<Tool> const tool = GetTool(toolNumber);
+ if (tool.IsNotNull())
+ {
+ tool->Print(reply);
+ }
+ else
+ {
+ reply.copy("Error: Attempt to print details of non-existent tool.\n");
+ }
+}
+
+void RepRap::StandbyTool(int toolNumber, bool simulating) noexcept
+{
+ ReadLockedPointer<Tool> const tool = GetTool(toolNumber);
+ if (tool.IsNotNull())
+ {
+ if (!simulating)
+ {
+ tool->Standby();
+ }
+ if (currentTool == tool.Ptr())
+ {
+ currentTool = nullptr;
+ }
+ }
+ else
+ {
+ platform->MessageF(ErrorMessage, "Attempt to standby a non-existent tool: %d\n", toolNumber);
+ }
+}
+
+ReadLockedPointer<Tool> RepRap::GetLockedCurrentTool() const noexcept
+{
+ ReadLocker lock(toolListLock);
+ return ReadLockedPointer<Tool>(lock, currentTool);
+}
+
+ReadLockedPointer<Tool> RepRap::GetTool(int toolNumber) const noexcept
+{
+ ReadLocker lock(toolListLock);
+ Tool* tool = toolList;
+ while (tool != nullptr)
+ {
+ if (tool->Number() == toolNumber)
+ {
+ return ReadLockedPointer<Tool>(lock, tool);
+ }
+ tool = tool->Next();
+ }
+ return ReadLockedPointer<Tool>(lock, nullptr); // not an error
+}
+
+// Return the current tool number, or -1 if no tool selected
+int RepRap::GetCurrentToolNumber() const noexcept
+{
+ return (currentTool == nullptr) ? -1 : currentTool->Number();
+}
+
+// Get the current tool, or failing that the default tool. May return nullptr if we can't
+// Called when a M104 or M109 command doesn't specify a tool number.
+ReadLockedPointer<Tool> RepRap::GetCurrentOrDefaultTool() const noexcept
+{
+ ReadLocker lock(toolListLock);
+ // If a tool is already selected, use that one, else use the lowest-numbered tool which is the one at the start of the tool list
+ return ReadLockedPointer<Tool>(lock, (currentTool != nullptr) ? currentTool : toolList);
+}
+
+// Return the lowest-numbered tool
+ReadLockedPointer<Tool> RepRap::GetFirstTool() const noexcept
+{
+ ReadLocker lock(toolListLock);
+ return ReadLockedPointer<Tool>(lock, toolList);
+}
+
+bool RepRap::IsHeaterAssignedToTool(int8_t heater) const noexcept
+{
+ ReadLocker lock(toolListLock);
+ for (Tool *tool = toolList; tool != nullptr; tool = tool->Next())
+ {
+ for (size_t i = 0; i < tool->HeaterCount(); i++)
+ {
+ if (tool->Heater(i) == heater)
+ {
+ // It's already in use by some tool
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+unsigned int RepRap::GetNumberOfContiguousTools() const noexcept
+{
+ int numTools = 0;
+ ReadLocker lock(toolListLock);
+ for (const Tool *t = toolList; t != nullptr && t->Number() == numTools; t = t->Next())
+ {
+ ++numTools;
+ }
+ return (unsigned int)numTools;
+}
+
+// Report the temperatures of one tool in M105 format
+void RepRap::ReportToolTemperatures(const StringRef& reply, const Tool *tool, bool includeNumber) const noexcept
+{
+ if (tool != nullptr && tool->HeaterCount() != 0)
+ {
+ if (reply.strlen() != 0)
+ {
+ reply.cat(' ');
+ }
+ if (includeNumber)
+ {
+ reply.catf("T%u", tool->Number());
+ }
+ else
+ {
+ reply.cat("T");
+ }
+
+ Heat& heat = reprap.GetHeat();
+ char sep = ':';
+ for (size_t i = 0; i < tool->HeaterCount(); ++i)
+ {
+ const int heater = tool->Heater(i);
+ reply.catf("%c%.1f /%.1f", sep, (double)heat.GetHeaterTemperature(heater), (double)heat.GetTargetTemperature(heater));
+ sep = ' ';
+ }
+ }
+}
+
+void RepRap::ReportAllToolTemperatures(const StringRef& reply) const noexcept
+{
+ ReadLocker lock(toolListLock);
+
+ // The following is believed to be compatible with Marlin and Octoprint, based on thread https://github.com/foosel/OctoPrint/issues/2590#issuecomment-385023980
+ ReportToolTemperatures(reply, currentTool, false);
+
+ for (const Tool *tool = toolList; tool != nullptr; tool = tool->Next())
+ {
+ ReportToolTemperatures(reply, tool, true);
+ }
+}
+
+GCodeResult RepRap::SetAllToolsFirmwareRetraction(GCodeBuffer& gb, const StringRef& reply, OutputBuffer*& outBuf) THROWS(GCodeException)
+{
+ GCodeResult rslt = GCodeResult::ok;
+ for (Tool *tool = toolList; tool != nullptr && rslt == GCodeResult::ok; tool = tool->Next())
+ {
+ rslt = tool->SetFirmwareRetraction(gb, reply, outBuf);
+ }
+ return rslt;
+}
+
+// Get the current axes used as X axis
+AxesBitmap RepRap::GetCurrentXAxes() const noexcept
+{
+ return Tool::GetXAxes(currentTool);
+}
+
+// Get the current axes used as Y axis
+AxesBitmap RepRap::GetCurrentYAxes() const noexcept
+{
+ return Tool::GetYAxes(currentTool);
+}
+
+// Get the current axes used as the specified axis
+AxesBitmap RepRap::GetCurrentAxisMapping(unsigned int axis) const noexcept
+{
+ return Tool::GetAxisMapping(currentTool, axis);
+}
+
+// Set the previous tool number. Inline because it is only called from one place.
+void RepRap::SetPreviousToolNumber() noexcept
+{
+ previousToolNumber = (currentTool != nullptr) ? currentTool->Number() : -1;
+}
+
+void RepRap::Tick() noexcept
+{
+ // Kicking the watchdog before it has been initialised may trigger it!
+ if (active)
+ {
+ WatchdogReset(); // kick the watchdog
+
+#if SAM4E || SAME70
+ WatchdogResetSecondary(); // kick the secondary watchdog
+#endif
+
+ if (!stopped)
+ {
+ platform->Tick();
+ ++ticksInSpinState;
+ ++heatTaskIdleTicks;
+ const bool heatTaskStuck = (heatTaskIdleTicks >= MaxTicksInSpinState);
+ if (heatTaskStuck || ticksInSpinState >= MaxTicksInSpinState) // if we stall for 20 seconds, save diagnostic data and reset
+ {
+ stopped = true;
+ heat->SwitchOffAll(true);
+ platform->EmergencyDisableDrivers();
+
+ // We now save the stack when we get stuck in a spin loop
+ __asm volatile("mrs r2, psp");
+ register const uint32_t * stackPtr asm ("r2"); // we want the PSP not the MSP
+ SoftwareReset(
+ (heatTaskStuck) ? SoftwareResetReason::heaterWatchdog : SoftwareResetReason::stuckInSpin,
+ stackPtr + 5); // discard uninteresting registers, keep LR PC PSR
+ }
+ }
+ }
+}
+
+// Return true if we are close to timeout
+bool RepRap::SpinTimeoutImminent() const noexcept
+{
+ return ticksInSpinState >= HighTicksInSpinState;
+}
+
+// Get the JSON status response for the web server (or later for the M105 command).
+// Type 1 is the ordinary JSON status response.
+// Type 2 is the same except that static parameters are also included.
+// Type 3 is the same but instead of static parameters we report print estimation values.
+OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) const noexcept
+{
+ // Need something to write to...
+ OutputBuffer *response;
+ if (!OutputBuffer::Allocate(response))
+ {
+ return nullptr;
+ }
+
+ // Machine status
+ response->printf("{\"status\":\"%c\",\"coords\":{", GetStatusCharacter());
+
+ // Homed axes
+ const size_t numVisibleAxes = gCodes->GetVisibleAxes();
+ AppendIntArray(response, "axesHomed", numVisibleAxes, [this](size_t axis) noexcept { return (gCodes->IsAxisHomed(axis)) ? 1 : 0; });
+
+ // XYZ positions
+ // Coordinates may be NaNs or infinities, for example when delta or SCARA homing fails. We must replace any NaNs or infinities to avoid JSON parsing errors.
+ // Ideally we would report "unknown" or similar for axis positions that are not known because we haven't homed them, but that requires changes to both DWC and PanelDue.
+ // So we report 9999.9 instead.
+
+ // First the user coordinates
+#if SUPPORT_WORKPLACE_COORDINATES
+ response->catf(",\"wpl\":%u,", gCodes->GetWorkplaceCoordinateSystemNumber());
+#else
+ response->cat(',');
+#endif
+
+ AppendFloatArray(response, "xyz", numVisibleAxes, [this](size_t axis) noexcept { return gCodes->GetUserCoordinate(axis); }, 3);
+
+ // Machine coordinates
+ response->cat(',');
+ AppendFloatArray(response, "machine", numVisibleAxes, [this](size_t axis) noexcept { return move->LiveCoordinate(axis, currentTool); }, 3);
+
+ // Actual extruder positions since power up, last G92 or last M23
+ response->cat(',');
+ AppendFloatArray(response, "extr", GetExtrudersInUse(), [this](size_t extruder) noexcept { return move->LiveCoordinate(ExtruderToLogicalDrive(extruder), currentTool); }, 1);
+
+ // Current speeds
+ response->catf("},\"speeds\":{\"requested\":%.1f,\"top\":%.1f}", (double)move->GetRequestedSpeed(), (double)move->GetTopSpeed());
+
+ // Current tool number
+ response->catf(",\"currentTool\":%d", GetCurrentToolNumber());
+
+ // Output notifications
+ {
+ const bool sendBeep = ((source == ResponseSource::AUX || !platform->IsAuxEnabled(0) || platform->IsAuxRaw(0)) && beepDuration != 0 && beepFrequency != 0);
+ const bool sendMessage = !message.IsEmpty();
+
+ float timeLeft = 0.0;
+ MutexLocker lock(messageBoxMutex);
+
+ if (mbox.active && mbox.timer != 0)
+ {
+ timeLeft = (float)(mbox.timeout) / 1000.0 - (float)(millis() - mbox.timer) / 1000.0;
+ }
+
+ if (sendBeep || sendMessage || mbox.active)
+ {
+ response->cat(",\"output\":{");
+
+ // Report beep values
+ if (sendBeep)
+ {
+ response->catf("\"beepDuration\":%u,\"beepFrequency\":%u", beepDuration, beepFrequency);
+ if (sendMessage || mbox.active)
+ {
+ response->cat(',');
+ }
+ }
+
+ // Report message
+ if (sendMessage)
+ {
+ response->catf("\"message\":\"%.s\"", message.c_str());
+ if (mbox.active)
+ {
+ response->cat(',');
+ }
+ }
+
+ // Report message box
+ if (mbox.active)
+ {
+ response->catf("\"msgBox\":{\"msg\":\"%.s\",\"title\":\"%.s\",\"mode\":%d,\"seq\":%" PRIu32 ",\"timeout\":%.1f,\"controls\":%u}",
+ mbox.message.c_str(), mbox.title.c_str(), mbox.mode, mbox.seq, (double)timeLeft, (unsigned int)mbox.controls.GetRaw());
+ }
+ response->cat('}');
+ }
+ }
+
+ // ATX power
+ response->catf(",\"params\":{\"atxPower\":%d", gCodes->AtxPowerControlled() ? (platform->AtxPower() ? 1 : 0) : -1);
+
+ // Parameters
+ {
+ // Cooling fan values
+ response->cat(',');
+ const size_t numFans = fansManager->GetNumFansToReport();
+ AppendIntArray(response, "fanPercent", numFans,
+ [this](size_t fan) noexcept
+ {
+ const float fanValue = fansManager->GetFanValue(fan);
+ return (fanValue < 0.0) ? -1 : (int)lrintf(fanValue * 100.0);
+ });
+
+ // Cooling fan names
+ if (type == 2)
+ {
+ response->cat(',');
+ AppendStringArray(response, "fanNames", numFans, [this](size_t fan) noexcept { return fansManager->GetFanName(fan); });
+ }
+
+ // Speed and Extrusion factors in %
+ response->catf(",\"speedFactor\":%.1f,", (double)(gCodes->GetSpeedFactor() * 100.0));
+ AppendFloatArray(response, "extrFactors", GetExtrudersInUse(), [this](size_t extruder) noexcept { return gCodes->GetExtrusionFactor(extruder) * 100.0; }, 1);
+
+ // Z babystepping
+ response->catf(",\"babystep\":%.3f}", (double)gCodes->GetTotalBabyStepOffset(Z_AXIS));
+
+ // G-code reply sequence for webserver (sequence number for AUX is handled later)
+ if (source == ResponseSource::HTTP)
+ {
+ response->catf(",\"seq\":%" PRIu32, network->GetHttpReplySeq());
+ }
+
+ // Sensors
+ response->cat(",\"sensors\":{");
+
+ // Probe
+ const auto zp = platform->GetZProbeOrDefault(0);
+ const int v0 = zp->GetReading();
+ int v1;
+ switch (zp->GetSecondaryValues(v1))
+ {
+ case 1:
+ response->catf("\"probeValue\":%d,\"probeSecondary\":[%d],", v0, v1);
+ break;
+ default:
+ response->catf("\"probeValue\":%d,", v0);
+ break;
+ }
+
+ // Send fan RPM value(s)
+ AppendIntArray(response, "fanRPM", numFans, [this](size_t fan) noexcept { return (int)fansManager->GetFanRPM(fan); });
+ response->cat('}');
+ }
+
+ /* Temperatures */
+ {
+ response->cat(",\"temps\":{");
+
+ /* Bed */
+ const int8_t bedHeater = (MaxBedHeaters > 0) ? heat->GetBedHeater(0) : -1;
+ if (bedHeater != -1)
+ {
+ response->catf("\"bed\":{\"current\":%.1f,\"active\":%.1f,\"standby\":%.1f,\"state\":%u,\"heater\":%d},",
+ (double)heat->GetHeaterTemperature(bedHeater), (double)heat->GetActiveTemperature(bedHeater), (double)heat->GetStandbyTemperature(bedHeater),
+ heat->GetStatus(bedHeater).ToBaseType(), bedHeater);
+ }
+
+ /* Chamber */
+ const int8_t chamberHeater = (MaxChamberHeaters > 0) ? heat->GetChamberHeater(0) : -1;
+ if (chamberHeater != -1)
+ {
+ response->catf("\"chamber\":{\"current\":%.1f,\"active\":%.1f,\"state\":%u,\"heater\":%d},",
+ (double)heat->GetHeaterTemperature(chamberHeater), (double)heat->GetActiveTemperature(chamberHeater),
+ heat->GetStatus(chamberHeater).ToBaseType(), chamberHeater);
+ }
+
+ /* Cabinet */
+ const int8_t cabinetHeater = (MaxChamberHeaters > 1) ? heat->GetChamberHeater(1) : -1;
+ if (cabinetHeater != -1)
+ {
+ response->catf("\"cabinet\":{\"current\":%.1f,\"active\":%.1f,\"state\":%u,\"heater\":%d},",
+ (double)heat->GetHeaterTemperature(cabinetHeater), (double)heat->GetActiveTemperature(cabinetHeater),
+ heat->GetStatus(cabinetHeater).ToBaseType(), cabinetHeater);
+ }
+
+ /* Heaters */
+
+ // Current temperatures
+ {
+ const size_t numHeaters = heat->GetNumHeatersToReport();
+ AppendFloatArray(response, "current", numHeaters, [this](size_t heater) noexcept { return heat->GetHeaterTemperature(heater); }, 1);
+
+ // Current states
+ response->cat(',');
+ AppendIntArray(response, "state", numHeaters, [this](size_t heater) noexcept { return (int)heat->GetStatus(heater).ToBaseType(); });
+
+ // Names of the sensors use to control heaters
+ if (type == 2)
+ {
+ response->cat(',');
+ AppendStringArray(response, "names", numHeaters, [this](size_t heater) noexcept { return heat->GetHeaterSensorName(heater); });
+ }
+ }
+
+ // Tool temperatures
+ response->cat(",\"tools\":{\"active\":[");
+ {
+ ReadLocker lock(toolListLock);
+ for (const Tool *tool = toolList; tool != nullptr; tool = tool->Next())
+ {
+ AppendFloatArray(response, nullptr, tool->heaterCount, [tool](unsigned int n) noexcept { return tool->activeTemperatures[n]; }, 1);
+ if (tool->Next() != nullptr)
+ {
+ response->cat(',');
+ }
+ }
+
+ response->cat("],\"standby\":[");
+ for (const Tool *tool = toolList; tool != nullptr; tool = tool->Next())
+ {
+ AppendFloatArray(response, nullptr, tool->heaterCount, [tool](unsigned int n) noexcept { return tool->standbyTemperatures[n]; }, 1);
+ if (tool->Next() != nullptr)
+ {
+ response->cat(',');
+ }
+ }
+ }
+
+ // Named extra temperature sensors
+ // TODO don't send the ones that we send in "names"
+ response->cat("]},\"extra\":[");
+ bool first = true;
+ unsigned int nextSensorNumber = 0;
+ for (;;)
+ {
+ const auto sensor = heat->FindSensorAtOrAbove(nextSensorNumber);
+ if (sensor.IsNull())
+ {
+ break;
+ }
+ const char * const nm = sensor->GetSensorName();
+ if (nm != nullptr)
+ {
+ if (!first)
+ {
+ response->cat(',');
+ }
+ first = false;
+ float temp;
+ (void)sensor->GetLatestTemperature(temp);
+ response->catf("{\"name\":\"%.s\",\"temp\":%.1f}", nm, HideNan(temp));
+ }
+ nextSensorNumber = sensor->GetSensorNumber() + 1;
+ }
+
+ response->cat("]}");
+ }
+
+ // Time since last reset
+ response->catf(",\"time\":%.1f", (double)(millis64()/1000u));
+
+#if SUPPORT_SCANNER
+ // Scanner
+ if (scanner->IsEnabled())
+ {
+ response->catf(",\"scanner\":{\"status\":\"%c\"", scanner->GetStatusCharacter());
+ response->catf(",\"progress\":%.1f}", (double)(scanner->GetProgress() * 100.0));
+ }
+#endif
+
+ // Spindles
+ if (gCodes->GetMachineType() == MachineType::cnc || type == 2)
+ {
+ size_t numSpindles = MaxSpindles;
+ while (numSpindles != 0 && platform->AccessSpindle(numSpindles - 1).GetState() == SpindleState::unconfigured)
+ {
+ --numSpindles;
+ }
+
+ if (numSpindles != 0)
+ {
+ response->cat(",\"spindles\":[");
+ for (size_t i = 0; i < numSpindles; i++)
+ {
+ if (i != 0)
+ {
+ response->cat(',');
+ }
+
+ const Spindle& spindle = platform->AccessSpindle(i);
+ response->catf("{\"current\":%" PRIi32 ",\"active\":%" PRIi32 ",\"state\":\"%s\"}", spindle.GetCurrentRpm(), spindle.GetRpm(), spindle.GetState().ToString());
+ }
+ response->cat(']');
+ }
+ }
+
+#if SUPPORT_LASER
+ if (gCodes->GetMachineType() == MachineType::laser)
+ {
+ response->catf(",\"laser\":%.1f", (double)(gCodes->GetLaserPwm() * 100.0)); // 2020-04-24: return the configured laser PWM even if the laser is temporarily turned off
+ }
+#endif
+
+ /* Extended Status Response */
+ if (type == 2)
+ {
+ // Cold Extrude/Retract
+ response->catf(",\"coldExtrudeTemp\":%.1f", (double)(heat->ColdExtrude() ? 0.0f : heat->GetExtrusionMinTemp()));
+ response->catf(",\"coldRetractTemp\":%.1f", (double)(heat->ColdExtrude() ? 0.0f : heat->GetRetractionMinTemp()));
+
+ // Compensation type
+ response->cat(",\"compensation\":");
+ if (move->IsUsingMesh())
+ {
+ response->cat("\"Mesh\"");
+ }
+ else
+ {
+ response->cat("\"None\"");
+ }
+
+ // Controllable Fans
+ FansBitmap controllableFans;
+ for (size_t fan = 0; fan < MaxFans; fan++)
+ {
+ if (fansManager->IsFanControllable(fan))
+ {
+ controllableFans.SetBit(fan);
+ }
+ }
+ response->catf(",\"controllableFans\":%lu", controllableFans.GetRaw());
+
+ // Maximum hotend temperature - DWC just wants the highest one
+ response->catf(",\"tempLimit\":%.1f", (double)(heat->GetHighestTemperatureLimit()));
+
+ // Endstops
+ uint32_t endstops = 0;
+ const size_t numTotalAxes = gCodes->GetTotalAxes();
+ for (size_t axis = 0; axis < numTotalAxes; axis++)
+ {
+ if (platform->GetEndstops().Stopped(axis))
+ {
+ endstops |= (1u << axis);
+ }
+ }
+ response->catf(",\"endstops\":%" PRIu32, endstops);
+
+ // Firmware name, machine geometry and number of axes
+ response->catf(",\"firmwareName\":\"%s\",\"firmwareVersion\":\"%s\",\"geometry\":\"%s\",\"axes\":%u,\"totalAxes\":%u,\"axisNames\":\"%s\"",
+ FIRMWARE_NAME, VERSION, move->GetGeometryString(), numVisibleAxes, numTotalAxes, gCodes->GetAxisLetters());
+
+#if HAS_MASS_STORAGE
+ // Total and mounted volumes
+ size_t mountedCards = 0;
+ for (size_t i = 0; i < NumSdCards; i++)
+ {
+ if (MassStorage::IsDriveMounted(i))
+ {
+ mountedCards |= (1 << i);
+ }
+ }
+ response->catf(",\"volumes\":%u,\"mountedVolumes\":%u", NumSdCards, mountedCards);
+#endif
+
+ // Machine mode and name
+ response->catf(",\"mode\":\"%.s\",\"name\":\"%.s\"", gCodes->GetMachineModeString(), myName.c_str());
+
+ // Probe trigger threshold, trigger height, type
+ {
+ const auto zp = platform->GetZProbeOrDefault(0);
+ response->catf(",\"probe\":{\"threshold\":%d,\"height\":%.2f,\"type\":%u}",
+ zp->GetAdcValue(), (double)zp->GetConfiguredTriggerHeight(), (unsigned int)zp->GetProbeType());
+ }
+
+ // Tool Mapping
+ {
+ response->cat(",\"tools\":[");
+ ReadLocker lock(toolListLock);
+ for (Tool *tool = toolList; tool != nullptr; tool = tool->Next())
+ {
+ // Number
+ response->catf("{\"number\":%d,", tool->Number());
+
+ // Name
+ const char * const toolName = tool->GetName();
+ if (toolName[0] != 0)
+ {
+ response->catf("\"name\":\"%.s\",", toolName);
+ }
+
+ // Heaters
+ AppendIntArray(response, "heaters", tool->HeaterCount(), [tool](size_t heater) noexcept { return tool->Heater(heater); });
+
+ // Extruder drives
+ response->cat(',');
+ AppendIntArray(response, "drives", tool->DriveCount(), [tool](size_t drive) noexcept { return tool->Drive(drive); });
+
+ // Axis mapping
+ response->cat(",\"axisMap\":[[");
+ tool->GetXAxisMap().Iterate
+ ([response](unsigned int xi, unsigned int count) noexcept
+ {
+ if (count != 0)
+ {
+ response->cat(',');
+ }
+ response->catf("%u", xi);
+ }
+ );
+ response->cat("],[");
+
+ tool->GetYAxisMap().Iterate
+ ([response](unsigned int yi, unsigned int count) noexcept
+ {
+ if (count != 0)
+ {
+ response->cat(',');
+ }
+ response->catf("%u", yi);
+ }
+ );
+ response->cat("]]");
+
+ // Fan mapping
+ response->catf(",\"fans\":%lu", tool->GetFanMapping().GetRaw());
+
+ // Filament (if any)
+ if (tool->GetFilament() != nullptr)
+ {
+ response->catf(",\"filament\":\"%.s\"", tool->GetFilament()->GetName());
+ }
+
+ // Spindle (if configured)
+ if (tool->spindleNumber > -1)
+ {
+ response->catf(",\"spindle\":%d,\"spindleRpm\":%" PRIi32, tool->spindleNumber, tool->spindleRpm);
+ }
+
+ // Offsets
+ response->cat(',');
+ AppendFloatArray(response, "offsets", numVisibleAxes, [tool](size_t axis) noexcept { return tool->GetOffset(axis); }, 2);
+
+ // Do we have any more tools?
+ response->cat((tool->Next() != nullptr) ? "}," : "}");
+ }
+ response->cat(']');
+ }
+
+ // MCU temperatures
+#if HAS_CPU_TEMP_SENSOR
+ {
+ const MinMaxCurrent temps = platform->GetMcuTemperatures();
+ response->catf(",\"mcutemp\":{\"min\":%.1f,\"cur\":%.1f,\"max\":%.1f}", (double)temps.min, (double)temps.current, (double)temps.max);
+ }
+#endif
+
+#if HAS_VOLTAGE_MONITOR
+ // Power in voltages
+ {
+ const MinMaxCurrent voltages = platform->GetPowerVoltages();
+ response->catf(",\"vin\":{\"min\":%.1f,\"cur\":%.1f,\"max\":%.1f}", (double)voltages.min, (double)voltages.current, (double)voltages.max);
+ }
+#endif
+
+#if HAS_12V_MONITOR
+ // Power in voltages
+ {
+ const MinMaxCurrent voltages = platform->GetV12Voltages();
+ response->catf(",\"v12\":{\"min\":%.1f,\"cur\":%.1f,\"max\":%.1f}", (double)voltages.min, (double)voltages.current, (double)voltages.max);
+ }
+#endif
+ }
+ else if (type == 3)
+ {
+ // Current Layer
+ response->catf(",\"currentLayer\":%d", printMonitor->GetCurrentLayer());
+
+ // Current Layer Time
+ response->catf(",\"currentLayerTime\":%.1f,", (double)(printMonitor->GetCurrentLayerTime()));
+
+ // Raw Extruder Positions
+ AppendFloatArray(response, "extrRaw", GetExtrudersInUse(), [this](size_t extruder) noexcept { return gCodes->GetRawExtruderTotalByDrive(extruder); }, 1);
+
+ // Fraction of file printed
+ response->catf(",\"fractionPrinted\":%.1f", (double)((printMonitor->IsPrinting()) ? (printMonitor->FractionOfFilePrinted() * 100.0) : 0.0));
+
+ // Byte position of the file being printed
+ response->catf(",\"filePosition\":%lu", gCodes->GetFilePosition());
+
+ // First Layer Duration is no longer included
+
+ // First Layer Height is no longer included
+
+ // Print Duration
+ response->catf(",\"printDuration\":%.1f", (double)(printMonitor->GetPrintDuration()));
+
+ // Warm-Up Time
+ response->catf(",\"warmUpDuration\":%.1f", (double)(printMonitor->GetWarmUpDuration()));
+
+ /* Print Time Estimations */
+ response->catf(",\"timesLeft\":{\"file\":%.1f,\"filament\":%.1f}", (double)(printMonitor->EstimateTimeLeft(fileBased)), (double)(printMonitor->EstimateTimeLeft(filamentBased)));
+ }
+
+ response->cat('}');
+ return response;
+}
+
+OutputBuffer *RepRap::GetConfigResponse() noexcept
+{
+ // We need some resources to return a valid config response...
+ OutputBuffer *response;
+ if (!OutputBuffer::Allocate(response))
+ {
+ return nullptr;
+ }
+
+ const size_t numAxes = gCodes->GetVisibleAxes();
+
+ // Axis minima
+ response->copy('{');
+ AppendFloatArray(response, "axisMins", numAxes, [this](size_t axis) noexcept { return platform->AxisMinimum(axis); }, 2);
+
+ // Axis maxima
+ response->cat(',');
+ AppendFloatArray(response, "axisMaxes", numAxes, [this](size_t axis) noexcept { return platform->AxisMaximum(axis); }, 2);
+
+ // Accelerations
+ response->cat(',');
+ AppendFloatArray(response, "accelerations", MaxAxesPlusExtruders, [this](size_t drive) noexcept { return platform->Acceleration(drive); }, 2);
+
+ // Motor currents
+ response->cat(',');
+ AppendIntArray(response, "currents", MaxAxesPlusExtruders, [this](size_t drive) noexcept { return (int)platform->GetMotorCurrent(drive, 906); });
+
+ // Firmware details
+ response->catf(",\"firmwareElectronics\":\"%.s", platform->GetElectronicsString());
+#ifdef DUET_NG
+ const char* expansionName = DuetExpansion::GetExpansionBoardName();
+ if (expansionName != nullptr)
+ {
+ response->catf(" + %.s", expansionName);
+ }
+ const char* additionalExpansionName = DuetExpansion::GetAdditionalExpansionBoardName();
+ if (additionalExpansionName != nullptr)
+ {
+ response->catf(" + %.s", additionalExpansionName);
+ }
+#endif
+ response->catf("\",\"firmwareName\":\"%.s\",\"firmwareVersion\":\"%.s\"", FIRMWARE_NAME, VERSION);
+#ifdef BOARD_SHORT_NAME
+ response->catf(",\"boardName\":\"%.s\"", BOARD_SHORT_NAME);
+#endif
+
+#if HAS_WIFI_NETWORKING
+ // If we have WiFi networking, send the WiFi module firmware version
+# ifdef DUET_NG
+ if (platform->IsDuetWiFi())
+# endif
+ {
+ response->catf(",\"dwsVersion\":\"%.s\"", network->GetWiFiServerVersion());
+ }
+#endif
+
+ response->catf(",\"firmwareDate\":\"%.s\"", DATE);
+
+#if HAS_MASS_STORAGE
+ // System files folder
+ response->catf(", \"sysdir\":\"%.s\"", platform->GetSysDir().Ptr());
+#endif
+
+ // Motor idle parameters
+ response->catf(",\"idleCurrentFactor\":%.1f", (double)(platform->GetIdleCurrentFactor() * 100.0));
+ response->catf(",\"idleTimeout\":%.1f,", (double)(move->IdleTimeout()));
+
+ // Minimum feedrates
+ AppendFloatArray(response, "minFeedrates", MaxAxesPlusExtruders, [this](size_t drive) noexcept { return platform->GetInstantDv(drive); }, 2);
+
+ // Maximum feedrates
+ response->cat(',');
+ AppendFloatArray(response, "maxFeedrates", MaxAxesPlusExtruders, [this](size_t drive) noexcept { return platform->MaxFeedrate(drive); }, 2);
+
+ // Config file is no longer included, because we can use rr_configfile or M503 instead
+ response->cat('}');
+
+ return response;
+}
+
+// Get the JSON status response for PanelDue
+// Type 0 was the old-style webserver status response, but is no longer supported.
+// Type 1 is the new-style webserver status response.
+// Type 2 is the M105 S2 response, which is like the new-style status response but some fields are omitted.
+// Type 3 is the M105 S3 response, which is like the M105 S2 response except that static values are also included.
+// 'seq' is the response sequence number, if it is not -1 and we have a different sequence number then we send the gcode response
+OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) const noexcept
+{
+ // Need something to write to...
+ OutputBuffer *response;
+ if (!OutputBuffer::Allocate(response))
+ {
+ // Should never happen
+ return nullptr;
+ }
+
+ // Send the status. Note that 'S' has always meant that the machine is halted in this version of the status response, so we use A for pAused.
+ char ch = GetStatusCharacter();
+ if (ch == 'S') // if paused then send 'A'
+ {
+ ch = 'A';
+ }
+ else if (ch == 'H') // if halted then send 'S'
+ {
+ ch = 'S';
+ }
+ response->printf("{\"status\":\"%c\",\"heaters\":", ch);
+
+ // Send the heater actual temperatures. If there is no bed heater, send zero for PanelDue.
+ const int8_t bedHeater = (MaxBedHeaters > 0) ? heat->GetBedHeater(0) : -1;
+ ch = ',';
+ response->catf("[%.1f", (double)((bedHeater == -1) ? 0.0 : heat->GetHeaterTemperature(bedHeater)));
+ for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++)
+ {
+ response->catf("%c%.1f", ch, (double)(heat->GetHeaterTemperature(heater)));
+ ch = ',';
+ }
+ response->cat((ch == '[') ? "[]" : "]");
+
+ // Send the heater active temperatures
+ response->catf(",\"active\":[%.1f", (double)((bedHeater == -1) ? 0.0 : heat->GetActiveTemperature(bedHeater)));
+ for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++)
+ {
+ response->catf(",%.1f", (double)(heat->GetActiveTemperature(heater)));
+ }
+ response->cat(']');
+
+ // Send the heater standby temperatures
+ response->catf(",\"standby\":[%.1f", (double)((bedHeater == -1) ? 0.0 : heat->GetStandbyTemperature(bedHeater)));
+ for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++)
+ {
+ response->catf(",%.1f", (double)(heat->GetStandbyTemperature(heater)));
+ }
+ response->cat(']');
+
+ // Send the heater statuses (0=off, 1=standby, 2=active, 3 = fault)
+ response->catf(",\"hstat\":[%u", (bedHeater == -1) ? 0 : heat->GetStatus(bedHeater).ToBaseType());
+ for (size_t heater = DefaultE0Heater; heater < GetToolHeatersInUse(); heater++)
+ {
+ response->catf(",%u", heat->GetStatus(heater).ToBaseType());
+ }
+ response->cat("],");
+
+ // User coordinates
+ const size_t numVisibleAxes = gCodes->GetVisibleAxes();
+ AppendFloatArray(response, "pos", numVisibleAxes, [this](size_t axis) noexcept { return gCodes->GetUserCoordinate(axis); }, 3);
+
+ // Machine coordinates
+ response->cat(',');
+ AppendFloatArray(response, "machine", numVisibleAxes, [this](size_t axis) noexcept { return move->LiveCoordinate(axis, currentTool); }, 3);
+
+ // Send the speed and extruder override factors
+ response->catf(",\"sfactor\":%.1f,", (double)(gCodes->GetSpeedFactor() * 100.0));
+ AppendFloatArray(response, "efactor", GetExtrudersInUse(), [this](size_t extruder) noexcept { return gCodes->GetExtrusionFactor(extruder) * 100.0; }, 1);
+
+ // Send the baby stepping offset
+ response->catf(",\"babystep\":%.03f", (double)(gCodes->GetTotalBabyStepOffset(Z_AXIS)));
+
+ // Send the current tool number
+ response->catf(",\"tool\":%d", GetCurrentToolNumber());
+
+ // Send the Z probe value
+ const auto zp = platform->GetZProbeOrDefault(0);
+ const int v0 = zp->GetReading();
+ int v1;
+ switch (zp->GetSecondaryValues(v1))
+ {
+ case 1:
+ response->catf(",\"probe\":\"%d (%d)\"", v0, v1);
+ break;
+ default:
+ response->catf(",\"probe\":\"%d\"", v0);
+ break;
+ }
+
+ // Send the fan settings, for PanelDue firmware 1.13 and later
+ // Currently, PanelDue assumes that the first value is the print cooling fan speed and only uses that one, so send the mapped fan speed first
+ response->catf(",\"fanPercent\":[%.1f", (double)(gCodes->GetMappedFanSpeed() * 100.0));
+ for (size_t i = 0; i < MaxFans; ++i)
+ {
+ const float fanValue = fansManager->GetFanValue(i);
+ response->catf(",%d", (fanValue < 0.0) ? -1 : (int)lrintf(fanValue * 100.0));
+ }
+ response->cat("],");
+
+ // Send fan RPM value(s)
+ AppendIntArray(response, "fanRPM", fansManager->GetNumFansToReport(), [this](size_t fan) { return (int)fansManager->GetFanRPM(fan);});
+
+ // Send the home state. To keep the messages short, we send 1 for homed and 0 for not homed, instead of true and false.
+ response->cat(',');
+ AppendIntArray(response, "homed", numVisibleAxes, [this](size_t axis) noexcept { return (gCodes->IsAxisHomed(axis)) ? 1 : 0; });
+
+ if (printMonitor->IsPrinting())
+ {
+ // Send the fraction printed
+ response->catf(",\"fraction_printed\":%.4f", (double)max<float>(0.0, printMonitor->FractionOfFilePrinted()));
+ }
+
+ // Short messages are now pushed directly to PanelDue, so don't include them here as well
+ // We no longer send the amount of http buffer space here because the web interface doesn't use these forms of status response
+
+ // Deal with the message box.
+ // Don't send it if we are flashing firmware, because when we flash firmware we send messages directly to PanelDue and we don't want them to get cleared.
+ if (!gCodes->IsFlashing())
+ {
+ float timeLeft = 0.0;
+ MutexLocker lock(messageBoxMutex);
+
+ if (mbox.active && mbox.timer != 0)
+ {
+ timeLeft = (float)(mbox.timeout) / 1000.0 - (float)(millis() - mbox.timer) / 1000.0;
+ }
+
+ if (mbox.active)
+ {
+ response->catf(",\"msgBox.mode\":%d,\"msgBox.seq\":%" PRIu32 ",\"msgBox.timeout\":%.1f,\"msgBox.controls\":%u,\"msgBox.msg\":\"%.s\",\"msgBox.title\":\"%.s\"",
+ mbox.mode, mbox.seq, (double)timeLeft, (unsigned int)mbox.controls.GetRaw(), mbox.message.c_str(), mbox.title.c_str());
+ }
+ else
+ {
+ response->cat(",\"msgBox.mode\":-1"); // tell PanelDue that there is no active message box
+ }
+ }
+
+ if (type == 2)
+ {
+ if (printMonitor->IsPrinting())
+ {
+ // Send estimated times left based on file progress, filament usage, and layers
+ response->catf(",\"timesLeft\":[%.1f,%.1f,0.0]",
+ (double)(printMonitor->EstimateTimeLeft(fileBased)),
+ (double)(printMonitor->EstimateTimeLeft(filamentBased)));
+ }
+ }
+ else if (type == 3)
+ {
+ // Add the static fields
+ response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"totalAxes\":%u,\"axisNames\":\"%s\",\"volumes\":%u,\"numTools\":%u,\"myName\":\"%.s\",\"firmwareName\":\"%.s\"",
+ move->GetGeometryString(), numVisibleAxes, gCodes->GetTotalAxes(), gCodes->GetAxisLetters(), NumSdCards, GetNumberOfContiguousTools(), myName.c_str(), FIRMWARE_NAME);
+ }
+
+ response->cat("}\n"); // include a newline to help PanelDue resync
+ return response;
+}
+
+#if HAS_MASS_STORAGE
+
+// Get the list of files in the specified directory in JSON format. PanelDue uses this one, so include a newline at the end.
+// If flagDirs is true then we prefix each directory with a * character.
+OutputBuffer *RepRap::GetFilesResponse(const char *dir, unsigned int startAt, bool flagsDirs) noexcept
+{
+ // Need something to write to...
+ OutputBuffer *response;
+ if (!OutputBuffer::Allocate(response))
+ {
+ return nullptr;
+ }
+
+ response->printf("{\"dir\":\"%.s\",\"first\":%u,\"files\":[", dir, startAt);
+ unsigned int err;
+ unsigned int nextFile = 0;
+
+ if (!MassStorage::CheckDriveMounted(dir))
+ {
+ err = 1;
+ }
+ else if (!MassStorage::DirectoryExists(dir))
+ {
+ err = 2;
+ }
+ else
+ {
+ err = 0;
+ FileInfo fileInfo;
+ unsigned int filesFound = 0;
+ bool gotFile = MassStorage::FindFirst(dir, fileInfo);
+
+ size_t bytesLeft = OutputBuffer::GetBytesLeft(response); // don't write more bytes than we can
+
+ while (gotFile)
+ {
+ if (fileInfo.fileName[0] != '.') // ignore Mac resource files and Linux hidden files
+ {
+ if (filesFound >= startAt)
+ {
+ // Make sure we can end this response properly
+ if (bytesLeft < fileInfo.fileName.strlen() * 2 + 20)
+ {
+ // No more space available - stop here
+ MassStorage::AbandonFindNext();
+ nextFile = filesFound;
+ break;
+ }
+
+ // Write separator and filename
+ if (filesFound != startAt)
+ {
+ bytesLeft -= response->cat(',');
+ }
+
+ bytesLeft -= response->catf((flagsDirs && fileInfo.isDirectory) ? "\"*%.s\"" : "\"%.s\"", fileInfo.fileName.c_str());
+ }
+ ++filesFound;
+ }
+ gotFile = MassStorage::FindNext(fileInfo);
+ }
+ }
+
+ if (err != 0)
+ {
+ response->catf("],\"err\":%u}\n", err);
+ }
+ else
+ {
+ response->catf("],\"next\":%u,\"err\":%u}\n", nextFile, err);
+ }
+
+ if (response->HadOverflow())
+ {
+ OutputBuffer::ReleaseAll(response);
+ }
+ return response;
+}
+
+// Get a JSON-style filelist including file types and sizes
+OutputBuffer *RepRap::GetFilelistResponse(const char *dir, unsigned int startAt) noexcept
+{
+ // Need something to write to...
+ OutputBuffer *response;
+ if (!OutputBuffer::Allocate(response))
+ {
+ return nullptr;
+ }
+
+ response->printf("{\"dir\":\"%.s\",\"first\":%u,\"files\":[", dir, startAt);
+ unsigned int err;
+ unsigned int nextFile = 0;
+
+ if (!MassStorage::CheckDriveMounted(dir))
+ {
+ err = 1;
+ }
+ else if (!MassStorage::DirectoryExists(dir))
+ {
+ err = 2;
+ }
+ else
+ {
+ err = 0;
+ FileInfo fileInfo;
+ unsigned int filesFound = 0;
+ bool gotFile = MassStorage::FindFirst(dir, fileInfo);
+ size_t bytesLeft = OutputBuffer::GetBytesLeft(response); // don't write more bytes than we can
+
+ while (gotFile)
+ {
+ if (fileInfo.fileName[0] != '.') // ignore Mac resource files and Linux hidden files
+ {
+ if (filesFound >= startAt)
+ {
+ // Make sure we can end this response properly
+ if (bytesLeft < fileInfo.fileName.strlen() * 2 + 50)
+ {
+ // No more space available - stop here
+ MassStorage::AbandonFindNext();
+ nextFile = filesFound;
+ break;
+ }
+
+ // Write delimiter
+ if (filesFound != startAt)
+ {
+ bytesLeft -= response->cat(',');
+ }
+
+ // Write another file entry
+ bytesLeft -= response->catf("{\"type\":\"%c\",\"name\":\"%.s\",\"size\":%" PRIu32,
+ fileInfo.isDirectory ? 'd' : 'f', fileInfo.fileName.c_str(), fileInfo.size);
+ tm timeInfo;
+ gmtime_r(&fileInfo.lastModified, &timeInfo);
+ if (timeInfo.tm_year <= /*19*/80)
+ {
+ // Don't send the last modified date if it is invalid
+ bytesLeft -= response->cat('}');
+ }
+ else
+ {
+ bytesLeft -= response->catf(",\"date\":\"%04u-%02u-%02uT%02u:%02u:%02u\"}",
+ timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
+ }
+ }
+ ++filesFound;
+ }
+ gotFile = MassStorage::FindNext(fileInfo);
+ }
+ }
+
+ // If there is no error, don't append "err":0 because if we do then DWC thinks there has been an error - looks like it doesn't check the value
+ if (err != 0)
+ {
+ response->catf("],\"err\":%u}\n", err);
+ }
+ else
+ {
+ response->catf("],\"next\":%u}\n", nextFile);
+ }
+
+ if (response->HadOverflow())
+ {
+ OutputBuffer::ReleaseAll(response);
+ }
+ return response;
+}
+
+#endif
+
+// Get information for the specified file, or the currently printing file (if 'filename' is null or empty), in JSON format
+// Return GCodeResult::Wating if the file doesn't exist, else GCodeResult::ok or GCodeResult::notFinished
+GCodeResult RepRap::GetFileInfoResponse(const char *filename, OutputBuffer *&response, bool quitEarly) noexcept
+{
+ const bool specificFile = (filename != nullptr && filename[0] != 0);
+ GCodeFileInfo info;
+ if (specificFile)
+ {
+#if HAS_MASS_STORAGE
+ // Poll file info for a specific file
+ String<MaxFilenameLength> filePath;
+ if (!MassStorage::CombineName(filePath.GetRef(), platform->GetGCodeDir(), filename))
+ {
+ info.isValid = false;
+ }
+ else if (MassStorage::GetFileInfo(filePath.c_str(), info, quitEarly) == GCodeResult::notFinished)
+ {
+ // This may take a few runs...
+ return GCodeResult::notFinished;
+ }
+#else
+ return GCodeResult::notFinished;
+#endif
+ }
+ else if (!printMonitor->GetPrintingFileInfo(info))
+ {
+ return GCodeResult::notFinished;
+ }
+
+ if (!OutputBuffer::Allocate(response))
+ {
+ return GCodeResult::notFinished;
+ }
+
+ if (info.isValid)
+ {
+ response->printf("{\"err\":0,\"size\":%lu,",info.fileSize);
+ tm timeInfo;
+ gmtime_r(&info.lastModifiedTime, &timeInfo);
+ if (timeInfo.tm_year > /*19*/80)
+ {
+ response->catf("\"lastModified\":\"%04u-%02u-%02uT%02u:%02u:%02u\",",
+ timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
+ }
+
+ response->catf("\"height\":%.2f,\"firstLayerHeight\":%.2f,\"layerHeight\":%.2f,",
+ (double)info.objectHeight, (double)info.firstLayerHeight, (double)info.layerHeight);
+ if (info.printTime != 0)
+ {
+ response->catf("\"printTime\":%" PRIu32 ",", info.printTime);
+ }
+ if (info.simulatedTime != 0)
+ {
+ response->catf("\"simulatedTime\":%" PRIu32 ",", info.simulatedTime);
+ }
+
+ response->cat("\"filament\":");
+ char ch = '[';
+ if (info.numFilaments == 0)
+ {
+ response->cat(ch);
+ }
+ else
+ {
+ for (size_t i = 0; i < info.numFilaments; ++i)
+ {
+ response->catf("%c%.1f", ch, (double)info.filamentNeeded[i]);
+ ch = ',';
+ }
+ }
+ response->cat("]");
+
+ if (!specificFile)
+ {
+ response->catf(",\"printDuration\":%d,\"fileName\":\"%.s\"", (int)printMonitor->GetPrintDuration(), printMonitor->GetPrintingFilename());
+ }
+
+ response->catf(",\"generatedBy\":\"%.s\"}\n", info.generatedBy.c_str());
+ return GCodeResult::ok;
+ }
+
+ response->copy("{\"err\":1}\n");
+ return GCodeResult::warning;
+}
+
+// Helper functions to write JSON arrays
+// Append float array using 1 decimal place
+void RepRap::AppendFloatArray(OutputBuffer *buf, const char *name, size_t numValues, stdext::inplace_function<float(size_t)> func, unsigned int numDecimalDigits) noexcept
+{
+ if (name != nullptr)
+ {
+ buf->catf("\"%s\":", name);
+ }
+ buf->cat('[');
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ if (i != 0)
+ {
+ buf->cat(',');
+ }
+ buf->catf(GetFloatFormatString(numDecimalDigits), HideNan(func(i)));
+ }
+ buf->cat(']');
+}
+
+void RepRap::AppendIntArray(OutputBuffer *buf, const char *name, size_t numValues, stdext::inplace_function<int(size_t)> func) noexcept
+{
+ if (name != nullptr)
+ {
+ buf->catf("\"%s\":", name);
+ }
+ buf->cat('[');
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ if (i != 0)
+ {
+ buf->cat(',');
+ }
+ buf->catf("%d", func(i));
+ }
+ buf->cat(']');
+}
+
+void RepRap::AppendStringArray(OutputBuffer *buf, const char *name, size_t numValues, stdext::inplace_function<const char *(size_t)> func) noexcept
+{
+ if (name != nullptr)
+ {
+ buf->catf("\"%s\":", name);
+ }
+ buf->cat('[');
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ if (i != 0)
+ {
+ buf->cat(',');
+ }
+ buf->catf("\"%.s\"", func(i));
+ }
+ buf->cat(']');
+}
+
+#if SUPPORT_OBJECT_MODEL
+
+// Return a query into the object model, or return nullptr if no buffer available
+// We append a newline to help PanelDue resync after receiving corrupt or incomplete data. DWC ignores it.
+OutputBuffer *RepRap::GetModelResponse(const char *key, const char *flags) const THROWS(GCodeException)
+{
+ OutputBuffer *outBuf;
+ if (OutputBuffer::Allocate(outBuf))
+ {
+ if (key == nullptr) { key = ""; }
+ if (flags == nullptr) { flags = ""; }
+
+ outBuf->printf("{\"key\":\"%.s\",\"flags\":\"%.s\"", key, flags);
+
+ const bool wantArrayLength = (*key == '#');
+ if (wantArrayLength)
+ {
+ ++key;
+ }
+
+ try
+ {
+ outBuf->cat(",\"result\":");
+ reprap.ReportAsJson(outBuf, key, flags, wantArrayLength);
+ outBuf->cat("}\n");
+ if (outBuf->HadOverflow())
+ {
+ OutputBuffer::ReleaseAll(outBuf);
+ }
+ }
+ catch (...)
+ {
+ OutputBuffer::ReleaseAll(outBuf);
+ throw;
+ }
+ }
+
+ return outBuf;
+}
+
+#endif
+
+// Send a beep. We send it to both PanelDue and the web interface.
+void RepRap::Beep(unsigned int freq, unsigned int ms) noexcept
+{
+ // Limit the frequency and duration to sensible values
+ freq = constrain<unsigned int>(freq, 50, 10000);
+ ms = constrain<unsigned int>(ms, 10, 60000);
+
+ // If there is an LCD device present, make it beep
+ bool bleeped = false;
+#if SUPPORT_12864_LCD
+ if (display->IsPresent())
+ {
+ display->Beep(freq, ms);
+ bleeped = true;
+ }
+#endif
+
+ if (platform->IsAuxEnabled(0) && !platform->IsAuxRaw(0))
+ {
+ platform->PanelDueBeep(freq, ms);
+ bleeped = true;
+ }
+
+ if (!bleeped)
+ {
+ beepFrequency = freq;
+ beepDuration = ms;
+ beepTimer = millis();
+ StateUpdated();
+ }
+}
+
+// Send a short message. We send it to both PanelDue and the web interface.
+void RepRap::SetMessage(const char *msg) noexcept
+{
+ message.copy(msg);
+#if SUPPORT_12864_LCD
+ ++messageSequence;
+#endif
+ StateUpdated();
+
+ if (platform->IsAuxEnabled(0) && !platform->IsAuxRaw(0))
+ {
+ platform->SendPanelDueMessage(0, msg);
+ }
+ platform->Message(MessageType::LogInfo, msg);
+}
+
+// Display a message box on the web interface
+void RepRap::SetAlert(const char *msg, const char *title, int mode, float timeout, AxesBitmap controls) noexcept
+{
+ MutexLocker lock(messageBoxMutex);
+ mbox.message.copy(msg);
+ mbox.title.copy(title);
+ mbox.mode = mode;
+ mbox.timer = (timeout <= 0.0) ? 0 : millis();
+ mbox.timeout = round(max<float>(timeout, 0.0) * 1000.0);
+ mbox.controls = controls;
+ mbox.active = true;
+ ++mbox.seq;
+ StateUpdated();
+}
+
+// Clear pending message box
+void RepRap::ClearAlert() noexcept
+{
+ MutexLocker lock(messageBoxMutex);
+ mbox.active = false;
+ StateUpdated();
+}
+
+// Get the status index
+size_t RepRap::GetStatusIndex() const noexcept
+{
+ return (processingConfig) ? 0 // Reading the configuration file
+#if HAS_LINUX_INTERFACE && SUPPORT_CAN_EXPANSION
+ : (gCodes->IsFlashing() || expansion->IsFlashing()) ? 1 // Flashing a new firmware binary
+#else
+ : (gCodes->IsFlashing()) ? 1 // Flashing a new firmware binary
+#endif
+ : (IsStopped()) ? 2 // Halted
+#if HAS_VOLTAGE_MONITOR
+ : (!platform->HasVinPower() && !gCodes->IsSimulating()) ? 3 // Off i.e. powered down
+#endif
+ : (gCodes->GetPauseState() == PauseState::pausing) ? 4 // Pausing
+ : (gCodes->GetPauseState() == PauseState::resuming) ? 5 // Resuming
+ : (gCodes->GetPauseState() == PauseState::paused) ? 6 // Paused
+ : (printMonitor->IsPrinting())
+ ? ((gCodes->IsSimulating()) ? 7 // Simulating
+ : 8 // Printing
+ )
+ : (gCodes->IsDoingToolChange()) ? 9 // Changing tool
+ : (gCodes->DoingFileMacro() || !move->NoLiveMovement() ||
+ gCodes->WaitingForAcknowledgement()) ? 10 // Busy
+ : 11; // Idle
+
+}
+
+// Get the status character for the new-style status response
+char RepRap::GetStatusCharacter() const noexcept
+{
+ return "CFHODRSMPTBI"[GetStatusIndex()];
+}
+
+const char* RepRap::GetStatusString() const noexcept
+{
+ static const char *const StatusStrings[] =
+ {
+ "starting",
+ "updating",
+ "halted",
+ "off",
+ "pausing",
+ "resuming",
+ "paused",
+ "simulating",
+ "processing",
+ "changingTool",
+ "busy",
+ "idle"
+ };
+ return StatusStrings[GetStatusIndex()];
+}
+
+bool RepRap::NoPasswordSet() const noexcept
+{
+ return (password[0] == 0 || CheckPassword(DEFAULT_PASSWORD));
+}
+
+bool RepRap::CheckPassword(const char *pw) const noexcept
+{
+ String<RepRapPasswordLength> copiedPassword;
+ copiedPassword.CopyAndPad(pw);
+ return password.ConstantTimeEquals(copiedPassword);
+}
+
+void RepRap::SetPassword(const char* pw) noexcept
+{
+ password.CopyAndPad(pw);
+}
+
+const char *RepRap::GetName() const noexcept
+{
+ return myName.c_str();
+}
+
+void RepRap::SetName(const char* nm) noexcept
+{
+ myName.copy(nm);
+
+ // Set new DHCP hostname
+ network->SetHostname(myName.c_str());
+ NetworkUpdated();
+}
+
+// Given that we want to extrude/retract the specified extruder drives, check if they are allowed.
+// For each disallowed one, log an error to report later and return a bit in the bitmap.
+// This may be called by an ISR!
+unsigned int RepRap::GetProhibitedExtruderMovements(unsigned int extrusions, unsigned int retractions) noexcept
+{
+ if (GetHeat().ColdExtrude())
+ {
+ return 0;
+ }
+
+ Tool * const tool = currentTool;
+ if (tool == nullptr)
+ {
+ // This should not happen, but if no tool is selected then don't allow any extruder movement
+ return extrusions | retractions;
+ }
+
+ unsigned int result = 0;
+ for (size_t driveNum = 0; driveNum < tool->DriveCount(); driveNum++)
+ {
+ const unsigned int extruderDrive = (unsigned int)(tool->Drive(driveNum));
+ const unsigned int mask = 1 << extruderDrive;
+ if (extrusions & mask)
+ {
+ if (!tool->ToolCanDrive(true))
+ {
+ result |= mask;
+ }
+ }
+ else if (retractions & mask)
+ {
+ if (!tool->ToolCanDrive(false))
+ {
+ result |= mask;
+ }
+ }
+ }
+
+ return result;
+}
+
+void RepRap::FlagTemperatureFault(int8_t dudHeater) noexcept
+{
+ ReadLocker lock(toolListLock);
+ if (toolList != nullptr)
+ {
+ toolList->FlagTemperatureFault(dudHeater);
+ }
+}
+
+GCodeResult RepRap::ClearTemperatureFault(int8_t wasDudHeater, const StringRef& reply) noexcept
+{
+ const GCodeResult rslt = heat->ResetFault(wasDudHeater, reply);
+ ReadLocker lock(toolListLock);
+ if (toolList != nullptr)
+ {
+ toolList->ClearTemperatureFault(wasDudHeater);
+ }
+ return rslt;
+}
+
+#if HAS_MASS_STORAGE
+
+// Save some resume information, returning true if successful
+// We assume that the tool configuration doesn't change, only the temperatures and the mix
+bool RepRap::WriteToolSettings(FileStore *f) noexcept
+{
+ // First write the settings of all tools except the current one and the command to select them if they are on standby
+ bool ok = true;
+ ReadLocker lock(toolListLock);
+ for (const Tool *t = toolList; t != nullptr && ok; t = t->Next())
+ {
+ if (t != currentTool)
+ {
+ ok = t->WriteSettings(f);
+ }
+ }
+
+ // Finally write the settings of the active tool and the commands to select it. If no current tool, just deselect all tools.
+ if (ok)
+ {
+ if (currentTool == nullptr)
+ {
+ ok = f->Write("T-1 P0\n");
+ }
+ else
+ {
+ ok = currentTool->WriteSettings(f);
+ if (ok)
+ {
+ String<StringLength20> buf;
+ buf.printf("T%u P0\n", currentTool->Number());
+ ok = f->Write(buf.c_str());
+ }
+ }
+ }
+ return ok;
+}
+
+// Save some information in config-override.g
+bool RepRap::WriteToolParameters(FileStore *f, const bool forceWriteOffsets) noexcept
+{
+ bool ok = true, written = false;
+ ReadLocker lock(toolListLock);
+ for (const Tool *t = toolList; ok && t != nullptr; t = t->Next())
+ {
+ const AxesBitmap axesProbed = t->GetAxisOffsetsProbed();
+ if (axesProbed.IsNonEmpty() || forceWriteOffsets)
+ {
+ String<StringLength256> scratchString;
+ if (!written)
+ {
+ scratchString.copy("; Probed tool offsets\n");
+ written = true;
+ }
+ scratchString.catf("G10 P%d", t->Number());
+ for (size_t axis = 0; axis < MaxAxes; ++axis)
+ {
+ if (forceWriteOffsets || axesProbed.IsBitSet(axis))
+ {
+ scratchString.catf(" %c%.2f", gCodes->GetAxisLetters()[axis], (double)(t->GetOffset(axis)));
+ }
+ }
+ scratchString.cat('\n');
+ ok = f->Write(scratchString.c_str());
+ }
+ }
+ return ok;
+}
+
+#endif
+
+// Firmware update operations
+
+#ifdef __LPC17xx__
+ #include "LPC/FirmwareUpdate.hpp"
+#else
+
+// Check the prerequisites for updating the main firmware. Return True if satisfied, else print a message to 'reply' and return false.
+bool RepRap::CheckFirmwareUpdatePrerequisites(const StringRef& reply, const StringRef& filenameRef) noexcept
+{
+#if HAS_MASS_STORAGE
+ FileStore * const firmwareFile = platform->OpenFile(FIRMWARE_DIRECTORY, filenameRef.IsEmpty() ? IAP_FIRMWARE_FILE : filenameRef.c_str(), OpenMode::read);
+ if (firmwareFile == nullptr)
+ {
+ String<MaxFilenameLength> firmwareBinaryLocation;
+ MassStorage::CombineName(firmwareBinaryLocation.GetRef(), FIRMWARE_DIRECTORY, filenameRef.IsEmpty() ? IAP_FIRMWARE_FILE : filenameRef.c_str());
+ reply.printf("Firmware binary \"%s\" not found", firmwareBinaryLocation.c_str());
+ return false;
+ }
+
+ // Check that the binary looks sensible. The first word is the initial stack pointer, which should be the top of RAM.
+ uint32_t firstDword;
+ bool ok =
+#if SAME5x
+ // We use UF2 file format, so look inside the payload
+ firmwareFile->Seek(32) &&
+#endif
+
+ firmwareFile->Read(reinterpret_cast<char*>(&firstDword), sizeof(firstDword)) == (int)sizeof(firstDword);
+ firmwareFile->Close();
+ if (!ok || firstDword !=
+#if SAME5x
+ HSRAM_ADDR + HSRAM_SIZE
+#elif SAM3XA
+ IRAM1_ADDR + IRAM1_SIZE
+#else
+ IRAM_ADDR + IRAM_SIZE
+#endif
+ )
+ {
+ reply.printf("Firmware binary \"%s\" is not valid for this electronics", FIRMWARE_DIRECTORY IAP_FIRMWARE_FILE);
+ return false;
+ }
+
+ if (!platform->FileExists(FIRMWARE_DIRECTORY, IAP_UPDATE_FILE))
+ {
+ reply.printf("In-application programming binary \"%s\" not found", FIRMWARE_DIRECTORY IAP_UPDATE_FILE);
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+// Update the firmware. Prerequisites should be checked before calling this.
+void RepRap::UpdateFirmware(const StringRef& filenameRef) noexcept
+{
+#if HAS_MASS_STORAGE
+ FileStore * const iapFile = platform->OpenFile(FIRMWARE_DIRECTORY, IAP_UPDATE_FILE, OpenMode::read);
+ if (iapFile == nullptr)
+ {
+ platform->Message(FirmwareUpdateMessage, "IAP file '" FIRMWARE_DIRECTORY IAP_UPDATE_FILE "' not found\n");
+ return;
+ }
+
+
+ PrepareToLoadIap();
+
+ // Use RAM-based IAP
+ iapFile->Read(reinterpret_cast<char *>(IAP_IMAGE_START), iapFile->Length());
+ iapFile->Close();
+ StartIap(filenameRef.c_str());
+#endif
+}
+
+void RepRap::PrepareToLoadIap() noexcept
+{
+#if SUPPORT_12864_LCD
+ display->UpdatingFirmware(); // put the firmware update message on the display and stop polling the display
+#endif
+
+ // Send this message before we start using RAM that may contain message buffers
+ platform->Message(AuxMessage, "Updating main firmware\n");
+ platform->Message(UsbMessage, "Shutting down USB interface to update main firmware. Try reconnecting after 30 seconds.\n");
+
+ // Allow time for the firmware update message to be sent
+ const uint32_t now = millis();
+ while (platform->FlushMessages() && millis() - now < 2000) { }
+
+ // The machine will be unresponsive for a few seconds, don't risk damaging the heaters.
+ // This also shuts down tasks and interrupts that might make use of the RAM that we are about to load the IAP binary into.
+ EmergencyStop(); // this also stops Platform::Tick being called, which is necessary because it access Z probe object in RAM used by IAP
+ network->Exit(); // kill the network task to stop it overwriting RAM that we use to hold the IAP
+ SmartDrivers::Exit(); // stop the drivers being polled via SPI or UART because it may use data in the last 64Kb of RAM
+ FilamentMonitor::Exit(); // stop the filament monitors generating interrupts, we may be about to overwrite them
+ fansManager->Exit(); // stop the fan tachos generating interrupts, we may be about to overwrite them
+ if (RTOSIface::GetCurrentTask() != Tasks::GetMainTask())
+ {
+ Tasks::TerminateMainTask(); // stop the main task if IAP is being written from another task
+ }
+
+#ifdef DUET_NG
+ DuetExpansion::Exit(); // stop the DueX polling task
+#endif
+ StopAnalogTask();
+
+ Cache::Disable(); // disable the cache because it interferes with flash memory access
+
+#if USE_MPU
+ ARM_MPU_Disable(); // make sure we can execute from RAM
+#endif
+
+#if 0
+ // Debug
+ memset(reinterpret_cast<char *>(IAP_IMAGE_START), 0x7E, 60 * 1024);
+ delay(2000);
+ for (char* p = reinterpret_cast<char *>(IAP_IMAGE_START); p < reinterpret_cast<char *>(IAP_IMAGE_START + (60 * 1024)); ++p)
+ {
+ if (*p != 0x7E)
+ {
+ debugPrintf("At %08" PRIx32 ": %02x\n", reinterpret_cast<uint32_t>(p), *p);
+ }
+ }
+ debugPrintf("Scan complete\n");
+ #endif
+}
+
+void RepRap::StartIap(const char *filename) noexcept
+{
+ // Disable all interrupts, then reallocate the vector table and program entry point to the new IAP binary
+ // This does essentially what the Atmel AT02333 paper suggests (see 3.2.2 ff)
+
+ // Disable all IRQs
+ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk; // disable the system tick exception
+ cpu_irq_disable();
+ for (size_t i = 0; i < 8; i++)
+ {
+ NVIC->ICER[i] = 0xFFFFFFFF; // Disable IRQs
+ NVIC->ICPR[i] = 0xFFFFFFFF; // Clear pending IRQs
+ }
+
+ // Disable all PIO IRQs, because the core assumes they are all disabled when setting them up
+#if !SAME5x
+ PIOA->PIO_IDR = 0xFFFFFFFF;
+ PIOB->PIO_IDR = 0xFFFFFFFF;
+ PIOC->PIO_IDR = 0xFFFFFFFF;
+# ifdef PIOD
+ PIOD->PIO_IDR = 0xFFFFFFFF;
+# endif
+# ifdef ID_PIOE
+ PIOE->PIO_IDR = 0xFFFFFFFF;
+# endif
+#endif
+
+#if HAS_MASS_STORAGE
+ if (filename != nullptr)
+ {
+ // Newer versions of IAP reserve space above the stack for us to pass the firmware filename
+ String<MaxFilenameLength> firmwareFileLocation;
+ MassStorage::CombineName(firmwareFileLocation.GetRef(), FIRMWARE_DIRECTORY, filename[0] == 0 ? IAP_FIRMWARE_FILE : filename);
+ const uint32_t topOfStack = *reinterpret_cast<uint32_t *>(IAP_IMAGE_START);
+ if (topOfStack + firmwareFileLocation.strlen() + 1 <=
+# if SAME5x
+ HSRAM_ADDR + HSRAM_SIZE
+# elif SAM3XA
+ IRAM1_ADDR + IRAM1_SIZE
+# else
+ IRAM_ADDR + IRAM_SIZE
+# endif
+ )
+ {
+ strcpy(reinterpret_cast<char*>(topOfStack), firmwareFileLocation.c_str());
+ }
+ }
+#endif
+
+#if defined(DUET_NG) || defined(DUET_M)
+ IoPort::WriteDigital(DiagPin, !DiagOnPolarity); // turn the DIAG LED off
+#endif
+
+ WatchdogReset(); // kick the watchdog one last time
+
+#if SAM4E || SAME70
+ WatchdogResetSecondary(); // kick the secondary watchdog
+#endif
+
+ // Modify vector table location
+ __DSB();
+ __ISB();
+ SCB->VTOR = ((uint32_t)IAP_IMAGE_START & SCB_VTOR_TBLOFF_Msk);
+ __DSB();
+ __ISB();
+
+ cpu_irq_enable();
+
+ __asm volatile ("mov r3, %0" : : "r" (IAP_IMAGE_START) : "r3");
+
+ // We are using separate process and handler stacks. Put the process stack 1K bytes below the handler stack.
+ __asm volatile ("ldr r1, [r3]");
+ __asm volatile ("msr msp, r1");
+ __asm volatile ("sub r1, #1024");
+ __asm volatile ("mov sp, r1");
+
+ __asm volatile ("isb");
+ __asm volatile ("ldr r1, [r3, #4]");
+ __asm volatile ("orr r1, r1, #1");
+ __asm volatile ("bx r1");
+ for (;;) { } // to keep gcc happy
+}
+
+#endif
+
+// Helper function for diagnostic tests in Platform.cpp, to cause a deliberate divide-by-zero
+/*static*/ uint32_t RepRap::DoDivide(uint32_t a, uint32_t b) noexcept
+{
+ return a/b;
+}
+
+// Helper function for diagnostic tests in Platform.cpp, to cause a deliberate bus fault or memory protection error
+/*static*/ void RepRap::GenerateBusFault() noexcept
+{
+#if SAME5x
+ (void)*(reinterpret_cast<const volatile char*>(0x30000000));
+#elif SAME70
+ (void)*(reinterpret_cast<const volatile char*>(0x30000000));
+#elif SAM4E || SAM4S
+ (void)*(reinterpret_cast<const volatile char*>(0x20800000));
+#elif SAM3XA
+ (void)*(reinterpret_cast<const volatile char*>(0x20200000));
+#elif defined(__LPC17xx__)
+ // The LPC176x/5x generates Bus Fault exception when accessing a reserved memory address
+ (void)*(reinterpret_cast<const volatile char*>(0x00080000));
+#else
+# error Unsupported processor
+#endif
+}
+
+// Helper function for diagnostic tests in Platform.cpp, to calculate sine and cosine
+/*static*/ float RepRap::SinfCosf(float angle) noexcept
+{
+ return sinf(angle) + cosf(angle);
+}
+
+// Helper function for diagnostic tests in Platform.cpp, to calculate square root
+/*static*/ float RepRap::FastSqrtf(float f) noexcept
+{
+ return ::fastSqrtf(f);
+}
+
+// Report an internal error
+void RepRap::ReportInternalError(const char *file, const char *func, int line) const noexcept
+{
+ platform->MessageF(ErrorMessage, "Internal Error in %s at %s(%d)\n", func, file, line);
+}
+
+#if SUPPORT_12864_LCD
+
+const char *RepRap::GetLatestMessage(uint16_t& sequence) const noexcept
+{
+ sequence = messageSequence;
+ return message.c_str();
+}
+
+#endif
+
+// End
diff --git a/src/Platform/RepRap.h b/src/Platform/RepRap.h
new file mode 100644
index 00000000..b3227255
--- /dev/null
+++ b/src/Platform/RepRap.h
@@ -0,0 +1,318 @@
+/****************************************************************************************************
+
+RepRapFirmware - Reprap
+
+RepRap is a simple class that acts as a container for an instance of all the others.
+
+-----------------------------------------------------------------------------------------------------
+
+Version 0.1
+
+21 May 2013
+
+Adrian Bowyer
+RepRap Professional Ltd
+http://reprappro.com
+
+Licence: GPL
+
+****************************************************************************************************/
+
+#ifndef REPRAP_H
+#define REPRAP_H
+
+#include "RepRapFirmware.h"
+#include "ObjectModel/ObjectModel.h"
+#include "MessageType.h"
+#include "RTOSIface/RTOSIface.h"
+#include "GCodes/GCodeResult.h"
+#include <General/inplace_function.h>
+
+#if SUPPORT_CAN_EXPANSION
+# include <CAN/ExpansionManager.h>
+#endif
+
+enum class ResponseSource
+{
+ HTTP,
+ AUX,
+ Generic
+};
+
+// Message box data
+struct MessageBox
+{
+ bool active;
+ String<MaxMessageLength> message;
+ String<MaxTitleLength> title;
+ int mode;
+ uint32_t seq;
+ uint32_t timer, timeout;
+ AxesBitmap controls;
+
+ MessageBox() noexcept : active(false), seq(0) { }
+};
+
+class RepRap INHERIT_OBJECT_MODEL
+{
+public:
+ RepRap() noexcept;
+ RepRap(const RepRap&) = delete;
+
+ void EmergencyStop() noexcept;
+ void Init() noexcept;
+ void Spin() noexcept;
+ void Exit() noexcept;
+ void Diagnostics(MessageType mtype) noexcept;
+ void DeferredDiagnostics(MessageType mtype) noexcept { diagnosticsDestination = mtype; }
+ void Timing(MessageType mtype) noexcept;
+
+ bool Debug(Module module) const noexcept;
+ void SetDebug(Module m, bool enable) noexcept;
+ void ClearDebug() noexcept;
+ void PrintDebug(MessageType mt) noexcept;
+ Module GetSpinningModule() const noexcept;
+
+ const char *GetName() const noexcept;
+ void SetName(const char* nm) noexcept;
+ bool NoPasswordSet() const noexcept;
+ bool CheckPassword(const char* pw) const noexcept;
+ void SetPassword(const char* pw) noexcept;
+
+ // Tool management
+ void AddTool(Tool* t) noexcept;
+ void DeleteTool(int toolNumber) noexcept;
+ void SelectTool(int toolNumber, bool simulating) noexcept;
+ void StandbyTool(int toolNumber, bool simulating) noexcept;
+ int GetCurrentToolNumber() const noexcept;
+ void SetPreviousToolNumber() noexcept;
+ Tool *GetCurrentTool() const noexcept;
+ ReadLockedPointer<Tool> GetLockedCurrentTool() const noexcept;
+ ReadLockedPointer<Tool> GetTool(int toolNumber) const noexcept;
+ ReadLockedPointer<Tool> GetCurrentOrDefaultTool() const noexcept;
+ ReadLockedPointer<Tool> GetFirstTool() const noexcept; // Return the lowest-numbered tool
+ AxesBitmap GetCurrentXAxes() const noexcept; // Get the current axes used as X axes
+ AxesBitmap GetCurrentYAxes() const noexcept; // Get the current axes used as Y axes
+ AxesBitmap GetCurrentAxisMapping(unsigned int axis) const noexcept;
+ bool IsHeaterAssignedToTool(int8_t heater) const noexcept;
+ unsigned int GetNumberOfContiguousTools() const noexcept;
+ void ReportAllToolTemperatures(const StringRef& reply) const noexcept;
+ GCodeResult SetAllToolsFirmwareRetraction(GCodeBuffer& gb, const StringRef& reply, OutputBuffer*& outBuf) THROWS(GCodeException);
+
+ unsigned int GetProhibitedExtruderMovements(unsigned int extrusions, unsigned int retractions) noexcept;
+ void PrintTool(int toolNumber, const StringRef& reply) const noexcept;
+ void FlagTemperatureFault(int8_t dudHeater) noexcept;
+ GCodeResult ClearTemperatureFault(int8_t wasDudHeater, const StringRef& reply) noexcept;
+
+ Platform& GetPlatform() const noexcept { return *platform; }
+ Move& GetMove() const noexcept { return *move; }
+ Heat& GetHeat() const noexcept { return *heat; }
+ GCodes& GetGCodes() const noexcept { return *gCodes; }
+ Network& GetNetwork() const noexcept { return *network; }
+ Scanner& GetScanner() const noexcept { return *scanner; }
+ PrintMonitor& GetPrintMonitor() const noexcept { return *printMonitor; }
+ FansManager& GetFansManager() const noexcept { return *fansManager; }
+
+#if SUPPORT_ROLAND
+ Roland& GetRoland() const noexcept { return *roland; }
+#endif
+#if SUPPORT_IOBITS
+ PortControl& GetPortControl() const noexcept { return *portControl; }
+#endif
+#if SUPPORT_12864_LCD
+ Display& GetDisplay() const noexcept { return *display; }
+ const char *GetLatestMessage(uint16_t& sequence) const noexcept;
+ const MessageBox& GetMessageBox() const noexcept { return mbox; }
+#endif
+#if HAS_LINUX_INTERFACE
+ bool UsingLinuxInterface() const noexcept { return usingLinuxInterface; }
+ LinuxInterface& GetLinuxInterface() const noexcept { return *linuxInterface; }
+#endif
+#if SUPPORT_CAN_EXPANSION
+ ExpansionManager& GetExpansion() const noexcept { return *expansion; }
+#endif
+
+ void Tick() noexcept;
+ bool SpinTimeoutImminent() const noexcept;
+ bool IsStopped() const noexcept;
+
+ uint16_t GetExtrudersInUse() const noexcept;
+ uint16_t GetToolHeatersInUse() const noexcept;
+
+ OutputBuffer *GetStatusResponse(uint8_t type, ResponseSource source) const noexcept;
+ OutputBuffer *GetConfigResponse() noexcept;
+ OutputBuffer *GetLegacyStatusResponse(uint8_t type, int seq) const noexcept;
+
+#if HAS_MASS_STORAGE
+ OutputBuffer *GetFilesResponse(const char* dir, unsigned int startAt, bool flagsDirs) noexcept;
+ OutputBuffer *GetFilelistResponse(const char* dir, unsigned int startAt) noexcept;
+#endif
+
+ GCodeResult GetFileInfoResponse(const char *filename, OutputBuffer *&response, bool quitEarly) noexcept;
+
+#if SUPPORT_OBJECT_MODEL
+ OutputBuffer *GetModelResponse(const char *key, const char *flags) const THROWS(GCodeException);
+#endif
+
+ void Beep(unsigned int freq, unsigned int ms) noexcept;
+ void SetMessage(const char *msg) noexcept;
+ void SetAlert(const char *msg, const char *title, int mode, float timeout, AxesBitmap controls) noexcept;
+ void ClearAlert() noexcept;
+
+#if HAS_MASS_STORAGE
+ bool WriteToolSettings(FileStore *f) noexcept; // save some information for the resume file
+ bool WriteToolParameters(FileStore *f, const bool forceWriteOffsets) noexcept; // save some information in config-override.g
+#endif
+
+ bool IsProcessingConfig() const noexcept { return processingConfig; }
+
+ // Firmware update operations
+ bool CheckFirmwareUpdatePrerequisites(const StringRef& reply, const StringRef& filenameRef) noexcept;
+ void UpdateFirmware(const StringRef& filenameRef) noexcept;
+ void PrepareToLoadIap() noexcept;
+ [[noreturn]] void StartIap(const char *filename) noexcept;
+
+ void ReportInternalError(const char *file, const char *func, int line) const noexcept; // report an internal error
+
+ static uint32_t DoDivide(uint32_t a, uint32_t b) noexcept; // helper function for diagnostic tests
+ static void GenerateBusFault() noexcept; // helper function for diagnostic tests
+ static float SinfCosf(float angle) noexcept; // helper function for diagnostic tests
+ static float FastSqrtf(float f) noexcept; // helper function for diagnostic tests
+
+ void KickHeatTaskWatchdog() noexcept { heatTaskIdleTicks = 0; }
+
+ void BoardsUpdated() noexcept { ++boardsSeq; }
+ void DirectoriesUpdated() noexcept { ++directoriesSeq; }
+ void FansUpdated() noexcept { ++fansSeq; }
+ void HeatUpdated() noexcept { ++heatSeq; }
+ void InputsUpdated() noexcept { ++inputsSeq; }
+ void JobUpdated() noexcept { ++jobSeq; }
+ void MoveUpdated() noexcept { ++moveSeq; }
+ void NetworkUpdated() noexcept { ++networkSeq; }
+ void ScannerUpdated() noexcept { ++scannerSeq; }
+ void SensorsUpdated() noexcept { ++sensorsSeq; }
+ void SpindlesUpdated() noexcept { ++spindlesSeq; }
+ void StateUpdated() noexcept { ++stateSeq; }
+ void ToolsUpdated() noexcept { ++toolsSeq; }
+ void VolumesUpdated() noexcept { ++volumesSeq; }
+
+protected:
+ DECLARE_OBJECT_MODEL
+ OBJECT_MODEL_ARRAY(boards)
+ OBJECT_MODEL_ARRAY(fans)
+ OBJECT_MODEL_ARRAY(gpout)
+ OBJECT_MODEL_ARRAY(inputs)
+ OBJECT_MODEL_ARRAY(spindles)
+ OBJECT_MODEL_ARRAY(tools)
+ OBJECT_MODEL_ARRAY(restorePoints)
+ OBJECT_MODEL_ARRAY(volumes)
+
+private:
+ static void EncodeString(StringRef& response, const char* src, size_t spaceToLeave, bool allowControlChars = false, char prefix = 0) noexcept;
+ static void AppendFloatArray(OutputBuffer *buf, const char *name, size_t numValues, stdext::inplace_function<float(size_t)> func, unsigned int numDecimalDigits) noexcept;
+ static void AppendIntArray(OutputBuffer *buf, const char *name, size_t numValues, stdext::inplace_function<int(size_t)> func) noexcept;
+ static void AppendStringArray(OutputBuffer *buf, const char *name, size_t numValues, stdext::inplace_function<const char *(size_t)> func) noexcept;
+
+ size_t GetStatusIndex() const noexcept;
+ char GetStatusCharacter() const noexcept;
+ const char* GetStatusString() const noexcept;
+ void ReportToolTemperatures(const StringRef& reply, const Tool *tool, bool includeNumber) const noexcept;
+ bool RunStartupFile(const char *filename) noexcept;
+
+ static constexpr uint32_t MaxTicksInSpinState = 20000; // timeout before we reset the processor
+ static constexpr uint32_t HighTicksInSpinState = 16000; // how long before we warn that timeout is approaching
+
+ static ReadWriteLock toolListLock;
+
+ Platform* platform;
+ Network* network;
+ Move* move;
+ Heat* heat;
+ GCodes* gCodes;
+ Scanner* scanner;
+ PrintMonitor* printMonitor;
+ FansManager* fansManager;
+
+#if SUPPORT_IOBITS
+ PortControl *portControl;
+#endif
+
+#if SUPPORT_12864_LCD
+ Display *display;
+#endif
+
+#if HAS_LINUX_INTERFACE
+ LinuxInterface *linuxInterface;
+#endif
+
+#if SUPPORT_ROLAND
+ Roland* roland;
+#endif
+
+#if SUPPORT_CAN_EXPANSION
+ ExpansionManager *expansion;
+#endif
+
+ mutable Mutex messageBoxMutex; // mutable so that we can lock and release it in const functions
+
+ uint16_t boardsSeq, directoriesSeq, fansSeq, heatSeq, inputsSeq, jobSeq, moveSeq;
+ uint16_t networkSeq, scannerSeq, sensorsSeq, spindlesSeq, stateSeq, toolsSeq, volumesSeq;
+
+ Tool* toolList; // the tool list is sorted in order of increasing tool number
+ Tool* currentTool;
+ uint32_t lastWarningMillis; // when we last sent a warning message for things that can happen very often
+
+ uint16_t activeExtruders;
+ uint16_t activeToolHeaters;
+ uint16_t numToolsToReport;
+
+ uint16_t ticksInSpinState;
+ uint16_t heatTaskIdleTicks;
+ uint32_t fastLoop, slowLoop;
+
+ uint32_t debug;
+
+ String<RepRapPasswordLength> password;
+ String<MachineNameLength> myName;
+
+ unsigned int beepFrequency, beepDuration;
+ uint32_t beepTimer;
+ String<MaxMessageLength> message;
+#if SUPPORT_12864_LCD
+ uint16_t messageSequence; // used by 12864 display to detect when there is a new message
+#endif
+
+ MessageBox mbox; // message box data
+
+ int16_t previousToolNumber; // the tool number we were using before the last tool change, or -1 if we weren't using a tool
+
+ // Deferred diagnostics
+ MessageType diagnosticsDestination;
+ bool justSentDiagnostics;
+
+ // State flags
+ Module spinningModule;
+ bool stopped;
+ bool active;
+ bool processingConfig;
+#if HAS_LINUX_INTERFACE
+ bool usingLinuxInterface;
+#endif
+};
+
+// A single instance of the RepRap class contains all the others
+extern RepRap reprap;
+
+inline bool RepRap::Debug(Module m) const noexcept { return debug & (1u << m); }
+inline Module RepRap::GetSpinningModule() const noexcept { return spinningModule; }
+
+inline Tool* RepRap::GetCurrentTool() const noexcept { return currentTool; }
+inline uint16_t RepRap::GetExtrudersInUse() const noexcept { return activeExtruders; }
+inline uint16_t RepRap::GetToolHeatersInUse() const noexcept { return activeToolHeaters; }
+inline bool RepRap::IsStopped() const noexcept { return stopped; }
+
+#define REPORT_INTERNAL_ERROR do { reprap.ReportInternalError((__FILE__), (__func__), (__LINE__)); } while(0)
+
+#endif
+
+
diff --git a/src/Platform/Roland.cpp b/src/Platform/Roland.cpp
new file mode 100644
index 00000000..8b87e779
--- /dev/null
+++ b/src/Platform/Roland.cpp
@@ -0,0 +1,267 @@
+// This class allows the RepRap firmware to transmit commands to a Roland mill
+// See: http://www.rolanddg.com/product/3d/3d/mdx-20_15/mdx-20_15.html
+// http://altlab.org/d/content/m/pangelo/ideas/rml_command_guide_en_v100.pdf
+
+#include "RepRapFirmware.h"
+
+#if SUPPORT_ROLAND
+
+Roland::Roland(Platform& p) : platform(p)
+{
+}
+
+void Roland::Init()
+{
+ pinMode(ROLAND_RTS_PIN, OUTPUT);
+ pinMode(ROLAND_CTS_PIN, INPUT);
+ digitalWrite(ROLAND_RTS_PIN, HIGH);
+
+ sBuffer = new StringRef(buffer, ARRAY_SIZE(buffer));
+ sBuffer->Clear();
+
+ bufferPointer = 0;
+ Zero(true);
+ longWait = platform.Time();
+ active = false;
+}
+
+void Roland::Spin()
+{
+ if (!Active())
+ {
+ platform.ClassReport(longWait);
+ return;
+ }
+
+ // 'U' is 01010101 in binary (nice for an oscilloscope...)
+
+ //SERIAL_AUX2_DEVICE.write('U');
+ //SERIAL_AUX2_DEVICE.flush();
+ //return;
+
+ // Are we sending something to the Roland?
+
+ if (Busy()) // Busy means we are sending something
+ {
+ if (digitalRead(ROLAND_CTS_PIN))
+ {
+ platform.ClassReport(longWait);
+ return;
+ }
+
+ SERIAL_AUX2_DEVICE.write(buffer[bufferPointer]);
+ SERIAL_AUX2_DEVICE.flush();
+ bufferPointer++;
+ }
+ else // Not sending.
+ {
+ // Finished talking to the Roland
+
+ sBuffer->Clear();
+ bufferPointer = 0;
+
+ // Anything new to do?
+
+ EndstopChecks endStopsToCheck;
+ uint8_t moveType;
+ FilePosition filePos;
+ if (reprap.GetGCodes()->ReadMove(move, endStopsToCheck, moveType, filePos))
+ {
+ move[AXES] = move[DRIVES]; // Roland doesn't have extruders etc.
+ ProcessMove();
+ }
+ }
+
+ platform.ClassReport(longWait);
+}
+
+void Roland::Zero(bool feed)
+{
+ size_t lim = feed ? AXES + 1 : AXES;
+ for(size_t axis = 0; axis < lim; axis++)
+ {
+ move[axis] = 0.0;
+ coordinates[axis] = 0.0;
+ oldCoordinates[axis] = 0.0;
+ offset[axis] = 0.0;
+ }
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.Message(HOST_MESSAGE, "Roland zero\n");
+ }
+}
+
+bool Roland::Busy()
+{
+ return buffer[bufferPointer] != 0;
+}
+
+bool Roland::ProcessHome()
+{
+ if (Busy())
+ {
+ return false;
+ }
+
+ sBuffer->copy("H;\n");
+ Zero(false);
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.MessageF(HOST_MESSAGE, "Roland home: %s", buffer);
+ }
+ return true;
+}
+
+bool Roland::ProcessDwell(long milliseconds)
+{
+ if (Busy())
+ {
+ return false;
+ }
+
+ sBuffer->printf("W%ld;", milliseconds);
+ sBuffer->catf("Z %.4f,%.4f,%.4f;", oldCoordinates[0], oldCoordinates[1], oldCoordinates[2]);
+ sBuffer->cat("W0;\n");
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.MessageF(HOST_MESSAGE, "Roland dwell: %s", buffer);
+ }
+ return true;
+}
+
+bool Roland::ProcessG92(float v, size_t axis)
+{
+ if (Busy())
+ {
+ return false;
+ }
+
+ move[axis] = v;
+ coordinates[axis] = move[axis]*ROLAND_FACTOR + offset[axis];
+ offset[axis] = oldCoordinates[axis];
+ oldCoordinates[axis] = coordinates[axis];
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.Message(HOST_MESSAGE, "Roland G92\n");
+ }
+ return true;
+}
+
+bool Roland::ProcessSpindle(float rpm)
+{
+ if (Busy())
+ {
+ return false;
+ }
+
+ if (rpm < 0.5) // Stop
+ {
+ sBuffer->printf("!MC 0;\n");
+ }
+ else // Go
+ {
+ sBuffer->printf("!RC%ld;!MC 1;\n", (long)(rpm + 100.0));
+ }
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.MessageF(HOST_MESSAGE, "Roland spindle: %s", buffer);
+ }
+ return true;
+
+}
+
+void Roland::GetCurrentRolandPosition(float moveBuffer[])
+{
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ moveBuffer[axis] = move[axis];
+ }
+
+ for(size_t axis = AXES; axis < DRIVES; axis++)
+ {
+ moveBuffer[axis] = 0.0;
+ }
+
+ moveBuffer[DRIVES] = move[AXES];
+}
+
+void Roland::ProcessMove()
+{
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ coordinates[axis] = move[axis] * ROLAND_FACTOR + offset[axis];
+ }
+ coordinates[AXES] = move[AXES];
+
+ // Start with feedrate; For some reason the Roland won't accept more than 4 d.p.
+
+ sBuffer->printf("V %.4f;", coordinates[AXES]);
+
+ // Now the move
+
+ sBuffer->catf("Z %.4f,%.4f,%.4f;\n", coordinates[0], coordinates[1], coordinates[2]);
+
+ for(size_t axis = 0; axis <= AXES; axis++)
+ {
+ oldCoordinates[axis] = coordinates[axis];
+ }
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.MessageF(HOST_MESSAGE, "Roland move: %s", buffer);
+ }
+}
+
+
+bool Roland::RawWrite(const char* s)
+{
+ if (Busy())
+ {
+ return false;
+ }
+
+ sBuffer->copy(s);
+ sBuffer->cat("\n");
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.MessageF(HOST_MESSAGE, "Roland rawwrite: %s", buffer);
+ }
+ return true;
+}
+
+bool Roland::Active()
+{
+ return active;
+}
+
+void Roland::Activate()
+{
+ digitalWrite(ROLAND_RTS_PIN, LOW);
+ active = true;
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.Message(HOST_MESSAGE, "Roland started\n");
+ }
+}
+
+bool Roland::Deactivate()
+{
+ if (Busy())
+ {
+ return false;
+ }
+
+ digitalWrite(ROLAND_RTS_PIN, HIGH);
+ active = false;
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform.Message(HOST_MESSAGE, "Roland stopped\n");
+ }
+ return true;
+}
+
+#endif
diff --git a/src/Platform/Roland.h b/src/Platform/Roland.h
new file mode 100644
index 00000000..3412e770
--- /dev/null
+++ b/src/Platform/Roland.h
@@ -0,0 +1,74 @@
+/****************************************************************************************************
+
+RepRapFirmware - Roland
+
+This class can interface with a Roland mill (e.g. Roland MDX-20/15) and allows the underlying hardware
+to act as a G-Code proxy, which translates G-Codes to internal Roland commands.
+
+-----------------------------------------------------------------------------------------------------
+
+Version 0.1
+
+Created on: Oct 14, 2015
+
+Adrian Bowyer
+
+Licence: GPL
+
+****************************************************************************************************/
+
+#ifndef ROLAND_H
+#define ROLAND_H
+
+#if SUPPORT_ROLAND
+
+// This class allows the RepRap firmware to transmit commands to a Roland mill
+// See: http://www.rolanddg.com/product/3d/3d/mdx-20_15/mdx-20_15.html
+// http://altlab.org/d/content/m/pangelo/ideas/rml_command_guide_en_v100.pdf
+
+#include "RepRapFirmware.h"
+#include "Core.h"
+#include "Platform.h"
+
+const float ROLAND_FACTOR = (1.016088061*100.0/2.54); // Roland units are 0.001"
+const size_t ROLAND_BUFFER_SIZE = 50;
+
+class Roland
+{
+ public:
+ Roland(Platform& p);
+ void Init();
+ void Spin();
+ bool ProcessHome();
+ bool ProcessDwell(long milliseconds);
+ bool ProcessG92(float v, size_t axis);
+ bool ProcessSpindle(float rpm);
+ bool RawWrite(const char* s);
+ void GetCurrentRolandPosition(float moveBuffer[]);
+ bool Active();
+ void Activate();
+ bool Deactivate();
+
+ private:
+ void ProcessMove();
+ void Zero(bool feed);
+ bool Busy();
+
+ Platform& platform;
+ float longWait;
+
+ float move[DRIVES+1];
+ float coordinates[AXES+1];
+ float oldCoordinates[AXES+1];
+ float offset[AXES+1];
+ char buffer[ROLAND_BUFFER_SIZE];
+ int bufferPointer;
+ StringRef *sBuffer;
+ bool active;
+};
+
+#endif
+
+#endif
+
+// vim: ts=4:sw=4
diff --git a/src/Platform/Scanner.cpp b/src/Platform/Scanner.cpp
new file mode 100644
index 00000000..90c5e87f
--- /dev/null
+++ b/src/Platform/Scanner.cpp
@@ -0,0 +1,608 @@
+/*
+ * Scanner.cpp
+ *
+ * Created on: 21 Mar 2017
+ * Author: Christian
+ */
+
+#include "Scanner.h"
+
+#if SUPPORT_SCANNER
+
+#include "RepRap.h"
+#include "Platform.h"
+
+
+const char* const SCANNER_ON_G = "scanner_on.g";
+const char* const SCANNER_OFF_G = "scanner_off.g";
+const char* const ALIGN_ON_G = "align_on.g";
+const char* const ALIGN_OFF_G = "align_off.g";
+const char* const SCAN_PRE_G = "scan_pre.g";
+const char* const SCAN_POST_G = "scan_post.g";
+const char* const CALIBRATE_PRE_G = "calibrate_pre.g";
+const char* const CALIBRATE_POST_G = "calibrate_post.g";
+
+#if SUPPORT_OBJECT_MODEL
+
+// Object model table and functions
+// Note: if using GCC version 7.3.1 20180622 and lambda functions are used in this table, you must compile this file with option -std=gnu++17.
+// Otherwise the table will be allocated in RAM instead of flash, which wastes too much RAM.
+
+// Macro to build a standard lambda function that includes the necessary type conversions
+#define OBJECT_MODEL_FUNC(...) OBJECT_MODEL_FUNC_BODY(Scanner, __VA_ARGS__)
+#define OBJECT_MODEL_FUNC_IF(...) OBJECT_MODEL_FUNC_IF_BODY(Scanner, __VA_ARGS__)
+
+constexpr ObjectModelTableEntry Scanner::objectModelTable[] =
+{
+ // Within each group, these entries must be in alphabetical order
+ // 0. Scanner members
+ { "progress", OBJECT_MODEL_FUNC(self->GetProgress(), 3), ObjectModelEntryFlags::none },
+ { "status", OBJECT_MODEL_FUNC(self->GetStatusCharacter()), ObjectModelEntryFlags::none },
+};
+
+constexpr uint8_t Scanner::objectModelTableDescriptor[] = { 1, 2 };
+
+DEFINE_GET_OBJECT_MODEL_TABLE(Scanner)
+
+#endif
+
+#if SCANNER_AS_SEPARATE_TASK
+
+# include "Tasks.h"
+
+constexpr uint32_t ScannerTaskStackWords = 400; // task stack size in dwords
+static Task<ScannerTaskStackWords> *scannerTask = nullptr;
+
+extern "C" void ScannerTask(void * pvParameters) noexcept
+{
+ for (;;)
+ {
+ reprap.GetScanner().Spin();
+ RTOSIface::Yield();
+ }
+}
+
+#endif
+
+void Scanner::Init() noexcept
+{
+ enabled = false;
+ SetState(ScannerState::Disconnected);
+ bufferPointer = 0;
+ fileBeingUploaded = nullptr;
+}
+
+void Scanner::SetState(const ScannerState s) noexcept
+{
+ progress = 0.0f;
+ doingGCodes = false;
+ if (state != s)
+ {
+ state = s;
+ reprap.ScannerUpdated();
+ }
+}
+
+void Scanner::Exit() noexcept
+{
+ if (IsEnabled())
+ {
+ // Notify the scanner that we cannot complete the current action
+ if (state == ScannerState::Scanning || state == ScannerState::Calibrating)
+ {
+ Cancel();
+ }
+
+ // Delete any pending uploads
+ if (fileBeingUploaded != nullptr)
+ {
+ fileBeingUploaded->Close();
+ fileBeingUploaded = nullptr;
+ platform.Delete(SCANS_DIRECTORY, uploadFilename);
+ }
+
+ // Pretend the scanner is no longer connected
+ SetState(ScannerState::Disconnected);
+ }
+}
+
+void Scanner::Spin() noexcept
+{
+ // Is the 3D scanner extension enabled at all and is a device registered?
+ if (!IsEnabled() || state == ScannerState::Disconnected)
+ {
+ return;
+ }
+
+ // Check if the scanner device is still present
+ if (!SERIAL_MAIN_DEVICE.IsConnected())
+ {
+ // Scanner has been detached - report a warning if we were scanning or uploading
+ if (state == ScannerState::ScanningPre || state == ScannerState::Scanning || state == ScannerState::ScanningPost ||
+ state == ScannerState::Uploading)
+ {
+ platform.Message(WarningMessage, "Scanner disconnected while a 3D scan or upload was in progress");
+ }
+
+ // Delete any pending uploads
+ if (fileBeingUploaded != nullptr)
+ {
+ fileBeingUploaded->Close();
+ fileBeingUploaded = nullptr;
+ platform.Delete(SCANS_DIRECTORY, uploadFilename);
+ }
+ SetState(ScannerState::Disconnected);
+
+ // Run a macro to perform custom actions when the scanner is removed
+ DoFileMacro(SCANNER_OFF_G);
+ }
+ else
+ {
+ // Deal with the current state
+ switch (state)
+ {
+ case ScannerState::EnablingAlign:
+ if (!IsDoingFileMacro())
+ {
+ // Send ALIGN ON to the scanner
+ platform.Message(MessageType::BlockingUsbMessage, "ALIGN ON\n");
+ SetState(ScannerState::Idle);
+ }
+ break;
+
+ case ScannerState::DisablingAlign:
+ if (!IsDoingFileMacro())
+ {
+ // Send ALIGN OFF to the scanner
+ platform.Message(MessageType::BlockingUsbMessage, "ALIGN OFF\n");
+ SetState(ScannerState::Idle);
+ }
+ break;
+
+ case ScannerState::ScanningPre:
+ if (!IsDoingFileMacro())
+ {
+ // Pre macro complete, build and send SCAN command
+ platform.MessageF(MessageType::BlockingUsbMessage, "SCAN %d %d %d %s\n", scanRange, scanResolution, scanMode, scanFilename.c_str());
+ SetState(ScannerState::Scanning);
+ }
+ break;
+
+ // Scanning state is controlled by external 3D scanner
+
+ case ScannerState::ScanningPost:
+ if (!IsDoingFileMacro())
+ {
+ // Post macro complete, reset the state
+ SetState(ScannerState::Idle);
+ }
+ break;
+
+ case ScannerState::CalibratingPre:
+ if (!IsDoingFileMacro())
+ {
+ // Pre macro complete, send CALIBRATE
+ platform.MessageF(MessageType::BlockingUsbMessage, "CALIBRATE %d\n", calibrationMode);
+ SetState(ScannerState::Calibrating);
+ }
+ break;
+
+ // Calibrating state is controlled by external 3D scanner
+
+ case ScannerState::CalibratingPost:
+ if (!IsDoingFileMacro())
+ {
+ // Post macro complete, reset the state
+ SetState(ScannerState::Idle);
+ }
+ break;
+
+ case ScannerState::Uploading:
+ {
+ const size_t initialUploadBytesLeft = uploadBytesLeft;
+
+ // Write incoming scan data from USB to the file
+ int bytesToRead = SERIAL_MAIN_DEVICE.available();
+ FileWriteBuffer *buf = fileBeingUploaded->GetWriteBuffer();
+ if (buf != nullptr)
+ {
+ // Copy whole blocks from the USB RX buffer
+ if (bytesToRead > (int)buf->BytesLeft())
+ {
+ bytesToRead = buf->BytesLeft();
+ }
+
+ SERIAL_MAIN_DEVICE.readBytes(buf->Data() + buf->BytesStored(), bytesToRead);
+ buf->DataStored(bytesToRead);
+ uploadBytesLeft -= bytesToRead;
+
+ // Note we call FileStore::Write here instead of FileStore::Flush because we
+ // do not want to update the FS table every time an upload buffer is written
+ if ((buf->BytesLeft() == 0 || uploadBytesLeft == 0) && !fileBeingUploaded->Write(buf->Data(), 0))
+ {
+ fileBeingUploaded->Close();
+ fileBeingUploaded = nullptr;
+ platform.Delete(SCANS_DIRECTORY, uploadFilename);
+
+ platform.Message(ErrorMessage, "Failed to write scan file\n");
+ SetState(ScannerState::Idle);
+ break;
+ }
+ }
+ else
+ {
+ // Write data character by character if there is no file write buffer available
+ while (bytesToRead > 0)
+ {
+ char b = static_cast<char>(SERIAL_MAIN_DEVICE.read());
+ bytesToRead--;
+
+ if (fileBeingUploaded->Write(&b, sizeof(char)))
+ {
+ uploadBytesLeft--;
+ if (uploadBytesLeft == 0)
+ {
+ // Upload complete
+ break;
+ }
+ }
+ else
+ {
+ fileBeingUploaded->Close();
+ fileBeingUploaded = nullptr;
+ platform.Delete(SCANS_DIRECTORY, uploadFilename);
+
+ platform.Message(ErrorMessage, "Failed to write scan file\n");
+ SetState(ScannerState::Idle);
+ break;
+ }
+ }
+ }
+
+ // Have we finished this upload?
+ if (fileBeingUploaded != nullptr && uploadBytesLeft == 0)
+ {
+ if (reprap.Debug(moduleScanner))
+ {
+ platform.MessageF(HttpMessage, "Finished uploading %u bytes of scan data\n", uploadSize);
+ }
+
+ fileBeingUploaded->Close();
+ fileBeingUploaded = nullptr;
+
+ SetState(ScannerState::Idle);
+ }
+ else if (uploadBytesLeft != initialUploadBytesLeft)
+ {
+ reprap.ScannerUpdated();
+ }
+ }
+ break;
+
+ default:
+ // Pick up incoming commands only if the GCodeBuffer is idle.
+ // The GCodes class will do the processing for us.
+ if (serialGCode->IsIdle() && SERIAL_MAIN_DEVICE.available() > 0)
+ {
+ char b = static_cast<char>(SERIAL_MAIN_DEVICE.read());
+ if (b == '\n' || b == '\r')
+ {
+ buffer[bufferPointer] = 0;
+ ProcessCommand();
+ bufferPointer = 0;
+ }
+ else
+ {
+ buffer[bufferPointer++] = b;
+ if (bufferPointer >= ScanBufferSize)
+ {
+ platform.Message(ErrorMessage, "Scan buffer overflow\n");
+ bufferPointer = 0;
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+// Process incoming commands from the scanner board
+void Scanner::ProcessCommand() noexcept
+{
+ // Output some info if debugging is enabled
+ if (reprap.Debug(moduleScanner))
+ {
+ platform.MessageF(HttpMessage, "Scanner request: '%s'\n", buffer);
+ }
+
+ // Register request: M751
+ if (StringEqualsIgnoreCase(buffer, "M751"))
+ {
+ platform.Message(MessageType::BlockingUsbMessage, "OK\n");
+ SetState(ScannerState::Idle);
+ }
+
+ // G-Code request: GCODE <CODE>
+ else if (StringStartsWith(buffer, "GCODE "))
+ {
+ doingGCodes = true;
+ serialGCode->PutAndDecode(&buffer[6], bufferPointer - 6);
+ }
+
+ // Switch to post-processing mode: POSTPROCESS
+ else if (StringEqualsIgnoreCase(buffer, "POSTPROCESS"))
+ {
+ SetState(ScannerState::PostProcessing);
+ }
+
+ // Progress indicator: PROGRESS <PERCENT>
+ else if (StringStartsWith(buffer, "PROGRESS "))
+ {
+ const float parsedProgress = SafeStrtof(&buffer[9]);
+ progress = constrain<float>(parsedProgress, 0.0f, 100.0f);
+ reprap.ScannerUpdated();
+ }
+
+ // Upload request: UPLOAD <SIZE> <FILENAME>
+ else if (StringStartsWith(buffer, "UPLOAD "))
+ {
+ uploadSize = StrToU32(&buffer[7]);
+ uploadFilename = nullptr;
+ for(size_t i = 8; i < bufferPointer - 1; i++)
+ {
+ if (buffer[i] == ' ')
+ {
+ uploadFilename = &buffer[i+1];
+ break;
+ }
+ }
+
+ if (uploadFilename != nullptr)
+ {
+ uploadBytesLeft = uploadSize;
+ fileBeingUploaded = platform.OpenFile(SCANS_DIRECTORY, uploadFilename, OpenMode::write);
+ if (fileBeingUploaded != nullptr)
+ {
+ SetState(ScannerState::Uploading);
+ if (reprap.Debug(moduleScanner))
+ {
+ platform.MessageF(HttpMessage, "Starting scan upload for file %s (%u bytes total)\n", uploadFilename, uploadSize);
+ }
+ }
+ }
+ else
+ {
+ platform.Message(ErrorMessage, "Malformed scanner upload request\n");
+ }
+ }
+
+ // Acknowledgment: OK
+ else if (StringEqualsIgnoreCase(buffer, "OK"))
+ {
+ if (state == ScannerState::Scanning)
+ {
+ // Scan complete, run scan_post.g
+ DoFileMacro(SCAN_POST_G);
+ SetState(ScannerState::ScanningPost);
+ }
+ else if (state == ScannerState::PostProcessing)
+ {
+ // Post-processing complete, reset the state
+ SetState(ScannerState::Idle);
+ }
+ else if (state == ScannerState::Calibrating)
+ {
+ // Calibration complete, run calibrate_post.g
+ DoFileMacro(CALIBRATE_POST_G);
+ SetState(ScannerState::CalibratingPost);
+ }
+ }
+
+ // Error message: ERROR msg
+ else if (StringStartsWith(buffer, "ERROR"))
+ {
+ // if this command contains a message, report it
+ if (bufferPointer > 6)
+ {
+ platform.MessageF(ErrorMessage, "%s\n", &buffer[6]);
+ }
+
+ // reset the state
+ SetState(ScannerState::Idle);
+ }
+}
+
+// Enable the scanner extensions
+bool Scanner::Enable() noexcept
+{
+ enabled = true;
+#if SCANNER_AS_SEPARATE_TASK
+ if (scannerTask == nullptr)
+ {
+ scannerTask = new Task<ScannerTaskStackWords>;
+ scannerTask->Create(ScannerTask, "SCANNER", nullptr, TaskBase::SpinPriority);
+ }
+#endif
+ return true;
+}
+
+// Register a scanner device
+void Scanner::Register() noexcept
+{
+ if (!IsRegistered())
+ {
+ platform.Message(MessageType::BlockingUsbMessage, "OK\n");
+ SetState(ScannerState::Idle);
+
+ DoFileMacro(SCANNER_ON_G);
+ }
+}
+
+// Initiate a new scan
+bool Scanner::StartScan(const char *filename, int range, int resolution, int mode) noexcept
+{
+ if (state != ScannerState::Idle)
+ {
+ // We're doing something else at this moment
+ return false;
+ }
+ if (!serialGCode->IsIdle())
+ {
+ // The G-code buffer is still blocked
+ return false;
+ }
+
+ // Copy the scan length/degree and the filename
+ scanFilename.copy(filename);
+ scanRange = range;
+ scanResolution = resolution;
+ scanMode = mode;
+
+ // Run the scan_pre macro and wait for it to finish
+ DoFileMacro(SCAN_PRE_G);
+ SetState(ScannerState::ScanningPre);
+
+ return true;
+}
+
+// Cancel current 3D scanner action
+bool Scanner::Cancel() noexcept
+{
+ if (state == ScannerState::ScanningPre || state == ScannerState::ScanningPost ||
+ state == ScannerState::CalibratingPre || state == ScannerState::CalibratingPost)
+ {
+ // We're waiting for the scan/calibration to start/end, so wait a little longer
+ return false;
+ }
+
+ if (state != ScannerState::Scanning && state != ScannerState::Calibrating)
+ {
+ // Only permit this request if the 3D scanner is actually working
+ return true;
+ }
+
+ // Send CANCEL request to the scanner
+ platform.Message(MessageType::BlockingUsbMessage, "CANCEL\n");
+ SetState(ScannerState::Idle);
+
+ return true;
+}
+
+// Send ALIGN ON/OFF to the 3D scanner
+bool Scanner::SetAlignment(bool on) noexcept
+{
+ if (state != ScannerState::Idle)
+ {
+ // Not ready yet, try again later
+ return false;
+ }
+ if (!serialGCode->IsIdle())
+ {
+ // The G-code buffer is still blocked
+ return false;
+ }
+
+ DoFileMacro(on ? ALIGN_ON_G : ALIGN_OFF_G);
+ SetState(on ? ScannerState::EnablingAlign : ScannerState::DisablingAlign);
+ return true;
+}
+
+// Sends SHUTDOWN to the 3D scanner and unregisters it
+bool Scanner::Shutdown() noexcept
+{
+ if (state != ScannerState::Idle)
+ {
+ // Not ready yet, try again later
+ return false;
+ }
+
+ // Send SHUTDOWN to the scanner and unregister it
+ platform.Message(MessageType::BlockingUsbMessage, "SHUTDOWN\n");
+ SetState(ScannerState::Disconnected);
+
+ return true;
+}
+
+// Calibrate the 3D scanner
+bool Scanner::Calibrate(int mode) noexcept
+{
+ if (state != ScannerState::Idle)
+ {
+ // We're doing something else at this moment
+ return false;
+ }
+ if (!serialGCode->IsIdle())
+ {
+ // The G-code buffer is still blocked
+ return false;
+ }
+
+ // In theory it would be good to verify if this succeeds, but the scanner client cannot give feedback (yet)
+ calibrationMode = mode;
+ DoFileMacro(CALIBRATE_PRE_G);
+ SetState(ScannerState::CalibratingPre);
+
+ return true;
+}
+
+const char Scanner::GetStatusCharacter() const noexcept
+{
+ switch (state)
+ {
+ case ScannerState::Disconnected:
+ return 'D';
+
+ case ScannerState::EnablingAlign:
+ case ScannerState::DisablingAlign:
+ case ScannerState::Idle:
+ return 'I';
+
+ case ScannerState::ScanningPre:
+ case ScannerState::Scanning:
+ case ScannerState::ScanningPost:
+ return 'S';
+
+ case ScannerState::PostProcessing:
+ return 'P';
+
+ case ScannerState::CalibratingPre:
+ case ScannerState::Calibrating:
+ case ScannerState::CalibratingPost:
+ return 'C';
+
+ case ScannerState::Uploading:
+ return 'U';
+ }
+
+ return 'I';
+}
+
+// Return the progress of the current operation
+float Scanner::GetProgress() const noexcept
+{
+ if (state == ScannerState::Uploading)
+ {
+ return (float)(uploadSize - uploadBytesLeft) / (float)uploadSize;
+ }
+
+ return progress;
+}
+
+// Is a macro file being executed?
+bool Scanner::IsDoingFileMacro() const noexcept
+{
+ return (serialGCode->IsDoingFileMacro() || (serialGCode->Seen('M') && serialGCode->GetIValue() == 98));
+}
+
+// Perform a file macro using the GCodeBuffer
+void Scanner::DoFileMacro(const char *filename) noexcept
+{
+ if (platform.SysFileExists(filename))
+ {
+ String<MaxFilenameLength + 7> gcode;
+ gcode.printf("M98 P\"%s\"\n", filename);
+ serialGCode->PutAndDecode(gcode.c_str());
+ }
+}
+
+#endif
diff --git a/src/Platform/Scanner.h b/src/Platform/Scanner.h
new file mode 100644
index 00000000..464a9f61
--- /dev/null
+++ b/src/Platform/Scanner.h
@@ -0,0 +1,112 @@
+/*
+ * Scanner.h
+ *
+ * Created on: 21 Mar 2017
+ * Author: Christian
+ */
+
+#ifndef SRC_SCANNER_H_
+#define SRC_SCANNER_H_
+
+#include "RepRapFirmware.h"
+#include "GCodes/GCodeBuffer/GCodeBuffer.h"
+#include "ObjectModel/ObjectModel.h"
+
+#if SUPPORT_SCANNER
+
+#define SCANNER_AS_SEPARATE_TASK (0) // set to 1 to use a separate task for the scanner (requires RTOS enabled too)
+//#define SCANNER_AS_SEPARATE_TASK (defined(RTOS)) // set to 1 to use a separate task for the scanner (requires RTOS enabled too)
+
+const size_t ScanBufferSize = 128; // Size of the buffer for incoming commands
+
+enum class ScannerState
+{
+ Disconnected, // scanner mode is disabled
+ Idle, // scanner is registered but not active
+
+ EnablingAlign, // running align_on.g
+ DisablingAlign, // running align_off.g
+
+ ScanningPre, // running scan_pre.g
+ Scanning, // 3D scanner is scanning
+ ScanningPost, // running scan_post.g
+
+ PostProcessing, // post-processor is busy
+
+ CalibratingPre, // running calibrate_pre.g
+ Calibrating, // 3D scanner is calibrating
+ CalibratingPost, // running calibrate_post.g
+
+ Uploading // uploading a binary file
+};
+
+class Scanner INHERIT_OBJECT_MODEL
+{
+public:
+ friend class GCodes;
+
+ Scanner(Platform& p) noexcept : platform(p) { }
+ void Init() noexcept;
+ void Exit() noexcept;
+ void Spin() noexcept;
+
+ bool IsEnabled() const noexcept { return enabled; } // Is the usage of a 3D scanner enabled?
+ bool Enable() noexcept; // Enable 3D scanner extension. Returns true when done
+
+ bool IsRegistered() const noexcept; // Is the 3D scanner registered and ready to use?
+ void Register() noexcept; // Register a 3D scanner instance
+ // External scanners are automatically unregistered when the main port (USB) is closed
+
+ // Start a new 3D scan. Returns true when the scan has been initiated
+ bool StartScan(const char *filename, int param, int resolution, int mode) noexcept;
+
+ bool Cancel() noexcept; // Cancel current 3D scanner action. Returns true when done
+ bool Calibrate(int mode) noexcept; // Calibrate the 3D scanner. Returns true when done
+ bool SetAlignment(bool on) noexcept; // Send ALIGN ON/OFF to the 3D scanner. Returns true when done
+ bool Shutdown() noexcept; // Send SHUTDOWN to the scanner and unregisters it
+
+ bool DoingGCodes() const noexcept { return doingGCodes; } // Has the scanner run any G-codes since the last state transition?
+ const char GetStatusCharacter() const noexcept; // Returns the status char for the status response
+ float GetProgress() const noexcept; // Returns the progress of the current action
+
+protected:
+ DECLARE_OBJECT_MODEL
+
+private:
+ GCodeBuffer *serialGCode;
+
+ void SetGCodeBuffer(GCodeBuffer *gb) noexcept;
+
+ void SetState(const ScannerState s) noexcept;
+ void ProcessCommand() noexcept;
+
+ bool IsDoingFileMacro() const noexcept;
+ void DoFileMacro(const char *filename) noexcept;
+
+ Platform& platform;
+
+ bool enabled;
+
+ bool doingGCodes;
+ float progress;
+ ScannerState state;
+
+ char buffer[ScanBufferSize];
+ size_t bufferPointer;
+
+ int calibrationMode;
+
+ String<MaxFilenameLength> scanFilename;
+ int scanRange, scanResolution, scanMode;
+
+ const char *uploadFilename;
+ size_t uploadSize, uploadBytesLeft;
+ FileStore *fileBeingUploaded;
+};
+
+inline bool Scanner::IsRegistered() const noexcept { return (state != ScannerState::Disconnected); }
+inline void Scanner::SetGCodeBuffer(GCodeBuffer *gb) noexcept { serialGCode = gb; }
+
+#endif
+
+#endif
diff --git a/src/Platform/TaskPriorities.h b/src/Platform/TaskPriorities.h
new file mode 100644
index 00000000..57c194f4
--- /dev/null
+++ b/src/Platform/TaskPriorities.h
@@ -0,0 +1,39 @@
+/*
+ * TaskPriorities.h
+ *
+ * Created on: 23 Oct 2019
+ * Author: David
+ */
+
+#ifndef SRC_TASKPRIORITIES_H_
+#define SRC_TASKPRIORITIES_H_
+
+// Task priorities
+namespace TaskPriority
+{
+ static constexpr int IdlePriority = 0;
+ static constexpr int SpinPriority = 1; // priority for tasks that rarely block
+#if defined(LPC_NETWORKING)
+ static constexpr int TcpPriority = 2;
+ //EMAC priority = 3 defined in FreeRTOSIPConfig.h
+ static constexpr int HeatPriority = 4;
+ static constexpr int SensorsPriority = 4;
+ static constexpr int AinPriority = 4;
+ static constexpr int HeightFollowingPriority = 4;
+ static constexpr int LaserPriority = 5;
+#else
+ static constexpr int HeatPriority = 2;
+ static constexpr int SensorsPriority = 2;
+ static constexpr int TmcPriority = 2;
+ static constexpr int AinPriority = 2;
+ static constexpr int HeightFollowingPriority = 2;
+ static constexpr int DueXPriority = 3;
+ static constexpr int LaserPriority = 3;
+ static constexpr int CanSenderPriority = 3;
+ static constexpr int CanReceiverPriority = 3;
+ static constexpr int CanClockPriority = 3;
+ static constexpr int EthernetPriority = 3;
+#endif
+}
+
+#endif /* SRC_TASKPRIORITIES_H_ */
diff --git a/src/Platform/Tasks.cpp b/src/Platform/Tasks.cpp
new file mode 100644
index 00000000..a1e25114
--- /dev/null
+++ b/src/Platform/Tasks.cpp
@@ -0,0 +1,394 @@
+/*
+ * Startup.cpp
+ *
+ * Created on: 26 Mar 2018
+ * Author: David
+ */
+
+#include "Tasks.h"
+#include "RepRap.h"
+#include "Platform.h"
+#include <Cache.h>
+#include <Platform/TaskPriorities.h>
+#include <Hardware/SoftwareReset.h>
+#include <Storage/CRC32.h>
+
+#if SAM4E || SAM4S || SAME70
+# include <efc/efc.h> // for efc_enable_cloe()
+#endif
+
+#if SAME5x
+# include <hpl_user_area.h>
+#endif
+
+#include <FreeRTOS.h>
+#include <task.h>
+#include <freertos_task_additions.h>
+#include <malloc.h>
+
+const uint8_t memPattern = 0xA5; // this must be the same pattern as FreeRTOS because we use common code for checking for stack overflow
+
+extern char _end; // defined in linker script
+extern char _estack; // defined in linker script
+
+// Define replacement standard library functions
+#include <syscalls.h>
+
+#ifndef DEBUG
+extern uint32_t _firmware_crc; // defined in linker script
+#endif
+
+// MAIN task data
+// The main task currently runs GCodes, so it needs to be large enough to hold the matrices used for delta auto calibration.
+// The worst case stack usage is after running delta auto calibration with Move debugging enabled.
+// The timer and idle tasks currently never do I/O, so they can be much smaller.
+#if SAME70
+constexpr unsigned int MainTaskStackWords = 1800; // on the SAME70 we use matrices of doubles
+#elif defined(__LPC17xx__)
+constexpr unsigned int MainTaskStackWords = 1110-(16*9); // LPC builds only support 16 calibration points, so less space needed
+#else
+constexpr unsigned int MainTaskStackWords = 1110; // on other processors we use matrixes of floats
+#endif
+
+static Task<MainTaskStackWords> mainTask;
+extern "C" [[noreturn]] void MainTask(void * pvParameters) noexcept;
+
+// Idle task data
+constexpr unsigned int IdleTaskStackWords = 40; // currently we don't use the idle talk for anything, so this can be quite small
+static Task<IdleTaskStackWords> idleTask;
+
+extern "C" void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) noexcept
+{
+ *ppxIdleTaskTCBBuffer = idleTask.GetTaskMemory();
+ *ppxIdleTaskStackBuffer = idleTask.GetStackBase();
+ *pulIdleTaskStackSize = idleTask.GetStackSize();
+}
+
+#if configUSE_TIMERS
+
+// Timer task data
+constexpr unsigned int TimerTaskStackWords = 60;
+static Task<TimerTaskStackWords> timerTask;
+
+extern "C" void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) noexcept
+{
+ *ppxTimerTaskTCBBuffer = timerTask.GetTaskMemory();
+ *ppxTimerTaskStackBuffer = timerTask.GetStackBase();
+ *pulTimerTaskStackSize = timerTask.GetStackSize();
+}
+
+#endif
+
+// Mutexes
+static Mutex i2cMutex;
+static Mutex mallocMutex;
+static Mutex filamentsMutex;
+
+// We need to make malloc/free thread safe. We must use a recursive mutex for it.
+extern "C" void GetMallocMutex() noexcept
+{
+ if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) // don't take mutex if scheduler not started or suspended
+ {
+ mallocMutex.Take();
+ }
+}
+
+extern "C" void ReleaseMallocMutex() noexcept
+{
+ if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) // don't release mutex if scheduler not started or suspended
+ {
+ mallocMutex.Release();
+ }
+}
+
+// Application entry point
+[[noreturn]] void AppMain() noexcept
+{
+ pinMode(DiagPin, (DiagOnPolarity) ? OUTPUT_LOW : OUTPUT_HIGH); // set up diag LED for debugging and turn it off
+
+#if !defined(DEBUG) && !defined(__LPC17xx__) // don't check the CRC of a debug build because debugger breakpoints mess up the CRC
+ // Check the integrity of the firmware by checking the firmware CRC
+ {
+ const char *firmwareStart = reinterpret_cast<const char*>(SCB->VTOR & 0xFFFFFF80);
+ CRC32 crc;
+ crc.Update(firmwareStart, (const char*)&_firmware_crc - firmwareStart);
+ if (crc.Get() != _firmware_crc)
+ {
+ // CRC failed so flash the diagnostic LED 3 times, pause and repeat. This is the same error code used by the Duet 3 expansion boards bootloader.
+ for (unsigned int i = 0; ; ++i)
+ {
+ const bool on = (i & 1) == 0 && (i & 15) < 6; // turn LED on if count is 0, 2, 4 or 16, 18, 20 etc. otherwise turn it off
+ digitalWrite(DiagPin, XNor(on, DiagOnPolarity));
+ for (unsigned int j = 0; j < 500; ++j)
+ {
+ delayMicroseconds(1000); // delayMicroseconds only works with low values of delay so do 1ms at a time
+ }
+ }
+ }
+ }
+#endif // !defined(DEBUG) && !defined(__LPC17xx__)
+
+ // Fill the free memory with a pattern so that we can check for stack usage and memory corruption
+ char* heapend = heapTop;
+ register const char * stack_ptr asm ("sp");
+ while (heapend + 16 < stack_ptr)
+ {
+ *heapend++ = memPattern;
+ }
+
+#if SAME5x
+ {
+ const uint32_t bootloaderSize = SCB->VTOR & 0xFFFFFF80;
+ if (bootloaderSize == 0x4000)
+ {
+ // Looks like this is release firmware that was loaded by a bootloader in the first 16Kb of flash
+ // Check that the bootloader is protected and EEPROM is configured
+ uint64_t nvmUserRow0 = *reinterpret_cast<const uint64_t*>(NVMCTRL_USER); // we only need values in the first 64 bits of the user area
+ constexpr uint64_t mask = ((uint64_t)0x0F << 32) | ((uint64_t)0x07 << 36) | (0x0F << 26); // we just want NVM_BOOT (bits 26-29), SEE.SBLK (bits 32-35) and SEE.PSZ (bits 36:38)
+ constexpr uint64_t reqValue = ((uint64_t)0x01 << 32) | ((uint64_t)0x03 << 36) | (13 << 26); // 4K SMART EEPROM and 16K bootloader (SBLK=1 PSZ=3 BOOTPROT=13)
+
+ if ((nvmUserRow0 & mask) != reqValue)
+ {
+ nvmUserRow0 = (nvmUserRow0 & ~mask) | reqValue; // set up the required value
+ _user_area_write(reinterpret_cast<void*>(NVMCTRL_USER), 0, reinterpret_cast<const uint8_t*>(&nvmUserRow0), sizeof(nvmUserRow0));
+
+ // If we reset immediately then the user area write doesn't complete and the bits get set to all 1s.
+ delayMicroseconds(10000);
+ Reset();
+ }
+ }
+ }
+#endif
+
+ CoreInit();
+ DeviceInit();
+
+ // Trap integer divide-by-zero.
+ // We could also trap unaligned memory access, if we change the gcc options to not generate code that uses unaligned memory access.
+ SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
+
+#if !defined(__LPC17xx__) && !SAME5x
+ // When doing a software reset, we disable the NRST input (User reset) to prevent the negative-going pulse that gets generated on it being held
+ // in the capacitor and changing the reset reason from Software to User. So enable it again here. We hope that the reset signal will have gone away by now.
+# ifndef RSTC_MR_KEY_PASSWD
+// Definition of RSTC_MR_KEY_PASSWD is missing in the SAM3X ASF files
+# define RSTC_MR_KEY_PASSWD (0xA5u << 24)
+# endif
+ RSTC->RSTC_MR = RSTC_MR_KEY_PASSWD | RSTC_MR_URSTEN;
+#endif
+
+ Cache::Init(); // initialise the cache and/or the MPU, if applicable to this processor
+ Cache::Enable();
+
+#if SAM4S
+ efc_enable_cloe(EFC0); // enable code loop optimisation
+#elif SAM4E || SAME70
+ efc_enable_cloe(EFC); // enable code loop optimisation
+#endif
+
+ idleTask.AddToList(); // add the FreeRTOS internal tasks to the task list
+
+#if configUSE_TIMERS
+ timerTask.AddToList();
+#endif
+
+ // Create the mutexes and the startup task
+ mallocMutex.Create("Malloc");
+ i2cMutex.Create("I2C");
+ filamentsMutex.Create("Filaments");
+ mainTask.Create(MainTask, "MAIN", nullptr, TaskPriority::SpinPriority);
+
+ vTaskStartScheduler(); // doesn't return
+ for (;;) { } // keep gcc happy
+}
+
+extern "C" [[noreturn]] void MainTask(void *pvParameters) noexcept
+{
+ reprap.Init();
+ for (;;)
+ {
+ reprap.Spin();
+ }
+}
+
+#ifdef __LPC17xx__
+ extern "C" size_t xPortGetTotalHeapSize( void );
+#endif
+
+// Return the amount of free handler stack space. It may be negative if the stack has overflowed into the area reserved for the heap.
+static ptrdiff_t GetHandlerFreeStack() noexcept
+{
+ const char * const ramend = &_estack;
+ const char * stack_lwm = sysStackLimit;
+ while (stack_lwm < ramend && *stack_lwm == memPattern)
+ {
+ ++stack_lwm;
+ }
+ return stack_lwm - sysStackLimit;
+}
+
+ptrdiff_t Tasks::GetNeverUsedRam() noexcept
+{
+ return heapLimit - heapTop;
+}
+
+const char* Tasks::GetHeapTop() noexcept
+{
+ return heapTop;
+}
+
+// Allocate memory permanently. Using this saves about 8 bytes per object. You must not call free() on the returned object.
+// It doesn't try to allocate from the free list maintained by malloc, only from virgin memory.
+void *Tasks::AllocPermanent(size_t sz, std::align_val_t align) noexcept
+{
+ GetMallocMutex();
+ void * const ret = CoreAllocPermanent(sz, align);
+ ReleaseMallocMutex();
+ return ret;
+}
+
+// Write data about the current task
+void Tasks::Diagnostics(MessageType mtype) noexcept
+{
+ Platform& p = reprap.GetPlatform();
+ p.Message(mtype, "=== RTOS ===\n");
+ // Print memory stats
+ {
+ const char * const ramstart =
+#if SAME5x
+ (char *) HSRAM_ADDR;
+#elif defined(__LPC17xx__)
+ (char *) 0x10000000;
+#else
+ (char *) IRAM_ADDR;
+#endif
+ p.MessageF(mtype, "Static ram: %d\n", &_end - ramstart);
+
+#ifdef __LPC17xx__
+ p.MessageF(mtype, "Dynamic Memory (RTOS Heap 5): %d free, %d never used\n", xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize() );
+#else
+ const struct mallinfo mi = mallinfo();
+ p.MessageF(mtype, "Dynamic ram: %d of which %d recycled\n", mi.uordblks, mi.fordblks);
+#endif
+ p.MessageF(mtype, "Never used RAM %d, free system stack %d words\n", GetNeverUsedRam(), GetHandlerFreeStack()/4);
+
+ } // end memory stats scope
+
+ p.Message(mtype, "Tasks:");
+ for (TaskBase *t = TaskBase::GetTaskList(); t != nullptr; t = t->GetNext())
+ {
+ ExtendedTaskStatus_t taskDetails;
+ vTaskGetExtendedInfo(t->GetFreeRTOSHandle(), &taskDetails);
+
+ const char* stateText;
+ switch (taskDetails.eCurrentState)
+ {
+ case esRunning:
+ stateText = "running";
+ break;
+ case esReady:
+ stateText = "ready";
+ break;
+ case esNotifyWaiting:
+ stateText = "notifyWait";
+ break;
+ case esResourceWaiting:
+ stateText = "resourceWait";
+ break;
+ case esDelaying:
+ stateText = "delaying";
+ break;
+ case esSuspended:
+ stateText = "suspended";
+ break;
+ case esBlocked:
+ stateText = "blocked";
+ break;
+ default:
+ stateText = "invalid";
+ break;
+ }
+
+ const char *mutexName = nullptr;
+ if (taskDetails.eCurrentState == esResourceWaiting)
+ {
+ const Mutex *m = Mutex::GetMutexList();
+ while (m != nullptr)
+ {
+ if ((const void *)m == taskDetails.pvResource)
+ {
+ mutexName = m->GetName();
+ break;
+ }
+ m = m->GetNext();
+ }
+ }
+
+ if (mutexName != nullptr)
+ {
+ p.MessageF(mtype, " %s(%s,%s,%u)", taskDetails.pcTaskName, stateText, mutexName, (unsigned int)taskDetails.usStackHighWaterMark);
+ }
+ else
+ {
+ p.MessageF(mtype, " %s(%s,%u)", taskDetails.pcTaskName, stateText, (unsigned int)taskDetails.usStackHighWaterMark);
+ }
+ }
+ p.Message(mtype, "\nOwned mutexes:");
+
+ for (const Mutex *m = Mutex::GetMutexList(); m != nullptr; m = m->GetNext())
+ {
+ const TaskHandle holder = m->GetHolder();
+ if (holder != nullptr)
+ {
+ p.MessageF(mtype, " %s(%s)", m->GetName(), pcTaskGetName(holder->GetFreeRTOSHandle()));
+ }
+ }
+ p.Message(mtype, "\n");
+}
+
+TaskHandle Tasks::GetMainTask() noexcept
+{
+ return &mainTask;
+}
+
+void Tasks::TerminateMainTask() noexcept
+{
+ mainTask.TerminateAndUnlink();
+}
+
+Mutex *Tasks::GetI2CMutex() noexcept
+{
+ return &i2cMutex;
+}
+
+Mutex *Tasks::GetFilamentsMutex() noexcept
+{
+ return &filamentsMutex;
+}
+
+// This intercepts the 1ms system tick
+extern "C" void vApplicationTickHook() noexcept
+{
+ CoreSysTick();
+ reprap.Tick();
+}
+
+// We don't need the time zone functionality. Declaring these saves 8Kb.
+extern "C" void __tzset() noexcept { }
+extern "C" void __tz_lock() noexcept { }
+extern "C" void __tz_unlock() noexcept { }
+extern "C" void _tzset_unlocked() noexcept { }
+
+#if SUPPORT_CAN_EXPANSION
+
+// Functions called by CanMessageBuffer in CANlib
+void *MessageBufferAlloc(size_t sz, std::align_val_t align) noexcept
+{
+ return Tasks::AllocPermanent(sz, align);
+}
+
+void MessageBufferDelete(void *ptr, std::align_val_t align) noexcept { }
+
+#endif
+
+// End
diff --git a/src/Platform/Tasks.h b/src/Platform/Tasks.h
new file mode 100644
index 00000000..f811a927
--- /dev/null
+++ b/src/Platform/Tasks.h
@@ -0,0 +1,35 @@
+/*
+ * Startup.h
+ *
+ * Created on: 26 Mar 2018
+ * Author: David
+ */
+
+#ifndef SRC_TASKS_H_
+#define SRC_TASKS_H_
+
+#include "RepRapFirmware.h"
+#include "MessageType.h"
+#include "RTOSIface/RTOSIface.h"
+
+namespace Tasks
+{
+ void Diagnostics(MessageType mtype) noexcept;
+ TaskHandle GetMainTask() noexcept;
+ void TerminateMainTask() noexcept;
+ ptrdiff_t GetNeverUsedRam() noexcept;
+ void *AllocPermanent(size_t sz, std::align_val_t align = (std::align_val_t)__STDCPP_DEFAULT_NEW_ALIGNMENT__) noexcept;
+ const char* GetHeapTop() noexcept;
+ Mutex *GetI2CMutex() noexcept;
+ Mutex *GetFilamentsMutex() noexcept;
+}
+
+#if SUPPORT_CAN_EXPANSION
+
+// Functions called by CanMessageBuffer in CANlib
+void *MessageBufferAlloc(size_t sz, std::align_val_t align) noexcept;
+void MessageBufferDelete(void *ptr, std::align_val_t align) noexcept;
+
+#endif
+
+#endif /* SRC_TASKS_H_ */