Subversion Repositories MK-Marlin

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 ron 1
/**
2
 * Lightweight Status Screen for the RepRapDiscount Full
3
 * Graphics Smart Controller (ST7920-based 128x64 LCD)
4
 *
5
 * (c) 2017 Aleph Objects, Inc.
6
 *
7
 * The code in this page is free software: you can
8
 * redistribute it and/or modify it under the terms of the GNU
9
 * General Public License (GNU GPL) as published by the Free Software
10
 * Foundation, either version 3 of the License, or (at your option)
11
 * any later version.  The code is distributed WITHOUT ANY WARRANTY;
12
 * without even the implied warranty of MERCHANTABILITY or FITNESS
13
 * FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
14
 *
15
 */
16
 
17
/**
18
 * Implementation of a Status Screen for the RepRapDiscount
19
 * Full Graphics Smart Controller using native ST7920 commands
20
 * instead of U8Glib.
21
 *
22
 * This alternative Status Screen makes use of the built-in character
23
 * generation capabilities of the ST7920 to update the Status Screen
24
 * with less SPI traffic and CPU use. In particular:
25
 *
26
 *  - The fan and bed animations are handled using custom characters
27
 *    that are stored in CGRAM. This allows for the animation to be
28
 *    updated by writing a single character to the text-buffer (DDRAM).
29
 *
30
 *  - All the information in the Status Screen is text that is written
31
 *    to DDRAM, so the work of generating the bitmaps is offloaded to
32
 *    the ST7920 rather than being render by U8Glib on the MCU.
33
 *
34
 *  - The graphics buffer (GDRAM) is only used for static graphics
35
 *    elements (nozzle and feedrate bitmaps) and for the progress
36
 *    bar, so updates are sporadic.
37
 */
38
 
39
#include "status_screen_lite_ST7920_class.h"
40
 
41
#include "duration_t.h"
42
 
43
#define BUFFER_WIDTH   256
44
#define BUFFER_HEIGHT  32
45
 
46
#define DDRAM_LINE_1   0x00
47
#define DDRAM_LINE_2   0x10
48
#define DDRAM_LINE_3   0x08
49
#define DDRAM_LINE_4   0x18
50
 
51
ST7920_Lite_Status_Screen::st7920_state_t ST7920_Lite_Status_Screen::current_bits;
52
 
53
void ST7920_Lite_Status_Screen::cmd(const uint8_t cmd) {
54
  if (!current_bits.synced || !current_bits.cmd) {
55
    current_bits.synced = true;
56
    current_bits.cmd    = true;
57
    sync_cmd();
58
  }
59
  write_byte(cmd);
60
}
61
 
62
void ST7920_Lite_Status_Screen::begin_data() {
63
  if (!current_bits.synced || current_bits.cmd) {
64
    current_bits.synced = true;
65
    current_bits.cmd    = false;
66
    sync_dat();
67
  }
68
}
69
 
70
void ST7920_Lite_Status_Screen::write_str(const char *str) {
71
  while (*str) write_byte(*str++);
72
}
73
 
74
void ST7920_Lite_Status_Screen::write_str(const char *str, uint8_t len) {
75
  while (*str && len--) write_byte(*str++);
76
}
77
 
78
void ST7920_Lite_Status_Screen::write_str_P(const char * const str) {
79
  const char *p_str = (const char *)str;
80
  while (char c = pgm_read_byte_near(p_str++)) write_byte(c);
81
}
82
 
83
void ST7920_Lite_Status_Screen::write_str(progmem_str str) {
84
  write_str_P((const char*)str);
85
}
86
 
87
void ST7920_Lite_Status_Screen::write_number(const int16_t value, const uint8_t digits/*=3*/) {
88
  char str[7];
89
  const char *fmt;
90
  switch (digits) {
91
    case 6: fmt = PSTR("%6d"); break;
92
    case 5: fmt = PSTR("%5d"); break;
93
    case 4: fmt = PSTR("%4d"); break;
94
    case 3: fmt = PSTR("%3d"); break;
95
    case 2: fmt = PSTR("%2d"); break;
96
    case 1: fmt = PSTR("%1d"); break;
97
  }
98
  sprintf_P(str, fmt, value);
99
  write_str(str);
100
}
101
 
102
void ST7920_Lite_Status_Screen::display_status(const bool display_on, const bool cursor_on, const bool blink_on) {
103
  extended_function_set(false);
104
  cmd(0b00001000 |
105
    (display_on ? 0b0100 : 0) |
106
    (cursor_on  ? 0b0010 : 0) |
107
    (blink_on   ? 0b0001 : 0)
108
  );
109
}
110
 
111
// Sets the extended and graphics bits simultaneously, regardless of
112
// the current state. This is a helper function for extended_function_set()
113
// and graphics()
114
void ST7920_Lite_Status_Screen::_extended_function_set(const bool extended, const bool graphics) {
115
  cmd(  0b00100000 |
116
    (extended   ? 0b00000100 : 0) |
117
    (graphics   ? 0b00000010 : 0)
118
  );
119
  current_bits.extended = extended;
120
  current_bits.graphics = graphics;
121
}
122
 
123
void ST7920_Lite_Status_Screen::extended_function_set(const bool extended) {
124
  if (extended != current_bits.extended)
125
    _extended_function_set(extended, current_bits.graphics);
126
}
127
 
128
void ST7920_Lite_Status_Screen::graphics(const bool graphics) {
129
  if (graphics != current_bits.graphics)
130
    _extended_function_set(current_bits.extended, graphics);
131
}
132
 
133
void ST7920_Lite_Status_Screen::entry_mode_select(const bool ac_increase, const bool shift) {
134
  extended_function_set(false);
135
  cmd(0b00000100 |
136
    (ac_increase ? 0b00000010 : 0) |
137
    (shift       ? 0b00000001 : 0)
138
  );
139
}
140
 
141
// Sets the sa bit regardless of the current state. This is a helper
142
// function for scroll_or_addr_select()
143
void ST7920_Lite_Status_Screen::_scroll_or_addr_select(const bool sa) {
144
  extended_function_set(true);
145
  cmd(0b00100010 |
146
    (sa   ? 0b000001 : 0)
147
  );
148
  current_bits.sa = sa;
149
}
150
 
151
void ST7920_Lite_Status_Screen::scroll_or_addr_select(const bool sa) {
152
  if (sa != current_bits.sa)
153
    _scroll_or_addr_select(sa);
154
}
155
 
156
void ST7920_Lite_Status_Screen::set_ddram_address(const uint8_t addr) {
157
  extended_function_set(false);
158
  cmd(0b10000000 | (addr & 0b00111111));
159
}
160
 
161
void ST7920_Lite_Status_Screen::set_cgram_address(const uint8_t addr) {
162
  extended_function_set(false);
163
  cmd(0b01000000 | (addr & 0b00111111));
164
}
165
 
166
void ST7920_Lite_Status_Screen::set_gdram_address(const uint8_t x, const uint8_t y) {
167
  extended_function_set(true);
168
  cmd(0b10000000 | (y & 0b01111111));
169
  cmd(0b10000000 | (x & 0b00001111));
170
}
171
 
172
void ST7920_Lite_Status_Screen::clear() {
173
  extended_function_set(false);
174
  cmd(0x00000001);
175
  delay(15);                 //delay for CGRAM clear
176
}
177
 
178
void ST7920_Lite_Status_Screen::home() {
179
  extended_function_set(false);
180
  cmd(0x00000010);
181
}
182
 
183
/* This fills the entire text buffer with spaces */
184
void ST7920_Lite_Status_Screen::clear_ddram() {
185
  set_ddram_address(DDRAM_LINE_1);
186
  begin_data();
187
  for (uint8_t i = 64; i--;) write_byte(' ');
188
}
189
 
190
/* This fills the entire graphics buffer with zeros */
191
void ST7920_Lite_Status_Screen::clear_gdram() {
192
  for (uint8_t y = 0; y < BUFFER_HEIGHT; y++) {
193
    set_gdram_address(0, y);
194
    begin_data();
195
    for (uint8_t i = (BUFFER_WIDTH) / 16; i--;) write_word(0);
196
  }
197
}
198
 
199
void ST7920_Lite_Status_Screen::load_cgram_icon(const uint16_t addr, const void *data) {
200
  const uint16_t *p_word = (const uint16_t *)data;
201
  set_cgram_address(addr);
202
  begin_data();
203
  for (uint8_t i = 16; i--;)
204
    write_word(pgm_read_word_near(p_word++));
205
}
206
 
207
/**
208
 * Draw an icon in GDRAM. Position specified in DDRAM
209
 * coordinates. i.e., X from 1 to 8, Y from 1 to 4.
210
 */
211
void ST7920_Lite_Status_Screen::draw_gdram_icon(uint8_t x, uint8_t y, const void *data) {
212
  const uint16_t *p_word = (const uint16_t *)data;
213
  if (y > 2) { // Handle display folding
214
    y -= 2;
215
    x += 8;
216
  }
217
  --x;
218
  --y;
219
  for (int i = 0; i < 16; i++) {
220
    set_gdram_address(x, i + y * 16);
221
    begin_data();
222
    write_word(pgm_read_word_near(p_word++));
223
  }
224
}
225
 
226
/************************** ICON DEFINITIONS *************************************/
227
 
228
#define CGRAM_ICON_1_ADDR 0x00
229
#define CGRAM_ICON_2_ADDR 0x10
230
#define CGRAM_ICON_3_ADDR 0x20
231
#define CGRAM_ICON_4_ADDR 0x30
232
 
233
#define CGRAM_ICON_1_WORD 0x00
234
#define CGRAM_ICON_2_WORD 0x02
235
#define CGRAM_ICON_3_WORD 0x04
236
#define CGRAM_ICON_4_WORD 0x06
237
 
238
const uint8_t degree_symbol_y_top = 1;
239
PROGMEM const uint8_t degree_symbol[] = {
240
  0b00110000,
241
  0b01001000,
242
  0b01001000,
243
  0b00110000,
244
};
245
 
246
const uint16_t nozzle_icon[] PROGMEM = {
247
  0b0000000000000000,
248
  0b0000000000000000,
249
  0b0000111111110000,
250
  0b0001111111111000,
251
  0b0001111111111000,
252
  0b0001111111111000,
253
  0b0000111111110000,
254
  0b0000111111110000,
255
  0b0001111111111000,
256
  0b0001111111111000,
257
  0b0001111111111000,
258
  0b0000011111100000,
259
  0b0000001111000000,
260
  0b0000000110000000,
261
  0b0000000000000000,
262
  0b0000000000000000
263
};
264
 
265
const uint16_t bed_icon[] PROGMEM = {
266
  0b0000000000000000,
267
  0b0000000000000000,
268
  0b0000000000000000,
269
  0b0000000000000000,
270
  0b0000000000000000,
271
  0b0000000000000000,
272
  0b0000000000000000,
273
  0b0000000000000000,
274
  0b0000000000000000,
275
  0b0000000000000000,
276
  0b0000000000000000,
277
  0b0111111111111110,
278
  0b0111111111111110,
279
  0b0110000000000110,
280
  0b0000000000000000,
281
  0b0000000000000000
282
};
283
 
284
const uint16_t heat1_icon[] PROGMEM = {
285
  0b0000000000000000,
286
  0b0010001000100000,
287
  0b0001000100010000,
288
  0b0000100010001000,
289
  0b0000100010001000,
290
  0b0001000100010000,
291
  0b0010001000100000,
292
  0b0010001000100000,
293
  0b0001000100010000,
294
  0b0000100010001000,
295
  0b0000000000000000,
296
  0b0000000000000000,
297
  0b0000000000000000,
298
  0b0000000000000000,
299
  0b0000000000000000,
300
  0b0000000000000000
301
};
302
 
303
const uint16_t heat2_icon[] PROGMEM = {
304
  0b0000000000000000,
305
  0b0000100010001000,
306
  0b0000100010001000,
307
  0b0001000100010000,
308
  0b0010001000100000,
309
  0b0010001000100000,
310
  0b0001000100010000,
311
  0b0000100010001000,
312
  0b0000100010001000,
313
  0b0001000100010000,
314
  0b0000000000000000,
315
  0b0000000000000000,
316
  0b0000000000000000,
317
  0b0000000000000000,
318
  0b0000000000000000,
319
  0b0000000000000000
320
};
321
 
322
const uint16_t fan1_icon[] PROGMEM = {
323
  0b0000000000000000,
324
  0b0111111111111110,
325
  0b0111000000001110,
326
  0b0110001111000110,
327
  0b0100001111000010,
328
  0b0100000110000010,
329
  0b0101100000011010,
330
  0b0101110110111010,
331
  0b0101100000011010,
332
  0b0100000110000010,
333
  0b0100001111000010,
334
  0b0110001111000110,
335
  0b0111000000001110,
336
  0b0111111111111110,
337
  0b0000000000000000,
338
  0b0000000000000000
339
};
340
 
341
const uint16_t fan2_icon[] PROGMEM = {
342
  0b0000000000000000,
343
  0b0111111111111110,
344
  0b0111000000001110,
345
  0b0110010000100110,
346
  0b0100111001110010,
347
  0b0101111001111010,
348
  0b0100110000110010,
349
  0b0100000110000010,
350
  0b0100110000110010,
351
  0b0101111001111010,
352
  0b0100111001110010,
353
  0b0110010000100110,
354
  0b0111000000001110,
355
  0b0111111111111110,
356
  0b0000000000000000,
357
  0b0000000000000000
358
};
359
 
360
const uint16_t feedrate_icon[] PROGMEM = {
361
  0b0000000000000000,
362
  0b0111111000000000,
363
  0b0110000000000000,
364
  0b0110000000000000,
365
  0b0110000000000000,
366
  0b0111111011111000,
367
  0b0110000011001100,
368
  0b0110000011001100,
369
  0b0110000011001100,
370
  0b0110000011111000,
371
  0b0000000011001100,
372
  0b0000000011001100,
373
  0b0000000011001100,
374
  0b0000000011001100,
375
  0b0000000000000000,
376
  0b0000000000000000
377
};
378
 
379
/************************** MAIN SCREEN *************************************/
380
 
381
// The ST7920 does not have a degree character, but we
382
// can fake it by writing it to GDRAM.
383
// This function takes as an argument character positions
384
// i.e x is [1-16], while the y position is [1-4]
385
void ST7920_Lite_Status_Screen::draw_degree_symbol(uint8_t x, uint8_t y, bool draw) {
386
  const uint8_t *p_bytes = degree_symbol;
387
    if (y > 2) {
388
      // Handle display folding
389
      y -= 2;
390
      x += 16;
391
    }
392
    x -= 1;
393
    y -= 1;
394
    const bool    oddChar = x & 1;
395
    const uint8_t x_word  = x >> 1;
396
    const uint8_t y_top   = degree_symbol_y_top;
397
    const uint8_t y_bot   = y_top + sizeof(degree_symbol)/sizeof(degree_symbol[0]);
398
    for (uint8_t i = y_top; i < y_bot; i++) {
399
      uint8_t byte = pgm_read_byte_near(p_bytes++);
400
      set_gdram_address(x_word,i+y*16);
401
      begin_data();
402
      if (draw) {
403
        write_byte(oddChar ? 0x00 : byte);
404
        write_byte(oddChar ? byte : 0x00);
405
      }
406
      else
407
        write_word(0x0000);
408
    }
409
}
410
 
411
void ST7920_Lite_Status_Screen::draw_static_elements() {
412
  scroll_or_addr_select(0);
413
 
414
  // Load the animated bed and fan icons
415
  load_cgram_icon(CGRAM_ICON_1_ADDR, heat1_icon);
416
  load_cgram_icon(CGRAM_ICON_2_ADDR, heat2_icon);
417
  load_cgram_icon(CGRAM_ICON_3_ADDR, fan1_icon);
418
  load_cgram_icon(CGRAM_ICON_4_ADDR, fan2_icon);
419
 
420
  // Draw the static icons in GDRAM
421
  draw_gdram_icon(1, 1, nozzle_icon);
422
  #if HOTENDS > 1
423
    draw_gdram_icon(1,2,nozzle_icon);
424
    draw_gdram_icon(1,3,bed_icon);
425
  #else
426
    draw_gdram_icon(1,2,bed_icon);
427
  #endif
428
  draw_gdram_icon(6,2,feedrate_icon);
429
 
430
  // Draw the initial fan icon
431
  draw_fan_icon(false);
432
}
433
 
434
/**
435
 * Although this is undocumented, the ST7920 allows the character
436
 * data buffer (DDRAM) to be used in conjunction with the graphics
437
 * bitmap buffer (CGRAM). The contents of the graphics buffer is
438
 * XORed with the data from the character generator. This allows
439
 * us to make the progess bar out of graphical data (the bar) and
440
 * text data (the percentage).
441
 */
442
void ST7920_Lite_Status_Screen::draw_progress_bar(const uint8_t value) {
443
  #if HOTENDS == 1
444
    // If we have only one extruder, draw a long progress bar on the third line
445
    const uint8_t top     = 1,         // Top in pixels
446
                  bottom  = 13,        // Bottom in pixels
447
                  left    = 12,        // Left edge, in 16-bit words
448
                  width   = 4;         // Width of progress bar, in 16-bit words
449
  #else
450
    const uint8_t top     = 16 + 1,
451
                  bottom  = 16 + 13,
452
                  left    = 5,
453
                  width   = 3;
454
  #endif
455
  const uint8_t char_pcnt  = 100 / width; // How many percent does each 16-bit word represent?
456
 
457
  // Draw the progress bar as a bitmap in CGRAM
458
  for (uint8_t y = top; y <= bottom; y++) {
459
    set_gdram_address(left, y);
460
    begin_data();
461
    for (uint8_t x = 0; x < width; x++) {
462
      uint16_t gfx_word = 0x0000;
463
      if ((x + 1) * char_pcnt <= value)
464
        gfx_word = 0xFFFF;                                              // Draw completely filled bytes
465
      else if ((x * char_pcnt) < value)
466
        gfx_word = int(0x8000) >> (value % char_pcnt) * 16 / char_pcnt; // Draw partially filled bytes
467
 
468
      // Draw the frame around the progress bar
469
      if (y == top || y == bottom)
470
        gfx_word = 0xFFFF;        // Draw top/bottom border
471
      else if (x == width - 1)
472
        gfx_word |= 0x0001;       // Draw right border
473
      else if (x == 0)
474
        gfx_word |= 0x8000;       // Draw left border
475
      write_word(gfx_word);
476
    }
477
  }
478
 
479
  // Draw the percentage as text in DDRAM
480
  #if HOTENDS == 1
481
    set_ddram_address(DDRAM_LINE_3 + 4);
482
    begin_data();
483
    write_byte(' ');
484
  #else
485
    set_ddram_address(DDRAM_LINE_2 + left);
486
    begin_data();
487
  #endif
488
 
489
  // Draw centered
490
  if (value > 9) {
491
    write_number(value, 4);
492
    write_str(F("% "));
493
  }
494
  else {
495
    write_number(value, 3);
496
    write_str(F("%  "));
497
  }
498
}
499
 
500
void ST7920_Lite_Status_Screen::draw_fan_icon(const bool whichIcon) {
501
  set_ddram_address(DDRAM_LINE_1 + 5);
502
  begin_data();
503
  write_word(whichIcon ? CGRAM_ICON_3_WORD : CGRAM_ICON_4_WORD);
504
}
505
 
506
void ST7920_Lite_Status_Screen::draw_heat_icon(const bool whichIcon, const bool heating) {
507
  set_ddram_address(
508
    #if HOTENDS == 1
509
      DDRAM_LINE_2
510
    #else
511
      DDRAM_LINE_3
512
    #endif
513
  );
514
  begin_data();
515
  if (heating)
516
    write_word(whichIcon ? CGRAM_ICON_1_WORD : CGRAM_ICON_2_WORD);
517
  else {
518
    write_byte(' ');
519
    write_byte(' ');
520
  }
521
}
522
 
523
#define FAR(a,b) (((a > b) ? (a-b) : (b-a)) > 2)
524
 
525
static struct {
526
  bool E1_show_target  : 1;
527
  bool E2_show_target  : 1;
528
  #if HAS_HEATED_BED
529
    bool bed_show_target : 1;
530
  #endif
531
} display_state = {
532
  true, true
533
  #if HAS_HEATED_BED
534
    , true
535
  #endif
536
};
537
 
538
void ST7920_Lite_Status_Screen::draw_temps(uint8_t line, const int16_t temp, const int16_t target, bool showTarget, bool targetStateChange) {
539
  switch (line) {
540
    case 1: set_ddram_address(DDRAM_LINE_1 + 1); break;
541
    case 2: set_ddram_address(DDRAM_LINE_2 + 1); break;
542
    case 3: set_ddram_address(DDRAM_LINE_3 + 1); break;
543
    case 4: set_ddram_address(DDRAM_LINE_3 + 1); break;
544
  }
545
  begin_data();
546
  write_number(temp);
547
 
548
  if (showTarget) {
549
    write_str(F("\x1A"));
550
    write_number(target);
551
  };
552
 
553
  if (targetStateChange) {
554
    if (!showTarget) write_str(F("    "));
555
    draw_degree_symbol(6,  line, !showTarget);
556
    draw_degree_symbol(10, line, showTarget);
557
  }
558
}
559
 
560
void ST7920_Lite_Status_Screen::draw_extruder_1_temp(const int16_t temp, const int16_t target, bool forceUpdate) {
561
  const bool show_target = target && FAR(temp, target);
562
  draw_temps(1, temp, target, show_target, display_state.E1_show_target != show_target || forceUpdate);
563
  display_state.E1_show_target = show_target;
564
}
565
 
566
void ST7920_Lite_Status_Screen::draw_extruder_2_temp(const int16_t temp, const int16_t target, bool forceUpdate) {
567
  const bool show_target = target && FAR(temp, target);
568
  draw_temps(2, temp, target, show_target, display_state.E2_show_target != show_target || forceUpdate);
569
  display_state.E2_show_target = show_target;
570
}
571
 
572
#if HAS_HEATED_BED
573
  void ST7920_Lite_Status_Screen::draw_bed_temp(const int16_t temp, const int16_t target, bool forceUpdate) {
574
    const bool show_target = target && FAR(temp, target);
575
    draw_temps(2
576
      #if HOTENDS > 1
577
        + 1
578
      #endif
579
      , temp, target, show_target, display_state.bed_show_target != show_target || forceUpdate
580
    );
581
    display_state.bed_show_target = show_target;
582
  }
583
#endif
584
 
585
void ST7920_Lite_Status_Screen::draw_fan_speed(const uint8_t value) {
586
  set_ddram_address(DDRAM_LINE_1 + 6);
587
  begin_data();
588
  write_number(value, 3);
589
  write_byte('%');
590
}
591
 
592
void ST7920_Lite_Status_Screen::draw_print_time(const duration_t &elapsed) {
593
  #if HOTENDS == 1
594
    set_ddram_address(DDRAM_LINE_3);
595
  #else
596
    set_ddram_address(DDRAM_LINE_3 + 5);
597
  #endif
598
  char str[7];
599
  str[elapsed.toDigital(str)] = ' ';
600
  begin_data();
601
  write_str(str, 6);
602
}
603
 
604
void ST7920_Lite_Status_Screen::draw_feedrate_percentage(const uint8_t percentage) {
605
  // We only have enough room for the feedrate when
606
  // we have one extruder
607
  #if HOTENDS == 1
608
    set_ddram_address(DDRAM_LINE_2 + 6);
609
    begin_data();
610
    write_number(percentage, 3);
611
    write_byte('%');
612
  #endif
613
}
614
 
615
void ST7920_Lite_Status_Screen::draw_status_message(const char *str) {
616
  set_ddram_address(DDRAM_LINE_4);
617
  begin_data();
618
  const uint8_t lcd_len = 16;
619
  #if ENABLED(STATUS_MESSAGE_SCROLLING)
620
 
621
    uint8_t slen = utf8_strlen(str);
622
 
623
    // If the string fits into the LCD, just print it and do not scroll it
624
    if (slen <= lcd_len) {
625
 
626
      // The string isn't scrolling and may not fill the screen
627
      write_str(str);
628
 
629
      // Fill the rest with spaces
630
      while (slen < lcd_len) {
631
        write_byte(' ');
632
        ++slen;
633
      }
634
    }
635
    else {
636
      // String is larger than the available space in screen.
637
 
638
      // Get a pointer to the next valid UTF8 character
639
      const char *stat = str + status_scroll_offset;
640
 
641
      // Get the string remaining length
642
      const uint8_t rlen = utf8_strlen(stat);
643
 
644
      // If we have enough characters to display
645
      if (rlen >= lcd_len) {
646
        // The remaining string fills the screen - Print it
647
        write_str(stat, lcd_len);
648
      }
649
      else {
650
        // The remaining string does not completely fill the screen
651
        write_str(stat);                        // The string leaves space
652
        uint8_t chars = lcd_len - rlen;         // Amount of space left in characters
653
 
654
        write_byte('.');                        // Always at 1+ spaces left, draw a dot
655
        if (--chars) {                          // Draw a second dot if there's space
656
          write_byte('.');
657
          if (--chars)
658
            write_str(str, chars);              // Print a second copy of the message
659
        }
660
      }
661
 
662
      // Adjust by complete UTF8 characters
663
      if (status_scroll_offset < slen) {
664
        status_scroll_offset++;
665
        while (!START_OF_UTF8_CHAR(str[status_scroll_offset]))
666
          status_scroll_offset++;
667
      }
668
      else
669
        status_scroll_offset = 0;
670
    }
671
  #else
672
    // Get the UTF8 character count of the string
673
    uint8_t slen = utf8_strlen(str);
674
 
675
    // Just print the string to the LCD
676
    write_str(str, lcd_len);
677
 
678
    // Fill the rest with spaces if there are missing spaces
679
    while (slen < lcd_len) {
680
      write_byte(' ');
681
      ++slen;
682
    }
683
  #endif
684
}
685
 
686
void ST7920_Lite_Status_Screen::draw_position(const float x, const float y, const float z, bool position_known) {
687
  char str[7];
688
  set_ddram_address(DDRAM_LINE_4);
689
  begin_data();
690
 
691
  // If position is unknown, flash the labels.
692
  const unsigned char alt_label = position_known ? 0 : (lcd_blink() ? ' ' : 0);
693
 
694
  dtostrf(x, -4, 0, str);
695
  write_byte(alt_label ? alt_label : 'X');
696
  write_str(str, 4);
697
 
698
  dtostrf(y, -4, 0, str);
699
  write_byte(alt_label ? alt_label : 'Y');
700
  write_str(str, 4);
701
 
702
  dtostrf(z, -5, 1, str);
703
  write_byte(alt_label ? alt_label : 'Z');
704
  write_str(str, 5);
705
}
706
 
707
bool ST7920_Lite_Status_Screen::indicators_changed() {
708
  // We only add the target temperatures to the checksum
709
  // because the actual temps fluctuate so by updating
710
  // them only during blinks we gain a bit of stability.
711
  const bool       blink             = lcd_blink();
712
  const uint8_t    feedrate_perc     = feedrate_percentage;
713
  const uint8_t    fan_speed         = ((fanSpeeds[0] + 1) * 100) / 256;
714
  const int16_t    extruder_1_target = thermalManager.degTargetHotend(0);
715
  #if HOTENDS > 1
716
    const int16_t  extruder_2_target = thermalManager.degTargetHotend(1);
717
  #endif
718
  #if HAS_HEATED_BED
719
    const int16_t  bed_target        = thermalManager.degTargetBed();
720
  #endif
721
  static uint16_t last_checksum = 0;
722
  const uint16_t checksum = blink ^ feedrate_perc ^ fan_speed ^ extruder_1_target
723
    #if HOTENDS > 1
724
      ^ extruder_2_target
725
    #endif
726
    #if HAS_HEATED_BED
727
      ^ bed_target
728
    #endif
729
  ;
730
  if (last_checksum == checksum) return false;
731
  last_checksum = checksum;
732
  return true;
733
}
734
 
735
void ST7920_Lite_Status_Screen::update_indicators(const bool forceUpdate) {
736
  if (forceUpdate || indicators_changed()) {
737
    const bool       blink             = lcd_blink();
738
    const duration_t elapsed           = print_job_timer.duration();
739
    const uint8_t    feedrate_perc     = feedrate_percentage;
740
    const uint8_t    fan_speed         = ((fanSpeeds[0] + 1) * 100) / 256;
741
    const int16_t    extruder_1_temp   = thermalManager.degHotend(0),
742
                     extruder_1_target = thermalManager.degTargetHotend(0);
743
    #if HOTENDS > 1
744
      const int16_t  extruder_2_temp   = thermalManager.degHotend(1),
745
                     extruder_2_target = thermalManager.degTargetHotend(1);
746
    #endif
747
    #if HAS_HEATED_BED
748
      const int16_t  bed_temp          = thermalManager.degBed(),
749
                     bed_target        = thermalManager.degTargetBed();
750
    #endif
751
 
752
    draw_extruder_1_temp(extruder_1_temp, extruder_1_target, forceUpdate);
753
    #if HOTENDS > 1
754
      draw_extruder_2_temp(extruder_2_temp, extruder_2_target, forceUpdate);
755
    #endif
756
    #if HAS_HEATED_BED
757
      draw_bed_temp(bed_temp, bed_target, forceUpdate);
758
    #endif
759
    draw_fan_speed(fan_speed);
760
    draw_print_time(elapsed);
761
    draw_feedrate_percentage(feedrate_perc);
762
 
763
    // Update the fan and bed animations
764
    if (fan_speed > 0) draw_fan_icon(blink);
765
    #if HAS_HEATED_BED
766
      if (bed_target > 0)
767
        draw_heat_icon(blink, true);
768
      else
769
        draw_heat_icon(false, false);
770
    #endif
771
  }
772
}
773
 
774
bool ST7920_Lite_Status_Screen::position_changed() {
775
  const float x_pos = current_position[X_AXIS],
776
              y_pos = current_position[Y_AXIS],
777
              z_pos = current_position[Z_AXIS];
778
  const uint8_t checksum = uint8_t(x_pos) ^ uint8_t(y_pos) ^ uint8_t(z_pos);
779
 
780
  static uint8_t last_checksum = 0;
781
  if (last_checksum == checksum) return false;
782
  last_checksum = checksum;
783
  return true;
784
}
785
 
786
bool ST7920_Lite_Status_Screen::status_changed() {
787
  uint8_t checksum = 0;
788
  for (const char *p = lcd_status_message; *p; p++) checksum ^= *p;
789
  static uint8_t last_checksum = 0;
790
  if (last_checksum == checksum) return false;
791
  last_checksum = checksum;
792
  return true;
793
}
794
 
795
bool ST7920_Lite_Status_Screen::blink_changed() {
796
  static uint8_t last_blink = 0;
797
  const bool blink = lcd_blink();
798
  if (last_blink == blink) return false;
799
  last_blink = blink;
800
  return true;
801
}
802
 
803
#ifndef STATUS_EXPIRE_SECONDS
804
  #define STATUS_EXPIRE_SECONDS 20
805
#endif
806
 
807
void ST7920_Lite_Status_Screen::update_status_or_position(bool forceUpdate) {
808
 
809
  #if STATUS_EXPIRE_SECONDS
810
    static uint8_t countdown = 0;
811
  #endif
812
 
813
  /**
814
   * There is only enough room in the display for either the
815
   * status message or the position, not both, so we choose
816
   * one or another. Whenever the status message changes,
817
   * we show it for a number of consecutive seconds, but
818
   * then go back to showing the position as soon as the
819
   * head moves, i.e:
820
   *
821
   *    countdown > 1    -- Show status
822
   *    countdown = 1    -- Show status, until movement
823
   *    countdown = 0    -- Show position
824
   *
825
   * If STATUS_EXPIRE_SECONDS is zero, the position display
826
   * will be disabled and only the status will be shown.
827
   */
828
  if (forceUpdate || status_changed()) {
829
    #if ENABLED(STATUS_MESSAGE_SCROLLING)
830
      status_scroll_offset = 0;
831
    #endif
832
    #if STATUS_EXPIRE_SECONDS
833
      countdown = lcd_status_message[0] ? STATUS_EXPIRE_SECONDS : 0;
834
    #endif
835
    draw_status_message(lcd_status_message);
836
    blink_changed(); // Clear changed flag
837
  }
838
  #if !STATUS_EXPIRE_SECONDS
839
    #if ENABLED(STATUS_MESSAGE_SCROLLING)
840
      else
841
        draw_status_message(lcd_status_message);
842
    #endif
843
  #else
844
    else if (countdown > 1 && blink_changed()) {
845
      countdown--;
846
      #if ENABLED(STATUS_MESSAGE_SCROLLING)
847
        draw_status_message(lcd_status_message);
848
      #endif
849
    }
850
    else if (countdown > 0 && blink_changed()) {
851
      if (position_changed()) {
852
        countdown--;
853
        forceUpdate = true;
854
      }
855
      #if ENABLED(STATUS_MESSAGE_SCROLLING)
856
        draw_status_message(lcd_status_message);
857
      #endif
858
    }
859
    if (countdown == 0 && (forceUpdate || position_changed() ||
860
      #if DISABLED(DISABLE_REDUCED_ACCURACY_WARNING)
861
        blink_changed()
862
      #endif
863
    )) {
864
      draw_position(
865
        current_position[X_AXIS],
866
        current_position[Y_AXIS],
867
        current_position[Z_AXIS],
868
        #if ENABLED(DISABLE_REDUCED_ACCURACY_WARNING)
869
          true
870
        #else
871
          all_axes_known()
872
        #endif
873
      );
874
    }
875
  #endif
876
}
877
 
878
void ST7920_Lite_Status_Screen::update_progress(const bool forceUpdate) {
879
  #if ENABLED(LCD_SET_PROGRESS_MANUALLY) || ENABLED(SDSUPPORT)
880
 
881
    #if DISABLED(LCD_SET_PROGRESS_MANUALLY)
882
      uint8_t progress_bar_percent = 0;
883
    #endif
884
 
885
    #if ENABLED(SDSUPPORT)
886
      // Progress bar % comes from SD when actively printing
887
      if (IS_SD_PRINTING()) progress_bar_percent = card.percentDone();
888
    #endif
889
 
890
    // Since the progress bar involves writing
891
    // quite a few bytes to GDRAM, only do this
892
    // when an update is actually necessary.
893
 
894
    static uint8_t last_progress = 0;
895
    if (!forceUpdate && last_progress == progress_bar_percent) return;
896
    last_progress = progress_bar_percent;
897
 
898
    draw_progress_bar(progress_bar_percent);
899
 
900
  #else
901
 
902
    UNUSED(forceUpdate);
903
 
904
  #endif // LCD_SET_PROGRESS_MANUALLY || SDSUPPORT
905
}
906
 
907
void ST7920_Lite_Status_Screen::update(const bool forceUpdate) {
908
  cs();
909
  update_indicators(forceUpdate);
910
  update_status_or_position(forceUpdate);
911
  update_progress(forceUpdate);
912
  ncs();
913
}
914
 
915
void ST7920_Lite_Status_Screen::reset_state_from_unknown() {
916
  _extended_function_set(true, true); // Do it twice as only one bit
917
  _extended_function_set(true, true); // get set at a time.
918
  _scroll_or_addr_select(false);
919
}
920
 
921
void ST7920_Lite_Status_Screen::on_entry() {
922
  cs();
923
  reset_state_from_unknown();
924
  clear();
925
  clear_gdram();
926
  draw_static_elements();
927
  update(true);
928
  ncs();
929
}
930
 
931
void ST7920_Lite_Status_Screen::on_exit() {
932
  cs();
933
  clear();
934
  _extended_function_set(true, true); // Restore state to what u8g expects.
935
  ncs();
936
}
937
 
938
// This is called prior to the KILL screen to
939
// clear the screen so we don't end up with a
940
// garbled display.
941
void ST7920_Lite_Status_Screen::clear_text_buffer() {
942
  cs();
943
  reset_state_from_unknown();
944
  clear();
945
  _extended_function_set(true, true); // Restore state to what u8g expects.
946
  ncs();
947
}
948
 
949
static void lcd_implementation_status_screen() {
950
  ST7920_Lite_Status_Screen::update(false);
951
}
952
 
953
/**
954
 * In order to properly update the lite Status Screen,
955
 * we must know when we have entered and left the
956
 * Status Screen. Since the ultralcd code is not
957
 * set up for doing this, we call this function before
958
 * each update indicating whether the current screen
959
 * is the Status Screen.
960
 *
961
 * This function keeps track of whether we have left or
962
 * entered the Status Screen and calls the on_entry()
963
 * and on_exit() methods for cleanup.
964
 */
965
static void lcd_in_status(const bool inStatus) {
966
  static bool lastInStatus = false;
967
  if (lastInStatus == inStatus) return;
968
  if ((lastInStatus = inStatus))
969
    ST7920_Lite_Status_Screen::on_entry();
970
  else
971
    ST7920_Lite_Status_Screen::on_exit();
972
}