537 lines
16 KiB
C++
537 lines
16 KiB
C++
|
/**
|
||
|
* 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 <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* extui_anycubic_chiron_lcd.cpp
|
||
|
*
|
||
|
* Anycubic Chiron TFT support for Marlin
|
||
|
*/
|
||
|
|
||
|
#include "../inc/MarlinConfigPre.h"
|
||
|
|
||
|
#if ENABLED(ANYCUBIC_LCD_CHIRON)
|
||
|
|
||
|
#include "extui/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) {
|
||
|
ANYCUBIC_LCD_SERIAL.write('\r');
|
||
|
ANYCUBIC_LCD_SERIAL.write('\n');
|
||
|
}
|
||
|
|
||
|
static void send(const char *str) {
|
||
|
ANYCUBIC_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++))
|
||
|
ANYCUBIC_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);
|
||
|
ANYCUBIC_LCD_SERIAL.print(value);
|
||
|
}
|
||
|
|
||
|
static void sendValue_P(PGM_P prefix, float value) {
|
||
|
send_P(prefix);
|
||
|
ANYCUBIC_LCD_SERIAL.print(value);
|
||
|
}
|
||
|
|
||
|
static void sendValueLine_P(PGM_P prefix, int value) {
|
||
|
send_P(prefix);
|
||
|
ANYCUBIC_LCD_SERIAL.print(value);
|
||
|
sendNewLine();
|
||
|
}
|
||
|
|
||
|
static void sendValueLine_P(PGM_P prefix, float value) {
|
||
|
send_P(prefix);
|
||
|
ANYCUBIC_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 (ANYCUBIC_LCD_SERIAL.available()) {
|
||
|
const char c = ANYCUBIC_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() {
|
||
|
ANYCUBIC_LCD_SERIAL.begin(115200);
|
||
|
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
|