Problems Drawing RAW file from SD on MKRZero

I wrote a short subroutine to read a RAW image from the built-in SD reader on the MKRZero and write it to an ILI9341 320×240 screen. I used some of Bodmer's routines as the starting point, and I was elated when an image appeared on my screen. My elation evaporated when I saw that the image seems to be shifted right from the upper left of my screen (0,0). The rightmost 20 or so columns are not where they should be; they're on the left of the screen.
I am hopeful someone can help me figure out what is happening.
Also, when I draw a RAW image, I can immediately draw another, no problem. If I draw an image, then draw a figure (like the black rectangle in the picture, the next RAW image seems to be drawn inside that figure. I can see it as the RAW image is loaded and drawn. If I call fillScreen, the image loads as I expect.
If anyone knows why this is happening or how to correct it, I would also be grateful.
As a note, I can't seem to get SDFat.h or Adafruit_GFX_AS.h or Adafruit_ILI9341_AS.h to compile into my MKRZero. I have had success with Bodmer's super fast drawRAW on my NANO using those libraries, but I seem to remember that they were not made for ARM processors.
I got a BMP to load at around 3000 ms and I got a RAW to load in about 1500 ms. That is no where near the 400 ms I got with Bodmer's drawRAW using the above mentioned libraries.
Any help is greatly appreciated.

MZ_screenRAW.ino (3.43 KB)

I am intrigued. The photo shows an image on a 320x240 ILI9341 display.
The sketch implies that the image is being read from an SD card.

Yet there are no solder marks on the SD header terminals !!
How can the SD "work" ?

Please attach the actual file. (You will probably need to put it inside a ZIP for the Forum).

Life is much safer if you start with publicly available code. I guess that Bodmer has some RAW files on GitHub. There are definitely example RAW files in the UTFT distribution.

Personally, I prefer proper image files like .JPG, .BMP, .PNG, ...
These can be viewed on any PC.

David.

Hello David. Thank you for your interest.

david_prentice:
I am intrigued. The photo shows an image on a 320x240 ILI9341 display.
The sketch implies that the image is being read from an SD card.

Yet there are no solder marks on the SD header terminals !!
How can the SD "work" ?

I am using an Arduino MKR Zero (not a clone). The MKR Zero has an SD card reader built into it, with its very own hardwired serial connections. You just declare the SDCARD_SS_PIN, and it works like magic. It is also 3V3 so there is no need for a logic shifter for the ILI9341. It has way more memory and claims to run at a faster speed. The downsides are that there is next to no documentation for it, and some of the libraries (see my first post for examples) do not run on the SAMD21 chip.
I suspect there are few out there who have experience with the MKR Zero.

david_prentice:
Please attach the actual file. (You will probably need to put it inside a ZIP for the Forum).

Life is much safer if you start with publicly available code. I guess that Bodmer has some RAW files on GitHub. There are definitely example RAW files in the UTFT distribution.

Personally, I prefer proper image files like .JPG, .BMP, .PNG, ...
These can be viewed on any PC.

I should have thought to attach the RAW image files. My apologies. Please see the ZIP file below.
The RAW files are 16-bit, 565 raw images. I used a file converter to make them from the original BMPs. Both of my photo viewer apps (I am running Windows 10), my higher end photo editor, and even Paint were able to open and display them (see attached).
I had been thinking that something was wrong with my application of the setAddrWindow command. I have tried several iterations of it:

  // Set whole screen as writable window starting at top left
  //myTft.setAddrWindow(int16_t(0), int16_t(0), int16_t(319), int16_t(239));
  //myTft.setAddrWindow(int(0), int(0), int(319), int(239));
  //myTft.setAddrWindow(0, 0, 319, 239);
  myTft.setAddrWindow(0, 0, rawWidth-1, rawHeight-1);
  //myTft.setAddrWindow(uint8_t(0), uint8_t(0), uint8_t(rawWidth-1), uint8_t(rawHeight-1));
  //myTft.setAddrWindow(int16_t(0), int16_t(0), int16_t(rawWidth-1), int16_t(rawHeight-1));

None seems to make a difference.
I tried commenting out the initial fillScreen command before the first RAW image is drawn:

  // Initialise the screen  
  myTft.begin();
  myTft.setRotation(1);
  //myTft.fillScreen(0x1393);
    
  // Show RAW image
  screenRAW("fa.raw");
  delay(1000);

  // Draw a black rectangle
  myTft.fillRect(24,   68, 16, 124, 0x0000);
  delay(1000);

  // Show RAW image
  screenRAW("ia.raw");

Interestingly, if I do that, the first RAW image doesn't draw at all, but the second one still tries to draw itself inside the black rectangle (see second image).
I have two questions to pursue. will I get the same results if I use just a screen rather than the ILI9341 shield you keenly observed? Will I get the same results if I ran this code on a NANO?
Again, thanks. I am deeply appreciative of any help.

PaintFA.jpg

screenRAW.zip (140 KB)

PaintFA.jpg

I ran your sketch on a 3.3V Uno.

Sure enough, the picture is skewed by 33 pixels.
You can solve by inserting this statement before you read the real data:

    rawFile.read(sdBuffer, 66);  //33 pixels is 66 bytes

Inspecting fa.raw:

filespec: ..\temp\fa.raw
000000 42 4D 42 58 02 00 00 00 00 00 42 00 00 00 28 00 *BMBX......B...(.*
000010 00 00 40 01 00 00 10 FF FF FF 01 00 10 00 03 00 *..@.............*
000020 00 00 42 58 02 00 12 0B 00 00 12 0B 00 00 00 00 *..BX............*
000030 00 00 00 00 00 00 00 F8 00 00 E0 07 00 00 1F 00 *.......°..Ó.....*
000040 00 00

I can see that you actually contain a .BMP header. The first two bytes are 0x42 0x4D.
This accounts for the extra 66 bytes. The 42 00 00 00 entry signifies that bitmap data starts at 0x00000042 (66 in decimal money)

I don't know where you got your RAW files from.

It looks as if you have just renamed a special 16-bit .BMP file as .RAW
Note that most Arduino .BMP sketches expect 24-bit pixel data.

I expect that my MCUFRIEND_kbv BMP sketch will display this format.

David.

david_prentice:
I can see that you actually contain a .BMP header. The first two bytes are 0x42 0x4D.
This accounts for the extra 66 bytes. The 42 00 00 00 entry signifies that bitmap data starts at 0x00000042 (66 in decimal money)

I don't know where you got your RAW files from.

It looks as if you have just renamed a special 16-bit .BMP file as .RAW
Note that most Arduino .BMP sketches expect 24-bit pixel data.

I expect that my MCUFRIEND_kbv BMP sketch will display this format.

That explains why all my programs can read those files.
I included the line of code you gave me, and, sure enough, it displays beautifully. Thanks once again.
I would love to try the MCUFRIEND_kbv BMP sketch. It looks promising. However, I have not been able to get the graphicstest example included in that library to function. I will include how I modified the sketch...

// All the mcufriend.com UNO shields have the same pinout.
// i.e. control pins A0-A4.  Data D2-D9.  microSD D10-D13.
// Touchscreens are normally A1, A2, D7, D6 but the order varies
//
// This demo should work with most Adafruit TFT libraries
// If you are not using a shield,  use a full Adafruit constructor()
// e.g. Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

#define LCD_CS 7 // Chip Select goes to Analog 3
#define LCD_CD 6 // Command/Data goes to Analog 2
//#define LCD_WR A1 // LCD Write goes to Analog 1
//#define LCD_RD A0 // LCD Read goes to Analog 0
//#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

#include <SPI.h>          // f.k. for Arduino-1.5.2
#include "Adafruit_GFX.h"// Hardware-specific library
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;
//#include <Adafruit_TFTLCD.h>
//Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
#include <Adafruit_ILI9341.h>  // Hardware-specific library
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

When I compile the above sketch, I get a

[color=orange][color=brown]conflicting declaration 'Adafruit_ILI9341 tft'[/color][/color]

...message.

graphictest_kbv_Edit.ino (18.5 KB)

You have a Chinese Red SPI display. I can see from your photos.

MCUFRIEND_kbv is designed for Parallel displays.
Some of the hardware methods are different to Adafruit_ILI9341. But it is pretty easy to port sketches from one to the other.

Although I prefer image file formats with headers that contain size and colour information, converting to a RAW file is ok just as long as you know the geometry etc.

David.

david_prentice:
MCUFRIEND_kbv is designed for Parallel displays.
Some of the hardware methods are different to Adafruit_ILI9341. But it is pretty easy to port sketches from one to the other.

Hi again David!
I would be interested in learning how to do the above. Using your library, do you think that there would be a speed improvement over the ~1500ms it takes to draw RAW files on an SPI? I suspect that parallel displays would be faster than SPI. I have some shields that (i believe) have parallel screens. It is difficult to tell how many pins they require though. The main reason I bought the SPI screens was to decrease the number of pins. I wanted to save the pins for LEDs and buttons (and card reader and MP3 player) when I was using the NANO. The MKR Zero has a few pins left now though.

The program I used to convert my images to RAW format is called Display Module Bitmap Converter. I guess it adds the little header David mentioned. I tried other converters, but I am going back to this one; it gives me the ability to see my images in File Explorer in Win10. With David's fix -- thanks again -- they work great.

I finally got my screen to work using a wired RST pin. I used pin 4 and it works great!

I still have two problems:

Problem One:
I still would love to get the read speed faster. Currently it is at around 1580 ms per RAW image, and it would be nice to get that under 1000 ms. I think if I could get tft.pushColors to work, I could improve the speed. I am unable to run any of the libraries that contain tft.pushColors (e.g. Adafruit_ILI9341_AS, MCUFRIEND_kbv), so I am limited to the tft.pushColor (singular) from the Adafruit_ILI9341 library. I have never written or modified a library before, so that will be a big learning curve for me.

Problem Two:
Why must I call the tft.fillScreen command before it works to draw a RAW file with tft.pushColor? It does work to draw a RAW image inside a filled rectangle. Whatever those two commands do, I would love it if the tft.setAddrWindow would do as well.

Once again, any help is greatly appreciated.

The program I used to convert my images to RAW format is called Display Module Bitmap Converter.

Please post a link to this program. I don't want to guess.

david_prentice:
Please post a link to this program. I don't want to guess.

DisplayModule's Image Converter DL

It seems to use something called "bitmap HEADERINFOV 3" (or maybe "BITMAPV3INFOHEADER"). All of my software can read these files, and more importantly, I can see the image in my file browser. I don't really need the information in the header (I already know the size of all my files since I created them), but a more robust function would need that information.

I was also wondering if reinstalling Arduino IDE may resolve the issue with some libraries not compiling.

Thanks for the link. I had never heard of this utility.

I happen to have written the equivalent command line program about 8 years ago.
I was not aware that anyone else had ever done it.

Your GUI program generates legal 16-bit .BMP format files. The 16-bit is big-endian.

Since very few Arduino programs support anything other than 24-bit .BMP files I did not think there would be any interest.

You will find that typical Arduino programs for .RAW pixel files are stored as uint16_t binary files.

It is possible to generate legal 16-bit .BMP format files with the 16-bit pixel in little-endian order.
But don't expect it to be widely supported for Arduino, MBED, RPi, ...

I could answer all your technical questions in #7, #9.
Do you really want to know?

Wikipedia had a good article explaining .BMP formats. Read that first. If your head does not hurt too much, I will continue from there.

David.

I had a fun morning trying to figure out BMP and RAW headers. It seems my converter either adds RGBA info (in which case I get a 70-byte header), adds RGB info (a 66-byte header), or no info (a 54-byte header). See TXT file in ZIP.

It also seems that the converter does not give the correct width of the RAW file, which is quite disappointing.

I am now working out a way to read the size of the header so that my code can read the RAW file no matter what the size of the header.

RAWandBMP.zip (319 KB)

I modified the code in the function I wrote, and I took out the line of code you sent me. It works!!!

/***************************************************************************************
** Function name:   recRAW
** Description:     Clears a section of the scren and draws a RAW image there
** Requirements:    The screen must be in 320 by 240 landscape (rotation 1 or 3)
** Based On:        Bodmer's drawRAW with help from David Prentice
** Modified by:     KodiakBart Feburary 2021
***************************************************************************************/

// This function opens a ".raw" image file with a width less than 255 and displays it at the given coordinates
void recRAW(char *filename, int16_t x, int16_t y, int16_t rawWidth, int16_t rawHeight, int16_t c) {
  File     rawFile;             // File name to draw
  int16_t  sdBuffer[rawWidth];  // SD read pixel buffer (16 bits per pixel)
  uint8_t  buffidx = 0;         // Current position in sdBuffer
  uint32_t drawTime;            // Keep track of speed

  // Check file exists and open it
  if ((rawFile = SD.open(filename)) == NULL) {
    Serial.println(F("File not found"));
    return;
  }

  // Exit if recRAW width is greater than max buffer size
  if (rawWidth > 255) return;

  // Clear the screen -- THERE HAS TO BE A BETTER WAY
  if (c == 0) myTft.fillRect(x, y, rawWidth-1, rawHeight-1, TOS_ORANGE);
  if (c == 1) myTft.fillRect(x, y, rawWidth-1, rawHeight-1, TOS_BLACK);
  
  // Save current time for performance evaluation
  drawTime = millis(); 

  // Set writable window starting at top left
  myTft.setAddrWindow(x, y, rawWidth-1, rawHeight-1);
  
  // Parse the RAW (Bitmap) header
  rawFile.read(sdBuffer, 10);                // Reads 10 bytes for File Type, File Size, and Reserved data
  uint32_t imageOffset = read32(rawFile);    // Reads 4 bytes for image offset
  Serial.println(imageOffset);               // Show in serial monitor
  rawFile.read(sdBuffer, imageOffset - 14);  // Reads the rest of the header
    
  // Read and dpush the data
  for (int row=0; row<rawHeight; row++) {
    // Read the row
    rawFile.read(sdBuffer, sizeof(sdBuffer));
    buffidx = 0;
    for (int col=0; col<rawWidth; col++) {
      myTft.pushColor(sdBuffer[buffidx++]);
    }
  }

  // Close file
  rawFile.close();

  // Show draw time
  drawTime = millis() - drawTime;
  Serial.println(drawTime);
}

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;
}

Yes, you have worked out how to read the header fields.

You have the Height field wrong. It should be negative.
This indicates whether to draw each row from the bottom upwards (default) or from the top down (easier)

When you have studied the Wikipedia article you can see if and when there are RGBA fields.
How the bitmaps are stored, padded, ...

In practice your Arduino needs to know the image geometry. e.g. a 240x320 is drawn differently to a 320x240 or even a 320x-240

If they are all "your own work" there is no problem.
But if you want to render foreign .BMP files you need to parse the header and cope with the many legal file formats.

David.

I changed the parsing code to this...

  // Parse the RAW (Bitmap) header
  rawFile.read(sdBuffer, 10);                 // Reads 10 bytes for File Type, File Size, and Reserved data
  imageOffset = read32(rawFile);              // Reads 4 bytes for image offset
  Serial.print(F("Header Size = "));          // Serial info
  Serial.println(imageOffset);                // Show in serial monitor
  headerReader = read32(rawFile);             // Reads unused header size
  headerReader = read32(rawFile);             // Reads 4 bytes for image offset
  Serial.print(F("Width = "));                // Serial info
  Serial.println(headerReader);               // Show in serial monitor
  headerReader = read32(rawFile);             // Reads 4 bytes for image offset
  Serial.print(F("Height = "));               // Serial info
  Serial.println(headerReader);               // Show in serial monitor
  rawFile.read(sdBuffer, imageOffset - 26);   // Reads the rest of the header

... and the serial monitor gave me this...

Header Size = 66
Width = 320
Height = 4294967056
1595

The width should be 240.

david_prentice:
You have the Height field wrong. It should be negative.
This indicates whether to draw each row from the bottom upwards (default) or from the top down (easier)

How can I read a negative HEX number?

Bart

Wait! I subtracted it from 4294967296 and that works for some reason. That is 2^32

Hello!
I am quite excited with my progress. I got the write time for a 320×240 RAW image down to around 1100 ms and I eliminated the need for the pesky fillScreen() call. It would be nice if it went even faster, but I am pleased with my progress so far. I used writePixels() from <Adafruit_SPITFT.h> with a buffer that encompasses an entire row (320 pixels). I really thought I would see more improvement (it knocked off about 400 ms), but it is improvement.

Thanks again for your help.

If you have any further suggestions,

/*     ####    ####   ####    #####   #####   #   #   ####     ###    #   #
      #       #       #   #   #       #       ##  #   #   #   #   #   #   #
       ###    #       ####    ###     ###     # # #   ####    #####   # # #
          #   #       #   #   #       #       #  ##   #   #   #   #   # # #
      ####     ####   #   #   #####   #####   #   #   #   #   #   #    ###

Description:     Fills the entire scre by drawing a 565 format 16 bit RAW image file at 0, 0
Requirements:    The screen must be in 320 by 240 landscape (rotation 1 or 3)
Based On:        Bodmer's drawRAW with help from Prentice
Modified by:     KodiakBart Feburary 2021
***************************************************************************************/
void screenRAW(char *filename, int c) {
  File     rawFile;               // File name to draw
  int16_t rawWidth = 320;         // Width of screen RAWs
  int16_t rawHeight = 240;        // Height of screen RAWs
  uint16_t  sdBuffer[rawWidth];   // SD read pixel buffer (16 bits per pixel)
  uint32_t drawTime;              // Keep track of speed
  uint32_t headerReader;          // To read data from the header
  uint32_t imageOffset;           // To read image offset data from the header

  if ((rawFile = SD.open(filename)) == NULL) {     // Check file exists and open it
    Serial.println(F("File not found"));           // Send a message to Serial
    return;                                        // Exit function if the file is not found
  }
  Serial.print(F("File Name = "));                 // Serial info
  Serial.println(filename);                        // Show in serial monitor
  //myTft.fillScreen(c);                           // Clear the screen -- THERE HAS TO BE A BETTER WAY
  drawTime = millis();                             // Save current time for performance evaluation
  myTft.startWrite();                              // Initiate the write sequence
  myTft.setAddrWindow(0, 0, rawWidth, rawHeight);  // Set whole screen as writable window starting at top left

  // Parse the RAW (Bitmap) header
  rawFile.read(sdBuffer, 10);                          // Reads 10 bytes for File Type, File Size, and Reserved data
  imageOffset = read32(rawFile);                       // Reads 4 bytes for image offset
  Serial.print(F("Header Size = "));                   // Serial info
  Serial.println(imageOffset);                         // Show in serial monitor
  headerReader = read32(rawFile);                      // Reads unused header size
  headerReader = read32(rawFile);                      // Reads 4 bytes for width
  Serial.print(F("Width = "));                         // Serial info
  Serial.println(headerReader);                        // Show in serial monitor
  headerReader = read32(rawFile);                      // Reads 4 bytes for height
  Serial.print(F("Height = "));                        // Serial info
  Serial.println(4294967296 - headerReader);           // Show in serial monitor
  rawFile.read(sdBuffer, imageOffset - 26);            // Reads the rest of the header
  Serial.print(F("Size of sdBuffer = "));              // Serial info
  Serial.println(sizeof(sdBuffer));                    // Show in serial monitor
  for (int row = 0; row < 240; row++) {                // Read and push the data
    rawFile.read(sdBuffer, 640);                       // Read the row
    myTft.writePixels(sdBuffer, uint32_t(rawWidth));   // Write the row
  }
  myTft.endWrite();                                    // End the write sequence
  rawFile.close();                                     // Close file
  drawTime = millis() - drawTime;                      // Show draw time
  Serial.print(F("Draw Time in ms = "));
  Serial.println(drawTime);
  Serial.println();
}

/*    ####    #####    ####   ####     ###    #   #
      #   #   #       #       #   #   #   #   #   #
      ####    ###     #       ####    #####   # # #
      #   #   #       #       #   #   #   #   # # #
      #   #   #####    ####   #   #   #   #    ###

Description:     Clears a given section of the screeen and draws a RAW image there with width less than 256
Requirements:    The screen must be in 320 by 240 landscape (rotation 1 or 3)
Based On:        Bodmer's drawRAW with help from David Prentice
Modified by:     KodiakBart Feburary 2021
***************************************************************************************/
void recRAW(char *filename, int16_t x, int16_t y, int16_t rawWidth, int16_t rawHeight, int c) {
  File     rawFile;              // File name to draw
  uint16_t  sdBuffer[rawWidth];  // SD read pixel buffer (16 bits per pixel)
  uint32_t drawTime;             // Keep track of speed
  uint32_t headerReader;         // To read data from the header
  uint32_t imageOffset;          // To read image offset data from the header

  if ((rawFile = SD.open(filename)) == NULL) {              // Check file exists and open it
    Serial.println(F("File not found"));                    // Send a message to Serial
    return;                                                 // Exit function if the file is not found
  }
  Serial.print(F("File Name = "));                          // Serial info
  Serial.println(filename);                                 // Show in serial monitor
  if (rawWidth > 255) return;                               // Exit if recRAW width is greater than max buffer size
  drawTime = millis();                                      // Save current time for performance evaluation
  myTft.startWrite();                                       // Initiate teh write sequence
  myTft.setAddrWindow(x, y, rawWidth, rawHeight);   // Set writable window starting at top left

  // Parse the RAW (Bitmap) header
  rawFile.read(sdBuffer, 10);                          // Reads 10 bytes for File Type, File Size, and Reserved data
  imageOffset = read32(rawFile);                       // Reads 4 bytes for image offset
  Serial.print(F("Header Size = "));                   // Serial info
  Serial.println(imageOffset);                         // Show in serial monitor
  headerReader = read32(rawFile);                      // Reads unused header size
  headerReader = read32(rawFile);                      // Reads 4 bytes for width
  Serial.print(F("Width = "));                         // Serial info
  Serial.println(headerReader);                        // Show in serial monitor
  headerReader = read32(rawFile);                      // Reads 4 bytes for height
  Serial.print(F("Height = "));                        // Serial info
  Serial.println(4294967296 - headerReader);           // Show in serial monitor
  rawFile.read(sdBuffer, imageOffset - 26);            // Reads the rest of the header
  for (int row = 0; row < rawHeight; row++) {          // Read and push the data
    rawFile.read(sdBuffer, sizeof(sdBuffer));          // Read the row
    myTft.writePixels(sdBuffer, uint32_t(rawWidth));   // Write the row
  }
  myTft.endWrite();                                    // End teh write sequence
  rawFile.close();                                     // Close file
  drawTime = millis() - drawTime;                      // Show draw time
  Serial.print(F("Draw Time in ms = "));
  Serial.println(drawTime);
  Serial.println();
}

// Reads 2 bytes from the open file and converts them to a number
// Used in screenRAW and recRAW above
uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

// Reads 4 bytes from the open file and converts them to a number
// Used in screenRAW and recRAW above
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;
}

It "looks" as if you are using Adafruit_ILI9341 library.

  for (int row = 0; row < rawHeight; row++) {          // Read and push the data
    rawFile.read(sdBuffer, sizeof(sdBuffer));          // Read the row
    myTft.writePixels(sdBuffer, uint32_t(rawWidth));   // Write the row
  }

Since your MKRZero is 48MHz I would expect the writePixels() to be pretty efficient. e.g. 154ms @ 8MHz SPI.
And the SD card read() to be similar.

However the SPI.h library on some Arduino Cores is really crap.
and the SD library might choose to read the SD card at a slower speed than 8MHz. e.g. 2MHz
in which case your writePixels() is also going use the slower speed.

Pure speculation. I don't have an MKRZero. But I do have an M0_Pro. So I might run your code to see for myself.

As a general rule you should use Transactions with SPI. i.e. ensure that the TFT library functions are using the correct SPI speed and format.
For example: read one row at a time with SD.read(). display row with tft.drawRGBBitmap() at the correct row on the screen.

David.

These are the libraries I used.

#include <Adafruit_GFX.h>             // Graphics library
#include <Adafruit_SPITFT.h>          // SPI
#include <Adafruit_SPITFT_Macros.h>   // ??
#include <Adafruit_ILI9341.h>         // Hardware-specific library
#include <SD.h>                       // Card reader
#include <CapacitiveSensor.h>         // To enable the capacitive touch buttons
#include <DFRobotDFPlayerMini.h>      // MP3 player

david_prentice:
Since your MKRZero is 48MHz I would expect the writePixels() to be pretty efficient. e.g. 154ms @ 8MHz SPI.
And the SD card read() to be similar.

That's what I was hoping for too, but alas...

david_prentice:
However the SPI.h library on some Arduino Cores is really crap.
and the SD library might choose to read the SD card at a slower speed than 8MHz. e.g. 2MHz
in which case your writePixels() is also going use the slower speed.

I have heard that the SPI library is quite slow. I will investigate further.

  for (int row = 0; row < 240; row++) {                // Read and push the data
    rawFile.read(sdBuffer, 640);                       // Read the row
    myTft.drawRGBBitmap(0, row, sdBuffer, 320,1);
    //myTft.writePixels(sdBuffer, uint32_t(rawWidth));   // Write the row
  }

This works, but there is no improvement in speed. It is still around the 1100 ms range. This leads me to believe it is the read speed.

david_prentice:
As a general rule you should use Transactions with SPI. i.e. ensure that the TFT library functions are using the correct SPI speed and format.

I have never used Transactions before. I will investigate further.
Thanks again for your help.
Bart

I tried Transaction at 20MHz and at 48MHz, but there was no improvement in speed. I am not sure I did it correctly, however.