diff options
Diffstat (limited to 'src/Platform')
-rw-r--r-- | src/Platform/Logger.cpp | 222 | ||||
-rw-r--r-- | src/Platform/Logger.h | 69 | ||||
-rw-r--r-- | src/Platform/MessageType.h | 100 | ||||
-rw-r--r-- | src/Platform/OutputMemory.cpp | 669 | ||||
-rw-r--r-- | src/Platform/OutputMemory.h | 184 | ||||
-rw-r--r-- | src/Platform/Platform.cpp | 5227 | ||||
-rw-r--r-- | src/Platform/Platform.h | 1098 | ||||
-rw-r--r-- | src/Platform/PortControl.cpp | 145 | ||||
-rw-r--r-- | src/Platform/PortControl.h | 41 | ||||
-rw-r--r-- | src/Platform/RepRap.cpp | 2926 | ||||
-rw-r--r-- | src/Platform/RepRap.h | 318 | ||||
-rw-r--r-- | src/Platform/Roland.cpp | 267 | ||||
-rw-r--r-- | src/Platform/Roland.h | 74 | ||||
-rw-r--r-- | src/Platform/Scanner.cpp | 608 | ||||
-rw-r--r-- | src/Platform/Scanner.h | 112 | ||||
-rw-r--r-- | src/Platform/TaskPriorities.h | 39 | ||||
-rw-r--r-- | src/Platform/Tasks.cpp | 394 | ||||
-rw-r--r-- | src/Platform/Tasks.h | 35 |
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_ */ |