Browse Source

update from 2.0.x branch

pull/45/head
Sergey 3 years ago
parent
commit
80552d3f70
  1. 4
      Marlin/src/gcode/bedlevel/G35.cpp
  2. 2
      Marlin/src/gcode/bedlevel/abl/G29.cpp
  3. 4
      Marlin/src/inc/Conditionals_LCD.h
  4. 312
      Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
  5. 4
      Marlin/src/lcd/extui/mks_ui/draw_dialog.cpp
  6. 2
      Marlin/src/lcd/extui/mks_ui/draw_dialog.h
  7. 4
      Marlin/src/lcd/extui/mks_ui/draw_media_select.cpp
  8. 2
      Marlin/src/lcd/extui/mks_ui/draw_pause_message.cpp
  9. 12
      Marlin/src/lcd/extui/mks_ui/draw_ready_print.cpp
  10. 4
      Marlin/src/lcd/extui/mks_ui/draw_tool.cpp
  11. 6
      Marlin/src/lcd/extui/mks_ui/draw_touch_calibration.cpp
  12. 188
      Marlin/src/lcd/extui/mks_ui/mks_hardware.cpp
  13. 2
      Marlin/src/lcd/extui/mks_ui/mks_hardware.h
  14. 647
      Marlin/src/lcd/extui/mks_ui/mks_hardware_test.cpp
  15. 33
      Marlin/src/lcd/extui/mks_ui/mks_hardware_test.h
  16. 141
      Marlin/src/lcd/extui/mks_ui/wifiSerial.cpp
  17. 2
      Marlin/src/module/planner.cpp
  18. 2
      Marlin/src/module/probe.cpp
  19. 68
      Marlin/src/pins/stm32f4/pins_BTT_SKR_PRO_common.h
  20. 6
      Marlin/src/pins/stm32f4/pins_MKS_ROBIN_NANO_V3.h
  21. 29
      buildroot/share/PlatformIO/scripts/mks_encrypt.py
  22. 42
      buildroot/share/PlatformIO/scripts/stm32_bootloader.py
  23. 1
      buildroot/tests/mks_robin_nano35
  24. 1
      buildroot/tests/mks_robin_nano35_maple

4
Marlin/src/gcode/bedlevel/G35.cpp

@ -106,7 +106,7 @@ void GcodeSuite::G35() {
const float z_probed_height = probe.probe_at_point(screws_tilt_adjust_pos[i], PROBE_PT_RAISE, 0, true);
if (isnan(z_probed_height)) {
SERIAL_ECHOPAIR("G35 failed at point ", i, " (");
SERIAL_ECHOPAIR("G35 failed at point ", i + 1, " (");
SERIAL_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i]));
SERIAL_CHAR(')');
SERIAL_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[i].y);
@ -115,7 +115,7 @@ void GcodeSuite::G35() {
}
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOPAIR("Probing point ", i, " (");
DEBUG_ECHOPAIR("Probing point ", i + 1, " (");
DEBUG_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i]));
DEBUG_CHAR(')');
DEBUG_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[i].y, SP_Z_STR, z_probed_height);

2
Marlin/src/gcode/bedlevel/abl/G29.cpp

@ -568,7 +568,7 @@ G29_TYPE GcodeSuite::G29() {
// Probe at 3 arbitrary points
if (abl.abl_probe_index < abl.abl_points) {
abl.probePos = points[abl.abl_probe_index];
abl.probePos = xy_pos_t(points[abl.abl_probe_index]);
_manual_goto_xy(abl.probePos);
// Disable software endstops to allow manual adjustment
// If G29 is not completed, they will not be re-enabled

4
Marlin/src/inc/Conditionals_LCD.h

@ -213,7 +213,7 @@
#define LCD_PROGRESS_BAR
#endif
#if ENABLED(TFTGLCD_PANEL_I2C)
#define LCD_I2C_ADDRESS 0x27 // Must be equal to panel's I2C slave addres
#define LCD_I2C_ADDRESS 0x33 // Must be 0x33 for STM32 main boards and equal to panel's I2C slave addres
#endif
#define LCD_USE_I2C_BUZZER // Enable buzzer on LCD, used for both I2C and SPI buses (LiquidTWI2 not required)
#define STD_ENCODER_PULSES_PER_STEP 2
@ -1053,7 +1053,7 @@
#endif
// E jerk exists with JD disabled (of course) but also when Linear Advance is disabled on Delta/SCARA
#if ENABLED(CLASSIC_JERK) || (IS_KINEMATIC && DISABLED(LIN_ADVANCE))
#if HAS_EXTRUDERS && (ENABLED(CLASSIC_JERK) || (IS_KINEMATIC && DISABLED(LIN_ADVANCE)))
#define HAS_CLASSIC_E_JERK 1
#endif

312
Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp

@ -57,6 +57,18 @@
#include "../../gcode/parser.h"
#endif
#if EITHER(HAS_COOLER, LASER_COOLANT_FLOW_METER)
#include "../../feature/cooler.h"
#endif
#if ENABLED(I2C_AMMETER)
#include "../../feature/ammeter.h"
#endif
#if HAS_CUTTER
#include "../../feature/spindle_laser.h"
#endif
#if ENABLED(AUTO_BED_LEVELING_UBL)
#include "../../feature/bedlevel/bedlevel.h"
#endif
@ -64,12 +76,12 @@
TFTGLCD lcd;
#define ICON_LOGO B00000001
#define ICON_TEMP1 B00000010 //hotend 1
#define ICON_TEMP2 B00000100 //hotend 2
#define ICON_TEMP3 B00001000 //hotend 3
#define ICON_TEMP1 B00000010 // Hotend 1
#define ICON_TEMP2 B00000100 // Hotend 2
#define ICON_TEMP3 B00001000 // Hotend 3
#define ICON_BED B00010000
#define ICON_FAN B00100000
#define ICON_HOT B01000000 //when any T > 50deg
#define ICON_HOT B01000000 // When any T > 50deg
#define PIC_MASK 0x7F
// LEDs not used, for compatibility with Smoothieware
@ -433,69 +445,161 @@ FORCE_INLINE void _draw_axis_value(const AxisEnum axis, const char *value, const
lcd_put_u8str(value);
}
FORCE_INLINE void _draw_heater_status(const heater_id_t heater_id, const char *prefix, const bool blink) {
uint8_t pic_hot_bits;
#if HAS_HEATED_BED
const bool isBed = heater_id < 0;
const celsius_t t1 = (isBed ? thermalManager.wholeDegBed() : thermalManager.wholeDegHotend(heater_id)),
t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater_id));
#else
const celsius_t t1 = thermalManager.wholeDegHotend(heater_id), t2 = thermalManager.degTargetHotend(heater_id);
#endif
#if HAS_HOTEND || HAS_HEATED_BED
#if HOTENDS < 2
if (heater_id == H_E0) {
lcd.setCursor(2, 5); lcd.print(prefix); //HE
lcd.setCursor(1, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(1, 7);
}
else {
lcd.setCursor(6, 5); lcd.print(prefix); //BED
lcd.setCursor(6, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(6, 7);
FORCE_INLINE void _draw_heater_status(const heater_id_t heater_id, const char *prefix, const bool blink) {
uint8_t pic_hot_bits;
#if HAS_HEATED_BED
const bool isBed = heater_id < 0;
const celsius_t t1 = (isBed ? thermalManager.wholeDegBed() : thermalManager.wholeDegHotend(heater_id)),
t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater_id));
#else
const celsius_t t1 = thermalManager.wholeDegHotend(heater_id), t2 = thermalManager.degTargetHotend(heater_id);
#endif
#if HOTENDS < 2
if (heater_id == H_E0) {
lcd.setCursor(2, 5); lcd.print(prefix); //HE
lcd.setCursor(1, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(1, 7);
}
else {
lcd.setCursor(6, 5); lcd.print(prefix); //BED
lcd.setCursor(6, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(6, 7);
}
#else
if (heater_id > H_BED) {
lcd.setCursor(heater_id * 4, 5); lcd.print(prefix); // HE1 or HE2 or HE3
lcd.setCursor(heater_id * 4, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(heater_id * 4, 7);
}
else {
lcd.setCursor(13, 5); lcd.print(prefix); //BED
lcd.setCursor(13, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(13, 7);
}
#endif // HOTENDS <= 1
#if !HEATER_IDLE_HANDLER
UNUSED(blink);
#else
if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
lcd.write(' ');
if (t2 >= 10) lcd.write(' ');
if (t2 >= 100) lcd.write(' ');
}
else
#endif // !HEATER_IDLE_HANDLER
lcd.print(i16tostr3rj(t2));
switch (heater_id) {
case H_BED: pic_hot_bits = ICON_BED; break;
case H_E0: pic_hot_bits = ICON_TEMP1; break;
case H_E1: pic_hot_bits = ICON_TEMP2; break;
case H_E2: pic_hot_bits = ICON_TEMP3;
default: break;
}
#else
if (heater_id > H_BED) {
lcd.setCursor(heater_id * 4, 5); lcd.print(prefix); // HE1 or HE2 or HE3
lcd.setCursor(heater_id * 4, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(heater_id * 4, 7);
if (t2) picBits |= pic_hot_bits;
else picBits &= ~pic_hot_bits;
if (t1 > 50) hotBits |= pic_hot_bits;
else hotBits &= ~pic_hot_bits;
if (hotBits) picBits |= ICON_HOT;
else picBits &= ~ICON_HOT;
}
#endif // HAS_HOTEND || HAS_HEATED_BED
#if HAS_COOLER
FORCE_INLINE void _draw_cooler_status(const bool blink) {
const celsius_t t2 = thermalManager.degTargetCooler();
lcd.setCursor(0, 5); lcd_put_u8str_P(PSTR("COOL"));
lcd.setCursor(1, 6); lcd_put_u8str(i16tostr3rj(thermalManager.wholeDegCooler()));
lcd.setCursor(1, 7);
#if !HEATER_IDLE_HANDLER
UNUSED(blink);
#else
if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
lcd_put_wchar(' ');
if (t2 >= 10) lcd_put_wchar(' ');
if (t2 >= 100) lcd_put_wchar(' ');
}
else
#endif
lcd_put_u8str(i16tostr3left(t2));
lcd_put_wchar(' ');
if (t2 < 10) lcd_put_wchar(' ');
if (t2) picBits |= ICON_TEMP1;
else picBits &= ~ICON_TEMP1;
}
#endif // HAS_COOLER
#if ENABLED(LASER_COOLANT_FLOW_METER)
FORCE_INLINE void _draw_flowmeter_status() {
lcd.setCursor(5, 5); lcd_put_u8str_P(PSTR("FLOW"));
lcd.setCursor(7, 6); lcd_put_wchar('L');
lcd.setCursor(6, 7); lcd_put_u8str(ftostr11ns(cooler.flowrate));
if (cooler.flowrate) picBits |= ICON_FAN;
else picBits &= ~ICON_FAN;
}
#endif
#if ENABLED(I2C_AMMETER)
FORCE_INLINE void _draw_ammeter_status() {
lcd.setCursor(10, 5); lcd_put_u8str_P(PSTR("ILAZ"));
ammeter.read();
lcd.setCursor(11, 6);
if (ammeter.current <= 0.999f)
{
lcd_put_u8str("mA");
lcd.setCursor(10, 7);
lcd_put_wchar(' '); lcd_put_u8str(ui16tostr3rj(uint16_t(ammeter.current * 1000 + 0.5f)));
}
else {
lcd.setCursor(13, 5); lcd.print(prefix); //BED
lcd.setCursor(13, 6); lcd.print(i16tostr3rj(t1));
lcd.setCursor(13, 7);
lcd_put_u8str(" A");
lcd.setCursor(10, 7);
lcd_put_u8str(ftostr12ns(ammeter.current));
}
#endif // HOTENDS <= 1
#if !HEATER_IDLE_HANDLER
UNUSED(blink);
#else
if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
lcd.write(' ');
if (t2 >= 10) lcd.write(' ');
if (t2 >= 100) lcd.write(' ');
}
else
#endif // !HEATER_IDLE_HANDLER
lcd.print(i16tostr3rj(t2));
switch (heater_id) {
case H_BED: pic_hot_bits = ICON_BED; break;
case H_E0: pic_hot_bits = ICON_TEMP1; break;
case H_E1: pic_hot_bits = ICON_TEMP2; break;
case H_E2: pic_hot_bits = ICON_TEMP3;
default: break;
if (ammeter.current) picBits |= ICON_BED;
else picBits &= ~ICON_BED;
}
if (t2) picBits |= pic_hot_bits;
else picBits &= ~pic_hot_bits;
#endif // I2C_AMMETER
if (t1 > 50) hotBits |= pic_hot_bits;
else hotBits &= ~pic_hot_bits;
#if HAS_CUTTER
if (hotBits) picBits |= ICON_HOT;
else picBits &= ~ICON_HOT;
}
FORCE_INLINE void _draw_cutter_status() {
lcd.setCursor(15, 5); lcd_put_u8str_P(PSTR("CUTT"));
#if CUTTER_UNIT_IS(RPM)
lcd.setCursor(16, 6); lcd_put_u8str_P(PSTR("RPM"));
lcd.setCursor(15, 7); lcd_put_u8str(ftostr31ns(float(cutter.unitPower) / 1000));
lcd_put_wchar('K');
#elif CUTTER_UNIT_IS(PERCENT)
lcd.setCursor(17, 6); lcd_put_wchar('%');
lcd.setCursor(18, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
#else
lcd.setCursor(17, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
#endif
if (cutter.unitPower) picBits |= ICON_HOT;
else picBits &= ~ICON_HOT;
}
#endif // HAS_CUTTER
#if HAS_PRINT_PROGRESS
@ -533,7 +637,7 @@ FORCE_INLINE void _draw_heater_status(const heater_id_t heater_id, const char *p
}
}
#endif
#endif // LCD_PROGRESS_BAR
void MarlinUI::draw_status_message(const bool blink) {
if (!PanelDetected) return;
@ -648,6 +752,19 @@ or
or
|X 000 Y 000 Z 000.00|
|FR100% SD100% C--:--|
| Progress bar line |
|Status message |
| |
|COOL FLOW ILAZ CUTT |
| ttc L mA RPM |
| tts f.f aaa rr.rK|
| ICO ICO ICO ICO |
| ICO ICO ICO ICO |
or
Equal to 24x10 text LCD
|X 000 Y 000 Z 000.00 |
@ -745,50 +862,61 @@ void MarlinUI::draw_status_screen() {
#endif
//
// Line 6..8 Temperatures, FAN
// Line 6..8 Temperatures, FAN for printer or Cooler, Flowmetter, Ampermeter, Cutter for laser/spindle
//
#if HOTENDS < 2
_draw_heater_status(H_E0, "HE", blink); // Hotend Temperature
#else
_draw_heater_status(H_E0, "HE1", blink); // Hotend 1 Temperature
_draw_heater_status(H_E1, "HE2", blink); // Hotend 2 Temperature
#if HOTENDS > 2
_draw_heater_status(H_E2, "HE3", blink); // Hotend 3 Temperature
#endif
#endif
#if HAS_HOTEND
#if HAS_HEATED_BED
#if HAS_LEVELING
_draw_heater_status(H_BED, (planner.leveling_active && blink ? "___" : "BED"), blink);
#if HOTENDS < 2
_draw_heater_status(H_E0, "HE", blink); // Hotend Temperature
#else
_draw_heater_status(H_BED, "BED", blink);
_draw_heater_status(H_E0, "HE1", blink); // Hotend 1 Temperature
_draw_heater_status(H_E1, "HE2", blink); // Hotend 2 Temperature
#if HOTENDS > 2
_draw_heater_status(H_E2, "HE3", blink); // Hotend 3 Temperature
#endif
#endif
#endif
#if HAS_FAN
uint16_t spd = thermalManager.fan_speed[0];
#if ENABLED(ADAPTIVE_FAN_SLOWING)
if (!blink) spd = thermalManager.scaledFanSpeed(0, spd);
#if HAS_HEATED_BED
#if HAS_LEVELING
_draw_heater_status(H_BED, (planner.leveling_active && blink ? "___" : "BED"), blink);
#else
_draw_heater_status(H_BED, "BED", blink);
#endif
#endif
uint16_t per = thermalManager.pwmToPercent(spd);
#if HOTENDS < 2
#define FANX 11
#else
#define FANX 17
#endif
lcd.setCursor(FANX, 5); lcd_put_u8str_P(PSTR("FAN"));
lcd.setCursor(FANX + 1, 6); lcd.write('%');
lcd.setCursor(FANX, 7);
lcd.print(i16tostr3rj(per));
#if HAS_FAN
uint16_t spd = thermalManager.fan_speed[0];
#if ENABLED(ADAPTIVE_FAN_SLOWING)
if (!blink) spd = thermalManager.scaledFanSpeed(0, spd);
#endif
uint16_t per = thermalManager.pwmToPercent(spd);
if (TERN0(HAS_FAN0, thermalManager.fan_speed[0]) || TERN0(HAS_FAN1, thermalManager.fan_speed[1]) || TERN0(HAS_FAN2, thermalManager.fan_speed[2]))
picBits |= ICON_FAN;
else
picBits &= ~ICON_FAN;
#if HOTENDS < 2
#define FANX 11
#else
#define FANX 17
#endif
lcd.setCursor(FANX, 5); lcd_put_u8str_P(PSTR("FAN"));
lcd.setCursor(FANX + 1, 6); lcd.write('%');
lcd.setCursor(FANX, 7);
lcd.print(i16tostr3rj(per));
if (TERN0(HAS_FAN0, thermalManager.fan_speed[0]) || TERN0(HAS_FAN1, thermalManager.fan_speed[1]) || TERN0(HAS_FAN2, thermalManager.fan_speed[2]))
picBits |= ICON_FAN;
else
picBits &= ~ICON_FAN;
#endif // HAS_FAN
#endif // HAS_FAN
#else
TERN_(HAS_COOLER, _draw_cooler_status(blink));
TERN_(LASER_COOLANT_FLOW_METER, _draw_flowmeter_status());
TERN_(I2C_AMMETER, _draw_ammeter_status());
TERN_(HAS_CUTTER, _draw_cutter_status());
#endif
//
// Line 9, 10 - icons

4
Marlin/src/lcd/extui/mks_ui/draw_dialog.cpp

@ -238,7 +238,7 @@ void lv_draw_dialog(uint8_t type) {
lv_obj_t *labelOk = lv_label_create_empty(btnOk); // Add a label to the button
lv_label_set_text(labelOk, print_file_dialog_menu.confirm); // Set the labels text
}
else if (DIALOG_IS(PAUSE_MESSAGE_PAUSING, PAUSE_MESSAGE_CHANGING, PAUSE_MESSAGE_UNLOAD, PAUSE_MESSAGE_LOAD, PAUSE_MESSAGE_PURGE, PAUSE_MESSAGE_RESUME, PAUSE_MESSAGE_HEATING)) {
else if (DIALOG_IS(PAUSE_MESSAGE_PARKING, PAUSE_MESSAGE_CHANGING, PAUSE_MESSAGE_UNLOAD, PAUSE_MESSAGE_LOAD, PAUSE_MESSAGE_PURGE, PAUSE_MESSAGE_RESUME, PAUSE_MESSAGE_HEATING)) {
// nothing to do
}
else if (DIALOG_IS(WIFI_ENABLE_TIPS)) {
@ -324,7 +324,7 @@ void lv_draw_dialog(uint8_t type) {
lv_label_set_text(labelDialog, print_file_dialog_menu.print_finish);
lv_obj_align(labelDialog, nullptr, LV_ALIGN_CENTER, 0, -20);
}
else if (DIALOG_IS(PAUSE_MESSAGE_PAUSING)) {
else if (DIALOG_IS(PAUSE_MESSAGE_PARKING)) {
lv_label_set_text(labelDialog, pause_msg_menu.pausing);
lv_obj_align(labelDialog, nullptr, LV_ALIGN_CENTER, 0, -20);
}

2
Marlin/src/lcd/extui/mks_ui/draw_dialog.h

@ -54,7 +54,7 @@ enum {
DIALOG_WIFI_ENABLE_TIPS,
DIALOG_PAUSE_MESSAGE_PAUSING,
DIALOG_PAUSE_MESSAGE_PARKING,
DIALOG_PAUSE_MESSAGE_CHANGING,
DIALOG_PAUSE_MESSAGE_UNLOAD,
DIALOG_PAUSE_MESSAGE_WAITING,

4
Marlin/src/lcd/extui/mks_ui/draw_media_select.cpp

@ -39,7 +39,7 @@ enum {
};
#if ENABLED(MKS_TEST)
extern uint8_t curent_disp_ui;
extern uint8_t current_disp_ui;
#endif
static void event_handler(lv_obj_t *obj, lv_event_t event) {
@ -49,7 +49,7 @@ static void event_handler(lv_obj_t *obj, lv_event_t event) {
case ID_T_USB_DISK: card.changeMedia(&card.media_driver_usbFlash); break;
case ID_T_SD_DISK: card.changeMedia(&card.media_driver_sdcard); break;
case ID_T_RETURN:
TERN_(MKS_TEST, curent_disp_ui = 1);
TERN_(MKS_TEST, current_disp_ui = 1);
lv_draw_ready_print();
return;
}

2
Marlin/src/lcd/extui/mks_ui/draw_pause_message.cpp

@ -31,7 +31,7 @@
void lv_draw_pause_message(const PauseMessage msg) {
switch (msg) {
case PAUSE_MESSAGE_PAUSING: clear_cur_ui(); lv_draw_dialog(DIALOG_PAUSE_MESSAGE_PAUSING); break;
case PAUSE_MESSAGE_PARKING: clear_cur_ui(); lv_draw_dialog(DIALOG_PAUSE_MESSAGE_PARKING); break;
case PAUSE_MESSAGE_CHANGING: clear_cur_ui(); lv_draw_dialog(DIALOG_PAUSE_MESSAGE_CHANGING); break;
case PAUSE_MESSAGE_UNLOAD: clear_cur_ui(); lv_draw_dialog(DIALOG_PAUSE_MESSAGE_UNLOAD); break;
case PAUSE_MESSAGE_WAITING: clear_cur_ui(); lv_draw_dialog(DIALOG_PAUSE_MESSAGE_WAITING); break;

12
Marlin/src/lcd/extui/mks_ui/draw_ready_print.cpp

@ -61,7 +61,7 @@ static lv_obj_t *buttonExt1, *labelExt1, *buttonFanstate, *labelFan;
#endif
#if ENABLED(MKS_TEST)
uint8_t curent_disp_ui = 0;
uint8_t current_disp_ui = 0;
#endif
enum { ID_TOOL = 1, ID_SET, ID_PRINT, ID_INFO_EXT, ID_INFO_BED, ID_INFO_FAN };
@ -106,8 +106,10 @@ void disp_det_error() {
lv_obj_t *e1, *e2, *e3, *bed;
void mks_disp_test() {
char buf[30] = {0};
sprintf_P(buf, PSTR("e1:%d"), thermalManager.wholeDegHotend(0));
lv_label_set_text(e1, buf);
#if HAS_HOTEND
sprintf_P(buf, PSTR("e1:%d"), thermalManager.wholeDegHotend(0));
lv_label_set_text(e1, buf);
#endif
#if HAS_MULTI_HOTEND
sprintf_P(buf, PSTR("e2:%d"), thermalManager.wholeDegHotend(1));
lv_label_set_text(e2, buf);
@ -126,7 +128,7 @@ void lv_draw_ready_print() {
ZERO(disp_state_stack._disp_state);
scr = lv_screen_create(PRINT_READY_UI, "");
if (TERN0(SDSUPPORT, mks_test_flag == 0x1E)) {
if (mks_test_flag == 0x1E) {
// Create image buttons
buttonTool = lv_imgbtn_create(scr, "F:/bmp_tool.bin", event_handler, ID_TOOL);
@ -147,7 +149,7 @@ void lv_draw_ready_print() {
#if HAS_MULTI_HOTEND
e2 = lv_label_create_empty(scr);
lv_obj_set_pos(e2, 20, 45);
sprintf_P(buf, PSTR("e1: %d"), thermalManager.wholeDegHotend(1));
sprintf_P(buf, PSTR("e2: %d"), thermalManager.wholeDegHotend(1));
lv_label_set_text(e2, buf);
#endif
#if HAS_HEATED_BED

4
Marlin/src/lcd/extui/mks_ui/draw_tool.cpp

@ -45,7 +45,7 @@ enum {
};
#if ENABLED(MKS_TEST)
extern uint8_t curent_disp_ui;
extern uint8_t current_disp_ui;
#endif
static void event_handler(lv_obj_t *obj, lv_event_t event) {
@ -75,7 +75,7 @@ static void event_handler(lv_obj_t *obj, lv_event_t event) {
lv_draw_more();
break;
case ID_T_RETURN:
TERN_(MKS_TEST, curent_disp_ui = 1);
TERN_(MKS_TEST, current_disp_ui = 1);
lv_draw_ready_print();
break;
}

6
Marlin/src/lcd/extui/mks_ui/draw_touch_calibration.cpp

@ -34,6 +34,10 @@
static lv_obj_t *scr;
static lv_obj_t *status_label;
#if ENABLED(MKS_TEST)
extern uint8_t current_disp_ui;
#endif
static void event_handler(lv_obj_t *obj, lv_event_t event);
enum {
@ -93,7 +97,7 @@ static void event_handler(lv_obj_t *obj, lv_event_t event) {
if (event != LV_EVENT_RELEASED) return;
switch (obj->mks_obj_id) {
case ID_TC_RETURN:
TERN_(MKS_TEST, curent_disp_ui = 1);
TERN_(MKS_TEST, current_disp_ui = 1);
lv_clear_touch_calibration_screen();
draw_return_ui();
break;

188
Marlin/src/lcd/extui/mks_ui/mks_hardware.cpp

@ -38,12 +38,44 @@
#if ENABLED(MKS_TEST)
#include "mks_hardware.h"
#include "../../../module/endstops.h"
bool pw_det_sta, pw_off_sta, mt_det_sta;
#if PIN_EXISTS(MT_DET_2)
bool mt_det2_sta;
#endif
bool endstopx1_sta, endstopx2_sta, endstopy1_sta, endstopy2_sta, endstopz1_sta, endstopz2_sta;
#if HAS_X_MIN || HAS_X_MAX
bool endstopx1_sta;
#else
constexpr static bool endstopx1_sta = true;
#endif
#if HAS_X2_MIN || HAS_X2_MAX
bool endstopx2_sta;
#else
constexpr static bool endstopx2_sta = true;
#endif
#if HAS_Y_MIN || HAS_Y_MAX
bool endstopy1_sta;
#else
constexpr static bool endstopy1_sta = true;
#endif
#if HAS_Y2_MIN || HAS_Y2_MAX
bool endstopy2_sta;
#else
constexpr static bool endstopy2_sta = true;
#endif
#if HAS_Z_MIN || HAS_Z_MAX
bool endstopz1_sta;
#else
constexpr static bool endstopz1_sta = true;
#endif
#if HAS_Z2_MIN || HAS_Z2_MAX
bool endstopz2_sta;
#else
constexpr static bool endstopz2_sta = true;
#endif
#define ESTATE(S) (READ(S##_PIN) != S##_ENDSTOP_INVERTING)
void test_gpio_readlevel_L() {
WRITE(WIFI_IO0_PIN, HIGH);
@ -54,10 +86,36 @@
#if PIN_EXISTS(MT_DET_2)
mt_det2_sta = (READ(MT_DET_2_PIN) == LOW);
#endif
endstopx1_sta = (READ(X_MIN_PIN) == LOW);
endstopy1_sta = (READ(Y_MIN_PIN) == LOW);
endstopz1_sta = (READ(Z_MIN_PIN) == LOW);
endstopz2_sta = (READ(Z_MAX_PIN) == LOW);
#if HAS_X_MIN
endstopx1_sta = ESTATE(X_MIN);
#elif HAS_X_MAX
endstopx1_sta = ESTATE(X_MAX);
#endif
#if HAS_X2_MIN
endstopx2_sta = ESTATE(X2_MIN);
#elif HAS_X2_MAX
endstopx2_sta = ESTATE(X2_MAX);
#endif
#if HAS_Y_MIN
endstopy1_sta = ESTATE(Y_MIN);
#elif HAS_Y_MAX
endstopy1_sta = ESTATE(Y_MAX);
#endif
#if HAS_Y2_MIN
endstopy2_sta = ESTATE(Y2_MIN);
#elif HAS_Y2_MAX
endstopy2_sta = ESTATE(Y2_MAX);
#endif
#if HAS_Z_MIN
endstopz1_sta = ESTATE(Z_MIN);
#elif HAS_Z_MAX
endstopz1_sta = ESTATE(Z_MAX);
#endif
#if HAS_Z2_MIN
endstopz2_sta = ESTATE(Z2_MIN);
#elif HAS_Z2_MAX
endstopz2_sta = ESTATE(Z2_MAX);
#endif
}
void test_gpio_readlevel_H() {
@ -69,44 +127,66 @@
#if PIN_EXISTS(MT_DET_2)
mt_det2_sta = (READ(MT_DET_2_PIN) == HIGH);
#endif
endstopx1_sta = (READ(X_MIN_PIN) == HIGH);
endstopy1_sta = (READ(Y_MIN_PIN) == HIGH);
endstopz1_sta = (READ(Z_MIN_PIN) == HIGH);
endstopz2_sta = (READ(Z_MAX_PIN) == HIGH);
#if HAS_X_MIN
endstopx1_sta = !ESTATE(X_MIN);
#elif HAS_X_MAX
endstopx1_sta = !ESTATE(X_MAX);
#endif
#if HAS_X2_MIN
endstopx2_sta = !ESTATE(X2_MIN);
#elif HAS_X2_MAX
endstopx2_sta = !ESTATE(X2_MAX);
#endif
#if HAS_Y_MIN
endstopy1_sta = !ESTATE(Y_MIN);
#elif HAS_Y_MAX
endstopy1_sta = !ESTATE(Y_MAX);
#endif
#if HAS_Y2_MIN
endstopy2_sta = !ESTATE(Y2_MIN);
#elif HAS_Y2_MAX
endstopy2_sta = !ESTATE(Y2_MAX);
#endif
#if HAS_Z_MIN
endstopz1_sta = !ESTATE(Z_MIN);
#elif HAS_Z_MAX
endstopz1_sta = !ESTATE(Z_MAX);
#endif
#if HAS_Z2_MIN
endstopz2_sta = !ESTATE(Z2_MIN);
#elif HAS_Z2_MAX
endstopz2_sta = !ESTATE(Z2_MAX);
#endif
}
void init_test_gpio() {
SET_INPUT_PULLUP(X_MIN_PIN);
SET_INPUT_PULLUP(Y_MIN_PIN);
SET_INPUT_PULLUP(Z_MIN_PIN);
SET_INPUT_PULLUP(Z_MAX_PIN);
endstops.init();
SET_OUTPUT(WIFI_IO0_PIN);
SET_INPUT_PULLUP(MT_DET_1_PIN);
#if PIN_EXISTS(MT_DET_1)
SET_INPUT_PULLUP(MT_DET_1_PIN);
#endif
#if PIN_EXISTS(MT_DET_2)
SET_INPUT_PULLUP(MT_DET_2_PIN);
#endif
SET_INPUT_PULLUP(MKS_TEST_POWER_LOSS_PIN);
SET_INPUT_PULLUP(MKS_TEST_PS_ON_PIN);
SET_INPUT_PULLUP(SERVO0_PIN);
SET_OUTPUT(X_ENABLE_PIN);
SET_OUTPUT(Y_ENABLE_PIN);
SET_OUTPUT(Z_ENABLE_PIN);
SET_OUTPUT(E0_ENABLE_PIN);
#if DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
SET_OUTPUT(E1_ENABLE_PIN);
OUT_WRITE(X_ENABLE_PIN, LOW);
#if HAS_Y_AXIS
OUT_WRITE(Y_ENABLE_PIN, LOW);
#endif
WRITE(X_ENABLE_PIN, LOW);
WRITE(Y_ENABLE_PIN, LOW);
WRITE(Z_ENABLE_PIN, LOW);
WRITE(E0_ENABLE_PIN, LOW);
#if DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
WRITE(E1_ENABLE_PIN, LOW);
#if HAS_Z_AXIS
OUT_WRITE(Z_ENABLE_PIN, LOW);
#endif
#if HAS_EXTRUDERS
OUT_WRITE(E0_ENABLE_PIN, LOW);
#endif
#if HAS_MULTI_EXTRUDER && DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
OUT_WRITE(E1_ENABLE_PIN, LOW);
#endif
#if ENABLED(MKS_HARDWARE_TEST_ONLY_E0)
@ -161,34 +241,54 @@
void mks_hardware_test() {
if (millis() % 2000 < 1000) {
thermalManager.fan_speed[0] = 255;
WRITE(X_DIR_PIN, LOW);
WRITE(Y_DIR_PIN, LOW);
WRITE(Z_DIR_PIN, LOW);
WRITE(E0_DIR_PIN, LOW);
#if DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
#if HAS_Y_AXIS
WRITE(Y_DIR_PIN, LOW);
#endif
#if HAS_Z_AXIS
WRITE(Z_DIR_PIN, LOW);
#endif
#if HAS_EXTRUDERS
WRITE(E0_DIR_PIN, LOW);
#endif
#if HAS_MULTI_EXTRUDER && DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
WRITE(E1_DIR_PIN, LOW);
#endif
thermalManager.fan_speed[0] = 255;
#if DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
#if HAS_MULTI_HOTEND && DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
WRITE(HEATER_1_PIN, HIGH); // HE1
#endif
WRITE(HEATER_0_PIN, HIGH); // HE0
WRITE(HEATER_BED_PIN, HIGH); // HOT-BED
#if HAS_HOTEND
WRITE(HEATER_0_PIN, HIGH); // HE0
#endif
#if HAS_HEATED_BED
WRITE(HEATER_BED_PIN, HIGH); // HOT-BED
#endif
}
else {
thermalManager.fan_speed[0] = 0;
WRITE(X_DIR_PIN, HIGH);
WRITE(Y_DIR_PIN, HIGH);
WRITE(Z_DIR_PIN, HIGH);
WRITE(E0_DIR_PIN, HIGH);
#if DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
#if HAS_Y_AXIS
WRITE(Y_DIR_PIN, HIGH);
#endif
#if HAS_Y_AXIS
WRITE(Z_DIR_PIN, HIGH);
#endif
#if HAS_EXTRUDERS
WRITE(E0_DIR_PIN, HIGH);
#endif
#if HAS_MULTI_EXTRUDER && DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
WRITE(E1_DIR_PIN, HIGH);
#endif
thermalManager.fan_speed[0] = 0;
#if DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
#if HAS_MULTI_HOTEND && DISABLED(MKS_HARDWARE_TEST_ONLY_E0)
WRITE(HEATER_1_PIN, LOW); // HE1
#endif
WRITE(HEATER_0_PIN, LOW); // HE0
WRITE(HEATER_BED_PIN, LOW); // HOT-BED
#if HAS_HOTEND
WRITE(HEATER_0_PIN, LOW); // HE0
#endif
#if HAS_HEATED_BED
WRITE(HEATER_BED_PIN, LOW); // HOT-BED
#endif
}
if (endstopx1_sta && endstopx2_sta && endstopy1_sta && endstopy2_sta && endstopz1_sta && endstopz2_sta) {

2
Marlin/src/lcd/extui/mks_ui/mks_hardware.h

@ -31,6 +31,8 @@
void mks_test_get();
void mks_gpio_test();
extern uint8_t mks_test_flag;
#else
#define mks_test_flag 0
#endif
// String display and assets

647
Marlin/src/lcd/extui/mks_ui/mks_hardware_test.cpp

@ -1,647 +0,0 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../../../inc/MarlinConfigPre.h"
#if HAS_TFT_LVGL_UI
#include "SPI_TFT.h"
#include "tft_lvgl_configuration.h"
#include "draw_ready_print.h"
#include "mks_hardware_test.h"
#include "draw_ui.h"
#include "pic_manager.h"
#include <lvgl.h>
#include "../../../MarlinCore.h"
#include "../../../module/temperature.h"
#include "../../../sd/cardreader.h"
uint8_t pw_det_sta, pw_off_sta, mt_det_sta, mt_det3_sta;
#if PIN_EXISTS(MT_DET_2)
uint8_t mt_det2_sta;
#endif
uint8_t endstopx1_sta, endstopx2_sta, endstopy1_sta, endstopy2_sta, endstopz1_sta, endstopz2_sta;
void test_gpio_readlevel_L() {
#if ENABLED(MKS_TEST)
volatile uint32_t itest;
WRITE(WIFI_IO0_PIN, HIGH);
itest = 10000;
while (itest--);
pw_det_sta = (READ(MKS_TEST_POWER_LOSS_PIN) == 0);
pw_off_sta = (READ(MKS_TEST_PS_ON_PIN) == 0);
mt_det_sta = (READ(MT_DET_1_PIN) == 0);
#if PIN_EXISTS(MT_DET_2)
mt_det2_sta = (READ(MT_DET_2_PIN) == 0);
#endif
endstopx1_sta = (READ(X_MIN_PIN) == 0);
endstopy1_sta = (READ(Y_MIN_PIN) == 0);
endstopz1_sta = (READ(Z_MIN_PIN) == 0);
endstopz2_sta = (READ(Z_MAX_PIN) == 0);
#endif
}
void test_gpio_readlevel_H() {
#if ENABLED(MKS_TEST)
volatile uint32_t itest;
WRITE(WIFI_IO0_PIN, LOW);
itest = 10000;
while (itest--);
pw_det_sta = (READ(MKS_TEST_POWER_LOSS_PIN) == 1);
pw_off_sta = (READ(MKS_TEST_PS_ON_PIN) == 1);
mt_det_sta = (READ(MT_DET_1_PIN) == 1);
#if PIN_EXISTS(MT_DET_2)
mt_det2_sta = (READ(MT_DET_2_PIN) == 1);
#endif
endstopx1_sta = (READ(X_MIN_PIN) == 1);
endstopy1_sta = (READ(Y_MIN_PIN) == 1);
endstopz1_sta = (READ(Z_MIN_PIN) == 1);
endstopz2_sta = (READ(Z_MAX_PIN) == 1);
#endif
}
void init_test_gpio() {
#ifdef MKS_TEST
SET_INPUT_PULLUP(X_MIN_PIN);
SET_INPUT_PULLUP(Y_MIN_PIN);
SET_INPUT_PULLUP(Z_MIN_PIN);
SET_INPUT_PULLUP(Z_MAX_PIN);
SET_OUTPUT(WIFI_IO0_PIN);
SET_INPUT_PULLUP(MT_DET_1_PIN);
#if PIN_EXISTS(MT_DET_2)
SET_INPUT_PULLUP(MT_DET_2_PIN);
#endif
SET_INPUT_PULLUP(MKS_TEST_POWER_LOSS_PIN);
SET_INPUT_PULLUP(MKS_TEST_PS_ON_PIN);
SET_INPUT_PULLUP(SERVO0_PIN);
SET_OUTPUT(X_ENABLE_PIN);
SET_OUTPUT(Y_ENABLE_PIN);
SET_OUTPUT(Z_ENABLE_PIN);
SET_OUTPUT(E0_ENABLE_PIN);
#if !MB(MKS_ROBIN_E3P)
SET_OUTPUT(E1_ENABLE_PIN);
#endif
WRITE(X_ENABLE_PIN, LOW);
WRITE(Y_ENABLE_PIN, LOW);
WRITE(Z_ENABLE_PIN, LOW);
WRITE(E0_ENABLE_PIN, LOW);
#if !MB(MKS_ROBIN_E3P)
WRITE(E1_ENABLE_PIN, LOW);
#endif
#if MB(MKS_ROBIN_E3P)
SET_INPUT_PULLUP(PA1);
SET_INPUT_PULLUP(PA3);
SET_INPUT_PULLUP(PC2);
SET_INPUT_PULLUP(PD8);
SET_INPUT_PULLUP(PE5);
SET_INPUT_PULLUP(PE6);
SET_INPUT_PULLUP(PE7);
#endif
#endif
}
void mks_test_beeper() {
#ifdef MKS_TEST
WRITE(BEEPER_PIN, HIGH);
delay(100);
WRITE(BEEPER_PIN, LOW);
delay(100);
#endif
}
void mks_gpio_test() {
#if ENABLED(MKS_TEST)
init_test_gpio();
test_gpio_readlevel_L();
test_gpio_readlevel_H();
test_gpio_readlevel_L();
if ((pw_det_sta == 1)
&& (pw_off_sta == 1)
&& (mt_det_sta == 1)
#if PIN_EXISTS(MT_DET_2)
&& (mt_det2_sta == 1)
#endif
#if MB(MKS_ROBIN_E3P)
&& (READ(PA1) == 0)
&& (READ(PA3) == 0)
&& (READ(PC2) == 0)
&& (READ(PD8) == 0)
&& (READ(PE5) == 0)
&& (READ(PE6) == 0)
&& (READ(PE7) == 0)
#endif
)
disp_det_ok();
else
disp_det_error();
if ( (endstopx1_sta == 1)
&& (endstopy1_sta == 1)
&& (endstopz1_sta == 1)
&& (endstopz2_sta == 1)
)
disp_Limit_ok();
else
disp_Limit_error();
#endif
}
void mks_hardware_test() {
#if ENABLED(MKS_TEST)
if (millis() % 2000 < 1000) {
WRITE(X_DIR_PIN, LOW);
WRITE(Y_DIR_PIN, LOW);
WRITE(Z_DIR_PIN, LOW);
WRITE(E0_DIR_PIN, LOW);
#if !MB(MKS_ROBIN_E3P)
WRITE(E1_DIR_PIN, LOW);
#endif
thermalManager.fan_speed[0] = 255;
#if !MB(MKS_ROBIN_E3P)
WRITE(HEATER_1_PIN, HIGH); // HE1
#endif
WRITE(HEATER_0_PIN, HIGH); // HE0
WRITE(HEATER_BED_PIN, HIGH); // HOT-BED
}
else {
WRITE(X_DIR_PIN, HIGH);
WRITE(Y_DIR_PIN, HIGH);
WRITE(Z_DIR_PIN, HIGH);
WRITE(E0_DIR_PIN, HIGH);
#if !MB(MKS_ROBIN_E3P)
WRITE(E1_DIR_PIN, HIGH);
#endif
thermalManager.fan_speed[0] = 0;
#if !MB(MKS_ROBIN_E3P)
WRITE(HEATER_1_PIN, LOW); // HE1
#endif
WRITE(HEATER_0_PIN, LOW); // HE0
WRITE(HEATER_BED_PIN, LOW); // HOT-BED
}
if ( (endstopx1_sta == 1) && (endstopx2_sta == 1)
&& (endstopy1_sta == 1) && (endstopy2_sta == 1)
&& (endstopz1_sta == 1) && (endstopz2_sta == 1)
) {
// nothing here
}
else {
}
if (disp_state == PRINT_READY_UI)
mks_disp_test();
#endif
}
static const uint16_t ASCII_Table_16x24[] PROGMEM = {
// Space ' '
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '!'
0x0000, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0000, 0x0000,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '"'
0x0000, 0x0000, 0x00CC, 0x00CC, 0x00CC, 0x00CC, 0x00CC, 0x00CC,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '#'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0C60, 0x0C60,
0x0C60, 0x0630, 0x0630, 0x1FFE, 0x1FFE, 0x0630, 0x0738, 0x0318,
0x1FFE, 0x1FFE, 0x0318, 0x0318, 0x018C, 0x018C, 0x018C, 0x0000,
// '$'
0x0000, 0x0080, 0x03E0, 0x0FF8, 0x0E9C, 0x1C8C, 0x188C, 0x008C,
0x0098, 0x01F8, 0x07E0, 0x0E80, 0x1C80, 0x188C, 0x188C, 0x189C,
0x0CB8, 0x0FF0, 0x03E0, 0x0080, 0x0080, 0x0000, 0x0000, 0x0000,
// '%'
0x0000, 0x0000, 0x0000, 0x180E, 0x0C1B, 0x0C11, 0x0611, 0x0611,
0x0311, 0x0311, 0x019B, 0x018E, 0x38C0, 0x6CC0, 0x4460, 0x4460,
0x4430, 0x4430, 0x4418, 0x6C18, 0x380C, 0x0000, 0x0000, 0x0000,
// '&'
0x0000, 0x01E0, 0x03F0, 0x0738, 0x0618, 0x0618, 0x0330, 0x01F0,
0x00F0, 0x00F8, 0x319C, 0x330E, 0x1E06, 0x1C06, 0x1C06, 0x3F06,
0x73FC, 0x21F0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// "'"
0x0000, 0x0000, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '('
0x0000, 0x0200, 0x0300, 0x0180, 0x00C0, 0x00C0, 0x0060, 0x0060,
0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
0x0060, 0x0060, 0x00C0, 0x00C0, 0x0180, 0x0300, 0x0200, 0x0000,
// ')'
0x0000, 0x0020, 0x0060, 0x00C0, 0x0180, 0x0180, 0x0300, 0x0300,
0x0600, 0x0600, 0x0600, 0x0600, 0x0600, 0x0600, 0x0600, 0x0600,
0x0300, 0x0300, 0x0180, 0x0180, 0x00C0, 0x0060, 0x0020, 0x0000,
// '*'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00C0, 0x00C0,
0x06D8, 0x07F8, 0x01E0, 0x0330, 0x0738, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '+'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x3FFC, 0x3FFC, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// ','
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0180, 0x0180, 0x0100, 0x0100, 0x0080, 0x0000, 0x0000,
// '-'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x07E0, 0x07E0, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '.'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x00C0, 0x00C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '/'
0x0000, 0x0C00, 0x0C00, 0x0600, 0x0600, 0x0600, 0x0300, 0x0300,
0x0300, 0x0380, 0x0180, 0x0180, 0x0180, 0x00C0, 0x00C0, 0x00C0,
0x0060, 0x0060, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '0'
0x0000, 0x03E0, 0x07F0, 0x0E38, 0x0C18, 0x180C, 0x180C, 0x180C,
0x180C, 0x180C, 0x180C, 0x180C, 0x180C, 0x180C, 0x0C18, 0x0E38,
0x07F0, 0x03E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '1'
0x0000, 0x0100, 0x0180, 0x01C0, 0x01F0, 0x0198, 0x0188, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '2'
0x0000, 0x03E0, 0x0FF8, 0x0C18, 0x180C, 0x180C, 0x1800, 0x1800,
0x0C00, 0x0600, 0x0300, 0x0180, 0x00C0, 0x0060, 0x0030, 0x0018,
0x1FFC, 0x1FFC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '3'
0x0000, 0x01E0, 0x07F8, 0x0E18, 0x0C0C, 0x0C0C, 0x0C00, 0x0600,
0x03C0, 0x07C0, 0x0C00, 0x1800, 0x1800, 0x180C, 0x180C, 0x0C18,
0x07F8, 0x03E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '4'
0x0000, 0x0C00, 0x0E00, 0x0F00, 0x0F00, 0x0D80, 0x0CC0, 0x0C60,
0x0C60, 0x0C30, 0x0C18, 0x0C0C, 0x3FFC, 0x3FFC, 0x0C00, 0x0C00,
0x0C00, 0x0C00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '5'
0x0000, 0x0FF8, 0x0FF8, 0x0018, 0x0018, 0x000C, 0x03EC, 0x07FC,
0x0E1C, 0x1C00, 0x1800, 0x1800, 0x1800, 0x180C, 0x0C1C, 0x0E18,
0x07F8, 0x03E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '6'
0x0000, 0x07C0, 0x0FF0, 0x1C38, 0x1818, 0x0018, 0x000C, 0x03CC,
0x0FEC, 0x0E3C, 0x1C1C, 0x180C, 0x180C, 0x180C, 0x1C18, 0x0E38,
0x07F0, 0x03E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '7'
0x0000, 0x1FFC, 0x1FFC, 0x0C00, 0x0600, 0x0600, 0x0300, 0x0380,
0x0180, 0x01C0, 0x00C0, 0x00E0, 0x0060, 0x0060, 0x0070, 0x0030,
0x0030, 0x0030, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '8'
0x0000, 0x03E0, 0x07F0, 0x0E38, 0x0C18, 0x0C18, 0x0C18, 0x0638,
0x07F0, 0x07F0, 0x0C18, 0x180C, 0x180C, 0x180C, 0x180C, 0x0C38,
0x0FF8, 0x03E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '9'
0x0000, 0x03E0, 0x07F0, 0x0E38, 0x0C1C, 0x180C, 0x180C, 0x180C,
0x1C1C, 0x1E38, 0x1BF8, 0x19E0, 0x1800, 0x0C00, 0x0C00, 0x0E1C,
0x07F8, 0x01F0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// ':'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0180, 0x0180,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// ';'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0180, 0x0180,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0180, 0x0180, 0x0100, 0x0100, 0x0080, 0x0000, 0x0000, 0x0000,
// '<'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x1000, 0x1C00, 0x0F80, 0x03E0, 0x00F8, 0x0018, 0x00F8, 0x03E0,
0x0F80, 0x1C00, 0x1000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '='
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x1FF8, 0x0000, 0x0000, 0x0000, 0x1FF8, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '>'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0008, 0x0038, 0x01F0, 0x07C0, 0x1F00, 0x1800, 0x1F00, 0x07C0,
0x01F0, 0x0038, 0x0008, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '?'
0x0000, 0x03E0, 0x0FF8, 0x0C18, 0x180C, 0x180C, 0x1800, 0x0C00,
0x0600, 0x0300, 0x0180, 0x00C0, 0x00C0, 0x00C0, 0x0000, 0x0000,
0x00C0, 0x00C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '@'
0x0000, 0x0000, 0x07E0, 0x1818, 0x2004, 0x29C2, 0x4A22, 0x4411,
0x4409, 0x4409, 0x4409, 0x2209, 0x1311, 0x0CE2, 0x4002, 0x2004,
0x1818, 0x07E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'A'
0x0000, 0x0380, 0x0380, 0x06C0, 0x06C0, 0x06C0, 0x0C60, 0x0C60,
0x1830, 0x1830, 0x1830, 0x3FF8, 0x3FF8, 0x701C, 0x600C, 0x600C,
0xC006, 0xC006, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'B'
0x0000, 0x03FC, 0x0FFC, 0x0C0C, 0x180C, 0x180C, 0x180C, 0x0C0C,
0x07FC, 0x0FFC, 0x180C, 0x300C, 0x300C, 0x300C, 0x300C, 0x180C,
0x1FFC, 0x07FC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'C'
0x0000, 0x07C0, 0x1FF0, 0x3838, 0x301C, 0x700C, 0x6006, 0x0006,
0x0006, 0x0006, 0x0006, 0x0006, 0x0006, 0x6006, 0x700C, 0x301C,
0x1FF0, 0x07E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'D'
0x0000, 0x03FE, 0x0FFE, 0x0E06, 0x1806, 0x1806, 0x3006, 0x3006,
0x3006, 0x3006, 0x3006, 0x3006, 0x3006, 0x1806, 0x1806, 0x0E06,
0x0FFE, 0x03FE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'E'
0x0000, 0x3FFC, 0x3FFC, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C,
0x1FFC, 0x1FFC, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C,
0x3FFC, 0x3FFC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'F'
0x0000, 0x3FF8, 0x3FF8, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018,
0x1FF8, 0x1FF8, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018,
0x0018, 0x0018, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'G'
0x0000, 0x0FE0, 0x3FF8, 0x783C, 0x600E, 0xE006, 0xC007, 0x0003,
0x0003, 0xFE03, 0xFE03, 0xC003, 0xC007, 0xC006, 0xC00E, 0xF03C,
0x3FF8, 0x0FE0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'H'
0x0000, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C,
0x3FFC, 0x3FFC, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C,
0x300C, 0x300C, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'I'
0x0000, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'J'
0x0000, 0x0600, 0x0600, 0x0600, 0x0600, 0x0600, 0x0600, 0x0600,
0x0600, 0x0600, 0x0600, 0x0600, 0x0600, 0x0618, 0x0618, 0x0738,
0x03F0, 0x01E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'K'
0x0000, 0x3006, 0x1806, 0x0C06, 0x0606, 0x0306, 0x0186, 0x00C6,
0x0066, 0x0076, 0x00DE, 0x018E, 0x0306, 0x0606, 0x0C06, 0x1806,
0x3006, 0x6006, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'L'
0x0000, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018,
0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018,
0x1FF8, 0x1FF8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'M'
0x0000, 0xE00E, 0xF01E, 0xF01E, 0xF01E, 0xD836, 0xD836, 0xD836,
0xD836, 0xCC66, 0xCC66, 0xCC66, 0xC6C6, 0xC6C6, 0xC6C6, 0xC6C6,
0xC386, 0xC386, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'N'
0x0000, 0x300C, 0x301C, 0x303C, 0x303C, 0x306C, 0x306C, 0x30CC,
0x30CC, 0x318C, 0x330C, 0x330C, 0x360C, 0x360C, 0x3C0C, 0x3C0C,
0x380C, 0x300C, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'O'
0x0000, 0x07E0, 0x1FF8, 0x381C, 0x700E, 0x6006, 0xC003, 0xC003,
0xC003, 0xC003, 0xC003, 0xC003, 0xC003, 0x6006, 0x700E, 0x381C,
0x1FF8, 0x07E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'P'
0x0000, 0x0FFC, 0x1FFC, 0x380C, 0x300C, 0x300C, 0x300C, 0x300C,
0x180C, 0x1FFC, 0x07FC, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C,
0x000C, 0x000C, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'Q'
0x0000, 0x07E0, 0x1FF8, 0x381C, 0x700E, 0x6006, 0xE003, 0xC003,
0xC003, 0xC003, 0xC003, 0xC003, 0xE007, 0x6306, 0x3F0E, 0x3C1C,
0x3FF8, 0xF7E0, 0xC000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'R'
0x0000, 0x0FFE, 0x1FFE, 0x3806, 0x3006, 0x3006, 0x3006, 0x3806,
0x1FFE, 0x07FE, 0x0306, 0x0606, 0x0C06, 0x1806, 0x1806, 0x3006,
0x3006, 0x6006, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'S'
0x0000, 0x03E0, 0x0FF8, 0x0C1C, 0x180C, 0x180C, 0x000C, 0x001C,
0x03F8, 0x0FE0, 0x1E00, 0x3800, 0x3006, 0x3006, 0x300E, 0x1C1C,
0x0FF8, 0x07E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'T'
0x0000, 0x7FFE, 0x7FFE, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'U'
0x0000, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C,
0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x300C, 0x1818,
0x1FF8, 0x07E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'V'
0x0000, 0x6003, 0x3006, 0x3006, 0x3006, 0x180C, 0x180C, 0x180C,
0x0C18, 0x0C18, 0x0E38, 0x0630, 0x0630, 0x0770, 0x0360, 0x0360,
0x01C0, 0x01C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'W'
0x0000, 0x6003, 0x61C3, 0x61C3, 0x61C3, 0x3366, 0x3366, 0x3366,
0x3366, 0x3366, 0x3366, 0x1B6C, 0x1B6C, 0x1B6C, 0x1A2C, 0x1E3C,
0x0E38, 0x0E38, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'X'
0x0000, 0xE00F, 0x700C, 0x3018, 0x1830, 0x0C70, 0x0E60, 0x07C0,
0x0380, 0x0380, 0x03C0, 0x06E0, 0x0C70, 0x1C30, 0x1818, 0x300C,
0x600E, 0xE007, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'Y'
0x0000, 0xC003, 0x6006, 0x300C, 0x381C, 0x1838, 0x0C30, 0x0660,
0x07E0, 0x03C0, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'Z'
0x0000, 0x7FFC, 0x7FFC, 0x6000, 0x3000, 0x1800, 0x0C00, 0x0600,
0x0300, 0x0180, 0x00C0, 0x0060, 0x0030, 0x0018, 0x000C, 0x0006,
0x7FFE, 0x7FFE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '['
0x0000, 0x03E0, 0x03E0, 0x0060, 0x0060, 0x0060, 0x0060, 0x0060,
0x0060, 0x0060, 0x0060, 0x0060, 0x0060, 0x0060, 0x0060, 0x0060,
0x0060, 0x0060, 0x0060, 0x0060, 0x0060, 0x03E0, 0x03E0, 0x0000,
// '\'
0x0000, 0x0030, 0x0030, 0x0060, 0x0060, 0x0060, 0x00C0, 0x00C0,
0x00C0, 0x01C0, 0x0180, 0x0180, 0x0180, 0x0300, 0x0300, 0x0300,
0x0600, 0x0600, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// ']'
0x0000, 0x03E0, 0x03E0, 0x0300, 0x0300, 0x0300, 0x0300, 0x0300,
0x0300, 0x0300, 0x0300, 0x0300, 0x0300, 0x0300, 0x0300, 0x0300,
0x0300, 0x0300, 0x0300, 0x0300, 0x0300, 0x03E0, 0x03E0, 0x0000,
// '^'
0x0000, 0x0000, 0x01C0, 0x01C0, 0x0360, 0x0360, 0x0360, 0x0630,
0x0630, 0x0C18, 0x0C18, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '_'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '''
0x0000, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'a'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03F0, 0x07F8,
0x0C1C, 0x0C0C, 0x0F00, 0x0FF0, 0x0CF8, 0x0C0C, 0x0C0C, 0x0F1C,
0x0FF8, 0x18F0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'b'
0x0000, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x03D8, 0x0FF8,
0x0C38, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x0C38,
0x0FF8, 0x03D8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'c'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03C0, 0x07F0,
0x0E30, 0x0C18, 0x0018, 0x0018, 0x0018, 0x0018, 0x0C18, 0x0E30,
0x07F0, 0x03C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'd'
0x0000, 0x1800, 0x1800, 0x1800, 0x1800, 0x1800, 0x1BC0, 0x1FF0,
0x1C30, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1C30,
0x1FF0, 0x1BC0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'e'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03C0, 0x0FF0,
0x0C30, 0x1818, 0x1FF8, 0x1FF8, 0x0018, 0x0018, 0x1838, 0x1C30,
0x0FF0, 0x07C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'f'
0x0000, 0x0F80, 0x0FC0, 0x00C0, 0x00C0, 0x00C0, 0x07F0, 0x07F0,
0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'g'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0DE0, 0x0FF8,
0x0E18, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0C0C, 0x0E18,
0x0FF8, 0x0DE0, 0x0C00, 0x0C0C, 0x061C, 0x07F8, 0x01F0, 0x0000,
// 'h'
0x0000, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x07D8, 0x0FF8,
0x1C38, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818,
0x1818, 0x1818, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'i'
0x0000, 0x00C0, 0x00C0, 0x0000, 0x0000, 0x0000, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'j'
0x0000, 0x00C0, 0x00C0, 0x0000, 0x0000, 0x0000, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00F8, 0x0078, 0x0000,
// 'k'
0x0000, 0x000C, 0x000C, 0x000C, 0x000C, 0x000C, 0x0C0C, 0x060C,
0x030C, 0x018C, 0x00CC, 0x006C, 0x00FC, 0x019C, 0x038C, 0x030C,
0x060C, 0x0C0C, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'l'
0x0000, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'm'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3C7C, 0x7EFF,
0xE3C7, 0xC183, 0xC183, 0xC183, 0xC183, 0xC183, 0xC183, 0xC183,
0xC183, 0xC183, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'n'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0798, 0x0FF8,
0x1C38, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818,
0x1818, 0x1818, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'o'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03C0, 0x0FF0,
0x0C30, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x0C30,
0x0FF0, 0x03C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'p'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03D8, 0x0FF8,
0x0C38, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x0C38,
0x0FF8, 0x03D8, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0000,
// 'q'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1BC0, 0x1FF0,
0x1C30, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1C30,
0x1FF0, 0x1BC0, 0x1800, 0x1800, 0x1800, 0x1800, 0x1800, 0x0000,
// 'r'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x07B0, 0x03F0,
0x0070, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
0x0030, 0x0030, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 's'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03E0, 0x03F0,
0x0E38, 0x0C18, 0x0038, 0x03F0, 0x07C0, 0x0C00, 0x0C18, 0x0E38,
0x07F0, 0x03E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 't'
0x0000, 0x0000, 0x0080, 0x00C0, 0x00C0, 0x00C0, 0x07F0, 0x07F0,
0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0,
0x07C0, 0x0780, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'u'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1818, 0x1818,
0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1818, 0x1C38,
0x1FF0, 0x19E0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'v'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x180C, 0x0C18,
0x0C18, 0x0C18, 0x0630, 0x0630, 0x0630, 0x0360, 0x0360, 0x0360,
0x01C0, 0x01C0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'w'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x41C1, 0x41C1,
0x61C3, 0x6363, 0x6363, 0x6363, 0x3636, 0x3636, 0x3636, 0x1C1C,
0x1C1C, 0x1C1C, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'x'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x381C, 0x1C38,
0x0C30, 0x0660, 0x0360, 0x0360, 0x0360, 0x0360, 0x0660, 0x0C30,
0x1C38, 0x381C, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 'y'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3018, 0x1830,
0x1830, 0x1870, 0x0C60, 0x0C60, 0x0CE0, 0x06C0, 0x06C0, 0x0380,
0x0380, 0x0380, 0x0180, 0x0180, 0x01C0, 0x00F0, 0x0070, 0x0000,
// 'z'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x1FFC, 0x1FFC,
0x0C00, 0x0600, 0x0300, 0x0180, 0x00C0, 0x0060, 0x0030, 0x0018,
0x1FFC, 0x1FFC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// '{'
0x0000, 0x0300, 0x0180, 0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x00C0,
0x00C0, 0x0060, 0x0060, 0x0030, 0x0060, 0x0040, 0x00C0, 0x00C0,
0x00C0, 0x00C0, 0x00C0, 0x00C0, 0x0180, 0x0300, 0x0000, 0x0000,
// '|'
0x0000, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0180, 0x0000,
// '}'
0x0000, 0x0060, 0x00C0, 0x01C0, 0x0180, 0x0180, 0x0180, 0x0180,
0x0180, 0x0300, 0x0300, 0x0600, 0x0300, 0x0100, 0x0180, 0x0180,
0x0180, 0x0180, 0x0180, 0x0180, 0x00C0, 0x0060, 0x0000, 0x0000,
// '~'
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x10F0, 0x1FF8, 0x0F08, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
void disp_char_1624(uint16_t x, uint16_t y, uint8_t c, uint16_t charColor, uint16_t bkColor) {
for (uint16_t i = 0; i < 24; i++) {
const uint16_t tmp_char = pgm_read_word(&ASCII_Table_16x24[((c - 0x20) * 24) + i]);
for (uint16_t j = 0; j < 16; j++)
SPI_TFT.SetPoint(x + j, y + i, ((tmp_char >> j) & 0x01) ? charColor : bkColor);
}
}
void disp_string(uint16_t x, uint16_t y, const char * string, uint16_t charColor, uint16_t bkColor) {
while (*string != '\0') {
disp_char_1624(x, y, *string, charColor, bkColor);
string++;
x += 16;
}
}
void disp_assets_update() {
SPI_TFT.LCD_clear(0x0000);
disp_string(100, 140, "Assets Updating...", 0xFFFF, 0x0000);
}
void disp_assets_update_progress(const char *msg) {
char buf[30];
memset(buf, ' ', COUNT(buf));
strncpy(buf, msg, strlen(msg));
buf[COUNT(buf)-1] = '\0';
disp_string(100, 165, buf, 0xFFFF, 0x0000);
}
uint8_t mks_test_flag = 0;
const char *MKSTestPath = "MKS_TEST";
#if ENABLED(SDSUPPORT)
void mks_test_get() {
SdFile dir, root = card.getroot();
if (dir.open(&root, MKSTestPath, O_RDONLY))
mks_test_flag = 0x1E;
}
#endif
#endif // HAS_TFT_LVGL_UI

33
Marlin/src/lcd/extui/mks_ui/mks_hardware_test.h

@ -1,33 +0,0 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <lvgl.h>
void mks_gpio_test();
void disp_char_1624(uint16_t x, uint16_t y, uint8_t c, uint16_t charColor, uint16_t bkColor);
void disp_string(uint16_t x, uint16_t y, const char * string, uint16_t charColor, uint16_t bkColor);
void mks_hardware_test();
void disp_assets_update();
void disp_assets_update_progress(const char *msg);
void mks_test_get();
extern uint8_t mks_test_flag;

141
Marlin/src/lcd/extui/mks_ui/wifiSerial.cpp

@ -1,141 +0,0 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2020 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 <https://www.gnu.org/licenses/>.
*
*/
#include "../../../inc/MarlinConfigPre.h"
#if HAS_TFT_LVGL_UI
#include "tft_lvgl_configuration.h"
#if ENABLED(MKS_WIFI_MODULE)
#include "draw_ui.h"
#include "wifiSerial.h"
#include <libmaple/libmaple.h>
#include <libmaple/gpio.h>
#include <libmaple/timer.h>
#include <libmaple/usart.h>
#include <libmaple/ring_buffer.h>
#include "../../../MarlinCore.h"
DEFINE_WFSERIAL(WifiSerial1, 1);
WifiSerial::WifiSerial(usart_dev *usart_device, uint8 tx_pin, uint8 rx_pin) {
this->usart_device = usart_device;
this->tx_pin = tx_pin;
this->rx_pin = rx_pin;
}
/**
* Set up / tear down
*/
#if STM32_MCU_SERIES == STM32_SERIES_F1
/* F1 MCUs have no GPIO_AFR[HL], so turn off PWM if there's a conflict
* on this GPIO bit. */
static void disable_timer_if_necessary(timer_dev *dev, uint8 ch) {
if (dev) timer_set_mode(dev, ch, TIMER_DISABLED);
}
static void usart_enable_no_irq(usart_dev *usart_device, bool with_irq) {
if (with_irq) usart_enable(usart_device);
else {
usart_reg_map *regs = usart_device->regs;
regs->CR1 |= (USART_CR1_TE | USART_CR1_RE); // don't change the word length etc, and 'or' in the pattern not overwrite |USART_CR1_M_8N1);
regs->CR1 |= USART_CR1_UE;
}
}
#elif STM32_MCU_SERIES == STM32_SERIES_F2 || STM32_MCU_SERIES == STM32_SERIES_F4
#define disable_timer_if_necessary(dev, ch) ((void)0)
static void usart_enable_no_irq(usart_dev *usart_device, bool with_irq) {
if (with_irq) usart_enable(usart_device);
else {
usart_reg_map *regs = usart_device->regs;
regs->CR1 |= (USART_CR1_TE | USART_CR1_RE); // don't change the word length etc, and 'or' in the pattern not overwrite |USART_CR1_M_8N1);
regs->CR1 |= USART_CR1_UE;
}
}
#else
#warning "Unsupported STM32 series; timer conflicts are possible"
#define usart_enable_no_irq(X, Y) usart_enable(X)
#endif
void WifiSerial::begin(uint32 baud) { begin(baud, SERIAL_8N1); }
/**
* Roger Clark.
* Note. The config parameter is not currently used. This is a work in progress.
* Code needs to be written to set the config of the hardware serial control register in question.
*/
void WifiSerial::begin(uint32 baud, uint8_t config) {
//ASSERT(baud <= this->usart_device->max_baud); // Roger Clark. Assert doesn't do anything useful, we may as well save the space in flash and ram etc
if (baud > this->usart_device->max_baud) return;
const stm32_pin_info *txi = &PIN_MAP[this->tx_pin],
*rxi = &PIN_MAP[this->rx_pin];
disable_timer_if_necessary(txi->timer_device, txi->timer_channel);
usart_init(this->usart_device);
// Reinitialize the receive buffer, mks_esp8266 fixed data frame length is 1k bytes
rb_init(this->usart_device->rb, WIFI_RX_BUF_SIZE, wifiRxBuf);
usart_config_gpios_async(this->usart_device,
rxi->gpio_device, rxi->gpio_bit,
txi->gpio_device, txi->gpio_bit,
config);
usart_set_baud_rate(this->usart_device, USART_USE_PCLK, baud);
usart_enable_no_irq(this->usart_device, baud == WIFI_BAUDRATE);
}
void WifiSerial::end() {
usart_disable(this->usart_device);
}
int WifiSerial::available() {
return usart_data_available(this->usart_device);
}
//
// I/O
//
int WifiSerial::read() {
if (usart_data_available(usart_device) <= 0) return -1;
return usart_getc(usart_device);
}
int WifiSerial::write(unsigned char ch) {
usart_putc(this->usart_device, ch);
return 1;
}
int WifiSerial::wifi_rb_is_full() {
return rb_is_full(this->usart_device->rb);
}
#endif // MKS_WIFI_MODULE
#endif // HAS_TFT_LVGL_UI

2
Marlin/src/module/planner.cpp

@ -2684,7 +2684,7 @@ bool Planner::_populate_block(block_t * const block, bool split_move,
#ifndef TRAVEL_EXTRA_XYJERK
#define TRAVEL_EXTRA_XYJERK 0
#endif
const float extra_xyjerk = (de <= 0) ? TRAVEL_EXTRA_XYJERK : 0;
const float extra_xyjerk = TERN0(HAS_EXTRUDERS, de <= 0) ? TRAVEL_EXTRA_XYJERK : 0;
uint8_t limited = 0;
TERN(HAS_LINEAR_E_JERK, LOOP_LINEAR_AXES, LOOP_LOGICAL_AXES)(i) {

2
Marlin/src/module/probe.cpp

@ -287,7 +287,7 @@ FORCE_INLINE void probe_specific_action(const bool deploy) {
#if ENABLED(PAUSE_BEFORE_DEPLOY_STOW)
do {
#if ENABLED(PAUSE_PROBE_DEPLOY_WHEN_TRIGGERED)
if (deploy == PROBE_TRIGGERED()) break;
if (deploy != PROBE_TRIGGERED()) break;
#endif
BUZZ(100, 659);

68
Marlin/src/pins/stm32f4/pins_BTT_SKR_PRO_common.h

@ -231,27 +231,81 @@
//
// Temperature Sensors
// Use ADC pins without pullup for sensors that don't need a pullup.
//
#define TEMP_0_PIN PF4 // T1 <-> E0
#define TEMP_1_PIN PF5 // T2 <-> E1
#define TEMP_2_PIN PF6 // T3 <-> E2
#define TEMP_BED_PIN PF3 // T0 <-> Bed
#if TEMP_SENSOR_0_IS_AD8495 || TEMP_SENSOR_0 == 20
#define TEMP_0_PIN PF8
#else
#define TEMP_0_PIN PF4 // T1 <-> E0
#endif
#if TEMP_SENSOR_1_IS_AD8495 || TEMP_SENSOR_1 == 20
#define TEMP_1_PIN PF9
#else
#define TEMP_1_PIN PF5 // T2 <-> E1
#endif
#if TEMP_SENSOR_2_IS_AD8495 || TEMP_SENSOR_2 == 20
#define TEMP_2_PIN PF10
#else
#define TEMP_2_PIN PF6 // T3 <-> E2
#endif
#if TEMP_SENSOR_BED_IS_AD8495 || TEMP_SENSOR_BED == 20
#define TEMP_BED_PIN PF7
#else
#define TEMP_BED_PIN PF3 // T0 <-> Bed
#endif
#ifdef TEMP_SENSOR_PROBE && !defined(TEMP_PROBE_PIN)
#if TEMP_SENSOR_PROBE_IS_AD8495 || TEMP_SENSOR_PROBE == 20
#if HOTENDS == 2
#define TEMP_PROBE_PIN PF10
#elif HOTENDS < 2
#define TEMP_PROBE_PIN PF9
#endif
#else
#if HOTENDS == 2
#define TEMP_PROBE_PIN TEMP_2_PIN
#elif HOTENDS < 2
#define TEMP_PROBE_PIN TEMP_1_PIN
#endif
#endif
#endif
#if TEMP_SENSOR_CHAMBER && !defined(TEMP_CHAMBER_PIN)
#if TEMP_SENSOR_CHAMBER_IS_AD8495 || TEMP_SENSOR_CHAMBER == 20
#define TEMP_CHAMBER_PIN PF10
#else
#define TEMP_CHAMBER_PIN TEMP_2_PIN
#endif
#endif
//
// Heaters / Fans
// Heaters
//
#define HEATER_0_PIN PB1 // Heater0
#define HEATER_1_PIN PD14 // Heater1
#define HEATER_2_PIN PB0 // Heater1
#if TEMP_SENSOR_CHAMBER && HOTENDS < 3
#define HEATER_CHAMBER_PIN PB0 // Heater2
#else
#define HEATER_2_PIN PB0 // Heater2
#endif
#define HEATER_BED_PIN PD12 // Hotbed
//
// Fans
//
#define FAN_PIN PC8 // Fan0
#define FAN1_PIN PE5 // Fan1
#define FAN2_PIN PE6 // Fan2
#ifndef E0_AUTO_FAN_PIN
#define E0_AUTO_FAN_PIN FAN1_PIN
#endif
#if ENABLED(USE_CONTROLLER_FAN) && HOTENDS < 2
#define CONTROLLER_FAN_PIN PE6 // Fan2
#else
#define FAN2_PIN PE6 // Fan2
#endif
//
// Misc. Functions
//

6
Marlin/src/pins/stm32f4/pins_MKS_ROBIN_NANO_V3.h

@ -220,6 +220,12 @@
#define WIFI_RESET_PIN PE9 // MKS ESP WIFI RESET PIN
#endif
// MKS TEST
#if ENABLED(MKS_TEST)
#define MKS_TEST_POWER_LOSS_PIN PA13 // PW_DET
#define MKS_TEST_PS_ON_PIN PB2 // PW_OFF
#endif
//
// Onboard SD card
//

29
buildroot/share/PlatformIO/scripts/mks_encrypt.py

@ -1,29 +0,0 @@
#
# buildroot/share/PlatformIO/scripts/mks_encrypt.py
#
# Apply encryption and save as 'build.firmware' for these environments:
# - env:mks_robin
# - env:mks_robin_e3
# - env:flsun_hispeedv1
# - env:mks_robin_nano35
#
Import("env")
from SCons.Script import DefaultEnvironment
board = DefaultEnvironment().BoardConfig()
if 'encrypt' in board.get("build").keys():
import marlin
# Encrypt ${PROGNAME}.bin and save it with the name given in build.encrypt
def encrypt(source, target, env):
marlin.encrypt_mks(source, target, env, board.get("build.encrypt"))
marlin.add_post_action(encrypt);
else:
import sys
print("You need to define output file via board_build.encrypt = 'filename' parameter", file=sys.stderr)
env.Exit(1);

42
buildroot/share/PlatformIO/scripts/stm32_bootloader.py

@ -1,42 +0,0 @@
#
# stm32_bootloader.py
#
import os,sys,marlin
Import("env")
from SCons.Script import DefaultEnvironment
board = DefaultEnvironment().BoardConfig()
board_keys = board.get("build").keys()
#
# For build.offset define LD_FLASH_OFFSET, used by ldscript.ld
#
if 'offset' in board_keys:
LD_FLASH_OFFSET = board.get("build.offset")
marlin.relocate_vtab(LD_FLASH_OFFSET)
# Flash size
maximum_flash_size = int(board.get("upload.maximum_size") / 1024)
marlin.replace_define('STM32_FLASH_SIZE', maximum_flash_size)
# Get upload.maximum_ram_size (defined by /buildroot/share/PlatformIO/boards/VARIOUS.json)
maximum_ram_size = board.get("upload.maximum_ram_size")
for i, flag in enumerate(env["LINKFLAGS"]):
if "-Wl,--defsym=LD_FLASH_OFFSET" in flag:
env["LINKFLAGS"][i] = "-Wl,--defsym=LD_FLASH_OFFSET=" + LD_FLASH_OFFSET
if "-Wl,--defsym=LD_MAX_DATA_SIZE" in flag:
env["LINKFLAGS"][i] = "-Wl,--defsym=LD_MAX_DATA_SIZE=" + str(maximum_ram_size - 40)
#
# For build.rename simply rename the firmware file.
#
if 'rename' in board_keys:
def rename_target(source, target, env):
firmware = os.path.join(target[0].dir.path, board.get("build.rename"))
import shutil
shutil.copy(target[0].path, firmware)
marlin.add_post_action(rename_target)

1
buildroot/tests/mks_robin_nano35

@ -22,6 +22,7 @@ use_example_configs Mks/Robin
opt_set MOTHERBOARD BOARD_MKS_ROBIN_NANO_V2
opt_disable TFT_INTERFACE_FSMC
opt_enable TFT_INTERFACE_SPI MKS_WIFI_MODULE
opt_add MKS_TEST
exec_test $1 $2 "MKS Robin v2 nano Emulated DOGM SPI, MKS_WIFI_MODULE" "$3"
#

1
buildroot/tests/mks_robin_nano35_maple

@ -32,6 +32,7 @@ use_example_configs Mks/Robin
opt_set MOTHERBOARD BOARD_MKS_ROBIN_NANO_V2
opt_disable TFT_INTERFACE_FSMC TFT_COLOR_UI TOUCH_SCREEN TFT_RES_320x240 SERIAL_PORT_2
opt_enable TFT_INTERFACE_SPI TFT_LVGL_UI TFT_RES_480x320 MKS_WIFI_MODULE
opt_add MKS_TEST
exec_test $1 $2 "MKS Robin v2 nano LVGL SPI w/ WiFi" "$3"
#

Loading…
Cancel
Save