/** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /** * anycubic_chiron_lcd.cpp * * Anycubic Chiron TFT support for Marlin */ #include "../../inc/MarlinConfigPre.h" #if ENABLED(ANYCUBIC_LCD_CHIRON) #include "ui_api.h" #if ENABLED(AUTO_BED_LEVELING_BILINEAR) #if GRID_MAX_POINTS_X != 5 || GRID_MAX_POINTS_Y != 5 #error ANYCUBIC CHIRON LCD requires a 5x5 bed leveling grid (GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y) #endif #else #error ANYCUBIC CHIRON LCD requires AUTO_BED_LEVELING_BILINEAR enabled #endif #if DISABLED(FILAMENT_RUNOUT_SENSOR) #error ANYCUBIC CHIRON LCD requires FILAMENT_RUNOUT_SENSOR enabled #endif #if ENABLED(POWER_LOSS_RECOVERY) #error ANYCUBIC CHIRON LCD does not currently support POWER_LOSS_RECOVERY #endif static bool is_auto_leveling = false; static bool is_printing_from_sd = false; static bool is_out_of_filament = false; static void sendNewLine(void) { LCD_SERIAL.write('\r'); LCD_SERIAL.write('\n'); } static void send(const char *str) { LCD_SERIAL.print(str); } static void sendLine(const char *str) { send(str); sendNewLine(); } static void send_P(PGM_P str) { while (const char c = pgm_read_byte(str++)) LCD_SERIAL.write(c); } static void sendLine_P(PGM_P str) { send_P(str); sendNewLine(); } static void sendValue_P(PGM_P prefix, int value) { send_P(prefix); LCD_SERIAL.print(value); } static void sendValue_P(PGM_P prefix, float value) { send_P(prefix); LCD_SERIAL.print(value); } static void sendValueLine_P(PGM_P prefix, int value) { send_P(prefix); LCD_SERIAL.print(value); sendNewLine(); } static void sendValueLine_P(PGM_P prefix, float value) { send_P(prefix); LCD_SERIAL.print(value); sendNewLine(); } static int parseIntArgument(const char *buffer, char letterId) { char *p = strchr(buffer, letterId); if (!p) return -1; return atoi(p+1); } static float parseFloatArgument(const char *buffer, char letterId) { char *p = strchr(buffer, letterId); if (!p) return NAN; return strtof(p+1, nullptr); } static int mmToHundredths(float x) { // Round if (x >= 0) x += 0.005f; else x -= 0.005f; return (int)(x * 100.0f); } static float hundredthsToMm(int x) { return x / 100.0f; } #define SEND_PGM(str) send_P(PSTR(str)) #define SENDLINE_PGM(str) sendLine_P(PSTR(str)) #define SENDVALUE_PGM(prefix, value) sendValue_P(PSTR(prefix), value) #define SENDVALUELINE_PGM(prefix, value) sendValueLine_P(PSTR(prefix), value) namespace ExtUI { static void moveAxis(float delta, feedRate_t feedrate, axis_t axis) { float pos = getAxisPosition_mm(axis); pos += delta; setAxisPosition_mm(pos, axis, feedrate); } static void handleCmd(const char *rx) { static FileList fileList; static char selectedFileShortName[8+1+3+1]; if (rx[0] != 'A') { SERIAL_ECHOPGM("Unexpected RX: "); SERIAL_ECHOLN(rx); return; } const int cmd = atoi(&rx[1]); // Uncomment for debugging RX //if (cmd > 7 && cmd != 20) { // SERIAL_ECHOPGM("RX: "); // SERIAL_ECHOLN(rx); //} switch (cmd) { case 0: // Get Hotend Actual Temperature SENDVALUELINE_PGM("A0V ", (int)getActualTemp_celsius(E0)); break; case 1: // Get Hotend Target Temperature SENDVALUELINE_PGM("A1V ", (int)getTargetTemp_celsius(E0)); break; case 2: // Get Bed Actual Temperature SENDVALUELINE_PGM("A2V ", (int)getActualTemp_celsius(BED)); break; case 3: // Get Bed Target Temperature SENDVALUELINE_PGM("A3V ", (int)getTargetTemp_celsius(BED)); break; case 4: // Get Fan Speed SENDVALUELINE_PGM("A4V ", (int)getTargetFan_percent(FAN0)); break; case 5: // Get Current Coordinates SENDVALUE_PGM("A5V X: ", getAxisPosition_mm(X)); SENDVALUE_PGM(" Y: ", getAxisPosition_mm(Y)); SENDVALUE_PGM(" Z: ", getAxisPosition_mm(Z)); sendNewLine(); break; case 6: // Get SD Card Print Status if (isPrintingFromMedia()) SENDVALUELINE_PGM("A6V ", (int)getProgress_percent()); else SENDLINE_PGM("A6V ---"); break; case 7: // Get Printing Time if (isPrinting()) { const int totalMinutes = getProgress_seconds_elapsed() / 60; SENDVALUE_PGM("A7V ", (int)(totalMinutes/60)); SENDVALUE_PGM(" H ", (int)(totalMinutes%60)); SENDLINE_PGM(" M"); } else { SENDLINE_PGM("A7V 999:999"); } break; case 8: // Get SD Card File List if (isMediaInserted()) { const int startIndex = parseIntArgument(rx, 'S'); SENDLINE_PGM("FN "); for (int i = 0, fileIndex = 0, numFiles = 0; i < (int)fileList.count() && numFiles < 4; i++) { fileList.seek(i); if (!fileList.isDir()) { if (fileIndex >= startIndex) { sendLine(fileList.shortFilename()); sendLine(fileList.longFilename()); numFiles++; } fileIndex++; } } SENDLINE_PGM("END"); } else { SENDLINE_PGM("J02"); } break; case 9: // Pause SD Card Print if (isPrintingFromMedia()) { pausePrint(); is_printing_from_sd = false; SENDLINE_PGM("J05"); } else { SENDLINE_PGM("J16"); // Print stopped } break; case 10: // Resume SD Card Print if (is_out_of_filament) { is_out_of_filament = false; // Filament change did eject the old filament automatically, // now continue and load the new one setUserConfirmed(); SENDLINE_PGM("J04"); // Printing from SD card } else if (isPrintingFromMediaPaused()) { resumePrint(); SENDLINE_PGM("J04"); // Printing from SD card } break; case 11: // Stop SD Card Print if (isPrintingFromMedia()) { stopPrint(); is_printing_from_sd = false; SENDLINE_PGM("J16"); // Print stopped } break; //case 12: // Kill // break; case 13: // Select File if (!isPrinting()) { // Store selected file name char *p = strchr(rx, ' '); if (p != nullptr && strlen(p+1) < sizeof(selectedFileShortName)) { strcpy(selectedFileShortName, p+1); SENDLINE_PGM("J20"); // Open succeeded } else SENDLINE_PGM("J21"); // Open failed } break; case 14: // Start Print if (!isPrinting() && strcmp(selectedFileShortName, "") != 0) { printFile(selectedFileShortName); is_printing_from_sd = true; SENDLINE_PGM("J04"); // Printing from SD card } break; case 15: // Resume from power outage // This is not supported, just report print as completed SENDLINE_PGM("J16"); // Print stopped break; case 16: // Set Hotend Target Temperature { int temp = parseIntArgument(rx, 'S'); if (temp >= 0) setTargetTemp_celsius(temp, E0); } break; case 17: // Set Bed Target Temperature { int temp = parseIntArgument(rx, 'S'); if (temp >= 0) setTargetTemp_celsius(temp, BED); } break; case 18: // Set Fan Speed { int temp = parseIntArgument(rx, 'S'); if (temp >= 0) setTargetFan_percent(temp, FAN0); } break; case 19: // Disable Motors injectCommands_P(PSTR("M84")); break; case 20: // Get/Set Printing Speed { int newPerc = parseIntArgument(rx, 'S'); if (newPerc >= 0) setFeedrate_percent(newPerc); else SENDVALUELINE_PGM("A20V ", (int)getFeedrate_percent()); } break; case 21: // Home axes if (!isPrinting()) { const bool hasX = strchr(rx, 'X') != nullptr, hasY = strchr(rx, 'Y') != nullptr, hasZ = strchr(rx, 'Z') != nullptr, hasC = strchr(rx, 'C') != nullptr; if (hasX || hasY || hasZ) { if (hasX) injectCommands_P(PSTR("G28 X")); if (hasY) injectCommands_P(PSTR("G28 Y")); if (hasZ) injectCommands_P(PSTR("G28 Z")); } else if (hasC) { injectCommands_P(PSTR("G28")); } } break; case 22: // Move axes if (!isPrinting()) { const int feedrate = parseIntArgument(rx, 'F') / 60; float delta; if (!isnan(delta = parseFloatArgument(rx, 'X'))) moveAxis(delta, feedrate, X); else if (!isnan(delta = parseFloatArgument(rx, 'Y'))) moveAxis(delta, feedrate, Y); else if (!isnan(delta = parseFloatArgument(rx, 'Z'))) moveAxis(delta, feedrate, Z); } break; case 23: // Preheat PLA setTargetTemp_celsius(PREHEAT_1_TEMP_HOTEND, E0); setTargetTemp_celsius(PREHEAT_1_TEMP_BED, BED); SENDLINE_PGM("OK"); break; case 24: // Preheat ABS setTargetTemp_celsius(PREHEAT_2_TEMP_HOTEND, E0); setTargetTemp_celsius(PREHEAT_2_TEMP_BED, BED); SENDLINE_PGM("OK"); break; case 25: // Cool down setTargetTemp_celsius(0, E0); setTargetTemp_celsius(0, BED); SENDLINE_PGM("J12"); break; case 26: // Refresh SD Card fileList.refresh(); break; //case 27: // Adjust Servo Angles // break; //case 28: // Filament Test // break; case 29: // Get Bed Autolevel Grid { int x = parseIntArgument(rx, 'X'), y = parseIntArgument(rx, 'Y'); if (x != -1 && y != -1) { xy_uint8_t coord; coord.set(x, y); const int value = mmToHundredths(getMeshPoint(coord)); SENDVALUELINE_PGM("A29V ", value); } } break; case 30: // Autolevel if (strchr(rx, 'S')) { // Autoleveling started by clicking "PROBE" and then "OK" // Note: // We check for completion by monitoring the command queue. // Since it will become empty *while* processing the last injected command, // we enqueue an extra 10ms delay so we can the determine when all the others // have completed. if (isMachineHomed()) injectCommands_P(PSTR("G29\nG4 P10")); else injectCommands_P(PSTR("G28\nG29\nG4 P10")); is_auto_leveling = true; } else { // Entering Autoleveling screen if (isPrinting()) SENDLINE_PGM("J24"); // Disallow autoleveling else SENDLINE_PGM("J26"); // Allow autoleveling } break; case 31: // Set Bed Autolevel Z offset if (strchr(rx, 'G')) { // Get SENDVALUELINE_PGM("A31V ", getZOffset_mm()); } else if (strchr(rx, 'S')) { // Set float delta = parseFloatArgument(rx, 'S'); delta = constrain(delta, -1.0, 1.0); setZOffset_mm(getZOffset_mm() + delta); SENDVALUELINE_PGM("A31V ", getZOffset_mm()); } else if (strchr(rx, 'D')) { // Save injectCommands_P(PSTR("M500")); } break; //case 32: // ? // break; case 33: // Get Version Info SENDLINE_PGM("J33 " SHORT_BUILD_VERSION); break; case 34: // Set Bed Autolevel Grid { int x = parseIntArgument(rx, 'X'), y = parseIntArgument(rx, 'Y'), v = parseIntArgument(rx, 'V'); if (x != -1 && y != -1 && v != -1) { // Set new value float value = hundredthsToMm(v); value = constrain(value, -10, 10); xy_uint8_t coord; coord.set(x, y); setMeshPoint(coord, value); } else if (strchr(rx, 'S')) { // Save (apply new values) injectCommands_P(PSTR("M500")); } else if (strchr(rx, 'C')) { // Cancel (discard new values) injectCommands_P(PSTR("M501")); } } break; } } #define RX_LEN_MAX 63 static void parseSerialRx() { static char rxBuffer[RX_LEN_MAX+1]; static uint8_t rxLen = 0; while (LCD_SERIAL.available()) { const char c = LCD_SERIAL.read(); switch (c) { case '\r': case '\n': if (rxLen > 0 && rxLen <= RX_LEN_MAX) { rxBuffer[rxLen] = '\0'; // Terminate string handleCmd(rxBuffer); } rxLen = 0; break; default: if (rxLen < RX_LEN_MAX) rxBuffer[rxLen++] = c; else { rxLen = 0xFF; // Overrun SERIAL_ECHOPGM("Warning: dropping long received line"); } break; } } } static void detectPrintFromSdCompletion() { // Note: printFile() queues some commands that actually start the print, so isPrintingFromMedia() // initially returns false if (is_printing_from_sd && !commandsInQueue() && !isPrintingFromMedia()) { is_printing_from_sd = false; SENDLINE_PGM("J14"); // Print done } } static void detectAutolevelingCompletion() { if (is_auto_leveling && !commandsInQueue()) { is_auto_leveling = false; injectCommands_P(PSTR("M500")); SENDLINE_PGM("J25"); // Autoleveling done } } void onStartup() { #ifndef LCD_BAUDRATE #define LCD_BAUDRATE 115200 #endif LCD_SERIAL.begin(LCD_BAUDRATE); sendNewLine(); SENDLINE_PGM("J17"); // Reset delay_ms(10); SENDLINE_PGM("J12"); // Ready } void onIdle() { parseSerialRx(); detectAutolevelingCompletion(); detectPrintFromSdCompletion(); } void onPrinterKilled(PGM_P const error, PGM_P const component) { } void onMediaInserted() { SENDLINE_PGM("J00"); // SD Inserted } void onMediaError() { } void onMediaRemoved() { SENDLINE_PGM("J01"); // SD Removed } void onPlayTone(const uint16_t frequency, const uint16_t duration) { tone(BEEPER_PIN, frequency, duration); } void onPrintTimerStarted() { } void onPrintTimerPaused() { } void onPrintTimerStopped() { } void onFilamentRunout(const extruder_t extruder) { is_out_of_filament = true; SENDLINE_PGM("J23"); // Filament runout SENDLINE_PGM("J18"); // Print paused // Note: printer will unload filament automatically } void onUserConfirmRequired(const char * const msg) { } void onStatusChanged(const char * const msg) { } void onFactoryReset() { } void onStoreSettings(char *buff) { } void onLoadSettings(const char *buff) { } void onConfigurationStoreWritten(bool success) { } void onConfigurationStoreRead(bool success) { } void onMeshUpdate(const int8_t xpos, const int8_t ypos, const float zval) { } #if ENABLED(POWER_LOSS_RECOVERY) void onPowerLossResume() { } #endif #if HAS_PID_HEATING void onPidTuning(const result_t rst) { } #endif } #endif // ANYCUBIC_LCD_CHIRON