SSD1305 issues on SPI


#1

Hi all,

Going a bit crazy here, seem to be farther behind than I was when I started working on this. Long story short, I’ve got a Newhaven OLED display, 128x32, attached to a Boron over SPI. It’s on a PCB with a few other bits and pieces, but right now I’m testing it in isolation. Earlier today, I had something that vaguely resembled a chopped up version of the image I want, but I’m now at the point that I can’t actually get an image on the screen at all. Can’t even get garbage. I’ve scoped the signal, and it’s definitely there. Rise and fall times aren’t great, but that may be my cheap USB scope more than anything. The code is loosely based on Adafruit’s SSD1305 driver, please note that this is test software and ended up with everything dumped in one file.

#include <Particle.h>
#include <SPI.h>

#define oled_cs_pin                       A4
#define oled_dc_pin                       D4
#define oled_reset                        D7

#define SSD1305_LCDWIDTH                  128
#define SSD1305_LCDHEIGHT                 32
#define SSD1305_SETLOWCOLUMN 0x00
#define SSD1305_SETHIGHCOLUMN 0x10
#define SSD1305_MEMORYMODE 0x20
#define SSD1305_SETCOLADDR 0x21
#define SSD1305_SETPAGEADDR 0x22
#define SSD1305_SETSTARTLINE 0x40

#define SSD1305_SETCONTRAST 0x81
#define SSD1305_SETBRIGHTNESS 0x82

#define SSD1305_SETLUT 0x91

#define SSD1305_SEGREMAP 0xA0
#define SSD1305_DISPLAYALLON_RESUME 0xA4
#define SSD1305_DISPLAYALLON 0xA5
#define SSD1305_NORMALDISPLAY 0xA6
#define SSD1305_INVERTDISPLAY 0xA7
#define SSD1305_SETMULTIPLEX 0xA8
#define SSD1305_DISPLAYDIM 0xAC
#define SSD1305_MASTERCONFIG 0x8E // AD
#define SSD1305_DISPLAYOFF 0xAE
#define SSD1305_DISPLAYON 0xAF

#define SSD1305_SETPAGESTART 0xB0

#define SSD1305_COMSCANINC 0xC0
#define SSD1305_COMSCANDEC 0xC8
#define SSD1305_SETDISPLAYOFFSET 0xD3
#define SSD1305_SETDISPLAYCLOCKDIV 0xD5
#define SSD1305_SETAREACOLOR 0xD8
#define SSD1305_SETPRECHARGE 0xD9
#define SSD1305_SETCOMPINS 0xDA
#define SSD1305_SETVCOMLEVEL 0xDB

    __SPISettings spi_settings{1*MHZ, MSBFIRST, SPI_MODE0};

static uint8_t buffer[SSD1305_LCDHEIGHT * SSD1305_LCDWIDTH / 8] = { 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8,
    0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF0, 0x18, 0x08, 0x08, 0x18, 0xF0, 0xE0, 0x00, 0xE0, 0xF0,
    0x18, 0x08, 0x88, 0x98, 0x90, 0x00, 0xE0, 0xF0, 0x18, 0x08, 0x88, 0x98, 0x90, 0x00, 0xF8, 0xF8,
    0x00, 0xF8, 0xF8, 0x70, 0xC0, 0x00, 0xF8, 0xF8, 0x00, 0xE0, 0xF0, 0x18, 0x08, 0x88, 0x98, 0x90,
    0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x88, 0x88, 0x88, 0x00, 0xE0, 0xF8, 0x18, 0xF8, 0xE0, 0x00,
    0x00, 0xF8, 0xF8, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0x00,
    0xF8, 0xF8, 0x00, 0xF8, 0xF8, 0x88, 0x88, 0x88, 0xF8, 0x70, 0x00, 0xF8, 0xF8, 0x88, 0x88, 0x88,
    0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F,
    0x08, 0x08, 0x08, 0x08, 0x00, 0x03, 0x07, 0x0C, 0x08, 0x08, 0x0C, 0x07, 0x03, 0x00, 0x03, 0x07,
    0x0C, 0x08, 0x08, 0x0F, 0x07, 0x00, 0x03, 0x07, 0x0C, 0x08, 0x08, 0x0F, 0x07, 0x00, 0x0F, 0x0F,
    0x00, 0x0F, 0x0F, 0x00, 0x01, 0x07, 0x0F, 0x0F, 0x00, 0x03, 0x07, 0x0C, 0x08, 0x08, 0x0F, 0x07,
    0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x03, 0x02, 0x03, 0x0F, 0x0E,
    0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x00, 0x07, 0x0F, 0x08, 0x08, 0x08,
    0x0F, 0x07, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x01, 0x07, 0x0E, 0x08, 0x0F, 0x0F, 0x08, 0x08, 0x08,
    0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF8, 0x0C, 0x04, 0x04, 0x0C, 0x08, 0x00, 0xF0,
    0xF8, 0x0C, 0x04, 0x04, 0x0C, 0xF8, 0xF0, 0x00, 0xFC, 0xFC, 0x38, 0xE0, 0x80, 0xFC, 0xFC, 0x00,
    0x04, 0x04, 0xFC, 0xFC, 0x04, 0x04, 0x00, 0xF0, 0xFC, 0x0C, 0xFC, 0xF0, 0x00, 0x00, 0xF0, 0xF8,
    0x0C, 0x04, 0x04, 0x0C, 0x08, 0x00, 0x04, 0x04, 0xFC, 0xFC, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00,
    0x38, 0x7C, 0x64, 0xE4, 0xC4, 0xCC, 0x88, 0x00, 0xFC, 0xFC, 0x00, 0x00, 0x00, 0xFC, 0xFC, 0x00,
    0xFC, 0xFC, 0x84, 0x84, 0x84, 0xFC, 0x78, 0x00, 0xFC, 0xFC, 0x84, 0x84, 0x84, 0xFC, 0x78, 0x00,
    0xF0, 0xF8, 0x0C, 0x04, 0x04, 0x0C, 0xF8, 0xF0, 0x00, 0xFC, 0xFC, 0x44, 0x44, 0xC4, 0xFC, 0x38,
    0x00, 0x04, 0x04, 0xFC, 0xFC, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x06, 0x04, 0x04, 0x06, 0x02, 0x00, 0x01,
    0x03, 0x06, 0x04, 0x04, 0x06, 0x03, 0x01, 0x00, 0x07, 0x07, 0x00, 0x00, 0x03, 0x07, 0x07, 0x00,
    0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x07, 0x07, 0x01, 0x01, 0x01, 0x07, 0x07, 0x00, 0x01, 0x03,
    0x06, 0x04, 0x04, 0x06, 0x02, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x02, 0x06, 0x04, 0x04, 0x04, 0x07, 0x03, 0x00, 0x03, 0x07, 0x04, 0x04, 0x04, 0x07, 0x03, 0x00,
    0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x03, 0x06, 0x04, 0x04, 0x06, 0x03, 0x01, 0x00, 0x07, 0x07, 0x00, 0x00, 0x00, 0x03, 0x07,
    0x04, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  };

SYSTEM_THREAD(ENABLED);

void setup() {
    SPI.begin();
    Serial.begin(57600);
    delay(1000);
    pinMode(oled_cs_pin, OUTPUT);
    pinMode(oled_dc_pin, OUTPUT);
    pinMode(oled_reset, OUTPUT);

    digitalWrite(oled_reset, LOW);
    delay(5);
    digitalWrite(oled_reset, HIGH);
    delay(5);
    digitalWrite(oled_cs_pin, LOW);
    digitalWrite(oled_dc_pin, LOW);
    delay(1);
    while(SPI.beginTransaction(spi_settings) < 0);
    Command(SSD1305_DISPLAYOFF);
    Command(SSD1305_SETDISPLAYCLOCKDIV);
    Command(0x10);
    Command(SSD1305_SETMULTIPLEX);
    Command(0x1F);
    Command(SSD1305_SETPRECHARGE);
    Command(0xD2);
    Command(SSD1305_SEGREMAP | 0x01);
    Command(SSD1305_COMSCANDEC);
    Command(SSD1305_SETCOMPINS);
    Command(0x12);
    Command(SSD1305_SETVCOMLEVEL);
    Command(0x08);
    Command(SSD1305_SETPAGEADDR);
    Command(0x03);
    Command(0x07);
    Command(SSD1305_SETCOLADDR);
    Command(0x04);
    Command(0x83);
    Command(SSD1305_DISPLAYALLON_RESUME);
    Command(SSD1305_NORMALDISPLAY);
    Command(SSD1305_SETSTARTLINE|0x20);
    Command(SSD1305_DISPLAYON);

    SPI.endTransaction();
    Serial.println("Setup complete.");
    digitalWrite(oled_cs_pin, HIGH);
}

void loop() {
    digitalWrite(oled_cs_pin, LOW);
    while(SPI.beginTransaction(spi_settings) < 0);

    uint16_t x = 0;
    for(int page = 4; page < 8; page++){
        digitalWrite(oled_dc_pin, LOW);
        Command(SSD1305_SETPAGESTART + page);
        Command(SSD1305_SETLOWCOLUMN | 0);
        Command(SSD1305_SETHIGHCOLUMN | 0);
        delay(1);
        digitalWrite(oled_dc_pin, HIGH);
        for(uint8_t w = 0; w <128; w++){
                SPI.transfer(buffer[x++]);
        }
    }
    digitalWrite(oled_cs_pin, HIGH);
    SPI.endTransaction();
    
    delay(150);
}

void Command(uint8_t comm){
    digitalWrite(oled_dc_pin, LOW);
    SPI.transfer(comm);
}

I’m running the Boron on 1.1.1 through Workbench. Anyone have a thought or two? Oh, the non-standard CS pin is right, I’ve got a MicroSD card on the same bus.

Thanks,
Peter


#2

Not saying what I have seen over the years is valid here.
I have never come across a LCD that you can send multiple “commands” within a CS.
I have only seen that you can send multiple data bytes (I.E. pixel data) within a CS.


#3

I would use digitalPinSet() and digitalPinReset() instead of digitalWrite().

You seem to be very aware of the criticality of timing with SPI.

Maybe use SYSTEM_THREAD(ENABLED); ?

You say it is based upon Adafruit_SSD1305 library, perhaps you could look at the Adafruit_SSD1306_RK library to see how that has been done. I have only used an I2C OLED display like this with the Gen3.


#4

Thanks for the help guys! We’ve made some progress, although it’s still a bit twitchy. Looks like it might be a small fight between the microsd card and the oled.


#5

@psherk you said:

Looks like it might be a small fight between the microsd card and the oled.

I too have a yet to be understood issue between an SSD130x display interfaced via SPI and an SDCARD using the SDFat lib, also using SPI.

I had to deploy a workaround which was:

  • initialise the SDFat lib before initialising the display
  • Every time I access the SDCARD I reinitialise the display when SDCARD access has completed

Am pretty sure that this is not a circuitry issue.

Please report back if this sorts your problem. We should work on solving the underlying issue!


#6

Hi @UMD, interesting, that very much reflects the direction I was starting to go. We’ve deployed a temporary fix by microsurgery on the PCB, swapping the OLED to SPI1, but if I can avoid that it would be excellent. I’ve got more work to do on it next week, so I’ll try to get some more info for you.

Thanks,
Peter


#7

@psherk, I made a slight modification to the Adafruit SSD1306 library. Pulled the following SPI initialisation out of Adafruit_SSD1306::begin() into its own method, ResetInterface(), so it can be used separately to the display controller initialisation.

As stated, I call this at the end of any session using the SDCARD.

void Adafruit_SSD1306::ResetInterface(void)
{
	digitalWrite(cs, HIGH);				// not selected
	SPI.setBitOrder(MSBFIRST);
	SPI.setClockDivider(SPI_CLOCK_DIV8);	// 72MHz / 8 = 9Mhz
	SPI.setDataMode(0);

	// Particle doco for SPI.begin says:
	// Note: The SPI firmware ONLY initializes the user-specified
	// slave-select pin as an OUTPUT.
	// The user's code must control the slave-select pin with
	// digitalWrite() before and after each SPI transfer for the
	// desired SPI slave device.
	// Calling SPI.end() does NOT reset the pin mode of the SPI pins.
	SPI.begin(SPI_MODE_MASTER, cs);		// using SS A2
} // Adafruit_SSD1306::ResetInterface(void)


#8

A better way to deal with differences between protocols on the same SPI bus would be to use SPI.beginTransaction() and SPI.endTransaction().
You can define the respective settings in an instance of SPISettings and pass that to the SPI.beginTransaction() call. That way the SPI object should take care of applying the correct settings for each transaction.

AFAICT, the SdFat and Adafruit_SSD1305_Library would support transactions already, but there is an open issue with the Particle definition of a macro that should enable that feature

Maybe commenting on that issue may help getting Particle’s attention on that (both libraries are already mentioned in the opening comment on that issue).

This should be low hangig fruit for Particle to get fixed, but I have not had any reply on that front so far :pensive:


#9

@scruffr, your valuable feedback kicked off a search in my code to discover this in my changelog for my modified Adafruit_SSD1306 lib:

2019-03-14 HR

  • Adafruit_SSD1306:: added spiBegin() (calls SPI.beginTransaction())
    and spiEnd() (calls SPI.endTransaction())

I had the exact same SDFat vs Display lockup issue with a bmpdraw routine (original source: https://github.com/adafruit/Adafruit_ILI9340/blob/master/examples/spitftbitmap/spitftbitmap.ino) four months ago. Resolved this issue by implementing calls to my above mentioned display.spiBegin() and display.spiEnd() functions.

Fast forward three months where I went on to cure the SDFat vs Display lockup issue in another piece of code using a completely different workaround as listed earlier in this ticket!

Talk about failing memory…

Anyhow, now realise from your comments that the SPI.beginTransaction() and SPI.endTransaction() should be handled within the Display (as a minimum) and SDFat libs.

Unfortunately the display library I am using (ADAFRUIT_SSD1306 v0.0.2 found in the Particle webIDE) does not have any mention of SPI_HAS_TRANSACTION, so perhaps I should be using another version (but note that the required macro has not been implemented by Particle) so as to have a proper solution.


#10

Since this topic is aimed at SSD1305 rather than SSD1306 I didn’t look at the other library :wink:
However, there is an Adafruit_SSD1306_RK library that does use SPI transactions (and is maintained by @rickkas7)


#11

@ScruffR, good point re SSD1305 vs SSD1306.

From my past use of other @rickkas7 libraries, my bet is that this will be a perfect solution!

I will migrate my code to use it and report back - @psherk you should be well on your way to a solution now too…


#12

My SSD1306 library has not been tested with SPI. I only use the I2C version of the SSD1306. It should work with SPI, but I never tested it.

Also note the two extra parameters for width and height. Those are compile time constants with the Adafruit library, but I switched to to be set at runtime because there are so many dimensions of displays now and you can’t set a compile-time define for a Particle library.


#13

@rickkas7, have just started to review your code and will take on board that it has not been tested with an SPI interface.

I will probably end up just sticking with my code and migrate any learnings from your code due to my cutting out sections that are not being used to conserve code space.

I note that you have an SPI.beginTransaction() (good) but not an SPI.endTransaction() (bad, I think)… This will be a problem for the issue at hand, which is the clash with the SDFat lib which also uses SPI.

https://docs.particle.io/reference/device-os/firmware/photon/#begintransaction-


#14

Wow, thanks very much for all the info guys. @ScruffR, would I be able to define the SPI_HAS_TRANSACTION myself, or is that an issue that has to be handled on Particle’s end?

@UMD, due to time constraints I’ve just shifted the screen over to SPI1 temporarily, but I’ll be testing this week to see if I can get everything working on a single channel.


#15

You can, but only inside the respective libraries since each module is compiled independently and hence a #define SPI_HAS_TRANSACTION 1 would only apply to that one module (aka source file) where it is defined.

Hence having it in Particle.h (or spark_wiring_spi.h referenced therein) - which has to be included in every module that also uses SPI - would be the once-for-all solution.

But that won’t happen when there is so little interest in the issue I raised and asked for support

Broader support for that issue by multiple people might help raising attention.


#16

@psherk, have just implemented SPI.beginTransaction() and SPI.endTransaction() in the SSD1306 library by placing SPI.beginTransaction() before an SPI data transfer and SPI.endTransaction() once it has completed.

Here are some code snippets:

// Indicates that we have spi.beginTransaction() and spi.endTransaction()
#define SPI_HAS_TRANSACTION

#ifdef SPI_HAS_TRANSACTION
/ Reconfigures the SPI peripheral with the supplied settings.
// In addition to reconfiguring the SPI peripheral, beginTransaction()
// also acquires the SPI peripheral lock, blocking other threads from
// using the selected SPI peripheral until SPI.endTransaction() is called.
void Adafruit_SSD1306::spiBegin(void) {
	SPI.beginTransaction(__SPISettings(9*MHZ, MSBFIRST, SPI_MODE0));
}

// Releases the SPI peripheral.
// This function releases the SPI peripheral lock, allowing other threads to use it.
void Adafruit_SSD1306::spiEnd(void) {
	SPI.endTransaction();
}
#endif

void Adafruit_SSD1306::invertDisplay(uint8_t i) {

#ifdef SPI_HAS_TRANSACTION
  spiBegin();
#endif
  if (i) {
    ssd1306_command(SSD1306_INVERTDISPLAY);
  } else {
    ssd1306_command(SSD1306_NORMALDISPLAY);
  }
#ifdef SPI_HAS_TRANSACTION
  spiEnd();
#endif
}

Am pretty sure this is case closed for you too. Please mark it as “solved” if this is the case!