Fork of Smoothie to port to mbed non-LPC targets.
Fork of Smoothie by
Panel.cpp
00001 /* 00002 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl). 00003 Smoothie 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. 00004 Smoothie 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. 00005 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. 00006 */ 00007 00008 #include "libs/Kernel.h" 00009 #include "Panel.h" 00010 #include "PanelScreen.h" 00011 00012 #include "libs/nuts_bolts.h" 00013 #include "libs/utils.h" 00014 #include "Button.h" 00015 00016 #include "modules/utils/player/PlayerPublicAccess.h" 00017 #include "screens/CustomScreen.h" 00018 #include "screens/MainMenuScreen.h" 00019 00020 #include "panels/I2CLCD.h" 00021 #include "panels/VikiLCD.h" 00022 #include "panels/Smoothiepanel.h" 00023 #include "panels/ReprapDiscountGLCD.h" 00024 #include "panels/ST7565.h" 00025 #include "version.h" 00026 00027 #define panel_checksum CHECKSUM("panel") 00028 #define enable_checksum CHECKSUM("enable") 00029 #define lcd_checksum CHECKSUM("lcd") 00030 #define i2c_lcd_checksum CHECKSUM("i2c_lcd") 00031 #define viki_lcd_checksum CHECKSUM("viki_lcd") 00032 #define smoothiepanel_checksum CHECKSUM("smoothiepanel") 00033 #define panelolu2_checksum CHECKSUM("panelolu2") 00034 #define rrd_glcd_checksum CHECKSUM("reprap_discount_glcd") 00035 #define st7565_glcd_checksum CHECKSUM("st7565_glcd") 00036 00037 #define menu_offset_checksum CHECKSUM("menu_offset") 00038 #define encoder_resolution_checksum CHECKSUM("encoder_resolution") 00039 #define jog_x_feedrate_checksum CHECKSUM("alpha_jog_feedrate") 00040 #define jog_y_feedrate_checksum CHECKSUM("beta_jog_feedrate") 00041 #define jog_z_feedrate_checksum CHECKSUM("gamma_jog_feedrate") 00042 #define longpress_delay_checksum CHECKSUM("longpress_delay") 00043 00044 #define hotend_temp_checksum CHECKSUM("hotend_temperature") 00045 #define bed_temp_checksum CHECKSUM("bed_temperature") 00046 00047 Panel::Panel() 00048 { 00049 this->counter_changed = false; 00050 this->click_changed = false; 00051 this->refresh_flag = false; 00052 this->enter_menu_mode(); 00053 this->lcd = NULL; 00054 this->do_buttons = false; 00055 this->idle_time = 0; 00056 this->start_up = true; 00057 this->current_screen = NULL; 00058 strcpy(this->playing_file, "Playing file"); 00059 } 00060 00061 Panel::~Panel() 00062 { 00063 delete this->lcd; 00064 } 00065 00066 void Panel::on_module_loaded() 00067 { 00068 // Exit if this module is not enabled 00069 if ( !THEKERNEL->config->value( panel_checksum, enable_checksum )->by_default(false)->as_bool() ) { 00070 delete this; 00071 return; 00072 } 00073 00074 // Initialise the LCD, see which LCD to use 00075 if (this->lcd != NULL) delete this->lcd; 00076 int lcd_cksm = get_checksum(THEKERNEL->config->value(panel_checksum, lcd_checksum)->by_default("i2c")->as_string()); 00077 00078 // Note checksums are not const expressions when in debug mode, so don't use switch 00079 if (lcd_cksm == i2c_lcd_checksum) { 00080 this->lcd = new I2CLCD(); 00081 } else if (lcd_cksm == viki_lcd_checksum) { 00082 this->lcd = new VikiLCD(); 00083 this->lcd->set_variant(0); 00084 } else if (lcd_cksm == panelolu2_checksum) { 00085 this->lcd = new VikiLCD(); 00086 this->lcd->set_variant(1); 00087 } else if (lcd_cksm == smoothiepanel_checksum) { 00088 this->lcd = new Smoothiepanel(); 00089 } else if (lcd_cksm == rrd_glcd_checksum) { 00090 this->lcd = new ReprapDiscountGLCD(); 00091 } else if (lcd_cksm == st7565_glcd_checksum) { 00092 this->lcd = new ST7565(); 00093 } else { 00094 // no lcd type defined 00095 return; 00096 } 00097 00098 this->custom_screen= new CustomScreen(); // this needs to be called here as it needs the config cache loaded 00099 00100 // some panels may need access to this global info 00101 this->lcd->setPanel(this); 00102 00103 // the number of screen lines the panel supports 00104 this->screen_lines = this->lcd->get_screen_lines(); 00105 00106 // some encoders may need more clicks to move menu, this is a divisor and is in config as it is 00107 // an end user usability issue 00108 this->menu_offset = THEKERNEL->config->value( panel_checksum, menu_offset_checksum )->by_default(0)->as_number(); 00109 00110 // override default encoder resolution if needed 00111 this->encoder_click_resolution = THEKERNEL->config->value( panel_checksum, encoder_resolution_checksum )->by_default(this->lcd->getEncoderResolution())->as_number(); 00112 00113 // load jogging feedrates in mm/min 00114 jogging_speed_mm_min[0] = THEKERNEL->config->value( panel_checksum, jog_x_feedrate_checksum )->by_default(3000.0f)->as_number(); 00115 jogging_speed_mm_min[1] = THEKERNEL->config->value( panel_checksum, jog_y_feedrate_checksum )->by_default(3000.0f)->as_number(); 00116 jogging_speed_mm_min[2] = THEKERNEL->config->value( panel_checksum, jog_z_feedrate_checksum )->by_default(300.0f )->as_number(); 00117 00118 // load the default preset temeratures 00119 default_hotend_temperature = THEKERNEL->config->value( panel_checksum, hotend_temp_checksum )->by_default(185.0f )->as_number(); 00120 default_bed_temperature = THEKERNEL->config->value( panel_checksum, bed_temp_checksum )->by_default(60.0f )->as_number(); 00121 00122 00123 this->up_button.up_attach( this, &Panel::on_up ); 00124 this->down_button.up_attach( this, &Panel::on_down ); 00125 this->click_button.up_attach( this, &Panel::on_select ); 00126 this->back_button.up_attach( this, &Panel::on_back ); 00127 this->pause_button.up_attach( this, &Panel::on_pause ); 00128 00129 00130 //setting longpress_delay 00131 int longpress_delay = THEKERNEL->config->value( panel_checksum, longpress_delay_checksum )->by_default(0)->as_number(); 00132 this->up_button.set_longpress_delay(longpress_delay); 00133 this->down_button.set_longpress_delay(longpress_delay); 00134 // this->click_button.set_longpress_delay(longpress_delay); 00135 // this->back_button.set_longpress_delay(longpress_delay); 00136 // this->pause_button.set_longpress_delay(longpress_delay); 00137 00138 00139 THEKERNEL->slow_ticker->attach( 100, this, &Panel::button_tick ); 00140 THEKERNEL->slow_ticker->attach( 1000, this, &Panel::encoder_check ); 00141 00142 // Register for events 00143 this->register_for_event(ON_IDLE); 00144 this->register_for_event(ON_MAIN_LOOP); 00145 this->register_for_event(ON_GCODE_RECEIVED); 00146 00147 // Refresh timer 00148 THEKERNEL->slow_ticker->attach( 20, this, &Panel::refresh_tick ); 00149 } 00150 00151 // Enter a screen, we only care about it now 00152 void Panel::enter_screen(PanelScreen *screen) 00153 { 00154 screen->panel = this; 00155 this->current_screen = screen; 00156 this->reset_counter(); 00157 this->current_screen->on_enter(); 00158 } 00159 00160 // Reset the counter 00161 void Panel::reset_counter() 00162 { 00163 *this->counter = 0; 00164 this->counter_changed = false; 00165 } 00166 00167 // Indicate the idle loop we want to call the refresh hook in the current screen 00168 // called 20 times a second 00169 uint32_t Panel::refresh_tick(uint32_t dummy) 00170 { 00171 this->refresh_flag = true; 00172 this->idle_time++; 00173 return 0; 00174 } 00175 00176 // Encoder pins changed in interrupt 00177 uint32_t Panel::encoder_check(uint32_t dummy) 00178 { 00179 // TODO if encoder reads go through i2c like on smoothie panel this needs to be 00180 // optionally done in idle loop, however when reading encoder directly it needs to be done 00181 // frequently, smoothie panel will return an actual delta count so won't miss any if polled slowly 00182 // NOTE this code will not work if change is not -1,0,-1 anything greater (as in above case) will not work properly 00183 static int encoder_counter = 0; 00184 int change = lcd->readEncoderDelta(); 00185 encoder_counter += change; 00186 00187 if ( change != 0 && encoder_counter % this->encoder_click_resolution == 0 ) { 00188 this->counter_changed = true; 00189 (*this->counter) += change; 00190 this->idle_time = 0; 00191 } 00192 return 0; 00193 } 00194 00195 // Read and update each button 00196 uint32_t Panel::button_tick(uint32_t dummy) 00197 { 00198 this->do_buttons = true; 00199 return 0; 00200 } 00201 00202 void Panel::on_gcode_received(void *argument) 00203 { 00204 Gcode *gcode = static_cast<Gcode *>(argument); 00205 if ( gcode->has_m) { 00206 if ( gcode->m == 117 ) { // set LCD message 00207 this->message = get_arguments(gcode->command); 00208 if (this->message.size() > 20) this->message = this->message.substr(0, 20); 00209 gcode->mark_as_taken(); 00210 } 00211 } 00212 } 00213 00214 // on main loop, we can send gcodes or do anything that waits in this loop 00215 void Panel::on_main_loop(void *argument) 00216 { 00217 if (this->current_screen != NULL) { 00218 this->current_screen->on_main_loop(); 00219 this->lcd->on_main_loop(); 00220 } 00221 } 00222 00223 00224 #define ohw_logo_antipixel_width 80 00225 #define ohw_logo_antipixel_height 15 00226 static const uint8_t ohw_logo_antipixel_bits[] = { 00227 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 00228 0x00, 0x00, 0x00, 0x01, 0x80, 0x0C, 0x00, 0x33, 0x18, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x5E, 00229 0x80, 0x2D, 0x6B, 0x9B, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x18, 0xAB, 0xFF, 0xFF, 00230 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x7B, 0xB3, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x7F, 0x80, 0x33, 00231 0x78, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x81, 0xF3, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 00232 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xB3, 0x18, 0xDD, 0x98, 0xC5, 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xAD, 00233 0x6B, 0x5D, 0x6B, 0x5D, 0x80, 0x73, 0x80, 0x3F, 0xFC, 0x21, 0x1B, 0x55, 0x08, 0xC5, 0x80, 0xF3, 00234 0xC0, 0x3F, 0xFD, 0xAD, 0x5B, 0x49, 0x6A, 0xDD, 0x80, 0xE1, 0xC0, 0x3F, 0xFD, 0xAD, 0x68, 0xDD, 00235 0x6B, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 00236 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF 00237 }; 00238 00239 // On idle things, we don't want to do shit in interrupts 00240 // don't queue gcodes in this 00241 void Panel::on_idle(void *argument) 00242 { 00243 if (this->start_up) { 00244 this->lcd->init(); 00245 00246 Version v; 00247 string build(v.get_build()); 00248 string date(v.get_build_date()); 00249 this->lcd->clear(); 00250 this->lcd->setCursor(0, 0); this->lcd->printf("Welcome to Smoothie"); 00251 this->lcd->setCursor(0, 1); this->lcd->printf("%s", build.substr(0, 20).c_str()); 00252 this->lcd->setCursor(0, 2); this->lcd->printf("%s", date.substr(0, 20).c_str()); 00253 this->lcd->setCursor(0, 3); this->lcd->printf("Please wait...."); 00254 00255 if (this->lcd->hasGraphics()) { 00256 this->lcd->bltGlyph(24, 40, ohw_logo_antipixel_width, ohw_logo_antipixel_height, ohw_logo_antipixel_bits); 00257 } 00258 00259 this->lcd->on_refresh(true); // tell lcd to display now 00260 00261 // Default top screen 00262 this->top_screen= new MainMenuScreen(); 00263 this->top_screen->set_panel(this); 00264 this->custom_screen->set_parent(this->top_screen); 00265 this->start_up = false; 00266 return; 00267 } 00268 00269 MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen); 00270 // after being idle for a while switch to Watch screen 00271 if (this->current_screen != NULL && this->idle_time > this->current_screen->idle_timeout_secs()*20) { 00272 this->idle_time = 0; 00273 if (mms->watch_screen != this->current_screen) { 00274 this->enter_screen(mms->watch_screen); 00275 // TODO do we need to reset any state? 00276 } 00277 00278 return; 00279 } 00280 00281 if(current_screen == NULL && this->idle_time > 20*4) { 00282 this->enter_screen(mms->watch_screen); 00283 return; 00284 } 00285 00286 if (this->do_buttons) { 00287 // we don't want to do I2C in interrupt mode 00288 this->do_buttons = false; 00289 00290 // read the actual buttons 00291 int but = lcd->readButtons(); 00292 if (but != 0) { 00293 this->idle_time = 0; 00294 if(current_screen == NULL) { 00295 // we were in startup screen so go to watch screen 00296 this->enter_screen(mms->watch_screen); 00297 return; 00298 } 00299 } 00300 00301 // fire events if the buttons are active and debounce is satisfied 00302 this->up_button.check_signal(but & BUTTON_UP); 00303 this->down_button.check_signal(but & BUTTON_DOWN); 00304 this->back_button.check_signal(but & BUTTON_LEFT); 00305 this->click_button.check_signal(but & BUTTON_SELECT); 00306 this->pause_button.check_signal(but & BUTTON_PAUSE); 00307 } 00308 00309 // If we are in menu mode and the position has changed 00310 if ( this->mode == MENU_MODE && this->counter_change() ) { 00311 this->menu_update(); 00312 } 00313 00314 // If we are in control mode 00315 if ( this->mode == CONTROL_MODE && this->counter_change() ) { 00316 this->control_value_update(); 00317 } 00318 00319 // If we must refresh 00320 if ( this->refresh_flag ) { 00321 this->refresh_flag = false; 00322 if (this->current_screen != NULL) { 00323 this->current_screen->on_refresh(); 00324 this->lcd->on_refresh(); 00325 } 00326 } 00327 } 00328 00329 // Hooks for button clicks 00330 uint32_t Panel::on_up(uint32_t dummy) 00331 { 00332 // this is simulating encoder clicks, but needs to be inverted to 00333 // increment values on up,increment by 00334 int inc = (this->mode == CONTROL_MODE) ? 1 : -(this->menu_offset+1); 00335 *this->counter += inc; 00336 this->counter_changed = true; 00337 return 0; 00338 } 00339 00340 uint32_t Panel::on_down(uint32_t dummy) 00341 { 00342 int inc = (this->mode == CONTROL_MODE) ? -1 : (this->menu_offset+1); 00343 *this->counter += inc; 00344 this->counter_changed = true; 00345 return 0; 00346 } 00347 00348 // on most menu screens will go back to previous higher menu 00349 uint32_t Panel::on_back(uint32_t dummy) 00350 { 00351 if (this->mode == MENU_MODE && this->current_screen != NULL && this->current_screen->parent != NULL) { 00352 this->enter_screen(this->current_screen->parent); 00353 } 00354 return 0; 00355 } 00356 00357 uint32_t Panel::on_select(uint32_t dummy) 00358 { 00359 // TODO make configurable, including turning off 00360 // buzz is ignored on panels that do not support buzz 00361 this->click_changed = true; 00362 this->idle_time = 0; 00363 lcd->buzz(60, 300); // 50ms 300Hz 00364 return 0; 00365 } 00366 00367 uint32_t Panel::on_pause(uint32_t dummy) 00368 { 00369 if (!paused) { 00370 THEKERNEL->pauser->take(); 00371 paused = true; 00372 } else { 00373 THEKERNEL->pauser->release(); 00374 paused = false; 00375 } 00376 return 0; 00377 } 00378 00379 bool Panel::counter_change() 00380 { 00381 if ( this->counter_changed ) { 00382 this->counter_changed = false; 00383 return true; 00384 } else { 00385 return false; 00386 } 00387 } 00388 bool Panel::click() 00389 { 00390 if ( this->click_changed ) { 00391 this->click_changed = false; 00392 return true; 00393 } else { 00394 return false; 00395 } 00396 } 00397 00398 00399 // Enter menu mode 00400 void Panel::enter_menu_mode() 00401 { 00402 this->mode = MENU_MODE; 00403 this->counter = &this->menu_selected_line; 00404 this->menu_changed = false; 00405 } 00406 00407 void Panel::setup_menu(uint16_t rows) 00408 { 00409 this->setup_menu(rows, min(rows, this->max_screen_lines())); 00410 } 00411 00412 void Panel::setup_menu(uint16_t rows, uint16_t lines) 00413 { 00414 this->menu_selected_line = 0; 00415 this->menu_current_line = 0; 00416 this->menu_start_line = 0; 00417 this->menu_rows = rows; 00418 this->panel_lines = lines; 00419 } 00420 00421 void Panel::menu_update() 00422 { 00423 // Limits, up and down 00424 // NOTE menu_selected_line is changed in an interrupt and can change at any time 00425 int msl = this->menu_selected_line; // hopefully this is atomic 00426 00427 #if 0 00428 // this allows it to wrap but with new method we dont; want to wrap 00429 msl = msl % ( this->menu_rows << this->menu_offset ); 00430 while ( msl < 0 ) { 00431 msl += this->menu_rows << this->menu_offset; 00432 } 00433 #else 00434 // limit selected line to screen lines available 00435 if(msl >= this->menu_rows<<this->menu_offset){ 00436 msl= (this->menu_rows-1)<<this->menu_offset; 00437 }else if(msl < 0) msl= 0; 00438 #endif 00439 00440 this->menu_selected_line = msl; // update atomically we hope 00441 // figure out which actual line to select, if we have a menu offset it means we want to move one line per two clicks 00442 if(msl % (this->menu_offset+1) == 0) { // only if divisible by offset 00443 this->menu_current_line = msl >> this->menu_offset; 00444 } 00445 00446 // What to display 00447 if ( this->menu_rows > this->panel_lines ) { 00448 #if 0 00449 // old way of scrolling not nice.... 00450 if ( this->menu_current_line >= 2 ) { 00451 this->menu_start_line = this->menu_current_line - 1; 00452 } 00453 if ( this->menu_current_line > this->menu_rows - this->panel_lines ) { 00454 this->menu_start_line = this->menu_rows - this->panel_lines; 00455 } 00456 #else 00457 // new way we only scroll the lines when the cursor hits the bottom of the screen or the top of the screen 00458 // do we want to scroll up? 00459 int sl= this->menu_current_line - this->menu_start_line; // screen line we are on 00460 if(sl >= this->panel_lines) { 00461 this->menu_start_line += ((sl+1)-this->panel_lines); // scroll up to keep it on the screen 00462 00463 }else if(sl < 0 ) { // do we want to scroll down? 00464 this->menu_start_line += sl; // scroll down 00465 } 00466 #endif 00467 00468 }else{ 00469 this->menu_start_line = 0; 00470 } 00471 00472 this->menu_changed = true; 00473 } 00474 00475 bool Panel::menu_change() 00476 { 00477 if ( this->menu_changed ) { 00478 this->menu_changed = false; 00479 return true; 00480 } else { 00481 return false; 00482 } 00483 } 00484 00485 bool Panel::control_value_change() 00486 { 00487 if ( this->control_value_changed ) { 00488 this->control_value_changed = false; 00489 return true; 00490 } else { 00491 return false; 00492 } 00493 } 00494 00495 bool Panel::enter_control_mode(float passed_normal_increment, float passed_pressed_increment) 00496 { 00497 this->mode = CONTROL_MODE; 00498 this->normal_increment = passed_normal_increment; 00499 this->counter = &this->control_normal_counter; 00500 this->control_normal_counter = 0; 00501 this->control_base_value = 0; 00502 return true; 00503 } 00504 00505 void Panel::control_value_update() 00506 { 00507 // TODO what do we do here? 00508 this->control_value_changed = true; 00509 } 00510 00511 void Panel::set_control_value(float value) 00512 { 00513 this->control_base_value = value; 00514 } 00515 00516 float Panel::get_control_value() 00517 { 00518 return this->control_base_value + (this->control_normal_counter * this->normal_increment); 00519 } 00520 00521 bool Panel::is_playing() const 00522 { 00523 void *returned_data; 00524 00525 bool ok = THEKERNEL->public_data->get_value( player_checksum, is_playing_checksum, &returned_data ); 00526 if (ok) { 00527 bool b = *static_cast<bool *>(returned_data); 00528 return b; 00529 } 00530 return false; 00531 } 00532 00533 void Panel::set_playing_file(string f) 00534 { 00535 // just copy the first 20 characters after the first / if there 00536 size_t n = f.find_last_of('/'); 00537 if (n == string::npos) n = 0; 00538 strncpy(playing_file, f.substr(n + 1, 19).c_str(), sizeof(playing_file)); 00539 playing_file[sizeof(playing_file) - 1] = 0; 00540 }
Generated on Tue Jul 12 2022 20:09:02 by 1.7.2