/* Arduino SdFat Library * Copyright (C) 2009 by William Greiman * * This file is part of the Arduino SdFat Library * * This Library 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 Library 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 the Arduino SdFat Library. If not, see * . */ #include "Marlin.h" #ifdef SDSUPPORT #include "SdFile.h" /** Create a file object and open it in the current working directory. * * \param[in] path A path with a valid 8.3 DOS name for a file to be opened. * * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive * OR of open flags. see SdBaseFile::open(SdBaseFile*, const char*, uint8_t). */ SdFile::SdFile(const char* path, uint8_t oflag) : SdBaseFile(path, oflag) { } //size=100B bool SdFile::openFilteredGcode(SdBaseFile* dirFile, const char* path){ if( open(dirFile, path, O_READ) ){ // compute the block to start with if( ! gfComputeNextFileBlock() ) return false; gfReset(); return true; } else { return false; } } //size=90B bool SdFile::seekSetFilteredGcode(uint32_t pos){ // SERIAL_PROTOCOLPGM("Seek:"); // SERIAL_PROTOCOLLN(pos); if(! seekSet(pos) )return false; if(! gfComputeNextFileBlock() )return false; gfReset(); return true; } //size=50B void SdFile::gfReset(){ gfCachePBegin = vol_->cache()->data; // reset cache read ptr to its begin gfCacheP = gfCachePBegin + gfOffset; } //FORCE_INLINE const uint8_t * find_endl(const uint8_t *p){ // while( *(++p) != '\n' ); // skip until a newline is found // return p; //} // think twice before allowing this to inline - manipulating 4B longs is costly // moreover - this function has its parameters in registers only, so no heavy stack usage besides the call/ret void __attribute__((noinline)) SdFile::gfUpdateCurrentPosition(uint16_t inc){ curPosition_ += inc; } #define find_endl(resultP, startP) \ __asm__ __volatile__ ( \ "cycle: \n" \ "ld r22, Z+ \n" \ "cpi r22, 0x0A \n" \ "brne cycle \n" \ : "=z" (resultP) /* result of the ASM code - in our case the Z register (R30:R31) */ \ : "z" (startP) /* input of the ASM code - in our case the Z register as well (R30:R31) */ \ : "r22" /* modifying register R22 - so that the compiler knows */ \ ) //size=400B // avoid calling the default heavy-weight read() for just one byte int16_t SdFile::readFilteredGcode(){ gfEnsureBlock(); // this is unfortunate :( ... other calls are using the cache and we can loose the data block of our gcode file // assume, we have the 512B block cache filled and terminated with a '\n' // SERIAL_PROTOCOLPGM("Read:"); // SERIAL_PROTOCOL(curPosition_); // SERIAL_PROTOCOL(':'); // for(uint8_t i = 0; i < 16; ++i){ // SERIAL_PROTOCOL( gfCacheP[i] ); // } // SERIAL_PROTOCOLLN(); const uint8_t *start = gfCacheP; uint8_t consecutiveCommentLines = 0; while( *gfCacheP == ';' ){ for(;;){ //while( *(++gfCacheP) != '\n' ); // skip until a newline is found - suboptimal code! // Wondering, why this "nice while cycle" is done in such a weird way using a separate find_endl() function? // Have a look at the ASM code GCC produced! // At first - a separate find_endl() makes the compiler understand, // that I don't need to store gfCacheP every time, I'm only interested in the final address where the '\n' was found // - the cycle can run on CPU registers only without touching memory besides reading the character being compared. // Not only makes the code run considerably faster, but is also 40B shorter! // This was the generated code: //FORCE_INLINE const uint8_t * find_endl(const uint8_t *p){ // while( *(++p) != '\n' ); // skip until a newline is found // return p; } // 11c5e: movw r30, r18 // 11c60: subi r18, 0xFF ; 255 // 11c62: sbci r19, 0xFF ; 255 // 11c64: ld r22, Z // 11c66: cpi r22, 0x0A ; 10 // 11c68: brne .-12 ; 0x11c5e // Still, even that was suboptimal as the compiler seems not to understand the usage of ld r22, Z+ (the plus is important) // aka automatic increment of the Z register (R30:R31 pair) // There is no other way than pure ASM! find_endl(gfCacheP, gfCacheP); // found a newline, prepare the next block if block cache end reached if( gfCacheP - gfCachePBegin > 512 ){ // at the end of block cache, fill new data in gfUpdateCurrentPosition( gfCacheP - start - 1 ); if( ! gfComputeNextFileBlock() )goto fail; gfEnsureBlock(); // fetch it into RAM gfCacheP = start = gfCachePBegin; } else { if(++consecutiveCommentLines == 255){ // SERIAL_PROTOCOLLN(sd->curPosition_); --gfCacheP; // unget the already consumed newline goto forceExit; } // peek the next byte - we are inside the block at least at 511th index - still safe if( *gfCacheP == ';' ){ // consecutive comment ++consecutiveCommentLines; } else { --gfCacheP; // unget the already consumed newline goto forceExit; } break; // found the real end of the line even across many blocks } } } forceExit: { gfUpdateCurrentPosition( gfCacheP - start + 1 ); int16_t rv = *gfCacheP++; // prepare next block if needed if( gfCacheP - gfCachePBegin >= 512 ){ // speed checking - now at roughly 170KB/s which is much closer to raw read speed of SD card blocks at ~250KB/s // SERIAL_PROTOCOL(millis2()); // SERIAL_PROTOCOL(':'); // SERIAL_PROTOCOLLN(curPosition_); if( ! gfComputeNextFileBlock() )goto fail; // don't need to force fetch the block here, it will get loaded on the next call gfCacheP = gfCachePBegin; } return rv; } fail: // SERIAL_PROTOCOLLNPGM("CacheFAIL"); return -1; } //size=70B bool SdFile::gfEnsureBlock(){ // SERIAL_PROTOCOLPGM("EB:"); // SERIAL_PROTOCOLLN(gfBlock); if ( vol_->cacheRawBlock(gfBlock, SdVolume::CACHE_FOR_READ)){ // terminate with a '\n' const uint16_t terminateOfs = fileSize_ - gfOffset; vol_->cache()->data[ terminateOfs < 512 ? terminateOfs : 512 ] = '\n'; return true; } else { return false; } } //#define shr9(resultCurPos, curPos) \ //__asm__ __volatile__ ( \ //"asr r23 \n" \ //"asr r22 \n" \ //"asr r21 \n" \ //"asr r20 \n" \ //"ldi r20, r21 \n" \ //"ldi r21, r22 \n" \ //"ldi r22, r23 \n" \ //"ldi r23, 0 \n" \ //: "=a" (resultCurPos) \ //: "a" (curPos) \ //) //size=350B bool SdFile::gfComputeNextFileBlock() { // error if not open or write only if (!isOpen() || !(flags_ & O_READ)) return false; gfOffset = curPosition_ & 0X1FF; // offset in block if (type_ == FAT_FILE_TYPE_ROOT_FIXED) { // SHR by 9 means skip the last byte and shift just 3 bytes by 1 // -> should be 8 instructions... and not the horrible loop shifting 4 bytes at once // still need to get some work on this gfBlock = vol_->rootDirStart() + (curPosition_ >> 9); } else { uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_); if (gfOffset == 0 && blockOfCluster == 0) { // start of new cluster if (curPosition_ == 0) { // use first cluster in file curCluster_ = firstCluster_; } else { // get next cluster from FAT if (!vol_->fatGet(curCluster_, &curCluster_)) return false; } } gfBlock = vol_->clusterStartBlock(curCluster_) + blockOfCluster; } return true; } //------------------------------------------------------------------------------ /** Write data to an open file. * * \note Data is moved to the cache but may not be written to the * storage device until sync() is called. * * \param[in] buf Pointer to the location of the data to be written. * * \param[in] nbyte Number of bytes to write. * * \return For success write() returns the number of bytes written, always * \a nbyte. If an error occurs, write() returns -1. Possible errors * include write() is called before a file has been opened, write is called * for a read-only file, device is full, a corrupt file system or an I/O error. * */ int16_t SdFile::write(const void* buf, uint16_t nbyte) { return SdBaseFile::write(buf, nbyte); } //------------------------------------------------------------------------------ /** Write a byte to a file. Required by the Arduino Print class. * \param[in] b the byte to be written. * Use writeError to check for errors. */ #if ARDUINO >= 100 size_t SdFile::write(uint8_t b) { return SdBaseFile::write(&b, 1); } #else void SdFile::write(uint8_t b) { SdBaseFile::write(&b, 1); } #endif //------------------------------------------------------------------------------ /** Write a string to a file. Used by the Arduino Print class. * \param[in] str Pointer to the string. * Use writeError to check for errors. */ void SdFile::write(const char* str) { SdBaseFile::write(str, strlen(str)); } //------------------------------------------------------------------------------ /** Write a PROGMEM string to a file. * \param[in] str Pointer to the PROGMEM string. * Use writeError to check for errors. */ void SdFile::write_P(PGM_P str) { for (uint8_t c; (c = pgm_read_byte(str)); str++) write(c); } //------------------------------------------------------------------------------ /** Write a PROGMEM string followed by CR/LF to a file. * \param[in] str Pointer to the PROGMEM string. * Use writeError to check for errors. */ void SdFile::writeln_P(PGM_P str) { write_P(str); write_P(PSTR("\r\n")); } #endif