New adafruit GFX 1.4.x libs doesn't work for me

Status
Not open for further replies.
Ughhh - retesting here and I also get the black screen issue. :(

Earlier I tested with version 1.2.4. Looks like something broke starting with version 1.2.6. I'm putting this library back on my list of issues to investigate.

Just to be realistic, I probably won't be able to dig into what went wrong for at least a couple weeks. In the meantime, I recommend using the library manager to uninstall 1.2.8 and install version 1.2.5. I tested just now and 1.2.5 seems to be working fine.

Here's how the buttons render at the end of "graphictest" with 1.2.5.

View attachment 16360

For the heck of it, I picked up an ST7789 from Amazon (https://smile.amazon.com/MakerFocus-Display-1-3inch-Interface-Routines/dp/B07P9X3L7M)
But not sure if it will actually work?
That is the pins are marked(GND, VCC, SCL, SDA, RES, DC, BLK)
i.e. no CS pin?
 
Well I have made some more progress. I don't think that it is a problem in the Teensy though. I suspect that the library is broken from 1.26 to 1.29 but I do not have a real Arduino board to verify on (everything that I have on hand is a Teensy 3.X :D)

The problem is in the ST77XX displayInit method in Adafruit_ST77xx.cpp. The routine spits out a standard display initialization sequence.

Here is 1.25
Code:
void Adafruit_ST77xx::displayInit(const uint8_t *addr) {

  uint8_t  numCommands, numArgs;
  uint16_t ms;

  numCommands = pgm_read_byte(addr++);   // Number of commands to follow
  while(numCommands--) {                 // For each command...
    startWrite();

    writeCommand(pgm_read_byte(addr++)); //   Read, issue command
    numArgs  = pgm_read_byte(addr++);    //   Number of args to follow
    ms       = numArgs & ST_CMD_DELAY;   //   If hibit set, delay follows args
    numArgs &= ~ST_CMD_DELAY;            //   Mask out delay bit
    while(numArgs--) {                   //   For each argument...
      spiWrite(pgm_read_byte(addr++));  //     Read, issue argument
    }
    endWrite();

    if(ms) {
      ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
      if(ms == 255) ms = 500;     // If 255, delay for 500 ms
      delay(ms);
    }
  }
}

and 1.26.

Code:
void Adafruit_ST77xx::displayInit(const uint8_t *addr) {

  uint8_t  numCommands, numArgs;
  uint16_t ms;

  startWrite();
  numCommands = pgm_read_byte(addr++);   // Number of commands to follow
  while(numCommands--) {                 // For each command...

    writeCommand(pgm_read_byte(addr++)); // Read, issue command
    numArgs  = pgm_read_byte(addr++);    // Number of args to follow
    ms       = numArgs & ST_CMD_DELAY;   // If hibit set, delay follows args
    numArgs &= ~ST_CMD_DELAY;            // Mask out delay bit
    while(numArgs--) {                   // For each argument...
      spiWrite(pgm_read_byte(addr++));   // Read, issue argument
    }

    if(ms) {
      ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
      if(ms == 255) ms = 500;     // If 255, delay for 500 ms
      delay(ms);
    }
  }
  endWrite();
}

The startWrite() and endWrite() routines were moved outside of the while loop (for unknown reasons) and this is what breaks on the ST7789 display

They seem to have noticed this and and tried to toggle the CS line inside the while loop as a patch for the ST7789. It didn't work

1.29
Code:
void Adafruit_ST77xx::displayInit(const uint8_t *addr) {

  uint8_t  numCommands, numArgs;
  uint16_t ms;

  startWrite();
  numCommands = pgm_read_byte(addr++);   // Number of commands to follow
  while(numCommands--) {                 // For each command...

    writeCommand(pgm_read_byte(addr++)); // Read, issue command
    numArgs  = pgm_read_byte(addr++);    // Number of args to follow
    ms       = numArgs & ST_CMD_DELAY;   // If hibit set, delay follows args
    numArgs &= ~ST_CMD_DELAY;            // Mask out delay bit
    while(numArgs--) {                   // For each argument...
      spiWrite(pgm_read_byte(addr++));   // Read, issue argument
    }
    SPI_CS_HIGH(); SPI_CS_LOW();  // ST7789 needs chip deselect after each

    if(ms) {
      ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
      if(ms == 255) ms = 500;     // If 255, delay for 500 ms
      delay(ms);
    }
  }
  endWrite();
}

so here is a patched 1.29 routine that works for me

Code:
void Adafruit_ST77xx::displayInit(const uint8_t *addr) {

  uint8_t  numCommands, numArgs;
  uint16_t ms;


  numCommands = pgm_read_byte(addr++);   // Number of commands to follow
  while(numCommands--) {                 // For each command...
	startWrite();

    writeCommand(pgm_read_byte(addr++)); // Read, issue command
    numArgs  = pgm_read_byte(addr++);    // Number of args to follow
    ms       = numArgs & ST_CMD_DELAY;   // If hibit set, delay follows args
    numArgs &= ~ST_CMD_DELAY;            // Mask out delay bit
    while(numArgs--) {                   // For each argument...
      spiWrite(pgm_read_byte(addr++));   // Read, issue argument
    }
//    SPI_CS_HIGH(); SPI_CS_LOW();  // ST7789 needs chip deselect after each
	endWrite();

    if(ms) {
      ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
      if(ms == 255) ms = 500;     // If 255, delay for 500 ms
      delay(ms);
    }
  }
}

IMG_4870.jpg
 
They seem to have noticed this and and tried to toggle the CS line inside the while loop as a patch for the ST7789. It didn't work

Oh, that's probably not going to work on Teensy 3.6 if they're using direct register I/O. The pins default to slew rate limiting, which is a feature you definitely do want if running the signals over those inches-long wires. With slew rate limiting, back-to-back writes directly to the register on Teensy 3.6 runs so fast (only 5 or 10 ns) that the pin's voltage can't fully change!
 
Hmmm can't edit my old post - anyway please change the references to 1.29 to 1.28 (the latest issue)

Based on Paul's insight I grabbed a fresh copy of 1.28 and added a 10uS delay between CS_HIGH and CS_LOW. Pleased to report that it now works with this change only. Tried smaller delays and for my breadboard setup with long leads 3uS was the shortest delay that would still work
 
I have encountered a problem, was searching for a solution, and found this thread. Hope it's OK to post here!

Yesterday I spoke with a friend who is unable to get his I2C OLED working with a Teensy. No problem say I, I have a sketch which does just that. I still have the hardware built on a breadboard, so I plug it in, and the last loaded sketch is operational and the screen shows the Adafuit logo before continuing.

Note: I very recently upgraded to Arduino 1.8.13.

So I find the sketch and try to compile, but there are a number of problems! The sketch fails to compile. I see that I have missing libraries (I have no idea why). So using the library manager I install Adafruit GFX library v1.10.3 and Adafruit SSD1306 v2.4.1 , and he sketch now compiles. I try to upload to my Teensy (LC) only to find I need to install Teensyduino. So I install 1.53 and now I can upload. To my surprise the OLED screen is blank, exactly the same as my friend reported.

Today I tried to debug. The Teensy (LC) Blink sketch works, as does the Serial Monitor. When I run the old sketch it compiles and loads but I see no serial output, which I found confusing. I now think that the code must get stuck in the initialiser "Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);" for software SPI.

Before I dive into debugging, is anyone aware of any known issues which might cause this?

Note: my friend is using a I2C display and did have it working with a Nano.

Thank you in advance for any help.
 
I have encountered a problem, was searching for a solution, and found this thread. Hope it's OK to post here!
In all cases like this it helps if you maybe post exactly what you are trying to run. Or tell us that it example X, and I did not edit anything. But better to actually show simple example code.
Also might help to see picture of your setup. As I am confused.
I thought you mentioned in first paragraph that it was: I2C OLED and it worked with a NANO. Question is which I2C display?

But than you mention. I think your setup and talk about SPI pins. Note: I have not done much with the more recent Adafruit_SSD1306 library.

They do have Example sketches one for the two main sizes... Like:128x32
Which defines those like:
Code:
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI   9
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
Note this one may try to emulate SPI. They do have another constructor in their file:
Code:
* Comment out above, uncomment this block to use hardware SPI
#define OLED_DC     6
#define OLED_CS     7
#define OLED_RESET  8
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  &SPI, OLED_DC, OLED_RESET, OLED_CS);
*/
That I would typically use... The main thing is you need to match which pins you are actually wiring up to what you define in your sketch.
 
Dear KurtE, thank you for the reply, sorry if I wasn't clear.

I am trying to help my friend who is new to the Teensy world, and is still learning how to program using the Arduino environment.

My OLED = 128x64 "generic" part, SPI (VCC,GND,D0,D1,RST,DC) pins, Ebay purchase (some time ago) so no known brand.

Friends OLED = 128x64 "generic" part, (VCC,GND, SCL,SDA) pins. Ebay purchase from China.

My OLED "old" existing project, Teensy LC based, last worked on in 2018. Used Adafruit libraries, worked without problems.

Friends recent OLED project. Used Nano. I have no other information at the moment (I will get him to post), but apparently he got it working. I have no idea about libraries.

Friends most recent OLED project. Used Teensy LC. No information on libraries or versions. He apparently ported his working code, but now gets a blank screen.

My "current" project is the OLED example "sdd1306_128x64_spi.ino". I also get a blank screen. I added Serial I/O to confirm that setup() was been executed, but saw no serial I/O, which is why I suspected the constructor.

Code:
/**************************************************************************
 This is an example for our Monochrome OLEDs based on SSD1306 drivers

 Pick one up today in the adafruit shop!
 ------> http://www.adafruit.com/category/63_98

 This example is for a 128x64 pixel display using 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,
 with contributions from the open source community.
 BSD license, check license.txt for more information
 All text above, and the splash screen below must be
 included in any redistribution.
 **************************************************************************/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI  11
#define OLED_CLK   14
#define OLED_DC     1
#define OLED_CS    12
#define OLED_RESET  0
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

/* 
Comment out above, uncomment this block to use hardware SPI
#define OLED_DC     6
#define OLED_CS     7
#define OLED_RESET  8
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  &SPI, OLED_DC, OLED_RESET, OLED_CS);
*/

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

void setup() {
  Serial.begin(9600);
  delay(2000);
  Serial.println(F("Serial initialised"));

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  Serial.println(F("SSD1306 initialised"));

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines

  testdrawrect();      // Draw rectangles (outlines)

  testfillrect();      // Draw rectangles (filled)

  testdrawcircle();    // Draw circles (outlines)

  testfillcircle();    // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle();  // Draw triangles (outlines)

  testfilltriangle();  // Draw triangles (filled)

  testdrawchar();      // Draw characters of the default font

  testdrawstyles();    // Draw 'stylized' characters

  testscrolltext();    // Draw scrolling text

  testdrawbitmap();    // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
  delay(1000);
  Serial.println(F("Serial "));
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

void testdrawrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=2) {
    display.drawRect(i, i, display.width()-2*i, display.height()-2*i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testfillrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=3) {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2);             // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x")); display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  int8_t f, icons[NUMFLAKES][3];

  // Initialize 'snowflake' positions
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }

  for(;;) { // Loop forever...
    display.clearDisplay(); // Clear the display buffer

    // Draw each snowflake:
    for(f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
    }

    display.display(); // Show the display buffer on the screen
    delay(200);        // Pause for 1/10 second

    // Then update coordinates of each flake...
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][YPOS] += icons[f][DELTAY];
      // If snowflake is off the bottom of the screen...
      if (icons[f][YPOS] >= display.height()) {
        // Reinitialize to a random position, just off the top
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
      }
    }
  }
}
 
I have now made progress! The OLED is now getting written to.

For debugging I used pin13 of the Teensy LC and blinked the LED. I confirmed that the code was hanging in the Adafruit_SSD1306 constructor.

On a hunch I changed the #define HAVE_PORTREG to ZHAVE_PORTREG, having determined that whatever it was it was "defined". This allows the OLED screen to work, although I still don't understand 100%.

In Adafruit_SSD1306.h there is the following code which "defines" HAVE_PORTREG.

Code:
#if defined(__AVR__)
typedef volatile uint8_t PortReg;
typedef uint8_t PortMask;
#define HAVE_PORTREG
#elif defined(__SAM3X8E__)
typedef volatile RwReg PortReg;
typedef uint32_t PortMask;
#define HAVE_PORTREG
#elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) &&                      \
    !defined(ARDUINO_ARCH_MBED)
typedef volatile uint32_t PortReg;
typedef uint32_t PortMask;
#define HAVE_PORTREG
#endif

For Teensy HAVE_PORTREG ends up defined.

In Adafruit_SSD1306.c this snippet of code shows how it is used.

Code:
     pinMode(mosiPin, OUTPUT); // MOSI and SCLK outputs
      pinMode(clkPin, OUTPUT);
#ifdef HAVE_PORTREG
      mosiPort = (PortReg *)portOutputRegister(digitalPinToPort(mosiPin));
      mosiPinMask = digitalPinToBitMask(mosiPin);
      clkPort = (PortReg *)portOutputRegister(digitalPinToPort(clkPin));
      clkPinMask = digitalPinToBitMask(clkPin);
      *clkPort &= ~clkPinMask; // Clock low
#else
      digitalWrite(clkPin, LOW); // Clock low
#endif

I have never heard of PortReg and I have no idea if Teensy has it (I assume that it must have because the code compiles), but it appears to stop the OLED working with the Teensy.

So I have a fix for myself (and the SPI code), however the I2C code shouldn't be affected by this define.

It would be nice to figure out the "clean" solution, and I assume that this can be fed back to Adafruit?
 
OK, there appears to be a crash/lockup when "Adafruit_SSD1306::ssd1306_commandList" is called for the first time to send a SPI byte.

It looks to me like PortReg allows direct "CPU" register write and read, presumably to speed up I/O. Surprisingly Google returns virtually no information on this subject.

The following macro is the place of the crash...

Code:
    SSD1306_MODE_COMMAND

Code:
#ifdef HAVE_PORTREG
#define SSD1306_SELECT *csPort &= ~csPinMask;       ///< Device select
#define SSD1306_DESELECT *csPort |= csPinMask;      ///< Device deselect
#define SSD1306_MODE_COMMAND *dcPort &= ~dcPinMask; ///< Command mode
#define SSD1306_MODE_DATA *dcPort |= dcPinMask;     ///< Data mode
#else

where

Code:
#ifdef HAVE_PORTREG
    dcPort    = (PortReg *)portOutputRegister(digitalPinToPort(dcPin));
    dcPinMask =                               digitalPinToBitMask(dcPin);
    csPort    = (PortReg *)portOutputRegister(digitalPinToPort(csPin));
    csPinMask =                               digitalPinToBitMask(csPin);
#endif

I do not know how to write out the values of these variables. I tried Serial IO, without success. Since the code hangs in the constructor it never reaches the setup() code, where Serial IO does work.

I am guessing, but would appreciate advice as to how to confirm that this poke...

Code:
*dcPort &= ~dcPinMask

causes a lockup.
 
I will be honest and say I don't typically ever use the SoftwareSPI bitbang solution.

And yes most Teensy boards do have underlying Port registers and masks... Which can give you faster stuff as it does not have to do the mapping internally each time you call digitalWrite.

Personally you would better served just setting up to use normal SPI pins. Not sure if you will get away with CS pin on pin 12 as this is typically the MISO pin with SPI.

But assuming this was pin 2 instead of 12...

I would probably do something like:
Code:
  Comment out above, uncomment this block to use hardware SPI
#define OLED_DC     1
#define OLED_CS    2
#define OLED_RESET  0
  Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  &SPI, OLED_DC, OLED_RESET, OLED_CS);

Also since you are saying you are using an alternate clock pin for SPI as pin 13 is the default.
Code:
  SPI.setSCK(14);
  SPI.begin();
  if (!display.begin(SSD1306_SWITCHCAPVCC)) {  if (!display.begin(SSD1306_SWITCHCAPVCC)) {
 
On T4, Bitbang SPI can be quite fast, above 10MHZ.
We had to slow down the T4-Arduino_inbuilt software-SPI (to do 10MHz max), because it was too fast for some chips, resp. libaries that use it.
The advantage is, you can use any pin.
 
The point of raising this issue was to find out if anyone else had had the same problem, i.e. confirm that we really have a bug, and get a solution. Hopefully my problems might be helpful to others if they face the same problem and "google" for a solution.

I now have a work-around, not a clean solution. However speed wise for most SPI users the difference is likely to be minor.

I realise that digitalWrite has a reputation for being slow, but having looked at the Teensy code, it seems to be pretty lean and presumably fast. So no need to use fastdigitalWrite or the direct register peek/poke as used by Adafruit. Direct peek/poke however would be helpful for non Teeny Arduinos.

BTW I asked my friend to comment out the HAVE_PORTREG, and his sketch is now working with his I2C OLED.

I am digging deeper to see if I can figure out if the bug is in the Adafruit library, or in the Teensy core library (unlikely I know, but some features might not be used often and so might not get stress tested!).
 
BTW I moved the SPI clock from 13 to 14 for temporary use in debugging software SPI. I would eventually like to get hardware SPI working, however my application is not at all SPI speed sensitive.

In order to debug where the crash/lockup was in the constructor, I flash the LED on digital pin 13 as an indicator. I'm sure that there are better ways, but it allowed me to determine exactly where the lockup was. What I am puzzled about is why did the CPU lockup? I wondered if a critical register might get accidently poked???

If there is a good method to debug this kind of lockup on the Teensy, I would be most interested to learn about it.
 
As for better ways, I usually do a couple of things.
a) Use IO pins, with doing digitalWrites or digitalToggle and key points and watch with Logic Analyzer.

b) Put in some Serial.print like statements at different locations like
Code:
Serial.println("Before tft.begin"); Serial.flush();
display.begin(SSD1306_SWITCHCAPVCC);
Serial.println("After tft.begin"); Serial.flush();
Note: I don't always do flush. I only do if I get strange results like a hard crash where cached up Serial.writes don't output.


c) Like a) and b) but I continue into the libraries I am calling.
 
Or just buy a led or two and stick it on an unused pin, and do digitalWrites to that pin instead of pin 13 (BUILTIN_LED). Note, if you are buying a plain LED, you will need to add an appropriate resistor to the LED. I've bought the following special LEDs that Adafruit (LED sequins) and Sparkfun (led with resistors) sells that includes the resistor, so all I have to do is hook the end with the '+' to the pin and the other end with the '-' to ground.

Fortunately, I have a Microcenter nearby and and an independent electronics store, that I don't have to pay for shipping. And in the deep past, there were local Radio Shacks that had these as well. But it can be irksome to have to pay $14 shipping for a $4 part.

There are also some sellers that have LED packs with 6 or so LEDs that you can connect to 6 different pins, and a pin to connect to ground, that let you have 6 separate status LEDs.

 
Again Note: with TLC - You can use pin 14 as the SPI clock, so you can easily just do the SPI.setSCK(14); And then system will use it to run SPI... And you are free to use the LED on 13. But note: this works on many Teensy boards it does not work on T4.x as there is no alternative to Pin 13 as SCK on SPI object. Although you do have other SPI objects that you could move it to like SPI1.

And actually on TLC you also have the option to use SPI1 on pn pins 0, 1, 6. 20 (and there are alternative pins on this object) as you can see on the T-LC card
 
Dear KurtE and MichaelMeissner . thank you for your replies.

I do have plenty of LEDs and resistors (not to immediate hand) so I simply chose to use digital pin 13 and LED as my crude debug method.

For software SPI the choice of pins is not critical, so I simple moved the SPI clock temporarily to pin 14 to free up in 13 and its LED.

My project which was using the OLED is a keyboard scanner. The OLED was added to help with debug, but is not strictly necessary. These OLEDs are very nice and much more useful and smaller than the usual 16 x 2 (or 4) LCD display. Pity that their prices seem to have doubled in the past year.
 
I have spent more time trying to understand what might be going wrong. Although I have a perfect work around, I feel that if there is a bug I ought to find it and share a solution with the community.

So I am now more or less certain that the lockup is caused when the standard digitalWrite is not used and the GPIO registers are directly poked. Apparently writing to a non existent register will cause a "bus error". I have no idea how the Teensy handles this situation, but it appears to lock up. That is something for a different thread! But it was a clue.

In one of the teensy "system" source files called "pins_arduino.h" there are a set of macros...

Code:
#elif defined(KINETISL)
struct digital_pin_bitband_and_config_table_struct {
        volatile uint8_t *reg;
        volatile uint32_t *config;
	uint8_t mask;
};
extern const struct digital_pin_bitband_and_config_table_struct digital_pin_to_info_PGM[];
// compatibility macros
#define digitalPinToPort(pin) (pin)
#define digitalPinToBitMask(pin) (digital_pin_to_info_PGM[(pin)].mask)
#define portOutputRegister(pin) ((digital_pin_to_info_PGM[(pin)].reg + 0))
#define portSetRegister(pin)    ((digital_pin_to_info_PGM[(pin)].reg + 4))
#define portClearRegister(pin)  ((digital_pin_to_info_PGM[(pin)].reg + 8))
#define portToggleRegister(pin) ((digital_pin_to_info_PGM[(pin)].reg + 12))
#define portInputRegister(pin)  ((digital_pin_to_info_PGM[(pin)].reg + 16))
#define portModeRegister(pin)   ((digital_pin_to_info_PGM[(pin)].reg + 20))
#define portConfigRegister(pin) ((digital_pin_to_info_PGM[(pin)].config))
#define digitalPinToPortReg(pin) (portOutputRegister(pin))
//#define digitalPinToBit(pin) (1)

#endif

Note that these macros are specifically for the Teensy LC (KINETISL).
They are for backwards compatibility with the AVR when direct memory peek and poke are used to access GPIO. As mentioned this can dramatically speed up I/O.
Now the AVR has 8 bit registers for the PORT control, whereas the Teensy 3.0 and above have 32 bit. The Teensy LC appears to fall somewhere in the middle as the macros are for 8 bit accesses.

I wrote some code to generate this table...

Code:
//                     portInputRegister   portOutputRegister   portModeRegister      digitalPinToBitMask
// Pin =  0 Port = B16 In = 0xf8000052(*)  Out = 0xf8000042(*)  Mode = 0xf8000056 (*) PinMask= 00000001
// Pin =  1 Port = B17 In = 0xf8000052(*)  Out = 0xf8000042(*)  Mode = 0xf8000056 (*) PinMask= 00000002
// Pin =  2 Port = D0  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000001
// Pin =  3 Port = A1  In = 0xf8000010     Out = 0xf8000000     Mode = 0xf8000014     PinMask= 00000002
// Pin =  4 Port = A2  In = 0xf8000010     Out = 0xf8000000     Mode = 0xf8000014     PinMask= 00000004
// Pin =  5 Port = D7  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000080
// Pin =  6 Port = D4  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000010
// Pin =  7 Port = D2  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000004
// Pin =  8 Port = D3  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000008
// Pin =  9 Port = C3  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000008
// Pin = 10 Port = C4  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000010
// Pin = 11 Port = C6  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000040
// Pin = 12 Port = C7  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000080
// Pin = 13 Port = C5  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000020
// Pin = 14 Port = D1  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000002
// Pin = 15 Port = C0  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000001
// Pin = 16 Port = B0  In = 0xf8000050     Out = 0xf8000040     Mode = 0xf8000054     PinMask= 00000001
// Pin = 17 Port = B1  In = 0xf8000050     Out = 0xf8000040     Mode = 0xf8000054     PinMask= 00000002
// Pin = 18 Port = B3  In = 0xf8000050     Out = 0xf8000040     Mode = 0xf8000054     PinMask= 00000008
// Pin = 19 Port = B2  In = 0xf8000050     Out = 0xf8000040     Mode = 0xf8000054     PinMask= 00000004
// Pin = 20 Port = D5  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000020
// Pin = 21 Port = D6  In = 0xf80000d0     Out = 0xf80000c0     Mode = 0xf80000d4     PinMask= 00000040
// Pin = 22 Port = C1  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000002
// Pin = 23 Port = C2  In = 0xf8000090     Out = 0xf8000080     Mode = 0xf8000094     PinMask= 00000004
// Pin = 24 N/A        In = 0xf8000112 N/A Out = 0xf8000102     Mode = 0xf8000116     PinMask= 00000010
// Pin = 25 N/A        In = 0xf8000112 N/A Out = 0xf8000102     Mode = 0xf8000116     PinMask= 00000020
// Pin = 26 E30        In = 0xf8000113(*)  Out = 0xf8000103(*)  Mode = 0xf8000117(*)  PinMask= 00000040

Now pins 24 and 25 are a bit of an anomoloy since they don't physically exist. Digital pin 30 is shared with the DAC.
Having initially got confused by reading the datasheet for the Teensy3.1 CPU (doh!) instead of the Teensy LC CPU, I finally realised my mistake.
Note that all digital pins on ports A, B, C and D are in the range 0 to 7 except Pin 0 which is B16, Pin 1 which is B17, and Pin 26 which is E30.

The macros generate invalid addresses for these 3 digital pins as marked with the asterix. Attempting to read or write to these registers using these macros will cause a bus error and lockup.

Guess which 2 pins I was using for RST and DC for the OLED. Yes digital pins 0 and 1.
Just a short while ago I moved the RST and DC connections to digital pins 9 and 10 and re-instated the #define HAVE_PORTREG. Guess what, the OLED now works, and sort of surprisingly (given that I thought that digitalWrite was quite efficient already) it refreshed much faster!

So I am going to claim that these psuedo 8 bit macros are broken for digital pins 0,1 and 30.
 
To help anyone who might be googling for a solution to having an OLED display not working with a Teensy LC, when using the Adafruit SSD1306 library (version 2.41 or similar).

If you are using the Teensy LC (NOTE specifically the LC) and you use digital pin 0, 1 or 26 for any of the 4 SPI connections, the code will lockup when run on the Teensy.

In the installed library file "Adafruit_SSD1306.h" there is a test of Arduino type (although not specifically for Teensies) which results in a #define HAVE_PORTREG being defined (i.e. it exists). When this #define exists the .cpp library code uses direct register access to set and clear GPIO port bits to speed up SPI and consequently OLED refresh.

There are two work-arounds
1) if you really must use one of these pins, then comment out the lines in Adafruit_SSD1306.h that have "#define HAVE_PORTREG".
2) Simply don't use any of these 3 pins. An added advantage is that the SPI will be faster.

Although I have not specifically checked the same advice applies even if you use the hardware SPI, since there are 2 other pins which need connecting to general GPIO. If possible don't use the 3 aforementioned pins.
 
Dear KurtE and MichaelMeissner . thank you for your replies.

I do have plenty of LEDs and resistors (not to immediate hand) so I simply chose to use digital pin 13 and LED as my crude debug method.

For software SPI the choice of pins is not critical, so I simple moved the SPI clock temporarily to pin 14 to free up in 13 and its LED.

My project which was using the OLED is a keyboard scanner. The OLED was added to help with debug, but is not strictly necessary. These OLEDs are very nice and much more useful and smaller than the usual 16 x 2 (or 4) LCD display. Pity that their prices seem to have doubled in the past year.

Indeed these small OLEDS are handy and useful for debug or other. Have only ever gotten i2c versions to use here. In the past year the default pin order seems to have changed on i2c to match QWIIC wiring or something and the Price does fluctuate. Fewer wires of i2c makes them easier to use.

Only Active devices can be properly written. The ARM processors will fault when an inactive device is used, the clocks have to be started first. Not sure how that applies to GPIO - using standard pinMode() to startup does the right thing as needed. Also the default code has all proper access and addressing encoded for all pins.
 
Having said all that, it appears that these macros are used elsewhere in the Teensy "system" source files.

The commonly used functions digitalWrite and digitalRead uses these macros.

e.g.

Code:
uint8_t digitalRead(uint8_t pin)
{
  if (pin >= CORE_NUM_DIGITAL) return 0; 
#ifdef KINETISK  
  return *portInputRegister(pin);
#else
  return (*portInputRegister(pin) & digitalPinToBitMask(pin)) ? 1 : 0;
#endif
}

So if my theory is correct, then using digitalWrite to pin 0, 1 or 26 should cause a lockup. Given that when HAVE_PORTREG is not defined the fallback is to use digitalWrite, which appears to have worked when I used pin 0 and pin 1.

I need to further investigate.
 
Sorry, this is now getting off topic (Adafruit SSD1306 / GFX library).

BTW digitalWrite works correctly with digital pin 0 and 1.

Note: I am using the terminology from the MC68000 :) , byte = 8 bits, word = 16 bits and long = 32bits. I have no idea what the correct ARM terminology is.

The macros generate invalid addresses for these 3 digital pins as marked with the asterix. Attempting to read or write to these registers using these macros will cause a bus error and lockup.

So I was wrong! The datasheet (chapter 42) says "The GPIO registers support 8-bit, 16-bit or 32-bit accesses".

The problem may lie with the way in which the read/write (peek/poke) is being done.

In digitalWrite (teensy code) I think that the access is type "uint8_t *", which results in a byte access, which is legal. The datahsheet shows the addresses of the GPIO registers as 32 bit on 4 byte boundaries, so B0 data out port would be 0xf800_0040. In the table the value shown is 0xf800_0042 which I interpreted as causing a bus error. I now believe that it will not, so long as the access is word or byte.

In the Adafruit library code the access type is defined in Adafruit_SSD1306.h as "uint32-t *". I now suspect that a 32 bit write or read to a non 4 byte aligned address will cause the lock-up, but I really need someone who better understands the CPU to confirm this or not!

So for digital pin 0, port B16, digitalWrite performs a byte read/write to 0xf8000_0042 to access a byte in the upper 16 bits (bits 16 to 23). This is where bit 16 is found. The mask of 0x01 now makes sense.

But for digital pin 0, port B16 the Adafruit code performs a 32bit read/write to 0xf800_0042 to access a 32 bit value, which results in (what the 68000 would call) an unaligned error. This is currently my best guess. TO BE CONFIRMED !!!
On stackoverflow I quickly found the following "On ARM-based systems you frequently cannot address a 32-bit word that is not aligned to a 4-byte boundary"

So the macros are correct, because the are used in the "unint8_t *" mode.

The Adafruit library code can be fixed by testing for a Teensy LC and using the following...

Code:
...
#elif defined(TEENSY_LC)
typedef volatile uint8_t PortReg;
typedef uint8_t PortMask;
#define HAVE_PORTREG
#elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) &&                      \
    !defined(ARDUINO_ARCH_MBED)
typedef volatile uint32_t PortReg;
typedef uint32_t PortMask;
#define HAVE_PORTREG
#endif

NOTE: "TEENSY_LC" above needs to be replaced with the correct #define, which I have no idea what this is for the LC!!!!
 
Final post!

I modified part of Adafruit_SSD1306.h to this. The #define for the Teensy LC is "KINETISL" (not TEENSY_LC !).

Code:
#if defined(__AVR__)
typedef volatile uint8_t PortReg;
typedef uint8_t PortMask;
#define HAVE_PORTREG
#elif defined(__SAM3X8E__)
typedef volatile RwReg PortReg;
typedef uint32_t PortMask;
#define HAVE_PORTREG
#elif defined(KINETISL)
// migry - fix for Teensy LC
typedef volatile uint8_t PortReg;
typedef uint8_t PortMask;
#define HAVE_PORTREG
#elif (defined(__arm__) || defined(ARDUINO_FEATHER52)) &&                      \
    !defined(ARDUINO_ARCH_MBED)
typedef volatile uint32_t PortReg;
typedef uint32_t PortMask;
#define HAVE_PORTREG
#endif

I reconnected RST and DC to Teensy LC digital pins 0 and 1 (the original wiring) and the sketch runs and the OLED displays correctly.

Although it might be rare, if you are a Teensy LC user, and you are using digital pins 0 and/or 1 with some custom library, just check to see how the library codes read and write access to these pins. Use of digitalWrite and digitalRead is 100% OK, but read the postings above in the case that direct register access is used (using the macros shown) for speed. Access using the wrong "type" of pointer will result in a lock-up.

Sorry for all the postings, but I hope that it helps out someone in the future, with this or related problem!
 
Status
Not open for further replies.
Back
Top