#include "Marlin.h" #include "cardreader.h" #include "ultralcd.h" #include "stepper.h" #include "temperature.h" #include "language.h" #ifdef SDSUPPORT #define LONGEST_FILENAME (longFilename[0] ? longFilename : filename) CardReader::CardReader() { #ifdef SDCARD_SORT_ALPHA sort_count = 0; #if SDSORT_GCODE sort_alpha = true; sort_folders = FOLDER_SORTING; //sort_reverse = false; #endif #endif filesize = 0; sdpos = 0; sdprinting = false; cardOK = false; saving = false; logging = false; autostart_atmillis=0; workDirDepth = 0; file_subcall_ctr=0; memset(workDirParents, 0, sizeof(workDirParents)); autostart_stilltocheck=true; //the SD start is delayed, because otherwise the serial cannot answer fast enough to make contact with the host software. lastnr=0; //power to SD reader #if SDPOWER > -1 SET_OUTPUT(SDPOWER); WRITE(SDPOWER,HIGH); #endif //SDPOWER autostart_atmillis=millis()+5000; } char *createFilename(char *buffer,const dir_t &p) //buffer>12characters { char *pos=buffer; for (uint8_t i = 0; i < 11; i++) { if (p.name[i] == ' ')continue; if (i == 8) { *pos++='.'; } *pos++=p.name[i]; } *pos++=0; return buffer; } /** +* Dive into a folder and recurse depth-first to perform a pre-set operation lsAction: +* LS_Count - Add +1 to nrFiles for every file within the parent +* LS_GetFilename - Get the filename of the file indexed by nrFiles +* LS_SerialPrint - Print the full path and size of each file to serial output +*/ void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/) { dir_t p; uint8_t cnt = 0; // Read the next entry from a directory while (parent.readDir(p, longFilename) > 0) { // If the entry is a directory and the action is LS_SerialPrint if (DIR_IS_SUBDIR(&p) && lsAction != LS_Count && lsAction != LS_GetFilename) { // Get the short name for the item, which we know is a folder char lfilename[FILENAME_LENGTH]; createFilename(lfilename, p); // Allocate enough stack space for the full path to a folder, trailing slash, and nul bool prepend_is_empty = (prepend[0] == '\0'); int len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(lfilename) + 1 + 1; char path[len]; // Append the FOLDERNAME12/ to the passed string. // It contains the full path to the "parent" argument. // We now have the full path to the item in this folder. strcpy(path, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty strcat(path, lfilename); // FILENAME_LENGTH-1 characters maximum strcat(path, "/"); // 1 character // Serial.print(path); // Get a new directory object using the full path // and dive recursively into it. SdFile dir; if (!dir.open(parent, lfilename, O_READ)) { if (lsAction == LS_SerialPrint) { //SERIAL_ECHO_START(); //SERIAL_ECHOPGM(MSG_SD_CANT_OPEN_SUBDIR); //SERIAL_ECHOLN(lfilename); } } lsDive(path, dir); // close() is done automatically by destructor of SdFile } else { uint8_t pn0 = p.name[0]; if (pn0 == DIR_NAME_FREE) break; if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue; if (longFilename[0] == '.') continue; if (!DIR_IS_FILE_OR_SUBDIR(&p) || (p.attributes & DIR_ATT_HIDDEN)) continue; filenameIsDir = DIR_IS_SUBDIR(&p); if (!filenameIsDir && (p.name[8] != 'G' || p.name[9] == '~')) continue; switch (lsAction) { case LS_Count: nrFiles++; break; case LS_SerialPrint: createFilename(filename, p); SERIAL_PROTOCOL(prepend); SERIAL_PROTOCOL(filename); //SERIAL_PROTOCOLCHAR(' '); SERIAL_PROTOCOLLN(p.fileSize); break; case LS_GetFilename: //SERIAL_ECHOPGM("File: "); createFilename(filename, p); /*MYSERIAL.println(filename); SERIAL_ECHOPGM("Write date: "); writeDate = p.lastWriteDate; MYSERIAL.println(writeDate); writeTime = p.lastWriteTime; SERIAL_ECHOPGM("Creation date: "); MYSERIAL.println(p.creationDate); SERIAL_ECHOPGM("Access date: "); MYSERIAL.println(p.lastAccessDate); SERIAL_ECHOLNPGM("");*/ creationDate = p.creationDate; creationTime = p.creationTime; //writeDate = p.lastAccessDate; if (match != NULL) { if (strcasecmp(match, filename) == 0) return; } else if (cnt == nrFiles) return; cnt++; break; } } } // while readDir } void CardReader::ls() { lsAction=LS_SerialPrint; //if(lsAction==LS_Count) //nrFiles=0; root.rewind(); lsDive("",root); } void CardReader::initsd() { cardOK = false; if(root.isOpen()) root.close(); #ifdef SDSLOW if (!card.init(SPI_HALF_SPEED,SDSS) #if defined(LCD_SDSS) && (LCD_SDSS != SDSS) && !card.init(SPI_HALF_SPEED,LCD_SDSS) #endif ) #else if (!card.init(SPI_FULL_SPEED,SDSS) #if defined(LCD_SDSS) && (LCD_SDSS != SDSS) && !card.init(SPI_FULL_SPEED,LCD_SDSS) #endif ) #endif { //if (!card.init(SPI_HALF_SPEED,SDSS)) SERIAL_ECHO_START; SERIAL_ECHOLNRPGM(MSG_SD_INIT_FAIL); } else if (!volume.init(&card)) { SERIAL_ERROR_START; SERIAL_ERRORLNRPGM(MSG_SD_VOL_INIT_FAIL); } else if (!root.openRoot(&volume)) { SERIAL_ERROR_START; SERIAL_ERRORLNRPGM(MSG_SD_OPENROOT_FAIL); } else { cardOK = true; SERIAL_ECHO_START; SERIAL_ECHOLNRPGM(MSG_SD_CARD_OK); } workDir=root; curDir=&root; #ifdef SDCARD_SORT_ALPHA presort(); #endif /* if(!workDir.openRoot(&volume)) { SERIAL_ECHOLNPGM(MSG_SD_WORKDIR_FAIL); } */ } void CardReader::setroot() { /*if(!workDir.openRoot(&volume)) { SERIAL_ECHOLNPGM(MSG_SD_WORKDIR_FAIL); }*/ workDir=root; curDir=&workDir; #ifdef SDCARD_SORT_ALPHA presort(); #endif } void CardReader::release() { sdprinting = false; cardOK = false; } void CardReader::startFileprint() { if(cardOK) { sdprinting = true; #ifdef SDCARD_SORT_ALPHA flush_presort(); #endif } } void CardReader::pauseSDPrint() { if(sdprinting) { sdprinting = false; } } void CardReader::openLogFile(char* name) { logging = true; openFile(name, false); } void CardReader::getDirName(char* name, uint8_t level) { workDirParents[level].getFilename(name); } uint16_t CardReader::getWorkDirDepth() { return workDirDepth; } void CardReader::getAbsFilename(char *t) { uint8_t cnt=0; *t='/';t++;cnt++; for(uint8_t i=0;i(int)SD_PROCEDURE_DEPTH-1) { SERIAL_ERROR_START; SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:"); SERIAL_ERRORLN(SD_PROCEDURE_DEPTH); kill("", 1); return; } SERIAL_ECHO_START; SERIAL_ECHOPGM("SUBROUTINE CALL target:\""); SERIAL_ECHO(name); SERIAL_ECHOPGM("\" parent:\""); //store current filename and position getAbsFilename(filenames[file_subcall_ctr]); SERIAL_ECHO(filenames[file_subcall_ctr]); SERIAL_ECHOPGM("\" pos"); SERIAL_ECHOLN(sdpos); filespos[file_subcall_ctr]=sdpos; file_subcall_ctr++; } else { SERIAL_ECHO_START; SERIAL_ECHOPGM("Now doing file: "); SERIAL_ECHOLN(name); } file.close(); } else //opening fresh file { file_subcall_ctr=0; //resetting procedure depth in case user cancels print while in procedure SERIAL_ECHO_START; SERIAL_ECHOPGM("Now fresh file: "); SERIAL_ECHOLN(name); } sdprinting = false; SdFile myDir; curDir=&root; char *fname=name; char *dirname_start,*dirname_end; if(name[0]=='/') { dirname_start=strchr(name,'/')+1; while(dirname_start>0) { dirname_end=strchr(dirname_start,'/'); //SERIAL_ECHO("start:");SERIAL_ECHOLN((int)(dirname_start-name)); //SERIAL_ECHO("end :");SERIAL_ECHOLN((int)(dirname_end-name)); if(dirname_end>0 && dirname_end>dirname_start) { char subdirname[13]; strncpy(subdirname, dirname_start, dirname_end-dirname_start); subdirname[dirname_end-dirname_start]=0; SERIAL_ECHOLN(subdirname); if(!myDir.open(curDir,subdirname,O_READ)) { SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); SERIAL_PROTOCOL(subdirname); SERIAL_PROTOCOLLNPGM("."); return; } else { //SERIAL_ECHOLN("dive ok"); } curDir=&myDir; dirname_start=dirname_end+1; } else // the reminder after all /fsa/fdsa/ is the filename { fname=dirname_start; //SERIAL_ECHOLN("remaider"); //SERIAL_ECHOLN(fname); break; } } } else //relative path { curDir=&workDir; } if(read) { if (file.open(curDir, fname, O_READ)) { filesize = file.fileSize(); SERIAL_PROTOCOLRPGM(MSG_SD_FILE_OPENED); SERIAL_PROTOCOL(fname); SERIAL_PROTOCOLRPGM(MSG_SD_SIZE); SERIAL_PROTOCOLLN(filesize); sdpos = 0; SERIAL_PROTOCOLLNRPGM(MSG_SD_FILE_SELECTED); getfilename(0, fname); lcd_setstatus(longFilename[0] ? longFilename : fname); lcd_setstatus("SD-PRINTING "); } else { SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); SERIAL_PROTOCOL(fname); SERIAL_PROTOCOLLNPGM("."); } } else { //write if (!file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)) { SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); SERIAL_PROTOCOL(fname); SERIAL_PROTOCOLLNPGM("."); } else { saving = true; SERIAL_PROTOCOLRPGM(MSG_SD_WRITE_TO_FILE); SERIAL_PROTOCOLLN(name); lcd_setstatus(fname); } } } void CardReader::removeFile(char* name) { if(!cardOK) return; file.close(); sdprinting = false; SdFile myDir; curDir=&root; char *fname=name; char *dirname_start,*dirname_end; if(name[0]=='/') { dirname_start=strchr(name,'/')+1; while(dirname_start>0) { dirname_end=strchr(dirname_start,'/'); //SERIAL_ECHO("start:");SERIAL_ECHOLN((int)(dirname_start-name)); //SERIAL_ECHO("end :");SERIAL_ECHOLN((int)(dirname_end-name)); if(dirname_end>0 && dirname_end>dirname_start) { char subdirname[13]; strncpy(subdirname, dirname_start, dirname_end-dirname_start); subdirname[dirname_end-dirname_start]=0; SERIAL_ECHOLN(subdirname); if(!myDir.open(curDir,subdirname,O_READ)) { SERIAL_PROTOCOLRPGM("open failed, File: "); SERIAL_PROTOCOL(subdirname); SERIAL_PROTOCOLLNPGM("."); return; } else { //SERIAL_ECHOLN("dive ok"); } curDir=&myDir; dirname_start=dirname_end+1; } else // the reminder after all /fsa/fdsa/ is the filename { fname=dirname_start; //SERIAL_ECHOLN("remaider"); //SERIAL_ECHOLN(fname); break; } } } else //relative path { curDir=&workDir; } if (file.remove(curDir, fname)) { SERIAL_PROTOCOLPGM("File deleted:"); SERIAL_PROTOCOLLN(fname); sdpos = 0; #ifdef SDCARD_SORT_ALPHA presort(); #endif } else { SERIAL_PROTOCOLPGM("Deletion failed, File: "); SERIAL_PROTOCOL(fname); SERIAL_PROTOCOLLNPGM("."); } } uint32_t CardReader::getFileSize() { return filesize; } void CardReader::getStatus() { if(sdprinting){ SERIAL_PROTOCOL(longFilename); SERIAL_PROTOCOLPGM("\n"); SERIAL_PROTOCOLRPGM(MSG_SD_PRINTING_BYTE); SERIAL_PROTOCOL(sdpos); SERIAL_PROTOCOLPGM("/"); SERIAL_PROTOCOLLN(filesize); uint16_t time = millis()/60000 - starttime/60000; SERIAL_PROTOCOL(itostr2(time/60)); SERIAL_PROTOCOL(':'); SERIAL_PROTOCOL(itostr2(time%60)); SERIAL_PROTOCOLPGM("\n"); } else{ SERIAL_PROTOCOLLNRPGM("Not printing"); } } void CardReader::write_command(char *buf) { char* begin = buf; char* npos = 0; char* end = buf + strlen(buf) - 1; file.writeError = false; if((npos = strchr(buf, 'N')) != NULL) { begin = strchr(npos, ' ') + 1; end = strchr(npos, '*') - 1; } end[1] = '\r'; end[2] = '\n'; end[3] = '\0'; file.write(begin); if (file.writeError) { SERIAL_ERROR_START; SERIAL_ERRORLNRPGM(MSG_SD_ERR_WRITE_TO_FILE); } } #define CHUNK_SIZE 64 void CardReader::write_command_no_newline(char *buf) { file.write(buf, CHUNK_SIZE); if (file.writeError) { SERIAL_ERROR_START; SERIAL_ERRORLNRPGM(MSG_SD_ERR_WRITE_TO_FILE); MYSERIAL.println("An error while writing to the SD Card."); } } void CardReader::checkautostart(bool force) { if(!force) { if(!autostart_stilltocheck) return; if(autostart_atmillis 0) { for(int8_t i=0;i<(int8_t)strlen((char*)p.name);i++) p.name[i]=tolower(p.name[i]); //Serial.print((char*)p.name); //Serial.print(" "); //Serial.println(autoname); if(p.name[9]!='~') //skip safety copies if(strncmp((char*)p.name,autoname,5)==0) { char cmd[30]; // M23: Select SD file sprintf_P(cmd, PSTR("M23 %s"), autoname); enquecommand(cmd); // M24: Start/resume SD print enquecommand_P(PSTR("M24")); found=true; } } if(!found) lastnr=-1; else lastnr++; } void CardReader::closefile(bool store_location) { file.sync(); file.close(); saving = false; logging = false; if(store_location) { //future: store printer state, filename and position for continuing a stopped print // so one can unplug the printer and continue printing the next day. } } void CardReader::getfilename(uint16_t nr, const char * const match/*=NULL*/) { curDir=&workDir; lsAction=LS_GetFilename; nrFiles=nr; curDir->rewind(); lsDive("",*curDir,match); } uint16_t CardReader::getnrfilenames() { curDir=&workDir; lsAction=LS_Count; nrFiles=0; curDir->rewind(); lsDive("",*curDir); //SERIAL_ECHOLN(nrFiles); return nrFiles; } void CardReader::chdir(const char * relpath) { SdFile newfile; SdFile *parent=&root; if(workDir.isOpen()) parent=&workDir; if(!newfile.open(*parent,relpath, O_READ)) { SERIAL_ECHO_START; SERIAL_ECHORPGM(MSG_SD_CANT_ENTER_SUBDIR); SERIAL_ECHOLN(relpath); } else { if (workDirDepth < MAX_DIR_DEPTH) { for (int d = ++workDirDepth; d--;) workDirParents[d+1] = workDirParents[d]; workDirParents[0]=*parent; } workDir=newfile; } } void CardReader::updir() { if(workDirDepth > 0) { --workDirDepth; workDir = workDirParents[0]; int d; for (int d = 0; d < workDirDepth; d++) workDirParents[d] = workDirParents[d+1]; } } #ifdef SDCARD_SORT_ALPHA /** * Get the name of a file in the current directory by sort-index */ void CardReader::getfilename_sorted(const uint16_t nr) { getfilename( #if SDSORT_GCODE sort_alpha && #endif (nr < sort_count) ? sort_order[nr] : nr ); } /** * Read all the files and produce a sort key * * We can do this in 3 ways... * - Minimal RAM: Read two filenames at a time sorting along... * - Some RAM: Buffer the directory just for this sort * - Most RAM: Buffer the directory and return filenames from RAM */ void CardReader::presort() { uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT); if (sdSort == SD_SORT_NONE) return; //sd sort is turned off #if !SDSORT_USES_RAM lcd_set_progress(); #endif lcd_implementation_clear(); lcd_print_at_PGM(0, 1, MSG_SORTING); #if SDSORT_GCODE if (!sort_alpha) return; #endif // Throw away old sort index flush_presort(); // If there are files, sort up to the limit uint16_t fileCnt = getnrfilenames(); if (fileCnt > 0) { // Never sort more than the max allowed // If you use folders to organize, 20 may be enough if (fileCnt > SDSORT_LIMIT) fileCnt = SDSORT_LIMIT; // Sort order is always needed. May be static or dynamic. #if SDSORT_DYNAMIC_RAM sort_order = new uint8_t[fileCnt]; #endif // Use RAM to store the entire directory during pre-sort. // SDSORT_LIMIT should be set to prevent over-allocation. #if SDSORT_USES_RAM // If using dynamic ram for names, allocate on the heap. #if SDSORT_CACHE_NAMES #if SDSORT_DYNAMIC_RAM sortshort = new char*[fileCnt]; sortnames = new char*[fileCnt]; #endif #elif SDSORT_USES_STACK char sortnames[fileCnt][LONG_FILENAME_LENGTH]; uint16_t creation_time[SDSORT_LIMIT]; uint16_t creation_date[SDSORT_LIMIT]; #endif // Folder sorting needs 1 bit per entry for flags. #if HAS_FOLDER_SORTING #if SDSORT_DYNAMIC_RAM isDir = new uint8_t[(fileCnt + 7) >> 3]; #elif SDSORT_USES_STACK uint8_t isDir[(fileCnt + 7) >> 3]; #endif #endif #else // !SDSORT_USES_RAM // By default re-read the names from SD for every compare // retaining only two filenames at a time. This is very // slow but is safest and uses minimal RAM. char name1[LONG_FILENAME_LENGTH + 1]; uint16_t creation_time_bckp; uint16_t creation_date_bckp; #endif if (fileCnt > 1) { // Init sort order. for (uint16_t i = 0; i < fileCnt; i++) { sort_order[i] = i; // If using RAM then read all filenames now. #if SDSORT_USES_RAM getfilename(i); #if SDSORT_DYNAMIC_RAM // Use dynamic method to copy long filename sortnames[i] = strdup(LONGEST_FILENAME); #if SDSORT_CACHE_NAMES // When caching also store the short name, since // we're replacing the getfilename() behavior. sortshort[i] = strdup(filename); #endif #else // Copy filenames into the static array strcpy(sortnames[i], LONGEST_FILENAME); creation_time[i] = creationTime; creation_date[i] = creationDate; #if SDSORT_CACHE_NAMES strcpy(sortshort[i], filename); #endif #endif // char out[30]; // sprintf_P(out, PSTR("---- %i %s %s"), i, filenameIsDir ? "D" : " ", sortnames[i]); // SERIAL_ECHOLN(out); #if HAS_FOLDER_SORTING const uint16_t bit = i & 0x07, ind = i >> 3; if (bit == 0) isDir[ind] = 0x00; if (filenameIsDir) isDir[ind] |= _BV(bit); #endif #endif } // Bubble Sort uint16_t counter = 0; for (uint16_t i = fileCnt; --i;) { bool didSwap = false; #if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used int8_t percent = ((counter * 100) / (fileCnt - 1)); for (int column = 0; column < 20; column++) { if (column < (percent / 5)) lcd_implementation_print_at(column, 2, "\x01"); //simple progress bar } counter++; #endif //MYSERIAL.println(int(i)); for (uint16_t j = 0; j < i; ++j) { const uint16_t o1 = sort_order[j], o2 = sort_order[j + 1]; // Compare names from the array or just the two buffered names #if SDSORT_USES_RAM #define _SORT_CMP_NODIR() (strcasecmp(sortnames[o1], sortnames[o2]) > 0) #define _SORT_CMP_TIME_NODIR() (((creation_date[o1] == creation_date[o2]) && (creation_time[o1] < creation_time[o2])) || \ (creation_date[o1] < creation_date [o2])) #else #define _SORT_CMP_NODIR() (strcasecmp(name1, name2) > 0) //true if lowercase(name1) > lowercase(name2) #define _SORT_CMP_TIME_NODIR() (((creation_date_bckp == creationDate) && (creation_time_bckp < creationTime)) || \ (creation_date_bckp < creationDate)) #endif #if HAS_FOLDER_SORTING #if SDSORT_USES_RAM // Folder sorting needs an index and bit to test for folder-ness. const uint8_t ind1 = o1 >> 3, bit1 = o1 & 0x07, ind2 = o2 >> 3, bit2 = o2 & 0x07; #define _SORT_CMP_DIR(fs) \ (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \ ? _SORT_CMP_NODIR() \ : (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0) #define _SORT_CMP_TIME_DIR(fs) \ (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \ ? _SORT_CMP_TIME_NODIR() \ : (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0) #else #define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs > 0 ? dir1 : !dir1)) #define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs > 0 ? dir1 : !dir1)) #endif #endif // The most economical method reads names as-needed // throughout the loop. Slow if there are many. #if !SDSORT_USES_RAM getfilename(o1); strcpy(name1, LONGEST_FILENAME); // save (or getfilename below will trounce it) creation_date_bckp = creationDate; creation_time_bckp = creationTime; #if HAS_FOLDER_SORTING bool dir1 = filenameIsDir; #endif getfilename(o2); char *name2 = LONGEST_FILENAME; // use the string in-place #endif // !SDSORT_USES_RAM // Sort the current pair according to settings. if ( #if HAS_FOLDER_SORTING (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_DIR(FOLDER_SORTING)) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_DIR(FOLDER_SORTING)) #else (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_NODIR()) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_NODIR()) #endif ) { sort_order[j] = o2; sort_order[j + 1] = o1; didSwap = true; //SERIAL_ECHOLNPGM("did swap"); } } if (!didSwap) break; } //end of bubble sort loop // Using RAM but not keeping names around #if (SDSORT_USES_RAM && !SDSORT_CACHE_NAMES) #if SDSORT_DYNAMIC_RAM for (uint16_t i = 0; i < fileCnt; ++i) free(sortnames[i]); #if HAS_FOLDER_SORTING free(isDir); #endif #endif #endif } else { sort_order[0] = 0; #if (SDSORT_USES_RAM && SDSORT_CACHE_NAMES) getfilename(0); #if SDSORT_DYNAMIC_RAM sortnames = new char*[1]; sortnames[0] = strdup(LONGEST_FILENAME); // malloc sortshort = new char*[1]; sortshort[0] = strdup(filename); // malloc isDir = new uint8_t[1]; #else strcpy(sortnames[0], LONGEST_FILENAME); strcpy(sortshort[0], filename); #endif isDir[0] = filenameIsDir ? 0x01 : 0x00; #endif } sort_count = fileCnt; } #if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used for (int column = 0; column <= 19; column++) lcd_implementation_print_at(column, 2, "\x01"); //simple progress bar delay(500); lcd_set_arrows(); lcd_update(2); #endif } void CardReader::flush_presort() { if (sort_count > 0) { #if SDSORT_DYNAMIC_RAM delete sort_order; #if SDSORT_CACHE_NAMES for (uint8_t i = 0; i < sort_count; ++i) { free(sortshort[i]); // strdup free(sortnames[i]); // strdup } delete sortshort; delete sortnames; #endif #endif sort_count = 0; } } #endif // SDCARD_SORT_ALPHA void CardReader::printingHasFinished() { st_synchronize(); if(file_subcall_ctr>0) //heading up to a parent file that called current as a procedure. { file.close(); file_subcall_ctr--; openFile(filenames[file_subcall_ctr],true,true); setIndex(filespos[file_subcall_ctr]); startFileprint(); } else { quickStop(); file.close(); sdprinting = false; if(SD_FINISHED_STEPPERRELEASE) { finishAndDisableSteppers(); //enquecommand_P(PSTR(SD_FINISHED_RELEASECOMMAND)); } autotempShutdown(); #ifdef SDCARD_SORT_ALPHA presort(); #endif } } bool CardReader::ToshibaFlashAir_GetIP(uint8_t *ip) { memset(ip, 0, 4); return card.readExtMemory(1, 1, 0x400+0x150, 4, ip); } #endif //SDSUPPORT