I made good progress today. Below is a copy of the working software that loads an image from the Teensy 3.6 on board SD card and displays it on a SSD1351 (128x128) OLED display. The read and the display combined takes less than 200ms vs. the more conventional code which writes pixel by pixel on the OLED, about 550ms.
/***************************************************
This sketch reads in bmp files (128 x 128) and
then displays the image onto a SSD1351 compatible
OLED display via hardware based SPI on a Teensy 3.6
using the built in SD card reader and the latest
SDFat library.
Why? Using the original code example from Adafruit
took approximate 525ms to load. Too slow for the
interactive purposes that I had in mind for my project
thus I looked for a faster method. Having seen a demo
of video being played via a Teensy 3.6 on a small (I belive
to be OLED display) I knew that there had to be a
faster way to get an image to load on a small OLED. Thus
this project was born.
It comprises code from Adafruit as well as
from "Dial"
------> https://community.particle.io/t/bulk-pixel-with-adafruit-ssd1351/18403
and the latest SDFAT library by Bill Greiman which can be found at
------> https://github.com/greiman/SdFat-beta
Additionally many thanks to"
o Caffine
o Bill Greiman
o Pete (el_supremo)
o Robinsloan
on the PJRC forums for their guidance and inspiration.
You can find the thread related to this code development
at:
------> https://forum.pjrc.com/threads/40871-Teensy-6-5-SDFat-BMP-file-read-fail
I am positive this code can be improved upon and look
forward to see what others can do to improve the performance.
Please utilize the forum posting above if you do make updates.
SoCalPinPlayer
David
****************************************************/
/***************************************************
This is a example sketch demonstrating bitmap drawing
capabilities of the SSD1351 library for the 1.5"
and 1.27" 16-bit Color OLEDs with SSD1351 driver chip
Pick one up today in the adafruit shop!
------> http://www.adafruit.com/products/1431
------> http://www.adafruit.com/products/1673
If you're using a 1.27" OLED, change SSD1351HEIGHT in Adafruit_SSD1351.h
to 96 instead of 128
These displays use SPI to communicate, 4 or 5 pins are required to
interface
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.
BSD license, all text above must be included in any redistribution
The Adafruit GFX Graphics core library is also required
https://github.com/adafruit/Adafruit-GFX-Library
Be sure to install it!
****************************************************/
#include <SPI.h>
#include <SdFat.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1351.h>
/*
// Set USE_SDIO to zero for SPI card access.
#define USE_SDIO 1
* SD chip select pin. Common values are:
*
* Arduino Ethernet shield, pin 4.
* SparkFun SD shield, pin 8.
* Adafruit SD shields and modules, pin 10.
* Default SD chip select is the SPI SS pin.
*/
// const uint8_t SD_CHIP_SELECT = SS;
/*
* Set DISABLE_CHIP_SELECT to disable a second SPI device.
* For example, with the Ethernet shield, set DISABLE_CHIP_SELECT
* to 10 to disable the Ethernet controller.
*/
const int8_t DISABLE_CHIP_SELECT = -1;
SdFatSdio sd;
// serial output steam
ArduinoOutStream cout(Serial);
// OLED Display SPI PINS -- Built in SD card on Teensy uses separate SPI
// If we are using the hardware SPI interface, these are the pins (for future ref)
#define sclk 13
#define mosi 11
#define cs 10
#define rst 6
#define dc 9
// Color definitions
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
// to draw images from the SD card, we will share the hardware SPI interface
Adafruit_SSD1351 tft = Adafruit_SSD1351(cs, dc, rst);
// For Arduino Uno/Duemilanove, etc
// connect the SD card with MOSI going to pin 11, MISO going to pin 12 and SCK going to pin 13 (standard)
// Then pin 10 goes to CS (or whatever you have set up)
// #define SD_CS BUILTIN_SDCARD // Set the chip select line to whatever you use (10 doesnt conflict with the library)
// the file itself
File bmpFile;
// information we extract about the bitmap file
int bmpWidth, bmpHeight;
uint8_t bmpDepth, bmpImageoffset;
void setup(void) {
Serial.begin(9600);
pinMode(cs, OUTPUT);
digitalWrite(cs, HIGH);
delay(2000);
Serial.begin(9600);
// Wait for USB Serial
// Comment out the following if you don't want the MCU to wait
// until the developer opens up the serial monitor on the
// Arduino IDE.
while (!Serial) {
SysCall::yield();
}
// initialize the OLED
tft.begin();
Serial.println("init");
tft.fillScreen(BLUE);
delay(500);
Serial.print("Initializing SD card...");
if (!sd.begin()) {
Serial.println("failed!");
return;
}
Serial.println("SD OK!");
// images need to be 128 x 128 in size
bmpDraw("lily128.bmp", 0, 0);
bmpDraw("img1.bmp", 0, 0);
bmpDraw("img2.bmp", 0, 0);
}
void loop() {
}
// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates. It's first
// reads the data into an array after converting the
// BMP pixels to tft format.
// The array is then written to the OLED display using
// displayImageOnScreen function that has been added
// to the Adafruit_SSD1351 .h and .cpp files.
#define BUFFPIXEL 20
void bmpDraw(char *filename, uint8_t x, uint8_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)
uint8_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 imgArry[16348]; // array that holds all pixel converted into tft format hard coded for 128 x 128 display
uint32_t pixPos = 0; // position in the image array
if((x >= tft.width()) || (y >= tft.height())) return;
Serial.println();
Serial.print("Loading image '");
Serial.print(filename);
Serial.println('\'');
// Open requested file on SD card
if ((bmpFile = sd.open(filename)) == NULL) {
Serial.print("File not found");
return;
}
// Parse BMP header
if(read16(bmpFile) == 0x4D42) { // BMP signature
Serial.print("File size: "); Serial.println(read32(bmpFile));
(void)read32(bmpFile); // Read & ignore creator bytes
bmpImageoffset = read32(bmpFile); // Start of image data
Serial.print("Image Offset: "); Serial.println(bmpImageoffset, DEC);
// Read DIB header
Serial.print("Header size: "); Serial.println(read32(bmpFile));
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if(read16(bmpFile) == 1) { // # planes -- must be '1'
Serial.println("No. of Planes: 1");
bmpDepth = read16(bmpFile); // bits per pixel
Serial.print("Bit Depth: "); Serial.println(bmpDepth);
if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
goodBmp = true; // Supported BMP format -- proceed!
Serial.print("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;
// tft.displayImageOnScreen(sdbuffer);
//========= Read in the BMP image put into imgArry ========
for (row=0; row<h; row++) { // For each scanline...
// tft.goTo(x, y+row);
// 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
}
// optimize by setting pins now
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++];
imgArry[pixPos] = tft.Color565(r,g,b);
pixPos++;
} // end pixel
} // end scanline
Serial.print("Imaged Loaded into array in ");
Serial.print(millis() - startTime);
Serial.println(" ms");
// --- lets write the array out to the OLED screen
startTime = millis();
tft.displayImageOnScreen(imgArry);
Serial.print("Imaged Loaded into OLED in ");
Serial.print(millis() - startTime);
Serial.println(" ms");
} // end goodBmp
}
}
bmpFile.close();
if(!goodBmp) Serial.println("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;
}
I am confident that this can be improved upon but will have to leave that to others.
David