Particle Photon OLED screen and SD card demo

What if you alter the background color from BLUE to something else, does this happen as expected?
Can you add some code to push the BMP data down Serial too, just to see if this looks OKish?

Have you checked that the bit depth of the BMP is 24?

If not, this should be flagged by a Serial.print() output - AFAIK (remember).

So this is what I found so far:

My OLED is responsive BEFORE the “if(!SD.begin(cs_sd))”. Before that line, I can change the screen color multiple times no problem; but after, tft.fillScreen() no longer does anything. This seems very similar to Mike’s original problem

As for the pic, I’m using both the lily128.bmp (176x176) and another I found that is128x128. Both are 24bit are being read fine!

Also, the reading/writing portion of the code seems to be okay. I did a Serial.print() of the r, g, b values and they are within range and changing at each read, so I believe the problem lies in tft functions no longer working after the SD begins.

Thanks again for all the help!

Oh,here are both initializing lines:

Adafruit_SSD1351 tft = Adafruit_SSD1351(cs_oled, dc, rst); // HARDWARE SPI

if (!SD.begin(cs_sd)){ // HARDWARE SPI

@jahydin, looking at the SSD1351 display library from mikeseeh, the SPI is set to MODE3 while the SD library requires SPI MODE0. There is your conflict.

There is no simple way to fix this and this is a common problem with SPI. It is possible to switch modes on the fly but that tends to “glitch” the SPI bus causing problems.

The SSD1351 library only writes to the display, it does not read anything. You could use the software SPI mode for the display using only two extra pins for CLK and MOSI. The display CS line could be held low all the time since it is no longer on the common hardware SPI bus. :wink:

1 Like

Oh no!
Okay, I changed my tft init to :
Adafruit_SSD1351 tft = Adafruit_SSD1351(cs_oled, dc, mosi, sclk, rst); // SOFTWARE SPI

How do I hold the display line low?

@jahydin, for now just use the cs_oled pin you have presently connected. You will need to specify different pins for mosi and sclk since they will conflict with the hardware SPI pins.

So here is my new pin setup:

#define sclk      A0
#define mosi      A1
#define miso      A4
#define dc        D7
#define cs_oled   D6
#define cs_sd     A2
#define rst       D5

I have pins A5 and A3 still connected to sclk and mosi (for SD right?)
I also have pins A0 and A1 connected to sclk and mosi (for oled)

But now SD card failing to initialize.

@jahydin, here is the pin assignment as I believe it should be:

For microSD
  A5 = MOSI
  A4 = MISO
  A3 = SCLK
  A2 = cs_sd

  A1 = MOSI
  A0 = SCLK
  D7 = dc
  D6 = cs_oled
  D5 = rst

If you can, use a different pin for the OLED dc pin since that pin has the onboard LED connected to it as well. The Adafruit constructor call should be:

Adafruit_SSD1351 tft = Adafruit_SSD1351(cs_oled, dc, A1, A0, rst); // SOFTWARE SPI


So I have:

With this config my screen works but SD fails to initialize. :frowning:

Thanks for taking the time to help me btw. I really want to be able to use this screen for my Photon.

Well I finally got it to work, but it is far from stable. I’d say it works half the time, and only when I unplug my battery and pop it back in.The other times it will get to about half way than slow to a crawl then garble. Reset seems to cause the SD to fail init every time. I ordered a separate sd card reader in the hopes it will be better than the “all in one” package. Was really hoping to get this work though. ;(

Anyways, here is my code that got it to work:


 #include "Particle.h"
 #include "Adafruit_mfGFX.h"
 #include "Adafruit_SSD1351.h"
 #include "SdFat.h"

// SPI interface, these are the pins
 #define sclk      A3
 #define mosi      A5
 #define miso      A4
 #define dc        D4
 #define cs_oled   D6
 #define cs_sd     A2
 #define rst       D5
const uint8_t chipSelect = cs_sd;

// 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

// for read16 and read32 to work properly
void bmpDraw(char *filename, uint8_t x, uint8_t y);
uint16_t read16(File &f);
uint32_t read32(File &f);

// instanciate an SD object
SdFat SD;

Adafruit_SSD1351 tft = Adafruit_SSD1351(cs_oled, dc, rst); // HARDWARE SPI
//Adafruit_SSD1351 tft = Adafruit_SSD1351(cs_oled, dc, mosi, sclk, rst); // SOFTWARE SPI

File bmpFile;
int bmpWidth, bmpHeight;
uint8_t bmpDepth, bmpImageoffset;

void setup(void) {
  //while (!Serial.available());

  // initialize the OLED


  tft.fillScreen(BLUE); //make it colorful

  // initialize the SD Card
  Serial.print("Initializing SD card...");

  //if (!SD.begin(mosi, miso, sclk, cs_sd)) { // SOFTWARE SPI
  if (!SD.begin(chipSelect, SPI_HALF_SPEED)){ // HARDWARE SPI
  Serial.println("SD OK!");
  // draw the image from the card
  bmpDraw("logo.bmp", 0, 0);


void loop() {

// Code from

// 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.

 #define BUFFPIXEL 50

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();

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

  Serial.print("Loading image '");

  // Open requested file on SD card
  if ((bmpFile = == NULL) {
    Serial.print("File not found");

  // 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'
      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: ");

        // 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...
          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?
            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
    , 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++];

            tft.drawPixel(x+col, y+row, tft.Color565(r,g,b));
            // optimized!
          } // end pixel
        } // end scanline
        Serial.print("Loaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp

  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] =; // LSB
  ((uint8_t *)&result)[1] =; // MSB
  return result;

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

Reformatted by ScruffR
To get this just wrap your code in

 // your code

Sorry about the format. I have no idea how to get it in a nice window like everyone else… :confused:

Not sure if this would make a difference, but try

// either use
// or
// and call Particle.connect() after you're done
// or use both ST & SM together

An ext card reader shouldn’t be required and might not help anyway.

Seemed about the same with STSTEM_THREAD, and I thought I was at 100% success (even with resets) using the SEMI_AUTOMATIC mode, but it stopped working consistantly after a a few resets or so. Thanks!

Which power supply do you use?
I had similar problems some time ago and after replacing the power supply it worked.

OMG, lol. Yup, that’s it. I’ve been using a little Li-ion 850mAh. I was using my usb for power (and the serial prompts) in the beginning, but that’s when I was getting my code sorted out (the SPI_HALF_SPEED is critical). Once I didn’t need the serial prompts I switched to my battery for convenience, got the code working, but never thought my battery could be the reason things were erratic.

Happy to say everything is running perfectly now. Multiple restarts and flashes and it draws the entire thing within a second!

Thanks to everyone who took the time to help me out, and especially you for putting this tutorial up to begin with. :smile:

Oh… so what power source did you end up using…?

1 Like

BTW, I just bundled up @MikSeeH’s code with a spark.json and slightly tweaked demos from Adafruit and published it to particle build as Adafruit_1351_Photon. So you can now include it via the Libraries tab in the Web IDE and get a quick start from the example code.

I didn’t (yet) manage to get the SD card working though, so if anyone here would like to help add that and add in a second example, it would be much appreciated. My copy is at

@nfriedly your test code is a huge help, right now I am trying to get the SDFat library working, but when I try to include it, it always gives this error:

If you are using Dev (as I assume by your screenshot) you’d need to have the library files locally stored in your project folder and #include "SdFat.h" I’d say.

But actually I’d rather do this in Build since SdFat is quite an elaborate library to include locally :wink: