add suspended status to panel
[clinton/Smoothieware.git] / src / modules / utils / panel / Panel.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 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.
4 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.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "libs/Kernel.h"
9 #include "Panel.h"
10 #include "PanelScreen.h"
11
12 #include "libs/nuts_bolts.h"
13 #include "libs/utils.h"
14 #include "Button.h"
15 #include "libs/USBDevice/USBMSD/SDCard.h"
16 #include "libs/SDFAT.h"
17
18 #include "modules/utils/player/PlayerPublicAccess.h"
19 #include "screens/CustomScreen.h"
20 #include "screens/MainMenuScreen.h"
21 #include "SlowTicker.h"
22 #include "Gcode.h"
23 #include "Pauser.h"
24 #include "TemperatureControlPublicAccess.h"
25 #include "ModifyValuesScreen.h"
26 #include "PublicDataRequest.h"
27 #include "PublicData.h"
28 #include "StreamOutputPool.h"
29 #include "platform_memory.h"
30
31 #include "panels/ReprapDiscountGLCD.h"
32 #include "panels/ST7565.h"
33 #include "panels/UniversalAdapter.h"
34
35 #include "version.h"
36 #include "checksumm.h"
37 #include "ConfigValue.h"
38 #include "Config.h"
39 #include "TemperatureControlPool.h"
40
41 // for parse_pins in mbed
42 #include "pinmap.h"
43
44 #define panel_checksum CHECKSUM("panel")
45 #define enable_checksum CHECKSUM("enable")
46 #define lcd_checksum CHECKSUM("lcd")
47 #define rrd_glcd_checksum CHECKSUM("reprap_discount_glcd")
48 #define st7565_glcd_checksum CHECKSUM("st7565_glcd")
49 #define viki2_checksum CHECKSUM("viki2")
50 #define mini_viki2_checksum CHECKSUM("mini_viki2")
51 #define universal_adapter_checksum CHECKSUM("universal_adapter")
52
53 #define menu_offset_checksum CHECKSUM("menu_offset")
54 #define encoder_resolution_checksum CHECKSUM("encoder_resolution")
55 #define jog_x_feedrate_checksum CHECKSUM("alpha_jog_feedrate")
56 #define jog_y_feedrate_checksum CHECKSUM("beta_jog_feedrate")
57 #define jog_z_feedrate_checksum CHECKSUM("gamma_jog_feedrate")
58 #define longpress_delay_checksum CHECKSUM("longpress_delay")
59
60 #define ext_sd_checksum CHECKSUM("external_sd")
61 #define sdcd_pin_checksum CHECKSUM("sdcd_pin")
62 #define spi_channel_checksum CHECKSUM("spi_channel")
63 #define spi_cs_pin_checksum CHECKSUM("spi_cs_pin")
64
65 #define hotend_temp_checksum CHECKSUM("hotend_temperature")
66 #define bed_temp_checksum CHECKSUM("bed_temperature")
67
68 Panel* Panel::instance= nullptr;
69
70 Panel::Panel()
71 {
72 instance= this;
73 this->counter_changed = false;
74 this->click_changed = false;
75 this->refresh_flag = false;
76 this->enter_menu_mode();
77 this->lcd = NULL;
78 this->do_buttons = false;
79 this->do_encoder = false;
80 this->idle_time = 0;
81 this->start_up = true;
82 this->current_screen = NULL;
83 this->sd= nullptr;
84 this->extmounter= nullptr;
85 this->external_sd_enable= false;
86 this->halted= false;
87 strcpy(this->playing_file, "Playing file");
88 }
89
90 Panel::~Panel()
91 {
92 delete this->lcd;
93 delete this->extmounter;
94 delete this->sd;
95 }
96
97 void Panel::on_module_loaded()
98 {
99 // Exit if this module is not enabled
100 if ( !THEKERNEL->config->value( panel_checksum, enable_checksum )->by_default(false)->as_bool() ) {
101 delete this;
102 return;
103 }
104
105 // Initialise the LCD, see which LCD to use
106 if (this->lcd != NULL) delete this->lcd;
107 int lcd_cksm = get_checksum(THEKERNEL->config->value(panel_checksum, lcd_checksum)->by_default("reprap_discount_glcd")->as_string());
108
109 // Note checksums are not const expressions when in debug mode, so don't use switch
110 if (lcd_cksm == rrd_glcd_checksum) {
111 this->lcd = new ReprapDiscountGLCD();
112 } else if (lcd_cksm == st7565_glcd_checksum) {
113 this->lcd = new ST7565();
114 } else if (lcd_cksm == viki2_checksum) {
115 this->lcd = new ST7565(1); // variant 1
116 } else if (lcd_cksm == mini_viki2_checksum) {
117 this->lcd = new ST7565(2); // variant 2
118 } else if (lcd_cksm == universal_adapter_checksum) {
119 this->lcd = new UniversalAdapter();
120 } else {
121 // no known lcd type defined
122 delete this;
123 return;
124 }
125
126 // external sd
127 if(THEKERNEL->config->value( panel_checksum, ext_sd_checksum )->by_default(false)->as_bool()) {
128 this->external_sd_enable= true;
129 // external sdcard detect
130 this->sdcd_pin.from_string(THEKERNEL->config->value( panel_checksum, ext_sd_checksum, sdcd_pin_checksum )->by_default("nc")->as_string())->as_input();
131 this->extsd_spi_channel = THEKERNEL->config->value(panel_checksum, ext_sd_checksum, spi_channel_checksum)->by_default(0)->as_number();
132 string s= THEKERNEL->config->value( panel_checksum, ext_sd_checksum, spi_cs_pin_checksum)->by_default("2.8")->as_string();
133 s= "P" + s; // Pinnames need to be Px_x
134 this->extsd_spi_cs= parse_pins(s.c_str());
135 this->register_for_event(ON_SECOND_TICK);
136 }
137
138 // these need to be called here as they need the config cache loaded as they enumerate modules
139 this->custom_screen= new CustomScreen();
140
141 // some panels may need access to this global info
142 this->lcd->setPanel(this);
143
144 // the number of screen lines the panel supports
145 this->screen_lines = this->lcd->get_screen_lines();
146
147 // some encoders may need more clicks to move menu, this is a divisor and is in config as it is
148 // an end user usability issue
149 this->menu_offset = THEKERNEL->config->value( panel_checksum, menu_offset_checksum )->by_default(0)->as_number();
150
151 // override default encoder resolution if needed
152 this->encoder_click_resolution = THEKERNEL->config->value( panel_checksum, encoder_resolution_checksum )->by_default(this->lcd->getEncoderResolution())->as_number();
153
154 // load jogging feedrates in mm/min
155 jogging_speed_mm_min[0] = THEKERNEL->config->value( panel_checksum, jog_x_feedrate_checksum )->by_default(3000.0f)->as_number();
156 jogging_speed_mm_min[1] = THEKERNEL->config->value( panel_checksum, jog_y_feedrate_checksum )->by_default(3000.0f)->as_number();
157 jogging_speed_mm_min[2] = THEKERNEL->config->value( panel_checksum, jog_z_feedrate_checksum )->by_default(300.0f )->as_number();
158
159 // load the default preset temeratures
160 default_hotend_temperature = THEKERNEL->config->value( panel_checksum, hotend_temp_checksum )->by_default(185.0f )->as_number();
161 default_bed_temperature = THEKERNEL->config->value( panel_checksum, bed_temp_checksum )->by_default(60.0f )->as_number();
162
163
164 this->up_button.up_attach( this, &Panel::on_up );
165 this->down_button.up_attach( this, &Panel::on_down );
166 this->click_button.up_attach( this, &Panel::on_select );
167 this->back_button.up_attach( this, &Panel::on_back );
168 this->pause_button.up_attach( this, &Panel::on_pause );
169
170
171 //setting longpress_delay
172 int longpress_delay = THEKERNEL->config->value( panel_checksum, longpress_delay_checksum )->by_default(0)->as_number();
173 this->up_button.set_longpress_delay(longpress_delay);
174 this->down_button.set_longpress_delay(longpress_delay);
175 // this->click_button.set_longpress_delay(longpress_delay);
176 // this->back_button.set_longpress_delay(longpress_delay);
177 // this->pause_button.set_longpress_delay(longpress_delay);
178
179
180 THEKERNEL->slow_ticker->attach( 50, this, &Panel::button_tick );
181 if(lcd->encoderReturnsDelta()) {
182 // panel handles encoder pins and returns a delta
183 THEKERNEL->slow_ticker->attach( 10, this, &Panel::encoder_tick );
184 }else{
185 // read encoder pins
186 THEKERNEL->slow_ticker->attach( 1000, this, &Panel::encoder_check );
187 }
188
189 // Register for events
190 this->register_for_event(ON_IDLE);
191 this->register_for_event(ON_MAIN_LOOP);
192 this->register_for_event(ON_GCODE_RECEIVED);
193 this->register_for_event(ON_HALT);
194
195 // Refresh timer
196 THEKERNEL->slow_ticker->attach( 20, this, &Panel::refresh_tick );
197 }
198
199 // Enter a screen, we only care about it now
200 void Panel::enter_screen(PanelScreen *screen)
201 {
202 if(this->current_screen != nullptr)
203 this->current_screen->on_exit();
204
205 this->current_screen = screen;
206 this->reset_counter();
207 this->current_screen->on_enter();
208 }
209
210 // Reset the counter
211 void Panel::reset_counter()
212 {
213 *this->counter = 0;
214 this->counter_changed = false;
215 }
216
217 // Indicate the idle loop we want to call the refresh hook in the current screen
218 // called 20 times a second
219 uint32_t Panel::refresh_tick(uint32_t dummy)
220 {
221 this->refresh_flag = true;
222 this->idle_time++;
223 return 0;
224 }
225
226 // Encoder pins changed in interrupt or call from on_idle
227 uint32_t Panel::encoder_check(uint32_t dummy)
228 {
229 // if encoder reads go through SPI like on universal panel adapter this needs to be
230 // done in idle loop, this is indicated by lcd->encoderReturnsDelta()
231 // however when reading encoder directly it needs to be done
232 // frequently, universal panel adapter will return an actual delta count so won't miss any if polled slowly
233 // NOTE FIXME (WHY is it in menu only?) this code will not work if change is not 1,0,-1 anything greater (as in above case) will not work properly
234 static int encoder_counter = 0; // keeps track of absolute encoder position
235 static int last_encoder_click= 0; // last readfing of divided encoder count
236 int change = lcd->readEncoderDelta();
237 encoder_counter += change;
238 int clicks= encoder_counter/this->encoder_click_resolution;
239 int delta= clicks - last_encoder_click; // the number of clicks this time
240 last_encoder_click= clicks;
241
242 if ( delta != 0 ) {
243 this->counter_changed = true;
244 (*this->counter) += delta;
245 this->idle_time = 0;
246 }
247 return 0;
248 }
249
250 // Read and update each button
251 uint32_t Panel::button_tick(uint32_t dummy)
252 {
253 this->do_buttons = true;
254 return 0;
255 }
256
257 // Read and update encoder
258 uint32_t Panel::encoder_tick(uint32_t dummy)
259 {
260 this->do_encoder = true;
261 return 0;
262 }
263
264 void Panel::on_gcode_received(void *argument)
265 {
266 Gcode *gcode = static_cast<Gcode *>(argument);
267 if ( gcode->has_m) {
268 if ( gcode->m == 117 ) { // set LCD message
269 this->message = get_arguments(gcode->get_command());
270 if (this->message.size() > 20) this->message = this->message.substr(0, 20);
271 gcode->mark_as_taken();
272 }
273 }
274 }
275
276 // on main loop, we can send gcodes or do anything that waits in this loop
277 void Panel::on_main_loop(void *argument)
278 {
279 if (this->current_screen != NULL) {
280 this->current_screen->on_main_loop();
281 this->lcd->on_main_loop();
282 }
283 }
284
285
286 #define ohw_logo_antipixel_width 80
287 #define ohw_logo_antipixel_height 15
288 static const uint8_t ohw_logo_antipixel_bits[] = {
289 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
290 0x00, 0x00, 0x00, 0x01, 0x80, 0x0C, 0x00, 0x33, 0x18, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x5E,
291 0x80, 0x2D, 0x6B, 0x9B, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x18, 0xAB, 0xFF, 0xFF,
292 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x7B, 0xB3, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x7F, 0x80, 0x33,
293 0x78, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x81, 0xF3, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD,
294 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xB3, 0x18, 0xDD, 0x98, 0xC5, 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xAD,
295 0x6B, 0x5D, 0x6B, 0x5D, 0x80, 0x73, 0x80, 0x3F, 0xFC, 0x21, 0x1B, 0x55, 0x08, 0xC5, 0x80, 0xF3,
296 0xC0, 0x3F, 0xFD, 0xAD, 0x5B, 0x49, 0x6A, 0xDD, 0x80, 0xE1, 0xC0, 0x3F, 0xFD, 0xAD, 0x68, 0xDD,
297 0x6B, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
298 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
299 };
300
301 // On idle things, we don't want to do shit in interrupts
302 // don't queue gcodes in this
303 void Panel::on_idle(void *argument)
304 {
305 if (this->start_up) {
306 this->lcd->init();
307
308 Version v;
309 string build(v.get_build());
310 string date(v.get_build_date());
311 this->lcd->clear();
312 this->lcd->setCursor(0, 0); this->lcd->printf("Welcome to Smoothie");
313 this->lcd->setCursor(0, 1); this->lcd->printf("%s", build.substr(0, 20).c_str());
314 this->lcd->setCursor(0, 2); this->lcd->printf("%s", date.substr(0, 20).c_str());
315 this->lcd->setCursor(0, 3); this->lcd->printf("Please wait....");
316
317 if (this->lcd->hasGraphics()) {
318 this->lcd->bltGlyph(24, 40, ohw_logo_antipixel_width, ohw_logo_antipixel_height, ohw_logo_antipixel_bits);
319 }
320
321 this->lcd->on_refresh(true); // tell lcd to display now
322
323 // Default top screen
324 this->top_screen= new MainMenuScreen();
325 this->custom_screen->set_parent(this->top_screen);
326 this->start_up = false;
327 return;
328 }
329
330 MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen);
331 // after being idle for a while switch to Watch screen
332 if (this->current_screen != NULL && this->idle_time > this->current_screen->idle_timeout_secs()*20) {
333 this->idle_time = 0;
334 if (mms->watch_screen != this->current_screen) {
335 this->enter_screen(mms->watch_screen);
336 // TODO do we need to reset any state?
337 }
338
339 return;
340 }
341
342 if(current_screen == NULL && this->idle_time > 20*4) {
343 this->enter_screen(mms->watch_screen);
344 return;
345 }
346
347 if(this->do_encoder) {
348 this->do_encoder= false;
349 encoder_check(0);
350 }
351
352 if (this->do_buttons) {
353 // we don't want to do SPI in interrupt mode
354 this->do_buttons = false;
355
356 // read the actual buttons
357 int but = lcd->readButtons();
358 if (but != 0) {
359 this->idle_time = 0;
360 if(current_screen == NULL) {
361 // we were in startup screen so go to watch screen
362 this->enter_screen(mms->watch_screen);
363 return;
364 }
365 }
366
367 // fire events if the buttons are active and debounce is satisfied
368 this->up_button.check_signal(but & BUTTON_UP);
369 this->down_button.check_signal(but & BUTTON_DOWN);
370 this->back_button.check_signal(but & BUTTON_LEFT);
371 this->click_button.check_signal(but & BUTTON_SELECT);
372 this->pause_button.check_signal(but & BUTTON_PAUSE);
373 }
374
375 // If we are in menu mode and the position has changed
376 if ( this->mode == MENU_MODE && this->counter_change() ) {
377 this->menu_update();
378 }
379
380 // If we are in control mode
381 if ( this->mode == CONTROL_MODE && this->counter_change() ) {
382 this->control_value_update();
383 }
384
385 // If we must refresh
386 if ( this->refresh_flag ) {
387 this->refresh_flag = false;
388 if (this->current_screen != NULL) {
389 this->current_screen->on_refresh();
390 this->lcd->on_refresh();
391 }
392 }
393 }
394
395 // Hooks for button clicks
396 uint32_t Panel::on_up(uint32_t dummy)
397 {
398 // this is simulating encoder clicks, but needs to be inverted to
399 // increment values on up,increment by
400 int inc = (this->mode == CONTROL_MODE) ? 1 : -(this->menu_offset+1);
401 *this->counter += inc;
402 this->counter_changed = true;
403 return 0;
404 }
405
406 uint32_t Panel::on_down(uint32_t dummy)
407 {
408 int inc = (this->mode == CONTROL_MODE) ? -1 : (this->menu_offset+1);
409 *this->counter += inc;
410 this->counter_changed = true;
411 return 0;
412 }
413
414 // on most menu screens will go back to previous higher menu
415 uint32_t Panel::on_back(uint32_t dummy)
416 {
417 if (this->mode == MENU_MODE && this->current_screen != NULL && this->current_screen->parent != NULL) {
418 this->enter_screen(this->current_screen->parent);
419 }
420 return 0;
421 }
422
423 uint32_t Panel::on_select(uint32_t dummy)
424 {
425 // TODO make configurable, including turning off
426 // buzz is ignored on panels that do not support buzz
427 this->click_changed = true;
428 this->idle_time = 0;
429 lcd->buzz(60, 300); // 50ms 300Hz
430 return 0;
431 }
432
433 uint32_t Panel::on_pause(uint32_t dummy)
434 {
435 if (!THEKERNEL->pauser->paused()) {
436 THEKERNEL->pauser->take();
437 } else {
438 THEKERNEL->pauser->release();
439 }
440 return 0;
441 }
442
443 bool Panel::counter_change()
444 {
445 if ( this->counter_changed ) {
446 this->counter_changed = false;
447 return true;
448 } else {
449 return false;
450 }
451 }
452 bool Panel::click()
453 {
454 if ( this->click_changed ) {
455 this->click_changed = false;
456 return true;
457 } else {
458 return false;
459 }
460 }
461
462
463 // Enter menu mode
464 void Panel::enter_menu_mode(bool force)
465 {
466 this->mode = MENU_MODE;
467 this->counter = &this->menu_selected_line;
468 this->menu_changed = force;
469 }
470
471 void Panel::setup_menu(uint16_t rows)
472 {
473 this->setup_menu(rows, min(rows, this->max_screen_lines()));
474 }
475
476 void Panel::setup_menu(uint16_t rows, uint16_t lines)
477 {
478 this->menu_selected_line = 0;
479 this->menu_current_line = 0;
480 this->menu_start_line = 0;
481 this->menu_rows = rows;
482 this->panel_lines = lines;
483 }
484
485 void Panel::menu_update()
486 {
487 // Limits, up and down
488 // NOTE menu_selected_line is changed in an interrupt and can change at any time
489 int msl = this->menu_selected_line; // hopefully this is atomic
490
491 #if 0
492 // this allows it to wrap but with new method we dont; want to wrap
493 msl = msl % ( this->menu_rows << this->menu_offset );
494 while ( msl < 0 ) {
495 msl += this->menu_rows << this->menu_offset;
496 }
497 #else
498 // limit selected line to screen lines available
499 if(msl >= this->menu_rows<<this->menu_offset){
500 msl= (this->menu_rows-1)<<this->menu_offset;
501 }else if(msl < 0) msl= 0;
502 #endif
503
504 this->menu_selected_line = msl; // update atomically we hope
505 // figure out which actual line to select, if we have a menu offset it means we want to move one line per two clicks
506 if(msl % (this->menu_offset+1) == 0) { // only if divisible by offset
507 this->menu_current_line = msl >> this->menu_offset;
508 }
509
510 // What to display
511 if ( this->menu_rows > this->panel_lines ) {
512 #if 0
513 // old way of scrolling not nice....
514 if ( this->menu_current_line >= 2 ) {
515 this->menu_start_line = this->menu_current_line - 1;
516 }
517 if ( this->menu_current_line > this->menu_rows - this->panel_lines ) {
518 this->menu_start_line = this->menu_rows - this->panel_lines;
519 }
520 #else
521 // new way we only scroll the lines when the cursor hits the bottom of the screen or the top of the screen
522 // do we want to scroll up?
523 int sl= this->menu_current_line - this->menu_start_line; // screen line we are on
524 if(sl >= this->panel_lines) {
525 this->menu_start_line += ((sl+1)-this->panel_lines); // scroll up to keep it on the screen
526
527 }else if(sl < 0 ) { // do we want to scroll down?
528 this->menu_start_line += sl; // scroll down
529 }
530 #endif
531
532 }else{
533 this->menu_start_line = 0;
534 }
535
536 this->menu_changed = true;
537 }
538
539 bool Panel::menu_change()
540 {
541 if ( this->menu_changed ) {
542 this->menu_changed = false;
543 return true;
544 } else {
545 return false;
546 }
547 }
548
549 bool Panel::control_value_change()
550 {
551 if ( this->control_value_changed ) {
552 this->control_value_changed = false;
553 return true;
554 } else {
555 return false;
556 }
557 }
558
559 bool Panel::enter_control_mode(float passed_normal_increment, float passed_pressed_increment)
560 {
561 this->mode = CONTROL_MODE;
562 this->normal_increment = passed_normal_increment;
563 this->counter = &this->control_normal_counter;
564 this->control_normal_counter = 0;
565 this->control_base_value = 0;
566 return true;
567 }
568
569 void Panel::control_value_update()
570 {
571 // TODO what do we do here?
572 this->control_value_changed = true;
573 }
574
575 void Panel::set_control_value(float value)
576 {
577 this->control_base_value = value;
578 }
579
580 float Panel::get_control_value()
581 {
582 return this->control_base_value + (this->control_normal_counter * this->normal_increment);
583 }
584
585 bool Panel::is_playing() const
586 {
587 void *returned_data;
588
589 bool ok = PublicData::get_value( player_checksum, is_playing_checksum, &returned_data );
590 if (ok) {
591 bool b = *static_cast<bool *>(returned_data);
592 return b;
593 }
594 return false;
595 }
596
597 bool Panel::is_suspended() const
598 {
599 void *returned_data;
600
601 bool ok = PublicData::get_value( player_checksum, is_suspended_checksum, &returned_data );
602 if (ok) {
603 bool b = *static_cast<bool *>(returned_data);
604 return b;
605 }
606 return false;
607 }
608
609 void Panel::set_playing_file(string f)
610 {
611 // just copy the first 20 characters after the first / if there
612 size_t n = f.find_last_of('/');
613 if (n == string::npos) n = 0;
614 strncpy(playing_file, f.substr(n + 1, 19).c_str(), sizeof(playing_file));
615 playing_file[sizeof(playing_file) - 1] = 0;
616 }
617
618 bool Panel::mount_external_sd(bool on)
619 {
620 // now setup the external sdcard if we have one and mount it
621 if(on) {
622 if(this->sd == nullptr) {
623 PinName mosi, miso, sclk, cs= this->extsd_spi_cs;
624 if(extsd_spi_channel == 0) {
625 mosi = P0_18; miso = P0_17; sclk = P0_15;
626 } else if(extsd_spi_channel == 1) {
627 mosi = P0_9; miso = P0_8; sclk = P0_7;
628 } else{
629 this->external_sd_enable= false;
630 THEKERNEL->streams->printf("Bad SPI channel for external SDCard\n");
631 return false;
632 }
633 size_t n= sizeof(SDCard);
634 void *v = AHB0.alloc(n);
635 memset(v, 0, n); // clear the allocated memory
636 this->sd= new(v) SDCard(mosi, miso, sclk, cs); // allocate object using zeroed memory
637 }
638 delete this->extmounter; // if it was not unmounted before
639 size_t n= sizeof(SDFAT);
640 void *v = AHB0.alloc(n);
641 memset(v, 0, n); // clear the allocated memory
642 this->extmounter= new(v) SDFAT("ext", this->sd); // use cleared allocated memory
643 this->sd->disk_initialize(); // first one seems to fail, but works next time
644 THEKERNEL->streams->printf("External SDcard mounted as /ext\n");
645 }else{
646 delete this->extmounter;
647 this->extmounter= nullptr;
648 THEKERNEL->streams->printf("External SDcard unmounted\n");
649 }
650 return true;
651 }
652
653 void Panel::on_second_tick(void *arg)
654 {
655 if(!this->external_sd_enable || this->start_up) return;
656
657 // sd insert detect, mount sdcard if inserted, unmount if removed
658 if(this->sdcd_pin.connected()) {
659 if(this->extmounter == nullptr && this->sdcd_pin.get()) {
660 mount_external_sd(true);
661 // go to the play screen and the /ext directory
662 // TODO we don't want to do this if we just booted and card was already in
663 THEKERNEL->current_path= "/ext";
664 MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen);
665 THEPANEL->enter_screen(mms->file_screen);
666
667 }else if(this->extmounter != nullptr && !this->sdcd_pin.get()){
668 mount_external_sd(false);
669 }
670 }else{
671 // TODO for panels with no sd card detect we need to poll to see if card is inserted - or not
672 }
673 }
674
675 void Panel::on_halt(void *arg)
676 {
677 halted= (arg == nullptr);
678 }