diff options
author | David Crocker <dcrocker@eschertech.com> | 2016-06-13 18:46:42 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2016-06-15 23:47:15 +0300 |
commit | b12a131e28320614762a11658754a1b459af5be8 (patch) | |
tree | 84ca808fe59af7e87dc69269f33d81aeeafef37c /src | |
parent | 76a635f75ad4717613ac31b427ac0a3c57d44e11 (diff) |
Version 1.13
Temporary fix for RTD temperature spikes causing failed prints
Added 'only if printing a file' condition in M581 command
Fixed M117 for PanelDue
Don't print extruder positions in M114 because they are always zero
Process M0 and M1 when in simulation mode
Allow setting of fan PWM from 50% upwards when a fan is in thermostatic
mode
Reduced default extruder heater PWM frequency
Axes are no longer homes when microstepping is changed
SD card interface speed is included in M122 output
Diffstat (limited to 'src')
-rw-r--r-- | src/Configuration.h | 10 | ||||
-rw-r--r-- | src/DuetNG/FirmwareUpdater.cpp | 64 | ||||
-rw-r--r-- | src/DuetNG/FirmwareUpdater.h | 20 | ||||
-rw-r--r-- | src/DuetNG/Network.cpp | 1084 | ||||
-rw-r--r-- | src/DuetNG/Network.h | 121 | ||||
-rw-r--r-- | src/DuetNG/Pins_DuetNG.h | 186 | ||||
-rw-r--r-- | src/DuetNG/TransactionBuffer.cpp | 58 | ||||
-rw-r--r-- | src/DuetNG/TransactionBuffer.h | 165 | ||||
-rw-r--r-- | src/DuetNG/Webserver.cpp | 1059 | ||||
-rw-r--r-- | src/DuetNG/Webserver.h | 171 | ||||
-rw-r--r-- | src/DuetNG/WifiFirmwareUploader.cpp | 743 | ||||
-rw-r--r-- | src/DuetNG/WifiFirmwareUploader.h | 96 | ||||
-rw-r--r-- | src/ExternalDrivers.cpp | 72 | ||||
-rw-r--r-- | src/GCodeBuffer.cpp | 23 | ||||
-rw-r--r-- | src/GCodes.cpp | 181 | ||||
-rw-r--r-- | src/GCodes.h | 9 | ||||
-rw-r--r-- | src/Heat.cpp | 13 | ||||
-rw-r--r-- | src/Platform.cpp | 162 | ||||
-rw-r--r-- | src/Platform.h | 15 | ||||
-rw-r--r-- | src/Reprap.cpp | 59 | ||||
-rw-r--r-- | src/TemperatureError.cpp | 5 |
21 files changed, 4120 insertions, 196 deletions
diff --git a/src/Configuration.h b/src/Configuration.h index e972ca60..32962045 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -26,11 +26,11 @@ Licence: GPL // Firmware name is now defined in the Pins file #ifndef VERSION -# define VERSION "1.13beta1" +# define VERSION "1.13" #endif #ifndef DATE -# define DATE "2016-05-27" +# define DATE "2016-06-13" #endif #define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman" @@ -68,7 +68,7 @@ const unsigned int MAIN_BAUD_RATE = 115200; // Default communication speed of const unsigned int AUX_BAUD_RATE = 57600; // Ditto - for auxiliary UART device const unsigned int AUX2_BAUD_RATE = 115200; // Ditto - for second auxiliary UART device -const uint32_t SERIAL_MAIN_TIMEOUT = 1000; // timeout in ms for sending dara to the main serial/USB port +const uint32_t SERIAL_MAIN_TIMEOUT = 1000; // timeout in ms for sending data to the main serial/USB port // Heater values @@ -83,7 +83,7 @@ const float TIME_TO_HOT = 150.0; // Seconds const uint8_t MAX_BAD_TEMPERATURE_COUNT = 4; // Number of bad temperature samples permitted before a heater fault is reported const float BAD_LOW_TEMPERATURE = -10.0; // Celsius -const float DEFAULT_TEMPERATURE_LIMIT = 260.0; // Celsius +const float DEFAULT_TEMPERATURE_LIMIT = 262.0; // Celsius const float HOT_END_FAN_TEMPERATURE = 45.0; // Temperature at which a thermostatic hot end fan comes on const float BAD_ERROR_TEMPERATURE = 2000.0; // Must exceed any reasonable 5temperature limit including DEFAULT_TEMPERATURE_LIMIT @@ -93,7 +93,7 @@ const unsigned int FirstRtdChannel = 200; // Temperature sensor channels 200.. // PWM frequencies const unsigned int SlowHeaterPwmFreq = 10; // slow PWM frequency for bed and chamber heaters, compatible with DC/AC SSRs -const unsigned int NormalHeaterPwmFreq = 500; // normal PWM frequency used for hot ends +const unsigned int NormalHeaterPwmFreq = 250; // normal PWM frequency used for hot ends const unsigned int DefaultFanPwmFreq = 500; // increase to 25kHz using M106 command to meet Intel 4-wire PWM fan specification // Default Z probe values diff --git a/src/DuetNG/FirmwareUpdater.cpp b/src/DuetNG/FirmwareUpdater.cpp new file mode 100644 index 00000000..aace1bef --- /dev/null +++ b/src/DuetNG/FirmwareUpdater.cpp @@ -0,0 +1,64 @@ +/* + * FirmwareUpdater.cpp + * + * Created on: 21 May 2016 + * Author: David + */ + +#include "FirmwareUpdater.h" +#include "RepRapFirmware.h" +#include "WifiFirmwareUploader.h" + +namespace FirmwareUpdater +{ + const unsigned int WifiFirmwareModule = 1; + const unsigned int WifiFilesModule = 2; + const unsigned int WifiExternalFirmwareModule = 3; + + // Check that the prerequisites are satisfied. + // Return true if yes, else print a message and return false. + bool CheckFirmwareUpdatePrerequisites(uint8_t moduleMap) + { + if ((moduleMap & (1 << WifiExternalFirmwareModule)) != 0 && (moduleMap & ((1 << WifiFirmwareModule) | (1 << WifiFilesModule))) != 0) + { + reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Invalid combination of firmware update modules\n"); + return false; + } + if ((moduleMap & (1 << WifiFirmwareModule)) != 0 && !reprap.GetPlatform()->GetMassStorage()->FileExists(SYS_DIR, WIFI_FIRMWARE_FILE)) + { + reprap.GetPlatform()->MessageF(GENERIC_MESSAGE, "File %s not found\n", WIFI_FIRMWARE_FILE); + return false; + } + if ((moduleMap & (1 << WifiFilesModule)) != 0 && !reprap.GetPlatform()->GetMassStorage()->FileExists(SYS_DIR, WIFI_WEB_FILE)) + { + reprap.GetPlatform()->MessageF(GENERIC_MESSAGE, "File %s not found\n", WIFI_WEB_FILE); + return false; + } + return true; + } + + bool IsReady() + { + return reprap.GetNetwork()->GetWifiUploader()->IsReady(); + } + + void UpdateModule(unsigned int module) + { + switch(module) + { + case WifiExternalFirmwareModule: + Network::ResetWiFiForExternalUpload(); + break; + + case WifiFirmwareModule: + reprap.GetNetwork()->GetWifiUploader()->SendUpdateFile(WIFI_FIRMWARE_FILE, SYS_DIR, WifiFirmwareUploader::FirmwareAddress); + break; + + case WifiFilesModule: + reprap.GetNetwork()->GetWifiUploader()->SendUpdateFile(WIFI_WEB_FILE, SYS_DIR, WifiFirmwareUploader::WebFilesAddress); + break; + } + } +} + +// End diff --git a/src/DuetNG/FirmwareUpdater.h b/src/DuetNG/FirmwareUpdater.h new file mode 100644 index 00000000..67b1b710 --- /dev/null +++ b/src/DuetNG/FirmwareUpdater.h @@ -0,0 +1,20 @@ +/* + * FirmwareUpdater.h + * + * Created on: 21 May 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_FIRMWAREUPDATER_H_ +#define SRC_DUETNG_FIRMWAREUPDATER_H_ + +#include <cstdint> + +namespace FirmwareUpdater +{ + bool CheckFirmwareUpdatePrerequisites(uint8_t moduleMap); + bool IsReady(); + void UpdateModule(unsigned int module); +} + +#endif /* SRC_DUETNG_FIRMWAREUPDATER_H_ */ diff --git a/src/DuetNG/Network.cpp b/src/DuetNG/Network.cpp new file mode 100644 index 00000000..1578dda2 --- /dev/null +++ b/src/DuetNG/Network.cpp @@ -0,0 +1,1084 @@ +/**************************************************************************************************** + + RepRapFirmware network comms to ESP8266-based device + + ****************************************************************************************************/ + +#include "WifiFirmwareUploader.h" +#include "RepRapFirmware.h" +#include "compiler.h" +#include "Pins.h" +#include "WifiFirmwareUploader.h" +#include "TransactionBuffer.h" + +// Define exactly one of the following as 1, thje other as zero +#define USE_PDC 0 // use peripheral DMA controller +#define USE_DMAC 1 // use general DMA controller + +#if USE_PDC +#include "pdc.h" +#endif + +#if USE_DMAC +#include "dmac.h" +#endif + +#include "matrix.h" + +// Forward declarations of static functions +static void spi_dma_disable(); +static bool spi_dma_check_rx_complete(); + +static TransactionBuffer inBuffer, outBuffer; +static uint32_t dummyOutBuffer[TransactionBuffer::headerDwords] = {0, 0, 0, 0, 0}; + +void EspTransferRequestIsr() +{ + reprap.GetNetwork()->EspRequestsTransfer(); +} + +/*-----------------------------------------------------------------------------------*/ +// WiFi interface class + +Network::Network(Platform* p) : platform(p), responseCode(0), responseBody(nullptr), responseText(nullptr), responseFile(nullptr), + spiTxUnderruns(0), spiRxOverruns(0), + state(disabled), activated(false), connectedToAp(false) + +{ + strcpy(hostname, HOSTNAME); + ClearIpAddress(); +} + +void Network::Init() +{ + // Make sure the ESP8266 is held in the reset state + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); + uploader = new WifiFirmwareUploader(Serial1); +} + +void Network::Activate() +{ + activated = true; + if (state == enabled) + { + Start(); + } +} + +void Network::Exit() +{ + Stop(); +} + +void Network::ClearIpAddress() +{ + for (size_t i = 0; i < ARRAY_SIZE(ipAddress); ++i) + { + ipAddress[i] = 0; + } +} + +// Start up the ESP. We assume it is not already started. +// ESP8266 boot modes: +// GPIO0 GPIO2 GPIO15 +// 0 1 0 Firmware download from UART +// 1 1 0 Normal boot from flash memory +// 0 0 1 SD card boot (not used in on Duet) +void Network::Start() +{ + // The ESP8266 is held in a reset state by a pulldown resistor until we enable it. + // Make sure the ESP8266 is in the reset state + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); + + // Take the ESP8266 out of power down + pinMode(EspEnablePin, OUTPUT); + digitalWrite(EspEnablePin, HIGH); + + // Set up our transfer request pin (GPIO4) as an output and set it low + pinMode(SamTfrReadyPin, OUTPUT); + digitalWrite(SamTfrReadyPin, LOW); + + // Set up our data ready pin (ESP GPIO0) as an output and set it high ready to boot the ESP from flash + pinMode(EspTransferRequestPin, OUTPUT); + digitalWrite(EspTransferRequestPin, HIGH); + + // GPIO2 also needs to be high to boot. It's connected to MISO on the SAM, so set the pullup resistor on that pin + pinMode(APIN_SPI_MISO, INPUT_PULLUP); + + // Set our CS input (ESP GPIO15) low ready for booting the ESP. This also clears the transfer ready latch. + pinMode(SamCsPin, OUTPUT); + digitalWrite(SamCsPin, LOW); + + // Make sure it has time to reset - no idea how long it needs, but 20ms should be plenty + delay(50); + + // Release the reset on the ESP8266 + digitalWrite(EspResetPin, HIGH); + + // Give it time to sample GPIO0 and GPIO15 + // GPIO0 has to be held high for sufficient time: + // - 10ms is not enough + // - 18ms after reset is released, an oscillating signal appears on GPIO0 for 55ms + // - so 18ms is probably long enough. Use 25ms for safety. + delay(50); + + // Relinquish control of our CS pin so that the ESP can take it over + pinMode(SamCsPin, INPUT); + + // Set the data request pin to be an input + pinMode(EspTransferRequestPin, INPUT_PULLUP); + attachInterrupt(EspTransferRequestPin, EspTransferRequestIsr, RISING); + + // The ESP takes about 300ms before it starts talking to use, so don't wait for it here, do that in Spin() + + // Clear the transaction buffers + inBuffer.Clear(); + outBuffer.Clear(); + + state = starting; + + (void) SPI->SPI_SR; // clear any pending interrupt + NVIC_SetPriority(SPI_IRQn, 10); + NVIC_EnableIRQ(SPI_IRQn); + + connectedToAp = false; + spiTxUnderruns = spiRxOverruns = 0; +} + +// Stop the ESP +void Network::Stop() +{ + if (state != disabled) + { + digitalWrite(SamTfrReadyPin, LOW); // tell the ESP we can't receive + for (int i = 0; i < 10 && (state == receivePending || state == sendReceivePending); ++i) + { + delay(1); + } + digitalWrite(EspResetPin, LOW); // put the ESP back into reset + NVIC_DisableIRQ(SPI_IRQn); + spi_disable(SPI); + spi_dma_check_rx_complete(); + spi_dma_disable(); + + ClearIpAddress(); + state = disabled; + connectedToAp = false; + } +} + +void Network::Spin() +{ +// static float lastTime = 0.0; + + // Main state machine. + // Take care with this, because ISRs may cause the following state transitions: + // idle -> transferPending + // transferPending -> processing + switch (state) + { + case starting: + // See if the ESP8266 has set CS high yet + if (digitalRead(SamCsPin) == HIGH) + { + // Setup the SPI controller in slave mode and assign the CS pin to it + platform->Message(HOST_MESSAGE, "WiFi server starting up\n"); + SetupSpi(); // set up the SPI subsystem + state = idle; + TryStartTransfer(); + } + break; + + case transferDone: +// platform->Message(HOST_MESSAGE, "Transfer done\n"); + if (spi_dma_check_rx_complete()) + { + if (inBuffer.IsReady()) + { +// platform->MessageF(DEBUG_MESSAGE, "Rec %u\n", inBuffer.GetFragment()); + if (inBuffer.IsValid()) + { + inBuffer.AppendNull(); +// platform->Message(HOST_MESSAGE, "Got data\n"); + } + else + { + if (reprap.Debug(moduleNetwork)) + { + platform->MessageF(DEBUG_MESSAGE, "Bad msg in: ip=%u.%u.%u.%u opcode=%04x frag=%u length=%u\n", + inBuffer.GetIp() & 255, + (inBuffer.GetIp() >> 8) & 255, + (inBuffer.GetIp() >> 16) & 255, + (inBuffer.GetIp() >> 24) & 255, + inBuffer.GetOpcode(), + inBuffer.GetFragment(), + inBuffer.GetLength() + ); + } + inBuffer.Clear(); + } + } + else + { +// platform->MessageF(DEBUG_MESSAGE, "Rec null %u %u %u %u %u\n", +// inBuffer.GetOpcode(), inBuffer.GetIp(), inBuffer.GetSeq(), inBuffer.GetFragment(), inBuffer.GetLength()); + } + state = processing; + } + else + { + break; + } + // no break + case processing: + // Deal with incoming data, if any + if (inBuffer.IsReady()) + { + ProcessIncomingData(inBuffer); // this may or may not clear inBuffer + } + if (!inBuffer.IsEmpty()) + { + break; // still processing + } + responseFragment = 0; + state = sending; + // no break + case sending: + if (outBuffer.IsEmpty()) + { + // See if we have more of the current response to send + if (responseBody != nullptr) + { + // We have a reply contained in an OutputBuffer + outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment); + if (responseFragment == 0) + { + // Put the return code and content length at the start of the message + outBuffer.AppendU32(responseCode); + outBuffer.AppendU32(responseBody->Length()); + } + + do + { + const size_t len = responseBody->BytesLeft(); + const size_t bytesWritten = outBuffer.AppendData(responseBody->Read(0), len); + if (bytesWritten < len) + { + // Output buffer is full so will will need to send another fragment + (void)responseBody->Read(bytesWritten); // say how much data we have taken from the buffer + break; + } + responseBody = OutputBuffer::Release(responseBody); + } + while (responseBody != nullptr); + + if (responseBody == nullptr) + { + outBuffer.SetLastFragment(); + DebugPrintResponse(); + state = idle; + } + else + { + ++responseFragment; + DebugPrintResponse(); + } + } + else if (responseText != nullptr) + { + // We have a simple text reply to send + outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment); + if (responseFragment == 0) + { + // Put the return code and content length at the start of the message + outBuffer.AppendU32(responseCode); + outBuffer.AppendU32(strlen(responseText)); + } + + const size_t len = strlen(responseText); + const size_t lenSent = outBuffer.AppendData(responseText, len); + if (lenSent < len) + { + responseText += lenSent; + ++responseFragment; + DebugPrintResponse(); + } + else + { + responseText = nullptr; + outBuffer.SetLastFragment(); + DebugPrintResponse(); + state = idle; + } + } + else if (responseFile != nullptr) + { + // We have a file reply to send + outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment); + if (responseFragment == 0) + { + // Put the return code and content length at the start of the message + outBuffer.AppendU32(responseCode); + outBuffer.AppendU32(responseFileBytes); + } + + size_t spaceLeft; + char *p = outBuffer.GetBuffer(spaceLeft); + uint32_t bytesToRead = min<uint32_t>(spaceLeft, responseFileBytes); + int bytesRead = responseFile->Read(p, bytesToRead); + if (bytesRead >= 0 && (uint32_t)bytesRead <= bytesToRead) + { + outBuffer.DataAppended((uint32_t)bytesRead); + } + + bool finished; + if (bytesRead == (int)bytesToRead) + { + responseFileBytes -= (uint32_t)bytesRead; + finished = (responseFileBytes == 0); + } + else + { + // We have a file read error, however it's too late to signal it unless this is the first fragment + finished = true; + } + + if (finished) + { + responseFile->Close(); + responseFile = nullptr; + outBuffer.SetLastFragment(); + DebugPrintResponse(); + state = idle; + } + else + { + ++responseFragment; + DebugPrintResponse(); + } + } + else + { + state = idle; + } + TryStartTransfer(); + } + break; + + case idle: + TryStartTransfer(); + break; + + case disabled: + uploader->Spin(); + break; + + default: + break; + } + + platform->ClassReport(longWait); +} + +void Network::DebugPrintResponse() +{ + if (reprap.Debug(moduleNetwork)) + { + char buffer[200]; + StringRef reply(buffer, ARRAY_SIZE(buffer)); + outBuffer.AppendNull(); + size_t len; + const char* s = (const char*)outBuffer.GetData(len); + uint32_t frag = outBuffer.GetFragment() & ~lastFragment; + + reply.printf("Resp %u: ", outBuffer.GetFragment()); + if (frag == 0 && len >= 8) + { + // First fragment, so there is a 2-word header + reply.catf("%08x %08x ", *(const uint32_t*)s, *(const uint32_t*)(s + 4)); + s += 8; + len -= 8; + } + if (len < 38) + { + reply.catf("%s\n", s); + } + else + { + reply.catf("%c%c%c%c...s\n", s[0], s[1], s[2], s[3], s + len - 30); + } + + platform->Message(HOST_MESSAGE, reply.Pointer()); + } +} + +void Network::ProcessIncomingData(TransactionBuffer &buf) +{ + uint32_t opcode = inBuffer.GetOpcode(); + size_t length; + const void *data = inBuffer.GetData(length); + switch(opcode & 0xFF0000FF) + { + case trTypeInfo | ttNetworkInfo: + // Network info received from host + // Data is 4 bytes of IP address, 4 bytes of free heap, 4 bytes of reset reason, 64 chars of host name, and 32 bytes of ssid + if (length >= 12) + { + const uint8_t *ip = (const uint8_t*) data; + uint32_t freeHeap = *(reinterpret_cast<const uint32_t*>(data) + 1); + uint32_t resetReason = *(reinterpret_cast<const uint32_t*>(data) + 2); + const char *hostName = reinterpret_cast<const char*>(data) + 12; + const char *ssid = reinterpret_cast<const char*>(data) + 76; + platform->MessageF(HOST_MESSAGE, "WiFi server connected to access point %s, IP=%u.%u.%u.%u\n", ssid, ip[0], ip[1], ip[2], ip[3]); + platform->MessageF(HOST_MESSAGE, "WiFi host name %s, free memory %u bytes, reset reason %u\n", hostName, freeHeap, resetReason); + memcpy(ipAddress, ip, 4); + connectedToAp = true; + } + inBuffer.Clear(); + break; + + case trTypeRequest | ttRr: +#if 0 + { + size_t length; + const char* data = (const char*)inBuffer.GetData(length); + if (length > 30) { data += (length - 30); } + platform->MessageF(DEBUG_MESSAGE, "IP %u.%u.%u.%u Frag %u %s\n", + inBuffer.GetIp() & 255, + (inBuffer.GetIp() >> 8) & 255, + (inBuffer.GetIp() >> 16) & 255, + (inBuffer.GetIp() >> 24) & 255, + inBuffer.GetFragment(), + data); + } +#else + // Do nothing - the webserver module will pick it up +#endif + break; + + default: + { + size_t length; + const char* data = (const char*)inBuffer.GetData(length); + platform->MessageF(DEBUG_MESSAGE, "Received opcode %08x length %u data %s\n", opcode, length, data); + } + inBuffer.Clear(); + break; + } +} + +// Called by the webserver module to get an incoming request +const char *Network::GetRequest(uint32_t& ip, size_t& length, uint32_t& fragment) const +{ + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + const void *data = inBuffer.GetData(length); + if (length > 0) + { + ip = inBuffer.GetIp(); + fragment = inBuffer.GetFragment(); + if ((fragment & 0x7FFFFFFF) == 0) + { + length += 1; // allow client to read the null at the end too + } +// platform->MessageF(HOST_MESSAGE, "Req: %s\n", (const char*)data); + return (const char*)data; + } + else + { + platform->Message(DEBUG_MESSAGE, "Bad request\n"); + inBuffer.Clear(); // bad request + } + } + } + return nullptr; +} + +// Send a reply from an OutputBuffer chain. Release the chain when we have finished with it. +void Network::SendReply(uint32_t ip, unsigned int code, OutputBuffer *body) +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response already being sent in SendReply(ob*)\n"); + } + else + { + responseIp = ip; + responseCode = code; + responseBody = body; + +#if 0 + //debug + { + char buf[101]; + size_t len = min<size_t>(ARRAY_UPB(buf), responseBody->DataLength()); + strncpy(buf, responseBody->Data(), len); + buf[len] = 0; + platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, buf); + } +#endif + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + } + } +} + +// Send a reply from a null-terminated string +void Network::SendReply(uint32_t ip, unsigned int code, const char *text) +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response already being sent in SendReply(cc*)\n"); + } + else + { + responseIp = ip; + responseCode = code; + responseText = text; + + //debug + //platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, text); + + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + } + } +} + +// Send a file as the reply. Close the file at the end. +void Network::SendReply(uint32_t ip, unsigned int code, FileStore *file) +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response already being sent in SendReply(fs*)\n"); + file->Close(); + } + else + { + responseIp = ip; + responseCode = code; + responseFile = file; + responseFileBytes = file->Length(); + + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + } + } +} + +// This is called when we have read a message fragment and there is no reply to send +void Network::DiscardMessage() +{ + if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr) + { + platform->Message(HOST_MESSAGE, "response being sent in DiscardMessage\n"); + } + + //debug + //platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, text); + + // Say we have taken the request + if (state == processing) + { + uint32_t opcode = inBuffer.GetOpcode(); + if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr)) + { + inBuffer.Clear(); + } + + // For faster response, change the state to idle so we can accept another packet immediately + state = idle; + TryStartTransfer(); + } +} + +void Network::Diagnostics(MessageType mtype) +{ + platform->Message(mtype, "Network Diagnostics:\n"); + const char* text = (state == starting) ? "starting" + : (state == disabled) ? "disabled" + : (state == enabled) ? "enabled but not running" + : "running"; + platform->MessageF(mtype, "WiFiServer is %s\n", text); + platform->MessageF(mtype, "SPI underruns %u, overruns %u\n", spiTxUnderruns, spiRxOverruns); +} + +void Network::Enable() +{ + if (state == disabled) + { + state = enabled; + if (activated) + { + Start(); + } + } +} + +void Network::Disable() +{ + if (activated && state != disabled) + { + Stop(); + platform->Message(GENERIC_MESSAGE, "WiFi server stopped\n"); + } +} + +bool Network::IsEnabled() const +{ + return state != disabled; +} + +const uint8_t *Network::IPAddress() const +{ + return ipAddress; +} + +uint16_t Network::GetHttpPort() const +{ + return DEFAULT_HTTP_PORT; +} + +void Network::SetHttpPort(uint16_t port) +{ + // Not supported +} + +// Set the DHCP hostname. Removes all whitespaces and converts the name to lower-case. +void Network::SetHostname(const char *name) +{ + size_t i = 0; + while (*name && i < ARRAY_UPB(hostname)) + { + char c = *name++; + if (c >= 'A' && c <= 'Z') + { + c += 'a' - 'A'; + } + + if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-') || (c == '_')) + { + hostname[i++] = c; + } + } + + if (i) + { + hostname[i] = 0; + } + else + { + strcpy(hostname, HOSTNAME); + } +} + +#if USE_PDC +static Pdc *spi_pdc; +#endif + +#if USE_DMAC + +// Our choice of DMA channels to use +const uint32_t CONF_SPI_DMAC_TX_CH = 1; +const uint32_t CONF_SPI_DMAC_RX_CH = 2; + +// Hardware IDs of the SPI transmit and receive DMA interfaces. See atsam datasheet. +const uint32_t DMA_HW_ID_SPI_TX = 1; +const uint32_t DMA_HW_ID_SPI_RX = 2; + +#endif + +static inline void spi_rx_dma_enable() +{ +#if USE_PDC + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_RXTEN); +#endif + +#if USE_DMAC + dmac_channel_enable(DMAC, CONF_SPI_DMAC_RX_CH); +#endif +} + +static inline void spi_tx_dma_enable() +{ +#if USE_PDC + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN); +#endif + +#if USE_DMAC + dmac_channel_enable(DMAC, CONF_SPI_DMAC_TX_CH); +#endif +} + +static inline void spi_rx_dma_disable() +{ +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_RXTDIS); +#endif + +#if USE_DMAC + dmac_channel_disable(DMAC, CONF_SPI_DMAC_RX_CH); +#endif +} + +static inline void spi_tx_dma_disable() +{ +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS); +#endif + +#if USE_DMAC + dmac_channel_disable(DMAC, CONF_SPI_DMAC_TX_CH); +#endif +} + +static void spi_dma_disable() +{ + spi_tx_dma_disable(); + spi_rx_dma_disable(); +} + +static bool spi_dma_check_rx_complete() +{ +#if USE_PDC + return true; +#endif + +#if USE_DMAC + uint32_t status = DMAC->DMAC_CHSR; + if ( ((status & (DMAC_CHSR_ENA0 << CONF_SPI_DMAC_RX_CH)) == 0) // controller is not enabled, perhaps because it finished a full buffer transfer + || ((status & (DMAC_CHSR_EMPT0 << CONF_SPI_DMAC_RX_CH)) != 0) // controller is enabled, probably suspended, and the FIFO is empty + ) + { + // Disable the channel. + // We also need to set the resume bit, otherwise it remains suspended when we re-enable it. + DMAC->DMAC_CHDR = (DMAC_CHDR_DIS0 << CONF_SPI_DMAC_RX_CH) | (DMAC_CHDR_RES0 << CONF_SPI_DMAC_RX_CH); + return true; + } + return false; +#endif +} + +static void spi_tx_dma_setup(const TransactionBuffer *buf, uint32_t maxTransmitLength) +{ +#if USE_PDC + pdc_packet_t pdc_spi_packet; + pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf); + pdc_spi_packet.ul_size = buf->PacketLength() * 4; // need length in bytes + pdc_tx_init(spi_pdc, &pdc_spi_packet, NULL); +#endif + +#if USE_DMAC + DMAC->DMAC_EBCISR; // clear any pending interrupts + + dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(buf)); + dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_TDR)); + dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_TX_CH, 0); + dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_TX_CH, maxTransmitLength | DMAC_CTRLA_SRC_WIDTH_WORD | DMAC_CTRLA_DST_WIDTH_BYTE); + dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_TX_CH, + DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED); +#endif +} + +static void spi_rx_dma_setup(const TransactionBuffer *buf) +{ +#if USE_PDC + pdc_packet_t pdc_spi_packet; + pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf); + pdc_spi_packet.ul_size = TransactionBuffer::MaxReceiveBytes; + pdc_rx_init(spi_pdc, &pdc_spi_packet, NULL); +#endif + +#if USE_DMAC + DMAC->DMAC_EBCISR; // clear any pending interrupts + + dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_RDR)); + dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(buf)); + dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_RX_CH, 0); + dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_RX_CH, TransactionBuffer::MaxTransferBytes | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_WORD); + dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_RX_CH, + DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING); +#endif +} + +/** + * \brief Set SPI slave transfer. + */ +static void spi_slave_dma_setup(bool dataToSend, bool allowReceive) +{ +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS | PERIPH_PTCR_RXTDIS); + + TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer); + spi_tx_dma_setup(outBufPointer); + if (allowReceive) + { + outBufPointer->SetDataTaken(); + spi_rx_dma_setup(&inBuffer); + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN | PERIPH_PTCR_RXTEN); + } + else + { + outBufPointer->ClearDataTaken(); + pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN); + } +#endif + +#if USE_DMAC + spi_dma_disable(); + + TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer); + if (allowReceive) + { + spi_rx_dma_setup(&inBuffer); + spi_rx_dma_enable(); + outBufPointer->SetDataTaken(); + } + else + { + outBufPointer->ClearDataTaken(); + } + + spi_tx_dma_setup(outBufPointer, (dataToSend) ? TransactionBuffer::MaxTransferBytes : 4 * TransactionBuffer::headerDwords); + spi_tx_dma_enable(); +#endif +} + +// Set up the SPI system +void Network::SetupSpi() +{ +#if USE_PDC + spi_pdc = spi_get_pdc_base(SPI); + // The PDCs are masters 2 and 3 and the SRAM is slave 0. Give the PDCs the highest priority. + matrix_set_master_burst_type(0, MATRIX_ULBT_8_BEAT_BURST); + matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER); + matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M2PR_Pos) | (3 << MATRIX_PRAS0_M3PR_Pos)); + matrix_set_slave_slot_cycle(0, 8); +#endif + +#if USE_DMAC + pmc_enable_periph_clk(ID_DMAC); + dmac_init(DMAC); + dmac_set_priority_mode(DMAC, DMAC_PRIORITY_ROUND_ROBIN); + dmac_enable(DMAC); + // The DMAC is master 4 and the SRAM is slave 0. Give the DMAC the highest priority. + matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER); + matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M4PR_Pos)); + // Set the slave slot cycle limit. + // If we leave it at the default value of 511 clock cycles, we get transmit underruns due to the HSMCI using the bus for too long. + // A value of 8 seems to work. I haven't tried other values yet. + matrix_set_slave_slot_cycle(0, 8); +#endif + + pmc_enable_periph_clk(ID_SPI); + spi_dma_disable(); + spi_reset(SPI); // this clears the transmit and receive registers and puts the SPI into slave mode + + // Set up the SPI pins + ConfigurePin(g_APinDescription[APIN_SPI_SCK]); + ConfigurePin(g_APinDescription[APIN_SPI_MOSI]); + ConfigurePin(g_APinDescription[APIN_SPI_MISO]); + ConfigurePin(g_APinDescription[APIN_SPI_SS0]); + +#if USE_DMAC + // Configure DMA RX channel + dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_RX_CH, + DMAC_CFG_SRC_PER(DMA_HW_ID_SPI_RX) | DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG); + + // Configure DMA TX channel + dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_TX_CH, + DMAC_CFG_DST_PER(DMA_HW_ID_SPI_TX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG); +#endif +} + +// Start a transfer if necessary. Not called from an ISR. +void Network::TryStartTransfer() +{ + cpu_irq_disable(); + if (state == idle) + { + if (outBuffer.IsReady()) + { + PrepareForTransfer(true, true); + } + else if (digitalRead(EspTransferRequestPin) == HIGH) + { + PrepareForTransfer(false, true); + } + } + else if (state == sending && outBuffer.IsReady()) + { + PrepareForTransfer(true, false); + } + cpu_irq_enable(); +} + +// This is called from the ISR when the ESP requests to send data +void Network::EspRequestsTransfer() +{ + irqflags_t flags = cpu_irq_save(); + if (state == idle) + { + PrepareForTransfer(false, true); + } + cpu_irq_restore(flags); +} + +// Set up the DMA controller and assert transfer ready. Must be called with interrupts disabled. +void Network::PrepareForTransfer(bool dataToSend, bool allowReceive) +//pre(state == idle || state == sending) +{ + if (allowReceive) + { + state = (dataToSend) ? sendReceivePending : receivePending; + } + + // DMA may have transferred an extra word to the SPI transmit data register. We need to clear this. + // The only way I can find to do this is to issue a software reset to the SPI system. + // Fortunately, this leaves the SPI system in slave mode. + spi_reset(SPI); + spi_set_bits_per_transfer(SPI, 0, SPI_CSR_BITS_8_BIT); + + // Set up the DMA controller + spi_slave_dma_setup(dataToSend, allowReceive); + spi_enable(SPI); + + // Enable the end-of transfer interrupt + (void)SPI->SPI_SR; // clear any pending interrupt + SPI->SPI_IER = SPI_IER_NSSR; // enable the NSS rising interrupt + + // Tell the ESP that we are ready to accept data + digitalWrite(SamTfrReadyPin, HIGH); +} + +// SPI interrupt handler, called when NSS goes high +void SPI_Handler() +{ + reprap.GetNetwork()->SpiInterrupt(); +} + +void Network::SpiInterrupt() +{ + uint32_t status = SPI->SPI_SR; // read status and clear interrupt + SPI->SPI_IDR = SPI_IER_NSSR; // disable the interrupt + if ((status & SPI_SR_NSSR) != 0) + { + if (state == sendReceivePending || state == receivePending) + { +#if USE_PDC + pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS | PERIPH_PTCR_RXTDIS); +#endif + +#if USE_DMAC + spi_tx_dma_disable(); + dmac_channel_suspend(DMAC, CONF_SPI_DMAC_RX_CH); // suspend the receive channel, don't disable it because the FIFO needs to empty first +#endif + spi_disable(SPI); + digitalWrite(SamTfrReadyPin, LOW); + if (state == sendReceivePending) + { + outBuffer.Clear(); // don't send the data again + } + if ((status & SPI_SR_OVRES) != 0) + { + ++spiRxOverruns; + } + if (state == sendReceivePending && (status & SPI_SR_UNDES) != 0) + { + ++spiTxUnderruns; + } + state = transferDone; + } + else if (state == sending) + { + spi_tx_dma_disable(); + spi_disable(SPI); + digitalWrite(SamTfrReadyPin, LOW); + outBuffer.Clear(); // don't send the data again + if ((status & SPI_SR_UNDES) != 0) + { + ++spiTxUnderruns; + } + } + } +} + +// Reset the ESP8266 and leave held in reset +void Network::ResetWiFi() +{ + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); +} + +// Reset the ESP8266 to take commands from the UART. The caller must wait for the reset to complete after calling this. +// ESP8266 boot modes: +// GPIO0 GPIO2 GPIO15 +// 0 1 0 Firmware download from UART +// 1 1 0 Normal boot from flash memory +// 0 0 1 SD card boot (not used in on Duet) +void Network::ResetWiFiForUpload() +{ + // Make sure the ESP8266 is in the reset state + pinMode(EspResetPin, OUTPUT); + digitalWrite(EspResetPin, LOW); + + // Take the ESP8266 out of power down + pinMode(EspEnablePin, OUTPUT); + digitalWrite(EspEnablePin, HIGH); + + // Set up our transfer request pin (GPIO4) as an output and set it low + pinMode(SamTfrReadyPin, OUTPUT); + digitalWrite(SamTfrReadyPin, LOW); + + // Set up our data ready pin (ESP GPIO0) as an output and set it low ready to boot the ESP from UART + pinMode(EspTransferRequestPin, OUTPUT); + digitalWrite(EspTransferRequestPin, LOW); + + // GPIO2 also needs to be high to boot up. It's connected to MISO on the SAM, so set the pullup resistor on that pin + pinMode(APIN_SPI_MISO, INPUT_PULLUP); + + // Set our CS input (ESP GPIO15) low ready for booting the ESP. This also clears the transfer ready latch. + pinMode(SamCsPin, OUTPUT); + digitalWrite(SamCsPin, LOW); + + // Make sure it has time to reset - no idea how long it needs, but 50ms should be plenty + delay(50); + + // Release the reset on the ESP8266 + digitalWrite(EspResetPin, HIGH); +} + +// Reset the ESP8266 to take commands from an external input. The caller must wait for the reset to complete after calling this. +void Network::ResetWiFiForExternalUpload() +{ + ResetWiFiForUpload(); + + // Set our TxD pin low to make things easier for the FTDI chip to drive the ESP RxD input + pinMode(APIN_UART1_TXD, OUTPUT); + digitalWrite(APIN_UART1_TXD, LOW); +} + +// End diff --git a/src/DuetNG/Network.h b/src/DuetNG/Network.h new file mode 100644 index 00000000..2555b830 --- /dev/null +++ b/src/DuetNG/Network.h @@ -0,0 +1,121 @@ +/**************************************************************************************************** + +RepRapFirmware - Network: RepRapPro Ormerod with Duet controller + +Separated out from Platform.h by dc42 and extended by zpl + +****************************************************************************************************/ + +#ifndef NETWORK_H +#define NETWORK_H + +#include <cctype> +#include <cstring> +#include <cstdlib> +#include <climits> + +#include "MessageType.h" + +// Return code definitions +const uint32_t rcNumber = 0x0000FFFF; +const uint32_t rcJson = 0x00010000; +const uint32_t rcKeepOpen = 0x00020000; + +static const uint8_t IP_ADDRESS[4] = { 192, 168, 1, 10 }; // Need some sort of default... +static const uint8_t NET_MASK[4] = { 255, 255, 255, 0 }; +static const uint8_t GATE_WAY[4] = { 192, 168, 1, 1 }; +static const uint16_t DEFAULT_HTTP_PORT = 80; + +class TransactionBuffer; +class WifiFirmwareUploader; + +// The main network class that drives the network. +class Network +{ + enum TransferState + { + disabled, // WiFi not active + enabled, // WiFi enabled but not started yet + starting, // starting up (waiting for WiFi to initialise) + idle, // nothing happening + receivePending, // we have asserted TransferReady and await completion of a receive-only transaction + sendReceivePending, // we have asserted TransferReady and await completion of a transmit/receive + transferDone, // transfer completed but receive DMA fifo may not have been flushed yet + processing, // a transaction has been completed but we haven't released the input buffer yet + sending // a transaction has been completed and we are sending the response + }; + +public: + const uint8_t *IPAddress() const; + void SetIPAddress(const uint8_t ipAddress[], const uint8_t netmask[], const uint8_t gateway[]); + + Network(Platform* p); + void Init(); + void Activate(); + void Exit(); + void Spin(); + void SpiInterrupt(); + void Diagnostics(MessageType mtype); + void Start(); + void Stop(); + + bool InLwip() const { return false; } + + void Enable(); + void Disable(); + bool IsEnabled() const; + + void SetHttpPort(uint16_t port); + uint16_t GetHttpPort() const; + + void SetHostname(const char *name); + void EspRequestsTransfer(); + +#if 0 + void AcquireBus(); + void ReleaseBus(); +#endif + + const char *GetRequest(uint32_t& ip, size_t& length, uint32_t& fragment) const; + void SendReply(uint32_t ip, unsigned int code, OutputBuffer *body); + void SendReply(uint32_t ip, unsigned int code, const char *text); + void SendReply(uint32_t ip, unsigned int code, FileStore *file); + void DiscardMessage(); + + WifiFirmwareUploader *GetWifiUploader() { return uploader; } + + static void ResetWiFi(); + static void ResetWiFiForUpload(); + static void ResetWiFiForExternalUpload(); + +private: + void SetupSpi(); + void PrepareForTransfer(bool dataToSend, bool allowReceive); + void ProcessIncomingData(TransactionBuffer &buf); + void ClearIpAddress(); + void TryStartTransfer(); + void DebugPrintResponse(); + + Platform *platform; + WifiFirmwareUploader *uploader; + + uint32_t responseIp; + uint32_t responseCode; + uint32_t responseFragment; + OutputBuffer *responseBody; + const char* responseText; + FileStore *responseFile; + uint32_t responseFileBytes; + + uint32_t spiTxUnderruns; + uint32_t spiRxOverruns; + + float longWait; + TransferState state; + bool activated; + bool connectedToAp; + uint8_t ipAddress[4]; + char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0 +}; + +#endif diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h new file mode 100644 index 00000000..1a80ea9d --- /dev/null +++ b/src/DuetNG/Pins_DuetNG.h @@ -0,0 +1,186 @@ +#ifndef PINS_DUETNG_H__ +#define PINS_DUETNG_H__ + +#define NAME "RepRapFirmware for Duet WiFi" + +const size_t NumFirmwareUpdateModules = 4; // 3 modules, plus one for manual upload to WiFi module +#define IAP_UPDATE_FILE "iap4e.bin" +#define IAP_FIRMWARE_FILE "DuetWiFiFirmware.bin" +#define WIFI_FIRMWARE_FILE "DuetWiFiServer.bin" +#define WIFI_WEB_FILE "DuetWebControl.bin" + +// Default board type +#ifdef PROTOTYPE_1 +#define DEFAULT_BOARD_TYPE BoardType::DuetNG_06 +#else +#define DEFAULT_BOARD_TYPE BoardType::DuetNG_10 +#endif + +#define SUPPORT_ETHERNET 0 // set nonzero to support embedded web interface over Ethernet +#define SUPPORT_INKJET 0 // set nonzero to support inkjet control +#define SUPPORT_ROLAND 0 // set nonzero to support Roland mill + +// The physical capabilities of the machine + +const size_t DRIVES = 9; // The number of drives in the machine, including X, Y, and Z plus extruder drives +#define DRIVES_(a,b,c,d,e,f,g,h,i) { a,b,c,d,e,f,g,h,i } + +// If enabled, the following control the use of the optional ExternalDrivers module +#define EXTERNAL_DRIVERS (9) +#define FIRST_EXTERNAL_DRIVE (0) + +const int8_t HEATERS = 7; // The number of heaters in the machine; 0 is the heated bed even if there isn't one +#define HEATERS_(a,b,c,d,e,f,g) { a,b,c,d,e,f,g } + +const size_t AXES = 3; // The number of movement axes in the machine, usually just X, Y and Z. <= DRIVES +const size_t NUM_SERIAL_CHANNELS = 2; // The number of serial IO channels (USB and one auxiliary UART) +#define SERIAL_MAIN_DEVICE SerialUSB +#define SERIAL_AUX_DEVICE Serial + +// The numbers of entries in each array must correspond with the values of DRIVES, AXES, or HEATERS. Set values to -1 to flag unavailability. + +// DRIVES + +const Pin ENABLE_PINS[DRIVES] = { 78, 41, 42, 49, 57, 87, 88, 89, 90 }; +const bool ENABLE_VALUES[DRIVES] = { false, false, false, false, false, false, false, false, false }; // What to send to enable a drive +const Pin STEP_PINS[DRIVES] = { 70, 71, 72, 69, 68, 66, 65, 64, 67 }; +const Pin DIRECTION_PINS[DRIVES] = { 75, 76, 77, 01, 73, 92, 86, 80, 81 }; +const bool DIRECTIONS[DRIVES] = { BACKWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS }; // What each axis needs to make it go forwards - defaults + +// Endstops +// RepRapFirmware only has a single endstop per axis. gcode defines if it is a max ("high end") or min ("low end") endstop. gcode also sets if it is active HIGH or LOW. +const Pin END_STOP_PINS[DRIVES] = { 46, 02, 93, 74, 48, 96, 97, 98, 99 }; + +// Indices for motor current digipots (if any): first 4 are for digipot 1 (on duet), second 4 for digipot 2 (on expansion board) +#ifdef PROTOTYPE_1 +const uint8_t POT_WIPES[8] = { 1, 3, 2, 0, 1, 3, 2, 0 }; +const float SENSE_RESISTOR = 0.1; // Stepper motor current sense resistor +const float MAX_STEPPER_DIGIPOT_VOLTAGE = (3.3 * 2.5 / (2.7 + 2.5)); // Stepper motor current reference voltage +const float STEPPER_DAC_VOLTAGE_RANGE = 2.02; // Stepper motor current reference voltage for E1 if using a DAC +const float STEPPER_DAC_VOLTAGE_OFFSET = -0.11; // Stepper motor current offset voltage for E1 if using a DAC +#endif + +// HEATERS + +const bool HEAT_ON = false; // false for inverted heater (e.g. Duet v0.6), true for not (e.g. Duet v0.4) + +const Pin TEMP_SENSE_PINS[HEATERS] = { 45, 47, 44, 61, 62, 63, 59 }; // Thermistor pin numbers + +#ifdef PROTOTYPE_1 +const Pin HEAT_ON_PINS[HEATERS] = { 19, 20, 16, 15, 37, 40, 43 }; // Heater pin numbers +#else +const Pin HEAT_ON_PINS[HEATERS] = { 19, 20, 16, 35, 37, 40, 43 }; // Heater pin numbers +#endif + +// Default thermistor parameters +// Bed thermistor: now assuming 100K +// Hot end thermistor: http://www.digikey.co.uk/product-search/en?x=20&y=11&KeyWords=480-3137-ND +const float BED_R25 = 100000.0; +const float BED_BETA = 3988.0; +const float EXT_R25 = 100000.0; +const float EXT_BETA = 4388.0; + +// Thermistor series resistor value in Ohms +const float THERMISTOR_SERIES_RS = 4700.0; + +// Number of SPI temperature sensors to support + +#if SUPPORT_ROLAND + +// chrishamm's pin assignments +const size_t MaxSpiTempSensors = 2; + +// Digital pins the 31855s have their select lines tied to +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 56, 27 }; + +#else + +const size_t MaxSpiTempSensors = 4; + +// Digital pins the 31855s have their select lines tied to +# ifdef PROTOTYPE_1 +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 24, 25, 50, 51 }; // SPI1_CS0, SPI1_CS1, CS2, CS3 +# else +const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 28, 50, 51, 52 }; // SPI0_CS1, SPI0_CS2, CS3, CS4 +# endif + +#endif + +// Arduino Due pin number that controls the ATX power on/off +const Pin ATX_POWER_PIN = 79; // Arduino Due pin number that controls the ATX power on/off + +// Analogue pin numbers +const Pin Z_PROBE_PIN = 33; // Z probe analog input + +// Digital pin number to turn the IR LED on (high) or off (low) +const Pin Z_PROBE_MOD_PIN = 34; // Digital pin number to turn the IR LED on (high) or off (low) on Duet v0.6 and v1.0 (PB21) + +// COOLING FANS + +const size_t NUM_FANS = 3; +const Pin COOLING_FAN_PINS[NUM_FANS] = { 55, 58, 00 }; +const Pin COOLING_FAN_RPM_PIN = 32; + +#if SUPPORT_INKJET +// Inkjet control pins +const Pin INKJET_SERIAL_OUT = xx; // Serial bitpattern into the shift register +const Pin INKJET_SHIFT_CLOCK = xx; // Shift the register +const Pin INKJET_STORAGE_CLOCK = xx; // Put the pattern in the output register +const Pin INKJET_OUTPUT_ENABLE = xx; // Make the output visible +const Pin INKJET_CLEAR = xx; // Clear the register to 0 + +#endif + +#if SUPPORT_ROLAND +// Roland mill +const Pin ROLAND_CTS_PIN = xx; // Expansion pin 11, PA12_TXD1 +const Pin ROLAND_RTS_PIN = xx; // Expansion pin 12, PA13_RXD1 + +#endif + +// Definition of which pins we allow to be controlled using M42 +// +// The allowed pins are these ones on the DueX4 expansion connector: +//TODO document these + +const size_t NUM_PINS_ALLOWED = 96; + +#if 1 +#define PINS_ALLOWED { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; //TODO temporary! +#else +#define PINS_ALLOWED { \ + /* pins 00-07 */ 0b00000000, \ + /* pins 08-15 */ 0b00000000, \ + /* pins 16-23 */ 0b00000110, \ + /* pins 24-31 */ 0b10000011, \ + /* pins 32-39 */ 0b00001001, \ + /* pins 40-47 */ 0b00000000, \ + /* pins 48-55 */ 0b00011000, \ + /* pins 56-63 */ 0b00000000, \ + /* pins 64-71 */ 0b00000000, \ + /* pins 72-79 */ 0b00000000, \ + /* pins 80-87 */ 0b00000000, \ + /* pins 88-95 */ 0b00001000 \ +} +#endif +// SAM4E Flash locations (may be expanded in the future) +const uint32_t IAP_FLASH_START = 0x00470000; +const uint32_t IAP_FLASH_END = 0x0047FBFF; + +// Duet pin numbers to control the WiFi interface +const Pin EspResetPin = 100; // Low on this in holds the WiFi module in reset (ESP_RESET) +const Pin EspEnablePin = 101; // High to enable the WiFi module, low to power it down (ESP_CH_PD) +const Pin EspTransferRequestPin = 95; // Input from the WiFi module indicating that it wants to transfer data (ESP GPIO0) +const Pin SamTfrReadyPin = 94; // Output from the SAM to the WiFi module indicating we can accept a data transfer (ESP GPIO4 via 7474) +const Pin SamCsPin = 11; // SPI NPCS pin, input from WiFi module + +// Timer allocation (no network timer on DuetNG) +// TC0 channel 0 is used for FAM2 +// TC0 channel 1 is used for the TMC clock +// TC0 channel 2 is available for us to use +#define STEP_TC (TC0) +#define STEP_TC_CHAN (2) +#define STEP_TC_IRQN TC2_IRQn +#define STEP_TC_HANDLER TC2_Handler + +#endif diff --git a/src/DuetNG/TransactionBuffer.cpp b/src/DuetNG/TransactionBuffer.cpp new file mode 100644 index 00000000..7179d4e0 --- /dev/null +++ b/src/DuetNG/TransactionBuffer.cpp @@ -0,0 +1,58 @@ +/* + * TransactionBuffer.cpp + * + * Created on: 17 May 2016 + * Author: David + */ + +#include "Core.h" +#include "TransactionBuffer.h" +#include <cstring> + +void TransactionBuffer::Clear() +{ + trType = 0; + ip = 0; + seq = 0; + fragment = 0; + dataLength = 0; +} + +bool TransactionBuffer::SetMessage(uint32_t tt, uint32_t p_ip, uint32_t frag) +{ + if (IsReady()) + { + return false; + } + trType = tt; + ip = p_ip; + seq = 0; + fragment = frag; + dataLength = 0; + return true; +} + +// Append data to the message in the output buffer, returning the length appended +size_t TransactionBuffer::AppendData(const void* dataToAppend, uint32_t length) +{ + uint32_t spaceLeft = maxSpiDataLength - dataLength; + uint32_t bytesToCopy = min<size_t>(length, spaceLeft); + memcpy((char*)data + dataLength, dataToAppend, bytesToCopy); + dataLength += bytesToCopy; + return bytesToCopy; +} + +// Get the address and size to write data into +char *TransactionBuffer::GetBuffer(size_t& length) +{ + length = maxSpiDataLength - dataLength; + return reinterpret_cast<char*>(data) + dataLength; +} + +// Say we have appended some data +void TransactionBuffer::DataAppended(size_t length) +{ + dataLength += length; +} + +// End diff --git a/src/DuetNG/TransactionBuffer.h b/src/DuetNG/TransactionBuffer.h new file mode 100644 index 00000000..e42d5531 --- /dev/null +++ b/src/DuetNG/TransactionBuffer.h @@ -0,0 +1,165 @@ +/* + * TransactionBuffer.h + * + * Created on: 17 May 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_TRANSACTIONBUFFER_H_ +#define SRC_DUETNG_TRANSACTIONBUFFER_H_ + +#include <cstdint> + +//------------------------------------------------------------------------------------------- +// Transaction buffer class +// ***** This must be kept in step with the corresponding class in the WiFi module code ***** +const uint32_t maxSpiDataLength = 2048; + +//--------------------------------------------------- +// SPI transaction type field bits +// Byte 3 (MSB) is the packet type. +// Byte 2 holds flags +// Byte 1 is currently unused +// Byte 0 is the opcode if the packet is a request or info message, or the error code if it is a response. + +// Packet types +const uint32_t trTypeRequest = 0x3A000000; // this is a request +const uint32_t trTypeResponse = 0xB6000000; // this is a response to a request +const uint32_t trTypeInfo = 0x93000000; // this is an informational message that does not require a response + +// Flags +const uint32_t ttDataTaken = 0x00010000; // indicates to the SAM the the ESP8266 has read its data, or vice verse + +// Opcodes for requests from web sever to Duet +const uint32_t ttRr = 0x01; // any request starting with "rr_" + +// Opcodes for info messages from web server to Duet +const uint32_t ttNetworkInfo = 0x70; // pass network info to Duet, e.g. when first connected + +// Opcodes for requests and info from Duet to web server +const uint32_t ttNetworkConfig = 0x80; // set network configuration (SSID, password etc.) +const uint32_t ttNetworkEnable = 0x81; // enable WiFi +const uint32_t ttGetNetworkInfo = 0x83; // get IP address etc. + +// Opcodes for info messages from Duet to server +const uint32_t ttMachineConfigChanged = 0x82; // notify server that the machine configuration has changed significantly + +// Last fragment bit +const uint32_t lastFragment = 0x80000000; + +class TransactionBuffer +{ + uint32_t trType; // type of transaction, and flags + uint32_t seq; // sequence number of the request + uint32_t ip; // requesting IP address + uint32_t fragment; // fragment number of this packet, top bit set if last fragment + uint32_t dataLength; // number of bytes of data following the header + uint32_t data[maxSpiDataLength/4]; // the actual data, if needed + uint32_t dummy; // extra so that we can append a null and to allow overrun to be detected + +public: + static const uint32_t headerDwords = 5; + + static const uint32_t MaxTransferBytes = maxSpiDataLength + (4 * headerDwords); + + // Initialise + void Init(); + + // Mark this buffer empty + void Clear(); + + // When we send this packet, tell the ESP that we have taken its data + void SetDataTaken() { trType |= ttDataTaken; } + + // Clear the DataTaken flag + void ClearDataTaken() { trType &= ~ttDataTaken; } + + // Say whether data was taken + bool DataWasTaken() const { return (trType & ttDataTaken) != 0; } + + // Return true if this buffer contains data + bool IsReady() const + { + return trType != 0; + } + + bool IsValid() const + { + return dataLength <= maxSpiDataLength; + } + + bool IsEmpty() const + { + return trType == 0; + } + + uint32_t GetOpcode() const + { + return trType; + } + + uint32_t GetSeq() const + { + return seq; + } + + uint32_t GetIp() const + { + return ip; + } + + uint32_t GetFragment() const + { + return fragment; + } + + uint32_t GetLength() const + { + return dataLength; + } + + const void* GetData(size_t& length) const + { + length = dataLength; + return data; + } + + void AppendNull() + { + if (IsReady()) + { + *((char *)data + dataLength) = 0; + } + } + + // Get SPI packet length in dwords + uint32_t PacketLength() const + { + return (IsReady()) ? (dataLength + 3)/4 + headerDwords : headerDwords; + } + + // Set up a message in this buffer + bool SetMessage(uint32_t tt, uint32_t p_ip, uint32_t frag); + + // Append data to the message in the output buffer, returning the length appended + size_t AppendData(const void* dataToAppend, uint32_t length); + + // Append a single 32-bit integer to the output buffer + size_t AppendU32(uint32_t val) + { + return AppendData(&val, sizeof(val)); + } + + void SetLastFragment() + { + fragment |= lastFragment; + } + + // Get the address and remaining size to write data into + char *GetBuffer(size_t& length); + + // Say we have appended some data + void DataAppended(size_t length); +}; + +#endif /* SRC_DUETNG_TRANSACTIONBUFFER_H_ */ diff --git a/src/DuetNG/Webserver.cpp b/src/DuetNG/Webserver.cpp new file mode 100644 index 00000000..27312f03 --- /dev/null +++ b/src/DuetNG/Webserver.cpp @@ -0,0 +1,1059 @@ +/**************************************************************************************************** + + RepRapFirmware - Webserver + + This class serves a single-page web applications to the attached network. This page forms the user's + interface with the RepRap machine. This software interprests returned values from the page and uses it + to generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like + temperature and uses those to construct the web page. + + The page itself - reprap.htm - uses Jquery.js to perform AJAX. See: + + http://jquery.com/ + + ----------------------------------------------------------------------------------------------------- + + Version 0.2 + + 10 May 2013 + + Adrian Bowyer + RepRap Professional Ltd + http://reprappro.com + + Licence: GPL + + ----------------------------------------------------------------------------------------------------- + + The supported requests are GET requests for files (for which the root is the www directory on the + SD card), and the following. These all start with "/rr_". Ordinary files used for the web interface + must not have names starting "/rr_" or they will not be found. + + rr_connect?password=xxx + Sent by the web interface software to establish an initial connection, indicating that + any state variables relating to the web interface (e.g. file upload in progress) should + be reset. This only happens if the password could be verified. + + rr_fileinfo Returns file information about the file being printed. + + rr_fileinfo?name=xxx + Returns file information about a file on the SD card or a JSON-encapsulated response + with err = 1 if the passed filename was invalid. + + rr_status New-style status response, in which temperatures, axis positions and extruder positions + are returned in separate variables. Another difference is that extruder positions are + returned as absolute positions instead of relative to the previous gcode. A client + may also request different status responses by specifying the "type" keyword, followed + by a custom status response type. Also see "M105 S1". + + rr_files?dir=xxx&flagDirs={1/0} + Returns a listing of the filenames in the /gcode directory of the SD card. 'dir' is a + directory path relative to the root of the SD card. If the 'dir' variable is not present, + it defaults to the /gcode directory. If flagDirs is set to 1, all directories will be + prefixed by an asterisk. + + rr_reply Returns the last-known G-code reply as plain text (not encapsulated as JSON). + + rr_upload?name=xxx + Upload a specified file using a POST request. The payload of this request has to be + the file content. Only one file may be uploaded at once. When the upload has finished, + a JSON response with the variable "err" will be returned, which will be 0 if the job + has finished without problems, it will be set to 1 otherwise. + + rr_upload_begin?name=xxx + Indicates that we wish to upload the specified file. xxx is the filename relative + to the root of the SD card. The directory component of the filename must already + exist. Returns variables ubuff (= max upload data we can accept in the next message) + and err (= 0 if the file was created successfully, nonzero if there was an error). + + rr_upload_data?data=xxx + Provides a data block for the file upload. Returns the samwe variables as rr_upload_begin, + except that err is only zero if the file was successfully created and there has not been + a file write error yet. This response is returned before attempting to write this data block. + + rr_upload_end + Indicates that we have finished sending upload data. The server closes the file and reports + the overall status in err. It may also return ubuff again. + + rr_upload_cancel + Indicates that the user wishes to cancel the current upload. Returns err and ubuff. + + rr_delete?name=xxx + Delete file xxx. Returns err (zero if successful). + + rr_mkdir?dir=xxx + Create a new directory xxx. Return err (zero if successful). + + rr_move?old=xxx&new=yyy + Rename an old file xxx to yyy. May also be used to move a file to another directory. + + ****************************************************************************************************/ + +#include "RepRapFirmware.h" + +const char* overflowResponse = "overflow"; +const char* badEscapeResponse = "bad escape"; + +// Constructor and initialisation +Webserver::Webserver(Platform* p, Network *n) : state(doingFilename), platform(p), network(n), numSessions(0), clientsServed(0) +{ + gcodeReadIndex = gcodeWriteIndex = 0; + gcodeReply = new OutputStack(); + processingDeferredRequest = false; + seq = 0; + webserverActive = false; +} + +void Webserver::Init() +{ + // initialise the webserver class + longWait = platform->Time(); + webserverActive = true; + numSessions = clientsServed = 0; + uploadIp = 0; + + // initialise all protocol handlers + ResetState(); +} + +// Deal with input/output from/to the client (if any) +void Webserver::Spin() +{ + if (webserverActive) + { + uint32_t ip; + size_t length; + uint32_t fragment; + const char* request = network->GetRequest(ip, length, fragment); + if (request != nullptr) + { + if (reprap.Debug(moduleWebserver)) + { + platform->MessageF(HOST_MESSAGE, "Request: %s fragment %u\n", request, fragment); + } + + uint32_t fragNum = fragment & ~lastFragmentFlag; + if (fragNum == 0) + { + HttpSession *session = StartSession(ip); + if (session == nullptr) + { + network->SendReply(ip, 400, "Too many sessions"); + } + else + { + // First fragment, so parse the request + ResetState(); + const char *error = nullptr; + bool finished = false; + while (!finished && length != 0) + { + finished = CharFromClient(*request++, error); + --length; + } + + if (!finished) + { + error = "Incomplete command"; + } + + if (error != nullptr) + { + network->SendReply(ip, 400, error); + } + else + { + ProcessFirstFragment(*session, clientMessage, (fragment & lastFragmentFlag) != 0); + } + } + } + else + { + HttpSession *session = FindSession(ip); + if (session != nullptr) + { + ProcessUploadFragment(*session, request, length, fragment); + } + else + { + // Discard the message + network->DiscardMessage(); + platform->MessageF(DEBUG_MESSAGE, "session not found, fragment=%u\n", fragment); + } + } + } + } + CheckSessions(); + platform->ClassReport(longWait); +} + +// This is called to process a file upload request. +void Webserver::StartUpload(HttpSession& session, const char* fileName, uint32_t fileLength) +{ + CancelUpload(session); + if (uploadIp != 0) + { + session.uploadState = uploadBusy; // upload buffer already in use + } + else + { + FileStore *file = platform->GetFileStore(FS_PREFIX, fileName, true); + if (file == nullptr) + { + session.uploadState = cantCreate; + } + else + { + session.fileBeingUploaded.Set(file); + session.postLength = fileLength; + session.bytesWritten = 0; + session.nextFragment = 1; + session.uploadState = uploading; + session.bufNumber = 0; + session.bufPointer = 0; + uploadIp = session.ip; + } + } +} + +// This is called when we have received the last upload packet +void Webserver::FinishUpload(HttpSession& session) +{ + bool b = session.fileBeingUploaded.Close(); + + if (session.uploadState == uploading) + { + if (!b) + { + session.uploadState = cantClose; + } + else if (session.bytesWritten != session.postLength) + { + session.uploadState = wrongLength; + } + } + + if (session.uploadState != uploading) + { + platform->MessageF(HOST_MESSAGE, "Error: Upload finished in state %d\n", (int)session.uploadState); + } + + network->SendReply(session.ip, 200 | rcJson, (session.uploadState == uploading) ? "{\"err\":0}" : "{\"err\":1}"); + session.uploadState = notUploading; +} + +void Webserver::CancelUpload(HttpSession& session) +{ + if (session.fileBeingUploaded.IsLive()) + { + session.fileBeingUploaded.Close(); + //TODO delete the file as well + } + if (uploadIp == session.ip) + { + uploadIp = 0; + } +} + +void Webserver::ProcessUploadFragment(HttpSession& session, const char* request, size_t length, uint32_t fragment) +{ + if (session.uploadState == uploading) + { + //platform->MessageF(HOST_MESSAGE, "writing fragment=%u\n", fragment); + uint32_t frag = fragment & ~lastFragmentFlag; + if (frag != session.nextFragment) + { + platform->MessageF(HOST_MESSAGE, "expecting fragment %u received %u\n", session.nextFragment, frag); + session.uploadState = wrongFragment; + } + } + + if (session.uploadState == uploading) + { + size_t bytesToCopy = uploadBufLength - session.bufPointer; + if (bytesToCopy > length) + { + bytesToCopy = length; + } + memcpy(reinterpret_cast<uint8_t*>(uploadBuffers[session.bufNumber]) + session.bufPointer, request, bytesToCopy); + session.bufPointer += bytesToCopy; + if (session.bufPointer == uploadBufLength) + { + // Switch to the other buffer. We know the rest of the data will fit because the buffer size is bigger than the SPI packet size. + session.bufNumber ^= 1; + if (bytesToCopy < length) + { + memcpy(uploadBuffers[session.bufNumber], request + bytesToCopy, length - bytesToCopy); + } + session.bufPointer = length - bytesToCopy; + + // Free up the SPI buffer before we start the file write + if ((fragment & lastFragmentFlag) == 0) + { + network->DiscardMessage(); + } + + if (session.fileBeingUploaded.Write(reinterpret_cast<const char*>(uploadBuffers[session.bufNumber ^ 1]), uploadBufLength)) + { + session.bytesWritten += uploadBufLength; + } + else + { + session.uploadState = cantWrite; + } + } + else if ((fragment & lastFragmentFlag) == 0) + { + network->DiscardMessage(); // free up the SPI buffer + } + } + else if ((fragment & lastFragmentFlag) == 0) + { + network->DiscardMessage(); // free up the SPI buffer + } + ++session.nextFragment; + + if ((fragment & lastFragmentFlag) != 0) + { + if (session.bufPointer != 0 && session.uploadState == uploading) + { + if (session.fileBeingUploaded.Write(reinterpret_cast<const char*>(uploadBuffers[session.bufNumber]), session.bufPointer)) + { + session.bytesWritten += session.bufPointer; + } + else + { + session.uploadState = cantWrite; + } + + } + FinishUpload(session); + } +} + +// Start a new session for this requester. Return nullptr if on more sessions available. +Webserver::HttpSession *Webserver::StartSession(uint32_t ip) +{ + HttpSession *s = FindSession(ip); + if (s != nullptr) + { + // Abandon the existing session for this requester and start a new one + s->nextFragment = 0; + s->fileBeingUploaded.Close(); // TODO delete any partially-uploaded file + return s; + } + + // Find an empty session + if (numSessions < maxHttpSessions) + { + s = &sessions[numSessions]; + s->ip = ip; + s->isAuthenticated = false; + s->nextFragment = 0; + s->fileBeingUploaded.Close(); // make sure no file is open + s->lastQueryTime = platform->Time(); + ++numSessions; + return s; + } + return nullptr; +} + +// Find an existing session for this requester, returning nullptr if there isn't one +Webserver::HttpSession *Webserver::FindSession(uint32_t ip) +{ + for (size_t i = 0; i < numSessions; ++i) + { + HttpSession *s = &sessions[i]; + if (s->ip == ip) + { + s->lastQueryTime = platform->Time(); + return s; + } + } + + return nullptr; +} + +// Delete a session +void Webserver::DeleteSession(uint32_t ip) +{ + for (size_t i = 0; i < numSessions; ++i) + { + HttpSession *s = &sessions[i]; + if (s->ip == ip) + { + s->fileBeingUploaded.Close(); // TODO delete it as well + for (size_t j = i + 1; j < numSessions; ++j) + { + memcpy(&sessions[j - 1], &sessions[j], sizeof(HttpSession)); + } + --numSessions; + break; + } + } + if (uploadIp == ip) + { + uploadIp = 0; // free the upload buffer + } +} + +void Webserver::Exit() +{ + platform->Message(GENERIC_MESSAGE, "Webserver class exited.\n"); + webserverActive = false; +} + +void Webserver::ResetState() +{ + clientPointer = 0; + state = doingFilename; + numQualKeys = 0; + processingDeferredRequest = false; +} + +void Webserver::Diagnostics(MessageType mtype) +{ + platform->Message(mtype, "Webserver Diagnostics:\n"); + platform->MessageF(mtype, "HTTP sessions: %d of %d\n", numSessions, maxHttpSessions); +} + +bool Webserver::GCodeAvailable(const WebSource source) const +{ + switch (source) + { + case WebSource::HTTP: + return gcodeReadIndex != gcodeWriteIndex; + + case WebSource::Telnet: + // Telnet not supported + return false; + } + + return false; +} + +char Webserver::ReadGCode(const WebSource source) +{ + switch (source) + { + case WebSource::HTTP: + if (gcodeReadIndex != gcodeWriteIndex) + { + char c = gcodeBuffer[gcodeReadIndex]; + gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength; + return c; + } + break; + + case WebSource::Telnet: + // Telnet not supported + return 0; + } + + return 0; +} + +void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) +{ + switch (source) + { + case WebSource::HTTP: +#if 0 + OutputBuffer::ReleaseAll(reply); +#else + if (reply != nullptr) + { + if (numSessions > 0) + { + // FIXME: This might cause G-code responses to be sent twice to fast HTTP clients, but + // I (chrishamm) cannot think of a nicer way to deal with slow clients at the moment... + gcodeReply->Push(reply); + clientsServed = 0; + seq++; + } + else + { + // Don't use buffers that may never get released... + OutputBuffer::ReleaseAll(reply); + } + } +#endif + break; + + case WebSource::Telnet: + // Telnet not supported + default: + OutputBuffer::ReleaseAll(reply); + break; + } +} + +void Webserver::HandleGCodeReply(const WebSource source, const char *reply) +{ + switch (source) + { + case WebSource::HTTP: +#if 0 +#else + if (numSessions > 0) + { + OutputBuffer *buffer = gcodeReply->GetLastItem(); + if (buffer == nullptr || buffer->IsReferenced()) + { + if (!OutputBuffer::Allocate(buffer)) + { + // No more space available, stop here + return; + } + gcodeReply->Push(buffer); + } + + buffer->cat(reply); + seq++; + } +#endif + break; + + case WebSource::Telnet: + default: + break; + } +} + +//---------------------------------------------------------------------------------------------------- + +// Process the first fragment of input from the client. +// Return true if the session should be kept open. +bool Webserver::ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment) +{ + // Get the first two key/value pairs + const char* key1 = (numQualKeys >= 1) ? qualifiers[0].key : ""; + const char* value1 = (numQualKeys >= 1) ? qualifiers[0].value : ""; + const char* key2 = (numQualKeys >= 1) ? qualifiers[1].key : ""; + const char* value2 = (numQualKeys >= 1) ? qualifiers[1].value : ""; + + // Process connect messages first + if (StringEquals(command, "connect") && StringEquals(key1, "password")) + { + const char *response; + if (session.isAuthenticated) + { + // This IP is already authenticated, no need to check the password again + response = "{\"err\":0}"; + } + else if (reprap.CheckPassword(value1)) + { + session.isAuthenticated = true; + response = "{\"err\":0}"; + } + else + { + // Wrong password + response = "{\"err\":1}"; + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + if (StringEquals(command, "disconnect")) + { + network->SendReply(session.ip, 200 | rcJson, "{\"err\":0}"); + DeleteSession(session.ip); + return false; + } + + // Try to authorise the user automatically to retain compatibility with the old web interface + if (!session.isAuthenticated && reprap.NoPasswordSet()) + { + session.isAuthenticated = true; + } + + // If the client is still not authenticated, stop here + if (!session.isAuthenticated) + { + network->SendReply(session.ip, 500, "Not authorized"); + return false; + } + + if (StringEquals(command, "reply")) + { + SendGCodeReply(session); + return false; + } + + // rr_configfile sends the config as plain text well + if (StringEquals(command, "configfile")) + { + SendConfigFile(session); + return false; + } + + if (StringEquals(command, "upload")) + { + if (StringEquals(key1, "name") && StringEquals(key2, "length")) + { + // Deal with file upload request + uint32_t fileLength = atol(value2); + StartUpload(session, value1, fileLength); + if (session.uploadState == uploading) + { + if (isOnlyFragment) + { + FinishUpload(session); + return false; + } + else + { + network->DiscardMessage(); // no reply needed yet + return true; // expecting more data + } + } + } + + network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); // TODO return the cause of the error + return false; + } + + if (StringEquals(command, "move")) + { + const char* response = "{\"err\":1}"; // assume failure + if (StringEquals(key1, "old") && StringEquals(key2, "new")) + { + bool success = platform->GetMassStorage()->Rename(value1, value2); + if (success) + { + response = "{\"err\":0}"; + } + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + if (StringEquals(command, "mkdir")) + { + const char* response = "{\"err\":1}"; // assume failure + if (StringEquals(key1, "dir")) + { + bool ok = (platform->GetMassStorage()->MakeDirectory(value1)); + if (ok) + { + response = "{\"err\":0}"; + } + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + if (StringEquals(command, "delete")) + { + const char* response = "{\"err\":1}"; // assume failure + if (StringEquals(key1, "name")) + { + bool ok = platform->GetMassStorage()->Delete("0:/", value1); + if (ok) + { + response = "{\"err\":0}"; + } + } + network->SendReply(session.ip, 200 | rcJson, response); + return false; + } + + // The remaining commands use an OutputBuffer for the response + OutputBuffer *response = nullptr; + if (StringEquals(command, "status")) + { + int type = 0; + if (StringEquals(key1, "type")) + { + // New-style JSON status responses + type = atoi(value1); + if (type < 1 || type > 3) + { + type = 1; + } + + response = reprap.GetStatusResponse(type, ResponseSource::HTTP); + } + else + { + response = reprap.GetLegacyStatusResponse(1, 0); + } + } + else if (StringEquals(command, "gcode")) + { + if (StringEquals(key1, "gcode")) + { + LoadGcodeBuffer(value1); + if (OutputBuffer::Allocate(response)) + { + response->printf("{\"buff\":%u}", GetGCodeBufferSpace()); + } + } + else + { + network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); + return false; + } + } + else if (StringEquals(command, "files")) + { + const char* dir = (StringEquals(key1, "dir")) ? value1 : platform->GetGCodeDir(); + bool flagDirs = false; + if (numQualKeys >= 2) + { + if (StringEquals(qualifiers[1].key, "flagDirs")) + { + flagDirs = StringEquals(qualifiers[1].value, "1"); + } + } + response = reprap.GetFilesResponse(dir, flagDirs); + } + else if (StringEquals(command, "fileinfo")) + { + if (reprap.GetPrintMonitor()->GetFileInfoResponse(StringEquals(key1, "name") ? value1 : nullptr, response)) + { + processingDeferredRequest = false; + } + else + { + processingDeferredRequest = true; + } + } + else if (StringEquals(command, "config")) + { + response = reprap.GetConfigResponse(); + } + else + { + platform->MessageF(HOST_MESSAGE, "KnockOut request: %s not recognised\n", command); + network->SendReply(session.ip, 400, "Unknown rr_ command"); + return false; + } + + if (response != nullptr) + { + network->SendReply(session.ip, 200 | rcJson, response); + } + else if (!processingDeferredRequest) + { + network->SendReply(session.ip, 500, "No buffer available"); + } + return processingDeferredRequest; +} + +void Webserver::SendGCodeReply(HttpSession& session) +{ + if (gcodeReply->IsEmpty()) + { + network->SendReply(session.ip, 200, ""); + } + else + { + clientsServed++; + if (clientsServed < numSessions) + { + gcodeReply->IncreaseReferences(1); + network->SendReply(session.ip, 200, gcodeReply->GetFirstItem()); + } + else + { + network->SendReply(session.ip, 200, gcodeReply->Pop()); + } + + if (reprap.Debug(moduleWebserver)) + { + platform->MessageF(HOST_MESSAGE, "Serving client %d of %d\n", clientsServed, numSessions); + } + } +} + +void Webserver::SendConfigFile(HttpSession& session) +{ + FileStore *configFile = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false); + if (configFile == nullptr) + { + network->SendReply(session.ip, 400, "File config.g not found"); + } + else + { + // Send an initial message containing the response code and the file size (needed for the Content-length field) + network->SendReply(session.ip, 200, configFile); // tell the network layer to send the file + } +} + +// Process a character from the client +// Rewritten as a state machine by dc42 to increase capability and speed, and reduce RAM requirement. +// On entry: +// There is space for at least 1 character in clientMessage. +// On return: +// If we return false: +// We want more characters. There is space for at least 1 character in clientMessage. +// If we return true: +// We have finished processing the message. No more characters may be read from this message. +// Whenever this calls ProcessMessage: +// The first line has been split up into words. Variables numCommandWords and commandWords give the number of words we found +// and the pointers to each word. The second word is treated specially. It is assumed to be a filename followed by an optional +// qualifier comprising key/value pairs. Both may include %xx escapes, and the qualifier may include + to mean space. We store +// a pointer to the filename without qualifier in commandWords[1]. We store the qualifier key/value pointers in array 'qualifiers' +// and the number of them in numQualKeys. +// The remaining lines have been parsed as header name/value pairs. Pointers to them are stored in array 'headers' and the number +// of them in numHeaders. +// If one of our arrays is about to overflow, or the message is not in a format we expect, then we call RejectMessage with an +// appropriate error code and string. +bool Webserver::CharFromClient(char c, const char* &error) +{ + switch(state) + { + case doingFilename: + switch(c) + { + case '\0': + case ' ': + case '\t': + case '\n': + case '\r': + clientMessage[clientPointer++] = 0; + numQualKeys = 0; + error = nullptr; + return true; + case '?': + clientMessage[clientPointer++] = 0; + numQualKeys = 0; + qualifiers[0].key = clientMessage + clientPointer; + state = doingQualifierKey; + break; + case '%': + state = doingFilenameEsc1; + break; + default: + clientMessage[clientPointer++] = c; + break; + } + break; + + case doingQualifierKey: + switch(c) + { + case '=': + clientMessage[clientPointer++] = 0; + qualifiers[numQualKeys].value = clientMessage + clientPointer; + ++numQualKeys; + state = doingQualifierValue; + break; + case '\n': // key with no value + case ' ': + case '\t': + case '\r': + case '%': // none of our keys needs escaping, so treat an escape within a key as an error + case '&': // key with no value + error = "bad qualifier key"; + return true; + default: + clientMessage[clientPointer++] = c; + break; + } + break; + + case doingQualifierValue: + switch(c) + { + case '\0': + case '\n': + case ' ': + case '\t': + case '\r': + clientMessage[clientPointer++] = 0; + qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null + error = nullptr; + return true; + case '%': + state = doingQualifierValueEsc1; + break; + case '&': + // Another variable is coming + clientMessage[clientPointer++] = 0; + qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null + if (numQualKeys < maxQualKeys) + { + state = doingQualifierKey; + } + else + { + error = "too many keys in qualifier"; + return true; + } + break; + case '+': + clientMessage[clientPointer++] = ' '; + break; + default: + clientMessage[clientPointer++] = c; + break; + } + break; + + case doingFilenameEsc1: + case doingQualifierValueEsc1: + if (c >= '0' && c <= '9') + { + decodeChar = (c - '0') << 4; + state = (HttpState)(state + 1); + } + else if (c >= 'A' && c <= 'F') + { + decodeChar = (c - ('A' - 10)) << 4; + state = (HttpState)(state + 1); + } + else + { + error = badEscapeResponse; + return true; + } + break; + + case doingFilenameEsc2: + case doingQualifierValueEsc2: + if (c >= '0' && c <= '9') + { + clientMessage[clientPointer++] = decodeChar | (c - '0'); + state = (HttpState)(state - 2); + } + else if (c >= 'A' && c <= 'F') + { + clientMessage[clientPointer++] = decodeChar | (c - ('A' - 10)); + state = (HttpState)(state - 2); + } + else + { + error = badEscapeResponse; + return true; + } + break; + } + + if (clientPointer == ARRAY_SIZE(clientMessage)) + { + error = overflowResponse; + return true; + } + return false; +} + +void Webserver::CheckSessions() +{ + // Check if any HTTP session can be purged + const float time = platform->Time(); + for (size_t i = 0; i < numSessions; ++i) + { + if ((time - sessions[i].lastQueryTime) > httpSessionTimeout) + { + DeleteSession(sessions[i].ip); + clientsServed++; // assume the disconnected client hasn't fetched the G-Code reply yet + } + } + + // If we cannot send the G-Code reply to anyone, we may free up some run-time space by dumping it + if (numSessions == 0 || clientsServed >= numSessions) + { + while (!gcodeReply->IsEmpty()) + { + OutputBuffer::ReleaseAll(gcodeReply->Pop()); + } + clientsServed = 0; + } +} + +// Process a received string of gcodes +void Webserver::LoadGcodeBuffer(const char* gc) +{ + char gcodeTempBuf[GCODE_LENGTH]; + uint16_t gtp = 0; + bool inComment = false; + for (;;) + { + char c = *gc++; + if (c == 0) + { + gcodeTempBuf[gtp] = 0; + ProcessGcode(gcodeTempBuf); + return; + } + + if (c == '\n') + { + gcodeTempBuf[gtp] = 0; + ProcessGcode(gcodeTempBuf); + gtp = 0; + inComment = false; + } + else + { + if (c == ';') + { + inComment = true; + } + + if (gtp == ARRAY_UPB(gcodeTempBuf)) + { + // gcode is too long, we haven't room for another character and a null + if (c != ' ' && !inComment) + { + platform->Message(HOST_MESSAGE, "Error: GCode local buffer overflow in HTTP webserver.\n"); + return; + } + // else we're either in a comment or the current character is a space. + // If we're in a comment, we'll silently truncate it. + // If the current character is a space, we'll wait until we see a non-comment character before reporting an error, + // in case the next character is end-of-line or the start of a comment. + } + else + { + gcodeTempBuf[gtp++] = c; + } + } + } +} + +// Process a null-terminated gcode +// We intercept one M Codes so we can deal with emergencies. That +// way things don't get out of sync, and - as a file name can contain +// a valid G code (!) - confusion is avoided. +void Webserver::ProcessGcode(const char* gc) +{ + if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop + { + reprap.EmergencyStop(); + gcodeReadIndex = gcodeWriteIndex; // clear the buffer + reprap.GetGCodes()->Reset(); + } + else + { + StoreGcodeData(gc, strlen(gc) + 1); + } +} + +// Process a received string of gcodes +void Webserver::StoreGcodeData(const char* data, uint16_t len) +{ + if (len > GetGCodeBufferSpace()) + { + platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in HTTP Webserver!\n"); + } + else + { + uint16_t remaining = gcodeBufferLength - gcodeWriteIndex; + if (len <= remaining) + { + memcpy(gcodeBuffer + gcodeWriteIndex, data, len); + } + else + { + memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining); + memcpy(gcodeBuffer, data + remaining, len - remaining); + } + gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength; + } +} + +// End diff --git a/src/DuetNG/Webserver.h b/src/DuetNG/Webserver.h new file mode 100644 index 00000000..ffef6d6f --- /dev/null +++ b/src/DuetNG/Webserver.h @@ -0,0 +1,171 @@ +/**************************************************************************************************** + +RepRapFirmware - Webserver + +This class serves a single-page web applications to the attached network. This page forms the user's +interface with the RepRap machine. This software interprests returned values from the page and uses it +to Generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like +temperature and uses those to construct the web page. + +The page itself - reprap.htm - uses Knockout.js and Jquery.js. See: + +http://knockoutjs.com/ + +http://jquery.com/ + +----------------------------------------------------------------------------------------------------- + +Version 0.2 + +10 May 2013 + +Adrian Bowyer +RepRap Professional Ltd +http://reprappro.com + +Licence: GPL + +****************************************************************************************************/ + +#ifndef WEBSERVER_H +#define WEBSERVER_H + + +// List of protocols that can execute G-Codes +enum class WebSource +{ + HTTP, + Telnet +}; + +const uint16_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2 +const uint16_t webMessageLength = 2000; // maximum length of the web message we accept after decoding +const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier +const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions +const float httpSessionTimeout = 20.0; // HTTP session timeout in seconds + +class Webserver +{ +public: + + friend class Platform; + + Webserver(Platform* p, Network *n); + void Init(); + void Spin(); + void Exit(); + void Diagnostics(MessageType mtype); + + bool GCodeAvailable(const WebSource source) const; + char ReadGCode(const WebSource source); + void HandleGCodeReply(const WebSource source, OutputBuffer *reply); + void HandleGCodeReply(const WebSource source, const char *reply); + uint32_t GetReplySeq() const { return seq; } + // Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version) + uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; } + +private: + static const uint32_t lastFragmentFlag = 0x80000000; + static const size_t uploadBufLength = 8192; + + enum UploadState + { + uploading = 0, + wrongFragment, + cantCreate, + cantWrite, + wrongLength, + cantClose, + notUploading, + uploadBusy + }; + + // HTTP sessions + struct HttpSession + { + uint32_t ip; + uint32_t nextFragment; + float lastQueryTime; + FileData fileBeingUploaded; + uint32_t postLength; + uint32_t bytesWritten; + UploadState uploadState; + size_t bufNumber; + size_t bufPointer; + bool isAuthenticated; + }; + + void ResetState(); + void CheckSessions(); + + bool CharFromClient(char c, const char* &error); + bool ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment); + void ProcessUploadFragment(HttpSession& session, const char* request, size_t length, uint32_t fragment); + void StartUpload(HttpSession& session, const char* fileName, uint32_t fileLength); + void FinishUpload(HttpSession& session); + void CancelUpload(HttpSession& session); + bool GetJsonResponse(uint32_t remoteIp, const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen); + void SendConfigFile(HttpSession& session); + + // Deal with incoming G-Codes + char gcodeBuffer[gcodeBufferLength]; + uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer + uint32_t seq; // sequence number for G-Code replies + + void LoadGcodeBuffer(const char* gc); + void ProcessGcode(const char* gc); + void StoreGcodeData(const char* data, uint16_t len); + void SendGCodeReply(HttpSession& session); + uint16_t GetGCodeBufferSpace() const; + + HttpSession *StartSession(uint32_t ip); // start a new session for this requester + HttpSession *FindSession(uint32_t ip); // find an existing session for this requester + void DeleteSession(uint32_t ip); // delete a session + + // Response from GCodes class + OutputStack *gcodeReply; + + enum HttpState + { + doingFilename, // receiving the filename (second word in the command line) + doingFilenameEsc1, // received '%' in the filename (e.g. we are being asked for a filename with spaces in it) + doingFilenameEsc2, // received '%' and one hex digit in the filename + doingQualifierKey, // receiving a key name in the HTTP request + doingQualifierValue, // receiving a key value in the HTTP request + doingQualifierValueEsc1, // received '%' in the qualifier + doingQualifierValueEsc2, // received '%' and one hex digit in the qualifier + }; + HttpState state; + + struct KeyValueIndices + { + const char* key; + const char* value; + }; + + uint32_t uploadBuffers[2][uploadBufLength/4]; // two 8K buffers for uploading files + uint32_t uploadIp; // session that owns the upload buffer + Platform *platform; + Network *network; + float longWait; + bool webserverActive; + + char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers + size_t clientPointer; // current index into clientMessage + char decodeChar; + + bool processingDeferredRequest; // it's no good idea to parse 128kB of text in one go... + + KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls + size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys + + HttpSession sessions[maxHttpSessions]; + size_t numSessions, clientsServed; +}; + +inline uint16_t Webserver::GetGCodeBufferSpace() const +{ + return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; +} + +#endif diff --git a/src/DuetNG/WifiFirmwareUploader.cpp b/src/DuetNG/WifiFirmwareUploader.cpp new file mode 100644 index 00000000..8bc740d8 --- /dev/null +++ b/src/DuetNG/WifiFirmwareUploader.cpp @@ -0,0 +1,743 @@ +/* + * EspFirmwareUpload.cpp + * + * Created on: 15 Apr 2016 + * Author: David + */ + +#include "WifiFirmwareUploader.h" +#include "Core.h" +#include "RepRapFirmware.h" + +// ESP8266 command codes +const uint8_t ESP_FLASH_BEGIN = 0x02; +const uint8_t ESP_FLASH_DATA = 0x03; +const uint8_t ESP_FLASH_END = 0x04; +const uint8_t ESP_MEM_BEGIN = 0x05; +const uint8_t ESP_MEM_END = 0x06; +const uint8_t ESP_MEM_DATA = 0x07; +const uint8_t ESP_SYNC = 0x08; +const uint8_t ESP_WRITE_REG = 0x09; +const uint8_t ESP_READ_REG = 0x0a; + +// MAC address storage locations +const uint32_t ESP_OTP_MAC0 = 0x3ff00050; +const uint32_t ESP_OTP_MAC1 = 0x3ff00054; +const uint32_t ESP_OTP_MAC2 = 0x3ff00058; +const uint32_t ESP_OTP_MAC3 = 0x3ff0005c; + +const size_t EspFlashBlockSize = 0x0400; // 1K byte blocks + +const uint8_t ESP_IMAGE_MAGIC = 0xe9; +const uint8_t ESP_CHECKSUM_MAGIC = 0xef; + +const uint32_t ESP_ERASE_CHIP_ADDR = 0x40004984; // &SPIEraseChip +const uint32_t ESP_SEND_PACKET_ADDR = 0x40003c80; // &send_packet +const uint32_t ESP_SPI_READ_ADDR = 0x40004b1c; // &SPIRead +const uint32_t ESP_UNKNOWN_ADDR = 0x40001121; // not used +const uint32_t ESP_USER_DATA_RAM_ADDR = 0x3ffe8000; // &user data ram +const uint32_t ESP_IRAM_ADDR = 0x40100000; // instruction RAM +const uint32_t ESP_FLASH_ADDR = 0x40200000; // address of start of Flash +const uint32_t ESP_FLASH_READ_STUB_BEGIN = IRAM_ADDR + 0x18; + +// Messages corresponding to result codes, should make sense when followed by " error" +const char *resultMessages[] = +{ + "no", + "timeout", + "comm write", + "connect", + "bad reply", + "file read", + "empty file", + "response header", + "slip frame", + "slip state", + "slip data" +}; + +// A note on baud rates. +// The ESP8266 supports 921600, 460800, 230400, 115200, 74880 and some lower baud rates. +// 921600b is not reliable because even though it sometimes succeeds in connecting, we get a bad response during uploading after a few blocks. +// Probably our UART ISR cannot receive bytes fast enough, perhaps because of the latency of the system tick ISR. +// 460800b doesn't always manage to connect, but if it does then uploading appears to be reliable. +// 230400b always manages to connect. +static const uint32_t uploadBaudRates[] = { 460800, 230400, 115200, 74880 }; + +WifiFirmwareUploader::WifiFirmwareUploader(UARTClass& port) + : uploadPort(port), uploadFile(nullptr), state(UploadState::idle) +{ +} + +bool WifiFirmwareUploader::IsReady() const +{ + return state == UploadState::idle; +} + +void WifiFirmwareUploader::MessageF(const char *fmt, ...) +{ + va_list vargs; + va_start(vargs, fmt); + reprap.GetPlatform()->MessageF(FIRMWARE_UPDATE_MESSAGE, fmt, vargs); + va_end(vargs); +} + +void WifiFirmwareUploader::flushInput() +{ + while (uploadPort.available() != 0) + { + (void)uploadPort.read(); + } +} + +// Extract 1-4 bytes of a value in little-endian order from a buffer beginning at a specified offset +uint32_t WifiFirmwareUploader::getData(unsigned byteCnt, const uint8_t *buf, int ofst) +{ + uint32_t val = 0; + + if (buf && byteCnt) + { + unsigned int shiftCnt = 0; + if (byteCnt > 4) + byteCnt = 4; + do + { + val |= (uint32_t)buf[ofst++] << shiftCnt; + shiftCnt += 8; + } while (--byteCnt); + } + return(val); +} + +// Put 1-4 bytes of a value in little-endian order into a buffer beginning at a specified offset. +void WifiFirmwareUploader::putData(uint32_t val, unsigned byteCnt, uint8_t *buf, int ofst) +{ + if (buf && byteCnt) + { + if (byteCnt > 4) + { + byteCnt = 4; + } + do + { + buf[ofst++] = (uint8_t)(val & 0xff); + val >>= 8; + } while (--byteCnt); + } +} + +// Read a byte optionally performing SLIP decoding. The return values are: +// +// 2 - an escaped byte was read successfully +// 1 - a non-escaped byte was read successfully +// 0 - no data was available +// -1 - the value 0xc0 was encountered (shouldn't happen) +// -2 - a SLIP escape byte was found but the following byte wasn't available +// -3 - a SLIP escape byte was followed by an invalid byte +int WifiFirmwareUploader::ReadByte(uint8_t& data, bool slipDecode) +{ + if (uploadPort.available() == 0) + { + return(0); + } + + // at least one byte is available + data = uploadPort.read(); + if (!slipDecode) + { + return(1); + } + + if (data == 0xc0) + { + // this shouldn't happen + return(-1); + } + + // if not the SLIP escape, we're done + if (data != 0xdb) + { + return(1); + } + + // SLIP escape, check availability of subsequent byte + if (uploadPort.available() == 0) + { + return(-2); + } + + // process the escaped byte + data = uploadPort.read(); + if (data == 0xdc) + { + data = 0xc0; + return(2); + } + + if (data == 0xdd) + { + data = 0xdb; + return(2); + } + // invalid + return(-3); +} + +// When we write a sync packet, there must be no gaps between most of the characters. +// So use this function, which does a block write to the UART buffer in the latest CoreNG. +void WifiFirmwareUploader::writePacketRaw(const uint8_t *buf, size_t len) +{ + uploadPort.write(buf, len); +} + +// Write a byte to the serial port optionally SLIP encoding. Return the number of bytes actually written. +inline void WifiFirmwareUploader::WriteByteRaw(uint8_t b) +{ + uploadPort.write(b); +} + +// Write a byte to the serial port optionally SLIP encoding. Return the number of bytes actually written. +inline void WifiFirmwareUploader::WriteByteSlip(uint8_t b) +{ + if (b == 0xC0) + { + WriteByteRaw(0xDB); + WriteByteRaw(0xDC); + } + else if (b == 0xDB) + { + WriteByteRaw(0xDB); + WriteByteRaw(0xDD); + } + else + { + uploadPort.write(b); + } +} + +// Wait for a data packet to be returned. If the body of the packet is +// non-zero length, return an allocated buffer indirectly containing the +// data and return the data length. Note that if the pointer for returning +// the data buffer is NULL, the response is expected to be two bytes of zero. +// +// If an error occurs, return a negative value. Otherwise, return the number +// of bytes in the response (or zero if the response was not the standard "two bytes of zero"). +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::readPacket(uint8_t op, uint32_t *valp, size_t& bodyLen, uint32_t msTimeout) +{ + enum class PacketState + { + begin = 0, + header, + body, + end, + done + }; + + const size_t headerLength = 8; + + uint32_t startTime = millis(); + uint8_t hdr[headerLength]; + uint16_t hdrIdx = 0; + bodyLen = 0; + uint16_t bodyIdx = 0; + uint8_t respBuf[2]; + + // wait for the response + uint16_t needBytes = 1; + PacketState state = PacketState::begin; + while (state != PacketState::done) + { + uint8_t c; + EspUploadResult stat; + + if (millis() - startTime > msTimeout) + { + return(EspUploadResult::timeout); + } + + if (uploadPort.available() < needBytes) + { + // insufficient data available + // preferably, return to Spin() here + continue; + } + + // sufficient bytes have been received for the current state, process them + switch(state) + { + case PacketState::begin: // expecting frame start + case PacketState::end: // expecting frame end + c = uploadPort.read(); + if (c != 0xc0) + { + return EspUploadResult::slipFrame; + } + if (state == PacketState::begin) + { + state = PacketState::header; + needBytes = 2; + } + else + { + state = PacketState::done; + } + break; + + case PacketState::header: // reading an 8-byte header + case PacketState::body: // reading the response body + { + int rslt; + // retrieve a byte with SLIP decoding + rslt = ReadByte(c, true); + if (rslt != 1 && rslt != 2) + { + // some error occurred + stat = (rslt == 0 || rslt == -2) ? EspUploadResult::slipData : EspUploadResult::slipFrame; + return stat; + } + else if (state == PacketState::header) + { + //store the header byte + hdr[hdrIdx++] = c; + if (hdrIdx >= headerLength) + { + // get the body length, prepare a buffer for it + bodyLen = (uint16_t)getData(2, hdr, 2); + + // extract the value, if requested + if (valp != nullptr) + { + *valp = getData(4, hdr, 4); + } + + if (bodyLen != 0) + { + state = PacketState::body; + } + else + { + needBytes = 1; + state = PacketState::end; + } + } + } + else + { + // Store the response body byte, check for completion + if (bodyIdx < ARRAY_SIZE(respBuf)) + { + respBuf[bodyIdx] = c; + } + ++bodyIdx; + if (bodyIdx >= bodyLen) + { + needBytes = 1; + state = PacketState::end; + } + } + } + break; + + default: // this shouldn't happen + return EspUploadResult::slipState; + } + } + + // Extract elements from the header + const uint8_t resp = (uint8_t)getData(1, hdr, 0); + const uint8_t opRet = (uint8_t)getData(1, hdr, 1); + // Sync packets often provoke a response with a zero opcode instead of ESP_SYNC + if (resp != 0x01 || opRet != op) + { +//debugPrintf("resp %02x %02x\n", resp, opRet); + return EspUploadResult::respHeader; + } + + return EspUploadResult::success; +} + +// Send a block of data performing SLIP encoding of the content. +inline void WifiFirmwareUploader::writePacket(const uint8_t *data, size_t len) +{ + while (len != 0) + { + WriteByteSlip(*data++); + --len; + } +} + +// Send a packet to the serial port while performing SLIP framing. The packet data comprises a header and an optional data block. +// A SLIP packet begins and ends with 0xc0. The data encapsulated has the bytes +// 0xc0 and 0xdb replaced by the two-byte sequences {0xdb, 0xdc} and {0xdb, 0xdd} respectively. +void WifiFirmwareUploader::writePacket(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen) +{ + WriteByteRaw(0xc0); // send the packet start character + writePacket(hdr, hdrLen); // send the header + writePacket(data, dataLen); // send the data block + WriteByteRaw(0xc0); // send the packet end character +} + +// Send a packet to the serial port while performing SLIP framing. The packet data comprises a header and an optional data block. +// This is like writePacket except that it does a fast block write for both the header and the main data with no SLIP encoding. Used to send sync commands. +void WifiFirmwareUploader::writePacketRaw(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen) +{ + WriteByteRaw(0xc0); // send the packet start character + writePacketRaw(hdr, hdrLen); // send the header + writePacketRaw(data, dataLen); // send the data block in raw mode + WriteByteRaw(0xc0); // send the packet end character +} + +// Send a command to the attached device together with the supplied data, if any. +// The data is supplied via a list of one or more segments. +void WifiFirmwareUploader::sendCommand(uint8_t op, uint32_t checkVal, const uint8_t *data, size_t dataLen) +{ + // populate the header + uint8_t hdr[8]; + putData(0, 1, hdr, 0); + putData(op, 1, hdr, 1); + putData(dataLen, 2, hdr, 2); + putData(checkVal, 4, hdr, 4); + + // send the packet + flushInput(); + if (op == ESP_SYNC) + { + writePacketRaw(hdr, sizeof(hdr), data, dataLen); + } + else + { + writePacket(hdr, sizeof(hdr), data, dataLen); + } +} + +// Send a command to the attached device together with the supplied data, if any, and get the response +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::doCommand(uint8_t op, const uint8_t *data, size_t dataLen, uint32_t checkVal, uint32_t *valp, uint32_t msTimeout) +{ + sendCommand(op, checkVal, data, dataLen); + size_t bodyLen; + EspUploadResult stat = readPacket(op, valp, bodyLen, msTimeout); + if (stat == EspUploadResult::success && bodyLen != 2) + { + stat = EspUploadResult::badReply; + } + + return stat; +} + +// Send a synchronising packet to the serial port in an attempt to induce +// the ESP8266 to auto-baud lock on the baud rate. +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::Sync(uint16_t timeout) +{ + uint8_t buf[36]; + + // compose the data for the sync attempt + memset(buf, 0x55, sizeof(buf)); + buf[0] = 0x07; + buf[1] = 0x07; + buf[2] = 0x12; + buf[3] = 0x20; + + EspUploadResult stat = doCommand(ESP_SYNC, buf, sizeof(buf), 0, nullptr, timeout); + + // If we got a response other than sync, discard it and wait for a sync response. This happens at higher baud rates. + for (int i = 0; i < 10 && stat == EspUploadResult::respHeader; ++i) + { + size_t bodyLen; + stat = readPacket(ESP_SYNC, nullptr, bodyLen, timeout); + } + + if (stat == EspUploadResult::success) + { + // Read and discard additional replies + for (;;) + { + size_t bodyLen; + EspUploadResult rc = readPacket(ESP_SYNC, nullptr, bodyLen, defaultTimeout); + if (rc != EspUploadResult::success || bodyLen != 2) + { + break; + } + } + } +//DEBUG +// else debugPrintf("stat=%d\n", (int)stat); + return stat; +} + +// Send a command to the device to begin the Flash process. +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashBegin(uint32_t addr, uint32_t size) +{ + // determine the number of blocks represented by the size + uint32_t blkCnt; + blkCnt = (size + EspFlashBlockSize - 1) / EspFlashBlockSize; + + // ensure that the address is on a block boundary + addr &= ~(EspFlashBlockSize - 1); + + // begin the Flash process + uint8_t buf[16]; + putData(size, 4, buf, 0); + putData(blkCnt, 4, buf, 4); + putData(EspFlashBlockSize, 4, buf, 8); + putData(addr, 4, buf, 12); + + uint32_t timeout = (size != 0) ? eraseTimeout : defaultTimeout; + return doCommand(ESP_FLASH_BEGIN, buf, sizeof(buf), 0, nullptr, timeout); +} + +// Send a command to the device to terminate the Flash process +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashFinish(bool reboot) +{ + uint8_t buf[4]; + putData(reboot ? 0 : 1, 4, buf, 0); + return doCommand(ESP_FLASH_END, buf, sizeof(buf), 0, nullptr, defaultTimeout); +} + +// Compute the checksum of a block of data +uint16_t WifiFirmwareUploader::checksum(const uint8_t *data, uint16_t dataLen, uint16_t cksum) +{ + if (data != NULL) + { + while (dataLen--) + { + cksum ^= (uint16_t)*data++; + } + } + return(cksum); +} + +WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashWriteBlock(uint16_t flashParmVal, uint16_t flashParmMask) +{ + const uint32_t blkSize = EspFlashBlockSize; + + // Allocate a data buffer for the combined header and block data + const uint16_t hdrOfst = 0; + const uint16_t dataOfst = 16; + const uint16_t blkBufSize = dataOfst + blkSize; + uint8_t blkBuf[blkBufSize]; + + // Prepare the header for the block + putData(blkSize, 4, blkBuf, hdrOfst + 0); + putData(uploadBlockNumber, 4, blkBuf, hdrOfst + 4); + putData(0, 4, blkBuf, hdrOfst + 8); + putData(0, 4, blkBuf, hdrOfst + 12); + + // Get the data for the block + size_t cnt = uploadFile->Read((char *)blkBuf + dataOfst, blkSize); + if (cnt != EspFlashBlockSize) + { + if (uploadFile->Position() == fileSize) + { + // partial last block, fill the remainder + memset(blkBuf + dataOfst + cnt, 0xff, blkSize - cnt); + } + else + { + return EspUploadResult::fileRead; + } + } + + // Patch the flash parameters into the first block if it is loaded at address 0 + if (uploadBlockNumber == 0 && uploadAddress == 0 && blkBuf[dataOfst] == ESP_IMAGE_MAGIC && flashParmMask != 0) + { + // update the Flash parameters + uint32_t flashParm = getData(2, blkBuf + dataOfst + 2, 0) & ~(uint32_t)flashParmMask; + putData(flashParm | flashParmVal, 2, blkBuf + dataOfst + 2, 0); + } + + // Calculate the block checksum + uint16_t cksum = checksum(blkBuf + dataOfst, blkSize, ESP_CHECKSUM_MAGIC); + EspUploadResult stat; + for (int i = 0; i < 3; i++) + { + if ((stat = doCommand(ESP_FLASH_DATA, blkBuf, blkBufSize, cksum, nullptr, blockWriteTimeout)) == EspUploadResult::success) + { + break; + } + } + + return stat; +} + +void WifiFirmwareUploader::Spin() +{ + switch (state) + { + case UploadState::resetting: + if (connectAttemptNumber == ARRAY_SIZE(uploadBaudRates) * retriesPerBaudRate) + { + // Time to give up + Network::ResetWiFi(); + uploadResult = EspUploadResult::connect; + state = UploadState::done; + } + else + { + // Reset the serial port at the new baud rate. Also reset the ESP8266. + const uint32_t baud = uploadBaudRates[connectAttemptNumber/retriesPerBaudRate]; + if (connectAttemptNumber % retriesPerBaudRate == 0) + { + // First attempt at this baud rate + MessageF("Trying to connect at %u baud: ", baud); + } + uploadPort.begin(baud); + uploadPort.setInterruptPriority(1); // we are going to move data at seriously high speeds + Network::ResetWiFiForUpload(); + lastAttemptTime = lastResetTime = millis(); + state = UploadState::connecting; + } + break; + + case UploadState::connecting: + if (millis() - lastAttemptTime >= connectAttemptInterval && millis() - lastResetTime >= resetDelay) + { + // Attempt to establish a connection to the ESP8266. + EspUploadResult res = Sync(syncTimeout); + lastAttemptTime = millis(); + if (res == EspUploadResult::success) + { + // Successful connection +// MessageF(" success on attempt %d\n", (connectAttemptNumber % retriesPerBaudRate) + 1); + MessageF(" success\n"); + state = UploadState::erasing; + } + else + { + // This attempt failed + ++connectAttemptNumber; + if (connectAttemptNumber % retriesPerReset == 0) + { + if (connectAttemptNumber % retriesPerBaudRate == 0) + { + MessageF(" failed\n"); + } + state = UploadState::resetting; // try a reset and a lower baud rate + } + } + } + break; + + case UploadState::erasing: + if (millis() - lastAttemptTime >= blockWriteInterval) + { + const uint32_t sectorsPerBlock = 16; + const uint32_t sectorSize = 4096; + const uint32_t numSectors = (fileSize + sectorSize - 1)/sectorSize; + const uint32_t startSector = uploadAddress/sectorSize; + uint32_t headSectors = sectorsPerBlock - (startSector % sectorsPerBlock); + + if (numSectors < headSectors) + { + headSectors = numSectors; + } + const uint32_t eraseSize = (numSectors < 2 * headSectors) + ? (numSectors + 1) / 2 * sectorSize + : (numSectors - headSectors) * sectorSize; + + MessageF("Erasing %u bytes...\n", fileSize); + uploadResult = flashBegin(uploadAddress, eraseSize); + if (uploadResult == EspUploadResult::success) + { + MessageF("Uploading file...\n"); + uploadBlockNumber = 0; + uploadNextPercentToReport = percentToReportIncrement; + lastAttemptTime = millis(); + state = UploadState::uploading; + } + else + { + MessageF("Erase failed\n"); + state = UploadState::done; + } + } + break; + + case UploadState::uploading: + // The ESP needs several milliseconds to recover from one packet before it will accept another + if (millis() - lastAttemptTime >= blockWriteInterval) + { + const uint32_t blkCnt = (fileSize + EspFlashBlockSize - 1) / EspFlashBlockSize; + if (uploadBlockNumber < blkCnt) + { + uploadResult = flashWriteBlock(0, 0); + lastAttemptTime = millis(); + if (uploadResult != EspUploadResult::success) + { + MessageF("Flash block upload failed\n"); + state = UploadState::done; + } + const unsigned int percentComplete = (100 * uploadBlockNumber)/blkCnt; + ++uploadBlockNumber; + if (percentComplete >= uploadNextPercentToReport) + { + MessageF("%u%% complete\n", percentComplete); + uploadNextPercentToReport += percentToReportIncrement; + } + } + else + { + state = UploadState::done; + } + } + break; + + case UploadState::done: + uploadFile->Close(); + uploadPort.end(); // disable the port, it has a high interrupt priority + if (uploadResult == EspUploadResult::success) + { + reprap.GetPlatform()->Message(FIRMWARE_UPDATE_MESSAGE, "Upload successful\n"); + if (restartOnCompletion) + { + reprap.GetNetwork()->Start(); + } + else + { + reprap.GetNetwork()->ResetWiFi(); + } + } + else + { + reprap.GetPlatform()->MessageF(FIRMWARE_UPDATE_MESSAGE, "Error: Installation failed due to %s error\n", resultMessages[(size_t)uploadResult]); + // Not safe to restart the network + reprap.GetNetwork()->ResetWiFi(); + } + state = UploadState::idle; + break; + + default: + break; + } +} + +// Try to upload the given file at the given address +void WifiFirmwareUploader::SendUpdateFile(const char *file, const char *dir, uint32_t address) +{ + Platform *platform = reprap.GetPlatform(); + uploadFile = platform->GetFileStore(dir, file, false); + if (uploadFile == nullptr) + { + platform->MessageF(FIRMWARE_UPDATE_MESSAGE, "Failed to open file %s\n", file); + return; + } + + fileSize = uploadFile->Length(); + if (fileSize == 0) + { + uploadFile->Close(); + platform->MessageF(FIRMWARE_UPDATE_MESSAGE, "Upload file is empty %s\n", file); + return; + } + + // Stop the network + Network *network = reprap.GetNetwork(); + restartOnCompletion = network->IsEnabled(); + network->Stop(); + + // Set up the state so that subsequent calls to Spin() will attempt the upload + uploadAddress = address; + connectAttemptNumber = 0; + state = UploadState::resetting; +} + +// End diff --git a/src/DuetNG/WifiFirmwareUploader.h b/src/DuetNG/WifiFirmwareUploader.h new file mode 100644 index 00000000..33f387f3 --- /dev/null +++ b/src/DuetNG/WifiFirmwareUploader.h @@ -0,0 +1,96 @@ +/* + * EspFirmwareUpload.h + * + * Created on: 15 Apr 2016 + * Author: David + */ + +#ifndef SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ +#define SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ + +#include "Core.h" +#include "FileStore.h" + +class WifiFirmwareUploader +{ +public: + WifiFirmwareUploader(UARTClass& port); + bool IsReady() const; + void SendUpdateFile(const char *file, const char *dir, uint32_t address); + void Spin(); + + static const uint32_t FirmwareAddress = 0x00000000; + static const uint32_t WebFilesAddress = 0x00100000; + +private: + static const uint32_t defaultTimeout = 500; // default timeout in milliseconds + static const uint32_t syncTimeout = 1000; + static const unsigned int retriesPerBaudRate = 9; + static const unsigned int retriesPerReset = 3; + static const uint32_t connectAttemptInterval = 50; + static const uint32_t resetDelay = 500; + static const uint32_t blockWriteInterval = 15; // 15ms is long enough, 10ms is mostly too short + static const uint32_t blockWriteTimeout = 200; + static const uint32_t eraseTimeout = 15000; // increased from 12 to 15 seconds because Roland's board was timing out + static const unsigned int percentToReportIncrement = 5; // how often we report % complete + + // Return codes - this list must be kept in step with the corresponding messages + enum class EspUploadResult + { + success = 0, + timeout, + connect, + badReply, + fileRead, + emptyFile, + respHeader, + slipFrame, + slipState, + slipData, + }; + + enum class UploadState + { + idle, + resetting, + connecting, + erasing, + uploading, + done + }; + + void MessageF(const char *fmt, ...); + uint32_t getData(unsigned byteCnt, const uint8_t *buf, int ofst); + void putData(uint32_t val, unsigned byteCnt, uint8_t *buf, int ofst); + int ReadByte(uint8_t& data, bool slipDecode); + void WriteByteRaw(uint8_t b); + void WriteByteSlip(uint8_t b); + void flushInput(); + EspUploadResult readPacket(uint8_t op, uint32_t *valp, size_t& bodyLen, uint32_t msTimeout); + void writePacket(const uint8_t *data, size_t len); + void writePacketRaw(const uint8_t *buf, size_t len); + void writePacket(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen); + void writePacketRaw(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen); + void sendCommand(uint8_t op, uint32_t checkVal, const uint8_t *data, size_t dataLen); + EspUploadResult doCommand(uint8_t op, const uint8_t *data, size_t dataLen, uint32_t checkVal, uint32_t *valp, uint32_t msTimeout); + EspUploadResult Sync(uint16_t timeout); + EspUploadResult flashBegin(uint32_t addr, uint32_t size); + EspUploadResult flashFinish(bool reboot); + static uint16_t checksum(const uint8_t *data, uint16_t dataLen, uint16_t cksum); + EspUploadResult flashWriteBlock(uint16_t flashParmVal, uint16_t flashParmMask); + + UARTClass& uploadPort; + FileStore *uploadFile; + FilePosition fileSize; + uint32_t uploadAddress; + uint32_t uploadBlockNumber; + unsigned int uploadNextPercentToReport; + unsigned int connectAttemptNumber; + uint32_t lastAttemptTime; + uint32_t lastResetTime; + UploadState state; + EspUploadResult uploadResult; + bool restartOnCompletion; +}; + +#endif /* SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ */ diff --git a/src/ExternalDrivers.cpp b/src/ExternalDrivers.cpp index 04634fa0..0fb01a0d 100644 --- a/src/ExternalDrivers.cpp +++ b/src/ExternalDrivers.cpp @@ -5,7 +5,6 @@ * Author: David */ -//#include "Platform.h" // for typedefs uint8_t etc. #include "RepRapFirmware.h" #ifdef EXTERNAL_DRIVERS @@ -41,29 +40,45 @@ const size_t NumExternalDrivers = DRIVES - FIRST_EXTERNAL_DRIVE; // 5V_USB 5 +3.3V 3 (+3.3V) #ifdef DUET_NG -# if 1 + +const Pin DriverSelectPins[NumExternalDrivers] = {78, 41, 42, 49, 57, 87, 88, 89, 90}; + +# ifdef PROTOTYPE_1 + // Pin assignments for the first prototype, using USART0 SPI const Pin DriversMosiPin = 27; // PB1 const Pin DriversMisoPin = 26; // PB0 const Pin DriversSclkPin = 30; // PB13 # define USART_EXT_DRV USART0 # define ID_USART_EXT_DRV ID_USART0 + # else + +# include "sam/drivers/tc/tc.h" + // Pin assignments for the second prototype, using USART1 SPI +const Pin DriversClockPin = 15; // PB15/TIOA1 const Pin DriversMosiPin = 22; // PA13 const Pin DriversMisoPin = 21; // PA22 const Pin DriversSclkPin = 23; // PA23 # define USART_EXT_DRV USART1 # define ID_USART_EXT_DRV ID_USART1 +# define TMC_CLOCK_TC TC0 +# define TMC_CLOCK_CHAN 1 +# define TMC_CLOCK_ID ID_TC1 // this is channel 1 on TC0 # endif -const Pin DriverSelectPins[NumExternalDrivers] = {87, 88, 89, 90}; + #else + +// Duet 0.6 or 0.8.5 + +const Pin DriverSelectPins[NumExternalDrivers] = {37, X8, 50, 47 /*, X13*/ }; const Pin DriversMosiPin = 16; // PA13 const Pin DriversMisoPin = 17; // PA12 const Pin DriversSclkPin = 54; // PA16 -const Pin DriverSelectPins[NumExternalDrivers] = {37, X8, 50, 47 /*, X13*/ }; # define USART_EXT_DRV USART1 # define ID_USART_EXT_DRV ID_USART1 + #endif const uint32_t DriversSpiClockFrequency = 1000000; // 1MHz SPI clock for now @@ -171,6 +186,7 @@ const uint32_t defaultSgscConfReg = // Driver configuration register const uint32_t defaultDrvConfReg = TMC_REG_DRVCONF + | TMC_DRVCONF_VSENSE // use high sensitivity range | 0; // Driver control register @@ -275,25 +291,40 @@ void TmcDriverState::SetMicrostepping(uint32_t shift, bool interpolate) void TmcDriverState::SetCurrent(float current) { - // I am assuming that the current sense resistor is 0.1 ohms as on the evaluation board. - // This gives us a range of 95mA to 3.05A in 95mA steps when VSENSE is high (but max allowed RMS current is 2A), - // or 52mA to 1.65A in 52mA steps when VSENSE is low. +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + + // The current sense resistor is 0.051 ohms. + // This gives us a range of 101mA to 3.236A in 101mA steps in the high sensitivity range (VSENSE = 1) + drvConfReg |= TMC_DRVCONF_VSENSE; // this should always be set, but send it again just in case + SpiSendWord(pin, drvConfReg); + + const uint32_t iCurrent = (current > 2000.0) ? 2000 : (current < 100) ? 100 : (uint32_t)current; + const uint32_t csBits = (uint32_t)((32 * iCurrent - 1600)/3236); // formula checked by simulation on a spreadsheet + sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK; + sgcsConfReg |= TMC_SGCSCONF_CS(csBits); + SpiSendWord(pin, sgcsConfReg); + +#else + + // The current sense resistor is 0.1 ohms on the evaluation board. + // This gives us a range of 95mA to 3.05A in 95mA steps when VSENSE is low (but max allowed RMS current is 2A), + // or 52mA to 1.65A in 52mA steps when VSENSE is high. if (current > 1650.0) { - // Need VSENSE = 1, but set up the current first to avoid temporarily exceeding the 2A rating + // Need VSENSE = 0, but set up the current first to avoid temporarily exceeding the 2A rating const uint32_t iCurrent = (current > 2000.0) ? 2000 : (uint32_t)current; const uint32_t csBits = (uint32_t)((32 * iCurrent - 1500)/3050); // formula checked by simulation on a spreadsheet sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK; sgcsConfReg |= TMC_SGCSCONF_CS(csBits); SpiSendWord(pin, sgcsConfReg); - drvConfReg |= TMC_DRVCONF_VSENSE; + drvConfReg &= ~TMC_DRVCONF_VSENSE; SpiSendWord(pin, drvConfReg); } else { - // Use VSENSE = 0 - drvConfReg &= ~TMC_DRVCONF_VSENSE; + // Use VSENSE = 1 + drvConfReg |= TMC_DRVCONF_VSENSE; SpiSendWord(pin, drvConfReg); const uint32_t iCurrent = (current < 50) ? 50 : (uint32_t)current; @@ -302,6 +333,8 @@ void TmcDriverState::SetCurrent(float current) sgcsConfReg |= TMC_SGCSCONF_CS(csBits); SpiSendWord(pin, sgcsConfReg); } + + #endif } void TmcDriverState::Enable(bool en) @@ -347,9 +380,24 @@ namespace ExternalDrivers pio_configure(pin1.pPort, PIO_PERIPH_A, pin1.ulPin, PIO_DEFAULT); #endif - // Enable the clock to UART1 + // Enable the clock to the USART pmc_enable_periph_clk(ID_USART_EXT_DRV); +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + // Set up the 15MHz clock to the TMC drivers on TIOA1 + pmc_enable_periph_clk(TMC_CLOCK_ID); + ConfigurePin(GetPinDescription(DriversClockPin)); // set up TIOA1 to be an output + tc_init(TMC_CLOCK_TC, TMC_CLOCK_CHAN, + TC_CMR_TCCLKS_TIMER_CLOCK1 | // clock is MCLK/2 (fastest available) + TC_CMR_BURST_NONE | // clock is not gated + TC_CMR_WAVE | // Waveform mode + TC_CMR_WAVSEL_UP_RC | // Counter runs up and reset when equals to RC + TC_CMR_EEVT_XC0 | // Set external events from XC0 (this sets up TIOB as output) + TC_CMR_ACPC_TOGGLE); // toggle TIOA output + tc_write_rc(TMC_CLOCK_TC, TMC_CLOCK_CHAN, 2); // divisor = 2, gives us a 15MHz clock with a master clock of 120MHz. + tc_start(TMC_CLOCK_TC, TMC_CLOCK_CHAN); +#endif + // Set up the CS pins and set them all high // When this becomes the standard code, we must set up the STEP and DIR pins here too. for (size_t drive = 0; drive < NumExternalDrivers; ++drive) diff --git a/src/GCodeBuffer.cpp b/src/GCodeBuffer.cpp index 6ebedb31..c8480e27 100644 --- a/src/GCodeBuffer.cpp +++ b/src/GCodeBuffer.cpp @@ -184,7 +184,6 @@ bool GCodeBuffer::Seen(char c) } // Get a float after a G Code letter found by a call to Seen() - float GCodeBuffer::GetFValue() { if (readPointer < 0) @@ -199,7 +198,6 @@ float GCodeBuffer::GetFValue() } // Get a :-separated list of floats after a key letter - const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) { if(readPointer < 0) @@ -214,7 +212,7 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) bool inList = true; while(inList) { - if(length >= returnedLength) // Array limit has been set in here + if (length >= returnedLength) // array limit has been set in here { platform->MessageF(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode float array that is too long: %s\n", gcodeBuffer); readPointer = -1; @@ -223,12 +221,11 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) } a[length] = (float)strtod(&gcodeBuffer[readPointer + 1], 0); length++; - readPointer++; - while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)) + do { readPointer++; - } - if(gcodeBuffer[readPointer] != LIST_SEPARATOR) + } while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)); + if (gcodeBuffer[readPointer] != LIST_SEPARATOR) { inList = false; } @@ -252,10 +249,9 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength) } // Get a :-separated list of longs after a key letter - const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength) { - if(readPointer < 0) + if (readPointer < 0) { platform->Message(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode long array before a search.\n"); readPointer = -1; @@ -266,7 +262,7 @@ const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength) bool inList = true; while(inList) { - if(length >= returnedLength) // Array limit has been set in here + if (length >= returnedLength) // Array limit has been set in here { platform->MessageF(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode long array that is too long: %s\n", gcodeBuffer); readPointer = -1; @@ -275,12 +271,11 @@ const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength) } l[length] = strtol(&gcodeBuffer[readPointer + 1], 0, 0); length++; - readPointer++; - while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)) + do { readPointer++; - } - if(gcodeBuffer[readPointer] != LIST_SEPARATOR) + } while(gcodeBuffer[readPointer] != 0 && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR)); + if (gcodeBuffer[readPointer] != LIST_SEPARATOR) { inList = false; } diff --git a/src/GCodes.cpp b/src/GCodes.cpp index fb2ae899..908f8ead 100644 --- a/src/GCodes.cpp +++ b/src/GCodes.cpp @@ -56,7 +56,6 @@ void GCodes::Exit() void GCodes::Init() { Reset(); - firmwareUpdateModuleMap = 0; distanceScale = 1.0; rawExtruderTotal = 0.0; for (size_t extruder = 0; extruder < DRIVES - AXES; extruder++) @@ -122,7 +121,7 @@ void GCodes::Reset() feedRate = pausedMoveBuffer[DRIVES] = DEFAULT_FEEDRATE/minutesToSeconds; ClearMove(); - for (size_t i =0; i < MaxTriggers; ++i) + for (size_t i = 0; i < MaxTriggers; ++i) { triggers[i].Init(); } @@ -139,6 +138,7 @@ void GCodes::Reset() isPaused = false; filePos = moveBuffer.filePos = noFilePosition; lastEndstopStates = platform->GetAllEndstopStates(); + firmwareUpdateModuleMap = 0; } float GCodes::FractionOfFilePrinted() const @@ -397,7 +397,7 @@ void GCodes::Spin() if (FirmwareUpdater::IsReady()) { bool updating = false; - for (unsigned int module = 0; module < NumFirmwareUpdateModules; ++module) + for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module) { if ((firmwareUpdateModuleMap & (1 << module)) != 0) { @@ -626,7 +626,10 @@ bool GCodes::CheckTriggers() unsigned int lowestTriggerPending = MaxTriggers; for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber) { - if ((triggers[triggerNumber].rising & risen) != 0 || (triggers[triggerNumber].falling & fallen) != 0) + const Trigger& ct = triggers[triggerNumber]; + if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0) + && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting())) + ) { triggersPending |= (1 << triggerNumber); } @@ -740,7 +743,6 @@ void GCodes::Diagnostics(MessageType mtype) platform->Message(mtype, "GCodes Diagnostics:\n"); platform->MessageF(mtype, "Move available? %s\n", moveAvailable ? "yes" : "no"); platform->MessageF(mtype, "Stack pointer: %u of %u\n", stackPointer, StackSize); - fileMacroGCode->Diagnostics(mtype); httpGCode->Diagnostics(mtype); telnetGCode->Diagnostics(mtype); @@ -1642,9 +1644,10 @@ void GCodes::GetCurrentCoordinates(StringRef& s) const s.catf("E%u:%.1f ", i - AXES, liveCoordinates[i]); } - // Print the stepper motor positions as Marlin does, as an aid to debugging + // Print the axis stepper motor positions as Marlin does, as an aid to debugging. + // Don't bother with the extruder endpoints, they are zero after any non-extruding move. s.cat(" Count"); - for (size_t i = 0; i < DRIVES; ++i) + for (size_t i = 0; i < AXES; ++i) { s.catf(" %d", reprap.GetMove()->GetEndPoint(i)); } @@ -2769,7 +2772,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) bool error = false; int code = gb->GetIValue(); - if (simulationMode != 0 && (code < 20 || code > 37) && code != 82 && code != 83 && code != 111 && code != 105 && code != 122 && code != 408 && code != 999) + if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 111 && code != 105 && code != 122 && code != 408 && code != 999) { return true; // we don't yet simulate most M codes } @@ -3409,6 +3412,10 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) hh |= (1 << (unsigned int)hnum); } } + if (hh != 0) + { + platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety + } platform->SetHeatersMonitored(fanNum, hh); } @@ -3510,7 +3517,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) DoEmergencyStop(); break; - case 114: // Deprecated + case 114: GetCurrentCoordinates(reply); break; @@ -3585,11 +3592,8 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) case 117: // Display message { - const char *msg = gb->GetUnprecedentedString(); - if (msg != nullptr) - { - reprap.SetMessage(msg); - } + const char *msg = gb->GetUnprecedentedString(true); + reprap.SetMessage((msg == nullptr) ? "" : msg); } break; @@ -4112,7 +4116,11 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) { seen = true; int microsteps = gb->GetIValue(); - if (!ChangeMicrostepping(axis, microsteps, interp)) + if (ChangeMicrostepping(axis, microsteps, interp)) + { + axisIsHomed[axis] = false; + } + else { platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n", axisLetters[axis], microsteps, (interp) ? " with interpolation" : ""); @@ -5018,63 +5026,81 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) triggersPending |= (1 << triggerNumber); } } - else if (gb->Seen('S')) + else { - int sval = gb->GetIValue(); - TriggerMask triggerMask = 0; - for (size_t axis = 0; axis < AXES; ++axis) + bool seen = false; + if (gb->Seen('C')) { - if (gb->Seen(axisLetters[axis])) - { - triggerMask |= (1u << axis); - } + seen = true; + triggers[triggerNumber].condition = gb->GetIValue(); + } + else if (triggers[triggerNumber].IsUnused()) + { + triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition } - if (gb->Seen(extrudeLetter)) + if (gb->Seen('S')) { - long eStops[DRIVES - AXES]; - size_t numEntries = DRIVES - AXES; - gb->GetLongArray(eStops, numEntries); - for (size_t i = 0; i < numEntries; ++i) + seen = true; + int sval = gb->GetIValue(); + TriggerMask triggerMask = 0; + for (size_t axis = 0; axis < AXES; ++axis) { - if (eStops[i] >= 0 && (unsigned long)eStops[i] < DRIVES - AXES) + if (gb->Seen(axisLetters[axis])) { - triggerMask |= (1u << (eStops[i] + AXES)); + triggerMask |= (1u << axis); } } - } - switch(sval) - { - case -1: - if (triggerMask == 0) + if (gb->Seen(extrudeLetter)) { - triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0; + long eStops[DRIVES - AXES]; + size_t numEntries = DRIVES - AXES; + gb->GetLongArray(eStops, numEntries); + for (size_t i = 0; i < numEntries; ++i) + { + if (eStops[i] >= 0 && (unsigned long)eStops[i] < DRIVES - AXES) + { + triggerMask |= (1u << (eStops[i] + AXES)); + } + } } - else + switch(sval) { - triggers[triggerNumber].rising &= (~triggerMask); - triggers[triggerNumber].falling &= (~triggerMask); - } - break; + case -1: + if (triggerMask == 0) + { + triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0; + } + else + { + triggers[triggerNumber].rising &= (~triggerMask); + triggers[triggerNumber].falling &= (~triggerMask); + } + break; - case 0: - triggers[triggerNumber].falling |= triggerMask; - break; + case 0: + triggers[triggerNumber].falling |= triggerMask; + break; - case 1: - triggers[triggerNumber].rising |= triggerMask; - break; + case 1: + triggers[triggerNumber].rising |= triggerMask; + break; - default: - platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n"); + default: + platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n"); + } + } + if (!seen) + { + reply.printf("Trigger %u fires on a rising edge on ", triggerNumber); + ListTriggers(reply, triggers[triggerNumber].rising); + reply.cat(" or a falling edge on "); + ListTriggers(reply, triggers[triggerNumber].falling); + reply.cat(" endstop inputs"); + if (triggers[triggerNumber].condition == 1) + { + reply.cat(" when printing from SD card"); + } } - } - else - { - reply.printf("Trigger %u fires on a rising edge on ", triggerNumber); - ListTriggers(reply, triggers[triggerNumber].rising); - reply.cat(" or a falling edge on "); - ListTriggers(reply, triggers[triggerNumber].falling); - reply.cat(" endstop inputs"); } } else @@ -5301,27 +5327,21 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) break; case 997: // Perform firmware update + if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) + { + return false; + } + reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended + DisableDrives(); // all motors off + if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update? { // Find out which modules we have been asked to update - long modulesToUpdate[3]; - size_t numUpdateModules; if (gb->Seen('S')) { - numUpdateModules = ARRAY_SIZE(modulesToUpdate); + long modulesToUpdate[3]; + size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate); gb->GetLongArray(modulesToUpdate, numUpdateModules); - } - else - { - numUpdateModules = 0; - } - - if (numUpdateModules == 0) - { - firmwareUpdateModuleMap = (1 << 0); // no modules specified, so update module 0 to match old behaviour - } - else - { for (size_t i = 0; i < numUpdateModules; ++i) { long t = modulesToUpdate[i]; @@ -5331,27 +5351,37 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) firmwareUpdateModuleMap = 0; break; } - firmwareUpdateModuleMap |= (1 << t); + firmwareUpdateModuleMap |= (1 << (unsigned int)t); } } + else + { + firmwareUpdateModuleMap = (1 << 0); // no modules specified, so update module 0 to match old behaviour + } + + if (firmwareUpdateModuleMap == 0) + { + break; // nothing to update + } // Check prerequisites of all modules to be updated, if any are not met then don't update any of them #ifdef DUET_NG if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap)) { + firmwareUpdateModuleMap = 0; break; } #endif if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites()) { + firmwareUpdateModuleMap = 0; break; } } // If we get here then we have the module map, and all prerequisites are satisfied - reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware - if (!DoDwellTime(1.0)) // wait a second so all HTTP clients are notified + if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified { return false; } @@ -5370,7 +5400,6 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply) if (val != 0) { reply.printf("Checksum error on line %d", val); - //resend = true; // FIXME? } } break; diff --git a/src/GCodes.h b/src/GCodes.h index c7f3c5fd..9a89fc56 100644 --- a/src/GCodes.h +++ b/src/GCodes.h @@ -78,10 +78,17 @@ struct Trigger { TriggerMask rising; TriggerMask falling; + uint8_t condition; void Init() { rising = falling = 0; + condition = 0; + } + + bool IsUnused() const + { + return rising == 0 && falling == 0; } }; @@ -257,7 +264,6 @@ private: uint32_t auxSeq; // Sequence number for AUX devices float simulationTime; // Accumulated simulation time uint8_t simulationMode; // 0 = not simulating, 1 = simulating, >1 are simulation modes for debugging - bool isFlashing; // Is a new firmware binary going to be flashed? FilePosition filePos; // The position we got up to in the file being printed // Firmware retraction settings @@ -273,6 +279,7 @@ private: // Firmware update uint8_t firmwareUpdateModuleMap; // Bitmap of firmware modules to be updated + bool isFlashing; // Is a new firmware binary going to be flashed? }; //***************************************************************************************************** diff --git a/src/Heat.cpp b/src/Heat.cpp index f7de96c1..b4060b59 100644 --- a/src/Heat.cpp +++ b/src/Heat.cpp @@ -71,12 +71,12 @@ void Heat::Spin() void Heat::Diagnostics(MessageType mtype) { - platform->Message(mtype, "Heat Diagnostics:\n"); + platform->MessageF(mtype, "Heat Diagnostics:\nBed heater = %d, chamber heater = %d\n", bedHeater, chamberHeater); for (size_t heater=0; heater < HEATERS; heater++) { if (pids[heater]->Active()) { - platform->MessageF(mtype, "Heater %d: I-accumulator = %.1f\n", heater, pids[heater]->GetAccumulator()); + platform->MessageF(mtype, "Heater %d is on, I-accum = %.1f\n", heater, pids[heater]->GetAccumulator()); } } } @@ -231,8 +231,7 @@ void PID::Init() averagePWM = 0.0; // Time the sensor was last sampled. During startup, we use the current - // time as the initial value so as to not trigger an immediate warning from - // the Tick ISR. + // time as the initial value so as to not trigger an immediate warning from the Tick ISR. lastSampleTime = millis(); } @@ -240,7 +239,7 @@ void PID::SwitchOn() { if (reprap.Debug(Module::moduleHeat)) { - platform->MessageF(GENERIC_MESSAGE, "Heater %d switched on.\n", heater); + platform->MessageF(GENERIC_MESSAGE, "Heater %d %s\n", heater, (temperatureFault) ? "not switched on due to temperature fault" : "switched on"); } switchedOff = temperatureFault; } @@ -474,6 +473,10 @@ void PID::SwitchOff() active = false; switchedOff = true; heatingUp = false; + if (reprap.Debug(Module::moduleHeat)) + { + platform->MessageF(GENERIC_MESSAGE, "Heater %d switched off", heater); + } } float PID::GetAveragePWM() const diff --git a/src/Platform.cpp b/src/Platform.cpp index 437b4ff8..d5083261 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -1,6 +1,6 @@ /**************************************************************************************************** - RepRapFirmware - Platform: RepRapPro Ormerod with Arduino Due controller + RepRapFirmware - Platform: RepRapPro Ormerod with Duet controller 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. @@ -25,7 +25,7 @@ #include "sam/drivers/tc/tc.h" #include "sam/drivers/hsmci/hsmci.h" -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) || defined (EXTERNAL_DRIVERS) # include "ExternalDrivers.h" #endif @@ -185,8 +185,10 @@ void Platform::Init() fileStructureInitialised = true; +#if !defined(DUET_NG) || defined(PROTOTYPE_1) mcpDuet.begin(); // only call begin once in the entire execution, this begins the I2C comms on that channel for all objects mcpExpansion.setMCP4461Address(0x2E); // not required for mcpDuet, as this uses the default address +#endif // Directories @@ -209,11 +211,15 @@ void Platform::Init() ARRAY_INIT(accelerations, ACCELERATIONS); ARRAY_INIT(driveStepsPerUnit, DRIVE_STEPS_PER_UNIT); ARRAY_INIT(instantDvs, INSTANT_DVS); + +#if !defined(DUET_NG) || defined(PROTOTYPE_1) ARRAY_INIT(potWipes, POT_WIPES); senseResistor = SENSE_RESISTOR; maxStepperDigipotVoltage = MAX_STEPPER_DIGIPOT_VOLTAGE; stepperDacVoltageRange = STEPPER_DAC_VOLTAGE_RANGE; stepperDacVoltageOffset = STEPPER_DAC_VOLTAGE_OFFSET; +#endif + maxAverageAcceleration = 10000.0; // high enough to have no effect until it is changed // Z PROBE @@ -264,14 +270,17 @@ void Platform::Init() { pinMode(directionPins[drive], OUTPUT); } -#ifdef EXTERNAL_DRIVERS + +#if !defined(DUET_NG) || defined(PROTOTYPE_1) +# ifdef EXTERNAL_DRIVERS if (drive < FIRST_EXTERNAL_DRIVE && enablePins[drive] >= 0) -#else +# else if (enablePins[drive] >= 0) -#endif +# endif { pinMode(enablePins[drive], OUTPUT); } +#endif if (endStopPins[drive] >= 0) { pinMode(endStopPins[drive], INPUT_PULLUP); @@ -928,6 +937,19 @@ void Platform::Beep(int freq, int ms) MessageF(AUX_MESSAGE, "{\"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::SendMessage(const char* msg) +{ + OutputBuffer *buf; + if (OutputBuffer::Allocate(buf)) + { + buf->copy("{\"message\":"); + buf->EncodeString(msg, strlen(msg), false, true); + buf->cat("}\n"); + Message(AUX_MESSAGE, buf); + } +} + // Note: the use of floating point time will cause the resolution to degrade over time. // For example, 1ms time resolution will only be available for about half an hour from startup. // Personally, I (dc42) would rather just maintain and provide the time in milliseconds in a uint32_t. @@ -1316,13 +1338,15 @@ void Platform::Diagnostics(MessageType mtype) } MessageF(mtype, "Free file entries: %u\n", numFreeFiles); - // Show the longest write time - MessageF(mtype, "Longest block write time: %.1fms\n", FileStore::GetAndClearLongestWriteTime()); - // Show the HSMCI speed - MessageF(mtype, "SD card speed: %.1fMHz\n", (float)hsmci_get_speed()/1000000.0); + MessageF(mtype, "SD card interface speed: %.1fMBytes/sec\n", (float)hsmci_get_speed()/1000000.0); + + // Show the longest SD card write time + MessageF(mtype, "SD card longest block write time: %.1fms\n", FileStore::GetAndClearLongestWriteTime()); // Debug +//MessageF(mtype, "TC_FMR = %08x, PWM_FPE = %08x, PWM_FSR = %08x\n", TC2->TC_FMR, PWM->PWM_FPE, PWM->PWM_FSR); +//MessageF(mtype, "PWM2 period %08x, duty %08x\n", PWM->PWM_CH_NUM[2].PWM_CPRD, PWM->PWM_CH_NUM[2].PWM_CDTY); //MessageF(mtype, "Shortest/longest times read %.1f/%.1f write %.1f/%.1f ms, %u/%u\n", // (float)shortestReadWaitTime/1000, (float)longestReadWaitTime/1000, (float)shortestWriteWaitTime/1000, (float)longestWriteWaitTime/1000, // maxRead, maxWrite); @@ -1660,21 +1684,25 @@ void Platform::EnableDrive(size_t drive) { UpdateMotorCurrent(driver); // the current may have been reduced by the idle timeout -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + ExternalDrivers::EnableDrive(driver, true); +#else +# ifdef EXTERNAL_DRIVERS if (driver >= FIRST_EXTERNAL_DRIVE) { ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, true); } else { -#endif +# endif const int pin = enablePins[driver]; if (pin >= 0) { digitalWrite(pin, enableValues[driver]); } -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif #endif } } @@ -1685,23 +1713,27 @@ void Platform::DisableDrive(size_t drive) { if (drive < DRIVES) { +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + ExternalDrivers::EnableDrive(driverNumbers[drive], false); +#else const size_t driver = driverNumbers[drive]; -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS if (driver >= FIRST_EXTERNAL_DRIVE) { ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, false); } else { -#endif +# endif const int pin = enablePins[driver]; if (pin >= 0) { digitalWrite(pin, !enableValues[driver]); } driveState[drive] = DriveStatus::disabled; -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif #endif } } @@ -1741,14 +1773,19 @@ void Platform::UpdateMotorCurrent(size_t drive) current *= idleCurrentFactor; } const size_t driver = driverNumbers[drive]; -#ifdef EXTERNAL_DRIVERS + +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + ExternalDrivers::SetCurrent(driver, current); +#else + +# ifdef EXTERNAL_DRIVERS if (driver >= FIRST_EXTERNAL_DRIVE) { ExternalDrivers::SetCurrent(driver - FIRST_EXTERNAL_DRIVE, current); } else { -#endif +# endif unsigned short pot = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDigipotVoltage/2)/maxStepperDigipotVoltage); if (driver < 4) { @@ -1757,37 +1794,39 @@ void Platform::UpdateMotorCurrent(size_t drive) } else { -#ifndef DUET_NG +# ifndef DUET_NG if (board == BoardType::Duet_085) { -#endif +# endif // Extruder 0 is on DAC channel 0 if (driver == 4) { float dacVoltage = max<float>(current * 0.008*senseResistor + stepperDacVoltageOffset, 0.0); // the voltage we want from the DAC relative to its minimum uint32_t dac = (uint32_t)((256 * dacVoltage + 0.5 * stepperDacVoltageRange)/stepperDacVoltageRange); -#ifdef DUET_NG +# ifdef DUET_NG AnalogWrite(DAC1, dac); -#else +# else AnalogWrite(DAC0, dac); -#endif +# endif } else { mcpExpansion.setNonVolatileWiper(potWipes[driver-1], pot); mcpExpansion.setVolatileWiper(potWipes[driver-1], pot); } -#ifndef DUET_NG +# ifndef DUET_NG } 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); } -#endif +# endif } -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif + #endif } } @@ -1815,7 +1854,10 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode) { if (drive < DRIVES) { -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + return ExternalDrivers::SetMicrostepping(driverNumbers[drive], microsteps, mode); +#else +# ifdef EXTERNAL_DRIVERS const size_t driver = driverNumbers[drive]; if (driver >= FIRST_EXTERNAL_DRIVE) { @@ -1823,12 +1865,14 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode) } else { -#endif +# endif // On-board 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; -#ifdef EXTERNAL_DRIVERS +# ifdef EXTERNAL_DRIVERS } +# endif + #endif } return false; @@ -1836,7 +1880,13 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode) unsigned int Platform::GetMicrostepping(size_t drive, bool& interpolation) const { -#ifdef EXTERNAL_DRIVERS +#if defined(DUET_NG) && !defined(PROTOTYPE_1) + if (drive < DRIVES) + { + return ExternalDrivers::GetMicrostepping(driverNumbers[drive], interpolation); + } +# else +# ifdef EXTERNAL_DRIVERS if (drive < DRIVES) { const size_t driver = driverNumbers[drive]; @@ -1845,8 +1895,8 @@ unsigned int Platform::GetMicrostepping(size_t drive, bool& interpolation) const return ExternalDrivers::GetMicrostepping(driver - FIRST_EXTERNAL_DRIVE, interpolation); } } +# endif #endif - // On-board drivers only support x16 microstepping without interpolation interpolation = false; return 16; @@ -1937,10 +1987,11 @@ void Platform::InitFans() for (size_t i = 0; i < NUM_FANS; ++i) { fans[i].Init(COOLING_FAN_PINS[i], - !HEAT_ON -#ifndef DUET_NG - // The cooling fan 0 output pin gets inverted if HEAT_ON == 0 on a Duet 0.4, 0.6 or 0.7 - && (board == BoardType::Duet_06 || board == BoardType::Duet_07) +#ifdef DUET_NG + false +#else + // The cooling fan output pin gets inverted if HEAT_ON == 0 on a Duet 0.6 or 0.7 + !HEAT_ON && (board == BoardType::Duet_06 || board == BoardType::Duet_07) #endif ); } @@ -1949,6 +2000,7 @@ void Platform::InitFans() { // Set fan 1 to be thermostatic by default, monitoring all heaters except the default bed heater fans[1].SetHeatersMonitored(0xFFFF & ~(1 << BED_HEATER)); + fans[1].SetValue(1.0); // set it full on } coolingFanRpmPin = COOLING_FAN_RPM_PIN; @@ -2060,7 +2112,9 @@ void Platform::Fan::Check() { if (heatersMonitored != 0) { - val = (reprap.GetPlatform()->AnyHeaterHot(heatersMonitored, triggerTemperature)) ? 1.0 : 0.0; + val = (reprap.GetPlatform()->AnyHeaterHot(heatersMonitored, triggerTemperature)) + ? max<float>(0.5, val) // make sure that thermostatic fans always run at 50% speed or more + : 0.0; Refresh(); } } @@ -2198,18 +2252,8 @@ void Platform::Message(MessageType type, const char *message) break; case FIRMWARE_UPDATE_MESSAGE: - Message(HOST_MESSAGE, message); - // Send an alert message to the aux port - { - OutputBuffer *buf; - if (OutputBuffer::Allocate(buf, false)) - { - buf->cat("{\"alert\":"); - buf->EncodeString(message, strlen(message), true, true); - buf->cat("}\n"); - Message(AUX_MESSAGE, buf); - } - } + Message(HOST_MESSAGE, message); // send message to USB + SendMessage(message); // send message to aux break; case GENERIC_MESSAGE: @@ -2287,7 +2331,7 @@ void Platform::Message(const MessageType type, OutputBuffer *buffer) break; case FIRMWARE_UPDATE_MESSAGE: - // We don't generate any of these with an OutputBuffer argument, but if we get one, just send it to USB + // We don't generate any of these with an OutputBuffer argument, but if do we get one, just send it to USB Message(HOST_MESSAGE, buffer); break; @@ -2416,7 +2460,11 @@ void Platform::SetBoardType(BoardType bt) if (bt == BoardType::Auto) { #ifdef DUET_NG - board = BoardType::DuetNG_08; +# ifdef PROTOTYPE_1 + board = BoardType::DuetNG_06; +# else + board = BoardType::DuetNG_10; +# endif #else // 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. @@ -2445,7 +2493,11 @@ const char* Platform::GetElectronicsString() const switch (board) { #ifdef DUET_NG - case BoardType::DuetNG_08: return "DuetNG 0.6"; +# ifdef PROTOTYPE_1 + case BoardType::DuetNG_06: return "DuetNG 0.6"; +# else + case BoardType::DuetNG_10: return "DuetNG 1.0"; +# endif #else case BoardType::Duet_06: return "Duet 0.6"; case BoardType::Duet_07: return "Duet 0.7"; @@ -2459,18 +2511,26 @@ const char* Platform::GetElectronicsString() const // Set the specified pin to the specified output level. Return true if success, false if not allowed. bool Platform::SetPin(int pin, int level) { - if (pin >= 0 && (unsigned int)pin < NUM_PINS_ALLOWED && (level == 0 || level == 1)) + if (pin >= 0 && (unsigned int)pin < NUM_PINS_ALLOWED && (level >= 0 || level <= 255)) { const size_t index = (unsigned int)pin/8; const uint8_t mask = 1 << ((unsigned int)pin & 7); if ((pinAccessAllowed[index] & mask) != 0) { +#ifdef DUET_NG //TODO temporary + if (level == 1) + { + level = 255; + } + AnalogWrite(pin, level, 1000); +#else if ((pinInitialised[index] & mask) == 0) { pinMode(pin, OUTPUT); pinInitialised[index] |= mask; } digitalWrite(pin, level); +#endif return true; } } diff --git a/src/Platform.h b/src/Platform.h index 67a40182..e8107bcb 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -49,7 +49,11 @@ Licence: GPL #include "Core.h" #include "OutputMemory.h" #include "ff.h" -#include "MCP4461.h" + +#if !defined(DUET_NG) || defined(PROTOTYPE_1) +# include "MCP4461.h" +#endif + #include "MassStorage.h" #include "FileStore.h" #include "MessageType.h" @@ -178,7 +182,11 @@ enum class BoardType : uint8_t { Auto = 0, #ifdef DUET_NG - DuetNG_08 = 1 +# ifdef PROTOTYPE_1 + DuetNG_06 = 1 +# else + DuetNG_10 = 1 +# endif #else Duet_06 = 1, Duet_07 = 2, @@ -591,6 +599,7 @@ public: // AUX device void Beep(int freq, int ms); + void SendMessage(const char* msg); // Hotend configuration float GetFilamentWidth() const; @@ -719,12 +728,14 @@ private: // Digipots +#if !defined(DUET_NG) || defined(PROTOTYPE_1) MCP4461 mcpDuet; MCP4461 mcpExpansion; Pin potWipes[8]; // we have only 8 digipots, on the Duet 0.8.5 we use the DAC for the 9th float senseResistor; float maxStepperDigipotVoltage; float stepperDacVoltageRange, stepperDacVoltageOffset; +#endif // Z probe diff --git a/src/Reprap.cpp b/src/Reprap.cpp index afbf8202..9bfd555a 100644 --- a/src/Reprap.cpp +++ b/src/Reprap.cpp @@ -562,12 +562,11 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number(); response->catf("]},\"currentTool\":%d", toolNumber); - /* Output - only reported once */ + // Output - only reported once { bool sendBeep = (beepDuration != 0 && beepFrequency != 0); bool sendMessage = (message[0] != 0); - bool sourceRight = (gCodes->HaveAux() && source == ResponseSource::AUX) || (!gCodes->HaveAux() && source == ResponseSource::HTTP); - if ((sendBeep || message[0] != 0) && sourceRight) + if (sendBeep || sendMessage) { response->cat(",\"output\":{"); @@ -579,7 +578,6 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) { response->cat(","); } - beepFrequency = beepDuration = 0; } @@ -600,17 +598,12 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) response->catf(",\"params\":{\"atxPower\":%d", platform->AtxPower() ? 1 : 0); // Cooling fan value - response->cat(",\"fanPercent\":["); + response->cat(",\"fanPercent\":"); + ch = '['; for(size_t i = 0; i < NUM_FANS; i++) { - if (i == NUM_FANS - 1) - { - response->catf("%.2f", platform->GetFanValue(i) * 100.0); - } - else - { - response->catf("%.2f,", platform->GetFanValue(i) * 100.0); - } + response->catf("%c%.2f", ch, platform->GetFanValue(i) * 100.0); + ch = ','; } // Speed and Extrusion factors @@ -1114,11 +1107,17 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) break; } - // Send the fan0 settings (for PanelDue firmware 1.13) - response->catf(",\"fanPercent\":[%.02f,%.02f]", platform->GetFanValue(0) * 100.0, platform->GetFanValue(1) * 100.0); + // Send the fan settings, for PanelDue firmware 1.13 and later + response->catf(",\"fanPercent\":"); + ch = '['; + for (size_t i = 0; i < NUM_FANS; ++i) + { + response->catf("%c%.02f", ch, platform->GetFanValue(i)); + ch = ','; + } - // Send fan RPM value - response->catf(",\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM())); + // Send fan RPM value (we only support one) + response->catf("],\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM())); // 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->catf(",\"homed\":[%d,%d,%d]", @@ -1132,14 +1131,10 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) response->catf(",\"fraction_printed\":%.4f", max<float>(0.0, gCodes->FractionOfFilePrinted())); } - response->cat(",\"message\":"); - response->EncodeString(message, ARRAY_SIZE(message), false); + // 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 formns of status response - if (type < 2) - { - response->catf(",\"buff\":%u", webserver->GetGCodeBufferSpace(WebSource::HTTP)); // send the amount of buffer space available for gcodes - } - else if (type == 2) + if (type == 2) { if (printMonitor->IsPrinting()) { @@ -1251,25 +1246,29 @@ OutputBuffer *RepRap::GetFilesResponse(const char *dir, bool flagsDirs) return response; } +// Send a beep. We send it to both PanelDue and the web interface. void RepRap::Beep(int freq, int ms) { + beepFrequency = freq; + beepDuration = ms; + if (gCodes->HaveAux()) { // If there is an LCD device present, make it beep platform->Beep(freq, ms); } - else - { - // Otherwise queue it until the webserver can process it - beepFrequency = freq; - beepDuration = ms; - } } +// Send a short message. We send it to both PanelDue and the web interface. void RepRap::SetMessage(const char *msg) { strncpy(message, msg, MESSAGE_LENGTH); message[MESSAGE_LENGTH] = 0; + + if (gCodes->HaveAux()) + { + platform->SendMessage(msg); + } } // Get the status character for the new-style status response diff --git a/src/TemperatureError.cpp b/src/TemperatureError.cpp index 182d0755..115d2841 100644 --- a/src/TemperatureError.cpp +++ b/src/TemperatureError.cpp @@ -30,15 +30,20 @@ const char* TemperatureErrorString(TemperatureError err) // and so the heater should just be shut off immediately. bool IsPermanentError(TemperatureError err) { +#if 1 + return false; +#else switch (err) { case TemperatureError::success: case TemperatureError::busBusy: case TemperatureError::ioError: + case TemperatureError::hardwareError: // need this one because it comes up sometimes (Jimustanguitar on seemecnc forum) return false; default: return true; } +#endif } // End |