Teensyduino File System Integration, including MTP and MSC

Yes, Frank is correct, at least a CRC should be used. I believe Defraster's testing involves a full binary compare of the copied files against the originals.

But the (old) problem of going too slowly (under 85 kbytes/sec) was unmistakable. It was never about playing an audio file (which was plenty fast enough). That particular issue happened while copying from Windows to Teensy. While trying to copy the file, after some time Windows would give up and show a dialog box error. After that error, nothing would work at all using Teensy's files from Windows until the USB was disconnected and reconnected.

I believe we now have all the LittleFS classes able to sustain over 85 kbytes/sec speed, which involved using 32K or 64K erase sizes on the slow NOR flash chips. The downside is even a tiny file allocates at least 32K or 64K, so copying lots of them can quickly fill up the chip with wasted space. I'm pretty sure that's a problem we're just going to have to accept on slow NOR flash chips, as 4K sectors are just too slow.

The ZLP issues (hopefully now all fixed) would manifest as files having less data than they should, or some files not copied at all. I believe much of the verification for those cases used numbers for the filename, where you could easily see if the number of bytes matched the filename.

Yes, byte by byte compare after the fact - with human readable index# blocks to allow one to easily see where pieces went missing or where they came from as early failures were files holding data from other places.

File naming as size worked well too - and the DIR name with .#files has value ... But, MakeFiles usable Verify Progress here off another day it seems ...
 
I tried but could not reproduce this issue.

First I renamed a file on the SD card to "06abc678901234567890123456789.jpg". Then I copied to the Windows desktop, then copied it to the SPI flash chip. I rebooted Teensy. It was still there on both with all 33 chars in the filename. Then I deleted the file from the desktop, and copied from the SPI flash to desktop again. Still all 33 chars.

Is there any chance your flash chip got formatted with a copy of LittleFS set to only 24 char filename limit? I believe LittleFS uses the smaller of its config and whatever was used at the time the media was formatted, so even if you've changed the source code, you'll probably still see the smaller size until the media is reformatted.

Don't think so - I usually do a format (quickFormat) from explorer prior to starting a new series of tests. LFS filename length has been set to 39 for a while. I am attaching a zip of one of the files - I also posted the 2 files in question in the Test directory of the repository.

EDIT: As a test I used your filename for a jpg and it copied. I also changed the name on one of the problem files to yours and it worked as well.

Followup: One filename I had to reduce the lenght to fit 39 characters so that is mystery is solved - i just can't count. The second file, the one I just posted is more than 39 characters as well - off by about 7-8 characters. Mystery solved. As I am typing this I think I know what happened - not going to say because its embarrassing :)

So guess the question is should we increase the Filename length in LFS or maybe put some test in MTP to automatically shorten the name or give warning that has to be shortened.
 

Attachments

  • T_4.1.zip
    427 bytes · Views: 28
Last edited:
On a different integration subject:

Suppose, I setup a Teensy with lets say an ILI9341 display (could be music), that I have as a electronic picture frame or some such thing. Suppose it cycles through the pictures contained within some directory on some storage.
And 95 percent of the time it is running off of Battery or 5v wall wart...

And only occasionally I this device in to USB port, to change the set of image files contained on the storage using MTP (delete some, add some new ones)...
And then I unplug it from USB...

There are probably many questions, options to think about, like:

Can the sketch detect when the USB Port is connected, like: if (MTP) { ok we have mtp }
Likewise when disconnected?

When/What code should handle the init and when. That is does the sketch need to do all of the initialization up front, or only when the usb is connected? All of our examples do everything up front.

Buffers and the like: maybe I want to use the DMAMEM for SendObject or the like when connected. When not connected, maybe I use it as a secondary frame buffer for the display that I use for transitions from one picture to another..

Code may go into different display mode when usb connected...

EDIT: Can the sketch control if MTP works or not. Example suppose I wish to have the screen change, when USB is plugged in and prompt the user for a password, as I would not like my dogs (Laik and Annie) to change which pictures are shown. As maybe Laik thinks I have too many pictures of Annie on it and not enough of him :)

...

Probably lots of other issues/opportunities ...

Guessing won't know, until try a skeleton sketch.

Thoughts

Some good questions. Could MTP.loop() have a return value indicating status? It is always called and state of it could be tracked easily.

Would be nice to know when Serial is really .available(). Added code to loop() and it seems about 5 seconds in current setup:
Code:
Serial online : 5010
loop:5744 CMD: 1004(GET_STORAGE_IDS)l: 12 T:3
5744 RESP:2001(RSP:OK)l: 12 T:3
loop:5744 CMD: 1005(GET_STORAGE_INFO)l: 16 T:4 : 10001
65537 0 name:MakeFiles 2203
5748 RESP:2001(RSP:OK)l: 12 T:4
loop:5748 CMD: 9801(GET_OBJECT_PROPS_SUPPORTED)l: 16 T:5 : 3000
5748 RESP:2001(RSP:OK)l: 12 T:5
loop:5748 CMD: 9801(GET_OBJECT_PROPS_SUPPORTED)l: 16 T:6 : 3001
5748 RESP:2001(RSP:OK)l: 12 T:6
Serial online : 6011
...
But {if(Serial)} true long before - even in setup but the first check on the second it prints to SerMon is 5010 millis.

@Kurt - did I see a buffer output to RAM prior to SerMon online to store early print? Seemed I saw that once and it seemed cool and I didn't pick it up.

Behind on reading most passing this AM - and going offline again ...
 
Some good questions. Could MTP.loop() have a return value indicating status? It is always called and state of it could be tracked easily.

Maybe, but I am guessing !MTP would be more consistent.

Would be nice to know when Serial is really .available(). Added code to loop() and it seems about 5 seconds in current setup:
Yes we had running into this for a long time with SEREMU, it would not start working until MTP was happy...

It was better with USB Hardware Serial but currently not an official supported setup... Probably should be... It has it's own issues.

@Kurt - did I see a buffer output to RAM prior to SerMon online to store early print? Seemed I saw that once and it seemed cool and I didn't pick it up.
Yes it is currently built into the SD_Program_SPI_QSPI_MTP-logger sketch. A real hack:
Code:
// Quick and Dirty memory Stream...
class RAMStream : public Stream {
public:
  // overrides for Stream
  virtual int available() { return (tail_ - head_); }
  virtual int read() { return (tail_ != head_) ? buffer_[head_++] : -1; }
  virtual int peek() { return (tail_ != head_) ? buffer_[head_] : -1; }

  // overrides for Print
  virtual size_t write(uint8_t b) {
    if (tail_ < sizeof(buffer_)) {
      buffer_[tail_++] = b;
      return 1;
    }
    return 0;
  }

  enum { BUFFER_SIZE = 32768 };
  uint8_t buffer_[BUFFER_SIZE];
  uint32_t head_ = 0;
  uint32_t tail_ = 0;
};

Then in setup at the start I tell MTP to use this as the stream to print to:
Code:
RAMStream rstream;
  // start up MTPD early which will if asked tell the MTP
  // host that we are busy, until we have finished setting
  // up...
  MTP.PrintStream(&rstream); // Setup which stream to use...


later on I do:
Code:
  // Open serial communications and wait for port to open:
  while (!DBGSerial && millis() < 5000) {
    // wait for serial port to connect.
  }

  // set to real stream
  MTP.PrintStream(&DBGSerial); // Setup which stream to use...
  int ch;
  while ((ch = rstream.read()) != -1)
    DBGSerial.write(ch);
Which then echoes everything I received while Serial was not available... At least at the time it was the only way I found to be able to debug the startup code, short of, using Hardware serial port...
 
Maybe, but I am guessing !MTP would be more consistent.

Yes we had running into this for a long time with SEREMU, it would not start working until MTP was happy...

It was better with USB Hardware Serial but currently not an official supported setup... Probably should be... It has it's own issues.

Thanks for the RAM Stream code link - saw it in passing ... and a lot has been passing by.

Yes, if ( !MTP.loop() ), or anything like that bool or other 'status' value as helpful could be useful. Not sure if there is ever an error state that Teensy might Auto Reset the connection where more detailed integer value might be useful? Where <0 error and 0==no connect, >0 good state.

Wondered the other day if tying MTP to DualSerial might avoid grief of Serial on that connect path?
 
So guess the question is should we increase the Filename length in LFS or maybe put some test in MTP to automatically shorten the name or give warning that has to be shortened.

I'm not familiar with how LittleFS actually stores the filename. Do you know? Does increasing to 256 chars cost much?
 
Can the sketch detect when the USB Port is connected, like: if (MTP) { ok we have mtp }
Likewise when disconnected?

Seems reasonable to support a bool operator like the other USB device classes.

It should probably be based on whether we've received OpenSession and not CloseSession. Windows sends OpenSession several seconds after USB enumeration, but on MacOS we only get it once the Android File Transfer program has been run.
 
I'm not familiar with how LittleFS actually stores the filename. Do you know? Does increasing to 256 chars cost much?

Quick search shows it isn't used often "lfs.h" - just one copy in "info struct" for each file:
Code:
// Maximum name size in bytes, may be redefined to reduce the size of the
// info struct. Limited to <= 1022. Stored in superblock and must be
// respected by other littlefs drivers.
#ifndef LFS_NAME_MAX
[B]//#define LFS_NAME_MAX 255
#define LFS_NAME_MAX 39
[/B]#endif

And by design IIRC there are two copies of each block?
Code:
// File info structure
struct lfs_info {
    // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
    uint8_t type;

    // Size of the file, only valid for REG files. Limited to 32-bits.
    lfs_size_t size;

[B]    // Name of the file stored as a null-terminated string. Limited to
    // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to
    // reduce RAM. LFS_NAME_MAX is stored in superblock and must be
    // respected by other littlefs drivers.
    char name[LFS_NAME_MAX+1];[/B]
};

With few files it won't hurt much. On larger media it won't add up to much?

Not sure what happens to OLD LFS images? verbiage above suggests it is STORED in the 'superblock'? Not sure where that is or if it means the fixed known value is vased for the image?

And:
Code:
C:\T_Drive\arduino-1.8.19\hardware\teensy\avr\libraries\LittleFS\src\littlefs\lfs_util.h:
   10  
   11  // Teensy specific use...
   12: #define LFS_NAME_MAX 39
 
Here is a real basic version of the spitftbitmap sketch that was changed to look at sd card for *.bmp files and display them...
Note: I have 3 Annie Pictures and 1 Laik on it :lol:

Code:
//=============================================================================
// Simple bitamp display program with MTP support added.
//=============================================================================

/***************************************************
  Some of this code originated with the spitftbitmap.ino sketch
  that is part of the Adafruit_ILI9341 library. 
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

#include <ILI9341_t3n.h>
#include <SPI.h>
#include <SD.h>
#include <MTP_Teensy.h>

#define TFT_DC  9
#define TFT_CS 10
#define TFT_RST 8
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST);

#define SD_CS BUILTIN_SDCARD  // Works on T_3.6 and T_4.1 ...
//#define SD_CS 6  // Works on SPI with this CS pin

File rootFile;
elapsedMillis emDisplayed;
#define DISPLAY_IMAGES_TIME 5000

void setup(void) {
  // mandatory to begin the MTP session.
  MTP.begin();

  // Keep the SD card inactive while working the display.
  pinMode(SD_CS, INPUT_PULLUP);
  delay(200);

  tft.begin();
  tft.fillScreen(ILI9341_BLUE);

  Serial.begin(9600);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.println(F("Waiting for Arduino Serial Monitor..."));
  while (!Serial) {
    if (millis() > 3000) break;
  }

  Serial.print(F("Initializing SD card..."));
  tft.println(F("Init SD card..."));
  while (!SD.begin(SD_CS)) {
    Serial.println(F("failed to access SD card!"));
    tft.println(F("failed to access SD card!"));
    delay(2000);
  }
  MTP.addFilesystem(SD, "SD Card");

  rootFile = SD.open("/");

  Serial.println("OK!");
  emDisplayed = DISPLAY_IMAGES_TIME; 
}

void loop() {
  MTP.loop();
  if (emDisplayed < DISPLAY_IMAGES_TIME) return; 
  bool did_rewind = false;

  Serial.println("Loop looking for image file");
  
  File imageFile;
  for (;;) {
    imageFile = rootFile.openNextFile();
    if (!imageFile) {
      if (did_rewind) break; // only go around once. 
      rootFile.rewindDirectory();
      imageFile = rootFile.openNextFile();
      did_rewind = true;
    }
    // maybe should check file name quick and dirty
    const char *name = imageFile.name();
    uint8_t name_len = strlen(name);
    if (!name) continue;
    if (stricmp(&name[name_len-4], ".bmp") == 0) break;
  }
  if (imageFile) {
    bmpDraw(imageFile, imageFile.name(), 0, 0);
  } else {
    tft.fillScreen(ILI9341_GREEN);
    tft.setTextColor(ILI9341_WHITE);
    tft.setTextSize(2);
    tft.println(F("No Files Found"));
  }
  emDisplayed = 0;
}

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance for tiny AVR chips.

// Larger buffers are slightly more efficient, but if
// the buffer is too large, extra data is read unnecessarily.
// For example, if the image is 240 pixels wide, a 100
// pixel buffer will read 3 groups of 100 pixels.  The
// last 60 pixels from the 3rd read may not be used.

#define BUFFPIXEL 80


//===========================================================
// Try Draw using writeRect
void bmpDraw(File &bmpFile, const char *filename, uint8_t x, uint16_t y) {

//  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint16_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  uint16_t awColors[320];  // hold colors for one row at a time...

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

// Open requested file on SD card
//  if (!(bmpFile = SD.open(filename))) {
//    Serial.print(F("File not found"));
//    return;
//  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            awColors[col] = tft.color565(r,g,b);
          } // end pixel
          tft.writeRect(0, row, w, 1, awColors);
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}



// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}
May push it up as example at some point...
And yes would be good to extend for other image files, and maybe resizing, but at least it is something different

screenshot.jpg
 
Here is a real basic version of the spitftbitmap sketch that was changed to look at sd card for *.bmp files and display them...
Note: I have 3 Annie Pictures and 1 Laik on it :lol:

Code:
//=============================================================================
// Simple bitamp display program with MTP support added.
//=============================================================================

/***************************************************
  Some of this code originated with the spitftbitmap.ino sketch
  that is part of the Adafruit_ILI9341 library. 
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

#include <ILI9341_t3n.h>
#include <SPI.h>
#include <SD.h>
#include <MTP_Teensy.h>

#define TFT_DC  9
#define TFT_CS 10
#define TFT_RST 8
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST);

#define SD_CS BUILTIN_SDCARD  // Works on T_3.6 and T_4.1 ...
//#define SD_CS 6  // Works on SPI with this CS pin

File rootFile;
elapsedMillis emDisplayed;
#define DISPLAY_IMAGES_TIME 5000

void setup(void) {
  // mandatory to begin the MTP session.
  MTP.begin();

  // Keep the SD card inactive while working the display.
  pinMode(SD_CS, INPUT_PULLUP);
  delay(200);

  tft.begin();
  tft.fillScreen(ILI9341_BLUE);

  Serial.begin(9600);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.println(F("Waiting for Arduino Serial Monitor..."));
  while (!Serial) {
    if (millis() > 3000) break;
  }

  Serial.print(F("Initializing SD card..."));
  tft.println(F("Init SD card..."));
  while (!SD.begin(SD_CS)) {
    Serial.println(F("failed to access SD card!"));
    tft.println(F("failed to access SD card!"));
    delay(2000);
  }
  MTP.addFilesystem(SD, "SD Card");

  rootFile = SD.open("/");

  Serial.println("OK!");
  emDisplayed = DISPLAY_IMAGES_TIME; 
}

void loop() {
  MTP.loop();
  if (emDisplayed < DISPLAY_IMAGES_TIME) return; 
  bool did_rewind = false;

  Serial.println("Loop looking for image file");
  
  File imageFile;
  for (;;) {
    imageFile = rootFile.openNextFile();
    if (!imageFile) {
      if (did_rewind) break; // only go around once. 
      rootFile.rewindDirectory();
      imageFile = rootFile.openNextFile();
      did_rewind = true;
    }
    // maybe should check file name quick and dirty
    const char *name = imageFile.name();
    uint8_t name_len = strlen(name);
    if (!name) continue;
    if (stricmp(&name[name_len-4], ".bmp") == 0) break;
  }
  if (imageFile) {
    bmpDraw(imageFile, imageFile.name(), 0, 0);
  } else {
    tft.fillScreen(ILI9341_GREEN);
    tft.setTextColor(ILI9341_WHITE);
    tft.setTextSize(2);
    tft.println(F("No Files Found"));
  }
  emDisplayed = 0;
}

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance for tiny AVR chips.

// Larger buffers are slightly more efficient, but if
// the buffer is too large, extra data is read unnecessarily.
// For example, if the image is 240 pixels wide, a 100
// pixel buffer will read 3 groups of 100 pixels.  The
// last 60 pixels from the 3rd read may not be used.

#define BUFFPIXEL 80


//===========================================================
// Try Draw using writeRect
void bmpDraw(File &bmpFile, const char *filename, uint8_t x, uint16_t y) {

//  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint16_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  uint16_t awColors[320];  // hold colors for one row at a time...

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

// Open requested file on SD card
//  if (!(bmpFile = SD.open(filename))) {
//    Serial.print(F("File not found"));
//    return;
//  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            awColors[col] = tft.color565(r,g,b);
          } // end pixel
          tft.writeRect(0, row, w, 1, awColors);
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}



// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}
May push it up as example at some point...
And yes would be good to extend for other image files, and maybe resizing, but at least it is something different

Very cool now can play with JPEG library and the new one I found for PNG's :) Another distraction :)
 
@KurtE
You know I can not leave well enough alone so I added displaying JPEG images as well. Definitely room for improvement like scaling etc. Its fun now.
Code:
//=============================================================================
// Simple bitamp display program with MTP support added.
//=============================================================================

/***************************************************
  Some of this code originated with the spitftbitmap.ino sketch
  that is part of the Adafruit_ILI9341 library. 
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

#include <JPEGDEC.h>
#include <ILI9341_t3n.h>
#include <SPI.h>
#include <SD.h>
#include <MTP_Teensy.h>

#define TFT_DC  9
#define TFT_CS 10
#define TFT_RST 8
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST);

#define SD_CS BUILTIN_SDCARD  // Works on T_3.6 and T_4.1 ...
//#define SD_CS 6  // Works on SPI with this CS pin

JPEGDEC jpeg;

File rootFile;
File myfile;

elapsedMillis emDisplayed;
#define DISPLAY_IMAGES_TIME 2500

//used for jpeg files primarily
void * myOpen(const char *filename, int32_t *size) {
  myfile = SD.open(filename);
  *size = myfile.size();
  return &myfile;
}
void myClose(void *handle) {
  if (myfile) myfile.close();
}
int32_t myRead(JPEGFILE *handle, uint8_t *buffer, int32_t length) {
  if (!myfile) return 0;
  return myfile.read(buffer, length);
}
int32_t mySeek(JPEGFILE *handle, int32_t position) {
  if (!myfile) return 0;
  return myfile.seek(position);
}
// Function to draw pixels to the display
int JPEGDraw(JPEGDRAW *pDraw) {
  //Serial.printf("jpeg draw: x,y=%d,%d, cx,cy = %d,%d\n",
     //pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight);
  tft.writeRect(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight, pDraw->pPixels);
  return 1;
}

void setup(void) {
  // mandatory to begin the MTP session.
  MTP.begin();

  // Keep the SD card inactive while working the display.
  pinMode(SD_CS, INPUT_PULLUP);
  delay(200);

  tft.begin();
  tft.fillScreen(ILI9341_BLUE);

  Serial.begin(9600);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.setRotation(1);
  tft.println(F("Waiting for Arduino Serial Monitor..."));
  while (!Serial) {
    if (millis() > 3000) break;
  }

  Serial.print(F("Initializing SD card..."));
  tft.println(F("Init SD card..."));
  while (!SD.begin(SD_CS)) {
    Serial.println(F("failed to access SD card!"));
    tft.println(F("failed to access SD card!"));
    delay(2000);
  }
  MTP.addFilesystem(SD, "SD Card");

  rootFile = SD.open("/");

  Serial.println("OK!");
  emDisplayed = DISPLAY_IMAGES_TIME; 
}

void loop() {
  MTP.loop();
  if (emDisplayed < DISPLAY_IMAGES_TIME) return; 
  bool did_rewind = false;
  const char *name;
  uint8_t name_len;
  bool bmp_file = false;
  bool jpg_file = false;
  
  Serial.println("Loop looking for image file");
  
  File imageFile;
  
  for (;;) {
    imageFile = rootFile.openNextFile();
    if (!imageFile) {
      if (did_rewind) break; // only go around once. 
      rootFile.rewindDirectory();
      imageFile = rootFile.openNextFile();
      did_rewind = true;
    }
    // maybe should check file name quick and dirty
    name = imageFile.name();
    name_len = strlen(name);
    if (!name) continue;

    if(stricmp(&name[name_len-4], ".bmp") == 0) bmp_file = true;
    if(stricmp(&name[name_len-4], ".jpg") == 0) jpg_file = true;
    if ( bmp_file || jpg_file ) break;
  }
  tft.fillScreen(ILI9341_BLACK);
  if (imageFile && bmp_file) {
    bmpDraw(imageFile, imageFile.name(), 0, 0);
  } else if(imageFile && jpg_file) {
    imageFile.close();
    jpeg.open(name, myOpen, myClose, myRead, mySeek, JPEGDraw);
    jpeg.decode(0, 0, JPEG_SCALE_QUARTER);
    jpeg.close();
  } else {
    tft.fillScreen(ILI9341_GREEN);
    tft.setTextColor(ILI9341_WHITE);
    tft.setTextSize(2);
    tft.println(F("No Files Found"));
  }
  emDisplayed = 0;
}

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance for tiny AVR chips.

// Larger buffers are slightly more efficient, but if
// the buffer is too large, extra data is read unnecessarily.
// For example, if the image is 240 pixels wide, a 100
// pixel buffer will read 3 groups of 100 pixels.  The
// last 60 pixels from the 3rd read may not be used.

#define BUFFPIXEL 80


//===========================================================
// Try Draw using writeRect
void bmpDraw(File &bmpFile, const char *filename, uint8_t x, uint16_t y) {

//  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint16_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  uint16_t awColors[320];  // hold colors for one row at a time...

  if((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

// Open requested file on SD card
//  if (!(bmpFile = SD.open(filename))) {
//    Serial.print(F("File not found"));
//    return;
//  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            awColors[col] = tft.color565(r,g,b);
          } // end pixel
          tft.writeRect(0, row, w, 1, awColors);
        } // end scanline
        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));
}



// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File &f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

EDIT: you can get the JPEG library from here: https://github.com/bitbank2/JPEGDEC
 
Last edited:
Ran into a problem with the latest version of LittleFS and DiskIOV2. I downloaded the cores and LittleFS (posts #1002 and #1003). Installed them after backing them. Recompiled DiskioTesting.ino and got the following error:
Code:
Compiling library "DiskIOV2"
/home/wwatson/arduino-1.8.19/hardware/teensy/../tools/arm/bin/arm-none-eabi-g++ -c -O2 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -std=gnu++14 -fno-exceptions -fpermissive -fno-rtti -fno-threadsafe-statics -felide-constructors -Wno-error=narrowing -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16 -D__IMXRT1062__ -DTEENSYDUINO=156 -DARDUINO=10819 -DARDUINO_TEENSY41 -DF_CPU=600000000 -DUSB_SERIAL -DLAYOUT_US_ENGLISH -I/tmp/arduino_build_175196/pch -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/cores/teensy4 -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/LittleFS/src -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SPI -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SD/src -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SdFat/src -I/home/wwatson/Arduino/libraries/DiskIOV2/src -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/USBHost_t36-FS_Integration_MSC/src -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Audio -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SerialFlash -I/home/wwatson/Arduino/libraries/Arduino-Teensy-Codec-lib -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Time -I/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Wire /home/wwatson/Arduino/libraries/DiskIOV2/src/diskIOMB.cpp -o /tmp/arduino_build_175196/libraries/DiskIOV2/diskIOMB.cpp.o
Using previously compiled file: /tmp/arduino_build_175196/libraries/DiskIOV2/diskIOV2.cpp.o
In file included from /home/wwatson/Arduino/libraries/DiskIOV2/src/diskIO.h:12:0,
                 from /home/wwatson/Arduino/libraries/DiskIOV2/src/diskIOMB.cpp:18:
/home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/LittleFS/src/LittleFS.h:589:29: error: pn_name causes a section type conflict with data
   PROGMEM static const char pn_name[] = "PROGRAM";
                             ^
In file included from /home/wwatson/arduino-1.8.19/hardware/teensy/avr/cores/teensy4/WProgram.h:41:0,
                 from /tmp/arduino_build_175196/pch/Arduino.h:6,
                 from /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Audio/analyze_fft256.h:30,
                 from /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Audio/Audio.h:61,
                 from /home/wwatson/Arduino/libraries/DiskIOV2/src/diskIOMB.cpp:11:
/home/wwatson/arduino-1.8.19/hardware/teensy/avr/cores/teensy4/avr/pgmspace.h:35:39: note: 'data' was declared here
 #define PSTR(str) ({static const char data[] PROGMEM = (str); &data[0];})
                                       ^
/home/wwatson/arduino-1.8.19/hardware/teensy/avr/cores/teensy4/avr/pgmspace.h:59:37: note: in definition of macro 'strcmp_P'
 #define strcmp_P(a, b) strcmp((a), (b))
                                     ^
/home/wwatson/Arduino/libraries/DiskIOV2/src/diskIOMB.cpp:737:46: note: in expansion of macro 'PSTR'
     if((parCnt == 3) && (strcmp_P(pParam[1], PSTR(">")) == 0)) {
                                              ^
Multiple libraries were found for "SD.h"
 Used: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SD
 Not used: /home/wwatson/arduino-1.8.19/libraries/SD
Using library LittleFS at version 1.0.0 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/LittleFS 
Using library SPI at version 1.0 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SPI 
Using library SD at version 2.0.0 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SD 
Using library SdFat at version 2.1.0 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SdFat 
Using library DiskIOV2 at version 2.0.0 in folder: /home/wwatson/Arduino/libraries/DiskIOV2 
Using library USBHost_t36-FS_Integration_MSC at version 0.1 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/USBHost_t36-FS_Integration_MSC 
Using library Audio at version 1.3 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Audio 
Using library SerialFlash at version 0.5 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/SerialFlash 
Using library Arduino-Teensy-Codec-lib at version 0.3 in folder: /home/wwatson/Arduino/libraries/Arduino-Teensy-Codec-lib 
Using library Time at version 1.6.1 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Time 
Using library Wire at version 1.0 in folder: /home/wwatson/arduino-1.8.19/hardware/teensy/avr/libraries/Wire 
Error compiling for board Teensy 4.1.

Now I have been trying all day to create a small test sketch to duplicate the same compile error without success. If I revert back to the previous version of LittleFS it compiles OK.
This is with Arduino 1.8.19 and TD1.56.
In DisioMB.h:
Code:
private:
    char currentDir[MAX_PATH_LEN];

    char cmdBuf[MAX_CMD_BUF_SIZE];
    char dirBuf[256];
    char *ParmPtr[10];
    uint8_t bufPos;
    bool watchMode;
    bool csvMode;
    uint8_t escSeq;
    unsigned long watchTimeout;
    const char* machName;
    int historyBufSize;
    char *historyBuf;
    int historyWrPos;
    int historyCursorPos;
    bool locEcho;
    uint8_t stateTelnet;

    static CMD_ENTRY Cmds[MAX_CMD_NUM];
    PARAM_ENTRY *Params;
    [COLOR="#FF0000"]static const char dirList[][5] PROGMEM;[/COLOR]

In DiskioMB:
Code:
const char dirList[][5] PROGMEM =
{
    "bin", "dev", "etc", "proc", "sbin", "var", "lib", "sys", "tmp", "usr", ""
};

And the problem:
Code:
// echo 82.00 > /dev/param
void microBox::Echo(char **pParam, uint8_t parCnt) {
    uint8_t idx;

    [COLOR="#FF0000"]if((parCnt == 3) && (strcmp_P(pParam[1], PSTR(">")) == 0)) {[/COLOR]
        idx = GetParamIdx(pParam[2]);
        if(idx != -1) {
            if(Params[idx].parType & PARTYPE_RW) {
                if(Params[idx].parType & PARTYPE_INT) {
                    int val;

                    val = atoi(pParam[0]);
                    *((int*)Params[idx].pParam) = val;
                } else if(Params[idx].parType & PARTYPE_DOUBLE) {
                    double val;

                    val = parseFloat(pParam[0]);
                    *((double*)Params[idx].pParam) = val;
                } else {
                    if(strlen(pParam[0]) < Params[idx].len)
                        strcpy((char*)Params[idx].pParam, pParam[0]);
                }
                if(Params[idx].setFunc != NULL)
                    (*Params[idx].setFunc)(Params[idx].id);
            } else
                Serial.println(F("echo: File readonly"));
        } else {
            ErrorDir(F("echo"));
        }
    } else {
        for(idx=0;idx<parCnt;idx++) {
            Serial.print(pParam[idx]);
            Serial.print(F(" "));
        }
        Serial.println();
    }
}

I know you guys are really busy with MTP and I probably am missing something obvious again but I thought it best to bring it up:)
 
@KurtE
You know I can not leave well enough alone so I added displaying JPEG images as well. Definitely room for improvement like scaling etc. Its fun now.

EDIT: you can get the JPEG library from here: https://github.com/bitbank2/JPEGDEC

Lots of fun. I copied it into mine and having some fun. Need to do some tweaking so both expect the images in the same orientation

Will play more tomorrow. I know in one of these libraries or examples had/have code to do the rotation...
 
@KurtE
You know I can not leave well enough alone so I added displaying JPEG images as well. Definitely room for improvement like scaling etc. Its fun now.

EDIT: you can get the JPEG library from here: https://github.com/bitbank2/JPEGDEC

Lots of fun. I copied it into mine and having some fun. Need to do some tweaking so both expect the images in the same orientation

Will play more tomorrow. I know in one of these libraries or examples had/have code to do the rotation...
 
Last edited:
Ran into a problem with the latest version of LittleFS and DiskIOV2.

Can you give me a link to DiskIOV2?

Maybe 1 or more functions which should be FLASHMEM are PROGMEM? Normally FLASHMEM is for functions and PROGMEM is for variables. But if you have no PROGMEM variables, using PROGMEM on functions works.... until you add 1 variable with PROGMEM. Then you get section type conflicts. Just a blind guess without seeing the code, to check that any functions meant to run from flash are properly defined with FLASHMEM and not PROGMEM.
 
Ran into a problem with the latest version of LittleFS and DiskIOV2. I downloaded the cores and LittleFS (posts #1002 and #1003). Installed them after backing them. Recompiled DiskioTesting.ino and got the following error:
Now I have been trying all day to create a small test sketch to duplicate the same compile error without success. If I revert back to the previous version of LittleFS it compiles OK.
This is with Arduino 1.8.19 and TD1.56.
In DisioMB.h:
Some of this stuff goes related with the stuff I was asking a coupole days ago in the thread
https://forum.pjrc.com/threads/69234-Teensy-4-x-F()-how-to-keep-strings-in-flash-and-not-in-the-ITCM

And again I am totally no expert in some of the nuances of some of these tags...
It complains about: PROGMEM static const char pn_name[] = "PROGRAM";
Don't know if it would help to move PROGMEM to later in line like: static const char pn_name[] PROGMEM = "PROGRAM";
Don't think so.

But for yours I don't know if your stuff mentioned in header file that has private:
Code:
static const char dirList[][5] PROGMEM
Part of a class? If so would probably not mention PROGMEM here, could be wrong.

And where you init it... and you have:
Code:
const char dirList[][5] PROGMEM =
{
    "bin", "dev", "etc", "proc", "sbin", "var", "lib", "sys", "tmp", "usr", ""
};
I would have expected that the class name would be mentioned here....
Something like:
Code:
const char DiskioMB::dirList[][5] PROGMEM =
{
    "bin", "dev", "etc", "proc", "sbin", "var", "lib", "sys", "tmp", "usr", ""
};
But again don't know...

Also Don't know how PSTR() works with some of this stuff... I know I was not having any luck with F()

Sorry not sure.. Worst case for a few of these cases in little fs we could simply return the string from DTCM, ...
 
Can you give me a link to DiskIOV2?

Maybe 1 or more functions which should be FLASHMEM are PROGMEM? Normally FLASHMEM is for functions and PROGMEM is for variables. But if you have no PROGMEM variables, using PROGMEM on functions works.... until you add 1 variable with PROGMEM. Then you get section type conflicts. Just a blind guess without seeing the code, to check that any functions meant to run from flash are properly defined with FLASHMEM and not PROGMEM.

Yes Here:
https://github.com/wwatson4506/DiskIO/tree/DiskIOV2

I just now Googled this and found this thread:
https://forum.pjrc.com/threads/62760-section-type-conflict
But is does not seem to apply to what I am seeing.
Maybe it does :)
 
Updated MakeFiles

It now scans ALL DIRS! And checks any file with fileSize as start of name to match reported length.

Byte check of file blocks not started.

Other tidbits added - KB/sec on each folder write - requires call to showMediaSpace(); between each Make????() call to reset.

Has #ifdef switch for DISK SD or LittleFS_SPINAND myfs;

Output is Verbose and unclean WIP
Did not test against MTP updates, should not affect creation or checking so far?
No menu yet. Always does the Make()'s from loop() to notSetup();, then any SerMon 'Enter' does the directoryVerify();

Edit this area to select the Make???() calls:
Code:
  return;
  // EDIT HERE - for more files, Dirs, larger or alternate file sizes
 
Sorry @mjs513 and myself are having too much fun playing with image viewer...

I just pushed up changes to it, that Mike added usage of the Jpeg decoder library...
Which I then made a few changes and probably mucked something up...

But is sort of fun being able to drag and drop new images in..

But it again shows me a couple of interesting questions, wonder how best to do things like:

I want the sketch to build regardless if you have the jpeg library installed or not, but if you do have it
I want to automatically enable it.

How?

Use something like this?
Code:
//#if defined __has_include
#if __has_include(<JPEGDEC.h>)
 __has_include does not work on Arduino unless something explictily had included it without...
#include <JPEGDEC.h>
#endif
#endif
This by itself will seldom if ever load that header file.

My guess is It may pass the __has_include that header if:
a) something else in the sketch includes it anyway without the has include test
b) It is contained in the sketch folder or in library folder that you explicitly included some other file in that directory...

So unless I am missing something obvious, in a simple sketch case like this it does not work...

@Paul - that is why the #if lines were commented out in the storage cpp file ... ;)
 
Seems reasonable to support a bool operator like the other USB device classes.

It should probably be based on whether we've received OpenSession and not CloseSession. Windows sends OpenSession several seconds after USB enumeration, but on MacOS we only get it once the Android File Transfer program has been run.

Agree and on surface sounds simple, BUT... We currently have a few chicken and egg issue here.

For example: I may not want to call mtp.loop() unless mtp is connected, but currently mtp.loop() is the code that grabs the MTP messages off of the queue and sees that an OpenSession message was received and saves the session id away...

processing the CloseSession - My guess is we won't ever see these, as the user will probably just unplug the usb cable...


Which I know you are also wanting to rearrange some of this code to probably be incorporated more of the simple message processing, maybe directly in what is now the usb_mtp.c code in core, which simply queues up the received buffers to be processed by the loop code... Which at a minimum I was also wanting to do some of this to replace the initial interval timer code looking for a select few messages to process.
 
@Paul - that is why the #if lines were commented out in the storage cpp file ... ;)

It wasn't compiling after I synced to the latest changes, so I put those #if lines back in.

If it's causing you trouble now, I could just install MemoryHexDump here as a short-term solution. But for the long-term goal of bringing MTP_Teensy into the core library (hopefully for 1.57), we can't have it depend on including any other libraries.
 
@KurtE - @PaulStoffregen

I hacked up the SendObjectInfo to address the issue of when you are using LFS.
Code:
  object_id_ = storage_.Create(store, parent, dir, filename);
[COLOR="#FF0000"]  if (object_id_ == 0xFFFFFFFFUL && strlen(filename) > 39) {
	printf("Filename too will be truncated to 39!!!\n");
	char newFileName[MTP_MAX_FILENAME_LEN];
	for(uint8_t j = 0; j < 3; j++) newFileName[j] = '@';
	for(uint8_t j = 3; j < 38; j++) newFileName[j] = filename[j-3];
	strcpy(filename, newFileName);
        store = Storage2Store(storage);
	printf("\"%s\"\n ", filename);
        //storage_.close();
	object_id_ = storage_.Create(store, parent, dir, filename);
	printf(" object_id returned: 0x%x\n", object_id_);
  }[/COLOR]
  if (object_id_ == 0xFFFFFFFFUL) {
    return MTP_RESPONSE_SPECIFICATION_OF_DESTINATION_UNSUPPORTED;
  }
code in Red is what is added. Not the prettiest or efficient but for a hack to test the premise it seems to work at least with the 2 files that kept failing for me. Before I push the change is there anything I am missing or a better way.
 
Agree and on surface sounds simple, BUT... We currently have a few chicken and egg issue here.

For example: I may not want to call mtp.loop() unless mtp is connected, but currently mtp.loop() is the code that grabs the MTP messages off of the queue and sees that an OpenSession message was received and saves the session id away...

If this matters, we could also check for OpenSession in the timer.


processing the CloseSession - My guess is we won't ever see these, as the user will probably just unplug the usb cable...

Agreed, I've never seen Windows transmit CloseSession. We would also need to look at the something like usb_configuration.

CloseSession does get sent by Android File Transfer on MacOS.

So far I've not spent much time looking at what Linux actually does.


Which I know you are also wanting to rearrange some of this code to probably be incorporated more of the simple message processing, maybe directly in what is now the usb_mtp.c code in core, which simply queues up the received buffers to be processed by the loop code... Which at a minimum I was also wanting to do some of this to replace the initial interval timer code looking for a select few messages to process.

Actually, running this stuff from interrupts makes me nervous. For now, I want to leave it as-is and focus on more important things. Long-term, I'm hoping to find a way to run this timer stuff and the loop function from yield() or MillisTimer() or something similar. And eventually I want more places in the core library to call yield(), like when the bool operator for Serial is about to return false, or when Serial.available() is about to return zero.

But right now, the absolute top priority is that we finally have reliable GetObject & SendObject. Some things in the code still concern me, like creating / opening the file inside SendObjectInfo, rather than SendObject.

Turning GetObject & SendObject into a state machine, so we don't stay in loop() for long times, is also pretty important.

I'm less worried about performance, but I do want to check the case where SendObject is used to put a large WAV file onto a SD card. My hope is we can sustain at least 10 Mbyte/sec when the destination is SDIO and no other code is accessing files on that SD card.

Transmitting events is also a place I want to focus effort soon. I've been holding off while we still had lingering questions about GetObject & SendObject reliably moving files back and forth, but it seems we're at a pretty good place now, where the lingering problems are mostly performance issues and inefficient use of media for tiny files.

Another area on my mental to-do list, which technically isn't just MTP, is some sort of file level locking and work to allow interrupt safe access to files. Technically speaking, that will probably be done in FS.h (as well some of the infrastructure for event generation) but MTP is the main use case.
 
But maybe we should just support 255 byte filename length?

Any chance you could try some of Defragster's huge file sets with LittleFS formatted to allow 255 vs 39 bytes. Does it make any real difference in usage of the media? Or RAM usage?

If the impact is minimum, let's just get rid of the 39 byte limit and allow 255.

Might also be worthwhile to test filenames using UTF8 encoded international characters and symbols. One corner case to consider is we get a long filename from MTP which uses Unicode16 and turns into longer than 255 bytes when encoded to UTF8. I'm pretty sure we are already properly truncating with UTF8 character boundary awareness (never creating invalid UTF8), but that hasn't actually been tested yet.
 
Back
Top