My Marlin configs for Fabrikator Mini and CTC i3 Pro B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

marlinui_TFTGLCD.cpp 32KB

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