datacute / Tiny4kOLED

Library for an ATTiny85 to use an SSD1306 powered, double buffered, 128x32 pixel OLED, over I2C
MIT License
264 stars 39 forks source link

oled.begin() not initializing properly #20

Closed Winston-Lu closed 4 years ago

Winston-Lu commented 4 years ago

I've been trying to figure out this problem for the past few days wondering why this library does not initialize the OLED properly. Using other libraries such as ux8x/u8g2 writes to the display fine, but takes up too much memory/program space for my liking. I have successfully printed on the screen once a few days ago, but I am unable to repeat this process.

I have a separate program using u8g2 to write text to the OLED display, and when I flash the Tiny4kOLED script in, the display does not clear but the rest of the script works fine (led flash). I've tried reinstalling the library, but did not work.

I am currently using an ATTINY85 with a blue SSD1306 128x32 oled screen, which should work. I have pin 0 on SDA, and pin 2 on SCK, and I'm using an Arduino Uno as an ISP to program, which has been working fine.

#include <Tiny4kOLED.h>
#define TESTLED 3

void setup() {
  oled.begin();
  oled.setFont(FONT8X16);
  oled.clear();
  oled.on();
  oled.switchRenderFrame();

  pinMode(TESTLED,OUTPUT);
}

void loop() {
  updateDisplay();
  digitalWrite(TESTLED,HIGH);
  delay(500);
  digitalWrite(TESTLED,LOW);
  delay(500);
}
void updateDisplay(){
  oled.clear();
  oled.setFont(FONT8X16);
  oled.setCursor(0,1);
  oled.print("ms: ");
  oled.print(millis());
  oled.switchFrame();
}

The U8g2 script that currently works with my setup is below:

#include <U8g2lib.h>
#define TESTLED 3

U8X8_SSD1306_128X32_UNIVISION_SW_I2C u8x8(2,0);
void setup() {
  u8x8.begin();
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  pinMode(TESTLED,OUTPUT);
}

void loop() {
  u8x8.drawString(0,0,"Working");
  digitalWrite(TESTLED,LOW);
  delay(500);
  digitalWrite(TESTLED,HIGH);
  delay(500);
}
datacute commented 4 years ago

Hi,

I've reduced the size of the initialisation sequence to only supply non-default values, but it could be that your screen needs more values to be set.

static const uint8_t ssd1306_init_sequence [] PROGMEM = {   // Initialization Sequence
//  0xAE,           // Display OFF (sleep mode)
//  0x20, 0b10,     // Set Memory Addressing Mode
                    // 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode;
                    // 10=Page Addressing Mode (RESET); 11=Invalid
//  0xB0,           // Set Page Start Address for Page Addressing Mode, 0-7
    0xC8,           // Set COM Output Scan Direction
//  0x00,           // ---set low column address
//  0x10,           // ---set high column address
//  0x40,           // --set start line address
//  0x81, 0x7F,     // Set contrast control register
    0xA1,           // Set Segment Re-map. A0=address mapped; A1=address 127 mapped.
//  0xA6,           // Set display mode. A6=Normal; A7=Inverse
    0xA8, 0x1F,     // Set multiplex ratio(1 to 64)
//  0xA4,           // Output RAM to Display
                    // 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
//  0xD3, 0x00,     // Set display offset. 00 = no offset
//  0xD5, 0x80,     // --set display clock divide ratio/oscillator frequency
//  0xD9, 0x22,     // Set pre-charge period
    0xDA, 0x02,     // Set com pins hardware configuration
//  0xDB, 0x20,     // --set vcomh 0x20 = 0.77xVcc
    0x8D, 0x14      // Set DC-DC enable
};

The u8g2 library sends quite a bit more, and some slightly different settings, which you can see here: https://github.com/olikraus/u8g2/blob/master/csrc/u8x8_d_ssd1306_128x32.c

You can call oled.begin with your own initialisation sequence. If none is supplied, the library will use the default like so:

begin(sizeof(ssd1306_init_sequence), ssd1306_init_sequence);
Winston-Lu commented 4 years ago

Hi,

I've reduced the size of the initialisation sequence to only supply non-default values, but it could be that your screen needs more values to be set.

static const uint8_t ssd1306_init_sequence [] PROGMEM = { // Initialization Sequence
//    0xAE,           // Display OFF (sleep mode)
//    0x20, 0b10,     // Set Memory Addressing Mode
                  // 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode;
                  // 10=Page Addressing Mode (RESET); 11=Invalid
//    0xB0,           // Set Page Start Address for Page Addressing Mode, 0-7
  0xC8,           // Set COM Output Scan Direction
//    0x00,           // ---set low column address
//    0x10,           // ---set high column address
//    0x40,           // --set start line address
//    0x81, 0x7F,     // Set contrast control register
  0xA1,           // Set Segment Re-map. A0=address mapped; A1=address 127 mapped.
//    0xA6,           // Set display mode. A6=Normal; A7=Inverse
  0xA8, 0x1F,     // Set multiplex ratio(1 to 64)
//    0xA4,           // Output RAM to Display
                  // 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
//    0xD3, 0x00,     // Set display offset. 00 = no offset
//    0xD5, 0x80,     // --set display clock divide ratio/oscillator frequency
//    0xD9, 0x22,     // Set pre-charge period
  0xDA, 0x02,     // Set com pins hardware configuration
//    0xDB, 0x20,     // --set vcomh 0x20 = 0.77xVcc
  0x8D, 0x14      // Set DC-DC enable
};

The u8g2 library sends quite a bit more, and some slightly different settings, which you can see here: https://github.com/olikraus/u8g2/blob/master/csrc/u8x8_d_ssd1306_128x32.c

You can call oled.begin with your own initialisation sequence. If none is supplied, the library will use the default like so:

begin(sizeof(ssd1306_init_sequence), ssd1306_init_sequence);

I've tried running a different init sequence, but still no luck. Not too sure what to look for either since there is no error and the u8g2 library has a lot of code to look through. Heres what I tried:

static const uint8_t ssd1306_init_sequence [] PROGMEM = {   // Initialization Sequence
    0xAE,           //display off
    0xD5, 0x80,     //clock divide ratio
    0xA8, 0x1F,     //Multiplex ratio
    0xD3, 0x00,     //Display offset
    0x40,           //start line at 0
    0x8D, 0x14,     //charge pump setting?
    0x20, 0x00,     //page addressing
    0xA1,           //segment remap
    0xC8,           //scan dir reverse, C0 for normal
    0xDA, 0x02,     // HW pin configuration
    0x81, 0x8F,     //contrast control
    0xD9, 0xF1,     //pre-charge period
    0xDB, 0x40,     //vcomh deselect level
    0x2E,           //disable scroll
    0xA4,           //output ram to display
    0xA6            //normal display mode
}

I've tried looking through the source code for a while but didn't get anywhere that seemed to get me closer since I'm still new to the OLED IIC thing. If there was some sort of stepper debug in the Arduino debugger that went through the libraries, that would be useful. Any other ideas?

datacute commented 4 years ago

Unless you had modified the source code in the library, you also needed to provide that init sequence in the begin call: oled.begin(sizeof(ssd1306_init_sequence), ssd1306_init_sequence);

However, your first reported code works fine for me, even after running your U8g2 code.

Running using the same init sequence as U8g2 also works for me.

I'm not sure what else to suggest.

Are you using Spence Konde's ATTinyCore?

Do you have a link to the screen you have? I assume it is one of the 4 pin ones, without a reset pin?

You could try alternative wire library implementations by including Tiny4kOLED_Wire.h or Tiny4kOLED_tiny-i2c.h instead of Tiny4kOLED.h

tiny-i2c is found here: https://github.com/technoblogy/tiny-i2c

Winston-Lu commented 4 years ago

I'm using this screen, and it does not have a reset pin: https://www.aliexpress.com/item/32982681500.html?spm=a2g0s.9042311.0.0.fc054c4dtTZCmE

I ended up using the ssd1306 library by Alexey Dynda, which works fine. I've added the #include and all the other includes that are defined anyways when Tiny4kOLED initializes as well. I'm puzzled as to why this would be happening as well, as I tried modifying the library's init sequence and calling the .begin(size,sequence) myself. Trying to follow the calls in the u8g2 library was too convoluted for someone of my experience, so I decided to go with another lightweight library. I have 3 ATTIny85's and 3 OLED's, and none of them seem to work unless I use a different library

aosodoev commented 4 years ago

FYI it works when display was powered before the MCU or when MCU was reset while display stayed powered on. E.g. after flashing program while everything was connected it works like a charm, but it fails to initialize if you cut and reconnect power.

With delay of about 2 seconds before oled.begin() call for some reason it initializes properly every time. With 1 second delay it still fails.

aosodoev commented 4 years ago

This workaround kinda does the job for me. But it results in ~1900 ms delay anyway.

  TinyI2C.init();
  while (!TinyI2C.start(0x3C, 0)) {
    delay(10);
  }
  TinyI2C.stop();
  delay(50);
  oled.begin();
Winston-Lu commented 4 years ago

This workaround kinda does the job for me. But it results in ~1900 ms delay anyway.

  TinyI2C.init();
  while (!TinyI2C.start(0x3C, 0)) {
    delay(10);
  }
  TinyI2C.stop();
  delay(50);
  oled.begin();

Trying the code just gives me a compilation error with a bunch of variables in the TinyI2CMaster library having undefined variables. Can you paste the entire code and see if it works? This is what I have:

#include <Tiny4kOLED.h>
#include <TinyI2CMaster.h>

void setup(){
  TinyI2C.init();
  while (!TinyI2C.start(0x3C, 0)) delay(10);
  TinyI2C.stop();
  delay(50);
  oled.begin();
}

void loop() {
  oled.clear();
  oled.setFont(FONT8X16);
  oled.setCursor(0,1);
  oled.print("ms: ");
  oled.print(millis());
  oled.switchFrame();
  delay(100);
}
aosodoev commented 4 years ago

This is complete sketch I used to test. What's weird is that this code works fine when compiled and uploaded with platformIO, but from Arduino IDE it doesn't work, although it compiles and uploads without errors, OLED stays in the same state it was the moment of flashing and remains dark after power cycle. I'm out of ideas at that point.

/*
 * Tiny4kOLED - Drivers for SSD1306 controlled dot matrix OLED/PLED 128x32 displays
 *
 * Based on ssd1306xled, re-written and extended by Stephen Denne
 * from 2017-04-25 at https://github.com/datacute/Tiny4kOLED
 *
 */

#include <Tiny4kOLED_tiny-i2c.h>
#include <TinyI2CMaster.h>

void initDisplay(unsigned long startDelay) {
  // Clear whatever random data has been left in memory.
  oled.clear();
  // Position the text cursor
  // In order to keep the library size small, text can only be positioned
  // with the top of the font aligned with one of the four 8 bit high RAM pages.
  // The Y value therefore can only have the value 0, 1, 2, or 3.
  // usage: oled.setCursor(X IN PIXELS, Y IN ROWS OF 8 PIXELS STARTING WITH 0);
  oled.setCursor(0, 0);
  oled.print(F("start: "));
  oled.print(startDelay);
  oled.setCursor(0, 2);
  // Write text to oled RAM (which is not currently being displayed).
  oled.print(F("ms:"));
}

void updateDisplay() {
  // Position the text cursor
  oled.setCursor(32, 2);
  // Write the number of milliseconds since power on.
  // The number increases, so always overwrites any stale data.
  // This means we do not need to repeatedly clear and initialize the display.
  oled.print(millis());
  // Swap which half of RAM is being written to, and which half is being displayed.
  // This is equivalent to calling both switchRenderFrame and switchDisplayFrame.
  oled.switchFrame();
}

// ============================================================================

void setup() {
  TinyI2C.init();
  while (!TinyI2C.start(0x3C, 0)) {
    delay(10);
  }
  TinyI2C.stop();
  delay(50);
  unsigned long startDelay = millis();
  // Send the initialization sequence to the oled. This leaves the display turned off.
  oled.begin();
  oled.on();
  // This example only uses a single font, so it can be set once here.
  // The characters in the 8x16 font are 8 pixels wide and 16 pixels tall.
  // 2 lines of 16 characters exactly fills 128x32.
  oled.setFont(FONT8X16);
  // Setup the first half of memory.
  initDisplay(startDelay);
  // Switch the half of RAM that we are writing to, to be the half that is non currently displayed.
  oled.switchRenderFrame();
  // Setup the second half of memory.
  initDisplay(startDelay);
  // Call your own display updating code.
  updateDisplay();
  // Turn on the display.
  oled.on();
}

void loop() {
  delay(50);
  updateDisplay();
}
datacute commented 4 years ago

Thanks @aosodoev for your comments and code example. That code works fine for me MOST of the time, from Arduino IDE 1.8.10 on Windows, with ATTinyCore by Spence Konde version 1.3.2 installed through boards manager.

My device (the 99% of the time that it starts properly) always reports start: 49

Very occasionally is shows a screen with random pixels turned on on three quarters of the screen with a quarter of the screen completely clear, and appears hung.

I tried this using a prototyping board with very few components:

I updated my TinyI2C (as mine was a year old, and didn't have the fix for ATtiny44/84) but that made no difference.

I took out the 50ms delay, and reported starting micros() instead of millis(), and consistently got 64.

It seems that my display is always ready to start.

The u8g2 library spends time triggering a non-existent reset pin as part of it initialisation, so maybe that explains why @Winston-Lu had success with that library?

aosodoev commented 4 years ago

Thank you for the nice library! I've just tested it with ssd1306 display from a different batch order and it works perfectly without any delay. "slow" module is blue oled, normal is white. Modules are not 100% identical, but schematics and driver IC supposed to be exactly the same.