SSD1306 Library crashing Teensy 2.0

Status
Not open for further replies.

elkayem

Active member
I love these little 128x64 OLED displays, and have used them in many projects. However, I'm encountering a very odd issue. I have a menu driven system, and use a rotary encoder to select items on the menu. Pictures say it all, so here is a picture of the type of menu driven interface I am using:

IMG_1618r.jpg

When I turn the encoder knob, the highlight will scroll through Selection #1 through #4. The problem is, my Teensy 2.0 is crashing after I spin the knob a few times. The highlight will scroll through all of the selections a few times, and then it will hang.

Through a careful set of Serial.println lines, I have determined that it is crashing at the I2C write within the Adafruit_SSD1306 library, called when I call display.display(). I have plenty of memory (1156 bytes of dynamic memory), and see no indication that I am running out of memory.

There is something interesting I've noticed though. If you look at my code, you'll see that my println lines to the OLED have spaces that go all the way to the right side of the screen. This is so the highlight extends all the way across.

Code:
display.println(F("Selection #1         "));

If I remove one of the spaces, the crashes are less frequent, two spaces and even less frequent... Removing four spaces from the end seems to eliminate the crashes, though maybe if I play with it long enough I can get it to happen. That's a workaround, but doesn't seem to solve the real problem, and I can't have my code crashing even once in a while.

Any tips on what might be going on would be really appreciated. Or even how to debug the problem.

Here is source code to reproduce the issue. I've cut it down to the bare minimum to reproduce the problem:

Code:
#include <Wire.h>
#include <Adafruit_SSD1306.h>

#define ENC_A   14
#define ENC_B   15

#define OLED_RESET 17
Adafruit_SSD1306 display(OLED_RESET);

int encoderPos, encoderPosPrev;
 
void setup()
{

 pinMode(ENC_A, INPUT_PULLUP);
 pinMode(ENC_B, INPUT_PULLUP);

 display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // OLED I2C Address, may need to change for different device,
                                            // Check with I2C_Scanner
  updateSelection();

}


void loop()
{
  updateEncoderPos();  
}

void updateEncoderPos() {
    static int encoderA, encoderB, encoderA_prev;   

    encoderA = digitalRead(ENC_A); 
    encoderB = digitalRead(ENC_B);
      
    if((!encoderA) && (encoderA_prev)){ // A has gone from high to low 
      encoderPosPrev = encoderPos;
      encoderB ? encoderPos++ : encoderPos--;   
      updateSelection();
    }
    encoderA_prev = encoderA;     
}


void updateSelection() { // Called whenever encoder is turned
  display.clearDisplay();
  display.setCursor(0,0); 
  display.setTextColor(WHITE,BLACK);
  display.print(F("SELECTIONS"));
  display.setCursor(0,16);
  setHighlight(0,4);
  display.println(F("Selection #1         "));
  setHighlight(1,4);
  display.println(F("Selection #2         "));
  setHighlight(2,4);
  display.println(F("Selection #3         "));
  setHighlight(3,4);
  display.println(F("Selection #4         "));

  display.display(); 
}

void setHighlight(int menuItem, int numMenuItems) {
  if (mod(encoderPos, numMenuItems) == menuItem) {
    display.setTextColor(BLACK,WHITE);
  }
  else {
    display.setTextColor(WHITE,BLACK);
  }
}

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}
 
A few more observations: it seems to be more likely to crash if I spin the encoder knob fast. I can turn the knob slowly all day with no problems.

Also, I have seen a crash at least once now when removing four spaces from the end of my println statements, i.e.,
Code:
display.println(F("Selection #1     "));
instead of
Code:
display.println(F("Selection #1         "));

So my workaround really isn't a workaround. It just makes the problem much less likely to occur.
 
What does this F("something") function? What happens when you put your bare text into println() without that function? My guess is that it is somewhat very slow and ressource consuming...
 
F() is a macro that tells the compiler to store and access the string from flash memory rather than RAM, saving the RAM for more important things. I believe Paul Stoffregen wrote this macro ages ago, and it is certainly a useful one to know. I have a lot of these menu driven screens in my code, and would quickly run out of RAM if I didn't use it.

Nevertheless, I did try removing this macro to see if it makes any difference. It didn't. Good suggestion to try though.
 
A few more observations: it seems to be more likely to crash if I spin the encoder knob fast. I can turn the knob slowly all day with no problems.

I've tried to reproduce your problem here with T2 and OLED (but only 128x32, no rotary encoder) and just touching pins 14 and 15 to ground. I couldn't get it to fail, but that jives with your turning knob slowly. Further study required ...
 
Can you measure the voltage on SDA and SCL when it gets into the locked up state?

Hi Paul, I sense you're on to something with your question.

I looked at SCL and SDA on my scope. When the message gets through (no crash), it appears to me that a full I2C command to the display takes about 45 ms (it's a lot of data). When the Teensy crashes, the message is cut short, with the SCL held high, but the SDA held low.

Here are two images. The first shows a full message:
DS1Z_QuickPrint4.png

The second shows what happens when I get a crash. I only see part of the message, less than 5 ms in this case:
DS1Z_QuickPrint6.png
 
And one more example of the I2C when the chip freezes up. This one is interesting, as it looks like SCK freezes up, but there is one more bit of data that gets sent. Still the same at freeze up (SCL high, SDA low). That seems true for all the cases I've seen, though the moment at which it freezes always seems to be different.

DS1Z_QuickPrint8.png
 
We see something like this occasionally with Teensy 3.2. It's pretty rare on Teensy 2.0, but perhaps still possible?

If noise or interference couples into the I2C signals, it's possible for Teensy to believe it has lost I2C bus control to another bus master. But there isn't another master talking on the bus, only noise.

Those rise times look pretty slow. You might first try adding 2K or even 1K pullup resistors on SDA and SCL. Maybe that will just magically fix the problem? Adding a small capacitance, like 47 or 100 pF between each signal to GND (close to the Teensy) might also help. If the signals run a fairly long distance, you might consider whether they're properly shielded from cross-talk of other digital signals. If some other digital signal runs close by for some distance, especially in the same cable, adding a 100 ohm resistor between the driving pin and the cable can really help.
 
Interesting idea. I’ll give these ideas a try when I get an opportunity. I can also try slowing down the I2C clock. It is running at 400K, as you noted by the short rise time. I do wonder, if Teensy thinks there is another master, who is holding SDA low?
 
Problem possibly resolved... I swapped out the OLED for another identical one, and the problem went away (so far). I twirled the wheel for several minutes and couldn't get it to recur. When I go back to the original OLED, the problem comes back. So it appears to be a HW issue. When I see the microcontroller hang up, I usually assume it is a SW issue. (With all the boneheaded mistakes I make in code, it usually is!) It's a good lesson not to assume all the HW is running as intended. Time will tell if it is gone permanently. My guess is that the transients from the rotary encoder were disrupting the I2C communication. I wouldn't see it if I turned the knob slowly, but if I turned it fast enough, I was probably grounding the encoder pin in the middle of an I2C message. For whatever reason, it appears one of my OLEDs was sensitive to this, but the other was not. It seems odd that this would cause the Teensy to completely hang though. I couldn't even reprogram it without power cycling!
 
Status
Not open for further replies.
Back
Top