Based on lcdchargen -- Custom Character Generator for HD44780 LCD Modules
----------------------------------------------------------------------------
"THE BEER-WARE LICENSE" (Revision 42):
 wrote this file. As long as you retain this notice you
can do whatever you want with this stuff. If we meet some day, and you think
this stuff is worth it, you can buy me a beer in return. Omer Kilic
----------------------------------------------------------------------------
Custom Sprite Generator for i2c OLED Modules
Use graphing paper to draw out all your sprites before using this tool to generate the code for each of them. We are mostly interested in the Pixels and Output sections of this page.
NOTICE: We're using PROGMEM. This tells the Arduino to use the same memory space as your program which gives you up to 32KB worth of memory in flash storage including your program. This memory is accessed differently than the 2KB of SRAM. In the i2c OLED Library (Display128x64.h), we use the pgm_read_byte function to read bytes of the sprite.
Click pixels to generate output.

Pixels









Output

PROGMEM const byte SPRITE[] = {
	0b00000000,
	0b00000000,
	0b00000000,
	0b00000000,
	0b00000000,
	0b00000000,
	0b00000000,
	0b00000000
};

Arduino Pins

OLED Module Arduino Pin
GND GND
VCC 5V
SCK A4
SDA A5
Example Arduino Sketch
#include <Wire.h>
#include "Display128x64.h"

            
PROGMEM const byte SPRITE_TEST[] = {
  0b10101010, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0xFF,
  0xFF, 0b10101010
};
            
void setup()
{
  Wire.begin();
  InitDisplay(SSD1306_SWITCHCAPVCC);
}

void loop()
{
  ClearDisplay();
  Blit(player_x - 8, player_y - 8, SPRITE_TEST);


  Wire.setClock(CLOCK_DISPLAY);
  PushToDisplay();
}
            
Display128x64.h
/**
   Bare minimum i2c driver for OLED display
   bkucenski@gmail.com 2/22/2019
   based on https://github.com/adafruit/Adafruit_SSD1306
*/
#ifndef DISPLAY128X64_H
#define DISPLAY128X64_H

#include <Wire.h>

#define CLOCK_DISPLAY 400000 // this seems to work.  Faster is better

#define SSD1306_MEMORYMODE          0x20 ///< See datasheet
#define SSD1306_COLUMNADDR          0x21 ///< See datasheet
#define SSD1306_PAGEADDR            0x22 ///< See datasheet
#define SSD1306_SETCONTRAST         0x81 ///< See datasheet
#define SSD1306_CHARGEPUMP          0x8D ///< See datasheet
#define SSD1306_SEGREMAP            0xA0 ///< See datasheet
#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< See datasheet
#define SSD1306_DISPLAYALLON        0xA5 ///< Not currently used
#define SSD1306_NORMALDISPLAY       0xA6 ///< See datasheet
#define SSD1306_INVERTDISPLAY       0xA7 ///< See datasheet
#define SSD1306_SETMULTIPLEX        0xA8 ///< See datasheet
#define SSD1306_DISPLAYOFF          0xAE ///< See datasheet
#define SSD1306_DISPLAYON           0xAF ///< See datasheet
#define SSD1306_COMSCANINC          0xC0 ///< Not currently used
#define SSD1306_COMSCANDEC          0xC8 ///< See datasheet
#define SSD1306_SETDISPLAYOFFSET    0xD3 ///< See datasheet
#define SSD1306_SETDISPLAYCLOCKDIV  0xD5 ///< See datasheet
#define SSD1306_SETPRECHARGE        0xD9 ///< See datasheet
#define SSD1306_SETCOMPINS          0xDA ///< See datasheet
#define SSD1306_SETVCOMDETECT       0xDB ///< See datasheet

#define SSD1306_SETLOWCOLUMN        0x00 ///< Not currently used
#define SSD1306_SETHIGHCOLUMN       0x10 ///< Not currently used
#define SSD1306_SETSTARTLINE        0x40 ///< See datasheet

#define SSD1306_EXTERNALVCC         0x01 ///< External display voltage source
#define SSD1306_SWITCHCAPVCC        0x02 ///< Gen. display voltage from 3.3V

#define SSD1306_RIGHT_HORIZONTAL_SCROLL              0x26 ///< Init rt scroll
#define SSD1306_LEFT_HORIZONTAL_SCROLL               0x27 ///< Init left scroll
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 ///< Init diag scroll
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL  0x2A ///< Init diag scroll
#define SSD1306_DEACTIVATE_SCROLL                    0x2E ///< Stop scroll
#define SSD1306_ACTIVATE_SCROLL                      0x2F ///< Start scroll
#define SSD1306_SET_VERTICAL_SCROLL_AREA             0xA3 ///< Set scroll range

#define SCREEN_ADDR 0x3C
#define SCREEN_WIDTH_BYTES 16
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_BYTES 1024

#if defined(BUFFER_LENGTH)
#define WIRE_MAX BUFFER_LENGTH          ///< AVR or similar Wire lib
#elif defined(SERIAL_BUFFER_SIZE)
#define WIRE_MAX (SERIAL_BUFFER_SIZE-1) ///< Newer Wire uses RingBuffer
#else
#define WIRE_MAX 32                     ///< Use common Arduino core default
#endif

byte screen[SCREEN_BYTES];

void PlotPixel(int x, int y, byte c)
{
  if (y < 0) {
    return;
  }
  if (y >= SCREEN_HEIGHT) {
    return;
  }
  if (x < 0) {
    return;
  }
  if (x >= SCREEN_WIDTH) {
    return;
  }

  if (c) {
    screen[x + (y / 8) * SCREEN_WIDTH] |= (1 << (y & 7));
  } else {
    screen[x  + (y / 8) * SCREEN_WIDTH] &= ~(1 << (y & 7));
  }
}

int ix, iy;
byte b;

// 16x16 sprites
void Blit(int x, int y, byte * img)
{
  for (iy = 0; iy < 16; iy++) {
    b = pgm_read_byte(img + iy * 2);
    for (ix = 0; ix < 8; ix++) {
      PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
    }
    b = pgm_read_byte(img + iy * 2 + 1);
    for (ix = 8; ix < 16; ix++) {
      PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
    }
  }
}

// 8x8 sprites
void Blit8(int x, int y, byte * img)
{
  for (iy = 0; iy < 8; iy++) {
    b = pgm_read_byte(img + iy);
    for (ix = 0; ix < 8; ix++) {
      PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
    }
  }
}

// 8x16 sprites
void Blit816(int x, int y, byte * img)
{
  for (iy = 0; iy < 16; iy++) {
    b = pgm_read_byte(img + iy);
    for (ix = 0; ix < 8; ix++) {
      PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
    }
  }
}

// 5x8 sprites
void Blit58(int x, int y, byte * img)
{
  for (ix = 0; ix < 5; ix++) {
    b = pgm_read_byte(img + ix);
    for (iy = 0; iy < 8; iy++) {
      PlotPixel(x + ix, y + iy, (byte)((b >> ((iy % 8))) & 1));
    }
  }
}

void BlitText58(int x, int y, byte * font, byte * str)
{
  while (str[0] && x < 128) {
    Blit58(x, y, font + 5 * str[0]);
    str++;
    x += 5;
  }
}

void BlitText816(int x, int y, byte * font, byte * str)
{
  while (str[0] && x < 128) {
    Blit816(x, y, font + 16 * str[0]);
    str++;
    x += 8;
  }
}

void ClearDisplay()
{
  memset(screen, 0, SCREEN_BYTES);
}

void SendCommand(uint8_t c)
{
  Wire.beginTransmission(SCREEN_ADDR);
  Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
  Wire.write(c);
  Wire.endTransmission();
}

void SendCommands(const uint8_t *c, uint8_t n)
{
  Wire.beginTransmission(SCREEN_ADDR);
  Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
  uint8_t bytesOut = 1;
  while (n--) {
    if (bytesOut >= WIRE_MAX) {
      Wire.endTransmission();
      Wire.beginTransmission(SCREEN_ADDR);
      Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
      bytesOut = 1;
    }
    Wire.write(pgm_read_byte(c++));
    bytesOut++;
  }
  Wire.endTransmission();
}

bool InitDisplay(uint8_t vccstate)
{
  ClearDisplay();

  // Init sequence
  static const uint8_t PROGMEM init1[] = {
    SSD1306_DISPLAYOFF,                   // 0xAE
    SSD1306_SETDISPLAYCLOCKDIV,           // 0xD5
    0x80,                                 // the suggested ratio 0x80
    SSD1306_SETMULTIPLEX
  };               // 0xA8
  SendCommands(init1, sizeof(init1));
  SendCommand(SCREEN_HEIGHT - 1);

  static const uint8_t PROGMEM init2[] = {
    SSD1306_SETDISPLAYOFFSET,             // 0xD3
    0x0,                                  // no offset
    SSD1306_SETSTARTLINE | 0x0,           // line #0
    SSD1306_CHARGEPUMP
  };                 // 0x8D
  SendCommands(init2, sizeof(init2));

  SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0x14);

  static const uint8_t PROGMEM init3[] = {
    SSD1306_MEMORYMODE,                   // 0x20
    0x00,                                 // 0x0 act like ks0108
    SSD1306_SEGREMAP | 0x1,
    SSD1306_COMSCANDEC
  };
  SendCommands(init3, sizeof(init3));

  if ((SCREEN_WIDTH == 128) && (SCREEN_HEIGHT == 32)) {
    static const uint8_t PROGMEM init4a[] = {
      SSD1306_SETCOMPINS,                 // 0xDA
      0x02,
      SSD1306_SETCONTRAST,                // 0x81
      0x8F
    };
    SendCommands(init4a, sizeof(init4a));
  } else if ((SCREEN_WIDTH == 128) && (SCREEN_HEIGHT == 64)) {
    static const uint8_t PROGMEM init4b[] = {
      SSD1306_SETCOMPINS,                 // 0xDA
      0x12,
      SSD1306_SETCONTRAST
    };              // 0x81
    SendCommands(init4b, sizeof(init4b));
    SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x9F : 0xCF);
  } else if ((SCREEN_WIDTH == 96) && (SCREEN_HEIGHT == 16)) {
    static const uint8_t PROGMEM init4c[] = {
      SSD1306_SETCOMPINS,                 // 0xDA
      0x2,    // ada x12
      SSD1306_SETCONTRAST
    };              // 0x81
    SendCommands(init4c, sizeof(init4c));
    SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0xAF);
  } else {
    // Other screen varieties -- TBD
  }

  SendCommand(SSD1306_SETPRECHARGE); // 0xd9
  SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x22 : 0xF1);
  static const uint8_t PROGMEM init5[] = {
    SSD1306_SETVCOMDETECT,               // 0xDB
    0x40,
    SSD1306_DISPLAYALLON_RESUME,         // 0xA4
    SSD1306_NORMALDISPLAY,               // 0xA6
    SSD1306_DEACTIVATE_SCROLL,
    SSD1306_DISPLAYON
  };                 // Main screen turn on
  SendCommands(init5, sizeof(init5));


  return true; // Success
}

void PushToDisplay()
{
  static const uint8_t PROGMEM dlist1[] = {
    SSD1306_PAGEADDR,
    0,                         // Page start address
    0xFF,                      // Page end (not really, but works here)
    SSD1306_COLUMNADDR,
    0
  };                       // Column start address
  SendCommands(dlist1, sizeof(dlist1));
  SendCommand(SCREEN_WIDTH - 1); // Column end address

  uint8_t *ptr   = screen;
  uint16_t count = SCREEN_BYTES;

  Wire.beginTransmission(SCREEN_ADDR);
  Wire.write((uint8_t)0x40);
  uint8_t bytesOut = 1;
  while (count--) {
    if (bytesOut >= WIRE_MAX) {
      Wire.endTransmission();
      Wire.beginTransmission(SCREEN_ADDR);
      Wire.write((uint8_t)0x40);
      bytesOut = 1;
    }
    Wire.write(*ptr++);
    bytesOut++;
  }
  Wire.endTransmission();
}

#endif


#endif