LittleFS port to Teensy/SPIFlash

Many pages/posts back :
seems to be the early posts for noting and code for skipping the Always Erase/Formatting before write resulting in the net 5X faster write throughput. Paul has implemented the blockIsBlank() code to read the block and skip formatting when it is already 0xff's at the time cost of us's not ms's - perhaps that isn't working as expected as it should when Media start with full LLformat already done for First Use?

... been an ongoing thing I've evolved some - but it is an invasive set of changes that needs to evolve/invade more for total integration if it is to work.

So far the only place I see the function blockIsBlank is called is in the function: LittleFS::lowLevelFormat

I have not completely instrumented the code yet, but my guess is that each time the code completes a 4k chunk, it probably calls in lfs.c
the function lfs_file_write calls the function lfs_ctz_extend which finds a block and appears to alwasy call lfs_bd_erase

Which calls through the FS to erase.

It would be great to bypass this if possible.
 
Is it possible to change the numbers of bytes transferred by MTP to a smaller size like 4096?

Sure, the USB uses 512 bytes or 64 bytes (depending on speed)
I choose 4096 to speed up uSD access (I'm only interested in fast access to embedded data storage, so 4096 disk buffer seemed adequate)
 
So far the only place I see the function blockIsBlank is called is in the function: LittleFS::lowLevelFormat

I have not completely instrumented the code yet, but my guess is that each time the code completes a 4k chunk, it probably calls in lfs.c
the function lfs_file_write calls the function lfs_ctz_extend which finds a block and appears to alwasy call lfs_bd_erase

Which calls through the FS to erase.

It would be great to bypass this if possible.

That must be correct - it DOES always format. And I saw blockIsBlank() and used it - and wrongly assumed it was all places needed - but ignored that because I was skipping it with posted code like:
Code:
		if ( checkformat[iiblk] & jjbit ) { // was set as formatted, unset as erase precedes usage

Though in going through the code I know that isn't the case. The LFS code calls the bare ::erase() functions.

For _program the LittleFS_Program::static_erase() function calls the EEPROM code - and that will return quickly without format as it must test on in that code.

So I ignored it but reworking ALL the erase() calls to call blockIsBlank() would be an improvement when preformatted.
 
I think I asked something about this in some of my earlier ramblings. Back in https://forum.pjrc.com/threads/58033-LittleFS-port-to-Teensy-SPIFlash?p=262644&viewfull=1#post262644

Wondering if the FS interface should have some method that one can ask it what is the preferred write size...

Alternatively one could maybe build this into MTP, code, like potentially in something like:
Code:
storage.addFilesystem(progmfs[ii], lfs_progm_str[ii]);
Code:
storage.addFilesystem(progmfs[ii], lfs_progm_str[ii], 4096);

Sorry - missed that with all the distractions. Just out of curiosity I did a grep of SDFat and the only place I see 8192 bytes is in FatFormater.h (just rambling now);
Code:
// Constants for file system structure optimized for flash.
uint16_t const BU16 = 128;
uint16_t const BU32 = 8192;
// Assume 512 byte sectors.
const uint16_t BYTES_PER_SECTOR = 512;
const uint16_t SECTORS_PER_MB = 0X100000/BYTES_PER_SECTOR;
const uint16_t FAT16_ROOT_ENTRY_COUNT = 512;
const uint16_t FAT16_ROOT_SECTOR_COUNT =
               32*FAT16_ROOT_ENTRY_COUNT/BYTES_PER_SECTOR;

WMXZ said:
Sure, the USB uses 512 bytes or 64 bytes (depending on speed)
I choose 4096 to speed up uSD access (I'm only interested in fast access to embedded data storage, so 4096 disk buffer seemed adequate)

Ok so in USB_DESC.h you show:
Code:
  #define MTP_TX_SIZE_480       512
  #define MTP_RX_SIZE_480       512
  #define MTP_TX_SIZE_12        64
  #define MTP_RX_SIZE_12        64
same for CDC. Since I have no idea what I am about to say forgive me. Not sure how that equates to 4096?
 
Currently (at least the version I have) goes to 8192.

Look at MTD.h in my current version line 92: #define DISK_BUFFER_SIZE 8*1024


Again my version looks very different although still has the normal MTPD::SendObject()

(note different code in file for T3 versus T4...

Currently the code
Code:
    bool MTPD::SendObject() 
    { 
      pull_packet(rx_data_buffer);
      read(0,0);
//      printContainer(); 

      uint32_t len = ReadMTPHeader();
      uint32_t index = sizeof(MTPHeader);
      disk_pos=0;
  
      // experiment to try setting the size of file up front
      printf("MTPD::SendObject: len:%u\n", len);


      
      while((int)len>0)
      { uint32_t bytes = MTP_RX_SIZE - index;                     // how many data in usb-packet
        bytes = min(bytes,len);                                   // loimit at end
        uint32_t to_copy=min(bytes, DISK_BUFFER_SIZE-disk_pos);   // how many data to copy to disk buffer
        memcpy(disk_buffer+disk_pos, rx_data_buffer + index,to_copy);
        disk_pos += to_copy;
        bytes -= to_copy;
        len -= to_copy;
        //printf("a %d %d %d %d %d\n", len,disk_pos,bytes,index,to_copy);
        //
        [COLOR="#FF0000"]if(disk_pos==DISK_BUFFER_SIZE)[/COLOR]
        {
          elapsedMicros em = 0;

          //[COLOR="#FF0000"]if(storage_->write((const char *)disk_buffer, DISK_BUFFER_SIZE)<DISK_BUFFER_SIZE) return false;[/COLOR]
          size_t bytes_written = storage_->write((const char *)disk_buffer, DISK_BUFFER_SIZE);
          printf("WR %u %u %u\n", DISK_BUFFER_SIZE, bytes_written, (uint32_t)em);
          if (bytes_written < DISK_BUFFER_SIZE) return false;

          disk_pos =0;

          if(bytes) // we have still data in transfer buffer, copy to initial disk_buffer
          {
            memcpy(disk_buffer,rx_data_buffer+index+to_copy,bytes);
            disk_pos += bytes;
            len -= bytes;
          }
          //printf("b %d %d %d %d %d\n", len,disk_pos,bytes,index,to_copy);
        }
        if(len>0)  // we have still data to be transfered
        { pull_packet(rx_data_buffer);
          index=0;
        }
      }
      //printf("len %d\n",disk_pos);
      if(disk_pos)
      {
        elapsedMicros em = 0;
        if(storage_->write((const char *)disk_buffer, disk_pos)<disk_pos) return false;
        printf("WR %u %u\n", DISK_BUFFER_SIZE, (uint32_t)em);
      }
      storage_->close();
      return true;
    }
As I mentioned, that fixed 8192, could somehow be specified by which FS in use. With a max of the buffer size in the object...
 
@KurtE
Thanks for the explanation - like I said trying to play catch up now. Was going to try and put NAND into LittleFS but getting too complicated - my head is spinning so need a distraction.

Going to download your version - anything I need to update besides MTP?
 
@KurtE
Thanks for the explanation - like I said trying to play catch up now. Was going to try and put NAND into LittleFS but getting too complicated - my head is spinning so need a distraction.

Going to download your version - anything I need to update besides MTP?

No, that is it for now... I did try a few hacks earlier with FS.h and littleFS, but discarded them.
 
@KurtE
Downloaded the latest core files for the T4 as well as you send-object branch and tried running but Teensy never showed up in windows explorer. Tried SD, program and others. Did also remove my SPIFFS version just in case. What am i missing or have I just been up too long.
 
Note: I currently typically run the mtp-test.ino sketch from sublime text.

Using the Tset with it configured for MTP-Disk. (using the rawhid stuff)... I am running with latest (at least as of yesterday) core. Also with version of TyCommander that works with the beta.

So When it downloads the program, it looks like TyCommander takes several seconds before it looks like it will take input. At that point I hit enter (with cursor in input area). This un-hangs the program and I get the output in the commander and Teensy shows up in windows. However if I wait very long after it will take input to give it input, I see debug info but no windows...
 
Just spent 642 seconds low level formatting a 64MB QSPI.

Did a quick edit to updated changes, prior changes were all meant to make no change unless manually activated. The Quick edit when added knownFormatted() is called to check the bitmap[] - when not allocated will call blockIsBlank() for SPI and QSPI media.

Without enabling the bitmap usage 'Z' calling :: myfs.checkFormat( true );

It will at least blockIsBlank() a block to be formatted before forcing a format. This takes 20 or 40 us instead of 24 ms ... ( times posted before may be diff this chip )

Changed LFSintegrity 'S' to now write a 2MB file { Formerly the TRUNCATE TEST CASE }

Code:
Start Big write of 2048000 Bytes  ... patience ...........................
Big write /0_bigftrunc.txt took  4.67 Sec for 2045952 Bytes : file3.size()=2045952
	Big write KBytes per second 438.55 

Bytes Used: 2060288, Bytes Total:67108864

In this case for Preformatted media the format is skipped - and would be a simple step. Perhaps Paul might have intended that in the first place as he does that check before formatting any _Program blocks.

I should have started with that { except it is ON and now I can't do a compare to turn it off } - doing that first - then having the bitmap[] external to the main code could determine blocks in need of 'background' or user imposed formatUnused() { versus current quick or lowLevel } where the bitmap work done would allow tested and formatting any unused block that needs to be formatted - on USER command.

I have updated my github :: github.com/Defragster/LittleFS

It should work to test. It may take a Day or two for time to rewrite the code with the above and bitmap[] allocs to more cleanly what I have now as :: myfs.formatUnused( )
 
Note: I currently typically run the mtp-test.ino sketch from sublime text.

Using the Tset with it configured for MTP-Disk. (using the rawhid stuff)... I am running with latest (at least as of yesterday) core. Also with version of TyCommander that works with the beta.

So When it downloads the program, it looks like TyCommander takes several seconds before it looks like it will take input. At that point I hit enter (with cursor in input area). This un-hangs the program and I get the output in the commander and Teensy shows up in windows. However if I wait very long after it will take input to give it input, I see debug info but no windows...

Thanks Kurt - that seemed to got it working - just hitting return in the Arduino IDE :)

Was playing with the buffer sizes and playing with the W25Q64 and W25Q128 flash chips. I have only one file that would give me problems and it seems to be working on the now on both chips. There is one caveat. I changed your buffersize - went smaller instead of larger.:
Code:
#define DISK_BUFFER_SIZE 8*256

Reason - made it a multiple of the config.progsize in LFS. Out of curiosity can you give it a try with your problem child file.
 
Had to run after posting the above ... then back in time to eat ...

So looking at prior posted code - NOW that I found the :: lfs_fs_traverse(&lfs, cb_usedBlocks, checkused);

I can rewrite that trivially and better.

Asking for used blocks - ON request - built into a bitmap because it is ALL or NOTHING.

The formatted bitmap can be forgone ( at this time for sure )

For the SPI and QSPI ::erase() the blockIsBlank() can be added - dirty blocks go fast (first few bytes show dirty) and formatted blocks do a full read to see that - but when formatted it will exit without doing reformat.

Then the prior myfs.formatUnused( # ); can format # blocks found to be unformatted, where 0 means ALL, otherwise it will just do the lfs_fs_traverse() and check unused blocks to be dirty and if dirty then format them. Should have another param so it doesn't always start at block #0 as they'll then always be formatted and takes longest to get to the next dirty block stepping over the used and finding all the unused already formatted - it could return the block it stopped on and that would have to be saved in the class struct for use next time to pass as that 2nd param.

_RAM doesn't need any of this, _Program already won't reFormat - but it uses static code to do static_erase and would need rework to pass through an instance::erase() to have access to class this data to perhaps do this. But the lfs_fs_traverse() will work with ::erase() to call its ::static_erase() ... TBD

... will give it a go asap ...

Oh- and this will make all the bitmaps temporary during use and no full bitmap for formatted - just block by block test in progress.

Backing out github edits ( not pushing up? ) but will see if the thoughts can get coded ... soon/easy enough
 
Last edited:
UPDATED and worth using:: Need the two 'src' files and updated LFSintegrity.ino from::
Code:
.
[B]     >>> New GITHUB :: See [URL="https://forum.pjrc.com/threads/58033-LittleFS-port-to-Teensy-SPIFlash?p=263058&viewfull=1#post263058"]post #566[/URL][/B]
.

Changes:: skip unneeded format and allow LIVE DISK reformat of unused blocks:
> Disk Integrity None - no changes to disk integrity - Run LFSintegrity:
- reformat unused space ( 'y' or 'Y' ) or ALL with 'f' << DO any of these at ANY time
- Fill HALF free space with 'B' a few times, then 'b' to remove files
- Fill 2MB free space with 'S' a few times, then 's' to remove files
- run the iterations as desired to add,grow,remove files [1,2,...9,h,k]
* Integrity Notes:
** 's' and 'b' on deleting the files verify length and simple content.
** Iteration created files are a bit more unique on creation and verified on delete with file size confirmation.
** Static variable of FileCount updated for any file added or removed on purpose and compared to directory file count when the prompt returns to SerMon

> LFS asking for format SPI & QSPI ( _Program already checks ) will confirm Format is needed before calling format :: if (!blockIsBlank(&config, block, buffer)) {

> No other changes to code ... except adding user call that leaves no trace - except Formatted unused blocks
Call:
myfs.formatUnused( 0, 0 ); // Format ALL UNUSED dirty blocks and format them
res=myfs.formatUnused( 1, 0 ); // Find and format 1 unused dirty block
res= myfs.formatUnused( 15, 0 ); // Find and format 15 unused dirty blocks

Why the second Param of '0'? If called again with res=myfs.formatUnused( 1, res );
Then the check for unformatted blocks starts there, not included in LFSintegrity as uploaded, but I just tested:
Code:
 myfs.formatUnused( 1 ) ...
FMT 2021,
	 formatUnused :: Done Formatting Low Level in [B]319531[/B] us (last 2022).

[  0.28 M](0.00533 M elap) Awaiting input 0123456789RdchkFqvplmusSBbyYxf+-? loops left 0 >
 myfs.formatUnused( 1 ) ...
FMT 2023,
	 formatUnused :: Done Formatting Low Level in [B]40282[/B] us (last 2024).

What's in that time for the First call?
> 319ms :: calls traverses the LFS used block list - then has to read ALL unused blocks to find that #2021 was DIRTY and format it

What's in that time for the Second call?
> 40ms :: calls traverses the LFS used block list - then has to read unused blocks starting at (last 2022) to find that #2024 was DIRTY and format it

Not really tested much - but enough to see it works.

Here is time of 262 seconds to format 609 Blocks after some runs of integrity
Code:
FMT 3048,FMT 3049,FMT 3050,FMT 3051,FMT 3052,FMT 3055,FMT 3057,FMT 3059,	#608
FMT 3062,
	 formatUnused :: Done Formatting Low Level in 26226905 us.

How fast can it create a 2MB file when Media is preformatted:
Code:
Start Big write of 2048000 Bytes........................
Big write /0_2MBfile.txt took  5.02 Sec for 2045952 Bytes : file3.size()=2045952
	Big write KBytes per second 407.48
 
Last edited:
Thanks Kurt - that seemed to got it working - just hitting return in the Arduino IDE :)

Was playing with the buffer sizes and playing with the W25Q64 and W25Q128 flash chips. I have only one file that would give me problems and it seems to be working on the now on both chips. There is one caveat. I changed your buffersize - went smaller instead of larger.:
Code:
#define DISK_BUFFER_SIZE 8*256

Reason - made it a multiple of the config.progsize in LFS. Out of curiosity can you give it a try with your problem child file.

Morning... Yep still have problem with 2kb... :(

Was distracted yesterday afternoon with other stuff, including trying to assemble a couple memory breakout boards and try to jumper them to Extended T4.1...
Found one of my castellated solder joints on the T4.1 had disconnected and touched it up.

Yesterday I could not talk to the two chips. One PSRAM the other the Winbond 25q128jvsq ...
Test reporting external memory showed 0 and the littlefs_q... hangs...

This morning tried unplugging the Winbond chip and PSRAM shows up as 8... Need to ring them back out again. But HiLow test showed the right pins (I think).
IMG_0289.jpg

Next up try Defragsters changes.

Also thinking of hacking up a quick and dirty test which does not use QSPI to verify it is just the speed. I was thinking of creating sub-class of LittleFS_RAM
where I overwrite the write method and update it such that it causes every write to take at least N milliseconds before it returns. To see what thresholds there may be.
 
Thanks KurtE (@all) - interested to hear what you find. A single SerMon CMDline like "Y2" will format up to 15 Blocks, then run two iterations. All CMD's are run before returning to loop so Y2 is the same as 2Y because the 'Y' is immediate and the '2' needs loop() to iterate. But typing that in TyComm allows 'up arrow and Enter' to repeat. Though an iteration can dirty way more than 15 blocks, so an 'f'ormat on occasion will have a lot of work to do after that. But repeating that will keep the Media formatted and the speed up.

NOTE: I have not tested myfs.formatUnused() yet on _Program - but as CLASS member it should work just fine. As noted _Program already won't Format a block ready to be used. Same behavior now done for SPI/QSPI noted below.

Pushed updates to github.com/Defragster/LittleFS/blob/main/examples/LFSintegrity/LFSintegrity.ino
>>> New GITHUB :: See post #566
> 64 bit rd/wr counts, no CUMM time yet.
> Added b/sec on verify reads - seeing 2.5MB/sec on reads - and they are all one byte read and compare - so not optimal.
> BOTH 'y' and 'Y' now start reformat where last one quit to be more timely :: res=myfs.formatUnused( 1, res );

The 'l'oop command shows the cumulative bytes read and written. I should put a cumulative TIME on those too so so the overall Bytes/Sec can be shown. And I need to make them uint64 as long runs can move more than 4GB.

The uploaded version dropped the RAM and RAM2 edits made - but for 8 and 16 MB PSRAM the 512 hardcoded Blocks needs to get adjusted as noted before. 512 alloc chunks are not large file friendly, and larger blocks where small files can fit into the DIR entry are more efficient.

Got FRAM yesterday - one of the 1Mb cypress and some of the ROHM FERAM 1's and legged 2Mb's.
@mjs513 - can solder the Cypress in a day or so to try that. Then would be interesting to see if the More_Bytes/dollar ROHM FERAM's can work as well - I suppose you got some of those by now?


@Paul - if you are reading - here is what erase() changes look like with blockIsBlank() :: github.com/Defragster/LittleFS/blob/main/src/LittleFS.cpp#L339
>> This stops every LFS new block from Formatting - even when already formatted. Same for SPI and QSPI.
Code:
int LittleFS_SPIFlash::erase(lfs_block_t block)
{
	if (!port) return LFS_ERR_IO;
	void *buffer = malloc(config.read_size);
	if ( buffer != nullptr) {
		if ( blockIsBlank(&config, block, buffer)) {
			free(buffer);
			return 0; // Already formatted exit no wait
		}
		free(buffer);
	}

And the 'Live Disk ReFormat' is here :: github.com/Defragster/LittleFS/blob/main/src/LittleFS.cpp#L194
Note line 171 has the #define DEBUGCF that shows FMT in action and is for DEBUG/tracking { already turned off the full map print line #207 with :: #ifdef DEBUGCF_2
 
Last edited:
@defragster - Is your version reasonable enough to try syncing to, to see if MTP likes copy of larger files?

At times wish your changes were in a different branch: as typically can not sync up to different fork/branch of the same branch name.
For example if your updates were in a branch named something like: LFSintegrity
Then I could do things like: git remote add defragster www.github.com\defagster\LittleFS

Then can probably using the windows app, simply pull in your branch and play and then easily go back to normally master (but in this case default is called main.

Unfortunately I am not a git guru, just know enough to be dangerous.

But if I were trying to do such a thing, the things I would probably do include:
a) Make an external copy of your directory in case all goes wrong.
b) In windows app, go the branch menu and create a new branch...
c) In windows tell system to push your branch to github...
d) Danger zone: I would open a command prompt: Create a command prompt and cd to git project (or use command within github to open it ctrl+` )
Then I would try something like:
Code:
git fetch upstream
git checkout main
git reset --hard upstream/main
git push origin main --force

Note: I do this most of the time when I wish to make sure my project is exactly in sync in the main (most of the time master) branch. ...
But warning this will toss everything you have in that branch away that is not what is up on the upstream version of that branch...
 
SEE :: github.com/Defragster/LFSintegrity

What a PIA - ghub is horrid ....


Unless you can test other wise - it is ready to ship

Changes are safe and minimal.

It is a 50% solution where it always incurs traverse over head - I am seeing 20ms for the DirWalk by LFS.

Same name is in the .git folder - had to DO thendelete .git folder and REDO and pointed to folder and it made a new subfolder so on my machine the sources are in :: "C:\Users\defragster\Documents\GitHub\LFSintegrity\LFSintegrity" ... Arghhh

Gotta run - half hour late if I leave soon ....
 
SEE :: github.com/Defragster/LFSintegrity

What a PIA - ghub is horrid ....


Unless you can test other wise - it is ready to ship

Changes are safe and minimal.

It is a 50% solution where it always incurs traverse over head - I am seeing 20ms for the DirWalk by LFS.

Same name is in the .git folder - had to DO thendelete .git folder and REDO and pointed to folder and it made a new subfolder so on my machine the sources are in :: "C:\Users\defragster\Documents\GitHub\LFSintegrity\LFSintegrity" ... Arghhh

Gotta run - half hour late if I leave soon ....
Thanks... But I don't see it... It probably created it as a Private Repository ;) Yes Github is fun ;) (NOT)...

FYI - I created a new branch on my repository called LFSintegrity.
Which has everything up to:
screenshot.jpg

Now to grab it and add that version/branch to your project you can maybe do something like:
Be in github app on the project: and do the CTRL+` to open command window (or select the command from the repository menu:

In that window you can probably do it like:

First I check to see if I have a remote setup for you (or in this case for me)

Code:
D:\GitHub\LittleFS>git remote -v
defragster      https://github.com/defragster/LittleFS.git (fetch)
defragster      https://github.com/defragster/LittleFS.git (push)
origin  https://github.com/KurtE/LittleFS.git (fetch)
origin  https://github.com/KurtE/LittleFS.git (push)
upstream        https://github.com/PaulStoffregen/LittleFS.git (fetch)
upstream        https://github.com/PaulStoffregen/LittleFS.git (push)

D:\GitHub\LittleFS>
Code:
git remote add kurte https://github.com/KurtE/LittleFS.git
git fetch kurte
git checkout kurte/LFSintegrity
git push origin LFSintegrity --force
Note some of these commands you can probably do from the github windows app.

Now assuming that works for you and you have the branch and you continue to make changes...

I then would typically sync mine up from time to time, by:
Code:
git checkout LFSintegrity
git fetch defragster
git reset --hard defragster/LFSintegrity
git push origin LFSintegrity --force
This is similar to what I do with @mjs513 when we are working on display libraries or USBHost...
Where if Mike logically owns some branch, I will create a new branch based off of his and then push up PRs to his branch.
 
Made PUBLIC - not sure why it defaulted to PRIVATE

Just back - only missed by a few minutes ....


As little as I pretend to understand about github through WINDOWS GUI - the CMDLine stuff has way more power and more cryptic so I've not endeavored to learn that.

The GUI does present ways to do stuff - I did the move to PUBLIC on the WEB DANGER zone.

GUI is not a lot of help. I pointed to above folder and rather than USE IT - it made a folder in that folder :?

And to get one I could edit separate from Paul's as noted I have to make another clone (fork?) in a uniquely named folder. Instead of LittleFS it is LittleFS_qF ... so now I have 3 copies ...

And I never work in the github copy of the files in DOCS - but a private folder else where then manually move stuff to github for changes because github can just ZAP anything in its folders ... (NOT) fun indeed

> If I hadn't been rushing out the door I should have started it with PJRC current code so the DIFF's were telling.

@KurtE - give it a code review and some testing - I think it is ready for PR given and feedback from @Paul.

... lunch just walked in the door :)
 
For many years they always defaulted as public as you were required to have a paying account to have private ones, and then something like within the last year or so they now allow us to have some private ones....

So I did clone your new project and then used WinMerge to verify that my new branch matched the new project...

I then rebuilt the MTP-test program with it. And the 333kb file made it over no problem. So I then tried an image > 3MB and it also went no problem.
I then double clicked on the resulting file and the image file opened up in Photoshop :D
screenshot.jpg

So it would be great if this got into the next beta! :D :D :D
 
For many years they always defaulted as public as you were required to have a paying account to have private ones, and then something like within the last year or so they now allow us to have some private ones....

So I did clone your new project and then used WinMerge to verify that my new branch matched the new project...

I then rebuilt the MTP-test program with it. And the 333kb file made it over no problem. So I then tried an image > 3MB and it also went no problem.
I then double clicked on the resulting file and the image file opened up in Photoshop :D
View attachment 22826

So it would be great if this got into the next beta! :D :D :D

Indeed - having it go in the Next Beta would be a win win kinda thing. :D :D :D

Awesome it made MTP work!

> I still worry about all those wait(){ yield() }; calls and the chance it gives 'Event' code to interfere. One thing would be loop() doing fileIO and wait()'ing on a write() - only to have yield() process a Serial event where incoming data is pushed to the same disk doing the wait(). The MEdia may stall the format to do the write - but that would re-enter LFS? Respecting preFormat will minimize that and the having a way to myfs.formatUnused() gives the user a way to prep the media for best performance when needed.

Those changes were an evolution - from first idea seeing the problem - then half into it finding the LFS_Traverse (ignoring it a week until your feedback goaded me) - that ended up in the final simple solution after seeing that large media give it a real stall when combined as such.

There is room for improvement in future, but for now skipping uneeded formats it good for life of the FLASH and the speed in use. And the ability to preFormat the unused dirty blocks makes it ready to run for logging purposes without destroying the image with a LLformat, or suffering as the Media gets dirtied.

I only tested so far on the one 64MB QSPI - will start it up on the 64MB SPI board with a second T_4.1.

I could remove the DEBUGCF SPEW. I could also add an AUTO intermittent myfs.formatUnused() switch to happen during the iterations so longer runs would prove all is well. ...
 
Will leave the Blk Fmt SPEW on until more feedback.

Updated examples/LFSintegrity/LFSintegrity.ino

enter 'a' for a one in 5 loop() 20 block AUTO reFormat :: if ( bAutoFormat && !(lCnt%5) ) res = myfs.formatUnused( 20, res );

FMT Spew looks like this:
Code:
[B]FMT 4708,FMT 4709,FMT 4710,FMT 4711,FMT 4712,FMT 4713,FMT 4715,FMT 4716,	#8[/B]
FMT 4717,FMT 5419,FMT 5426,FMT 5434,FMT 5442,FMT 5446,FMT 5448,FMT 5450,	#16
FMT 5452,FMT 5454,FMT 5457,FMT 5460,:: /Q_file.txt  QSPI_DISK +++ Add +++ [sz 55296 add 18432] ++ Q   Verify /Q_file.txt 73728B  @KB/sec 2536.75 
:: /R_file.txt  QSPI_DISK +++ Add +++ [sz 58368 add 19456] ++ R   Verify /R_file.txt 77824B  @KB/sec 2527.90 
:: /S_file.txt  QSPI_DISK +++ Add +++ [sz 61440 add 20480] ++ S   Verify /S_file.txt 81920B  @KB/sec 2539.76 
:: /T_file.txt  QSPI_DISK +++ Add +++ [sz 64512 add 21504] ++ T   Verify /T_file.txt 86016B  @KB/sec 2554.07 
:: /U_file.txt  QSPI_DISK +++ Add +++ [sz 67584 add 22528] ++ U   Verify /U_file.txt 90112B  @KB/sec 2557.97 
[B]FMT 5463,FMT 5466,FMT 5470,FMT 5474,FMT 5478,FMT 5482,FMT 5487,FMT 5492,	#8[/B]
FMT 5497,FMT 5502,FMT 5508,FMT 5514,FMT 5520,FMT 5526,:: /V_file.txt  QSPI_DISK +++ Add +++ [sz 70656 add 23552] ++ V   Verify /V_file.txt 94208B  @KB/sec 2547.61 
:: /W_file.txt  QSPI_DISK +++ Add +++ [sz 73728 add 24576] ++ W   Verify /W_file.txt 98304B  @KB/sec 2566.95 
:: /X_file.txt  QSPI_DISK +++ Add +++ [sz 76800 add 25600] ++ X   Verify /X_file.txt 102400B  @KB/sec 2577.79 
:: /Y_file.txt  QSPI_DISK +++ Add +++ [sz 79872 add 26624] ++ Y   Verify /Y_file.txt 106496B  @KB/sec 2556.68 
:: /Z_file.txt  QSPI_DISK +++ Add +++ [sz 82944 add 27648] ++ Z   Verify /Z_file.txt 110592B  @KB/sec 2546.21 
[B]FMT 5533,FMT 5540,FMT 5547,FMT 5554,FMT 5562,[/B]:: /A_file.txt  QSPI_DISK +++ Add +++ [sz 36864 add 12288] ++ A   Verify /A_file.txt 49152B  @KB/sec 2528.00

That shows three sets of formatUnused() - the first did 16, then the next only found 14 ( to the end of media ) - and was SLOW finding all blocks formatted, then the third found only 5, and was slow as well.

When it doesn't find the requested number it does not start over at Block Zero - until it reaches the end ??? , it also does not return Zero saying start at the beginning as it might? Should??? Hard to tell where LFS will look next - or where the next batch of dirty blocks may appear?
<EDIT> :: It does do this - the code is there - the disk was just CLEAN with all unused preFormatted - so there was a long delay reading all the formatted blocks. I put a "// TODO?:" in the code - in future the bitmap if saved and updated when LFS formats as noted before could work around the lengthy time lost doing the LFS_Traverse and blockIsBlank() checking - when a bitmap of known formatted is kept as I was doing before. That would be more invasive and critical to get just right. This change while unbelievably impressive - is safe :)

But the next iterations started a DELETE pass and those blocks were DIRTY in front of 'last formatted' return value it so it started on them?

I see the ++ Add ++ could report the Bytes/sec write time as well - that wouls show when it is having to do inline format verus using preFormatted blocks.
 
Last edited:
LFSintegrity/LFSintegrity.ino UPDATED

The +++ Add now reports Byte write speed:
Code:
:: /S_file.txt  QSPI_DISK +++ Add [sz 20480 add 20480] @KB/sec 379.77 ++ S   Verify /S_file.txt 40960B  @KB/sec 2540.63 
:: /T_file.txt  QSPI_DISK +++ Add [sz 21504 add 21504] @KB/sec 361.73 ++ T   Verify /T_file.txt 43008B  @KB/sec 2541.54 
:: /U_file.txt  QSPI_DISK +++ Add [sz 22528 add 22528] @KB/sec 353.89 ++ U   Verify /U_file.txt 45056B  @KB/sec 2530.53 
:: /V_file.txt  QSPI_DISK +++ Add [sz 23552 add 23552] @KB/sec 339.12 ++ V   Verify /V_file.txt 47104B  @KB/sec 2531.79

Turning of the 'a'uto reFormat after polluting the media with a Big half disk file and removing is and the 2MB file that was there ( though iterations alone do it before too long )

Then fresh blocks ran out:
Code:
[ 28.03 M](0.01224 M elap) Awaiting input 0123456789RdchkFqvplmusSBbyYxfa+-? loops left 0 >5
:: /V_file.txt  QSPI_DISK +++ Add [sz 23552 add 23552] @KB/sec 337.99 ++ V   Verify /V_file.txt 47104B  @KB/sec 2529.21 
:: /W_file.txt  QSPI_DISK +++ Add [sz 24576 add 24576] @KB/sec 390.13 ++ W   Verify /W_file.txt 49152B  @KB/sec 2537.79 
:: /X_file.txt  QSPI_DISK +++ Add [sz 25600 add 25600] @KB/sec 363.32 ++ X   Verify /X_file.txt 51200B  @KB/sec 2538.55 
:: /Y_file.txt  QSPI_DISK +++ Add [sz 26624 add 26624] @KB/sec 358.97 ++ Y   Verify /Y_file.txt 53248B  @KB/sec 2527.79 
:: /Z_file.txt  QSPI_DISK +++ Add [sz 27648 add 27648] @KB/sec 343.05 ++ Z   Verify /Z_file.txt 55296B  @KB/sec 2528.86 
:: /A_file.txt  QSPI_DISK +++ Add [sz 12288 add 12288] @KB/sec 368.58 ++ A   Verify /A_file.txt 24576B  @KB/sec 2442.46 
:: /B_file.txt  QSPI_DISK +++ Add [sz 6144 add 3072] @KB/sec 212.18 ++ B   Verify /B_file.txt 9216B  @KB/sec 2182.33 
:: /C_file.txt  QSPI_DISK +++ Add [sz 8192 add 4096] @KB/sec 73.80 ++ C   Verify /C_file.txt 12288B  @KB/sec 2260.49 
:: /D_file.txt  QSPI_DISK +++ Add [sz 10240 add 5120] @KB/sec 49.48 ++ D   Verify /D_file.txt 15360B  @KB/sec 2328.68 
:: /E_file.txt  QSPI_DISK +++ Add [sz 12288 add 6144] @KB/sec 39.32 ++ E   Verify /E_file.txt 18432B  @KB/sec 2489.13 
:: /F_file.txt  QSPI_DISK +++ Add [sz 14336 add 7168] @KB/sec 48.88 ++ F   Verify /F_file.txt 21504B  @KB/sec 2491.77 
:: /G_file.txt  QSPI_DISK +++ Add [sz 16384 add 8192] @KB/sec 57.82 ++ G   Verify /G_file.txt 24576B  @KB/sec 2500.86 
:: /H_file.txt  QSPI_DISK +++ Add [sz 18432 add 9216] @KB/sec 61.89 ++ H   Verify /H_file.txt 27648B  @KB/sec 2509.58 
...

No signs of corruption in any case yet.

Started an 'f' FULL myfs.unusedFormat( 0,0);
<edit 'f'ormat done> :: NO DATA LOSS
LOOK - the "(10.75665 M elap)" time is right - and the disk directory data is still GOOD and accurate to continue 'iterations' back as FULL SPEED!

Code:
[ 29.56 M](0.00021 M elap) Awaiting input 0123456789RdchkFqvplmusSBbyYxfa+-? loops left 0 >
 myfs.formatUnused( 0 ) ...
FMT 2,FMT 3,FMT 4,FMT 5,FMT 6,FMT 7,FMT 8,FMT 9,	#8
FMT 10,FMT 11,FMT 12,FMT 13,FMT 14,FMT 15,FMT 16,FMT 17,	#16
FMT 18,FMT 19,FMT 20,FMT 21,FMT 22,FMT 23,FMT 24,FMT 25,	#24

...

FMT 16354,FMT 16355,FMT 16356,FMT 16357,FMT 16358,FMT 16359,FMT 16360,FMT 16361,	#16240
FMT 16362,FMT 16363,FMT 16364,FMT 16365,FMT 16366,FMT 16367,FMT 16368,FMT 16369,	#16248
FMT 16370,FMT 16371,FMT 16372,FMT 16373,FMT 16374,FMT 16375,FMT 16376,FMT 16377,	#16256
FMT 16378,FMT 16379,FMT 16380,FMT 16381,FMT 16382,FMT 16383,
	 formatUnused :: Done Formatting Low Level in 645399166 us.

[ 40.37 M](10.75665 M elap) Awaiting input 0123456789RdchkFqvplmusSBbyYxfa+-? loops left 0 > d
printDirectory QSPI_DISK
--------------
FILE	A_file.txt		12288
FILE	B_file.txt		6144
FILE	C_file.txt		8192
FILE	D_file.txt		10240
FILE	E_file.txt		12288
FILE	F_file.txt		7168
FILE	G_file.txt		8192
FILE	H_file.txt		9216
FILE	I_file.txt		10240
FILE	J_file.txt		11264
FILE	K_file.txt		12288
FILE	L_file.txt		13312
FILE	M_file.txt		14336
FILE	N_file.txt		15360
FILE	O_file.txt		16384
FILE	P_file.txt		17408
FILE	Q_file.txt		18432
FILE	R_file.txt		19456
FILE	S_file.txt		20480
FILE	T_file.txt		21504
FILE	U_file.txt		22528
FILE	V_file.txt		23552
FILE	W_file.txt		24576
FILE	X_file.txt		25600
FILE	Y_file.txt		26624
FILE	Z_file.txt		27648

 0 dirs with 26 files of Size 414720 Bytes
 Total 26 files of Size 414720 Bytes
Bytes Used: 499712, Bytes Total:67108864

UPDATE on SPI 64MB T_4.1. The SPI write throughput is not much slower - but the blockIsBlanck() checks are PAINFUL!
See the WRITE speed not much lower thatn QSPI - but the READ speed is closer to half.
:: /M_file.txt SPI_DISK +++ Add [sz 14336 add 14336] @KB/sec 61.28 ++ M Verify /M_file.txt 28672B @KB/sec 1307.79
:: /N_file.txt SPI_DISK +++ Add [sz 15360 add 15360] @KB/sec 242.96 ++ N Verify /N_file.txt 30720B @KB/sec 1306.68
FMT 2479,FMT 2483,FMT 2487,FMT 2491,FMT 2495,:: /O_file.txt SPI_DISK +++ Add [sz 16384 add 16384] @KB/sec 290.97 ++ O Verify /O_file.txt 32768B @KB/sec 1326.21
:: /P_file.txt SPI_DISK +++ Add [sz 17408 add 17408] @KB/sec 276.87 ++ P Verify /P_file.txt 34816B @KB/sec 1328.04
:: /Q_file.txt SPI_DISK +++ Add [sz 18432 add 18432] @KB/sec 261.86 ++ Q Verify /Q_file.txt 36864B @KB/sec 1293.56
:: /R_file.txt SPI_DISK +++ Add [sz 19456 add 19456] @KB/sec 253.08 ++ R Verify /R_file.txt 38912B @KB/sec 1293.49
:: /S_file.txt SPI_DISK +++ Add [sz 20480 add 20480] @KB/sec 293.29 ++ S Verify /S_file.txt 40960B @KB/sec 1290.24
FMT 2500,FMT 2505,FMT 2510,FMT 2515,FMT 2521,:: /T_file.txt SPI_DISK +++ Add [sz 21504 add 21504] @KB/sec 281.71 ++ T Verify /T_file.txt 43008B @KB/sec 1290.33
:: /U_file.txt SPI_DISK +++ Add [sz 22528 add 22528] @KB/sec 268.39 ++ U Verify /U_file.txt 45056B @KB/sec 1259.64

>> USING defragster LFSintegrity on github - with no reFormat on formatted media
Here is QSPI 2MB write and delete with verify read on 64MB Flash:
Code:
Start Big write of 2048000 Bytes........................
Big write /0_2MBfile.txt took  5.00 Sec for 2045952 Bytes : file3.size()=2045952
	Big write KBytes per second 409.36 

Bytes Used: 2568192, Bytes Total:67108864

[ 54.70 M](0.08343 M elap) Awaiting input 0123456789RdchkFqvplmusSBbyYxfa+-? loops left 0 >
Delete with read verify all #bigfile's
	Verify /0_2MBfile.txt bytes 2045952 : ..................................................	GOOD! >>  bytes 2045952
	Big read&compare KBytes per second 2484.52

And the same for SPI 2MB on 64MB chip [ the WRITE is 75% and the READ is 50% of speed ]:
Code:
Start Big write of 2048000 Bytes........................
Big write /0_2MBfile.txt took  6.44 Sec for 2045952 Bytes : file3.size()=2045952
	Big write KBytes per second 317.65 

Bytes Used: 2891776, Bytes Total:67108864

[ 14.98 M](0.10837 M elap) Awaiting input 0123456789RdchkFqvplmusSBbyYxfa+-? loops left 0 >
Delete with read verify all #bigfile's
	Verify /0_2MBfile.txt bytes 2045952 : ..................................................	GOOD! >>  bytes 2045952
	Big read&compare KBytes per second 1236.57

<edit>
Add ref for PSRAM _RAM DISK speed:
Code:
:: /7_dir/L_file.txt  RAM_DISK +++ Add [sz 35328 add 11776] @KB/sec 1931.12 ++ L   Verify /7_dir/L_file.txt 47104B  @KB/sec 2496.37 
:: /7_dir/M_file.txt  RAM_DISK +++ Add [sz 38400 add 12800] @KB/sec 1490.97 ++ M   Verify /7_dir/M_file.txt 51200B  @KB/sec 2507.22 

Start Big write of 2048000 Bytes........................
Big write /0_2MBfile.txt took  0.47 Sec for 2045952 Bytes : file3.size()=2045952
	Big write KBytes per second 4308.58 

Bytes Used: 7133184, Bytes Total:16777216

[102.80 M](0.00845 M elap) Awaiting input 0123456789RdchkFqvplmusSBbyYxfa+-? loops left 0 >
Delete with read verify all #bigfile's
	Verify /0_2MBfile.txt bytes 2045952 : ..................................................	GOOD! >>  bytes 2045952
	Big read&compare KBytes per second 2421.30
 
Last edited:
@defragster - looks like it is working great.

I have just been sort of dink-ing around and I think the memory breakout board I did with the Flash works ok on SPI. Although probably should rev it and make it nicer like optional PU on CS.

Also additional marking to know what pins would be need to hook up to FlexSPIO2 pins...

@all @Paul - compatibility question?
I also spent some time playing with my simple version of write a large file, where you can type in: size write_size file_name
And it tries to create it.

I also goofed and left it setup to do RAM and at different times in my simple test I hung...
Code:
    while (file_size) {
      elapsedMicros em = 0;
      int output_count = (file_size > write_size) ? write_size : file_size;
      int  bytes_output = f.write(buffer, output_count);
      Serial.write('.');
      if (output_count != bytes_output) {
        Serial.printf("  %d - %d - %u\n", output_count, bytes_output, (uint32_t)em);
        }
      }
      file_size -= bytes_output;
      if (++write_counter == 80) {
        Serial.printf("\n%08u:", file_size);
        write_counter = 0;
      }
    }
    elapsedMicros emClose = 0;
    f.close();
    Serial.printf("\n*** Closed: %u %u\n\n", (uint32_t)emTotal, (uint32_t)emClose);
  }
Yes quick and dirty...
The problem was: int bytes_output = f.write(buffer, output_count);
The bytes_output returned was -28

So somehow I wrote -28 bytes? Turns out the LittleFS when it errors returns a negative number in this case -28 is
Code:
LFS_ERR_NOSPC       = -28,  // No space left on device
Which is in lfs.h ...

Question is, should we be returning this in these calls?
From Arduino docs: https://www.arduino.cc/en/Reference/FileWrite
The write returns a byte? Which appears to be wrong.

The SD library has: virtual size_t write(const void *buf, size_t size) {
Dito for FS.h

And my impression of c++ standards for size_t is unsigned?

Just wondering what the right answer is here?
 
Back
Top