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.

ultralcd_TFTGLCD.cpp 32KB

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