Subversion Repositories Tronxy-X3A-Marlin

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 ron 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
 
23
#ifndef ULTRALCD_IMPL_HD44780_H
24
#define ULTRALCD_IMPL_HD44780_H
25
 
26
/**
27
 * Implementation of the LCD display routines for a Hitachi HD44780 display.
28
 * These are the most common LCD character displays.
29
 */
30
 
31
#include "utility.h"
32
#include "duration_t.h"
33
 
34
#if ENABLED(AUTO_BED_LEVELING_UBL)
35
  #include "ubl.h"
36
 
37
  #if ENABLED(ULTIPANEL)
38
    #define ULTRA_X_PIXELS_PER_CHAR    5
39
    #define ULTRA_Y_PIXELS_PER_CHAR    8
40
    #define ULTRA_COLUMNS_FOR_MESH_MAP 7
41
    #define ULTRA_ROWS_FOR_MESH_MAP    4
42
 
43
    #define N_USER_CHARS    8
44
 
45
    #define TOP_LEFT      _BV(0)
46
    #define TOP_RIGHT     _BV(1)
47
    #define LOWER_LEFT    _BV(2)
48
    #define LOWER_RIGHT   _BV(3)
49
  #endif
50
#endif
51
 
52
extern volatile uint8_t buttons;  //an extended version of the last checked buttons in a bit array.
53
 
54
////////////////////////////////////
55
// Setup button and encode mappings for each panel (into 'buttons' variable
56
//
57
// This is just to map common functions (across different panels) onto the same
58
// macro name. The mapping is independent of whether the button is directly connected or
59
// via a shift/i2c register.
60
 
61
#if ENABLED(ULTIPANEL)
62
 
63
  //
64
  // Setup other button mappings of each panel
65
  //
66
  #if ENABLED(LCD_I2C_VIKI)
67
    #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
68
 
69
    // button and encoder bit positions within 'buttons'
70
    #define B_LE (BUTTON_LEFT   << B_I2C_BTN_OFFSET)    // The remaining normalized buttons are all read via I2C
71
    #define B_UP (BUTTON_UP     << B_I2C_BTN_OFFSET)
72
    #define B_MI (BUTTON_SELECT << B_I2C_BTN_OFFSET)
73
    #define B_DW (BUTTON_DOWN   << B_I2C_BTN_OFFSET)
74
    #define B_RI (BUTTON_RIGHT  << B_I2C_BTN_OFFSET)
75
 
76
    #undef LCD_CLICKED
77
    #if BUTTON_EXISTS(ENC)
78
      // the pause/stop/restart button is connected to BTN_ENC when used
79
      #define B_ST (EN_C)                            // Map the pause/stop/resume button into its normalized functional name
80
      #define LCD_CLICKED (buttons & (B_MI|B_RI|B_ST)) // pause/stop button also acts as click until we implement proper pause/stop.
81
    #else
82
      #define LCD_CLICKED (buttons & (B_MI|B_RI))
83
    #endif
84
 
85
    // I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
86
    #define LCD_HAS_SLOW_BUTTONS
87
 
88
  #elif ENABLED(LCD_I2C_PANELOLU2)
89
 
90
    #if !BUTTON_EXISTS(ENC) // Use I2C if not directly connected to a pin
91
 
92
      #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
93
 
94
      #define B_MI (PANELOLU2_ENCODER_C << B_I2C_BTN_OFFSET) // requires LiquidTWI2 library v1.2.3 or later
95
 
96
      #undef LCD_CLICKED
97
      #define LCD_CLICKED (buttons & B_MI)
98
 
99
      // I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
100
      #define LCD_HAS_SLOW_BUTTONS
101
 
102
    #endif
103
 
104
  #elif DISABLED(NEWPANEL) // old style ULTIPANEL
105
    // Shift register bits correspond to buttons:
106
    #define BL_LE 7   // Left
107
    #define BL_UP 6   // Up
108
    #define BL_MI 5   // Middle
109
    #define BL_DW 4   // Down
110
    #define BL_RI 3   // Right
111
    #define BL_ST 2   // Red Button
112
    #define B_LE (_BV(BL_LE))
113
    #define B_UP (_BV(BL_UP))
114
    #define B_MI (_BV(BL_MI))
115
    #define B_DW (_BV(BL_DW))
116
    #define B_RI (_BV(BL_RI))
117
    #define B_ST (_BV(BL_ST))
118
    #define LCD_CLICKED (buttons & (B_MI|B_ST))
119
  #endif
120
 
121
#endif // ULTIPANEL
122
 
123
////////////////////////////////////
124
// Create LCD class instance and chipset-specific information
125
#if ENABLED(LCD_I2C_TYPE_PCF8575)
126
  // note: these are register mapped pins on the PCF8575 controller not Arduino pins
127
  #define LCD_I2C_PIN_BL  3
128
  #define LCD_I2C_PIN_EN  2
129
  #define LCD_I2C_PIN_RW  1
130
  #define LCD_I2C_PIN_RS  0
131
  #define LCD_I2C_PIN_D4  4
132
  #define LCD_I2C_PIN_D5  5
133
  #define LCD_I2C_PIN_D6  6
134
  #define LCD_I2C_PIN_D7  7
135
 
136
  #include <Wire.h>
137
  #include <LCD.h>
138
  #include <LiquidCrystal_I2C.h>
139
  #define LCD_CLASS LiquidCrystal_I2C
140
  LCD_CLASS lcd(LCD_I2C_ADDRESS, LCD_I2C_PIN_EN, LCD_I2C_PIN_RW, LCD_I2C_PIN_RS, LCD_I2C_PIN_D4, LCD_I2C_PIN_D5, LCD_I2C_PIN_D6, LCD_I2C_PIN_D7);
141
 
142
#elif ENABLED(LCD_I2C_TYPE_MCP23017)
143
  //for the LED indicators (which maybe mapped to different things in lcd_implementation_update_indicators())
144
  #define LED_A 0x04 //100
145
  #define LED_B 0x02 //010
146
  #define LED_C 0x01 //001
147
 
148
  #define LCD_HAS_STATUS_INDICATORS
149
 
150
  #include <Wire.h>
151
  #include <LiquidTWI2.h>
152
  #define LCD_CLASS LiquidTWI2
153
  #if ENABLED(DETECT_DEVICE)
154
    LCD_CLASS lcd(LCD_I2C_ADDRESS, 1);
155
  #else
156
    LCD_CLASS lcd(LCD_I2C_ADDRESS);
157
  #endif
158
 
159
#elif ENABLED(LCD_I2C_TYPE_MCP23008)
160
  #include <Wire.h>
161
  #include <LiquidTWI2.h>
162
  #define LCD_CLASS LiquidTWI2
163
  #if ENABLED(DETECT_DEVICE)
164
    LCD_CLASS lcd(LCD_I2C_ADDRESS, 1);
165
  #else
166
    LCD_CLASS lcd(LCD_I2C_ADDRESS);
167
  #endif
168
 
169
#elif ENABLED(LCD_I2C_TYPE_PCA8574)
170
  #include <LiquidCrystal_I2C.h>
171
  #define LCD_CLASS LiquidCrystal_I2C
172
  LCD_CLASS lcd(LCD_I2C_ADDRESS, LCD_WIDTH, LCD_HEIGHT);
173
 
174
// 2 wire Non-latching LCD SR from:
175
// https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/schematics#!shiftregister-connection
176
#elif ENABLED(SR_LCD_2W_NL)
177
  extern "C" void __cxa_pure_virtual() { while (1); }
178
  #include <LCD.h>
179
  #include <LiquidCrystal_SR.h>
180
  #define LCD_CLASS LiquidCrystal_SR
181
  #if PIN_EXISTS(SR_STROBE)
182
    LCD_CLASS lcd(SR_DATA_PIN, SR_CLK_PIN, SR_STROBE_PIN);
183
  #else
184
    LCD_CLASS lcd(SR_DATA_PIN, SR_CLK_PIN);
185
  #endif
186
#elif ENABLED(LCM1602)
187
  #include <Wire.h>
188
  #include <LCD.h>
189
  #include <LiquidCrystal_I2C.h>
190
  #define LCD_CLASS LiquidCrystal_I2C
191
  LCD_CLASS lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
192
#else
193
  // Standard directly connected LCD implementations
194
  #include <LiquidCrystal.h>
195
  #define LCD_CLASS LiquidCrystal
196
  LCD_CLASS lcd(LCD_PINS_RS, LCD_PINS_ENABLE, LCD_PINS_D4, LCD_PINS_D5, LCD_PINS_D6, LCD_PINS_D7); //RS,Enable,D4,D5,D6,D7
197
#endif
198
 
199
#include "utf_mapper.h"
200
 
201
#if ENABLED(LCD_PROGRESS_BAR)
202
  static millis_t progress_bar_ms = 0;     // Start millis of the current progress bar cycle
203
  #if PROGRESS_MSG_EXPIRE > 0
204
    static millis_t expire_status_ms = 0;  // millis at which to expire the status message
205
  #endif
206
  #define LCD_STR_PROGRESS  "\x03\x04\x05"
207
#endif
208
 
209
#if ENABLED(LCD_HAS_STATUS_INDICATORS)
210
  static void lcd_implementation_update_indicators();
211
#endif
212
 
213
static void createChar_P(const char c, const byte * const ptr) {
214
  byte temp[8];
215
  for (uint8_t i = 0; i < 8; i++)
216
    temp[i] = pgm_read_byte(&ptr[i]);
217
  lcd.createChar(c, temp);
218
}
219
 
220
#define CHARSET_MENU 0
221
#define CHARSET_INFO 1
222
#define CHARSET_BOOT 2
223
 
224
static void lcd_set_custom_characters(
225
  #if ENABLED(LCD_PROGRESS_BAR) || ENABLED(SHOW_BOOTSCREEN)
226
    const uint8_t screen_charset=CHARSET_INFO
227
  #endif
228
) {
229
  // CHARSET_BOOT
230
  #if ENABLED(SHOW_BOOTSCREEN)
231
    const static PROGMEM byte corner[4][8] = { {
232
      B00000,
233
      B00000,
234
      B00000,
235
      B00000,
236
      B00001,
237
      B00010,
238
      B00100,
239
      B00100
240
    }, {
241
      B00000,
242
      B00000,
243
      B00000,
244
      B11100,
245
      B11100,
246
      B01100,
247
      B00100,
248
      B00100
249
    }, {
250
      B00100,
251
      B00010,
252
      B00001,
253
      B00000,
254
      B00000,
255
      B00000,
256
      B00000,
257
      B00000
258
    }, {
259
      B00100,
260
      B01000,
261
      B10000,
262
      B00000,
263
      B00000,
264
      B00000,
265
      B00000,
266
      B00000
267
    } };
268
  #endif // SHOW_BOOTSCREEN
269
 
270
  // CHARSET_INFO
271
  const static PROGMEM byte bedTemp[8] = {
272
    B00000,
273
    B11111,
274
    B10101,
275
    B10001,
276
    B10101,
277
    B11111,
278
    B00000,
279
    B00000
280
  };
281
 
282
  const static PROGMEM byte degree[8] = {
283
    B01100,
284
    B10010,
285
    B10010,
286
    B01100,
287
    B00000,
288
    B00000,
289
    B00000,
290
    B00000
291
  };
292
 
293
  const static PROGMEM byte thermometer[8] = {
294
    B00100,
295
    B01010,
296
    B01010,
297
    B01010,
298
    B01010,
299
    B10001,
300
    B10001,
301
    B01110
302
  };
303
 
304
  const static PROGMEM byte uplevel[8] = {
305
    B00100,
306
    B01110,
307
    B11111,
308
    B00100,
309
    B11100,
310
    B00000,
311
    B00000,
312
    B00000
313
  };
314
 
315
  const static PROGMEM byte feedrate[8] = {
316
    B11100,
317
    B10000,
318
    B11000,
319
    B10111,
320
    B00101,
321
    B00110,
322
    B00101,
323
    B00000
324
  };
325
 
326
  const static PROGMEM byte clock[8] = {
327
    B00000,
328
    B01110,
329
    B10011,
330
    B10101,
331
    B10001,
332
    B01110,
333
    B00000,
334
    B00000
335
  };
336
 
337
  #if ENABLED(LCD_PROGRESS_BAR)
338
 
339
    // CHARSET_INFO
340
    const static PROGMEM byte progress[3][8] = { {
341
      B00000,
342
      B10000,
343
      B10000,
344
      B10000,
345
      B10000,
346
      B10000,
347
      B10000,
348
      B00000
349
    }, {
350
      B00000,
351
      B10100,
352
      B10100,
353
      B10100,
354
      B10100,
355
      B10100,
356
      B10100,
357
      B00000
358
    }, {
359
      B00000,
360
      B10101,
361
      B10101,
362
      B10101,
363
      B10101,
364
      B10101,
365
      B10101,
366
      B00000
367
    } };
368
 
369
  #endif // LCD_PROGRESS_BAR
370
 
371
  #if ENABLED(SDSUPPORT)
372
 
373
    // CHARSET_MENU
374
    const static PROGMEM byte refresh[8] = {
375
      B00000,
376
      B00110,
377
      B11001,
378
      B11000,
379
      B00011,
380
      B10011,
381
      B01100,
382
      B00000,
383
    };
384
    const static PROGMEM byte folder[8] = {
385
      B00000,
386
      B11100,
387
      B11111,
388
      B10001,
389
      B10001,
390
      B11111,
391
      B00000,
392
      B00000
393
    };
394
 
395
  #endif // SDSUPPORT
396
 
397
  #if ENABLED(SHOW_BOOTSCREEN)
398
    // Set boot screen corner characters
399
    if (screen_charset == CHARSET_BOOT) {
400
      for (uint8_t i = 4; i--;)
401
        createChar_P(i, corner[i]);
402
    }
403
    else
404
  #endif
405
    { // Info Screen uses 5 special characters
406
      createChar_P(LCD_BEDTEMP_CHAR, bedTemp);
407
      createChar_P(LCD_DEGREE_CHAR, degree);
408
      createChar_P(LCD_STR_THERMOMETER[0], thermometer);
409
      createChar_P(LCD_FEEDRATE_CHAR, feedrate);
410
      createChar_P(LCD_CLOCK_CHAR, clock);
411
 
412
      #if ENABLED(LCD_PROGRESS_BAR)
413
        if (screen_charset == CHARSET_INFO) { // 3 Progress bar characters for info screen
414
          for (int16_t i = 3; i--;)
415
            createChar_P(LCD_STR_PROGRESS[i], progress[i]);
416
        }
417
        else
418
      #endif
419
        {
420
          createChar_P(LCD_UPLEVEL_CHAR, uplevel);
421
          #if ENABLED(SDSUPPORT)
422
            // SD Card sub-menu special characters
423
            createChar_P(LCD_STR_REFRESH[0], refresh);
424
            createChar_P(LCD_STR_FOLDER[0], folder);
425
          #endif
426
        }
427
    }
428
}
429
 
430
static void lcd_implementation_init(
431
  #if ENABLED(LCD_PROGRESS_BAR)
432
    const uint8_t screen_charset=CHARSET_INFO
433
  #endif
434
) {
435
 
436
  #if ENABLED(LCD_I2C_TYPE_PCF8575)
437
    lcd.begin(LCD_WIDTH, LCD_HEIGHT);
438
    #ifdef LCD_I2C_PIN_BL
439
      lcd.setBacklightPin(LCD_I2C_PIN_BL, POSITIVE);
440
      lcd.setBacklight(HIGH);
441
    #endif
442
 
443
  #elif ENABLED(LCD_I2C_TYPE_MCP23017)
444
    lcd.setMCPType(LTI_TYPE_MCP23017);
445
    lcd.begin(LCD_WIDTH, LCD_HEIGHT);
446
    lcd_implementation_update_indicators();
447
 
448
  #elif ENABLED(LCD_I2C_TYPE_MCP23008)
449
    lcd.setMCPType(LTI_TYPE_MCP23008);
450
    lcd.begin(LCD_WIDTH, LCD_HEIGHT);
451
 
452
  #elif ENABLED(LCD_I2C_TYPE_PCA8574)
453
    lcd.init();
454
    lcd.backlight();
455
 
456
  #else
457
    lcd.begin(LCD_WIDTH, LCD_HEIGHT);
458
  #endif
459
 
460
  lcd_set_custom_characters(
461
    #if ENABLED(LCD_PROGRESS_BAR)
462
      screen_charset
463
    #endif
464
  );
465
 
466
  lcd.clear();
467
}
468
 
469
void lcd_implementation_clear() { lcd.clear(); }
470
void lcd_print(const char c) { charset_mapper(c); }
471
void lcd_print(const char *str) { while (*str) lcd.print(*str++); }
472
void lcd_printPGM(const char *str) { while (const char c = pgm_read_byte(str)) lcd.print(c), ++str; }
473
 
474
void lcd_print_utf(const char *str, uint8_t n=LCD_WIDTH) {
475
  char c;
476
  while (n && (c = *str)) n -= charset_mapper(c), ++str;
477
}
478
 
479
void lcd_printPGM_utf(const char *str, uint8_t n=LCD_WIDTH) {
480
  char c;
481
  while (n && (c = pgm_read_byte(str))) n -= charset_mapper(c), ++str;
482
}
483
 
484
#if ENABLED(SHOW_BOOTSCREEN)
485
 
486
  void lcd_erase_line(const int16_t line) {
487
    lcd.setCursor(0, line);
488
    for (uint8_t i = LCD_WIDTH + 1; --i;)
489
      lcd.write(' ');
490
  }
491
 
492
  // Scroll the PSTR 'text' in a 'len' wide field for 'time' milliseconds at position col,line
493
  void lcd_scroll(const int16_t col, const int16_t line, const char* const text, const int16_t len, const int16_t time) {
494
    uint8_t slen = utf8_strlen_P(text);
495
    if (slen < len) {
496
      // Fits into,
497
      lcd.setCursor(col, line);
498
      lcd_printPGM_utf(text, len);
499
      while (slen < len) {
500
        lcd.write(' ');
501
        ++slen;
502
      }
503
      safe_delay(time);
504
    }
505
    else {
506
      const char* p = text;
507
      int dly = time / MAX(slen, 1);
508
      for (uint8_t i = 0; i <= slen; i++) {
509
 
510
        // Go to the correct place
511
        lcd.setCursor(col, line);
512
 
513
        // Print the text
514
        lcd_printPGM_utf(p, len);
515
 
516
        // Fill with spaces
517
        uint8_t ix = slen - i;
518
        while (ix < len) {
519
          lcd.write(' ');
520
          ++ix;
521
        }
522
 
523
        // Delay
524
        safe_delay(dly);
525
 
526
        // Advance to the next UTF8 valid position
527
        p++;
528
        while (!START_OF_UTF8_CHAR(pgm_read_byte(p))) p++;
529
      }
530
    }
531
  }
532
 
533
  static void logo_lines(const char* const extra) {
534
    int16_t indent = (LCD_WIDTH - 8 - utf8_strlen_P(extra)) / 2;
535
    lcd.setCursor(indent, 0); lcd.print('\x00'); lcd_printPGM(PSTR( "------" ));  lcd.write('\x01');
536
    lcd.setCursor(indent, 1);                    lcd_printPGM(PSTR("|Marlin|"));  lcd_printPGM(extra);
537
    lcd.setCursor(indent, 2); lcd.write('\x02'); lcd_printPGM(PSTR( "------" ));  lcd.write('\x03');
538
  }
539
 
540
  void lcd_bootscreen() {
541
    lcd_set_custom_characters(CHARSET_BOOT);
542
    lcd.clear();
543
 
544
    #define LCD_EXTRA_SPACE (LCD_WIDTH-8)
545
 
546
    #define CENTER_OR_SCROLL(STRING,DELAY) \
547
      lcd_erase_line(3); \
548
      if (strlen(STRING) <= LCD_WIDTH) { \
549
        lcd.setCursor((LCD_WIDTH - utf8_strlen_P(PSTR(STRING))) / 2, 3); \
550
        lcd_printPGM_utf(PSTR(STRING)); \
551
        safe_delay(DELAY); \
552
      } \
553
      else { \
554
        lcd_scroll(0, 3, PSTR(STRING), LCD_WIDTH, DELAY); \
555
      }
556
 
557
    #ifdef STRING_SPLASH_LINE1
558
      //
559
      // Show the Marlin logo with splash line 1
560
      //
561
      if (LCD_EXTRA_SPACE >= strlen(STRING_SPLASH_LINE1) + 1) {
562
        //
563
        // Show the Marlin logo, splash line1, and splash line 2
564
        //
565
        logo_lines(PSTR(" " STRING_SPLASH_LINE1));
566
        #ifdef STRING_SPLASH_LINE2
567
          CENTER_OR_SCROLL(STRING_SPLASH_LINE2, 2000);
568
        #else
569
          safe_delay(2000);
570
        #endif
571
      }
572
      else {
573
        //
574
        // Show the Marlin logo with splash line 1
575
        // After a delay show splash line 2, if it exists
576
        //
577
        #ifdef STRING_SPLASH_LINE2
578
          #define _SPLASH_WAIT_1 1500
579
        #else
580
          #define _SPLASH_WAIT_1 2000
581
        #endif
582
        logo_lines(PSTR(""));
583
        CENTER_OR_SCROLL(STRING_SPLASH_LINE1, _SPLASH_WAIT_1);
584
        #ifdef STRING_SPLASH_LINE2
585
          CENTER_OR_SCROLL(STRING_SPLASH_LINE2, 1500);
586
        #endif
587
      }
588
    #elif defined(STRING_SPLASH_LINE2)
589
      //
590
      // Show splash line 2 only, alongside the logo if possible
591
      //
592
      if (LCD_EXTRA_SPACE >= strlen(STRING_SPLASH_LINE2) + 1) {
593
        logo_lines(PSTR(" " STRING_SPLASH_LINE2));
594
        safe_delay(2000);
595
      }
596
      else {
597
        logo_lines(PSTR(""));
598
        CENTER_OR_SCROLL(STRING_SPLASH_LINE2, 2000);
599
      }
600
    #else
601
      //
602
      // Show only the Marlin logo
603
      //
604
      logo_lines(PSTR(""));
605
      safe_delay(2000);
606
    #endif
607
 
608
    lcd.clear();
609
    safe_delay(100);
610
    lcd_set_custom_characters();
611
    lcd.clear();
612
  }
613
 
614
#endif // SHOW_BOOTSCREEN
615
 
616
void lcd_kill_screen() {
617
  lcd.setCursor(0, 0);
618
  lcd_print_utf(lcd_status_message);
619
  #if LCD_HEIGHT < 4
620
    lcd.setCursor(0, 2);
621
  #else
622
    lcd.setCursor(0, 2);
623
    lcd_printPGM_utf(PSTR(MSG_HALTED));
624
    lcd.setCursor(0, 3);
625
  #endif
626
  lcd_printPGM_utf(PSTR(MSG_PLEASE_RESET));
627
}
628
 
629
//
630
// Before homing, blink '123' <-> '???'.
631
// Homed but unknown... '123' <-> '   '.
632
// Homed and known, display constantly.
633
//
634
FORCE_INLINE void _draw_axis_value(const AxisEnum axis, const char *value, const bool blink) {
635
  lcd_print('X' + uint8_t(axis));
636
  if (blink)
637
    lcd.print(value);
638
  else {
639
    if (!TEST(axis_homed, axis))
640
      while (const char c = *value++) lcd_print(c <= '.' ? c : '?');
641
    else {
642
      #if DISABLED(HOME_AFTER_DEACTIVATE) && DISABLED(DISABLE_REDUCED_ACCURACY_WARNING)
643
        if (!TEST(axis_known_position, axis))
644
          lcd_printPGM(axis == Z_AXIS ? PSTR("      ") : PSTR("    "));
645
        else
646
      #endif
647
          lcd.print(value);
648
    }
649
  }
650
}
651
 
652
FORCE_INLINE void _draw_heater_status(const int8_t heater, const char prefix, const bool blink) {
653
  #if HAS_HEATED_BED
654
    const bool isBed = heater < 0;
655
    const float t1 = (isBed ? thermalManager.degBed()       : thermalManager.degHotend(heater)),
656
                t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater));
657
  #else
658
    const float t1 = thermalManager.degHotend(heater), t2 = thermalManager.degTargetHotend(heater);
659
  #endif
660
 
661
  if (prefix >= 0) lcd.print(prefix);
662
 
663
  lcd.print(itostr3(t1 + 0.5));
664
  lcd.write('/');
665
 
666
  #if !HEATER_IDLE_HANDLER
667
    UNUSED(blink);
668
  #else
669
    const bool is_idle = (
670
      #if HAS_HEATED_BED
671
        isBed ? thermalManager.is_bed_idle() :
672
      #endif
673
      thermalManager.is_heater_idle(heater)
674
    );
675
 
676
    if (!blink && is_idle) {
677
      lcd.write(' ');
678
      if (t2 >= 10) lcd.write(' ');
679
      if (t2 >= 100) lcd.write(' ');
680
    }
681
    else
682
  #endif
683
      lcd.print(itostr3left(t2 + 0.5));
684
 
685
  if (prefix >= 0) {
686
    lcd.print((char)LCD_DEGREE_CHAR);
687
    lcd.write(' ');
688
    if (t2 < 10) lcd.write(' ');
689
  }
690
}
691
 
692
#if ENABLED(LCD_PROGRESS_BAR)
693
 
694
  inline void lcd_draw_progress_bar(const uint8_t percent) {
695
    const int16_t tix = (int16_t)(percent * (LCD_WIDTH) * 3) / 100,
696
              cel = tix / 3,
697
              rem = tix % 3;
698
    uint8_t i = LCD_WIDTH;
699
    char msg[LCD_WIDTH + 1], b = ' ';
700
    msg[LCD_WIDTH] = '\0';
701
    while (i--) {
702
      if (i == cel - 1)
703
        b = LCD_STR_PROGRESS[2];
704
      else if (i == cel && rem != 0)
705
        b = LCD_STR_PROGRESS[rem - 1];
706
      msg[i] = b;
707
    }
708
    lcd.print(msg);
709
  }
710
 
711
#endif // LCD_PROGRESS_BAR
712
 
713
/**
714
Possible status screens:
715
16x2   |000/000 B000/000|
716
       |0123456789012345|
717
 
718
16x4   |000/000 B000/000|
719
       |SD100%  Z 000.00|
720
       |F100%     T--:--|
721
       |0123456789012345|
722
 
723
20x2   |T000/000D B000/000D |
724
       |01234567890123456789|
725
 
726
20x4   |T000/000D B000/000D |
727
       |X 000 Y 000 Z 000.00|
728
       |F100%  SD100% T--:--|
729
       |01234567890123456789|
730
 
731
20x4   |T000/000D B000/000D |
732
       |T000/000D   Z 000.00|
733
       |F100%  SD100% T--:--|
734
       |01234567890123456789|
735
*/
736
static void lcd_implementation_status_screen() {
737
  const bool blink = lcd_blink();
738
 
739
  //
740
  // Line 1
741
  //
742
 
743
  lcd.setCursor(0, 0);
744
 
745
  #if LCD_WIDTH < 20
746
 
747
    //
748
    // Hotend 0 Temperature
749
    //
750
    _draw_heater_status(0, -1, blink);
751
 
752
    //
753
    // Hotend 1 or Bed Temperature
754
    //
755
    #if HOTENDS > 1 || HAS_HEATED_BED
756
 
757
      lcd.setCursor(8, 0);
758
      #if HOTENDS > 1
759
        lcd.print((char)LCD_STR_THERMOMETER[0]);
760
        _draw_heater_status(1, -1, blink);
761
      #else
762
        lcd.print((char)LCD_BEDTEMP_CHAR);
763
        _draw_heater_status(-1, -1, blink);
764
      #endif
765
 
766
    #endif // HOTENDS > 1 || HAS_HEATED_BED
767
 
768
  #else // LCD_WIDTH >= 20
769
 
770
    //
771
    // Hotend 0 Temperature
772
    //
773
    _draw_heater_status(0, LCD_STR_THERMOMETER[0], blink);
774
 
775
    //
776
    // Hotend 1 or Bed Temperature
777
    //
778
    #if HOTENDS > 1 || HAS_HEATED_BED
779
      lcd.setCursor(10, 0);
780
      #if HOTENDS > 1
781
        _draw_heater_status(1, LCD_STR_THERMOMETER[0], blink);
782
      #else
783
        _draw_heater_status(-1, (
784
          #if HAS_LEVELING
785
            planner.leveling_active && blink ? '_' :
786
          #endif
787
          LCD_BEDTEMP_CHAR
788
        ), blink);
789
      #endif
790
 
791
    #endif // HOTENDS > 1 || HAS_HEATED_BED
792
 
793
  #endif // LCD_WIDTH >= 20
794
 
795
  //
796
  // Line 2
797
  //
798
 
799
  #if LCD_HEIGHT > 2
800
 
801
    #if LCD_WIDTH < 20
802
 
803
      #if ENABLED(SDSUPPORT)
804
        lcd.setCursor(0, 2);
805
        lcd_printPGM(PSTR("SD"));
806
        if (IS_SD_PRINTING())
807
          lcd.print(itostr3(card.percentDone()));
808
        else
809
          lcd_printPGM(PSTR("---"));
810
          lcd.write('%');
811
      #endif // SDSUPPORT
812
 
813
    #else // LCD_WIDTH >= 20
814
 
815
      lcd.setCursor(0, 1);
816
 
817
      // If the first line has two extruder temps,
818
      // show more temperatures on the next line
819
 
820
      #if HOTENDS > 2 || (HOTENDS > 1 && HAS_HEATED_BED)
821
 
822
        #if HOTENDS > 2
823
          _draw_heater_status(2, LCD_STR_THERMOMETER[0], blink);
824
          lcd.setCursor(10, 1);
825
        #endif
826
 
827
        _draw_heater_status(-1, (
828
          #if HAS_LEVELING
829
            planner.leveling_active && blink ? '_' :
830
          #endif
831
          LCD_BEDTEMP_CHAR
832
        ), blink);
833
 
834
      #else // HOTENDS <= 2 && (HOTENDS <= 1 || !HAS_HEATED_BED)
835
 
836
        _draw_axis_value(X_AXIS, ftostr4sign(LOGICAL_X_POSITION(current_position[X_AXIS])), blink);
837
 
838
        lcd.write(' ');
839
 
840
        _draw_axis_value(Y_AXIS, ftostr4sign(LOGICAL_Y_POSITION(current_position[Y_AXIS])), blink);
841
 
842
      #endif // HOTENDS <= 2 && (HOTENDS <= 1 || !HAS_HEATED_BED)
843
 
844
    #endif // LCD_WIDTH >= 20
845
 
846
    lcd.setCursor(LCD_WIDTH - 8, 1);
847
    _draw_axis_value(Z_AXIS, ftostr52sp(LOGICAL_Z_POSITION(current_position[Z_AXIS])), blink);
848
 
849
    #if HAS_LEVELING && !HAS_HEATED_BED
850
      lcd.write(planner.leveling_active || blink ? '_' : ' ');
851
    #endif
852
 
853
  #endif // LCD_HEIGHT > 2
854
 
855
  //
856
  // Line 3
857
  //
858
 
859
  #if LCD_HEIGHT > 3
860
 
861
    lcd.setCursor(0, 2);
862
    lcd.print((char)LCD_FEEDRATE_CHAR);
863
    lcd.print(itostr3(feedrate_percentage));
864
    lcd.write('%');
865
 
866
    #if LCD_WIDTH >= 20 && ENABLED(SDSUPPORT)
867
 
868
      lcd.setCursor(7, 2);
869
      lcd_printPGM(PSTR("SD"));
870
      if (IS_SD_PRINTING())
871
        lcd.print(itostr3(card.percentDone()));
872
      else
873
        lcd_printPGM(PSTR("---"));
874
      lcd.write('%');
875
 
876
    #endif // LCD_WIDTH >= 20 && SDSUPPORT
877
 
878
    char buffer[10];
879
    duration_t elapsed = print_job_timer.duration();
880
    uint8_t len = elapsed.toDigital(buffer);
881
 
882
    lcd.setCursor(LCD_WIDTH - len - 1, 2);
883
    lcd.print((char)LCD_CLOCK_CHAR);
884
    lcd_print(buffer);
885
 
886
  #endif // LCD_HEIGHT > 3
887
 
888
  //
889
  // Last Line
890
  // Status Message (which may be a Progress Bar or Filament display)
891
  //
892
 
893
  lcd.setCursor(0, LCD_HEIGHT - 1);
894
 
895
  #if ENABLED(LCD_PROGRESS_BAR)
896
 
897
    // Draw the progress bar if the message has shown long enough
898
    // or if there is no message set.
899
    #if DISABLED(LCD_SET_PROGRESS_MANUALLY)
900
      const uint8_t progress_bar_percent = card.percentDone();
901
    #endif
902
    if (progress_bar_percent > 2 && (ELAPSED(millis(), progress_bar_ms + PROGRESS_BAR_MSG_TIME) || !lcd_status_message[0]))
903
      return lcd_draw_progress_bar(progress_bar_percent);
904
 
905
  #elif ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
906
 
907
    // Show Filament Diameter and Volumetric Multiplier %
908
    // After allowing lcd_status_message to show for 5 seconds
909
    if (ELAPSED(millis(), previous_lcd_status_ms + 5000UL)) {
910
      lcd_printPGM(PSTR("Dia "));
911
      lcd.print(ftostr12ns(filament_width_meas));
912
      lcd_printPGM(PSTR(" V"));
913
      lcd.print(itostr3(100.0 * (
914
          parser.volumetric_enabled
915
            ? planner.volumetric_area_nominal / planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
916
            : planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
917
        )
918
      ));
919
      lcd.write('%');
920
      return;
921
    }
922
 
923
  #endif // FILAMENT_LCD_DISPLAY && SDSUPPORT
924
 
925
  #if ENABLED(STATUS_MESSAGE_SCROLLING)
926
    static bool last_blink = false;
927
 
928
    // Get the UTF8 character count of the string
929
    uint8_t slen = utf8_strlen(lcd_status_message);
930
 
931
    // If the string fits into the LCD, just print it and do not scroll it
932
    if (slen <= LCD_WIDTH) {
933
 
934
      // The string isn't scrolling and may not fill the screen
935
      lcd_print_utf(lcd_status_message);
936
 
937
      // Fill the rest with spaces
938
      while (slen < LCD_WIDTH) {
939
        lcd.write(' ');
940
        ++slen;
941
      }
942
    }
943
    else {
944
      // String is larger than the available space in screen.
945
 
946
      // Get a pointer to the next valid UTF8 character
947
      const char *stat = lcd_status_message + status_scroll_offset;
948
 
949
      // Get the string remaining length
950
      const uint8_t rlen = utf8_strlen(stat);
951
 
952
      // If we have enough characters to display
953
      if (rlen >= LCD_WIDTH) {
954
        // The remaining string fills the screen - Print it
955
        lcd_print_utf(stat, LCD_WIDTH);
956
      }
957
      else {
958
 
959
        // The remaining string does not completely fill the screen
960
        lcd_print_utf(stat, LCD_WIDTH);               // The string leaves space
961
        uint8_t chars = LCD_WIDTH - rlen;             // Amount of space left in characters
962
 
963
        lcd.write('.');                               // Always at 1+ spaces left, draw a dot
964
        if (--chars) {                                // Draw a second dot if there's space
965
          lcd.write('.');
966
          if (--chars)
967
            lcd_print_utf(lcd_status_message, chars); // Print a second copy of the message
968
        }
969
      }
970
      if (last_blink != blink) {
971
        last_blink = blink;
972
 
973
        // Adjust by complete UTF8 characters
974
        if (status_scroll_offset < slen) {
975
          status_scroll_offset++;
976
          while (!START_OF_UTF8_CHAR(lcd_status_message[status_scroll_offset]))
977
            status_scroll_offset++;
978
        }
979
        else
980
          status_scroll_offset = 0;
981
      }
982
    }
983
  #else
984
    UNUSED(blink);
985
 
986
    // Get the UTF8 character count of the string
987
    uint8_t slen = utf8_strlen(lcd_status_message);
988
 
989
    // Just print the string to the LCD
990
    lcd_print_utf(lcd_status_message, LCD_WIDTH);
991
 
992
    // Fill the rest with spaces if there are missing spaces
993
    while (slen < LCD_WIDTH) {
994
      lcd.write(' ');
995
      ++slen;
996
    }
997
  #endif
998
}
999
 
1000
#if ENABLED(ULTIPANEL)
1001
 
1002
  #if ENABLED(ADVANCED_PAUSE_FEATURE)
1003
 
1004
    static void lcd_implementation_hotend_status(const uint8_t row, const uint8_t extruder=active_extruder) {
1005
      if (row < LCD_HEIGHT) {
1006
        lcd.setCursor(LCD_WIDTH - 9, row);
1007
        _draw_heater_status(extruder, LCD_STR_THERMOMETER[0], lcd_blink());
1008
      }
1009
    }
1010
 
1011
  #endif // ADVANCED_PAUSE_FEATURE
1012
 
1013
  static void lcd_implementation_drawmenu_static(const uint8_t row, const char* pstr, const bool center=true, const bool invert=false, const char *valstr=NULL) {
1014
    UNUSED(invert);
1015
    char c;
1016
    int8_t n = LCD_WIDTH;
1017
    lcd.setCursor(0, row);
1018
    if (center && !valstr) {
1019
      int8_t pad = (LCD_WIDTH - utf8_strlen_P(pstr)) / 2;
1020
      while (--pad >= 0) { lcd.write(' '); n--; }
1021
    }
1022
    while (n > 0 && (c = pgm_read_byte(pstr))) {
1023
      n -= charset_mapper(c);
1024
      pstr++;
1025
    }
1026
    if (valstr) while (n > 0 && (c = *valstr)) {
1027
      n -= charset_mapper(c);
1028
      valstr++;
1029
    }
1030
    while (n-- > 0) lcd.write(' ');
1031
  }
1032
 
1033
  static void lcd_implementation_drawmenu_generic(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char post_char) {
1034
    char c;
1035
    uint8_t n = LCD_WIDTH - 2;
1036
    lcd.setCursor(0, row);
1037
    lcd.print(sel ? pre_char : ' ');
1038
    while ((c = pgm_read_byte(pstr)) && n > 0) {
1039
      n -= charset_mapper(c);
1040
      pstr++;
1041
    }
1042
    while (n--) lcd.write(' ');
1043
    lcd.print(post_char);
1044
  }
1045
 
1046
  static void lcd_implementation_drawmenu_setting_edit_generic(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char* const data) {
1047
    char c;
1048
    uint8_t n = LCD_WIDTH - 2 - utf8_strlen(data);
1049
    lcd.setCursor(0, row);
1050
    lcd.print(sel ? pre_char : ' ');
1051
    while ((c = pgm_read_byte(pstr)) && n > 0) {
1052
      n -= charset_mapper(c);
1053
      pstr++;
1054
    }
1055
    lcd.write(':');
1056
    while (n--) lcd.write(' ');
1057
    lcd_print(data);
1058
  }
1059
  static void lcd_implementation_drawmenu_setting_edit_generic_P(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char* const data) {
1060
    char c;
1061
    uint8_t n = LCD_WIDTH - 2 - utf8_strlen_P(data);
1062
    lcd.setCursor(0, row);
1063
    lcd.print(sel ? pre_char : ' ');
1064
    while ((c = pgm_read_byte(pstr)) && n > 0) {
1065
      n -= charset_mapper(c);
1066
      pstr++;
1067
    }
1068
    lcd.write(':');
1069
    while (n--) lcd.write(' ');
1070
    lcd_printPGM(data);
1071
  }
1072
 
1073
  #define DRAWMENU_SETTING_EDIT_GENERIC(_src) lcd_implementation_drawmenu_setting_edit_generic(sel, row, pstr, '>', _src)
1074
  #define DRAW_BOOL_SETTING(sel, row, pstr, data) lcd_implementation_drawmenu_setting_edit_generic_P(sel, row, pstr, '>', (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
1075
 
1076
  void lcd_implementation_drawedit(const char* pstr, const char* const value=NULL) {
1077
    lcd.setCursor(1, 1);
1078
    lcd_printPGM_utf(pstr);
1079
    if (value != NULL) {
1080
      lcd.write(':');
1081
      const uint8_t valrow = (utf8_strlen_P(pstr) + 1 + utf8_strlen(value) + 1) > (LCD_WIDTH - 2) ? 2 : 1; // Value on the next row if it won't fit
1082
      lcd.setCursor((LCD_WIDTH - 1) - (utf8_strlen(value) + 1), valrow);                                  // Right-justified, padded by spaces
1083
      lcd.write(' ');                                                                                     // overwrite char if value gets shorter
1084
      lcd_print(value);
1085
    }
1086
  }
1087
 
1088
  #if ENABLED(SDSUPPORT)
1089
 
1090
    static void lcd_implementation_drawmenu_sd(const bool sel, const uint8_t row, const char* const pstr, CardReader& theCard, const uint8_t concat, const char post_char) {
1091
      UNUSED(pstr);
1092
      lcd.setCursor(0, row);
1093
      lcd.print(sel ? '>' : ' ');
1094
 
1095
      uint8_t n = LCD_WIDTH - concat;
1096
      const char *outstr = theCard.longest_filename();
1097
      if (theCard.longFilename[0]) {
1098
        #if ENABLED(SCROLL_LONG_FILENAMES)
1099
          if (sel) {
1100
            uint8_t name_hash = row;
1101
            for (uint8_t l = FILENAME_LENGTH; l--;)
1102
              name_hash = ((name_hash << 1) | (name_hash >> 7)) ^ theCard.filename[l];  // rotate, xor
1103
            if (filename_scroll_hash != name_hash) {                            // If the hash changed...
1104
              filename_scroll_hash = name_hash;                                 // Save the new hash
1105
              filename_scroll_max = MAX(0, utf8_strlen(theCard.longFilename) - n);  // Update the scroll limit
1106
              filename_scroll_pos = 0;                                          // Reset scroll to the start
1107
              lcd_status_update_delay = 8;                                      // Don't scroll right away
1108
            }
1109
            outstr += filename_scroll_pos;
1110
          }
1111
        #else
1112
          theCard.longFilename[n] = '\0'; // cutoff at screen edge
1113
        #endif
1114
      }
1115
 
1116
      char c;
1117
      while (n && (c = *outstr)) {
1118
        n -= charset_mapper(c);
1119
        ++outstr;
1120
      }
1121
      while (n) { --n; lcd.write(' '); }
1122
 
1123
      lcd.print(post_char);
1124
    }
1125
 
1126
    static void lcd_implementation_drawmenu_sdfile(const bool sel, const uint8_t row, const char* pstr, CardReader& theCard) {
1127
      lcd_implementation_drawmenu_sd(sel, row, pstr, theCard, 2, ' ');
1128
    }
1129
 
1130
    static void lcd_implementation_drawmenu_sddirectory(const bool sel, const uint8_t row, const char* pstr, CardReader& theCard) {
1131
      lcd_implementation_drawmenu_sd(sel, row, pstr, theCard, 2, LCD_STR_FOLDER[0]);
1132
    }
1133
 
1134
  #endif // SDSUPPORT
1135
 
1136
  #define lcd_implementation_drawmenu_back(sel, row, pstr, dummy) lcd_implementation_drawmenu_generic(sel, row, pstr, LCD_UPLEVEL_CHAR, LCD_UPLEVEL_CHAR)
1137
  #define lcd_implementation_drawmenu_submenu(sel, row, pstr, data) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', LCD_STR_ARROW_RIGHT[0])
1138
  #define lcd_implementation_drawmenu_gcode(sel, row, pstr, gcode) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', ' ')
1139
  #define lcd_implementation_drawmenu_function(sel, row, pstr, data) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', ' ')
1140
 
1141
  #if ENABLED(LCD_HAS_SLOW_BUTTONS)
1142
 
1143
    extern millis_t next_button_update_ms;
1144
 
1145
    static uint8_t lcd_implementation_read_slow_buttons() {
1146
      #if ENABLED(LCD_I2C_TYPE_MCP23017)
1147
        // Reading these buttons this is likely to be too slow to call inside interrupt context
1148
        // so they are called during normal lcd_update
1149
        uint8_t slow_bits = lcd.readButtons() << B_I2C_BTN_OFFSET;
1150
        #if ENABLED(LCD_I2C_VIKI)
1151
          if ((slow_bits & (B_MI | B_RI)) && PENDING(millis(), next_button_update_ms)) // LCD clicked
1152
            slow_bits &= ~(B_MI | B_RI); // Disable LCD clicked buttons if screen is updated
1153
        #endif // LCD_I2C_VIKI
1154
        return slow_bits;
1155
      #endif // LCD_I2C_TYPE_MCP23017
1156
    }
1157
 
1158
  #endif // LCD_HAS_SLOW_BUTTONS
1159
 
1160
  #if ENABLED(LCD_HAS_STATUS_INDICATORS)
1161
 
1162
    static void lcd_implementation_update_indicators() {
1163
      // Set the LEDS - referred to as backlights by the LiquidTWI2 library
1164
      static uint8_t ledsprev = 0;
1165
      uint8_t leds = 0;
1166
 
1167
      #if HAS_HEATED_BED
1168
        if (thermalManager.degTargetBed() > 0) leds |= LED_A;
1169
      #endif
1170
      if (thermalManager.degTargetHotend(0) > 0) leds |= LED_B;
1171
 
1172
      #if FAN_COUNT > 0
1173
        if (0
1174
          #if HAS_FAN0
1175
            || fanSpeeds[0]
1176
          #endif
1177
          #if HAS_FAN1
1178
            || fanSpeeds[1]
1179
          #endif
1180
          #if HAS_FAN2
1181
            || fanSpeeds[2]
1182
          #endif
1183
        ) leds |= LED_C;
1184
      #endif // FAN_COUNT > 0
1185
 
1186
      #if HOTENDS > 1
1187
        if (thermalManager.degTargetHotend(1) > 0) leds |= LED_C;
1188
      #endif
1189
 
1190
      if (leds != ledsprev) {
1191
        lcd.setBacklight(leds);
1192
        ledsprev = leds;
1193
      }
1194
    }
1195
 
1196
  #endif // LCD_HAS_STATUS_INDICATORS
1197
 
1198
  #if ENABLED(AUTO_BED_LEVELING_UBL)
1199
 
1200
    /**
1201
      Possible map screens:
1202
 
1203
      16x2   |X000.00  Y000.00|
1204
             |(00,00)  Z00.000|
1205
 
1206
      20x2   | X:000.00  Y:000.00 |
1207
             | (00,00)   Z:00.000 |
1208
 
1209
      16x4   |+-------+(00,00)|
1210
             ||       |X000.00|
1211
             ||       |Y000.00|
1212
             |+-------+Z00.000|
1213
 
1214
      20x4   | +-------+  (00,00) |
1215
             | |       |  X:000.00|
1216
             | |       |  Y:000.00|
1217
             | +-------+  Z:00.000|
1218
    */
1219
 
1220
    typedef struct {
1221
      uint8_t custom_char_bits[ULTRA_Y_PIXELS_PER_CHAR];
1222
    } custom_char;
1223
 
1224
    typedef struct {
1225
      uint8_t column, row;
1226
      uint8_t y_pixel_offset, x_pixel_offset;
1227
      uint8_t x_pixel_mask;
1228
    } coordinate;
1229
 
1230
    void add_edges_to_custom_char(custom_char * const custom, coordinate * const ul, coordinate * const lr, coordinate * const brc, const uint8_t cell_location);
1231
    FORCE_INLINE static void clear_custom_char(custom_char * const cc) { ZERO(cc->custom_char_bits); }
1232
 
1233
    /*
1234
    // This debug routine should be deleted by anybody that sees it.  It doesn't belong here
1235
    // But I'm leaving it for now until we know the 20x4 Radar Map is working right.
1236
    // We may need it again if any funny lines show up on the mesh points.
1237
    void dump_custom_char(char *title, custom_char *c) {
1238
      SERIAL_PROTOCOLLN(title);
1239
      for (uint8_t j = 0; j < 8; j++) {
1240
        for (uint8_t i = 7; i >= 0; i--)
1241
          SERIAL_PROTOCOLCHAR(TEST(c->custom_char_bits[j], i) ? '1' : '0');
1242
        SERIAL_EOL();
1243
      }
1244
      SERIAL_EOL();
1245
    }
1246
    //*/
1247
 
1248
    coordinate pixel_location(int16_t x, int16_t y) {
1249
      coordinate ret_val;
1250
      int16_t xp, yp, r, c;
1251
 
1252
      x++; y++; // +1 because lines on the left and top
1253
 
1254
      c = x / (ULTRA_X_PIXELS_PER_CHAR);
1255
      r = y / (ULTRA_Y_PIXELS_PER_CHAR);
1256
 
1257
      ret_val.column = c;
1258
      ret_val.row    = r;
1259
 
1260
      xp = x - c * (ULTRA_X_PIXELS_PER_CHAR);   // get the pixel offsets into the character cell
1261
      xp = ULTRA_X_PIXELS_PER_CHAR - 1 - xp;    // column within relevant character cell (0 on the right)
1262
      yp = y - r * (ULTRA_Y_PIXELS_PER_CHAR);
1263
 
1264
      ret_val.x_pixel_mask   = _BV(xp);
1265
      ret_val.x_pixel_offset = xp;
1266
      ret_val.y_pixel_offset = yp;
1267
      return ret_val;
1268
    }
1269
 
1270
    inline coordinate pixel_location(const uint8_t x, const uint8_t y) { return pixel_location((int16_t)x, (int16_t)y); }
1271
 
1272
    void lcd_implementation_ubl_plot(const uint8_t x, const uint8_t inverted_y) {
1273
 
1274
      #if LCD_WIDTH >= 20
1275
        #define _LCD_W_POS 12
1276
        #define _PLOT_X 1
1277
        #define _MAP_X 3
1278
        #define _LABEL(C,X,Y) lcd.setCursor(X, Y); lcd.print(C)
1279
        #define _XLABEL(X,Y) _LABEL("X:",X,Y)
1280
        #define _YLABEL(X,Y) _LABEL("Y:",X,Y)
1281
        #define _ZLABEL(X,Y) _LABEL("Z:",X,Y)
1282
      #else
1283
        #define _LCD_W_POS 8
1284
        #define _PLOT_X 0
1285
        #define _MAP_X 1
1286
        #define _LABEL(X,Y,C) lcd.setCursor(X, Y); lcd.write(C)
1287
        #define _XLABEL(X,Y) _LABEL('X',X,Y)
1288
        #define _YLABEL(X,Y) _LABEL('Y',X,Y)
1289
        #define _ZLABEL(X,Y) _LABEL('Z',X,Y)
1290
      #endif
1291
 
1292
      #if LCD_HEIGHT <= 3   // 16x2 or 20x2 display
1293
 
1294
        /**
1295
         * Show X and Y positions
1296
         */
1297
        _XLABEL(_PLOT_X, 0);
1298
        lcd.print(ftostr52(LOGICAL_X_POSITION(pgm_read_float(&ubl._mesh_index_to_xpos[x]))));
1299
 
1300
        _YLABEL(_LCD_W_POS, 0);
1301
        lcd.print(ftostr52(LOGICAL_Y_POSITION(pgm_read_float(&ubl._mesh_index_to_ypos[inverted_y]))));
1302
 
1303
        lcd.setCursor(_PLOT_X, 0);
1304
 
1305
      #else // 16x4 or 20x4 display
1306
 
1307
        coordinate upper_left, lower_right, bottom_right_corner;
1308
        custom_char new_char;
1309
        uint8_t i, j, k, l, m, n, n_rows, n_cols, y,
1310
                bottom_line, right_edge,
1311
                x_map_pixels, y_map_pixels,
1312
                pixels_per_x_mesh_pnt, pixels_per_y_mesh_pnt,
1313
                suppress_x_offset = 0, suppress_y_offset = 0;
1314
 
1315
        y = GRID_MAX_POINTS_Y - inverted_y - 1;
1316
 
1317
        upper_left.column  = 0;
1318
        upper_left.row     = 0;
1319
        lower_right.column = 0;
1320
        lower_right.row    = 0;
1321
 
1322
        lcd_implementation_clear();
1323
 
1324
        x_map_pixels = (ULTRA_X_PIXELS_PER_CHAR) * (ULTRA_COLUMNS_FOR_MESH_MAP) - 2;  // minus 2 because we are drawing a box around the map
1325
        y_map_pixels = (ULTRA_Y_PIXELS_PER_CHAR) * (ULTRA_ROWS_FOR_MESH_MAP) - 2;
1326
 
1327
        pixels_per_x_mesh_pnt = x_map_pixels / (GRID_MAX_POINTS_X);
1328
        pixels_per_y_mesh_pnt = y_map_pixels / (GRID_MAX_POINTS_Y);
1329
 
1330
        if (pixels_per_x_mesh_pnt >= ULTRA_X_PIXELS_PER_CHAR) {         // There are only 2 custom characters available, so the X
1331
          pixels_per_x_mesh_pnt = ULTRA_X_PIXELS_PER_CHAR;              // size of the mesh point needs to fit within them independent
1332
          suppress_x_offset = 1;                                        // of where the starting pixel is located.
1333
        }
1334
 
1335
        if (pixels_per_y_mesh_pnt >= ULTRA_Y_PIXELS_PER_CHAR) {         // There are only 2 custom characters available, so the Y
1336
          pixels_per_y_mesh_pnt = ULTRA_Y_PIXELS_PER_CHAR;              // size of the mesh point needs to fit within them independent
1337
          suppress_y_offset = 1;                                        // of where the starting pixel is located.
1338
        }
1339
 
1340
        x_map_pixels = pixels_per_x_mesh_pnt * (GRID_MAX_POINTS_X);     // now we have the right number of pixels to make both
1341
        y_map_pixels = pixels_per_y_mesh_pnt * (GRID_MAX_POINTS_Y);     // directions fit nicely
1342
 
1343
        right_edge = pixels_per_x_mesh_pnt * (GRID_MAX_POINTS_X) + 1;   // find location of right edge within the character cell
1344
        bottom_line= pixels_per_y_mesh_pnt * (GRID_MAX_POINTS_Y) + 1;   // find location of bottome line within the character cell
1345
 
1346
        n_rows = bottom_line / (ULTRA_Y_PIXELS_PER_CHAR) + 1;
1347
        n_cols = right_edge / (ULTRA_X_PIXELS_PER_CHAR) + 1;
1348
 
1349
        for (i = 0; i < n_cols; i++) {
1350
          lcd.setCursor(i, 0);
1351
          lcd.print((char)0x00);                     // top line of the box
1352
 
1353
          lcd.setCursor(i, n_rows - 1);
1354
          lcd.write(0x01);                           // bottom line of the box
1355
        }
1356
 
1357
        for (j = 0; j < n_rows; j++) {
1358
          lcd.setCursor(0, j);
1359
          lcd.write(0x02);                           // Left edge of the box
1360
          lcd.setCursor(n_cols - 1, j);
1361
          lcd.write(0x03);                           // right edge of the box
1362
        }
1363
 
1364
        /**
1365
         * If the entire 4th row is not in use, do not put vertical bars all the way down to the bottom of the display
1366
         */
1367
 
1368
        k = pixels_per_y_mesh_pnt * (GRID_MAX_POINTS_Y) + 2;
1369
        l = (ULTRA_Y_PIXELS_PER_CHAR) * n_rows;
1370
        if (l > k && l - k >= (ULTRA_Y_PIXELS_PER_CHAR) / 2) {
1371
          lcd.setCursor(0, n_rows - 1);            // left edge of the box
1372
          lcd.write(' ');
1373
          lcd.setCursor(n_cols - 1, n_rows - 1);   // right edge of the box
1374
          lcd.write(' ');
1375
        }
1376
 
1377
        clear_custom_char(&new_char);
1378
        new_char.custom_char_bits[0] = 0b11111U;              // char #0 is used for the top line of the box
1379
        lcd.createChar(0, (uint8_t*)&new_char);
1380
 
1381
        clear_custom_char(&new_char);
1382
        k = (GRID_MAX_POINTS_Y) * pixels_per_y_mesh_pnt + 1;  // row of pixels for the bottom box line
1383
        l = k % (ULTRA_Y_PIXELS_PER_CHAR);                    // row within relevant character cell
1384
        new_char.custom_char_bits[l] = 0b11111U;              // char #1 is used for the bottom line of the box
1385
        lcd.createChar(1, (uint8_t*)&new_char);
1386
 
1387
        clear_custom_char(&new_char);
1388
        for (j = 0; j < ULTRA_Y_PIXELS_PER_CHAR; j++)
1389
          new_char.custom_char_bits[j] = 0b10000U;            // char #2 is used for the left edge of the box
1390
        lcd.createChar(2, (uint8_t*)&new_char);
1391
 
1392
        clear_custom_char(&new_char);
1393
        m = (GRID_MAX_POINTS_X) * pixels_per_x_mesh_pnt + 1;  // Column of pixels for the right box line
1394
        n = m % (ULTRA_X_PIXELS_PER_CHAR);                    // Column within relevant character cell
1395
        i = ULTRA_X_PIXELS_PER_CHAR - 1 - n;                  // Column within relevant character cell (0 on the right)
1396
        for (j = 0; j < ULTRA_Y_PIXELS_PER_CHAR; j++)
1397
          new_char.custom_char_bits[j] = (uint8_t)_BV(i);     // Char #3 is used for the right edge of the box
1398
        lcd.createChar(3, (uint8_t*)&new_char);
1399
 
1400
        i = x * pixels_per_x_mesh_pnt - suppress_x_offset;
1401
        j = y * pixels_per_y_mesh_pnt - suppress_y_offset;
1402
        upper_left = pixel_location(i, j);
1403
 
1404
        k = (x + 1) * pixels_per_x_mesh_pnt - 1 - suppress_x_offset;
1405
        l = (y + 1) * pixels_per_y_mesh_pnt - 1 - suppress_y_offset;
1406
        lower_right = pixel_location(k, l);
1407
 
1408
        bottom_right_corner = pixel_location(x_map_pixels, y_map_pixels);
1409
 
1410
        /**
1411
         * First, handle the simple case where everything is within a single character cell.
1412
         * If part of the Mesh Plot is outside of this character cell, we will follow up
1413
         * and deal with that next.
1414
         */
1415
 
1416
        //dump_custom_char("at entry:", &new_char);
1417
 
1418
        clear_custom_char(&new_char);
1419
        const uint8_t ypix = MIN(upper_left.y_pixel_offset + pixels_per_y_mesh_pnt, ULTRA_Y_PIXELS_PER_CHAR);
1420
        for (j = upper_left.y_pixel_offset; j < ypix; j++) {
1421
          i = upper_left.x_pixel_mask;
1422
          for (k = 0; k < pixels_per_x_mesh_pnt; k++) {
1423
            new_char.custom_char_bits[j] |= i;
1424
            i >>= 1;
1425
          }
1426
        }
1427
        //dump_custom_char("after loops:", &new_char);
1428
 
1429
        add_edges_to_custom_char(&new_char, &upper_left, &lower_right, &bottom_right_corner, TOP_LEFT);
1430
        //dump_custom_char("after add edges", &new_char);
1431
        lcd.createChar(4, (uint8_t*)&new_char);
1432
 
1433
        lcd.setCursor(upper_left.column, upper_left.row);
1434
        lcd.write(0x04);
1435
        //dump_custom_char("after lcd update:", &new_char);
1436
 
1437
        /**
1438
         * Next, check for two side by side character cells being used to display the Mesh Point
1439
         * If found...  do the right hand character cell next.
1440
         */
1441
        if (upper_left.column == lower_right.column - 1) {
1442
          l = upper_left.x_pixel_offset;
1443
          clear_custom_char(&new_char);
1444
          for (j = upper_left.y_pixel_offset; j < ypix; j++) {
1445
            i = _BV(ULTRA_X_PIXELS_PER_CHAR - 1);                  // Fill in the left side of the right character cell
1446
            for (k = 0; k < pixels_per_x_mesh_pnt - 1 - l; k++) {
1447
              new_char.custom_char_bits[j] |= i;
1448
              i >>= 1;
1449
            }
1450
          }
1451
          add_edges_to_custom_char(&new_char, &upper_left, &lower_right, &bottom_right_corner, TOP_RIGHT);
1452
 
1453
          lcd.createChar(5, (uint8_t *) &new_char);
1454
 
1455
          lcd.setCursor(lower_right.column, upper_left.row);
1456
          lcd.write(0x05);
1457
        }
1458
 
1459
        /**
1460
         * Next, check for two character cells stacked on top of each other being used to display the Mesh Point
1461
         */
1462
        if (upper_left.row == lower_right.row - 1) {
1463
          l = ULTRA_Y_PIXELS_PER_CHAR - upper_left.y_pixel_offset;  // Number of pixel rows in top character cell
1464
          k = pixels_per_y_mesh_pnt - l;                            // Number of pixel rows in bottom character cell
1465
          clear_custom_char(&new_char);
1466
          for (j = 0; j < k; j++) {
1467
            i = upper_left.x_pixel_mask;
1468
            for (m = 0; m < pixels_per_x_mesh_pnt; m++) {           // Fill in the top side of the bottom character cell
1469
              new_char.custom_char_bits[j] |= i;
1470
              if (!(i >>= 1)) break;
1471
            }
1472
          }
1473
          add_edges_to_custom_char(&new_char, &upper_left, &lower_right, &bottom_right_corner, LOWER_LEFT);
1474
          lcd.createChar(6, (uint8_t *) &new_char);
1475
 
1476
          lcd.setCursor(upper_left.column, lower_right.row);
1477
          lcd.write(0x06);
1478
        }
1479
 
1480
        /**
1481
         * Next, check for four character cells being used to display the Mesh Point.  If that is
1482
         * what is here, we work to fill in the character cell that is down one and to the right one
1483
         * from the upper_left character cell.
1484
         */
1485
 
1486
        if (upper_left.column == lower_right.column - 1 && upper_left.row == lower_right.row - 1) {
1487
          l = ULTRA_Y_PIXELS_PER_CHAR - upper_left.y_pixel_offset;   // Number of pixel rows in top character cell
1488
          k = pixels_per_y_mesh_pnt - l;                             // Number of pixel rows in bottom character cell
1489
          clear_custom_char(&new_char);
1490
          for (j = 0; j < k; j++) {
1491
            l = upper_left.x_pixel_offset;
1492
            i = _BV(ULTRA_X_PIXELS_PER_CHAR - 1);                    // Fill in the left side of the right character cell
1493
            for (m = 0; m < pixels_per_x_mesh_pnt - 1 - l; m++) {    // Fill in the top side of the bottom character cell
1494
              new_char.custom_char_bits[j] |= i;
1495
              i >>= 1;
1496
            }
1497
          }
1498
          add_edges_to_custom_char(&new_char, &upper_left, &lower_right, &bottom_right_corner, LOWER_RIGHT);
1499
          lcd.createChar(7, (uint8_t*)&new_char);
1500
 
1501
          lcd.setCursor(lower_right.column, lower_right.row);
1502
          lcd.write(0x07);
1503
        }
1504
 
1505
      #endif
1506
 
1507
      /**
1508
       * Print plot position
1509
       */
1510
      lcd.setCursor(_LCD_W_POS, 0);
1511
      lcd.write('(');
1512
      lcd.print(x);
1513
      lcd.write(',');
1514
      lcd.print(inverted_y);
1515
      lcd.write(')');
1516
 
1517
      #if LCD_HEIGHT <= 3   // 16x2 or 20x2 display
1518
 
1519
        /**
1520
         * Print Z values
1521
         */
1522
        _ZLABEL(_LCD_W_POS, 1);
1523
        if (!isnan(ubl.z_values[x][inverted_y]))
1524
          lcd.print(ftostr43sign(ubl.z_values[x][inverted_y]));
1525
        else
1526
          lcd_printPGM(PSTR(" -----"));
1527
 
1528
      #else                 // 16x4 or 20x4 display
1529
 
1530
        /**
1531
         * Show all values at right of screen
1532
         */
1533
        _XLABEL(_LCD_W_POS, 1);
1534
        lcd.print(ftostr52(LOGICAL_X_POSITION(pgm_read_float(&ubl._mesh_index_to_xpos[x]))));
1535
        _YLABEL(_LCD_W_POS, 2);
1536
        lcd.print(ftostr52(LOGICAL_Y_POSITION(pgm_read_float(&ubl._mesh_index_to_ypos[inverted_y]))));
1537
 
1538
        /**
1539
         * Show the location value
1540
         */
1541
        _ZLABEL(_LCD_W_POS, 3);
1542
        if (!isnan(ubl.z_values[x][inverted_y]))
1543
          lcd.print(ftostr43sign(ubl.z_values[x][inverted_y]));
1544
        else
1545
          lcd_printPGM(PSTR(" -----"));
1546
 
1547
      #endif // LCD_HEIGHT > 3
1548
    }
1549
 
1550
    void add_edges_to_custom_char(custom_char * const custom, coordinate * const ul, coordinate * const lr, coordinate * const brc, uint8_t cell_location) {
1551
      uint8_t i, k;
1552
      int16_t n_rows = lr->row    - ul->row    + 1,
1553
              n_cols = lr->column - ul->column + 1;
1554
 
1555
      /**
1556
       * Check if Top line of box needs to be filled in
1557
       */
1558
      if (ul->row == 0 && ((cell_location & TOP_LEFT) || (cell_location & TOP_RIGHT))) {   // Only fill in the top line for the top character cells
1559
 
1560
        if (n_cols == 1) {
1561
          if (ul->column != brc->column)
1562
            custom->custom_char_bits[0] = 0xFF;                             // Single column in middle
1563
          else
1564
            for (i = brc->x_pixel_offset; i < ULTRA_X_PIXELS_PER_CHAR; i++) // Single column on right side
1565
              SBI(custom->custom_char_bits[0], i);
1566
        }
1567
        else if ((cell_location & TOP_LEFT) || lr->column != brc->column)   // Multiple column in the middle or with right cell in middle
1568
          custom->custom_char_bits[0] = 0xFF;
1569
        else
1570
          for (i = brc->x_pixel_offset; i < ULTRA_X_PIXELS_PER_CHAR; i++)
1571
            SBI(custom->custom_char_bits[0], i);
1572
      }
1573
 
1574
      /**
1575
       * Check if left line of box needs to be filled in
1576
       */
1577
      if ((cell_location & TOP_LEFT) || (cell_location & LOWER_LEFT)) {
1578
        if (ul->column == 0) {                                              // Left column of characters on LCD Display
1579
          k = ul->row == brc->row ? brc->y_pixel_offset : ULTRA_Y_PIXELS_PER_CHAR; // If it isn't the last row... do the full character cell
1580
          for (i = 0; i < k; i++)
1581
            SBI(custom->custom_char_bits[i], ULTRA_X_PIXELS_PER_CHAR - 1);
1582
        }
1583
      }
1584
 
1585
      /**
1586
       * Check if bottom line of box needs to be filled in
1587
       */
1588
 
1589
      // Single row of mesh plot cells
1590
      if (n_rows == 1 /* && (cell_location == TOP_LEFT || cell_location == TOP_RIGHT) */ && ul->row == brc->row) {
1591
        if (n_cols == 1)                                                    // Single row, single column case
1592
          k = ul->column == brc->column ? brc->x_pixel_mask : 0x01;
1593
        else if (cell_location & TOP_RIGHT)                                 // Single row, multiple column case
1594
          k = lr->column == brc->column ? brc->x_pixel_mask : 0x01;
1595
        else                                                                // Single row, left of multiple columns
1596
          k = 0x01;
1597
        while (k < _BV(ULTRA_X_PIXELS_PER_CHAR)) {
1598
          custom->custom_char_bits[brc->y_pixel_offset] |= k;
1599
          k <<= 1;
1600
        }
1601
      }
1602
 
1603
      // Double row of characters on LCD Display
1604
      // And this is a bottom custom character
1605
      if (n_rows == 2 && (cell_location == LOWER_LEFT || cell_location == LOWER_RIGHT) && lr->row == brc->row) {
1606
        if (n_cols == 1)                                                  // Double row, single column case
1607
          k = ul->column == brc->column ? brc->x_pixel_mask : 0x01;
1608
        else if (cell_location & LOWER_RIGHT)                             // Double row, multiple column case
1609
          k = lr->column == brc->column ? brc->x_pixel_mask : 0x01;
1610
        else                                                              // Double row, left of multiple columns
1611
          k = 0x01;
1612
        while (k < _BV(ULTRA_X_PIXELS_PER_CHAR)) {
1613
          custom->custom_char_bits[brc->y_pixel_offset] |= k;
1614
          k <<= 1;
1615
        }
1616
      }
1617
 
1618
      /**
1619
       * Check if right line of box needs to be filled in
1620
       */
1621
      // Nothing to do if the lower right part of the mesh pnt isn't in the same column as the box line
1622
      if (lr->column == brc->column) {
1623
        // This mesh point is in the same character cell as the right box line
1624
        if (ul->column == brc->column || (cell_location & TOP_RIGHT) || (cell_location & LOWER_RIGHT)) {
1625
          // If not the last row... do the full character cell
1626
          k = ul->row == brc->row ? brc->y_pixel_offset : ULTRA_Y_PIXELS_PER_CHAR;
1627
          for (i = 0; i < k; i++) custom->custom_char_bits[i] |= brc->x_pixel_mask;
1628
        }
1629
      }
1630
    }
1631
 
1632
  #endif // AUTO_BED_LEVELING_UBL
1633
 
1634
#endif // ULTIPANEL
1635
 
1636
#endif // ULTRALCD_IMPL_HD44780_H