My Marlin configs for Fabrikator Mini and CTC i3 Pro B
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

marlinui_TFTGLCD.cpp 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  1. /**
  2. * Marlin 3D Printer Firmware
  3. * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  4. *
  5. * Based on Sprinter and grbl.
  6. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. *
  21. */
  22. #include "../../inc/MarlinConfigPre.h"
  23. #if IS_TFTGLCD_PANEL
  24. /**
  25. * marlinui_TFTGLCD.cpp
  26. *
  27. * Implementation of the LCD display routines for a TFT GLCD displays with external controller.
  28. * This display looks like a REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER but has good text font
  29. * and supports color output.
  30. */
  31. #if NONE(__AVR__, TARGET_LPC1768, STM32F1, STM32F4xx)
  32. #warning "Selected platform not yet tested. Please contribute your good pin mappings."
  33. #endif
  34. #if ENABLED(TFTGLCD_PANEL_SPI)
  35. #include <SPI.h>
  36. #else
  37. #include <Wire.h>
  38. #endif
  39. #include "marlinui_TFTGLCD.h"
  40. #include "../marlinui.h"
  41. #include "../../libs/numtostr.h"
  42. #include "../../sd/cardreader.h"
  43. #include "../../module/temperature.h"
  44. #include "../../module/printcounter.h"
  45. #include "../../module/planner.h"
  46. #include "../../module/motion.h"
  47. #if DISABLED(LCD_PROGRESS_BAR) && BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
  48. #include "../../feature/filwidth.h"
  49. #include "../../gcode/parser.h"
  50. #endif
  51. #if EITHER(HAS_COOLER, LASER_COOLANT_FLOW_METER)
  52. #include "../../feature/cooler.h"
  53. #endif
  54. #if ENABLED(I2C_AMMETER)
  55. #include "../../feature/ammeter.h"
  56. #endif
  57. #if HAS_CUTTER
  58. #include "../../feature/spindle_laser.h"
  59. #endif
  60. #if ENABLED(AUTO_BED_LEVELING_UBL)
  61. #include "../../feature/bedlevel/bedlevel.h"
  62. #endif
  63. TFTGLCD lcd;
  64. #define ICON_LOGO B00000001
  65. #define ICON_TEMP1 B00000010 // Hotend 1
  66. #define ICON_TEMP2 B00000100 // Hotend 2
  67. #define ICON_TEMP3 B00001000 // Hotend 3
  68. #define ICON_BED B00010000
  69. #define ICON_FAN B00100000
  70. #define ICON_HOT B01000000 // When any T > 50deg
  71. #define PIC_MASK 0x7F
  72. // LEDs not used, for compatibility with Smoothieware
  73. #define LED_HOTEND_ON B00000001
  74. #define LED_BED_ON B00000010
  75. #define LED_FAN_ON B00000100
  76. #define LED_HOT B00001000
  77. #define LED_MASK 0x0F
  78. #define FBSIZE (LCD_WIDTH * LCD_HEIGHT + 2)
  79. #define MIDDLE_Y ((LCD_HEIGHT - 1) / 2)
  80. // Markers for change line colors
  81. #define COLOR_EDIT '#'
  82. #define COLOR_ERROR '!'
  83. #ifdef CONVERT_TO_EXT_ASCII //use standard pseudographic symbols in ASCII table
  84. #define LR 179 //vertical line
  85. #define TRC 191 //top right corner
  86. #define BLC 192 //bottom left corner
  87. #define GL 196 //horizontal line
  88. #define BRC 217 //bottom right corner, should be replaced to 12 for some languages
  89. #define TLC 218 //top left corner, should be replaced to 13 for some languages
  90. #else //next symbols must be present in panel font
  91. #define LR 8 //equal to 179
  92. #define TRC 9 //equal to 191
  93. #define BLC 10 //equal to 192
  94. #define GL 11 //equal to 196
  95. #define BRC 12 //equal to 217
  96. #define TLC 13 //equal to 218
  97. #endif
  98. #define Marlin 0x01
  99. enum Commands { // based on Smoothieware commands
  100. GET_SPI_DATA = 0,
  101. READ_BUTTONS, // read buttons
  102. READ_ENCODER, // read encoder
  103. LCD_WRITE, // write all screen to LCD
  104. BUZZER, // beep buzzer
  105. CONTRAST, // set contrast (brightnes)
  106. // Other commands... 0xE0 thru 0xFF
  107. GET_LCD_ROW = 0xE0, // for detect panel
  108. GET_LCD_COL, // reserved for compatibility with Smoothieware, not used
  109. LCD_PUT, // write one line to LCD
  110. CLR_SCREEN,
  111. INIT_SCREEN = 0xFE // clear panel buffer
  112. };
  113. static unsigned char framebuffer[FBSIZE];
  114. static unsigned char *fb;
  115. static uint8_t cour_line;
  116. static uint8_t picBits, ledBits, hotBits;
  117. static uint8_t PanelDetected = 0;
  118. // Different platforms use different SPI methods
  119. #if ANY(__AVR__, TARGET_LPC1768, __STM32F1__, ARDUINO_ARCH_SAM, __SAMD51__, __MK20DX256__, __MK64FX512__)
  120. #define SPI_SEND_ONE(V) SPI.transfer(V);
  121. #define SPI_SEND_TWO(V) SPI.transfer16(V);
  122. #elif EITHER(STM32F4xx, STM32F1xx)
  123. #define SPI_SEND_ONE(V) SPI.transfer(V, SPI_CONTINUE);
  124. #define SPI_SEND_TWO(V) SPI.transfer16(V, SPI_CONTINUE);
  125. #elif defined(ARDUINO_ARCH_ESP32)
  126. #define SPI_SEND_ONE(V) SPI.write(V);
  127. #define SPI_SEND_TWO(V) SPI.write16(V);
  128. #endif
  129. #if ANY(__AVR__, ARDUINO_ARCH_SAM, __SAMD51__, __MK20DX256__, __MK64FX512__)
  130. #define SPI_SEND_SOME(V,L,Z) SPI.transfer(&V[Z], L);
  131. #elif EITHER(STM32F4xx, STM32F1xx)
  132. #define SPI_SEND_SOME(V,L,Z) SPI.transfer(&V[Z], L, SPI_CONTINUE);
  133. #elif ANY(TARGET_LPC1768, __STM32F1__, ARDUINO_ARCH_ESP32)
  134. #define SPI_SEND_SOME(V,L,Z) do{ for (uint16_t i = 0; i < L; i++) SPI_SEND_ONE(V[(Z)+i]); }while(0)
  135. #endif
  136. // Constructor
  137. TFTGLCD::TFTGLCD() {}
  138. // Clear local buffer
  139. void TFTGLCD::clear_buffer() {
  140. memset(&framebuffer[0], ' ', FBSIZE - 2);
  141. framebuffer[FBSIZE - 1] = framebuffer[FBSIZE - 2] = 0;
  142. picBits = ledBits = 0;
  143. }
  144. // Clear panel's screen
  145. void TFTGLCD::clr_screen() {
  146. if (!PanelDetected) return;
  147. #if ENABLED(TFTGLCD_PANEL_SPI)
  148. WRITE(TFTGLCD_CS, LOW);
  149. SPI_SEND_ONE(CLR_SCREEN);
  150. WRITE(TFTGLCD_CS, HIGH);
  151. #else
  152. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS); //set I2C device address
  153. Wire.write(CLR_SCREEN);
  154. Wire.endTransmission(); //transmit data
  155. #endif
  156. }
  157. // Set new text cursor position
  158. void TFTGLCD::setCursor(uint8_t col, uint8_t row) {
  159. fb = &framebuffer[0] + col + row * LCD_WIDTH;
  160. cour_line = row;
  161. }
  162. // Send char to buffer
  163. void TFTGLCD::write(char c) {
  164. *fb++ = c;
  165. }
  166. // Send text line to buffer
  167. void TFTGLCD::print(const char *line) {
  168. while (*line) *fb++ = *line++;
  169. }
  170. // For menu
  171. void TFTGLCD::print_line() {
  172. if (!PanelDetected) return;
  173. #if ENABLED(TFTGLCD_PANEL_SPI)
  174. WRITE(TFTGLCD_CS, LOW);
  175. SPI_SEND_ONE(LCD_PUT);
  176. SPI_SEND_ONE(cour_line);
  177. SPI_SEND_SOME(framebuffer, LCD_WIDTH, cour_line * LCD_WIDTH);
  178. WRITE(TFTGLCD_CS, HIGH);
  179. #else
  180. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS); //set I2C device address
  181. Wire.write(LCD_PUT);
  182. Wire.write(cour_line);
  183. Wire.write(&framebuffer[cour_line * LCD_WIDTH], LCD_WIDTH); //transfer 1 line to txBuffer
  184. Wire.endTransmission(); //transmit data
  185. safe_delay(1);
  186. #endif
  187. }
  188. void TFTGLCD::print_screen() {
  189. if (!PanelDetected) return;
  190. framebuffer[FBSIZE - 2] = picBits & PIC_MASK;
  191. framebuffer[FBSIZE - 1] = ledBits;
  192. #if ENABLED(TFTGLCD_PANEL_SPI)
  193. // Send all framebuffer to panel
  194. WRITE(TFTGLCD_CS, LOW);
  195. SPI_SEND_ONE(LCD_WRITE);
  196. SPI_SEND_SOME(framebuffer, FBSIZE, 0);
  197. WRITE(TFTGLCD_CS, HIGH);
  198. #else
  199. uint8_t r;
  200. // Send framebuffer to panel by line
  201. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  202. // First line
  203. Wire.write(LCD_WRITE);
  204. Wire.write(&framebuffer[0], LCD_WIDTH);
  205. Wire.endTransmission();
  206. for (r = 1; r < (LCD_HEIGHT - 1); r++) {
  207. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  208. Wire.write(&framebuffer[r * LCD_WIDTH], LCD_WIDTH);
  209. Wire.endTransmission();
  210. }
  211. // Last line
  212. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  213. Wire.write(&framebuffer[r * LCD_WIDTH], LCD_WIDTH);
  214. Wire.write(&framebuffer[FBSIZE - 2], 2);
  215. Wire.endTransmission();
  216. #endif
  217. }
  218. void TFTGLCD::setContrast(uint16_t contrast) {
  219. if (!PanelDetected) return;
  220. #if ENABLED(TFTGLCD_PANEL_SPI)
  221. WRITE(TFTGLCD_CS, LOW);
  222. SPI_SEND_ONE(CONTRAST);
  223. SPI_SEND_ONE((uint8_t)contrast);
  224. WRITE(TFTGLCD_CS, HIGH);
  225. #else
  226. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  227. Wire.write(CONTRAST);
  228. Wire.write((uint8_t)contrast);
  229. Wire.endTransmission();
  230. #endif
  231. }
  232. extern volatile int8_t encoderDiff;
  233. // Read buttons and encoder states
  234. uint8_t MarlinUI::read_slow_buttons(void) {
  235. if (!PanelDetected) return 0;
  236. #if ENABLED(TFTGLCD_PANEL_SPI)
  237. uint8_t b = 0;
  238. WRITE(TFTGLCD_CS, LOW);
  239. SPI_SEND_ONE(READ_ENCODER);
  240. #ifndef STM32F4xx
  241. WRITE(TFTGLCD_CS, LOW); // for delay
  242. #endif
  243. encoderDiff += SPI_SEND_ONE(READ_BUTTONS);
  244. #ifndef STM32F4xx
  245. WRITE(TFTGLCD_CS, LOW); // for delay
  246. WRITE(TFTGLCD_CS, LOW);
  247. #endif
  248. b = SPI_SEND_ONE(GET_SPI_DATA);
  249. WRITE(TFTGLCD_CS, HIGH);
  250. return b;
  251. #else
  252. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  253. Wire.write(READ_ENCODER);
  254. Wire.endTransmission();
  255. #ifdef __AVR__
  256. Wire.requestFrom((uint8_t)LCD_I2C_ADDRESS, 2, 0, 0, 1);
  257. #elif defined(STM32F1)
  258. Wire.requestFrom((uint8_t)LCD_I2C_ADDRESS, (uint8_t)2);
  259. #elif EITHER(STM32F4xx, TARGET_LPC1768)
  260. Wire.requestFrom(LCD_I2C_ADDRESS, 2);
  261. #endif
  262. encoderDiff += Wire.read();
  263. return Wire.read(); //buttons
  264. #endif
  265. }
  266. // Duration in ms, freq in Hz
  267. void MarlinUI::buzz(const long duration, const uint16_t freq) {
  268. if (!PanelDetected) return;
  269. if (!buzzer_enabled) return;
  270. #if ENABLED(TFTGLCD_PANEL_SPI)
  271. WRITE(TFTGLCD_CS, LOW);
  272. SPI_SEND_ONE(BUZZER);
  273. SPI_SEND_TWO((uint16_t)duration);
  274. SPI_SEND_TWO(freq);
  275. WRITE(TFTGLCD_CS, HIGH);
  276. #else
  277. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  278. Wire.write(BUZZER);
  279. Wire.write((uint8_t)(duration >> 8));
  280. Wire.write((uint8_t)duration);
  281. Wire.write((uint8_t)(freq >> 8));
  282. Wire.write((uint8_t)freq);
  283. Wire.endTransmission();
  284. #endif
  285. }
  286. void MarlinUI::init_lcd() {
  287. uint8_t t;
  288. lcd.clear_buffer();
  289. t = 0;
  290. #if ENABLED(TFTGLCD_PANEL_SPI)
  291. // SPI speed must be less 10MHz
  292. SET_OUTPUT(TFTGLCD_CS);
  293. WRITE(TFTGLCD_CS, HIGH);
  294. spiInit(TERN(__STM32F1__, SPI_QUARTER_SPEED, SPI_FULL_SPEED));
  295. WRITE(TFTGLCD_CS, LOW);
  296. SPI_SEND_ONE(GET_LCD_ROW);
  297. t = SPI_SEND_ONE(GET_SPI_DATA);
  298. #else
  299. #ifdef TARGET_LPC1768
  300. Wire.begin(); //init twi/I2C
  301. #else
  302. Wire.begin((uint8_t)LCD_I2C_ADDRESS); //init twi/I2C
  303. #endif
  304. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  305. Wire.write((uint8_t)GET_LCD_ROW); // put command to buffer
  306. Wire.endTransmission(); // send buffer
  307. #ifdef __AVR__
  308. Wire.requestFrom((uint8_t)LCD_I2C_ADDRESS, 1, 0, 0, 1);
  309. #elif ANY(STM32F1, STM32F4xx, TARGET_LPC1768)
  310. Wire.requestFrom(LCD_I2C_ADDRESS, 1);
  311. #endif
  312. t = (uint8_t)Wire.read();
  313. #endif
  314. if (t == LCD_HEIGHT) {
  315. PanelDetected = 1;
  316. #if ENABLED(TFTGLCD_PANEL_SPI)
  317. SPI_SEND_ONE(INIT_SCREEN);
  318. SPI_SEND_ONE(Marlin);
  319. WRITE(TFTGLCD_CS, HIGH);
  320. #else
  321. Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
  322. Wire.write((uint8_t)INIT_SCREEN);
  323. Wire.write(Marlin);
  324. Wire.endTransmission();
  325. #endif
  326. }
  327. else
  328. PanelDetected = 0;
  329. safe_delay(100);
  330. }
  331. bool MarlinUI::detected() {
  332. return PanelDetected;
  333. }
  334. void MarlinUI::clear_lcd() {
  335. if (!PanelDetected) return;
  336. lcd.clr_screen();
  337. lcd.clear_buffer();
  338. }
  339. int16_t MarlinUI::contrast; // Initialized by settings.load()
  340. void MarlinUI::set_contrast(const int16_t value) {
  341. contrast = constrain(value, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX);
  342. lcd.setContrast(contrast);
  343. }
  344. static void center_text_P(PGM_P pstart, uint8_t y) {
  345. uint8_t len = utf8_strlen_P(pstart);
  346. if (len < LCD_WIDTH)
  347. lcd.setCursor((LCD_WIDTH - len) / 2, y);
  348. else
  349. lcd.setCursor(0, y);
  350. lcd_put_u8str_P(pstart);
  351. }
  352. #if ENABLED(SHOW_BOOTSCREEN)
  353. void MarlinUI::show_bootscreen() {
  354. if (!PanelDetected) return;
  355. //
  356. // Show the Marlin logo, splash line1, and splash line 2
  357. //
  358. uint8_t indent = (LCD_WIDTH - 8) / 2;
  359. // symbols 217 (bottom right corner) and 218 (top left corner) are using for letters in some languages
  360. // and they should be moved to beginning ASCII table as special symbols
  361. lcd.setCursor(indent, 0); lcd.write(TLC); lcd_put_u8str_P(PSTR("------")); lcd.write(TRC);
  362. lcd.setCursor(indent, 1); lcd.write(LR); lcd_put_u8str_P(PSTR("Marlin")); lcd.write(LR);
  363. lcd.setCursor(indent, 2); lcd.write(BLC); lcd_put_u8str_P(PSTR("------")); lcd.write(BRC);
  364. center_text_P(PSTR(SHORT_BUILD_VERSION), 3);
  365. center_text_P(PSTR(MARLIN_WEBSITE_URL), 4);
  366. picBits = ICON_LOGO;
  367. lcd.print_screen();
  368. }
  369. void MarlinUI::bootscreen_completion(const millis_t sofar) {
  370. if ((BOOTSCREEN_TIMEOUT) > sofar) safe_delay((BOOTSCREEN_TIMEOUT) - sofar);
  371. }
  372. #endif // SHOW_BOOTSCREEN
  373. void MarlinUI::draw_kill_screen() {
  374. if (!PanelDetected) return;
  375. lcd.clear_buffer();
  376. lcd.setCursor(0, 3); lcd.write(COLOR_ERROR);
  377. lcd.setCursor((LCD_WIDTH - utf8_strlen(status_message)) / 2 + 1, 3);
  378. lcd_put_u8str(status_message);
  379. center_text_P(GET_TEXT(MSG_HALTED), 5);
  380. center_text_P(GET_TEXT(MSG_PLEASE_RESET), 6);
  381. lcd.print_screen();
  382. }
  383. //
  384. // Before homing, blink '123' <-> '???'.
  385. // Homed but unknown... '123' <-> ' '.
  386. // Homed and known, display constantly.
  387. //
  388. FORCE_INLINE void _draw_axis_value(const AxisEnum axis, const char *value, const bool blink) {
  389. lcd.write('X' + uint8_t(axis));
  390. if (blink)
  391. lcd.print(value);
  392. else if (axis_should_home(axis))
  393. while (const char c = *value++) lcd.write(c <= '.' ? c : '?');
  394. else if (NONE(HOME_AFTER_DEACTIVATE, DISABLE_REDUCED_ACCURACY_WARNING) && !axis_is_trusted(axis))
  395. lcd_put_u8str_P(axis == Z_AXIS ? PSTR(" ") : PSTR(" "));
  396. else
  397. lcd_put_u8str(value);
  398. }
  399. #if HAS_HOTEND || HAS_HEATED_BED
  400. FORCE_INLINE void _draw_heater_status(const heater_id_t heater_id, const char *prefix, const bool blink) {
  401. uint8_t pic_hot_bits;
  402. #if HAS_HEATED_BED
  403. const bool isBed = heater_id < 0;
  404. const celsius_t t1 = (isBed ? thermalManager.wholeDegBed() : thermalManager.wholeDegHotend(heater_id)),
  405. t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater_id));
  406. #else
  407. const celsius_t t1 = thermalManager.wholeDegHotend(heater_id), t2 = thermalManager.degTargetHotend(heater_id);
  408. #endif
  409. #if HOTENDS < 2
  410. if (heater_id == H_E0) {
  411. lcd.setCursor(2, 5); lcd.print(prefix); //HE
  412. lcd.setCursor(1, 6); lcd.print(i16tostr3rj(t1));
  413. lcd.setCursor(1, 7);
  414. }
  415. else {
  416. lcd.setCursor(6, 5); lcd.print(prefix); //BED
  417. lcd.setCursor(6, 6); lcd.print(i16tostr3rj(t1));
  418. lcd.setCursor(6, 7);
  419. }
  420. #else
  421. if (heater_id > H_BED) {
  422. lcd.setCursor(heater_id * 4, 5); lcd.print(prefix); // HE1 or HE2 or HE3
  423. lcd.setCursor(heater_id * 4, 6); lcd.print(i16tostr3rj(t1));
  424. lcd.setCursor(heater_id * 4, 7);
  425. }
  426. else {
  427. lcd.setCursor(13, 5); lcd.print(prefix); //BED
  428. lcd.setCursor(13, 6); lcd.print(i16tostr3rj(t1));
  429. lcd.setCursor(13, 7);
  430. }
  431. #endif // HOTENDS <= 1
  432. #if !HEATER_IDLE_HANDLER
  433. UNUSED(blink);
  434. #else
  435. if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
  436. lcd.write(' ');
  437. if (t2 >= 10) lcd.write(' ');
  438. if (t2 >= 100) lcd.write(' ');
  439. }
  440. else
  441. #endif // !HEATER_IDLE_HANDLER
  442. lcd.print(i16tostr3rj(t2));
  443. switch (heater_id) {
  444. case H_BED: pic_hot_bits = ICON_BED; break;
  445. case H_E0: pic_hot_bits = ICON_TEMP1; break;
  446. case H_E1: pic_hot_bits = ICON_TEMP2; break;
  447. case H_E2: pic_hot_bits = ICON_TEMP3;
  448. default: break;
  449. }
  450. if (t2) picBits |= pic_hot_bits;
  451. else picBits &= ~pic_hot_bits;
  452. if (t1 > 50) hotBits |= pic_hot_bits;
  453. else hotBits &= ~pic_hot_bits;
  454. if (hotBits) picBits |= ICON_HOT;
  455. else picBits &= ~ICON_HOT;
  456. }
  457. #endif // HAS_HOTEND || HAS_HEATED_BED
  458. #if HAS_COOLER
  459. FORCE_INLINE void _draw_cooler_status(const bool blink) {
  460. const celsius_t t2 = thermalManager.degTargetCooler();
  461. lcd.setCursor(0, 5); lcd_put_u8str_P(PSTR("COOL"));
  462. lcd.setCursor(1, 6); lcd_put_u8str(i16tostr3rj(thermalManager.wholeDegCooler()));
  463. lcd.setCursor(1, 7);
  464. #if !HEATER_IDLE_HANDLER
  465. UNUSED(blink);
  466. #else
  467. if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
  468. lcd_put_wchar(' ');
  469. if (t2 >= 10) lcd_put_wchar(' ');
  470. if (t2 >= 100) lcd_put_wchar(' ');
  471. }
  472. else
  473. #endif
  474. lcd_put_u8str(i16tostr3left(t2));
  475. lcd_put_wchar(' ');
  476. if (t2 < 10) lcd_put_wchar(' ');
  477. if (t2) picBits |= ICON_TEMP1;
  478. else picBits &= ~ICON_TEMP1;
  479. }
  480. #endif // HAS_COOLER
  481. #if ENABLED(LASER_COOLANT_FLOW_METER)
  482. FORCE_INLINE void _draw_flowmeter_status() {
  483. lcd.setCursor(5, 5); lcd_put_u8str_P(PSTR("FLOW"));
  484. lcd.setCursor(7, 6); lcd_put_wchar('L');
  485. lcd.setCursor(6, 7); lcd_put_u8str(ftostr11ns(cooler.flowrate));
  486. if (cooler.flowrate) picBits |= ICON_FAN;
  487. else picBits &= ~ICON_FAN;
  488. }
  489. #endif
  490. #if ENABLED(I2C_AMMETER)
  491. FORCE_INLINE void _draw_ammeter_status() {
  492. lcd.setCursor(10, 5); lcd_put_u8str_P(PSTR("ILAZ"));
  493. ammeter.read();
  494. lcd.setCursor(11, 6);
  495. if (ammeter.current <= 0.999f)
  496. {
  497. lcd_put_u8str("mA");
  498. lcd.setCursor(10, 7);
  499. lcd_put_wchar(' '); lcd_put_u8str(ui16tostr3rj(uint16_t(ammeter.current * 1000 + 0.5f)));
  500. }
  501. else {
  502. lcd_put_u8str(" A");
  503. lcd.setCursor(10, 7);
  504. lcd_put_u8str(ftostr12ns(ammeter.current));
  505. }
  506. if (ammeter.current) picBits |= ICON_BED;
  507. else picBits &= ~ICON_BED;
  508. }
  509. #endif // I2C_AMMETER
  510. #if HAS_CUTTER
  511. FORCE_INLINE void _draw_cutter_status() {
  512. lcd.setCursor(15, 5); lcd_put_u8str_P(PSTR("CUTT"));
  513. #if CUTTER_UNIT_IS(RPM)
  514. lcd.setCursor(16, 6); lcd_put_u8str_P(PSTR("RPM"));
  515. lcd.setCursor(15, 7); lcd_put_u8str(ftostr31ns(float(cutter.unitPower) / 1000));
  516. lcd_put_wchar('K');
  517. #elif CUTTER_UNIT_IS(PERCENT)
  518. lcd.setCursor(17, 6); lcd_put_wchar('%');
  519. lcd.setCursor(18, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
  520. #else
  521. lcd.setCursor(17, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
  522. #endif
  523. if (cutter.unitPower) picBits |= ICON_HOT;
  524. else picBits &= ~ICON_HOT;
  525. }
  526. #endif // HAS_CUTTER
  527. #if HAS_PRINT_PROGRESS
  528. FORCE_INLINE void _draw_print_progress() {
  529. if (!PanelDetected) return;
  530. const uint8_t progress = ui._get_progress();
  531. #if ENABLED(SDSUPPORT)
  532. lcd_put_u8str_P(PSTR("SD"));
  533. #elif ENABLED(LCD_SET_PROGRESS_MANUALLY)
  534. lcd_put_u8str_P(PSTR("P:"));
  535. #endif
  536. if (progress)
  537. lcd.print(ui8tostr3rj(progress));
  538. else
  539. lcd_put_u8str_P(PSTR("---"));
  540. lcd.write('%');
  541. }
  542. #endif // HAS_PRINT_PROGRESS
  543. #if ENABLED(LCD_PROGRESS_BAR)
  544. void MarlinUI::draw_progress_bar(const uint8_t percent) {
  545. if (!PanelDetected) return;
  546. if (fb == &framebuffer[0] + LCD_WIDTH * 2) { // For status screen
  547. lcd.write('%'); lcd.write(percent);
  548. }
  549. else { // For progress bar test
  550. lcd.setCursor(LCD_WIDTH / 2 - 2, MIDDLE_Y);
  551. lcd.print(i16tostr3rj(percent)); lcd.write('%');
  552. lcd.print_line();
  553. lcd.setCursor(0, MIDDLE_Y + 1);
  554. lcd.write('%'); lcd.write(percent);
  555. lcd.print_line();
  556. }
  557. }
  558. #endif // LCD_PROGRESS_BAR
  559. void MarlinUI::draw_status_message(const bool blink) {
  560. if (!PanelDetected) return;
  561. lcd.setCursor(0, 3);
  562. #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
  563. // Alternate Status message and Filament display
  564. if (ELAPSED(millis(), next_filament_display)) {
  565. lcd_put_u8str_P(PSTR("Dia "));
  566. lcd.print(ftostr12ns(filament_width_meas));
  567. lcd_put_u8str_P(PSTR(" V"));
  568. lcd.print(i16tostr3rj(100.0 * (
  569. parser.volumetric_enabled
  570. ? planner.volumetric_area_nominal / planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
  571. : planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
  572. )
  573. ));
  574. lcd.write('%');
  575. return;
  576. }
  577. #endif // FILAMENT_LCD_DISPLAY && SDSUPPORT
  578. // Get the UTF8 character count of the string
  579. uint8_t slen = utf8_strlen(status_message);
  580. #if ENABLED(STATUS_MESSAGE_SCROLLING)
  581. static bool last_blink = false;
  582. // If the string fits into the LCD, just print it and do not scroll it
  583. if (slen <= LCD_WIDTH) {
  584. // The string isn't scrolling and may not fill the screen
  585. lcd_put_u8str(status_message);
  586. // Fill the rest with spaces
  587. while (slen < LCD_WIDTH) { lcd.write(' '); ++slen; }
  588. }
  589. else {
  590. // String is larger than the available space in screen.
  591. // Get a pointer to the next valid UTF8 character
  592. // and the string remaining length
  593. uint8_t rlen;
  594. const char *stat = status_and_len(rlen);
  595. lcd_put_u8str_max(stat, LCD_WIDTH); // The string leaves space
  596. // If the remaining string doesn't completely fill the screen
  597. if (rlen < LCD_WIDTH) {
  598. uint8_t chars = LCD_WIDTH - rlen; // Amount of space left in characters
  599. lcd.write(' '); // Always at 1+ spaces left, draw a space
  600. if (--chars) { // Draw a second space if there's room
  601. lcd.write(' ');
  602. if (--chars) { // Draw a third space if there's room
  603. lcd.write(' ');
  604. if (--chars)
  605. lcd_put_u8str_max(status_message, chars); // Print a second copy of the message
  606. }
  607. }
  608. }
  609. if (last_blink != blink) {
  610. last_blink = blink;
  611. advance_status_scroll();
  612. }
  613. }
  614. #else
  615. UNUSED(blink);
  616. // Just print the string to the LCD
  617. lcd_put_u8str_max(status_message, LCD_WIDTH);
  618. // Fill the rest with spaces if there are missing spaces
  619. while (slen < LCD_WIDTH) {
  620. lcd.write(' ');
  621. ++slen;
  622. }
  623. #endif
  624. }
  625. /**
  626. Possible status screens:
  627. Equal to 20x10 text LCD
  628. |X 000 Y 000 Z 000.00|
  629. |FR100% SD100% C--:--|
  630. | Progress bar line |
  631. |Status message |
  632. | |
  633. | HE BED FAN |
  634. | ttc ttc % | ttc - current temperature
  635. | tts tts %%% | tts - set temperature, %%% - percent for FAN
  636. | ICO ICO ICO ICO | ICO - icon 48x48, placed in 2 text lines
  637. | ICO ICO ICO ICO | ICO
  638. or
  639. |X 000 Y 000 Z 000.00|
  640. |FR100% SD100% C--:--|
  641. | Progress bar line |
  642. |Status message |
  643. | |
  644. |HE1 HE2 HE3 BED ICO|
  645. |ttc ttc ttc ttc ICO|
  646. |tts tts tts tts %%%|
  647. |ICO ICO ICO ICO ICO|
  648. |ICO ICO ICO ICO ICO|
  649. or
  650. |X 000 Y 000 Z 000.00|
  651. |FR100% SD100% C--:--|
  652. | Progress bar line |
  653. |Status message |
  654. | |
  655. |COOL FLOW ILAZ CUTT |
  656. | ttc L mA RPM |
  657. | tts f.f aaa rr.rK|
  658. | ICO ICO ICO ICO |
  659. | ICO ICO ICO ICO |
  660. or
  661. Equal to 24x10 text LCD
  662. |X 000 Y 000 Z 000.00 |
  663. |FR100% SD100% C--:--|
  664. | Progress bar line |
  665. |Status message |
  666. | |
  667. |HE1 HE2 HE3 BED FAN |
  668. |ttc ttc ttc ttc % |
  669. |tts tts tts tts %%% |
  670. |ICO ICO ICO ICO ICO ICO|
  671. |ICO ICO ICO ICO ICO ICO|
  672. */
  673. void MarlinUI::draw_status_screen() {
  674. if (!PanelDetected) return;
  675. const bool blink = get_blink();
  676. lcd.clear_buffer();
  677. //
  678. // Line 1 - XYZ coordinates
  679. //
  680. lcd.setCursor(0, 0);
  681. const xyz_pos_t lpos = current_position.asLogical();
  682. _draw_axis_value(X_AXIS, ftostr4sign(lpos.x), blink); lcd.write(' ');
  683. _draw_axis_value(Y_AXIS, ftostr4sign(lpos.y), blink); lcd.write(' ');
  684. _draw_axis_value(Z_AXIS, ftostr52sp(lpos.z), blink);
  685. #if HAS_LEVELING && !HAS_HEATED_BED
  686. lcd.write(planner.leveling_active || blink ? '_' : ' ');
  687. #endif
  688. //
  689. // Line 2 - feedrate, , time
  690. //
  691. lcd.setCursor(0, 1);
  692. lcd_put_u8str_P(PSTR("FR")); lcd.print(i16tostr3rj(feedrate_percentage)); lcd.write('%');
  693. #if BOTH(SDSUPPORT, HAS_PRINT_PROGRESS)
  694. lcd.setCursor(LCD_WIDTH / 2 - 3, 1);
  695. _draw_print_progress();
  696. #endif
  697. char buffer[10];
  698. duration_t elapsed = print_job_timer.duration();
  699. uint8_t len = elapsed.toDigital(buffer);
  700. lcd.setCursor((LCD_WIDTH - 1) - len, 1);
  701. lcd.write(LCD_STR_CLOCK[0]); lcd.print(buffer);
  702. //
  703. // Line 3 - progressbar
  704. //
  705. lcd.setCursor(0, 2);
  706. #if ENABLED(LCD_PROGRESS_BAR)
  707. draw_progress_bar(_get_progress());
  708. #else
  709. lcd.write('%'); lcd.write(0);
  710. #endif
  711. //
  712. // Line 4 - Status Message (which may be a Filament display)
  713. //
  714. draw_status_message(blink);
  715. //
  716. // Line 5
  717. //
  718. #if HOTENDS <= 1 || (HOTENDS <= 2 && !HAS_HEATED_BED)
  719. #if DUAL_MIXING_EXTRUDER
  720. lcd.setCursor(0, 4);
  721. // Two-component mix / gradient instead of XY
  722. char mixer_messages[12];
  723. const char *mix_label;
  724. #if ENABLED(GRADIENT_MIX)
  725. if (mixer.gradient.enabled) {
  726. mixer.update_mix_from_gradient();
  727. mix_label = "Gr";
  728. }
  729. else
  730. #endif
  731. {
  732. mixer.update_mix_from_vtool();
  733. mix_label = "Mx";
  734. }
  735. sprintf_P(mixer_messages, PSTR("%s %d;%d%% "), mix_label, int(mixer.mix[0]), int(mixer.mix[1]));
  736. lcd_put_u8str(mixer_messages);
  737. #endif
  738. #endif
  739. //
  740. // Line 6..8 Temperatures, FAN for printer or Cooler, Flowmetter, Ampermeter, Cutter for laser/spindle
  741. //
  742. #if HAS_HOTEND
  743. #if HOTENDS < 2
  744. _draw_heater_status(H_E0, "HE", blink); // Hotend Temperature
  745. #else
  746. _draw_heater_status(H_E0, "HE1", blink); // Hotend 1 Temperature
  747. _draw_heater_status(H_E1, "HE2", blink); // Hotend 2 Temperature
  748. #if HOTENDS > 2
  749. _draw_heater_status(H_E2, "HE3", blink); // Hotend 3 Temperature
  750. #endif
  751. #endif
  752. #if HAS_HEATED_BED
  753. #if HAS_LEVELING
  754. _draw_heater_status(H_BED, (planner.leveling_active && blink ? "___" : "BED"), blink);
  755. #else
  756. _draw_heater_status(H_BED, "BED", blink);
  757. #endif
  758. #endif
  759. #if HAS_FAN
  760. uint16_t spd = thermalManager.fan_speed[0];
  761. #if ENABLED(ADAPTIVE_FAN_SLOWING)
  762. if (!blink) spd = thermalManager.scaledFanSpeed(0, spd);
  763. #endif
  764. uint16_t per = thermalManager.pwmToPercent(spd);
  765. #if HOTENDS < 2
  766. #define FANX 11
  767. #else
  768. #define FANX 17
  769. #endif
  770. lcd.setCursor(FANX, 5); lcd_put_u8str_P(PSTR("FAN"));
  771. lcd.setCursor(FANX + 1, 6); lcd.write('%');
  772. lcd.setCursor(FANX, 7);
  773. lcd.print(i16tostr3rj(per));
  774. if (TERN0(HAS_FAN0, thermalManager.fan_speed[0]) || TERN0(HAS_FAN1, thermalManager.fan_speed[1]) || TERN0(HAS_FAN2, thermalManager.fan_speed[2]))
  775. picBits |= ICON_FAN;
  776. else
  777. picBits &= ~ICON_FAN;
  778. #endif // HAS_FAN
  779. #else
  780. TERN_(HAS_COOLER, _draw_cooler_status(blink));
  781. TERN_(LASER_COOLANT_FLOW_METER, _draw_flowmeter_status());
  782. TERN_(I2C_AMMETER, _draw_ammeter_status());
  783. TERN_(HAS_CUTTER, _draw_cutter_status());
  784. #endif
  785. //
  786. // Line 9, 10 - icons
  787. //
  788. lcd.print_screen();
  789. }
  790. #if HAS_LCD_MENU
  791. #include "../menu/menu.h"
  792. #if ENABLED(ADVANCED_PAUSE_FEATURE)
  793. void MarlinUI::draw_hotend_status(const uint8_t row, const uint8_t extruder) {
  794. if (!PanelDetected) return;
  795. lcd.setCursor((LCD_WIDTH - 14) / 2, row + 1);
  796. lcd.write(LCD_STR_THERMOMETER[0]); lcd_put_u8str_P(PSTR(" E")); lcd.write('1' + extruder); lcd.write(' ');
  797. lcd.print(i16tostr3rj(thermalManager.wholeDegHotend(extruder))); lcd.write(LCD_STR_DEGREE[0]); lcd.write('/');
  798. lcd.print(i16tostr3rj(thermalManager.degTargetHotend(extruder))); lcd.write(LCD_STR_DEGREE[0]);
  799. lcd.print_line();
  800. }
  801. #endif
  802. // Draw a static item with no left-right margin required. Centered by default.
  803. void MenuItem_static::draw(const uint8_t row, PGM_P const pstr, const uint8_t style/*=SS_DEFAULT*/, const char * const valstr/*=nullptr*/) {
  804. if (!PanelDetected) return;
  805. uint8_t n = LCD_WIDTH;
  806. lcd.setCursor(0, row);
  807. if ((style & SS_CENTER) && !valstr) {
  808. int8_t pad = (LCD_WIDTH - utf8_strlen_P(pstr)) / 2;
  809. while (--pad >= 0) { lcd.write(' '); n--; }
  810. }
  811. n = lcd_put_u8str_ind_P(pstr, itemIndex, itemString, n);
  812. if (valstr) n -= lcd_put_u8str_max(valstr, n);
  813. for (; n; --n) lcd.write(' ');
  814. lcd.print_line();
  815. }
  816. // Draw a generic menu item with pre_char (if selected) and post_char
  817. void MenuItemBase::_draw(const bool sel, const uint8_t row, PGM_P const pstr, const char pre_char, const char post_char) {
  818. if (!PanelDetected) return;
  819. lcd.setCursor(0, row);
  820. lcd.write(sel ? pre_char : ' ');
  821. uint8_t n = lcd_put_u8str_ind_P(pstr, itemIndex, itemString, LCD_WIDTH - 2);
  822. for (; n; --n) lcd.write(' ');
  823. lcd.write(post_char);
  824. lcd.print_line();
  825. }
  826. // Draw a menu item with a (potentially) editable value
  827. void MenuEditItemBase::draw(const bool sel, const uint8_t row, PGM_P const pstr, const char * const data, const bool pgm) {
  828. if (!PanelDetected) return;
  829. const uint8_t vlen = data ? (pgm ? utf8_strlen_P(data) : utf8_strlen(data)) : 0;
  830. lcd.setCursor(0, row);
  831. lcd.write(sel ? LCD_STR_ARROW_RIGHT[0] : ' ');
  832. uint8_t n = lcd_put_u8str_ind_P(pstr, itemIndex, itemString, LCD_WIDTH - 2 - vlen);
  833. if (vlen) {
  834. lcd.write(':');
  835. for (; n; --n) lcd.write(' ');
  836. if (pgm) lcd_put_u8str_P(data); else lcd_put_u8str(data);
  837. }
  838. lcd.print_line();
  839. }
  840. // Low-level draw_edit_screen can be used to draw an edit screen from anyplace
  841. // This line moves to the last line of the screen for UBL plot screen on the panel side
  842. void MenuEditItemBase::draw_edit_screen(PGM_P const pstr, const char * const value/*=nullptr*/) {
  843. if (!PanelDetected) return;
  844. ui.encoder_direction_normal();
  845. const uint8_t y = TERN0(AUTO_BED_LEVELING_UBL, ui.external_control) ? LCD_HEIGHT - 1 : MIDDLE_Y;
  846. lcd.setCursor(0, y);
  847. lcd.write(COLOR_EDIT);
  848. lcd_put_u8str_P(pstr);
  849. if (value) {
  850. lcd.write(':');
  851. lcd.setCursor((LCD_WIDTH - 1) - (utf8_strlen(value) + 1), y); // Right-justified, padded by spaces
  852. lcd.write(' '); // Overwrite char if value gets shorter
  853. lcd.print(value);
  854. lcd.write(' ');
  855. lcd.print_line();
  856. }
  857. }
  858. // The Select Screen presents a prompt and two "buttons"
  859. void MenuItem_confirm::draw_select_screen(PGM_P const yes, PGM_P const no, const bool yesno, PGM_P const pref, const char * const string, PGM_P const suff) {
  860. if (!PanelDetected) return;
  861. ui.draw_select_screen_prompt(pref, string, suff);
  862. lcd.setCursor(0, MIDDLE_Y);
  863. lcd.write(COLOR_EDIT);
  864. lcd.write(yesno ? ' ' : '['); lcd_put_u8str_P(no); lcd.write(yesno ? ' ' : ']');
  865. lcd.setCursor(LCD_WIDTH - utf8_strlen_P(yes) - 3, MIDDLE_Y);
  866. lcd.write(yesno ? '[' : ' '); lcd_put_u8str_P(yes); lcd.write(yesno ? ']' : ' ');
  867. lcd.print_line();
  868. }
  869. #if ENABLED(SDSUPPORT)
  870. void MenuItem_sdbase::draw(const bool sel, const uint8_t row, PGM_P const, CardReader &theCard, const bool isDir) {
  871. if (!PanelDetected) return;
  872. lcd.setCursor(0, row);
  873. lcd.write(sel ? LCD_STR_ARROW_RIGHT[0] : ' ');
  874. constexpr uint8_t maxlen = LCD_WIDTH - 2;
  875. uint8_t n = maxlen - lcd_put_u8str_max(ui.scrolled_filename(theCard, maxlen, row, sel), maxlen);
  876. for (; n; --n) lcd.write(' ');
  877. lcd.write(isDir ? LCD_STR_FOLDER[0] : ' ');
  878. lcd.print_line();
  879. }
  880. #endif // SDSUPPORT
  881. #if ENABLED(LCD_HAS_STATUS_INDICATORS)
  882. void MarlinUI::update_indicators() {}
  883. #endif // LCD_HAS_STATUS_INDICATORS
  884. #if ENABLED(AUTO_BED_LEVELING_UBL)
  885. /**
  886. * Map screen:
  887. * |/---------\ (00,00) |
  888. * || . . . . | X:000.00|
  889. * || . . . . | Y:000.00|
  890. * || . . . . | Z:00.000|
  891. * || . . . . | |
  892. * || . . . . | |
  893. * || . . . . | |
  894. * |+---------/ |
  895. * | |
  896. * |____________________|
  897. */
  898. void MarlinUI::ubl_plot(const uint8_t x_plot, const uint8_t y_plot) {
  899. if (!PanelDetected) return;
  900. #define _LCD_W_POS 12
  901. lcd.clear_buffer();
  902. //print only top left corner. All frame with grid points will be printed by panel
  903. lcd.setCursor(0, 0);
  904. *fb++ = TLC; //top left corner - marker for plot parameters
  905. *fb = (GRID_MAX_POINTS_X << 4) + GRID_MAX_POINTS_Y; //set mesh size
  906. // Print plot position
  907. lcd.setCursor(_LCD_W_POS, 0);
  908. *fb++ = '('; lcd.print(i16tostr3left(x_plot));
  909. *fb++ = ','; lcd.print(i16tostr3left(y_plot)); *fb = ')';
  910. // Show all values
  911. lcd.setCursor(_LCD_W_POS, 1); lcd_put_u8str_P(PSTR("X:"));
  912. lcd.print(ftostr52(LOGICAL_X_POSITION(pgm_read_float(&ubl._mesh_index_to_xpos[x_plot]))));
  913. lcd.setCursor(_LCD_W_POS, 2); lcd_put_u8str_P(PSTR("Y:"));
  914. lcd.print(ftostr52(LOGICAL_Y_POSITION(pgm_read_float(&ubl._mesh_index_to_ypos[y_plot]))));
  915. // Show the location value
  916. lcd.setCursor(_LCD_W_POS, 3); lcd_put_u8str_P(PSTR("Z:"));
  917. if (!isnan(ubl.z_values[x_plot][y_plot]))
  918. lcd.print(ftostr43sign(ubl.z_values[x_plot][y_plot]));
  919. else
  920. lcd_put_u8str_P(PSTR(" -----"));
  921. center_text_P(GET_TEXT(MSG_UBL_FINE_TUNE_MESH), 8);
  922. lcd.print_screen();
  923. }
  924. #endif // AUTO_BED_LEVELING_UBL
  925. #endif // HAS_LCD_MENU
  926. #endif // IS_TFTGLCD_PANEL