Tech Talk with Gus 02 - 0.96" 128x64 OLED display

It’s time for the first official episode of Tech Talk with Gus. In this episode we’ll be talking about and demonstrating an OLED 0.96" display. These cool little displays can be used in a number of projects, and are very cheap.

SSD1306 Datasheet

Adafruit_SSD1306 Drivers

14 Likes

And before you ask, here is the code :slight_smile:


// code is from adafruit origianlly https://github.com/adafruit/Adafruit_SSD1306/
// Many improvements are needed. This is just a "get me started" code

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace SSD1306_OLED
{
    public class Program
    {
        static int SSD1306_LCDHEIGHT = 64;
        static int SSD1306_LCDWIDTH = 128;
        static ushort SSD1306_I2C_ADDRESS = (0x3C);

        static I2CDevice.Configuration con =
           new I2CDevice.Configuration(SSD1306_I2C_ADDRESS, 400);
        static I2CDevice I2C = new I2CDevice(con);

        static void ssd1306_command(int cmd)
        {
            I2CDevice.I2CTransaction[] xActions =
           new I2CDevice.I2CTransaction[1];

            // create write buffer (we need one byte)
            byte[] buff = new byte[2];
            buff[1] = (byte) cmd;
            xActions[0] = I2CDevice.CreateWriteTransaction(buff);
            if (I2C.Execute(xActions, 1000) == 0)
            {
                Debug.Print("Failed to perform I2C transaction");
            }
            
        }
        static void InitDisplay()
        {

            // Init sequence
            ssd1306_command(0xae);// SSD1306_DISPLAYOFF);                    // 0xAE
            ssd1306_command(0xd5);// SSD1306_SETDISPLAYCLOCKDIV);            // 0xD5
            ssd1306_command(0x80);                                  // the suggested ratio 0x80

            ssd1306_command(0xa8);// SSD1306_SETMULTIPLEX);                  // 0xA8
            ssd1306_command(SSD1306_LCDHEIGHT - 1);

            ssd1306_command(0xd3);// SSD1306_SETDISPLAYOFFSET);              // 0xD3
            ssd1306_command(0x0);                                   // no offset
            ssd1306_command(0x40);// SSD1306_SETSTARTLINE | 0x0);            // line #0
            ssd1306_command(0x8d);// SSD1306_CHARGEPUMP);                    // 0x8D

            if (false)//vccstate == SSD1306_EXTERNALVCC)

            { ssd1306_command(0x10); }

            else

            { ssd1306_command(0x14); }

            ssd1306_command(0x20);// SSD1306_MEMORYMODE);                    // 0x20
            ssd1306_command(0x00);                                  // 0x0 act like ks0108
            ssd1306_command(0xa1);// SSD1306_SEGREMAP | 0x1);
            ssd1306_command(0xc8);// SSD1306_COMSCANDEC);


            ssd1306_command(0xda);// SSD1306_SETCOMPINS);                    // 0xDA
            ssd1306_command(0x12);
            ssd1306_command(0x81);// SSD1306_SETCONTRAST);                   // 0x81

            if (false)//vccstate == SSD1306_EXTERNALVCC)
            { ssd1306_command(0x9F); }
            else
            { ssd1306_command(0xCF); }

            ssd1306_command(0xd9);// SSD1306_SETPRECHARGE);                  // 0xd9

            if (false)//vccstate == SSD1306_EXTERNALVCC)
            { ssd1306_command(0x22); }
            else
            { ssd1306_command(0xF1); }

            ssd1306_command(0xd8);// SSD1306_SETVCOMDETECT);                 // 0xDB
            ssd1306_command(0x40);
            ssd1306_command(0xa4);//SSD1306_DISPLAYALLON_RESUME);           // 0xA4
            ssd1306_command(0xa6);// SSD1306_NORMALDISPLAY);                 // 0xA6

            ssd1306_command(0x2e);// SSD1306_DEACTIVATE_SCROLL);

            ssd1306_command(0xaf);// SSD1306_DISPLAYON);//--turn on oled panel

            ///////////////////
            ///////////////////
            
        }

        static void Render(byte[] data)
        {
            ssd1306_command(0x21);// SSD1306_COLUMNADDR);
            ssd1306_command(0);   // Column start address (0 = reset)
            ssd1306_command(SSD1306_LCDWIDTH - 1); // Column end address (127 = reset)
            ssd1306_command(0x22);// SSD1306_PAGEADDR);
            ssd1306_command(0); // Page start address (0 = reset)
            ssd1306_command(7); // Page end address


            byte[] img = new byte[(128 * 64 / 8)+1];
            img[0] = 0x40;
            Array.Copy(data, 0, img, 1, 128 * 64 / 8);

            //img[50]=0xAA;
            xActions[0] = I2CDevice.CreateWriteTransaction(img);
            
            if (I2C.Execute(xActions, 1000) == 0)
            {
                Debug.Print("Failed to perform I2C transaction");
            }
        }
        public static void Main()
        {
            InitDisplay();
            Bitmap LCD = new Bitmap(128, 64);
            Font fnt = Resources.GetFont(Resources.FontResources.NinaB);
            int counter = 0;
            int x = 50;
            int dx = 5;
            while (true)
            {
                LCD.DrawText("Count: " + counter++, fnt, Microsoft.SPOT.Presentation.Media.Color.White, 10, 10);
                LCD.DrawEllipse(Microsoft.SPOT.Presentation.Media.Color.White, x, 40, 5, 5);
                LCD.DrawEllipse(Microsoft.SPOT.Presentation.Media.Color.White, 128-x, 50, 5, 5);
                byte[] img = GHI.Utilities.Bitmaps.Convert(LCD, GHI.Utilities.Bitmaps.Format.Bpp1x128);

                Render(img);
                LCD.Clear();

                x += dx;
                if (x < 0 || x > 128)
                    dx *= -1;

                System.Threading.Thread.Sleep(30);
            }
        }
    }
}


6 Likes

So then previous one was episode 0; cool.

Since the value 0x78 had to be shifted then why didn’t they just put 0x3C… weird.

@ Gus, in your evaluation did you notice anywhere where you can change the address. Also, do you have to update the entire screen each time or can it be partially updated.

@ Mr. John Smith - both representations of the address are correct. It is 8bit vs 7bit thing.

You can update partial areas on the display. See the render code above.

???

It is the “first” episode but #2 in the queue :wall:

@ Gary - As programmers we all know that zero (0) is a number, but most humans start counting from one (1). Since this was the first real episode (i.e. episode 1) then the introduction episode before would count as episode zero (0). It’s an inside joke among programmers.

3 Likes

nobody was laughing :whistle:

2 Likes

Nice :)… I once started to write a driver for one of those displays… But until now it only can display text in a fixed size…

1 Like

Nice one !!! :clap: :clap:

Nice to hear Gus use Frickin’ Easy for FEZ (quickly followed by Fast and Easy). Nostalgic.

5 Likes

It’s really not that weird. The slave address consists of 7bits as the address and the 8’th is the R/~W bit. On the PCB they are simply giving you the full 8 bits that you are to send as the device address. Which on 0x78 the R/~W bit is 0 indicating we want to write to it.

Here is where it does get weird. Some compilers are smart, and others take what you pass litterally when it comes to this.
For example, say with compiler A(smart) & B(literally) i want to write to a I2C device that has a slave address of 0x3F.

// this will automatically shift up the value passed by one, then send it
Compiler A:
I2cWriteAddress(0x3f);

// this will literally send what you pass
Compiler B:
I2cWriteAddress(0x7E);

So it’s all about knowing how your compiler talks to I2C devices.
The simplest way i have found when in doubt about the address is to do the following.

for(x=0;x<0xfe;x++)
{
// Assuming your compilers write will return if you got an ack or not. Some do, some dont.
y = I2cWriteAddress(x);
printf(“%x=%d\r\n”,x,y);
}

when you run this it will quickly show you what devices it picks up on the bus as the address.

1 Like

@ VersaModule - it is the library, not the compiler. But you are right. I like the search idea as well.

With our system, you can convert about any font and use easily.

I miss the beauty of Gadgeteer sockets… :whistle:

1 Like

Greate One, thanks GUS!

Riaan

1 Like

@ Gus - Hi Gus. I don’t see any pins being passed in a constructor for the I2C class. I am making the move to .netmf and don’t see how the code knows which pins to use.

I bought a couple of these displays and am working with a G30.

Thanks for the video. I hope there are many more coming.

I2C pins are dedicated on each processor, so you don’t need to tell it what one to use, it’ll always be the same. You will find the pins labelled SDA and SCL on the schematics of the board you’re using…
If you want to use Software I2C which lets you define what pins to use (but the code has to handle the hardware interaction instead of letting hardware do it) you can check out the software I2C section of https://www.ghielectronics.com/docs/12/i2c

@ Brett - Thanks Brett, I appreciate completeness of your response.

Thanks Gus great job!

1 Like