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.cpp 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218
  1. /**
  2. * Marlin 3D Printer Firmware
  3. * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. #include "../inc/MarlinConfigPre.h"
  23. // These displays all share the MarlinUI class
  24. #if HAS_SPI_LCD || ENABLED(MALYAN_LCD) || ENABLED(EXTENSIBLE_UI)
  25. #include "ultralcd.h"
  26. #if ENABLED(SDSUPPORT)
  27. #include "../sd/cardreader.h"
  28. #endif
  29. MarlinUI ui;
  30. #endif
  31. #if HAS_SPI_LCD
  32. #if HAS_GRAPHICAL_LCD
  33. #include "dogm/ultralcd_DOGM.h"
  34. #endif
  35. #include "lcdprint.h"
  36. #include "../sd/cardreader.h"
  37. #include "../module/temperature.h"
  38. #include "../module/planner.h"
  39. #include "../module/printcounter.h"
  40. #include "../module/motion.h"
  41. #include "../gcode/queue.h"
  42. #include "../Marlin.h"
  43. #if ENABLED(POWER_LOSS_RECOVERY)
  44. #include "../feature/power_loss_recovery.h"
  45. #endif
  46. #if ENABLED(AUTO_BED_LEVELING_UBL)
  47. #include "../feature/bedlevel/bedlevel.h"
  48. #endif
  49. #if DISABLED(LCD_USE_I2C_BUZZER)
  50. #include "../libs/buzzer.h"
  51. #endif
  52. #if HAS_ENCODER_ACTION
  53. volatile uint8_t MarlinUI::buttons;
  54. #if ENABLED(LCD_HAS_SLOW_BUTTONS)
  55. volatile uint8_t MarlinUI::slow_buttons;
  56. #endif
  57. #endif
  58. #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
  59. uint8_t lcd_sd_status;
  60. #endif
  61. #if ENABLED(STATUS_MESSAGE_SCROLLING)
  62. uint8_t MarlinUI::status_scroll_offset; // = 0
  63. #if LONG_FILENAME_LENGTH > CHARSIZE * 2 * (LCD_WIDTH)
  64. #define MAX_MESSAGE_LENGTH LONG_FILENAME_LENGTH
  65. #else
  66. #define MAX_MESSAGE_LENGTH CHARSIZE * 2 * (LCD_WIDTH)
  67. #endif
  68. #else
  69. #define MAX_MESSAGE_LENGTH CHARSIZE * (LCD_WIDTH)
  70. #endif
  71. #if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS
  72. bool MarlinUI::defer_return_to_status;
  73. #endif
  74. char MarlinUI::status_message[MAX_MESSAGE_LENGTH + 1];
  75. uint8_t MarlinUI::lcd_status_update_delay = 1; // First update one loop delayed
  76. uint8_t MarlinUI::status_message_level; // = 0
  77. #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
  78. millis_t MarlinUI::next_filament_display; // = 0
  79. #endif
  80. #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
  81. uint8_t MarlinUI::progress_bar_percent; // = 0
  82. #endif
  83. millis_t next_button_update_ms;
  84. #if HAS_GRAPHICAL_LCD
  85. bool MarlinUI::drawing_screen, MarlinUI::first_page; // = false
  86. #endif
  87. // Encoder Handling
  88. #if HAS_ENCODER_ACTION
  89. uint32_t MarlinUI::encoderPosition;
  90. volatile int8_t encoderDiff; // Updated in update_buttons, added to encoderPosition every LCD update
  91. #endif
  92. #if HAS_LCD_MENU
  93. #include "menu/menu.h"
  94. #include "../sd/cardreader.h"
  95. #if ENABLED(SDSUPPORT)
  96. #if ENABLED(SCROLL_LONG_FILENAMES)
  97. uint8_t MarlinUI::filename_scroll_pos, MarlinUI::filename_scroll_max;
  98. #endif
  99. const char * const MarlinUI::scrolled_filename(CardReader &theCard, const uint8_t maxlen, uint8_t hash, const bool doScroll) {
  100. const char *outstr = theCard.longest_filename();
  101. if (theCard.longFilename[0]) {
  102. #if ENABLED(SCROLL_LONG_FILENAMES)
  103. if (doScroll) {
  104. for (uint8_t l = FILENAME_LENGTH; l--;)
  105. hash = ((hash << 1) | (hash >> 7)) ^ theCard.filename[l]; // rotate, xor
  106. static uint8_t filename_scroll_hash;
  107. if (filename_scroll_hash != hash) { // If the hash changed...
  108. filename_scroll_hash = hash; // Save the new hash
  109. filename_scroll_max = MAX(0, utf8_strlen(theCard.longFilename) - maxlen); // Update the scroll limit
  110. filename_scroll_pos = 0; // Reset scroll to the start
  111. lcd_status_update_delay = 8; // Don't scroll right away
  112. }
  113. outstr += filename_scroll_pos;
  114. }
  115. #else
  116. theCard.longFilename[maxlen] = '\0'; // cutoff at screen edge
  117. #endif
  118. }
  119. return outstr;
  120. }
  121. #endif
  122. screenFunc_t MarlinUI::currentScreen; // Initialized in CTOR
  123. #if ENABLED(ENCODER_RATE_MULTIPLIER)
  124. bool MarlinUI::encoderRateMultiplierEnabled;
  125. millis_t MarlinUI::lastEncoderMovementMillis = 0;
  126. void MarlinUI::enable_encoder_multiplier(const bool onoff) {
  127. encoderRateMultiplierEnabled = onoff;
  128. lastEncoderMovementMillis = 0;
  129. }
  130. #endif
  131. #if ENABLED(REVERSE_MENU_DIRECTION)
  132. int8_t MarlinUI::encoderDirection = 1;
  133. #endif
  134. bool MarlinUI::lcd_clicked;
  135. float move_menu_scale;
  136. bool MarlinUI::use_click() {
  137. const bool click = lcd_clicked;
  138. lcd_clicked = false;
  139. return click;
  140. }
  141. #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION)
  142. bool MarlinUI::external_control; // = false
  143. void MarlinUI::wait_for_release() {
  144. while (button_pressed()) safe_delay(50);
  145. safe_delay(50);
  146. }
  147. #endif
  148. #endif
  149. void MarlinUI::init() {
  150. init_lcd();
  151. #if HAS_DIGITAL_ENCODER
  152. #if BUTTON_EXISTS(EN1)
  153. SET_INPUT_PULLUP(BTN_EN1);
  154. #endif
  155. #if BUTTON_EXISTS(EN2)
  156. SET_INPUT_PULLUP(BTN_EN2);
  157. #endif
  158. #if BUTTON_EXISTS(ENC)
  159. SET_INPUT_PULLUP(BTN_ENC);
  160. #endif
  161. #if ENABLED(REPRAPWORLD_KEYPAD) && DISABLED(ADC_KEYPAD)
  162. SET_OUTPUT(SHIFT_CLK);
  163. OUT_WRITE(SHIFT_LD, HIGH);
  164. SET_INPUT_PULLUP(SHIFT_OUT);
  165. #endif
  166. #if BUTTON_EXISTS(UP)
  167. SET_INPUT(BTN_UP);
  168. #endif
  169. #if BUTTON_EXISTS(DWN)
  170. SET_INPUT(BTN_DWN);
  171. #endif
  172. #if BUTTON_EXISTS(LFT)
  173. SET_INPUT(BTN_LFT);
  174. #endif
  175. #if BUTTON_EXISTS(RT)
  176. SET_INPUT(BTN_RT);
  177. #endif
  178. #else // !HAS_DIGITAL_ENCODER
  179. #if ENABLED(SR_LCD_2W_NL) // Non latching 2 wire shift register
  180. SET_OUTPUT(SR_DATA_PIN);
  181. SET_OUTPUT(SR_CLK_PIN);
  182. #elif defined(SHIFT_CLK)
  183. SET_OUTPUT(SHIFT_CLK);
  184. OUT_WRITE(SHIFT_LD, HIGH);
  185. OUT_WRITE(SHIFT_EN, LOW);
  186. SET_INPUT_PULLUP(SHIFT_OUT);
  187. #endif // SR_LCD_2W_NL
  188. #endif // !HAS_DIGITAL_ENCODER
  189. #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
  190. SET_INPUT_PULLUP(SD_DETECT_PIN);
  191. lcd_sd_status = 2; // UNKNOWN
  192. #endif
  193. #if HAS_ENCODER_ACTION && ENABLED(LCD_HAS_SLOW_BUTTONS)
  194. slow_buttons = 0;
  195. #endif
  196. update_buttons();
  197. #if HAS_ENCODER_ACTION
  198. encoderDiff = 0;
  199. #endif
  200. }
  201. bool MarlinUI::get_blink() {
  202. static uint8_t blink = 0;
  203. static millis_t next_blink_ms = 0;
  204. millis_t ms = millis();
  205. if (ELAPSED(ms, next_blink_ms)) {
  206. blink ^= 0xFF;
  207. next_blink_ms = ms + 1000 - (LCD_UPDATE_INTERVAL) / 2;
  208. }
  209. return blink != 0;
  210. }
  211. ////////////////////////////////////////////
  212. ///////////// Keypad Handling //////////////
  213. ////////////////////////////////////////////
  214. #if ENABLED(REPRAPWORLD_KEYPAD)
  215. volatile uint8_t MarlinUI::buttons_reprapworld_keypad;
  216. #if DISABLED(ADC_KEYPAD) && HAS_LCD_MENU
  217. void lcd_move_x();
  218. void lcd_move_y();
  219. void lcd_move_z();
  220. void _reprapworld_keypad_move(const AxisEnum axis, const int16_t dir) {
  221. move_menu_scale = REPRAPWORLD_KEYPAD_MOVE_STEP;
  222. encoderPosition = dir;
  223. switch (axis) {
  224. case X_AXIS: lcd_move_x(); break;
  225. case Y_AXIS: lcd_move_y(); break;
  226. case Z_AXIS: lcd_move_z();
  227. default: break;
  228. }
  229. }
  230. #endif
  231. bool MarlinUI::handle_keypad() {
  232. #if ENABLED(ADC_KEYPAD)
  233. #define ADC_MIN_KEY_DELAY 100
  234. if (buttons_reprapworld_keypad) {
  235. #if HAS_ENCODER_ACTION
  236. refresh(LCDVIEW_REDRAW_NOW);
  237. if (encoderDirection == -1) { // side effect which signals we are inside a menu
  238. #if HAS_LCD_MENU
  239. if (RRK(EN_REPRAPWORLD_KEYPAD_DOWN)) encoderPosition -= ENCODER_STEPS_PER_MENU_ITEM;
  240. else if (RRK(EN_REPRAPWORLD_KEYPAD_UP)) encoderPosition += ENCODER_STEPS_PER_MENU_ITEM;
  241. else if (RRK(EN_REPRAPWORLD_KEYPAD_LEFT)) { MenuItem_back::action(); quick_feedback(); }
  242. else if (RRK(EN_REPRAPWORLD_KEYPAD_RIGHT)) { return_to_status(); quick_feedback(); }
  243. #endif
  244. }
  245. else if (RRK(EN_REPRAPWORLD_KEYPAD_DOWN)) encoderPosition += ENCODER_PULSES_PER_STEP;
  246. else if (RRK(EN_REPRAPWORLD_KEYPAD_UP)) encoderPosition -= ENCODER_PULSES_PER_STEP;
  247. else if (RRK(EN_REPRAPWORLD_KEYPAD_RIGHT)) encoderPosition = 0;
  248. #endif
  249. next_button_update_ms = millis() + ADC_MIN_KEY_DELAY;
  250. return true;
  251. }
  252. #else // !ADC_KEYPAD
  253. static uint8_t keypad_debounce = 0;
  254. if (!RRK( EN_REPRAPWORLD_KEYPAD_F1 | EN_REPRAPWORLD_KEYPAD_F2
  255. | EN_REPRAPWORLD_KEYPAD_F3 | EN_REPRAPWORLD_KEYPAD_DOWN
  256. | EN_REPRAPWORLD_KEYPAD_RIGHT | EN_REPRAPWORLD_KEYPAD_MIDDLE
  257. | EN_REPRAPWORLD_KEYPAD_UP | EN_REPRAPWORLD_KEYPAD_LEFT )
  258. ) {
  259. if (keypad_debounce > 0) keypad_debounce--;
  260. }
  261. else if (!keypad_debounce) {
  262. keypad_debounce = 2;
  263. const bool homed = all_axes_homed();
  264. #if HAS_LCD_MENU
  265. if (RRK(EN_REPRAPWORLD_KEYPAD_MIDDLE)) goto_screen(menu_move);
  266. #if DISABLED(DELTA) && Z_HOME_DIR == -1
  267. if (RRK(EN_REPRAPWORLD_KEYPAD_F2)) _reprapworld_keypad_move(Z_AXIS, 1);
  268. #endif
  269. if (homed) {
  270. #if ENABLED(DELTA) || Z_HOME_DIR != -1
  271. if (RRK(EN_REPRAPWORLD_KEYPAD_F2)) _reprapworld_keypad_move(Z_AXIS, 1);
  272. #endif
  273. if (RRK(EN_REPRAPWORLD_KEYPAD_F3)) _reprapworld_keypad_move(Z_AXIS, -1);
  274. if (RRK(EN_REPRAPWORLD_KEYPAD_LEFT)) _reprapworld_keypad_move(X_AXIS, -1);
  275. if (RRK(EN_REPRAPWORLD_KEYPAD_RIGHT)) _reprapworld_keypad_move(X_AXIS, 1);
  276. if (RRK(EN_REPRAPWORLD_KEYPAD_DOWN)) _reprapworld_keypad_move(Y_AXIS, 1);
  277. if (RRK(EN_REPRAPWORLD_KEYPAD_UP)) _reprapworld_keypad_move(Y_AXIS, -1);
  278. }
  279. #endif // HAS_LCD_MENU
  280. if (!homed && RRK(EN_REPRAPWORLD_KEYPAD_F1)) enqueue_and_echo_commands_P(PSTR("G28"));
  281. return true;
  282. }
  283. #endif // !ADC_KEYPAD
  284. return false;
  285. }
  286. #endif // REPRAPWORLD_KEYPAD
  287. /**
  288. * Status Screen
  289. *
  290. * This is very display-dependent, so the lcd implementation draws this.
  291. */
  292. #if ENABLED(LCD_PROGRESS_BAR)
  293. millis_t MarlinUI::progress_bar_ms; // = 0
  294. #if PROGRESS_MSG_EXPIRE > 0
  295. millis_t MarlinUI::expire_status_ms; // = 0
  296. #endif
  297. #endif
  298. #if HAS_PRINT_PROGRESS
  299. uint8_t MarlinUI::get_progress() {
  300. #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
  301. uint8_t &progress = progress_bar_percent;
  302. #else
  303. uint8_t progress = 0;
  304. #endif
  305. #if ENABLED(SDSUPPORT)
  306. if (IS_SD_PRINTING()) progress = card.percentDone();
  307. #endif
  308. return progress;
  309. }
  310. #endif
  311. void MarlinUI::status_screen() {
  312. #if HAS_LCD_MENU
  313. encoder_direction_normal();
  314. ENCODER_RATE_MULTIPLY(false);
  315. #endif
  316. #if ENABLED(LCD_PROGRESS_BAR)
  317. //
  318. // HD44780 implements the following message blinking and
  319. // message expiration because Status Line and Progress Bar
  320. // share the same line on the display.
  321. //
  322. millis_t ms = millis();
  323. // If the message will blink rather than expire...
  324. #if DISABLED(PROGRESS_MSG_ONCE)
  325. if (ELAPSED(ms, progress_bar_ms + PROGRESS_BAR_MSG_TIME + PROGRESS_BAR_BAR_TIME))
  326. progress_bar_ms = ms;
  327. #endif
  328. #if PROGRESS_MSG_EXPIRE > 0
  329. // Handle message expire
  330. if (expire_status_ms > 0) {
  331. // Expire the message if a job is active and the bar has ticks
  332. if (get_progress() > 2 && !print_job_timer.isPaused()) {
  333. if (ELAPSED(ms, expire_status_ms)) {
  334. status_message[0] = '\0';
  335. expire_status_ms = 0;
  336. }
  337. }
  338. else {
  339. // Defer message expiration before bar appears
  340. // and during any pause (not just SD)
  341. expire_status_ms += LCD_UPDATE_INTERVAL;
  342. }
  343. }
  344. #endif // PROGRESS_MSG_EXPIRE
  345. #endif // LCD_PROGRESS_BAR
  346. #if HAS_LCD_MENU
  347. if (use_click()) {
  348. #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
  349. next_filament_display = millis() + 5000UL; // Show status message for 5s
  350. #endif
  351. goto_screen(menu_main);
  352. init_lcd(); // May revive the LCD if static electricity killed it
  353. return;
  354. }
  355. #endif // HAS_LCD_MENU
  356. #if ENABLED(ULTIPANEL_FEEDMULTIPLY)
  357. const int16_t new_frm = feedrate_percentage + (int32_t)encoderPosition;
  358. // Dead zone at 100% feedrate
  359. if ((feedrate_percentage < 100 && new_frm > 100) || (feedrate_percentage > 100 && new_frm < 100)) {
  360. feedrate_percentage = 100;
  361. encoderPosition = 0;
  362. }
  363. else if (feedrate_percentage == 100) {
  364. if ((int32_t)encoderPosition > ENCODER_FEEDRATE_DEADZONE) {
  365. feedrate_percentage += (int32_t)encoderPosition - (ENCODER_FEEDRATE_DEADZONE);
  366. encoderPosition = 0;
  367. }
  368. else if ((int32_t)encoderPosition < -(ENCODER_FEEDRATE_DEADZONE)) {
  369. feedrate_percentage += (int32_t)encoderPosition + ENCODER_FEEDRATE_DEADZONE;
  370. encoderPosition = 0;
  371. }
  372. }
  373. else {
  374. feedrate_percentage = new_frm;
  375. encoderPosition = 0;
  376. }
  377. feedrate_percentage = constrain(feedrate_percentage, 10, 999);
  378. #endif // ULTIPANEL_FEEDMULTIPLY
  379. draw_status_screen();
  380. }
  381. void MarlinUI::kill_screen(PGM_P lcd_msg) {
  382. init();
  383. setalertstatusPGM(lcd_msg);
  384. draw_kill_screen();
  385. }
  386. void MarlinUI::quick_feedback(const bool clear_buttons/*=true*/) {
  387. #if HAS_LCD_MENU
  388. refresh();
  389. #endif
  390. #if HAS_ENCODER_ACTION
  391. if (clear_buttons) buttons = 0;
  392. next_button_update_ms = millis() + 500;
  393. #else
  394. UNUSED(clear_buttons);
  395. #endif
  396. // Buzz and wait. The delay is needed for buttons to settle!
  397. buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
  398. #if HAS_LCD_MENU
  399. #if ENABLED(LCD_USE_I2C_BUZZER)
  400. delay(10);
  401. #elif PIN_EXISTS(BEEPER)
  402. for (int8_t i = 5; i--;) { buzzer.tick(); delay(2); }
  403. #endif
  404. #endif
  405. }
  406. ////////////////////////////////////////////
  407. /////////////// Manual Move ////////////////
  408. ////////////////////////////////////////////
  409. #if HAS_LCD_MENU
  410. extern bool no_reentry; // Flag to prevent recursion into menu handlers
  411. int8_t manual_move_axis = (int8_t)NO_AXIS;
  412. millis_t manual_move_start_time = 0;
  413. #if IS_KINEMATIC
  414. bool MarlinUI::processing_manual_move = false;
  415. float manual_move_offset = 0;
  416. #endif
  417. #if E_MANUAL > 1
  418. int8_t MarlinUI::manual_move_e_index = 0;
  419. #endif
  420. /**
  421. * If the most recent manual move hasn't been fed to the planner yet,
  422. * and the planner can accept one, send a move immediately.
  423. */
  424. void MarlinUI::manage_manual_move() {
  425. if (processing_manual_move) return;
  426. if (manual_move_axis != (int8_t)NO_AXIS && ELAPSED(millis(), manual_move_start_time) && !planner.is_full()) {
  427. #if IS_KINEMATIC
  428. const float old_feedrate = feedrate_mm_s;
  429. feedrate_mm_s = MMM_TO_MMS(manual_feedrate_mm_m[manual_move_axis]);
  430. #if EXTRUDERS > 1
  431. const int8_t old_extruder = active_extruder;
  432. if (manual_move_axis == E_AXIS) active_extruder = manual_move_e_index;
  433. #endif
  434. // Set movement on a single axis
  435. set_destination_from_current();
  436. destination[manual_move_axis] += manual_move_offset;
  437. // Reset for the next move
  438. manual_move_offset = 0;
  439. manual_move_axis = (int8_t)NO_AXIS;
  440. // DELTA and SCARA machines use segmented moves, which could fill the planner during the call to
  441. // move_to_destination. This will cause idle() to be called, which can then call this function while the
  442. // previous invocation is being blocked. Modifications to manual_move_offset shouldn't be made while
  443. // processing_manual_move is true or the planner will get out of sync.
  444. processing_manual_move = true;
  445. prepare_move_to_destination(); // will call set_current_from_destination()
  446. processing_manual_move = false;
  447. feedrate_mm_s = old_feedrate;
  448. #if EXTRUDERS > 1
  449. active_extruder = old_extruder;
  450. #endif
  451. #else
  452. planner.buffer_line(current_position, MMM_TO_MMS(manual_feedrate_mm_m[manual_move_axis]), manual_move_axis == E_AXIS ? manual_move_e_index : active_extruder);
  453. manual_move_axis = (int8_t)NO_AXIS;
  454. #endif
  455. }
  456. }
  457. #endif // HAS_LCD_MENU
  458. /**
  459. * Update the LCD, read encoder buttons, etc.
  460. * - Read button states
  461. * - Check the SD Card slot state
  462. * - Act on RepRap World keypad input
  463. * - Update the encoder position
  464. * - Apply acceleration to the encoder position
  465. * - Do refresh(LCDVIEW_CALL_REDRAW_NOW) on controller events
  466. * - Reset the Info Screen timeout if there's any input
  467. * - Update status indicators, if any
  468. *
  469. * Run the current LCD menu handler callback function:
  470. * - Call the handler only if lcdDrawUpdate != LCDVIEW_NONE
  471. * - Before calling the handler, LCDVIEW_CALL_NO_REDRAW => LCDVIEW_NONE
  472. * - Call the menu handler. Menu handlers should do the following:
  473. * - If a value changes, set lcdDrawUpdate to LCDVIEW_REDRAW_NOW and draw the value
  474. * (Encoder events automatically set lcdDrawUpdate for you.)
  475. * - if (should_draw()) { redraw }
  476. * - Before exiting the handler set lcdDrawUpdate to:
  477. * - LCDVIEW_CLEAR_CALL_REDRAW to clear screen and set LCDVIEW_CALL_REDRAW_NEXT.
  478. * - LCDVIEW_REDRAW_NOW to draw now (including remaining stripes).
  479. * - LCDVIEW_CALL_REDRAW_NEXT to draw now and get LCDVIEW_REDRAW_NOW on the next loop.
  480. * - LCDVIEW_CALL_NO_REDRAW to draw now and get LCDVIEW_NONE on the next loop.
  481. * - NOTE: For graphical displays menu handlers may be called 2 or more times per loop,
  482. * so don't change lcdDrawUpdate without considering this.
  483. *
  484. * After the menu handler callback runs (or not):
  485. * - Clear the LCD if lcdDrawUpdate == LCDVIEW_CLEAR_CALL_REDRAW
  486. * - Update lcdDrawUpdate for the next loop (i.e., move one state down, usually)
  487. *
  488. * This function is only called from the main thread.
  489. */
  490. LCDViewAction MarlinUI::lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
  491. bool MarlinUI::detected() {
  492. return
  493. #if (ENABLED(LCD_I2C_TYPE_MCP23017) || ENABLED(LCD_I2C_TYPE_MCP23008)) && defined(DETECT_DEVICE)
  494. lcd.LcdDetected() == 1
  495. #else
  496. true
  497. #endif
  498. ;
  499. }
  500. void MarlinUI::update() {
  501. static uint16_t max_display_update_time = 0;
  502. static millis_t next_lcd_update_ms;
  503. #if HAS_LCD_MENU
  504. #if LCD_TIMEOUT_TO_STATUS
  505. static millis_t return_to_status_ms = 0;
  506. #endif
  507. // Handle any queued Move Axis motion
  508. manage_manual_move();
  509. // Update button states for button_pressed(), etc.
  510. // If the state changes the next update may be delayed 300-500ms.
  511. update_buttons();
  512. // If the action button is pressed...
  513. static bool wait_for_unclick; // = 0
  514. if (!external_control && button_pressed()) {
  515. if (!wait_for_unclick) { // If not waiting for a debounce release:
  516. wait_for_unclick = true; // - Set debounce flag to ignore continous clicks
  517. lcd_clicked = !wait_for_user && !no_reentry; // - Keep the click if not waiting for a user-click
  518. wait_for_user = false; // - Any click clears wait for user
  519. quick_feedback(); // - Always make a click sound
  520. }
  521. }
  522. else wait_for_unclick = false;
  523. #if BUTTON_EXISTS(BACK)
  524. if (LCD_BACK_CLICKED()) {
  525. quick_feedback();
  526. goto_previous_screen();
  527. }
  528. #endif
  529. #endif // HAS_LCD_MENU
  530. #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT)
  531. const uint8_t sd_status = (uint8_t)IS_SD_INSERTED();
  532. if (sd_status != lcd_sd_status && detected()) {
  533. uint8_t old_sd_status = lcd_sd_status; // prevent re-entry to this block!
  534. lcd_sd_status = sd_status;
  535. if (sd_status) {
  536. safe_delay(500); // Some boards need a delay to get settled
  537. card.initsd();
  538. if (old_sd_status == 2)
  539. card.beginautostart(); // Initial boot
  540. else
  541. setstatusPGM(PSTR(MSG_SD_INSERTED));
  542. }
  543. else {
  544. card.release();
  545. if (old_sd_status != 2) setstatusPGM(PSTR(MSG_SD_REMOVED));
  546. }
  547. refresh();
  548. init_lcd(); // May revive the LCD if static electricity killed it
  549. }
  550. #endif // SDSUPPORT && SD_DETECT_PIN
  551. const millis_t ms = millis();
  552. if (ELAPSED(ms, next_lcd_update_ms)
  553. #if HAS_GRAPHICAL_LCD
  554. || drawing_screen
  555. #endif
  556. ) {
  557. next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL;
  558. #if ENABLED(LCD_HAS_STATUS_INDICATORS)
  559. update_indicators();
  560. #endif
  561. #if HAS_ENCODER_ACTION
  562. #if ENABLED(LCD_HAS_SLOW_BUTTONS)
  563. slow_buttons = read_slow_buttons(); // Buttons that take too long to read in interrupt context
  564. #endif
  565. #if ENABLED(REPRAPWORLD_KEYPAD)
  566. if (
  567. #if ENABLED(ADC_KEYPAD)
  568. handle_keypad()
  569. #else
  570. handle_keypad()
  571. #endif
  572. ) {
  573. #if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS
  574. return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
  575. #endif
  576. }
  577. #endif
  578. const float abs_diff = ABS(encoderDiff);
  579. const bool encoderPastThreshold = (abs_diff >= (ENCODER_PULSES_PER_STEP));
  580. if (encoderPastThreshold || lcd_clicked) {
  581. if (encoderPastThreshold) {
  582. #if HAS_LCD_MENU && ENABLED(ENCODER_RATE_MULTIPLIER)
  583. int32_t encoderMultiplier = 1;
  584. if (encoderRateMultiplierEnabled) {
  585. const float encoderMovementSteps = abs_diff / (ENCODER_PULSES_PER_STEP);
  586. if (lastEncoderMovementMillis) {
  587. // Note that the rate is always calculated between two passes through the
  588. // loop and that the abs of the encoderDiff value is tracked.
  589. const float encoderStepRate = encoderMovementSteps / float(ms - lastEncoderMovementMillis) * 1000;
  590. if (encoderStepRate >= ENCODER_100X_STEPS_PER_SEC) encoderMultiplier = 100;
  591. else if (encoderStepRate >= ENCODER_10X_STEPS_PER_SEC) encoderMultiplier = 10;
  592. #if ENABLED(ENCODER_RATE_MULTIPLIER_DEBUG)
  593. SERIAL_ECHO_START();
  594. SERIAL_ECHOPAIR("Enc Step Rate: ", encoderStepRate);
  595. SERIAL_ECHOPAIR(" Multiplier: ", encoderMultiplier);
  596. SERIAL_ECHOPAIR(" ENCODER_10X_STEPS_PER_SEC: ", ENCODER_10X_STEPS_PER_SEC);
  597. SERIAL_ECHOPAIR(" ENCODER_100X_STEPS_PER_SEC: ", ENCODER_100X_STEPS_PER_SEC);
  598. SERIAL_EOL();
  599. #endif
  600. }
  601. lastEncoderMovementMillis = ms;
  602. } // encoderRateMultiplierEnabled
  603. #else
  604. constexpr int32_t encoderMultiplier = 1;
  605. #endif // ENCODER_RATE_MULTIPLIER
  606. encoderPosition += (encoderDiff * encoderMultiplier) / (ENCODER_PULSES_PER_STEP);
  607. encoderDiff = 0;
  608. }
  609. #if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS
  610. return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
  611. #endif
  612. refresh(LCDVIEW_REDRAW_NOW);
  613. }
  614. #endif
  615. // This runs every ~100ms when idling often enough.
  616. // Instead of tracking changes just redraw the Status Screen once per second.
  617. if (on_status_screen() && !lcd_status_update_delay--) {
  618. lcd_status_update_delay = 9
  619. #if HAS_GRAPHICAL_LCD
  620. + 3
  621. #endif
  622. ;
  623. max_display_update_time--;
  624. refresh(LCDVIEW_REDRAW_NOW);
  625. }
  626. #if HAS_LCD_MENU && ENABLED(SCROLL_LONG_FILENAMES)
  627. // If scrolling of long file names is enabled and we are in the sd card menu,
  628. // cause a refresh to occur until all the text has scrolled into view.
  629. if (currentScreen == menu_sdcard && filename_scroll_pos < filename_scroll_max && !lcd_status_update_delay--) {
  630. lcd_status_update_delay = 6;
  631. refresh(LCDVIEW_REDRAW_NOW);
  632. filename_scroll_pos++;
  633. #if LCD_TIMEOUT_TO_STATUS
  634. return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
  635. #endif
  636. }
  637. #endif
  638. // then we want to use 1/2 of the time only.
  639. uint16_t bbr2 = planner.block_buffer_runtime() >> 1;
  640. if ((should_draw() || drawing_screen) && (!bbr2 || bbr2 > max_display_update_time)) {
  641. // Change state of drawing flag between screen updates
  642. if (!drawing_screen) switch (lcdDrawUpdate) {
  643. case LCDVIEW_CALL_NO_REDRAW:
  644. refresh(LCDVIEW_NONE);
  645. break;
  646. case LCDVIEW_CLEAR_CALL_REDRAW:
  647. case LCDVIEW_CALL_REDRAW_NEXT:
  648. refresh(LCDVIEW_REDRAW_NOW);
  649. case LCDVIEW_REDRAW_NOW: // set above, or by a handler through LCDVIEW_CALL_REDRAW_NEXT
  650. case LCDVIEW_NONE:
  651. break;
  652. } // switch
  653. #if ENABLED(ADC_KEYPAD)
  654. buttons_reprapworld_keypad = 0;
  655. #endif
  656. #if HAS_GRAPHICAL_LCD
  657. #if ENABLED(LIGHTWEIGHT_UI)
  658. const bool in_status = on_status_screen(),
  659. do_u8g_loop = !in_status;
  660. lcd_in_status(in_status);
  661. if (in_status) status_screen();
  662. #else
  663. constexpr bool do_u8g_loop = true;
  664. #endif
  665. if (do_u8g_loop) {
  666. if (!drawing_screen) { // If not already drawing pages
  667. u8g.firstPage(); // Start the first page
  668. drawing_screen = first_page = true; // Flag as drawing pages
  669. }
  670. set_font(FONT_MENU); // Setup font for every page draw
  671. u8g.setColorIndex(1); // And reset the color
  672. run_current_screen(); // Draw and process the current screen
  673. first_page = false;
  674. // The screen handler can clear drawing_screen for an action that changes the screen.
  675. // If still drawing and there's another page, update max-time and return now.
  676. // The nextPage will already be set up on the next call.
  677. if (drawing_screen && (drawing_screen = u8g.nextPage())) {
  678. NOLESS(max_display_update_time, millis() - ms);
  679. return;
  680. }
  681. }
  682. #else
  683. run_current_screen();
  684. #endif
  685. #if HAS_LCD_MENU
  686. lcd_clicked = false;
  687. #endif
  688. // Keeping track of the longest time for an individual LCD update.
  689. // Used to do screen throttling when the planner starts to fill up.
  690. NOLESS(max_display_update_time, millis() - ms);
  691. }
  692. #if HAS_LCD_MENU && LCD_TIMEOUT_TO_STATUS
  693. // Return to Status Screen after a timeout
  694. if (on_status_screen() || defer_return_to_status)
  695. return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS;
  696. else if (ELAPSED(ms, return_to_status_ms))
  697. return_to_status();
  698. #endif
  699. // Change state of drawing flag between screen updates
  700. if (!drawing_screen) switch (lcdDrawUpdate) {
  701. case LCDVIEW_CLEAR_CALL_REDRAW:
  702. clear_lcd(); break;
  703. case LCDVIEW_REDRAW_NOW:
  704. refresh(LCDVIEW_NONE);
  705. case LCDVIEW_NONE:
  706. case LCDVIEW_CALL_REDRAW_NEXT:
  707. case LCDVIEW_CALL_NO_REDRAW:
  708. default: break;
  709. } // switch
  710. } // ELAPSED(ms, next_lcd_update_ms)
  711. }
  712. #if ENABLED(ADC_KEYPAD)
  713. typedef struct {
  714. uint16_t ADCKeyValueMin, ADCKeyValueMax;
  715. uint8_t ADCKeyNo;
  716. } _stADCKeypadTable_;
  717. static const _stADCKeypadTable_ stADCKeyTable[] PROGMEM = {
  718. // VALUE_MIN, VALUE_MAX, KEY
  719. { 4000, 4096, 1 + BLEN_REPRAPWORLD_KEYPAD_F1 }, // F1
  720. { 4000, 4096, 1 + BLEN_REPRAPWORLD_KEYPAD_F2 }, // F2
  721. { 4000, 4096, 1 + BLEN_REPRAPWORLD_KEYPAD_F3 }, // F3
  722. { 300, 500, 1 + BLEN_REPRAPWORLD_KEYPAD_LEFT }, // LEFT
  723. { 1900, 2200, 1 + BLEN_REPRAPWORLD_KEYPAD_RIGHT }, // RIGHT
  724. { 570, 870, 1 + BLEN_REPRAPWORLD_KEYPAD_UP }, // UP
  725. { 2670, 2870, 1 + BLEN_REPRAPWORLD_KEYPAD_DOWN }, // DOWN
  726. { 1150, 1450, 1 + BLEN_REPRAPWORLD_KEYPAD_MIDDLE }, // ENTER
  727. };
  728. uint8_t get_ADC_keyValue(void) {
  729. if (thermalManager.ADCKey_count >= 16) {
  730. const uint16_t currentkpADCValue = thermalManager.current_ADCKey_raw >> 2;
  731. thermalManager.current_ADCKey_raw = 0;
  732. thermalManager.ADCKey_count = 0;
  733. if (currentkpADCValue < 4000)
  734. for (uint8_t i = 0; i < ADC_KEY_NUM; i++) {
  735. const uint16_t lo = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMin),
  736. hi = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMax);
  737. if (WITHIN(currentkpADCValue, lo, hi)) return pgm_read_byte(&stADCKeyTable[i].ADCKeyNo);
  738. }
  739. }
  740. return 0;
  741. }
  742. #endif
  743. #if HAS_ENCODER_ACTION
  744. #if DISABLED(ADC_KEYPAD) && (ENABLED(REPRAPWORLD_KEYPAD) || !HAS_DIGITAL_ENCODER)
  745. /**
  746. * Setup Rotary Encoder Bit Values (for two pin encoders to indicate movement)
  747. * These values are independent of which pins are used for EN_A and EN_B indications
  748. * The rotary encoder part is also independent to the chipset used for the LCD
  749. */
  750. #define GET_SHIFT_BUTTON_STATES(DST) \
  751. uint8_t new_##DST = 0; \
  752. WRITE(SHIFT_LD, LOW); \
  753. WRITE(SHIFT_LD, HIGH); \
  754. for (int8_t i = 0; i < 8; i++) { \
  755. new_##DST >>= 1; \
  756. if (READ(SHIFT_OUT)) SBI(new_##DST, 7); \
  757. WRITE(SHIFT_CLK, HIGH); \
  758. WRITE(SHIFT_CLK, LOW); \
  759. } \
  760. DST = ~new_##DST; //invert it, because a pressed switch produces a logical 0
  761. #endif
  762. #if defined(EN_A) && defined(EN_B)
  763. #define encrot0 0
  764. #define encrot1 2
  765. #define encrot2 3
  766. #define encrot3 1
  767. #endif
  768. /**
  769. * Read encoder buttons from the hardware registers
  770. * Warning: This function is called from interrupt context!
  771. */
  772. void MarlinUI::update_buttons() {
  773. static uint8_t lastEncoderBits;
  774. const millis_t now = millis();
  775. if (ELAPSED(now, next_button_update_ms)) {
  776. #if HAS_DIGITAL_ENCODER
  777. uint8_t newbutton = 0;
  778. #if BUTTON_EXISTS(EN1)
  779. if (BUTTON_PRESSED(EN1)) newbutton |= EN_A;
  780. #endif
  781. #if BUTTON_EXISTS(EN2)
  782. if (BUTTON_PRESSED(EN2)) newbutton |= EN_B;
  783. #endif
  784. #if BUTTON_EXISTS(ENC)
  785. if (BUTTON_PRESSED(ENC)) newbutton |= EN_C;
  786. #endif
  787. #if BUTTON_EXISTS(BACK)
  788. if (BUTTON_PRESSED(BACK)) newbutton |= EN_D;
  789. #endif
  790. //
  791. // Directional buttons
  792. //
  793. #if LCD_HAS_DIRECTIONAL_BUTTONS
  794. const int8_t pulses = (ENCODER_PULSES_PER_STEP) * encoderDirection;
  795. if (false) {
  796. // for the else-ifs below
  797. }
  798. #if BUTTON_EXISTS(UP)
  799. else if (BUTTON_PRESSED(UP)) {
  800. encoderDiff = (ENCODER_STEPS_PER_MENU_ITEM) * pulses;
  801. next_button_update_ms = now + 300;
  802. }
  803. #endif
  804. #if BUTTON_EXISTS(DWN)
  805. else if (BUTTON_PRESSED(DWN)) {
  806. encoderDiff = -(ENCODER_STEPS_PER_MENU_ITEM) * pulses;
  807. next_button_update_ms = now + 300;
  808. }
  809. #endif
  810. #if BUTTON_EXISTS(LFT)
  811. else if (BUTTON_PRESSED(LFT)) {
  812. encoderDiff = -pulses;
  813. next_button_update_ms = now + 300;
  814. }
  815. #endif
  816. #if BUTTON_EXISTS(RT)
  817. else if (BUTTON_PRESSED(RT)) {
  818. encoderDiff = pulses;
  819. next_button_update_ms = now + 300;
  820. }
  821. #endif
  822. #endif // LCD_HAS_DIRECTIONAL_BUTTONS
  823. #if ENABLED(ADC_KEYPAD)
  824. buttons = 0;
  825. if (buttons_reprapworld_keypad == 0) {
  826. uint8_t newbutton_reprapworld_keypad = get_ADC_keyValue();
  827. if (WITHIN(newbutton_reprapworld_keypad, 1, 8))
  828. buttons_reprapworld_keypad = _BV(newbutton_reprapworld_keypad - 1);
  829. }
  830. #else
  831. buttons = newbutton
  832. #if ENABLED(LCD_HAS_SLOW_BUTTONS)
  833. | slow_buttons
  834. #endif
  835. ;
  836. #if ENABLED(REPRAPWORLD_KEYPAD)
  837. GET_SHIFT_BUTTON_STATES(buttons_reprapworld_keypad);
  838. #endif
  839. #endif
  840. #else // !HAS_DIGITAL_ENCODER
  841. GET_SHIFT_BUTTON_STATES(buttons);
  842. #endif
  843. } // next_button_update_ms
  844. // Manage encoder rotation
  845. #define ENCODER_SPIN(_E1, _E2) switch (lastEncoderBits) { case _E1: encoderDiff += encoderDirection; break; case _E2: encoderDiff -= encoderDirection; }
  846. uint8_t enc = 0;
  847. if (buttons & EN_A) enc |= B01;
  848. if (buttons & EN_B) enc |= B10;
  849. if (enc != lastEncoderBits) {
  850. switch (enc) {
  851. case encrot0: ENCODER_SPIN(encrot3, encrot1); break;
  852. case encrot1: ENCODER_SPIN(encrot0, encrot2); break;
  853. case encrot2: ENCODER_SPIN(encrot1, encrot3); break;
  854. case encrot3: ENCODER_SPIN(encrot2, encrot0); break;
  855. }
  856. if (external_control) {
  857. #if ENABLED(AUTO_BED_LEVELING_UBL)
  858. ubl.encoder_diff = encoderDiff; // Make encoder rotation available to UBL G29 mesh editing.
  859. #endif
  860. encoderDiff = 0; // Hide the encoder event from the current screen handler.
  861. }
  862. lastEncoderBits = enc;
  863. }
  864. }
  865. #if ENABLED(LCD_HAS_SLOW_BUTTONS)
  866. uint8_t MarlinUI::read_slow_buttons() {
  867. #if ENABLED(LCD_I2C_TYPE_MCP23017)
  868. // Reading these buttons this is likely to be too slow to call inside interrupt context
  869. // so they are called during normal lcd_update
  870. uint8_t slow_bits = lcd.readButtons() << B_I2C_BTN_OFFSET;
  871. #if ENABLED(LCD_I2C_VIKI)
  872. if ((slow_bits & (B_MI | B_RI)) && PENDING(millis(), next_button_update_ms)) // LCD clicked
  873. slow_bits &= ~(B_MI | B_RI); // Disable LCD clicked buttons if screen is updated
  874. #endif // LCD_I2C_VIKI
  875. return slow_bits;
  876. #endif // LCD_I2C_TYPE_MCP23017
  877. }
  878. #endif // LCD_HAS_SLOW_BUTTONS
  879. #endif // HAS_ENCODER_ACTION
  880. ////////////////////////////////////////////
  881. /////////////// Status Line ////////////////
  882. ////////////////////////////////////////////
  883. void MarlinUI::finishstatus(const bool persist) {
  884. #if !(ENABLED(LCD_PROGRESS_BAR) && (PROGRESS_MSG_EXPIRE > 0))
  885. UNUSED(persist);
  886. #endif
  887. #if ENABLED(LCD_PROGRESS_BAR)
  888. progress_bar_ms = millis();
  889. #if PROGRESS_MSG_EXPIRE > 0
  890. expire_status_ms = persist ? 0 : progress_bar_ms + PROGRESS_MSG_EXPIRE;
  891. #endif
  892. #endif
  893. #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
  894. next_filament_display = millis() + 5000UL; // Show status message for 5s
  895. #endif
  896. #if ENABLED(STATUS_MESSAGE_SCROLLING)
  897. status_scroll_offset = 0;
  898. #endif
  899. refresh();
  900. }
  901. bool MarlinUI::has_status() { return (status_message[0] != '\0'); }
  902. void MarlinUI::setstatus(const char * const message, const bool persist) {
  903. if (status_message_level > 0) return;
  904. // Here we have a problem. The message is encoded in UTF8, so
  905. // arbitrarily cutting it will be a problem. We MUST be sure
  906. // that there is no cutting in the middle of a multibyte character!
  907. // Get a pointer to the null terminator
  908. const char* pend = message + strlen(message);
  909. // If length of supplied UTF8 string is greater than
  910. // our buffer size, start cutting whole UTF8 chars
  911. while ((pend - message) > MAX_MESSAGE_LENGTH) {
  912. --pend;
  913. while (!START_OF_UTF8_CHAR(*pend)) --pend;
  914. };
  915. // At this point, we have the proper cut point. Use it
  916. uint8_t maxLen = pend - message;
  917. strncpy(status_message, message, maxLen);
  918. status_message[maxLen] = '\0';
  919. finishstatus(persist);
  920. }
  921. #include <stdarg.h>
  922. void MarlinUI::status_printf_P(const uint8_t level, PGM_P const fmt, ...) {
  923. if (level < status_message_level) return;
  924. status_message_level = level;
  925. va_list args;
  926. va_start(args, fmt);
  927. vsnprintf_P(status_message, MAX_MESSAGE_LENGTH, fmt, args);
  928. va_end(args);
  929. finishstatus(level > 0);
  930. }
  931. void MarlinUI::setstatusPGM(PGM_P const message, int8_t level) {
  932. if (level < 0) level = status_message_level = 0;
  933. if (level < status_message_level) return;
  934. status_message_level = level;
  935. // Here we have a problem. The message is encoded in UTF8, so
  936. // arbitrarily cutting it will be a problem. We MUST be sure
  937. // that there is no cutting in the middle of a multibyte character!
  938. // Get a pointer to the null terminator
  939. PGM_P pend = message + strlen_P(message);
  940. // If length of supplied UTF8 string is greater than
  941. // our buffer size, start cutting whole UTF8 chars
  942. while ((pend - message) > MAX_MESSAGE_LENGTH) {
  943. --pend;
  944. while (!START_OF_UTF8_CHAR(pgm_read_byte(pend))) --pend;
  945. };
  946. // At this point, we have the proper cut point. Use it
  947. uint8_t maxLen = pend - message;
  948. strncpy_P(status_message, message, maxLen);
  949. status_message[maxLen] = '\0';
  950. finishstatus(level > 0);
  951. }
  952. void MarlinUI::setalertstatusPGM(PGM_P const message) {
  953. setstatusPGM(message, 1);
  954. #if HAS_LCD_MENU
  955. return_to_status();
  956. #endif
  957. }
  958. #endif // HAS_SPI_LCD
  959. #if HAS_SPI_LCD || ENABLED(EXTENSIBLE_UI)
  960. #include "../module/printcounter.h"
  961. /**
  962. * Reset the status message
  963. */
  964. void MarlinUI::reset_status() {
  965. static const char paused[] PROGMEM = MSG_PRINT_PAUSED;
  966. static const char printing[] PROGMEM = MSG_PRINTING;
  967. static const char welcome[] PROGMEM = WELCOME_MSG;
  968. PGM_P msg;
  969. if (print_job_timer.isPaused())
  970. msg = paused;
  971. #if ENABLED(SDSUPPORT)
  972. else if (IS_SD_PRINTING())
  973. return setstatus(card.longest_filename(), true);
  974. #endif
  975. else if (print_job_timer.isRunning())
  976. msg = printing;
  977. else
  978. msg = welcome;
  979. setstatusPGM(msg, -1);
  980. }
  981. #endif