Lightweight status screen for ST7920

- This status screen uses the ST7920 character generator to greatly
  reduce SPI traffic and MCU load when updating the status screen.

- Has been tested with the RepRapDiscount Full Graphics Smart Controller
  but should work with any LCD that uses an ST7920 or fully compatible
  controller.
2.0.x
Marcio Teixeira committed by Scott Lahteine
parent 59c98d6fd5
commit 532bb3aaa1

@ -236,7 +236,7 @@ script:
# #
- restore_configs - restore_configs
- opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT - opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT
- opt_enable_adv SDCARD_SORT_ALPHA STATUS_MESSAGE_SCROLLING SCROLL_LONG_FILENAMES - opt_enable_adv SDCARD_SORT_ALPHA STATUS_MESSAGE_SCROLLING SCROLL_LONG_FILENAMES LIGHTWEIGHT_UI
- build_marlin_pio ${TRAVIS_BUILD_DIR} ${TEST_PLATFORM} - build_marlin_pio ${TRAVIS_BUILD_DIR} ${TEST_PLATFORM}
# #
# REPRAPWORLD_KEYPAD # REPRAPWORLD_KEYPAD

@ -656,6 +656,22 @@
// Swap the CW/CCW indicators in the graphics overlay // Swap the CW/CCW indicators in the graphics overlay
//#define OVERLAY_GFX_REVERSE //#define OVERLAY_GFX_REVERSE
#if ENABLED(U8GLIB_ST7920)
/**
* ST7920-based LCDs can emulate a 16 x 4 character display using
* the ST7920 character-generator for very fast screen updates.
* Enable LIGHTWEIGHT_UI to use this special display mode.
*
* Since LIGHTWEIGHT_UI has limited space, the position and status
* message occupy the same line. Set STATUS_EXPIRE_SECONDS to the
* length of time to display the status message before clearing.
*/
//#define LIGHTWEIGHT_UI
#if ENABLED(LIGHTWEIGHT_UI)
#define STATUS_EXPIRE_SECONDS 20
#endif
#endif
#endif // DOGLCD #endif // DOGLCD
// @section safety // @section safety

@ -656,6 +656,22 @@
// Swap the CW/CCW indicators in the graphics overlay // Swap the CW/CCW indicators in the graphics overlay
//#define OVERLAY_GFX_REVERSE //#define OVERLAY_GFX_REVERSE
#if ENABLED(U8GLIB_ST7920)
/**
* ST7920-based LCDs can emulate a 16 x 4 character display using
* the ST7920 character-generator for very fast screen updates.
* Enable LIGHTWEIGHT_UI to use this special display mode.
*
* Since LIGHTWEIGHT_UI has limited space, the position and status
* message occupy the same line. Set STATUS_EXPIRE_SECONDS to the
* length of time to display the status message before clearing.
*/
//#define LIGHTWEIGHT_UI
#if ENABLED(LIGHTWEIGHT_UI)
#define STATUS_EXPIRE_SECONDS 20
#endif
#endif
#endif // DOGLCD #endif // DOGLCD
// @section safety // @section safety

@ -0,0 +1,442 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* status_screen_DOGM.h
*
* Standard Status Screen for Graphical Display
*/
#ifndef _STATUS_SCREEN_DOGM_H_
#define _STATUS_SCREEN_DOGM_H_
FORCE_INLINE void _draw_centered_temp(const int16_t temp, const uint8_t x, const uint8_t y) {
const char * const str = itostr3(temp);
u8g.setPrintPos(x - (str[0] != ' ' ? 0 : str[1] != ' ' ? 1 : 2) * DOG_CHAR_WIDTH / 2, y);
lcd_print(str);
lcd_printPGM(PSTR(LCD_STR_DEGREE " "));
}
#ifndef HEAT_INDICATOR_X
#define HEAT_INDICATOR_X 8
#endif
FORCE_INLINE void _draw_heater_status(const uint8_t x, const int8_t heater, const bool blink) {
#if !HEATER_IDLE_HANDLER
UNUSED(blink);
#endif
#if HAS_TEMP_BED
const bool isBed = heater < 0;
#else
constexpr bool isBed = false;
#endif
if (PAGE_UNDER(7)) {
#if HEATER_IDLE_HANDLER
const bool is_idle = (!isBed ? thermalManager.is_heater_idle(heater) :
#if HAS_TEMP_BED
thermalManager.is_bed_idle()
#else
false
#endif
);
if (blink || !is_idle)
#endif
_draw_centered_temp((isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater)) + 0.5, x, 7); }
if (PAGE_CONTAINS(21, 28))
_draw_centered_temp((isBed ? thermalManager.degBed() : thermalManager.degHotend(heater)) + 0.5, x, 28);
if (PAGE_CONTAINS(17, 20)) {
const uint8_t h = isBed ? 7 : HEAT_INDICATOR_X,
y = isBed ? 18 : 17;
if (isBed ? thermalManager.isHeatingBed() : thermalManager.isHeatingHotend(heater)) {
u8g.setColorIndex(0); // white on black
u8g.drawBox(x + h, y, 2, 2);
u8g.setColorIndex(1); // black on white
}
else {
u8g.drawBox(x + h, y, 2, 2);
}
}
}
FORCE_INLINE void _draw_axis_label(const AxisEnum axis, const char* const pstr, const bool blink) {
if (blink)
lcd_printPGM(pstr);
else {
if (!axis_homed[axis])
u8g.print('?');
else {
#if DISABLED(HOME_AFTER_DEACTIVATE) && DISABLED(DISABLE_REDUCED_ACCURACY_WARNING)
if (!axis_known_position[axis])
u8g.print(' ');
else
#endif
lcd_printPGM(pstr);
}
}
}
inline void lcd_implementation_status_message(const bool blink) {
#if ENABLED(STATUS_MESSAGE_SCROLLING)
static bool last_blink = false;
const uint8_t slen = lcd_strlen(lcd_status_message);
const char *stat = lcd_status_message + status_scroll_pos;
if (slen <= LCD_WIDTH)
lcd_print_utf(stat); // The string isn't scrolling
else {
if (status_scroll_pos <= slen - LCD_WIDTH)
lcd_print_utf(stat); // The string fills the screen
else {
uint8_t chars = LCD_WIDTH;
if (status_scroll_pos < slen) { // First string still visible
lcd_print_utf(stat); // The string leaves space
chars -= slen - status_scroll_pos; // Amount of space left
}
u8g.print('.'); // Always at 1+ spaces left, draw a dot
if (--chars) {
if (status_scroll_pos < slen + 1) // Draw a second dot if there's space
--chars, u8g.print('.');
if (chars) lcd_print_utf(lcd_status_message, chars); // Print a second copy of the message
}
}
if (last_blink != blink) {
last_blink = blink;
// Skip any non-printing bytes
if (status_scroll_pos < slen) while (!PRINTABLE(lcd_status_message[status_scroll_pos])) status_scroll_pos++;
if (++status_scroll_pos >= slen + 2) status_scroll_pos = 0;
}
}
#else
UNUSED(blink);
lcd_print_utf(lcd_status_message);
#endif
}
static void lcd_implementation_status_screen() {
const bool blink = lcd_blink();
#if FAN_ANIM_FRAMES > 2
static bool old_blink;
static uint8_t fan_frame;
if (old_blink != blink) {
old_blink = blink;
if (!fanSpeeds[0] || ++fan_frame >= FAN_ANIM_FRAMES) fan_frame = 0;
}
#endif
// Status Menu Font
lcd_setFont(FONT_STATUSMENU);
//
// Fan Animation
//
// Draws the whole heading image as a B/W bitmap rather than
// drawing the elements separately.
// This was done as an optimization, as it was slower to draw
// multiple parts compared to a single bitmap.
//
// The bitmap:
// - May be offset in X
// - Includes all nozzle(s), bed(s), and the fan.
//
// TODO:
//
// - Only draw the whole header on the first
// entry to the status screen. Nozzle, bed, and
// fan outline bits don't change.
//
if (PAGE_UNDER(STATUS_SCREENHEIGHT + 1)) {
u8g.drawBitmapP(
STATUS_SCREEN_X, STATUS_SCREEN_Y,
(STATUS_SCREENWIDTH + 7) / 8, STATUS_SCREENHEIGHT,
#if HAS_FAN0
#if FAN_ANIM_FRAMES > 2
fan_frame == 1 ? status_screen1_bmp :
fan_frame == 2 ? status_screen2_bmp :
#if FAN_ANIM_FRAMES > 3
fan_frame == 3 ? status_screen3_bmp :
#endif
#else
blink && fanSpeeds[0] ? status_screen1_bmp :
#endif
#endif
status_screen0_bmp
);
}
//
// Temperature Graphics and Info
//
if (PAGE_UNDER(28)) {
// Extruders
HOTEND_LOOP() _draw_heater_status(STATUS_SCREEN_HOTEND_TEXT_X(e), e, blink);
// Heated bed
#if HOTENDS < 4 && HAS_TEMP_BED
_draw_heater_status(STATUS_SCREEN_BED_TEXT_X, -1, blink);
#endif
#if HAS_FAN0
if (PAGE_CONTAINS(20, 27)) {
// Fan
const int16_t per = ((fanSpeeds[0] + 1) * 100) / 256;
if (per) {
u8g.setPrintPos(STATUS_SCREEN_FAN_TEXT_X, STATUS_SCREEN_FAN_TEXT_Y);
lcd_print(itostr3(per));
u8g.print('%');
}
}
#endif
}
#if ENABLED(SDSUPPORT)
//
// SD Card Symbol
//
if (card.isFileOpen() && PAGE_CONTAINS(42 - (TALL_FONT_CORRECTION), 51 - (TALL_FONT_CORRECTION))) {
// Upper box
u8g.drawBox(42, 42 - (TALL_FONT_CORRECTION), 8, 7); // 42-48 (or 41-47)
// Right edge
u8g.drawBox(50, 44 - (TALL_FONT_CORRECTION), 2, 5); // 44-48 (or 43-47)
// Bottom hollow box
u8g.drawFrame(42, 49 - (TALL_FONT_CORRECTION), 10, 4); // 49-52 (or 48-51)
// Corner pixel
u8g.drawPixel(50, 43 - (TALL_FONT_CORRECTION)); // 43 (or 42)
}
#endif // SDSUPPORT
#if ENABLED(SDSUPPORT) || ENABLED(LCD_SET_PROGRESS_MANUALLY)
//
// Progress bar frame
//
#define PROGRESS_BAR_X 54
#define PROGRESS_BAR_WIDTH (LCD_PIXEL_WIDTH - PROGRESS_BAR_X)
if (PAGE_CONTAINS(49, 52 - (TALL_FONT_CORRECTION))) // 49-52 (or 49-51)
u8g.drawFrame(
PROGRESS_BAR_X, 49,
PROGRESS_BAR_WIDTH, 4 - (TALL_FONT_CORRECTION)
);
#if DISABLED(LCD_SET_PROGRESS_MANUALLY)
const uint8_t progress_bar_percent = card.percentDone();
#endif
if (progress_bar_percent > 1) {
//
// Progress bar solid part
//
if (PAGE_CONTAINS(50, 51 - (TALL_FONT_CORRECTION))) // 50-51 (or just 50)
u8g.drawBox(
PROGRESS_BAR_X + 1, 50,
(uint16_t)((PROGRESS_BAR_WIDTH - 2) * progress_bar_percent * 0.01), 2 - (TALL_FONT_CORRECTION)
);
//
// SD Percent Complete
//
#if ENABLED(DOGM_SD_PERCENT)
if (PAGE_CONTAINS(41, 48)) {
// Percent complete
u8g.setPrintPos(55, 48);
u8g.print(itostr3(progress_bar_percent));
u8g.print('%');
}
#endif
}
//
// Elapsed Time
//
#if DISABLED(DOGM_SD_PERCENT)
#define SD_DURATION_X (PROGRESS_BAR_X + (PROGRESS_BAR_WIDTH / 2) - len * (DOG_CHAR_WIDTH / 2))
#else
#define SD_DURATION_X (LCD_PIXEL_WIDTH - len * DOG_CHAR_WIDTH)
#endif
if (PAGE_CONTAINS(41, 48)) {
char buffer[10];
duration_t elapsed = print_job_timer.duration();
bool has_days = (elapsed.value >= 60*60*24L);
uint8_t len = elapsed.toDigital(buffer, has_days);
u8g.setPrintPos(SD_DURATION_X, 48);
lcd_print(buffer);
}
#endif // SDSUPPORT || LCD_SET_PROGRESS_MANUALLY
//
// XYZ Coordinates
//
#if ENABLED(USE_SMALL_INFOFONT)
#define INFO_FONT_HEIGHT 7
#else
#define INFO_FONT_HEIGHT 8
#endif
#define XYZ_BASELINE (30 + INFO_FONT_HEIGHT)
#define X_LABEL_POS 3
#define X_VALUE_POS 11
#define XYZ_SPACING 40
#if ENABLED(XYZ_HOLLOW_FRAME)
#define XYZ_FRAME_TOP 29
#define XYZ_FRAME_HEIGHT INFO_FONT_HEIGHT + 3
#else
#define XYZ_FRAME_TOP 30
#define XYZ_FRAME_HEIGHT INFO_FONT_HEIGHT + 1
#endif
// Before homing the axis letters are blinking 'X' <-> '?'.
// When axis is homed but axis_known_position is false the axis letters are blinking 'X' <-> ' '.
// When everything is ok you see a constant 'X'.
static char xstring[5], ystring[5], zstring[7];
#if ENABLED(FILAMENT_LCD_DISPLAY)
static char wstring[5], mstring[4];
#endif
// At the first page, regenerate the XYZ strings
if (page.page == 0) {
strcpy(xstring, ftostr4sign(LOGICAL_X_POSITION(current_position[X_AXIS])));
strcpy(ystring, ftostr4sign(LOGICAL_Y_POSITION(current_position[Y_AXIS])));
strcpy(zstring, ftostr52sp(FIXFLOAT(LOGICAL_Z_POSITION(current_position[Z_AXIS]))));
#if ENABLED(FILAMENT_LCD_DISPLAY)
strcpy(wstring, ftostr12ns(filament_width_meas));
strcpy(mstring, itostr3(100.0 * (
parser.volumetric_enabled
? planner.volumetric_area_nominal / planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
: planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
)
));
#endif
}
if (PAGE_CONTAINS(XYZ_FRAME_TOP, XYZ_FRAME_TOP + XYZ_FRAME_HEIGHT - 1)) {
#if ENABLED(XYZ_HOLLOW_FRAME)
u8g.drawFrame(0, XYZ_FRAME_TOP, LCD_PIXEL_WIDTH, XYZ_FRAME_HEIGHT); // 8: 29-40 7: 29-39
#else
u8g.drawBox(0, XYZ_FRAME_TOP, LCD_PIXEL_WIDTH, XYZ_FRAME_HEIGHT); // 8: 30-39 7: 30-37
#endif
if (PAGE_CONTAINS(XYZ_BASELINE - (INFO_FONT_HEIGHT - 1), XYZ_BASELINE)) {
#if DISABLED(XYZ_HOLLOW_FRAME)
u8g.setColorIndex(0); // white on black
#endif
u8g.setPrintPos(0 * XYZ_SPACING + X_LABEL_POS, XYZ_BASELINE);
_draw_axis_label(X_AXIS, PSTR(MSG_X), blink);
u8g.setPrintPos(0 * XYZ_SPACING + X_VALUE_POS, XYZ_BASELINE);
lcd_print(xstring);
u8g.setPrintPos(1 * XYZ_SPACING + X_LABEL_POS, XYZ_BASELINE);
_draw_axis_label(Y_AXIS, PSTR(MSG_Y), blink);
u8g.setPrintPos(1 * XYZ_SPACING + X_VALUE_POS, XYZ_BASELINE);
lcd_print(ystring);
u8g.setPrintPos(2 * XYZ_SPACING + X_LABEL_POS, XYZ_BASELINE);
_draw_axis_label(Z_AXIS, PSTR(MSG_Z), blink);
u8g.setPrintPos(2 * XYZ_SPACING + X_VALUE_POS, XYZ_BASELINE);
lcd_print(zstring);
#if DISABLED(XYZ_HOLLOW_FRAME)
u8g.setColorIndex(1); // black on white
#endif
}
}
//
// Feedrate
//
if (PAGE_CONTAINS(51 - INFO_FONT_HEIGHT, 49)) {
lcd_setFont(FONT_MENU);
u8g.setPrintPos(3, 50);
lcd_print(LCD_STR_FEEDRATE[0]);
lcd_setFont(FONT_STATUSMENU);
u8g.setPrintPos(12, 50);
lcd_print(itostr3(feedrate_percentage));
u8g.print('%');
//
// Filament sensor display if SD is disabled
//
#if ENABLED(FILAMENT_LCD_DISPLAY) && DISABLED(SDSUPPORT)
u8g.setPrintPos(56, 50);
lcd_print(wstring);
u8g.setPrintPos(102, 50);
lcd_print(mstring);
u8g.print('%');
lcd_setFont(FONT_MENU);
u8g.setPrintPos(47, 50);
lcd_print(LCD_STR_FILAM_DIA);
u8g.setPrintPos(93, 50);
lcd_print(LCD_STR_FILAM_MUL);
#endif
}
//
// Status line
//
#define STATUS_BASELINE (55 + INFO_FONT_HEIGHT)
if (PAGE_CONTAINS(STATUS_BASELINE - (INFO_FONT_HEIGHT - 1), STATUS_BASELINE)) {
u8g.setPrintPos(0, STATUS_BASELINE);
#if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
if (PENDING(millis(), previous_lcd_status_ms + 5000UL)) { //Display both Status message line and Filament display on the last line
lcd_implementation_status_message(blink);
}
else {
lcd_printPGM(PSTR(LCD_STR_FILAM_DIA));
u8g.print(':');
lcd_print(wstring);
lcd_printPGM(PSTR(" " LCD_STR_FILAM_MUL));
u8g.print(':');
lcd_print(mstring);
u8g.print('%');
}
#else
lcd_implementation_status_message(blink);
#endif
}
}
#endif // _STATUS_SCREEN_DOGM_H_

@ -0,0 +1,853 @@
/**
* Lightweight Status Screen for the RepRapDiscount Full
* Graphics Smart Controller (ST7920-based 128x64 LCD)
*
* (c) 2017 Aleph Objects, Inc.
*
* The code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
*/
/**
* Implementation of a Status Screen for the RepRapDiscount
* Full Graphics Smart Controller using native ST7920 commands
* instead of U8Glib.
*
* This alternative Status Screen makes use of the built-in character
* generation capabilities of the ST7920 to update the Status Screen
* with less SPI traffic and CPU use. In particular:
*
* - The fan and bed animations are handled using custom characters
* that are stored in CGRAM. This allows for the animation to be
* updated by writing a single character to the text-buffer (DDRAM).
*
* - All the information in the Status Screen is text that is written
* to DDRAM, so the work of generating the bitmaps is offloaded to
* the ST7920 rather than being render by U8Glib on the MCU.
*
* - The graphics buffer (GDRAM) is only used for static graphics
* elements (nozzle and feedrate bitmaps) and for the progress
* bar, so updates are sporadic.
*/
#include "status_screen_lite_ST7920_class.h"
#include "../../libs/duration_t.h"
#define BUFFER_WIDTH 256
#define BUFFER_HEIGHT 32
#define DDRAM_LINE_1 0x00
#define DDRAM_LINE_2 0x10
#define DDRAM_LINE_3 0x08
#define DDRAM_LINE_4 0x18
ST7920_Lite_Status_Screen::st7920_state_t ST7920_Lite_Status_Screen::current_bits;
void ST7920_Lite_Status_Screen::cmd(const uint8_t cmd) {
if (!current_bits.synced || !current_bits.cmd) {
current_bits.synced = true;
current_bits.cmd = true;
sync_cmd();
}
write_byte(cmd);
}
void ST7920_Lite_Status_Screen::begin_data() {
if (!current_bits.synced || current_bits.cmd) {
current_bits.synced = true;
current_bits.cmd = false;
sync_dat();
}
}
void ST7920_Lite_Status_Screen::write_str(const char *str) {
while (*str) write_byte(*str++);
}
void ST7920_Lite_Status_Screen::write_str(const char *str, uint8_t len) {
while (*str && len--) write_byte(*str++);
}
void ST7920_Lite_Status_Screen::write_str_P(const char * const str) {
const char *p_str = (const char *)str;
while (char c = pgm_read_byte_near(p_str++)) write_byte(c);
}
void ST7920_Lite_Status_Screen::write_str(progmem_str str) {
write_str_P((const char*)str);
}
void ST7920_Lite_Status_Screen::write_number(const int16_t value, const uint8_t digits/*=3*/) {
char str[7];
const char *fmt;
switch (digits) {
case 6: fmt = PSTR("%6d"); break;
case 5: fmt = PSTR("%5d"); break;
case 4: fmt = PSTR("%4d"); break;
case 3: fmt = PSTR("%3d"); break;
case 2: fmt = PSTR("%2d"); break;
case 1: fmt = PSTR("%1d"); break;
}
sprintf_P(str, fmt, value);
write_str(str);
}
void ST7920_Lite_Status_Screen::display_status(const bool display_on, const bool cursor_on, const bool blink_on) {
extended_function_set(false);
cmd(0b00001000 |
(display_on ? 0b0100 : 0) |
(cursor_on ? 0b0010 : 0) |
(blink_on ? 0b0001 : 0)
);
}
// Sets the extended and graphics bits simultaneously, regardless of
// the current state. This is a helper function for extended_function_set()
// and graphics()
void ST7920_Lite_Status_Screen::_extended_function_set(const bool extended, const bool graphics) {
cmd( 0b00100000 |
(extended ? 0b00000100 : 0) |
(graphics ? 0b00000010 : 0)
);
current_bits.extended = extended;
current_bits.graphics = graphics;
}
void ST7920_Lite_Status_Screen::extended_function_set(const bool extended) {
if (extended != current_bits.extended)
_extended_function_set(extended, current_bits.graphics);
}
void ST7920_Lite_Status_Screen::graphics(const bool graphics) {
if (graphics != current_bits.graphics)
_extended_function_set(current_bits.extended, graphics);
}
void ST7920_Lite_Status_Screen::entry_mode_select(const bool ac_increase, const bool shift) {
extended_function_set(false);
cmd(0b00000100 |
(ac_increase ? 0b00000010 : 0) |
(shift ? 0b00000001 : 0)
);
}
// Sets the sa bit regardless of the current state. This is a helper
// function for scroll_or_addr_select()
void ST7920_Lite_Status_Screen::_scroll_or_addr_select(const bool sa) {
extended_function_set(true);
cmd(0b00100010 |
(sa ? 0b000001 : 0)
);
current_bits.sa = sa;
}
void ST7920_Lite_Status_Screen::scroll_or_addr_select(const bool sa) {
if (sa != current_bits.sa)
_scroll_or_addr_select(sa);
}
void ST7920_Lite_Status_Screen::set_ddram_address(const uint8_t addr) {
extended_function_set(false);
cmd(0b10000000 | (addr & 0b00111111));
}
void ST7920_Lite_Status_Screen::set_cgram_address(const uint8_t addr) {
extended_function_set(false);
cmd(0b01000000 | (addr & 0b00111111));
}
void ST7920_Lite_Status_Screen::set_gdram_address(const uint8_t x, const uint8_t y) {
extended_function_set(true);
cmd(0b10000000 | (y & 0b01111111));
cmd(0b10000000 | (x & 0b00001111));
}
void ST7920_Lite_Status_Screen::clear() {
extended_function_set(false);
cmd(0x00000001);
delay(15); //delay for CGRAM clear
}
void ST7920_Lite_Status_Screen::home() {
extended_function_set(false);
cmd(0x00000010);
}
/* This fills the entire text buffer with spaces */
void ST7920_Lite_Status_Screen::clear_ddram() {
set_ddram_address(DDRAM_LINE_1);
begin_data();
for (uint8_t i = 64; i--;) write_byte(' ');
}
/* This fills the entire graphics buffer with zeros */
void ST7920_Lite_Status_Screen::clear_gdram() {
for (uint8_t y = 0; y < BUFFER_HEIGHT; y++) {
set_gdram_address(0, y);
begin_data();
for (uint8_t i = (BUFFER_WIDTH) / 16; i--;) write_word(0);
}
}
void ST7920_Lite_Status_Screen::load_cgram_icon(const uint16_t addr, const void *data) {
const uint16_t *p_word = (const uint16_t *)data;
set_cgram_address(addr);
begin_data();
for (uint8_t i = 16; i--;)
write_word(pgm_read_word_near(p_word++));
}
/**
* Draw an icon in GDRAM. Position specified in DDRAM
* coordinates. i.e., X from 1 to 8, Y from 1 to 4.
*/
void ST7920_Lite_Status_Screen::draw_gdram_icon(uint8_t x, uint8_t y, const void *data) {
const uint16_t *p_word = (const uint16_t *)data;
if (y > 2) { // Handle display folding
y -= 2;
x += 8;
}
--x;
--y;
for (int i = 0; i < 16; i++) {
set_gdram_address(x, i + y * 16);
begin_data();
write_word(pgm_read_word_near(p_word++));
}
}
/************************** ICON DEFINITIONS *************************************/
#define CGRAM_ICON_1_ADDR 0x00
#define CGRAM_ICON_2_ADDR 0x10
#define CGRAM_ICON_3_ADDR 0x20
#define CGRAM_ICON_4_ADDR 0x30
#define CGRAM_ICON_1_WORD 0x00
#define CGRAM_ICON_2_WORD 0x02
#define CGRAM_ICON_3_WORD 0x04
#define CGRAM_ICON_4_WORD 0x06
const uint16_t nozzle_icon[] PROGMEM = {
0b0000000000000000,
0b0000000000000000,
0b0000111111110000,
0b0001111111111000,
0b0001111111111000,
0b0001111111111000,
0b0000111111110000,
0b0000111111110000,
0b0001111111111000,
0b0001111111111000,
0b0001111111111000,
0b0000011111100000,
0b0000001111000000,
0b0000000110000000,
0b0000000000000000,
0b0000000000000000
};
const uint16_t bed_icon[] PROGMEM = {
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0111111111111110,
0b0111111111111110,
0b0110000000000110,
0b0000000000000000,
0b0000000000000000
};
const uint16_t heat1_icon[] PROGMEM = {
0b0000000000000000,
0b0010001000100000,
0b0001000100010000,
0b0000100010001000,
0b0000100010001000,
0b0001000100010000,
0b0010001000100000,
0b0010001000100000,
0b0001000100010000,
0b0000100010001000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000
};
const uint16_t heat2_icon[] PROGMEM = {
0b0000000000000000,
0b0000100010001000,
0b0000100010001000,
0b0001000100010000,
0b0010001000100000,
0b0010001000100000,
0b0001000100010000,
0b0000100010001000,
0b0000100010001000,
0b0001000100010000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000
};
const uint16_t fan1_icon[] PROGMEM = {
0b0000000000000000,
0b0111111111111110,
0b0111000000001110,
0b0110001111000110,
0b0100001111000010,
0b0100000110000010,
0b0101100000011010,
0b0101110110111010,
0b0101100000011010,
0b0100000110000010,
0b0100001111000010,
0b0110001111000110,
0b0111000000001110,
0b0111111111111110,
0b0000000000000000,
0b0000000000000000
};
const uint16_t fan2_icon[] PROGMEM = {
0b0000000000000000,
0b0111111111111110,
0b0111000000001110,
0b0110010000100110,
0b0100111001110010,
0b0101111001111010,
0b0100110000110010,
0b0100000110000010,
0b0100110000110010,
0b0101111001111010,
0b0100111001110010,
0b0110010000100110,
0b0111000000001110,
0b0111111111111110,
0b0000000000000000,
0b0000000000000000
};
const uint16_t feedrate_icon[] PROGMEM = {
0b0000000000000000,
0b0111111000000000,
0b0100000000000000,
0b0100000000000000,
0b0100000000000000,
0b0111111011111000,
0b0100000010000100,
0b0100000010000100,
0b0100000010000100,
0b0100000011111000,
0b0000000010001000,
0b0000000010000100,
0b0000000010000100,
0b0000000010000010,
0b0000000000000000,
0b0000000000000000
};
/************************** MAIN SCREEN *************************************/
void ST7920_Lite_Status_Screen::draw_static_elements() {
scroll_or_addr_select(0);
// Load the animated bed and fan icons
load_cgram_icon(CGRAM_ICON_1_ADDR, heat1_icon);
load_cgram_icon(CGRAM_ICON_2_ADDR, heat2_icon);
load_cgram_icon(CGRAM_ICON_3_ADDR, fan1_icon);
load_cgram_icon(CGRAM_ICON_4_ADDR, fan2_icon);
// Draw the static icons in GDRAM
draw_gdram_icon(1, 1, nozzle_icon);
#if EXTRUDERS == 2
draw_gdram_icon(1,2,nozzle_icon);
draw_gdram_icon(1,3,bed_icon);
#else
draw_gdram_icon(1,2,bed_icon);
#endif
draw_gdram_icon(6,2,feedrate_icon);
// Draw the initial fan icon
draw_fan_icon(false);
}
/**
* Although this is undocumented, the ST7920 allows the character
* data buffer (DDRAM) to be used in conjunction with the graphics
* bitmap buffer (CGRAM). The contents of the graphics buffer is
* XORed with the data from the character generator. This allows
* us to make the progess bar out of graphical data (the bar) and
* text data (the percentage).
*/
void ST7920_Lite_Status_Screen::draw_progress_bar(const uint8_t value) {
#if EXTRUDERS == 1
// If we have only one extruder, draw a long progress bar on the third line
const uint8_t top = 1, // Top in pixels
bottom = 13, // Bottom in pixels
left = 8, // Left edge, in 16-bit words
width = 5; // Width of progress bar, in 16-bit words
#else
const uint8_t top = 16 + 1, // Top in pixels
bottom = 16 + 13, // Bottom in pixels
left = 5, // Left edge, in 16-bit words
width = 3; // Width of progress bar, in 16-bit words
#endif
const uint8_t char_pcnt = 100 / width; // How many percent does each 16-bit word represent?
// Draw the progress bar as a bitmap in CGRAM
for (uint8_t y = top; y <= bottom; y++) {
set_gdram_address(left, y);
begin_data();
for (uint8_t x = 0; x < width; x++) {
uint16_t gfx_word = 0x0000;
if ((x + 1) * char_pcnt <= value)
gfx_word = 0xFFFF; // Draw completely filled bytes
else if ((x * char_pcnt) < value)
gfx_word = int(0x8000) >> (value % char_pcnt) * 16 / char_pcnt; // Draw partially filled bytes
// Draw the frame around the progress bar
if (y == top || y == bottom)
gfx_word = 0xFFFF; // Draw top/bottom border
else if (x == width - 1)
gfx_word |= 0x0001; // Draw right border
else if (x == 0)
gfx_word |= 0x8000; // Draw left border
write_word(gfx_word);
}
}
// Draw the percentage as text in DDRAM
set_ddram_address(
#if EXTRUDERS == 1
DDRAM_LINE_3 + 1
#else
DDRAM_LINE_2 + left
#endif
);
begin_data();
if (value > 9) {
write_number(value, 4);
write_str(F("% "));
}
else {
write_number(value, 3);
write_str(F("% "));
}
}
void ST7920_Lite_Status_Screen::draw_fan_icon(const bool whichIcon) {
set_ddram_address(DDRAM_LINE_1 + 5);
begin_data();
write_word(whichIcon ? CGRAM_ICON_3_WORD : CGRAM_ICON_4_WORD);
}
void ST7920_Lite_Status_Screen::draw_heat_icon(const bool whichIcon, const bool heating) {
set_ddram_address(
#if EXTRUDERS == 1
DDRAM_LINE_2
#else
DDRAM_LINE_3
#endif
);
begin_data();
if (heating)
write_word(whichIcon ? CGRAM_ICON_1_WORD : CGRAM_ICON_2_WORD);
else {
write_byte(' ');
write_byte(' ');
}
}
#define FAR(a,b) (((a > b) ? (a-b) : (b-a)) > 1)
void ST7920_Lite_Status_Screen::draw_extruder_1_temp(const int16_t temp, const int16_t target) {
set_ddram_address(DDRAM_LINE_1 + 1);
begin_data();
write_number(temp);
if (target && FAR(temp, target)) {
write_str(F("\x1A"));
write_number(target);
}
else
write_str(F(" "));
}
void ST7920_Lite_Status_Screen::draw_extruder_2_temp(const int16_t temp, const int16_t target) {
set_ddram_address(DDRAM_LINE_2 + 1);
begin_data();
write_number(temp);
if (target && FAR(temp, target)) {
write_str(F("\x1A"));
write_number(target);
}
else
write_str(F(" "));
}
void ST7920_Lite_Status_Screen::draw_bed_temp(const int16_t temp, const int16_t target) {
set_ddram_address(
#if EXTRUDERS == 1
DDRAM_LINE_2 + 1
#else
DDRAM_LINE_3 + 1
#endif
);
begin_data();
write_number(temp);
if (target && FAR(temp, target)) {
write_str(F("\x1A"));
write_number(target);
}
else
write_str(F(" "));
}
void ST7920_Lite_Status_Screen::draw_fan_speed(const uint8_t value) {
set_ddram_address(DDRAM_LINE_1 + 6);
begin_data();
write_number(value, 4);
}
void ST7920_Lite_Status_Screen::draw_print_time(const uint32_t elapsed) {
const uint8_t hrs = elapsed / 3600,
min = (elapsed / 60) % 60;
char str[7];
sprintf_P(str, hrs > 99 ? PSTR("%03d:%02d") : PSTR(" %02d:%02d"), hrs, min);
set_ddram_address(DDRAM_LINE_3 + 5);
begin_data();
write_str(str);
}
void ST7920_Lite_Status_Screen::draw_feedrate_percentage(const uint8_t percentage) {
// We only have enough room for the feedrate when
// we have one extruder
#if EXTRUDERS == 1
set_ddram_address(DDRAM_LINE_2 + 6);
begin_data();
write_number(percentage, 4);
#endif
}
void ST7920_Lite_Status_Screen::draw_status_message(const char *str) {
set_ddram_address(DDRAM_LINE_4);
begin_data();
#if ENABLED(STATUS_MESSAGE_SCROLLING)
const uint8_t lcd_len = 16;
const uint8_t padding = 2;
uint8_t str_len = strlen(str);
// Trim whitespace at the end of the str, as for some reason
// messages like "Card Inserted" are padded with many spaces
while (str_len > 0 && str[str_len - 1] == ' ') str_len--;
if (str_len <= lcd_len) {
// It all fits on the LCD without scrolling
write_str(str);
}
else {
// Print the message repeatedly until covering the LCD
uint8_t c = status_scroll_pos;
for (uint8_t n = 0; n < lcd_len; n++) {
write_byte(c < str_len ? str[c] : ' ');
c++;
c %= str_len + padding; // Wrap around
}
// Scroll the message
if (status_scroll_pos == str_len + padding)
status_scroll_pos = 0;
else
status_scroll_pos++;
}
#else
write_str(str, 16);
#endif
}
void ST7920_Lite_Status_Screen::draw_position(const float x, const float y, const float z, bool position_known) {
char str[7];
set_ddram_address(DDRAM_LINE_4);
begin_data();
// If position is unknown, flash the labels.
const unsigned char alt_label = position_known ? 0 : (lcd_blink() ? ' ' : 0);
dtostrf(x, -4, 0, str);
write_byte(alt_label ? alt_label : 'X');
write_str(str, 4);
dtostrf(y, -4, 0, str);
write_byte(alt_label ? alt_label : 'Y');
write_str(str, 4);
dtostrf(z, -5, 1, str);
write_byte(alt_label ? alt_label : 'Z');
write_str(str, 5);
}
bool ST7920_Lite_Status_Screen::indicators_changed() {
// We only add the target temperatures to the checksum
// because the actual temps fluctuate so by updating
// them only during blinks we gain a bit of stability.
const bool blink = lcd_blink();
const uint8_t feedrate_perc = feedrate_percentage;
const uint8_t fan_speed = ((fanSpeeds[0] + 1) * 100) / 256;
const float extruder_1_target = thermalManager.degTargetHotend(0);
#if EXTRUDERS == 2
const float extruder_2_target = thermalManager.degTargetHotend(1);
#endif
const float bed_target = thermalManager.degTargetBed();
static uint8_t last_checksum = 0;
const uint8_t checksum =
uint8_t(blink) ^
uint8_t(feedrate_perc) ^
uint8_t(fan_speed) ^
uint8_t(extruder_1_target) ^
#if EXTRUDERS == 2
uint8_t(extruder_2_target) ^
#endif
uint8_t(bed_target);
if (last_checksum == checksum) return false;
last_checksum = checksum;
return true;
}
void ST7920_Lite_Status_Screen::update_indicators(const bool forceUpdate) {
if (forceUpdate || indicators_changed()) {
const bool blink = lcd_blink();
const duration_t elapsed = print_job_timer.duration();
const uint32_t seconds_elapsed = elapsed.value;
const uint8_t feedrate_perc = feedrate_percentage;
const uint8_t fan_speed = ((fanSpeeds[0] + 1) * 100) / 256;
const float extruder_1_temp = thermalManager.degHotend(0);
const float extruder_1_target = thermalManager.degTargetHotend(0);
#if EXTRUDERS == 2
const float extruder_2_temp = thermalManager.degHotend(1);
const float extruder_2_target = thermalManager.degTargetHotend(1);
#endif
const float bed_temp = thermalManager.degBed();
const float bed_target = thermalManager.degTargetBed();
draw_extruder_1_temp(extruder_1_temp, extruder_1_target);
#if EXTRUDERS == 2
draw_extruder_2_temp(extruder_2_temp, extruder_2_target);
#endif
draw_bed_temp(bed_temp, bed_target);
draw_fan_speed(fan_speed);
draw_print_time(seconds_elapsed);
draw_feedrate_percentage(feedrate_perc);
// Update the fan and bed animations
if (fan_speed > 0) draw_fan_icon(blink);
if (bed_target > 0)
draw_heat_icon(blink, true);
else
draw_heat_icon(false, false);
}
}
bool ST7920_Lite_Status_Screen::position_changed() {
const float x_pos = current_position[X_AXIS],
y_pos = current_position[Y_AXIS],
z_pos = current_position[Z_AXIS];
const uint8_t checksum = uint8_t(x_pos) ^ uint8_t(y_pos) ^ uint8_t(z_pos);
static uint8_t last_checksum = 0;
if (last_checksum == checksum) return false;
last_checksum = checksum;
return true;
}
bool ST7920_Lite_Status_Screen::status_changed() {
uint8_t checksum = 0;
for (const char *p = lcd_status_message; *p; p++) checksum ^= *p;
static uint8_t last_checksum = 0;
if (last_checksum == checksum) return false;
last_checksum = checksum;
return true;
}
bool ST7920_Lite_Status_Screen::blink_changed() {
static uint8_t last_blink = 0;
const bool blink = lcd_blink();
if (last_blink == blink) return false;
last_blink = blink;
return true;
}
void ST7920_Lite_Status_Screen::update_status_or_position(bool forceUpdate) {
static uint8_t countdown = 0;
/**
* There is only enough room in the display for either the
* status message or the position, not both, so we choose
* one or another. Whenever the status message changes,
* we show it for a number of consecutive seconds, but
* then go back to showing the position as soon as the
* head moves, i.e:
*
* countdown > 1 -- Show status
* countdown = 1 -- Show status, until movement
* countdown = 0 -- Show position
*/
if (forceUpdate || status_changed()) {
#if ENABLED(STATUS_MESSAGE_SCROLLING)
status_scroll_pos = 0;
#endif
#ifndef STATUS_EXPIRE_SECONDS
#define STATUS_EXPIRE_SECONDS 20
#endif
countdown = lcd_strlen(lcd_status_message) ? STATUS_EXPIRE_SECONDS : 0;
draw_status_message(lcd_status_message);
blink_changed(); // Clear changed flag
}
else if (countdown > 1 && blink_changed()) {
countdown--;
#if ENABLED(STATUS_MESSAGE_SCROLLING)
draw_status_message(lcd_status_message);
#endif
}
else if (countdown > 0 && blink_changed()) {
if (position_changed()) {
countdown--;
forceUpdate = true;
}
#if ENABLED(STATUS_MESSAGE_SCROLLING)
draw_status_message(lcd_status_message);
#endif
}
if (countdown == 0 && (forceUpdate || position_changed() ||
#if DISABLED(DISABLE_REDUCED_ACCURACY_WARNING)
blink_changed()
#endif
)) {
draw_position(
current_position[X_AXIS],
current_position[Y_AXIS],
current_position[Z_AXIS],
#if ENABLED(DISABLE_REDUCED_ACCURACY_WARNING)
true
#else
axis_known_position[X_AXIS] &&
axis_known_position[Y_AXIS] &&
axis_known_position[Z_AXIS]
#endif
);
}
}
void ST7920_Lite_Status_Screen::update_progress(const bool forceUpdate) {
const uint8_t percent_done =
#if ENABLED(SDSUPPORT)
card.percentDone()
#else
0
#endif
;
// Since the progress bar involves writing
// quite a few bytes to GDRAM, only do this
// when an update is actually necessary.
static uint8_t last_progress = 0;
if (!forceUpdate && last_progress == percent_done) return;
last_progress = percent_done;
draw_progress_bar(percent_done);
}
void ST7920_Lite_Status_Screen::update(const bool forceUpdate) {
cs();
update_indicators(forceUpdate);
update_status_or_position(forceUpdate);
update_progress(forceUpdate);
ncs();
}
void ST7920_Lite_Status_Screen::reset_state_from_unknown() {
_extended_function_set(true, true); // Do it twice as only one bit
_extended_function_set(true, true); // get set at a time.
_scroll_or_addr_select(false);
}
void ST7920_Lite_Status_Screen::on_entry() {
cs();
reset_state_from_unknown();
clear();
clear_gdram();
draw_static_elements();
update(true);
ncs();
}
void ST7920_Lite_Status_Screen::on_exit() {
cs();
clear();
_extended_function_set(true, true); // Restore state to what u8g expects.
ncs();
}
// This is called prior to the KILL screen to
// clear the screen so we don't end up with a
// garbled display.
void ST7920_Lite_Status_Screen::clear_text_buffer() {
cs();
reset_state_from_unknown();
clear();
_extended_function_set(true, true); // Restore state to what u8g expects.
ncs();
}
static void lcd_implementation_status_screen() {
ST7920_Lite_Status_Screen::update(false);
}
/**
* In order to properly update the lite Status Screen,
* we must know when we have entered and left the
* Status Screen. Since the ultralcd code is not
* set up for doing this, we call this function before
* each update indicating whether the current screen
* is the Status Screen.
*
* This function keeps track of whether we have left or
* entered the Status Screen and calls the on_entry()
* and on_exit() methods for cleanup.
*/
static void lcd_in_status(const bool inStatus) {
static bool lastInStatus = false;
if (lastInStatus == inStatus) return;
if ((lastInStatus = inStatus))
ST7920_Lite_Status_Screen::on_entry();
else
ST7920_Lite_Status_Screen::on_exit();
}

@ -0,0 +1,108 @@
/**
* Lightweight Status Screen for the RepRapDiscount Full
* Graphics Smart Controller (ST7920-based 128x64 LCD)
*
* (c) 2017 Aleph Objects, Inc.
*
* The code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
*/
#ifndef STATUS_SCREEN_LITE_ST7920_CLASS_H
#define STATUS_SCREEN_LITE_ST7920_CLASS_H
#include "../../core/macros.h"
typedef const __FlashStringHelper *progmem_str;
class ST7920_Lite_Status_Screen {
private:
static struct st7920_state_t {
uint8_t synced : 1; // Whether a sync has been sent
uint8_t cmd : 1; // Whether the sync was cmd or data
uint8_t extended : 1;
uint8_t graphics : 1;
uint8_t sa : 1;
} current_bits;
static void cs();
static void ncs();
static void sync_cmd();
static void sync_dat();
static void write_byte(const uint8_t w);
FORCE_INLINE static void write_word(const uint16_t w) {
write_byte((w >> 8) & 0xFF);
write_byte((w >> 0) & 0xFF);
}
static void cmd(const uint8_t cmd);
static void begin_data();
static void write_str(const char *str);
static void write_str(const char *str, const uint8_t len);
static void write_str_P(const char * const str);
static void write_str(progmem_str str);
static void write_number(const int16_t value, const uint8_t digits=3);
static void _extended_function_set(const bool extended, const bool graphics);
static void _scroll_or_addr_select(const bool sa);
static void reset_state_from_unknown();
static void home();
static void display_status(const bool display_on, const bool cursor_on, const bool blink_on);
static void extended_function_set(const bool extended);
static void graphics(const bool graphics);
static void entry_mode_select(const bool ac_increase, const bool shift);
static void scroll_or_addr_select(const bool sa);
static void set_ddram_address(const uint8_t addr);
static void set_cgram_address(const uint8_t addr);
static void set_gdram_address(const uint8_t x, const uint8_t y);
static void clear();
static void clear_ddram();
static void clear_gdram();
static void load_cgram_icon(const uint16_t addr, const void *data);
static void draw_gdram_icon(uint8_t x, uint8_t y, const void *data);
static uint8_t string_checksum(const char *str);
protected:
static void draw_static_elements();
static void draw_progress_bar(const uint8_t value);
static void draw_fan_icon(const bool whichIcon);
static void draw_heat_icon(const bool whichIcon, const bool heating);
static void draw_extruder_1_temp(const int16_t temp, const int16_t target);
static void draw_extruder_2_temp(const int16_t temp, const int16_t target);
static void draw_bed_temp(const int16_t temp, const int16_t target);
static void draw_fan_speed(const uint8_t value);
static void draw_print_time(const uint32_t elapsed);
static void draw_feedrate_percentage(const uint8_t percentage);
static void draw_status_message(const char *str);
static void draw_position(const float x, const float y, const float z, bool position_known = true);
static bool indicators_changed();
static bool position_changed();
static bool blink_changed();
static bool status_changed();
static void update_indicators(const bool forceUpdate);
static void update_position(const bool forceUpdate, bool resetChecksum);
static void update_status_or_position(bool forceUpdate);
static void update_progress(const bool forceUpdate);
public:
static void update(const bool forceUpdate);
static void on_entry();
static void on_exit();
static void clear_text_buffer();
};
#endif // STATUS_SCREEN_LITE_ST7920_CLASS_H

@ -0,0 +1,39 @@
/**
* Lightweight Status Screen for the RepRapDiscount Full
* Graphics Smart Controller (ST7920-based 128x64 LCD)
*
* (c) 2017 Aleph Objects, Inc.
*
* The code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
*/
#include "status_screen_lite_ST7920_class.h"
void ST7920_Lite_Status_Screen::cs() {
ST7920_CS();
current_bits.synced = false;
}
void ST7920_Lite_Status_Screen::ncs() {
ST7920_NCS();
current_bits.synced = false;
}
void ST7920_Lite_Status_Screen::sync_cmd() {
ST7920_SET_CMD();
}
void ST7920_Lite_Status_Screen::sync_dat() {
ST7920_SET_DAT();
}
void ST7920_Lite_Status_Screen::write_byte(const uint8_t data) {
ST7920_WRITE_BYTE(data);
}

@ -125,10 +125,12 @@ uint8_t u8g_dev_rrd_st7920_128x64_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t msg, vo
ST7920_CS(); ST7920_CS();
u8g_Delay(120); //initial delay for boot up u8g_Delay(120); //initial delay for boot up
ST7920_SET_CMD(); ST7920_SET_CMD();
ST7920_WRITE_BYTE(0x20); //non-extended mode
ST7920_WRITE_BYTE(0x08); //display off, cursor+blink off ST7920_WRITE_BYTE(0x08); //display off, cursor+blink off
ST7920_WRITE_BYTE(0x01); //clear CGRAM ram ST7920_WRITE_BYTE(0x01); //clear DDRAM ram
u8g_Delay(15); //delay for CGRAM clear u8g_Delay(15); //delay for DDRAM clear
ST7920_WRITE_BYTE(0x3E); //extended mode + GDRAM active ST7920_WRITE_BYTE(0x24); //extended mode
ST7920_WRITE_BYTE(0x26); //extended mode + GDRAM active
for (y = 0; y < (LCD_PIXEL_HEIGHT) / 2; y++) { //clear GDRAM for (y = 0; y < (LCD_PIXEL_HEIGHT) / 2; y++) { //clear GDRAM
ST7920_WRITE_BYTE(0x80 | y); //set y ST7920_WRITE_BYTE(0x80 | y); //set y
ST7920_WRITE_BYTE(0x80); //set x = 0 ST7920_WRITE_BYTE(0x80); //set x = 0
@ -182,6 +184,12 @@ uint8_t u8g_dev_st7920_128x64_rrd_buf[(LCD_PIXEL_WIDTH) * (PAGE_HEIGHT) / 8] U
u8g_pb_t u8g_dev_st7920_128x64_rrd_pb = {{PAGE_HEIGHT, LCD_PIXEL_HEIGHT, 0, 0, 0}, LCD_PIXEL_WIDTH, u8g_dev_st7920_128x64_rrd_buf}; u8g_pb_t u8g_dev_st7920_128x64_rrd_pb = {{PAGE_HEIGHT, LCD_PIXEL_HEIGHT, 0, 0, 0}, LCD_PIXEL_WIDTH, u8g_dev_st7920_128x64_rrd_buf};
u8g_dev_t u8g_dev_st7920_128x64_rrd_sw_spi = {u8g_dev_rrd_st7920_128x64_fn, &u8g_dev_st7920_128x64_rrd_pb, &u8g_com_null_fn}; u8g_dev_t u8g_dev_st7920_128x64_rrd_sw_spi = {u8g_dev_rrd_st7920_128x64_fn, &u8g_dev_st7920_128x64_rrd_pb, &u8g_com_null_fn};
#if ENABLED(LIGHTWEIGHT_UI)
// We have to include the code for the lightweight UI here
// as it relies on macros that are only defined in this file.
#include "status_screen_lite_ST7920_spi.h"
#endif
#pragma GCC reset_options #pragma GCC reset_options
#endif // U8G_HAL_LINKS #endif // U8G_HAL_LINKS

@ -5141,20 +5141,34 @@ void lcd_update() {
#endif #endif
#if ENABLED(DOGLCD) #if ENABLED(DOGLCD)
if (!drawing_screen) { // If not already drawing pages #if ENABLED(LIGHTWEIGHT_UI)
u8g.firstPage(); // Start the first page #if ENABLED(ULTIPANEL)
drawing_screen = 1; // Flag as drawing pages const bool in_status = currentScreen == lcd_status_screen;
} #else
lcd_setFont(FONT_MENU); // Setup font for every page draw constexpr bool in_status = true;
u8g.setColorIndex(1); // And reset the color #endif
CURRENTSCREEN(); // Draw and process the current screen const bool do_u8g_loop = !in_status;
lcd_in_status(in_status);
// The screen handler can clear drawing_screen for an action that changes the screen. if (in_status) lcd_status_screen();
// If still drawing and there's another page, update max-time and return now. #else
// The nextPage will already be set up on the next call. constexpr bool do_u8g_loop = true;
if (drawing_screen && (drawing_screen = u8g.nextPage())) { #endif
NOLESS(max_display_update_time, millis() - ms); if (do_u8g_loop) {
return; if (!drawing_screen) { // If not already drawing pages
u8g.firstPage(); // Start the first page
drawing_screen = 1; // Flag as drawing pages
}
lcd_setFont(FONT_MENU); // Setup font for every page draw
u8g.setColorIndex(1); // And reset the color
CURRENTSCREEN(); // Draw and process the current screen
// The screen handler can clear drawing_screen for an action that changes the screen.
// If still drawing and there's another page, update max-time and return now.
// The nextPage will already be set up on the next call.
if (drawing_screen && (drawing_screen = u8g.nextPage())) {
NOLESS(max_display_update_time, millis() - ms);
return;
}
} }
#else #else
CURRENTSCREEN(); CURRENTSCREEN();

@ -353,6 +353,12 @@ void lcd_printPGM_utf(const char *str, uint8_t n=LCD_WIDTH) {
#endif // SHOW_BOOTSCREEN #endif // SHOW_BOOTSCREEN
#if ENABLED(LIGHTWEIGHT_UI)
#include "dogm/status_screen_lite_ST7920.h"
#else
#include "dogm/status_screen_DOGM.h"
#endif
// Initialize or re-initialize the LCD // Initialize or re-initialize the LCD
static void lcd_implementation_init() { static void lcd_implementation_init() {
@ -383,6 +389,9 @@ static void lcd_implementation_init() {
// The kill screen is displayed for unrecoverable conditions // The kill screen is displayed for unrecoverable conditions
void lcd_kill_screen() { void lcd_kill_screen() {
#if ENABLED(LIGHTWEIGHT_UI)
ST7920_Lite_Status_Screen::clear_text_buffer();
#endif
const uint8_t h4 = u8g.getHeight() / 4; const uint8_t h4 = u8g.getHeight() / 4;
u8g.firstPage(); u8g.firstPage();
do { do {
@ -398,420 +407,6 @@ void lcd_kill_screen() {
void lcd_implementation_clear() { } // Automatically cleared by Picture Loop void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
//
// Status Screen
//
FORCE_INLINE void _draw_centered_temp(const int16_t temp, const uint8_t x, const uint8_t y) {
const char * const str = itostr3(temp);
u8g.setPrintPos(x - (str[0] != ' ' ? 0 : str[1] != ' ' ? 1 : 2) * DOG_CHAR_WIDTH / 2, y);
lcd_print(str);
lcd_printPGM(PSTR(LCD_STR_DEGREE " "));
}
#ifndef HEAT_INDICATOR_X
#define HEAT_INDICATOR_X 8
#endif
FORCE_INLINE void _draw_heater_status(const uint8_t x, const int8_t heater, const bool blink) {
#if !HEATER_IDLE_HANDLER
UNUSED(blink);
#endif
#if HAS_TEMP_BED
const bool isBed = heater < 0;
#else
constexpr bool isBed = false;
#endif
if (PAGE_UNDER(7)) {
#if HEATER_IDLE_HANDLER
const bool is_idle = (!isBed ? thermalManager.is_heater_idle(heater) :
#if HAS_TEMP_BED
thermalManager.is_bed_idle()
#else
false
#endif
);
if (blink || !is_idle)
#endif
_draw_centered_temp((isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater)) + 0.5, x, 7); }
if (PAGE_CONTAINS(21, 28))
_draw_centered_temp((isBed ? thermalManager.degBed() : thermalManager.degHotend(heater)) + 0.5, x, 28);
if (PAGE_CONTAINS(17, 20)) {
const uint8_t h = isBed ? 7 : HEAT_INDICATOR_X,
y = isBed ? 18 : 17;
if (isBed ? thermalManager.isHeatingBed() : thermalManager.isHeatingHotend(heater)) {
u8g.setColorIndex(0); // white on black
u8g.drawBox(x + h, y, 2, 2);
u8g.setColorIndex(1); // black on white
}
else {
u8g.drawBox(x + h, y, 2, 2);
}
}
}
FORCE_INLINE void _draw_axis_label(const AxisEnum axis, const char* const pstr, const bool blink) {
if (blink)
lcd_printPGM(pstr);
else {
if (!axis_homed[axis])
u8g.print('?');
else {
#if DISABLED(HOME_AFTER_DEACTIVATE) && DISABLED(DISABLE_REDUCED_ACCURACY_WARNING)
if (!axis_known_position[axis])
u8g.print(' ');
else
#endif
lcd_printPGM(pstr);
}
}
}
inline void lcd_implementation_status_message(const bool blink) {
#if ENABLED(STATUS_MESSAGE_SCROLLING)
static bool last_blink = false;
const uint8_t slen = lcd_strlen(lcd_status_message);
const char *stat = lcd_status_message + status_scroll_pos;
if (slen <= LCD_WIDTH)
lcd_print_utf(stat); // The string isn't scrolling
else {
if (status_scroll_pos <= slen - LCD_WIDTH)
lcd_print_utf(stat); // The string fills the screen
else {
uint8_t chars = LCD_WIDTH;
if (status_scroll_pos < slen) { // First string still visible
lcd_print_utf(stat); // The string leaves space
chars -= slen - status_scroll_pos; // Amount of space left
}
u8g.print('.'); // Always at 1+ spaces left, draw a dot
if (--chars) {
if (status_scroll_pos < slen + 1) // Draw a second dot if there's space
--chars, u8g.print('.');
if (chars) lcd_print_utf(lcd_status_message, chars); // Print a second copy of the message
}
}
if (last_blink != blink) {
last_blink = blink;
// Skip any non-printing bytes
if (status_scroll_pos < slen) while (!PRINTABLE(lcd_status_message[status_scroll_pos])) status_scroll_pos++;
if (++status_scroll_pos >= slen + 2) status_scroll_pos = 0;
}
}
#else
UNUSED(blink);
lcd_print_utf(lcd_status_message);
#endif
}
static void lcd_implementation_status_screen() {
const bool blink = lcd_blink();
#if FAN_ANIM_FRAMES > 2
static bool old_blink;
static uint8_t fan_frame;
if (old_blink != blink) {
old_blink = blink;
if (!fanSpeeds[0] || ++fan_frame >= FAN_ANIM_FRAMES) fan_frame = 0;
}
#endif
// Status Menu Font
lcd_setFont(FONT_STATUSMENU);
//
// Fan Animation
//
// Draws the whole heading image as a B/W bitmap rather than
// drawing the elements separately.
// This was done as an optimization, as it was slower to draw
// multiple parts compared to a single bitmap.
//
// The bitmap:
// - May be offset in X
// - Includes all nozzle(s), bed(s), and the fan.
//
// TODO:
//
// - Only draw the whole header on the first
// entry to the status screen. Nozzle, bed, and
// fan outline bits don't change.
//
if (PAGE_UNDER(STATUS_SCREENHEIGHT + 1)) {
u8g.drawBitmapP(
STATUS_SCREEN_X, STATUS_SCREEN_Y,
(STATUS_SCREENWIDTH + 7) / 8, STATUS_SCREENHEIGHT,
#if HAS_FAN0
#if FAN_ANIM_FRAMES > 2
fan_frame == 1 ? status_screen1_bmp :
fan_frame == 2 ? status_screen2_bmp :
#if FAN_ANIM_FRAMES > 3
fan_frame == 3 ? status_screen3_bmp :
#endif
#else
blink && fanSpeeds[0] ? status_screen1_bmp :
#endif
#endif
status_screen0_bmp
);
}
//
// Temperature Graphics and Info
//
if (PAGE_UNDER(28)) {
// Extruders
HOTEND_LOOP() _draw_heater_status(STATUS_SCREEN_HOTEND_TEXT_X(e), e, blink);
// Heated bed
#if HOTENDS < 4 && HAS_TEMP_BED
_draw_heater_status(STATUS_SCREEN_BED_TEXT_X, -1, blink);
#endif
#if HAS_FAN0
if (PAGE_CONTAINS(20, 27)) {
// Fan
const int16_t per = ((fanSpeeds[0] + 1) * 100) / 256;
if (per) {
u8g.setPrintPos(STATUS_SCREEN_FAN_TEXT_X, STATUS_SCREEN_FAN_TEXT_Y);
lcd_print(itostr3(per));
u8g.print('%');
}
}
#endif
}
#if ENABLED(SDSUPPORT)
//
// SD Card Symbol
//
if (card.isFileOpen() && PAGE_CONTAINS(42 - (TALL_FONT_CORRECTION), 51 - (TALL_FONT_CORRECTION))) {
// Upper box
u8g.drawBox(42, 42 - (TALL_FONT_CORRECTION), 8, 7); // 42-48 (or 41-47)
// Right edge
u8g.drawBox(50, 44 - (TALL_FONT_CORRECTION), 2, 5); // 44-48 (or 43-47)
// Bottom hollow box
u8g.drawFrame(42, 49 - (TALL_FONT_CORRECTION), 10, 4); // 49-52 (or 48-51)
// Corner pixel
u8g.drawPixel(50, 43 - (TALL_FONT_CORRECTION)); // 43 (or 42)
}
#endif // SDSUPPORT
#if ENABLED(SDSUPPORT) || ENABLED(LCD_SET_PROGRESS_MANUALLY)
//
// Progress bar frame
//
#define PROGRESS_BAR_X 54
#define PROGRESS_BAR_WIDTH (LCD_PIXEL_WIDTH - PROGRESS_BAR_X)
if (PAGE_CONTAINS(49, 52 - (TALL_FONT_CORRECTION))) // 49-52 (or 49-51)
u8g.drawFrame(
PROGRESS_BAR_X, 49,
PROGRESS_BAR_WIDTH, 4 - (TALL_FONT_CORRECTION)
);
#if DISABLED(LCD_SET_PROGRESS_MANUALLY)
const uint8_t progress_bar_percent = card.percentDone();
#endif
if (progress_bar_percent > 1) {
//
// Progress bar solid part
//
if (PAGE_CONTAINS(50, 51 - (TALL_FONT_CORRECTION))) // 50-51 (or just 50)
u8g.drawBox(
PROGRESS_BAR_X + 1, 50,
(uint16_t)((PROGRESS_BAR_WIDTH - 2) * progress_bar_percent * 0.01), 2 - (TALL_FONT_CORRECTION)
);
//
// SD Percent Complete
//
#if ENABLED(DOGM_SD_PERCENT)
if (PAGE_CONTAINS(41, 48)) {
// Percent complete
u8g.setPrintPos(55, 48);
u8g.print(itostr3(progress_bar_percent));
u8g.print('%');
}
#endif
}
//
// Elapsed Time
//
#if DISABLED(DOGM_SD_PERCENT)
#define SD_DURATION_X (PROGRESS_BAR_X + (PROGRESS_BAR_WIDTH / 2) - len * (DOG_CHAR_WIDTH / 2))
#else
#define SD_DURATION_X (LCD_PIXEL_WIDTH - len * DOG_CHAR_WIDTH)
#endif
if (PAGE_CONTAINS(41, 48)) {
char buffer[10];
duration_t elapsed = print_job_timer.duration();
bool has_days = (elapsed.value >= 60*60*24L);
uint8_t len = elapsed.toDigital(buffer, has_days);
u8g.setPrintPos(SD_DURATION_X, 48);
lcd_print(buffer);
}
#endif // SDSUPPORT || LCD_SET_PROGRESS_MANUALLY
//
// XYZ Coordinates
//
#if ENABLED(USE_SMALL_INFOFONT)
#define INFO_FONT_HEIGHT 7
#else
#define INFO_FONT_HEIGHT 8
#endif
#define XYZ_BASELINE (30 + INFO_FONT_HEIGHT)
#define X_LABEL_POS 3
#define X_VALUE_POS 11
#define XYZ_SPACING 40
#if ENABLED(XYZ_HOLLOW_FRAME)
#define XYZ_FRAME_TOP 29
#define XYZ_FRAME_HEIGHT INFO_FONT_HEIGHT + 3
#else
#define XYZ_FRAME_TOP 30
#define XYZ_FRAME_HEIGHT INFO_FONT_HEIGHT + 1
#endif
// Before homing the axis letters are blinking 'X' <-> '?'.
// When axis is homed but axis_known_position is false the axis letters are blinking 'X' <-> ' '.
// When everything is ok you see a constant 'X'.
static char xstring[5], ystring[5], zstring[7];
#if ENABLED(FILAMENT_LCD_DISPLAY)
static char wstring[5], mstring[4];
#endif
// At the first page, regenerate the XYZ strings
if (page.page == 0) {
strcpy(xstring, ftostr4sign(LOGICAL_X_POSITION(current_position[X_AXIS])));
strcpy(ystring, ftostr4sign(LOGICAL_Y_POSITION(current_position[Y_AXIS])));
strcpy(zstring, ftostr52sp(FIXFLOAT(LOGICAL_Z_POSITION(current_position[Z_AXIS]))));
#if ENABLED(FILAMENT_LCD_DISPLAY)
strcpy(wstring, ftostr12ns(filament_width_meas));
strcpy(mstring, itostr3(100.0 * (
parser.volumetric_enabled
? planner.volumetric_area_nominal / planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
: planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
)
));
#endif
}
if (PAGE_CONTAINS(XYZ_FRAME_TOP, XYZ_FRAME_TOP + XYZ_FRAME_HEIGHT - 1)) {
#if ENABLED(XYZ_HOLLOW_FRAME)
u8g.drawFrame(0, XYZ_FRAME_TOP, LCD_PIXEL_WIDTH, XYZ_FRAME_HEIGHT); // 8: 29-40 7: 29-39
#else
u8g.drawBox(0, XYZ_FRAME_TOP, LCD_PIXEL_WIDTH, XYZ_FRAME_HEIGHT); // 8: 30-39 7: 30-37
#endif
if (PAGE_CONTAINS(XYZ_BASELINE - (INFO_FONT_HEIGHT - 1), XYZ_BASELINE)) {
#if DISABLED(XYZ_HOLLOW_FRAME)
u8g.setColorIndex(0); // white on black
#endif
u8g.setPrintPos(0 * XYZ_SPACING + X_LABEL_POS, XYZ_BASELINE);
_draw_axis_label(X_AXIS, PSTR(MSG_X), blink);
u8g.setPrintPos(0 * XYZ_SPACING + X_VALUE_POS, XYZ_BASELINE);
lcd_print(xstring);
u8g.setPrintPos(1 * XYZ_SPACING + X_LABEL_POS, XYZ_BASELINE);
_draw_axis_label(Y_AXIS, PSTR(MSG_Y), blink);
u8g.setPrintPos(1 * XYZ_SPACING + X_VALUE_POS, XYZ_BASELINE);
lcd_print(ystring);
u8g.setPrintPos(2 * XYZ_SPACING + X_LABEL_POS, XYZ_BASELINE);
_draw_axis_label(Z_AXIS, PSTR(MSG_Z), blink);
u8g.setPrintPos(2 * XYZ_SPACING + X_VALUE_POS, XYZ_BASELINE);
lcd_print(zstring);
#if DISABLED(XYZ_HOLLOW_FRAME)
u8g.setColorIndex(1); // black on white
#endif
}
}
//
// Feedrate
//
if (PAGE_CONTAINS(51 - INFO_FONT_HEIGHT, 49)) {
lcd_setFont(FONT_MENU);
u8g.setPrintPos(3, 50);
lcd_print(LCD_STR_FEEDRATE[0]);
lcd_setFont(FONT_STATUSMENU);
u8g.setPrintPos(12, 50);
lcd_print(itostr3(feedrate_percentage));
u8g.print('%');
//
// Filament sensor display if SD is disabled
//
#if ENABLED(FILAMENT_LCD_DISPLAY) && DISABLED(SDSUPPORT)
u8g.setPrintPos(56, 50);
lcd_print(wstring);
u8g.setPrintPos(102, 50);
lcd_print(mstring);
u8g.print('%');
lcd_setFont(FONT_MENU);
u8g.setPrintPos(47, 50);
lcd_print(LCD_STR_FILAM_DIA);
u8g.setPrintPos(93, 50);
lcd_print(LCD_STR_FILAM_MUL);
#endif
}
//
// Status line
//
#define STATUS_BASELINE (55 + INFO_FONT_HEIGHT)
if (PAGE_CONTAINS(STATUS_BASELINE - (INFO_FONT_HEIGHT - 1), STATUS_BASELINE)) {
u8g.setPrintPos(0, STATUS_BASELINE);
#if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
if (PENDING(millis(), previous_lcd_status_ms + 5000UL)) { //Display both Status message line and Filament display on the last line
lcd_implementation_status_message(blink);
}
else {
lcd_printPGM(PSTR(LCD_STR_FILAM_DIA));
u8g.print(':');
lcd_print(wstring);
lcd_printPGM(PSTR(" " LCD_STR_FILAM_MUL));
u8g.print(':');
lcd_print(mstring);
u8g.print('%');
}
#else
lcd_implementation_status_message(blink);
#endif
}
}
#if ENABLED(ULTIPANEL) #if ENABLED(ULTIPANEL)
uint8_t row_y1, row_y2; uint8_t row_y1, row_y2;

Loading…
Cancel
Save