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 32KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  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() {
  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 (!sound_on) 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. #if HAS_LCD_CONTRAST
  340. void MarlinUI::_set_contrast() { lcd.setContrast(contrast); }
  341. #endif
  342. static void center_text(FSTR_P const fstart, const uint8_t y) {
  343. const uint8_t len = utf8_strlen(fstart);
  344. lcd.setCursor(len < LCD_WIDTH ? (LCD_WIDTH - len) / 2 : 0, y);
  345. lcd_put_u8str(fstart);
  346. }
  347. #if ENABLED(SHOW_BOOTSCREEN)
  348. void MarlinUI::show_bootscreen() {
  349. if (!PanelDetected) return;
  350. //
  351. // Show the Marlin logo, splash line1, and splash line 2
  352. //
  353. uint8_t indent = (LCD_WIDTH - 8) / 2;
  354. // symbols 217 (bottom right corner) and 218 (top left corner) are using for letters in some languages
  355. // and they should be moved to beginning ASCII table as special symbols
  356. lcd.setCursor(indent, 0); lcd.write(TLC); lcd_put_u8str(F("------")); lcd.write(TRC);
  357. lcd.setCursor(indent, 1); lcd.write(LR); lcd_put_u8str(F("Marlin")); lcd.write(LR);
  358. lcd.setCursor(indent, 2); lcd.write(BLC); lcd_put_u8str(F("------")); lcd.write(BRC);
  359. center_text(F(SHORT_BUILD_VERSION), 3);
  360. center_text(F(MARLIN_WEBSITE_URL), 4);
  361. picBits = ICON_LOGO;
  362. lcd.print_screen();
  363. }
  364. void MarlinUI::bootscreen_completion(const millis_t sofar) {
  365. if ((BOOTSCREEN_TIMEOUT) > sofar) safe_delay((BOOTSCREEN_TIMEOUT) - sofar);
  366. }
  367. #endif // SHOW_BOOTSCREEN
  368. void MarlinUI::draw_kill_screen() {
  369. if (!PanelDetected) return;
  370. lcd.clear_buffer();
  371. lcd.setCursor(0, 3); lcd.write(COLOR_ERROR);
  372. lcd.setCursor((LCD_WIDTH - utf8_strlen(status_message)) / 2 + 1, 3);
  373. lcd_put_u8str(status_message);
  374. center_text(GET_TEXT_F(MSG_HALTED), 5);
  375. center_text(GET_TEXT_F(MSG_PLEASE_RESET), 6);
  376. lcd.print_screen();
  377. }
  378. //
  379. // Before homing, blink '123' <-> '???'.
  380. // Homed but unknown... '123' <-> ' '.
  381. // Homed and known, display constantly.
  382. //
  383. FORCE_INLINE void _draw_axis_value(const AxisEnum axis, const char *value, const bool blink) {
  384. lcd.write('X' + uint8_t(axis));
  385. if (blink)
  386. lcd.print(value);
  387. else if (axis_should_home(axis))
  388. while (const char c = *value++) lcd.write(c <= '.' ? c : '?');
  389. else if (NONE(HOME_AFTER_DEACTIVATE, DISABLE_REDUCED_ACCURACY_WARNING) && !axis_is_trusted(axis))
  390. lcd_put_u8str(axis == Z_AXIS ? F(" ") : F(" "));
  391. else
  392. lcd_put_u8str(value);
  393. }
  394. #if HAS_HOTEND || HAS_HEATED_BED
  395. FORCE_INLINE void _draw_heater_status(const heater_id_t heater_id, const char *prefix, const bool blink) {
  396. uint8_t pic_hot_bits;
  397. #if HAS_HEATED_BED
  398. const bool isBed = heater_id < 0;
  399. const celsius_t t1 = (isBed ? thermalManager.wholeDegBed() : thermalManager.wholeDegHotend(heater_id)),
  400. t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater_id));
  401. #else
  402. const celsius_t t1 = thermalManager.wholeDegHotend(heater_id), t2 = thermalManager.degTargetHotend(heater_id);
  403. #endif
  404. #if HOTENDS < 2
  405. if (heater_id == H_E0) {
  406. lcd.setCursor(2, 5); lcd.print(prefix); //HE
  407. lcd.setCursor(1, 6); lcd.print(i16tostr3rj(t1));
  408. lcd.setCursor(1, 7);
  409. }
  410. else {
  411. lcd.setCursor(6, 5); lcd.print(prefix); //BED
  412. lcd.setCursor(6, 6); lcd.print(i16tostr3rj(t1));
  413. lcd.setCursor(6, 7);
  414. }
  415. #else
  416. if (heater_id > H_BED) {
  417. lcd.setCursor(heater_id * 4, 5); lcd.print(prefix); // HE1 or HE2 or HE3
  418. lcd.setCursor(heater_id * 4, 6); lcd.print(i16tostr3rj(t1));
  419. lcd.setCursor(heater_id * 4, 7);
  420. }
  421. else {
  422. lcd.setCursor(13, 5); lcd.print(prefix); //BED
  423. lcd.setCursor(13, 6); lcd.print(i16tostr3rj(t1));
  424. lcd.setCursor(13, 7);
  425. }
  426. #endif // HOTENDS <= 1
  427. #if !HEATER_IDLE_HANDLER
  428. UNUSED(blink);
  429. #else
  430. if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
  431. lcd.write(' ');
  432. if (t2 >= 10) lcd.write(' ');
  433. if (t2 >= 100) lcd.write(' ');
  434. }
  435. else
  436. #endif // !HEATER_IDLE_HANDLER
  437. lcd.print(i16tostr3rj(t2));
  438. switch (heater_id) {
  439. case H_BED: pic_hot_bits = ICON_BED; break;
  440. case H_E0: pic_hot_bits = ICON_TEMP1; break;
  441. case H_E1: pic_hot_bits = ICON_TEMP2; break;
  442. case H_E2: pic_hot_bits = ICON_TEMP3;
  443. default: break;
  444. }
  445. if (t2) picBits |= pic_hot_bits;
  446. else picBits &= ~pic_hot_bits;
  447. if (t1 > 50) hotBits |= pic_hot_bits;
  448. else hotBits &= ~pic_hot_bits;
  449. if (hotBits) picBits |= ICON_HOT;
  450. else picBits &= ~ICON_HOT;
  451. }
  452. #endif // HAS_HOTEND || HAS_HEATED_BED
  453. #if HAS_COOLER
  454. FORCE_INLINE void _draw_cooler_status(const bool blink) {
  455. const celsius_t t2 = thermalManager.degTargetCooler();
  456. lcd.setCursor(0, 5); lcd_put_u8str(F("COOL"));
  457. lcd.setCursor(1, 6); lcd_put_u8str(i16tostr3rj(thermalManager.wholeDegCooler()));
  458. lcd.setCursor(1, 7);
  459. #if !HEATER_IDLE_HANDLER
  460. UNUSED(blink);
  461. #else
  462. if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
  463. lcd_put_wchar(' ');
  464. if (t2 >= 10) lcd_put_wchar(' ');
  465. if (t2 >= 100) lcd_put_wchar(' ');
  466. }
  467. else
  468. #endif
  469. lcd_put_u8str(i16tostr3left(t2));
  470. lcd_put_wchar(' ');
  471. if (t2 < 10) lcd_put_wchar(' ');
  472. if (t2) picBits |= ICON_TEMP1;
  473. else picBits &= ~ICON_TEMP1;
  474. }
  475. #endif // HAS_COOLER
  476. #if ENABLED(LASER_COOLANT_FLOW_METER)
  477. FORCE_INLINE void _draw_flowmeter_status() {
  478. lcd.setCursor(5, 5); lcd_put_u8str(F("FLOW"));
  479. lcd.setCursor(7, 6); lcd_put_wchar('L');
  480. lcd.setCursor(6, 7); lcd_put_u8str(ftostr11ns(cooler.flowrate));
  481. if (cooler.flowrate) picBits |= ICON_FAN;
  482. else picBits &= ~ICON_FAN;
  483. }
  484. #endif
  485. #if ENABLED(I2C_AMMETER)
  486. FORCE_INLINE void _draw_ammeter_status() {
  487. lcd.setCursor(10, 5); lcd_put_u8str(F("ILAZ"));
  488. ammeter.read();
  489. lcd.setCursor(11, 6);
  490. if (ammeter.current <= 0.999f)
  491. {
  492. lcd_put_u8str("mA");
  493. lcd.setCursor(10, 7);
  494. lcd_put_wchar(' '); lcd_put_u8str(ui16tostr3rj(uint16_t(ammeter.current * 1000 + 0.5f)));
  495. }
  496. else {
  497. lcd_put_u8str(" A");
  498. lcd.setCursor(10, 7);
  499. lcd_put_u8str(ftostr12ns(ammeter.current));
  500. }
  501. if (ammeter.current) picBits |= ICON_BED;
  502. else picBits &= ~ICON_BED;
  503. }
  504. #endif // I2C_AMMETER
  505. #if HAS_CUTTER
  506. FORCE_INLINE void _draw_cutter_status() {
  507. lcd.setCursor(15, 5); lcd_put_u8str(F("CUTT"));
  508. #if CUTTER_UNIT_IS(RPM)
  509. lcd.setCursor(16, 6); lcd_put_u8str(F("RPM"));
  510. lcd.setCursor(15, 7); lcd_put_u8str(ftostr31ns(float(cutter.unitPower) / 1000));
  511. lcd_put_wchar('K');
  512. #elif CUTTER_UNIT_IS(PERCENT)
  513. lcd.setCursor(17, 6); lcd_put_wchar('%');
  514. lcd.setCursor(18, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
  515. #else
  516. lcd.setCursor(17, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
  517. #endif
  518. if (cutter.unitPower) picBits |= ICON_HOT;
  519. else picBits &= ~ICON_HOT;
  520. }
  521. #endif // HAS_CUTTER
  522. #if HAS_PRINT_PROGRESS
  523. FORCE_INLINE void _draw_print_progress() {
  524. if (!PanelDetected) return;
  525. const uint8_t progress = ui._get_progress();
  526. #if ENABLED(SDSUPPORT)
  527. lcd_put_u8str(F("SD"));
  528. #elif ENABLED(LCD_SET_PROGRESS_MANUALLY)
  529. lcd_put_u8str(F("P:"));
  530. #endif
  531. if (progress)
  532. lcd.print(ui8tostr3rj(progress));
  533. else
  534. lcd_put_u8str(F("---"));
  535. lcd.write('%');
  536. }
  537. #endif // HAS_PRINT_PROGRESS
  538. #if ENABLED(LCD_PROGRESS_BAR)
  539. void MarlinUI::draw_progress_bar(const uint8_t percent) {
  540. if (!PanelDetected) return;
  541. if (fb == &framebuffer[0] + LCD_WIDTH * 2) { // For status screen
  542. lcd.write('%'); lcd.write(percent);
  543. }
  544. else { // For progress bar test
  545. lcd.setCursor(LCD_WIDTH / 2 - 2, MIDDLE_Y);
  546. lcd.print(i16tostr3rj(percent)); lcd.write('%');
  547. lcd.print_line();
  548. lcd.setCursor(0, MIDDLE_Y + 1);
  549. lcd.write('%'); lcd.write(percent);
  550. lcd.print_line();
  551. }
  552. }
  553. #endif // LCD_PROGRESS_BAR
  554. void MarlinUI::draw_status_message(const bool blink) {
  555. if (!PanelDetected) return;
  556. lcd.setCursor(0, 3);
  557. #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
  558. // Alternate Status message and Filament display
  559. if (ELAPSED(millis(), next_filament_display)) {
  560. lcd_put_u8str(F("Dia "));
  561. lcd.print(ftostr12ns(filament_width_meas));
  562. lcd_put_u8str(F(" V"));
  563. lcd.print(i16tostr3rj(100.0 * (
  564. parser.volumetric_enabled
  565. ? planner.volumetric_area_nominal / planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
  566. : planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
  567. )
  568. ));
  569. lcd.write('%');
  570. return;
  571. }
  572. #endif // FILAMENT_LCD_DISPLAY && SDSUPPORT
  573. // Get the UTF8 character count of the string
  574. uint8_t slen = utf8_strlen(status_message);
  575. #if ENABLED(STATUS_MESSAGE_SCROLLING)
  576. static bool last_blink = false;
  577. // If the string fits into the LCD, just print it and do not scroll it
  578. if (slen <= LCD_WIDTH) {
  579. // The string isn't scrolling and may not fill the screen
  580. lcd_put_u8str(status_message);
  581. // Fill the rest with spaces
  582. while (slen < LCD_WIDTH) { lcd.write(' '); ++slen; }
  583. }
  584. else {
  585. // String is larger than the available space in screen.
  586. // Get a pointer to the next valid UTF8 character
  587. // and the string remaining length
  588. uint8_t rlen;
  589. const char *stat = status_and_len(rlen);
  590. lcd_put_u8str_max(stat, LCD_WIDTH); // The string leaves space
  591. // If the remaining string doesn't completely fill the screen
  592. if (rlen < LCD_WIDTH) {
  593. uint8_t chars = LCD_WIDTH - rlen; // Amount of space left in characters
  594. lcd.write(' '); // Always at 1+ spaces left, draw a space
  595. if (--chars) { // Draw a second space if there's room
  596. lcd.write(' ');
  597. if (--chars) { // Draw a third space if there's room
  598. lcd.write(' ');
  599. if (--chars)
  600. lcd_put_u8str_max(status_message, chars); // Print a second copy of the message
  601. }
  602. }
  603. }
  604. if (last_blink != blink) {
  605. last_blink = blink;
  606. advance_status_scroll();
  607. }
  608. }
  609. #else
  610. UNUSED(blink);
  611. // Just print the string to the LCD
  612. lcd_put_u8str_max(status_message, LCD_WIDTH);
  613. // Fill the rest with spaces if there are missing spaces
  614. while (slen < LCD_WIDTH) {
  615. lcd.write(' ');
  616. ++slen;
  617. }
  618. #endif
  619. }
  620. /**
  621. Possible status screens:
  622. Equal to 20x10 text LCD
  623. |X 000 Y 000 Z 000.00|
  624. |FR100% SD100% C--:--|
  625. | Progress bar line |
  626. |Status message |
  627. | |
  628. | HE BED FAN |
  629. | ttc ttc % | ttc - current temperature
  630. | tts tts %%% | tts - set temperature, %%% - percent for FAN
  631. | ICO ICO ICO ICO | ICO - icon 48x48, placed in 2 text lines
  632. | ICO ICO ICO ICO | ICO
  633. or
  634. |X 000 Y 000 Z 000.00|
  635. |FR100% SD100% C--:--|
  636. | Progress bar line |
  637. |Status message |
  638. | |
  639. |HE1 HE2 HE3 BED ICO|
  640. |ttc ttc ttc ttc ICO|
  641. |tts tts tts tts %%%|
  642. |ICO ICO ICO ICO ICO|
  643. |ICO ICO ICO ICO ICO|
  644. or
  645. |X 000 Y 000 Z 000.00|
  646. |FR100% SD100% C--:--|
  647. | Progress bar line |
  648. |Status message |
  649. | |
  650. |COOL FLOW ILAZ CUTT |
  651. | ttc L mA RPM |
  652. | tts f.f aaa rr.rK|
  653. | ICO ICO ICO ICO |
  654. | ICO ICO ICO ICO |
  655. or
  656. Equal to 24x10 text LCD
  657. |X 000 Y 000 Z 000.00 |
  658. |FR100% SD100% C--:--|
  659. | Progress bar line |
  660. |Status message |
  661. | |
  662. |HE1 HE2 HE3 BED FAN |
  663. |ttc ttc ttc ttc % |
  664. |tts tts tts tts %%% |
  665. |ICO ICO ICO ICO ICO ICO|
  666. |ICO ICO ICO ICO ICO ICO|
  667. */
  668. void MarlinUI::draw_status_screen() {
  669. if (!PanelDetected) return;
  670. const bool blink = get_blink();
  671. lcd.clear_buffer();
  672. //
  673. // Line 1 - XYZ coordinates
  674. //
  675. lcd.setCursor(0, 0);
  676. const xyz_pos_t lpos = current_position.asLogical();
  677. _draw_axis_value(X_AXIS, ftostr4sign(lpos.x), blink); lcd.write(' ');
  678. _draw_axis_value(Y_AXIS, ftostr4sign(lpos.y), blink); lcd.write(' ');
  679. _draw_axis_value(Z_AXIS, ftostr52sp(lpos.z), blink);
  680. #if HAS_LEVELING && !HAS_HEATED_BED
  681. lcd.write(planner.leveling_active || blink ? '_' : ' ');
  682. #endif
  683. //
  684. // Line 2 - feedrate, , time
  685. //
  686. lcd.setCursor(0, 1);
  687. lcd_put_u8str(F("FR")); lcd.print(i16tostr3rj(feedrate_percentage)); lcd.write('%');
  688. #if BOTH(SDSUPPORT, HAS_PRINT_PROGRESS)
  689. lcd.setCursor(LCD_WIDTH / 2 - 3, 1);
  690. _draw_print_progress();
  691. #endif
  692. char buffer[10];
  693. duration_t elapsed = print_job_timer.duration();
  694. uint8_t len = elapsed.toDigital(buffer);
  695. lcd.setCursor((LCD_WIDTH - 1) - len, 1);
  696. lcd.write(LCD_STR_CLOCK[0]); lcd.print(buffer);
  697. //
  698. // Line 3 - progressbar
  699. //
  700. lcd.setCursor(0, 2);
  701. #if ENABLED(LCD_PROGRESS_BAR)
  702. draw_progress_bar(_get_progress());
  703. #else
  704. lcd.write('%'); lcd.write(0);
  705. #endif
  706. //
  707. // Line 4 - Status Message (which may be a Filament display)
  708. //
  709. draw_status_message(blink);
  710. //
  711. // Line 5
  712. //
  713. #if HOTENDS <= 1 || (HOTENDS <= 2 && !HAS_HEATED_BED)
  714. #if DUAL_MIXING_EXTRUDER
  715. lcd.setCursor(0, 4);
  716. // Two-component mix / gradient instead of XY
  717. char mixer_messages[12];
  718. const char *mix_label;
  719. #if ENABLED(GRADIENT_MIX)
  720. if (mixer.gradient.enabled) {
  721. mixer.update_mix_from_gradient();
  722. mix_label = "Gr";
  723. }
  724. else
  725. #endif
  726. {
  727. mixer.update_mix_from_vtool();
  728. mix_label = "Mx";
  729. }
  730. sprintf_P(mixer_messages, PSTR("%s %d;%d%% "), mix_label, int(mixer.mix[0]), int(mixer.mix[1]));
  731. lcd_put_u8str(mixer_messages);
  732. #endif
  733. #endif
  734. //
  735. // Line 6..8 Temperatures, FAN for printer or Cooler, Flowmetter, Ampermeter, Cutter for laser/spindle
  736. //
  737. #if HAS_HOTEND
  738. #if HOTENDS < 2
  739. _draw_heater_status(H_E0, "HE", blink); // Hotend Temperature
  740. #else
  741. _draw_heater_status(H_E0, "HE1", blink); // Hotend 1 Temperature
  742. _draw_heater_status(H_E1, "HE2", blink); // Hotend 2 Temperature
  743. #if HOTENDS > 2
  744. _draw_heater_status(H_E2, "HE3", blink); // Hotend 3 Temperature
  745. #endif
  746. #endif
  747. #if HAS_HEATED_BED
  748. #if HAS_LEVELING
  749. _draw_heater_status(H_BED, (planner.leveling_active && blink ? "___" : "BED"), blink);
  750. #else
  751. _draw_heater_status(H_BED, "BED", blink);
  752. #endif
  753. #endif
  754. #if HAS_FAN
  755. uint16_t spd = thermalManager.fan_speed[0];
  756. #if ENABLED(ADAPTIVE_FAN_SLOWING)
  757. if (!blink) spd = thermalManager.scaledFanSpeed(0, spd);
  758. #endif
  759. uint16_t per = thermalManager.pwmToPercent(spd);
  760. #if HOTENDS < 2
  761. #define FANX 11
  762. #else
  763. #define FANX 17
  764. #endif
  765. lcd.setCursor(FANX, 5); lcd_put_u8str(F("FAN"));
  766. lcd.setCursor(FANX + 1, 6); lcd.write('%');
  767. lcd.setCursor(FANX, 7);
  768. lcd.print(i16tostr3rj(per));
  769. if (TERN0(HAS_FAN0, thermalManager.fan_speed[0]) || TERN0(HAS_FAN1, thermalManager.fan_speed[1]) || TERN0(HAS_FAN2, thermalManager.fan_speed[2]))
  770. picBits |= ICON_FAN;
  771. else
  772. picBits &= ~ICON_FAN;
  773. #endif // HAS_FAN
  774. #else
  775. TERN_(HAS_COOLER, _draw_cooler_status(blink));
  776. TERN_(LASER_COOLANT_FLOW_METER, _draw_flowmeter_status());
  777. TERN_(I2C_AMMETER, _draw_ammeter_status());
  778. TERN_(HAS_CUTTER, _draw_cutter_status());
  779. #endif
  780. //
  781. // Line 9, 10 - icons
  782. //
  783. lcd.print_screen();
  784. }
  785. #if HAS_MARLINUI_MENU
  786. #include "../menu/menu.h"
  787. #if ENABLED(ADVANCED_PAUSE_FEATURE)
  788. void MarlinUI::draw_hotend_status(const uint8_t row, const uint8_t extruder) {
  789. if (!PanelDetected) return;
  790. lcd.setCursor((LCD_WIDTH - 14) / 2, row + 1);
  791. lcd.write(LCD_STR_THERMOMETER[0]); lcd_put_u8str(F(" E")); lcd.write('1' + extruder); lcd.write(' ');
  792. lcd.print(i16tostr3rj(thermalManager.wholeDegHotend(extruder))); lcd.write(LCD_STR_DEGREE[0]); lcd.write('/');
  793. lcd.print(i16tostr3rj(thermalManager.degTargetHotend(extruder))); lcd.write(LCD_STR_DEGREE[0]);
  794. lcd.print_line();
  795. }
  796. #endif
  797. // Draw a static item with no left-right margin required. Centered by default.
  798. void MenuItem_static::draw(const uint8_t row, FSTR_P const fstr, const uint8_t style/*=SS_DEFAULT*/, const char * const valstr/*=nullptr*/) {
  799. if (!PanelDetected) return;
  800. uint8_t n = LCD_WIDTH;
  801. lcd.setCursor(0, row);
  802. if ((style & SS_CENTER) && !valstr) {
  803. int8_t pad = (LCD_WIDTH - utf8_strlen(fstr)) / 2;
  804. while (--pad >= 0) { lcd.write(' '); n--; }
  805. }
  806. n = lcd_put_u8str_ind(fstr, itemIndex, itemString, n);
  807. if (valstr) n -= lcd_put_u8str_max(valstr, n);
  808. for (; n; --n) lcd.write(' ');
  809. lcd.print_line();
  810. }
  811. // Draw a generic menu item with pre_char (if selected) and post_char
  812. void MenuItemBase::_draw(const bool sel, const uint8_t row, FSTR_P const fstr, const char pre_char, const char post_char) {
  813. if (!PanelDetected) return;
  814. lcd.setCursor(0, row);
  815. lcd.write(sel ? pre_char : ' ');
  816. uint8_t n = lcd_put_u8str_ind(fstr, itemIndex, itemString, LCD_WIDTH - 2);
  817. for (; n; --n) lcd.write(' ');
  818. lcd.write(post_char);
  819. lcd.print_line();
  820. }
  821. // Draw a menu item with a (potentially) editable value
  822. void MenuEditItemBase::draw(const bool sel, const uint8_t row, FSTR_P const fstr, const char * const data, const bool pgm) {
  823. if (!PanelDetected) return;
  824. const uint8_t vlen = data ? (pgm ? utf8_strlen_P(data) : utf8_strlen(data)) : 0;
  825. lcd.setCursor(0, row);
  826. lcd.write(sel ? LCD_STR_ARROW_RIGHT[0] : ' ');
  827. uint8_t n = lcd_put_u8str_ind(fstr, itemIndex, itemString, LCD_WIDTH - 2 - vlen);
  828. if (vlen) {
  829. lcd.write(':');
  830. for (; n; --n) lcd.write(' ');
  831. if (pgm) lcd_put_u8str_P(data); else lcd_put_u8str(data);
  832. }
  833. lcd.print_line();
  834. }
  835. // Low-level draw_edit_screen can be used to draw an edit screen from anyplace
  836. // This line moves to the last line of the screen for UBL plot screen on the panel side
  837. void MenuEditItemBase::draw_edit_screen(FSTR_P const fstr, const char * const value/*=nullptr*/) {
  838. if (!PanelDetected) return;
  839. ui.encoder_direction_normal();
  840. const uint8_t y = TERN0(AUTO_BED_LEVELING_UBL, ui.external_control) ? LCD_HEIGHT - 1 : MIDDLE_Y;
  841. lcd.setCursor(0, y);
  842. lcd.write(COLOR_EDIT);
  843. lcd_put_u8str(fstr);
  844. if (value) {
  845. lcd.write(':');
  846. lcd.setCursor((LCD_WIDTH - 1) - (utf8_strlen(value) + 1), y); // Right-justified, padded by spaces
  847. lcd.write(' '); // Overwrite char if value gets shorter
  848. lcd.print(value);
  849. lcd.write(' ');
  850. lcd.print_line();
  851. }
  852. }
  853. // The Select Screen presents a prompt and two "buttons"
  854. void MenuItem_confirm::draw_select_screen(FSTR_P const yes, FSTR_P const no, const bool yesno, FSTR_P const pref, const char * const string, FSTR_P const suff) {
  855. if (!PanelDetected) return;
  856. ui.draw_select_screen_prompt(pref, string, suff);
  857. lcd.write(COLOR_EDIT);
  858. if (no) {
  859. lcd.setCursor(0, MIDDLE_Y);
  860. lcd.write(yesno ? ' ' : '['); lcd_put_u8str(no); lcd.write(yesno ? ' ' : ']');
  861. }
  862. if (yes) {
  863. lcd.setCursor(LCD_WIDTH - utf8_strlen(yes) - 3, MIDDLE_Y);
  864. lcd.write(yesno ? '[' : ' '); lcd_put_u8str(yes); lcd.write(yesno ? ']' : ' ');
  865. }
  866. lcd.print_line();
  867. }
  868. #if ENABLED(SDSUPPORT)
  869. void MenuItem_sdbase::draw(const bool sel, const uint8_t row, FSTR_P const, CardReader &theCard, const bool isDir) {
  870. if (!PanelDetected) return;
  871. lcd.setCursor(0, row);
  872. lcd.write(sel ? LCD_STR_ARROW_RIGHT[0] : ' ');
  873. constexpr uint8_t maxlen = LCD_WIDTH - 2;
  874. uint8_t n = maxlen - lcd_put_u8str_max(ui.scrolled_filename(theCard, maxlen, row, sel), maxlen);
  875. for (; n; --n) lcd.write(' ');
  876. lcd.write(isDir ? LCD_STR_FOLDER[0] : ' ');
  877. lcd.print_line();
  878. }
  879. #endif // SDSUPPORT
  880. #if ENABLED(LCD_HAS_STATUS_INDICATORS)
  881. void MarlinUI::update_indicators() {}
  882. #endif // LCD_HAS_STATUS_INDICATORS
  883. #if ENABLED(AUTO_BED_LEVELING_UBL)
  884. /**
  885. * Map screen:
  886. * |/---------\ (00,00) |
  887. * || . . . . | X:000.00|
  888. * || . . . . | Y:000.00|
  889. * || . . . . | Z:00.000|
  890. * || . . . . | |
  891. * || . . . . | |
  892. * || . . . . | |
  893. * |+---------/ |
  894. * | |
  895. * |____________________|
  896. */
  897. void MarlinUI::ubl_plot(const uint8_t x_plot, const uint8_t y_plot) {
  898. if (!PanelDetected) return;
  899. #define _LCD_W_POS 12
  900. lcd.clear_buffer();
  901. //print only top left corner. All frame with grid points will be printed by panel
  902. lcd.setCursor(0, 0);
  903. *fb++ = TLC; //top left corner - marker for plot parameters
  904. *fb = (GRID_MAX_POINTS_X << 4) + GRID_MAX_POINTS_Y; //set mesh size
  905. // Print plot position
  906. lcd.setCursor(_LCD_W_POS, 0);
  907. *fb++ = '('; lcd.print(i16tostr3left(x_plot));
  908. *fb++ = ','; lcd.print(i16tostr3left(y_plot)); *fb = ')';
  909. // Show all values
  910. lcd.setCursor(_LCD_W_POS, 1); lcd_put_u8str(F("X:"));
  911. lcd.print(ftostr52(LOGICAL_X_POSITION(pgm_read_float(&bedlevel._mesh_index_to_xpos[x_plot]))));
  912. lcd.setCursor(_LCD_W_POS, 2); lcd_put_u8str(F("Y:"));
  913. lcd.print(ftostr52(LOGICAL_Y_POSITION(pgm_read_float(&bedlevel._mesh_index_to_ypos[y_plot]))));
  914. // Show the location value
  915. lcd.setCursor(_LCD_W_POS, 3); lcd_put_u8str(F("Z:"));
  916. if (!isnan(bedlevel.z_values[x_plot][y_plot]))
  917. lcd.print(ftostr43sign(bedlevel.z_values[x_plot][y_plot]));
  918. else
  919. lcd_put_u8str(F(" -----"));
  920. center_text(GET_TEXT_F(MSG_UBL_FINE_TUNE_MESH), 8);
  921. lcd.print_screen();
  922. }
  923. #endif // AUTO_BED_LEVELING_UBL
  924. #endif // HAS_MARLINUI_MENU
  925. #endif // IS_TFTGLCD_PANEL