Geert Roumen
Published © CC BY-SA

Using OLED display as external monitor

Creating content for small screen displays involves quite some steps, with this tool you can start creating the content as easy as drawing.

IntermediateProtip1 hour2,497

Things used in this project

Hardware components

1.3Inch OLED display (128*64)
Any display supported by the u8g2 library might work, but the code might need some tweaks to make it work.
×1
Breadboard (generic)
Breadboard (generic)
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Nano 33 BLE Sense
Arduino Nano 33 BLE Sense
Any Microcontroller with an I2C interface and at least 2k RAM memory should work
×1

Software apps and online services

Processing
The Processing Foundation Processing

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Depends on if the headers come pre-soldered

Story

Read more

Schematics

Schematic

Fritzing source file

Code

Arduino_ExternalScreen_Binairy_OLED

Arduino
This code lets a Processing sketch control an OLED screen based on the SH1106 driver which can only display black or coloured/white.
/*
  @Author Geert Roumen
  @Description This code lets a Processing sketch control an OLED screen
  @Hardware

  Arduino Nano 33 BLE
  https://content.arduino.cc/assets/Pinout-NANOble_latest.png
  SDA     A4
  SCL     A5
  5V      
  GND
 
  or

  ESP32 f.e. Adafruit Feather ESP32
  SCL     22
  SDA     23
  5V/USB  VIN
  GND     GND
  
  OLED screen: 
  
  http://www.lcdwiki.com/1.3inch_IIC_OLED_Module_SKU:MC130GX
  
  https://www.amazon.de/1-3-Inch-OLED-Display-Parent/dp/B078J78R45?ref_=ast_sto_dp&th=1&psc=1
*/
#include <U8g2lib.h>
//Define the size of the screen, and thus also the size of the packages coming from Processing
#define WIDTH 128
#define HEIGHT 64
//This is the bit per pixel ratio, 1 means every bit contains a pixel
#define BPP 1
//The width in bytes is depending on the width and the bits per pixel
#define WIDTH_BYTE WIDTH/(8/BPP)
//Buffer size is the full size of the messages that Arduino receives from Processing
#define BUFFER_SIZE WIDTH_BYTE*HEIGHT
//Create an empty array of bytes, all containing 0's for now.
byte buffer_bmp[BUFFER_SIZE] = {0};

                               //This is for the SH1106 used in the tutorial, 
                               //you can find other options by looking it up here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
                               U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(
                                 /*rotation R0=0deg,R1=90deg, R2=180deg*/
                                 U8G2_R0,
                                 /* reset=*/
                                 U8X8_PIN_NONE);

void setup(void) {
  //Set the bus clock speed, 
  u8g2.setBusClock(3400000UL);
  u8g2.begin();
  Serial.begin(200000);
  u8g2.clearBuffer(); // clear the internal menory
  u8g2.drawBitmap( 0, 0, WIDTH_BYTE, HEIGHT, buffer_bmp);
  u8g2.sendBuffer();
}

void loop(void) {
  downloadImage(BUFFER_SIZE); //Wait for a package to be received
  u8g2.clearBuffer(); // clear the internal menory
  u8g2.drawBitmap( 0, 0, WIDTH_BYTE, HEIGHT, buffer_bmp); //Draw this image on the screen
  u8g2.sendBuffer(); // transfer internal memory to the display
}

/*
This function blocks the program untill it receives a package from the Processing sketch
*/
void downloadImage(int len) {
  int i = 0;
  //wait for sync byte
  while (Serial.read() != 'R') {
  //keep looping untill it finds an R
  }
  while (Serial.read() != '.') {
  //keep looping untill it finds a .
  }
  //make brightness 'undefined' by makeing it -1
  int brightness = -1;
  
  while (i < len) {
  //Loop untill it went through the full image
    while (Serial.available()) {
      //if the brightness is still undefined
      if (brightness == -1) {
        //Define the brightness (first byte of the payload)
        brightness = Serial.read();
        //Set the contrast of the display
        displaySetContrast(brightness);
      } else {
        //If it is not the first byte of the payload, load the received byte into the buffer
        buffer_bmp[i] = Serial.read();
        //Add one to the index, so the next byte is going into the next slot in the buffer
        i++;
      }
    }

  }
}

void displaySetContrast(byte contrast) {
  u8g2.setContrast(contrast);
}

Processing_binairy_OLED_bitmap

Processing
This code sends a bitmap of the bounding box at the cursor to the Arduino which will control an OLED screen.
/*
  @Author Geert Roumen
  @Description This code sends a bitmap of the bounding box at the cursor to 
  the Arduino which will control an OLED screen.
  @Warning Make sure you select the right Serial port, otherwise it will not work
  @Hardware

  Arduino Nano 33 BLE
  https://content.arduino.cc/assets/Pinout-NANOble_latest.png
  SDA     A4
  SCL     A5
  5V      
  GND
 
  or

  ESP32 f.e. Adafruit Feather ESP32
  SCL     22
  SDA     23
  5V/USB  VIN
  GND     GND
  
*/

//Import the AWT (Abstract Window Toolkit)
import java.awt.*;
//Make a AWT robot, which will help us to capture a part of the screen, 
//and works on multiple operating systems
Robot robot;
import java.awt.image.*;
//Import the processing serial library (to communicate with the Arduino)
import processing.serial.*;
private static final int screen_width = 128;
private static final int screen_height = 64;
private static final int display_scale = 4;
// Create object from Serial class
Serial myPort;  
void settings(){
  noSmooth();
    size(screen_width*display_scale,screen_height*display_scale);

}
void setup(){
    noSmooth();

  //You can set the framerate to your preference, but too high might be causing troubles with the communication
  frameRate(3);
  /*
  Important!
  
  Replace the 2 with whatever index the port you are using is in, when running the sketch it will give you a list of all devices:
  [0] "/dev/cu.Bluetooth-Incoming-Port"
  [1] "/dev/cu.STR-DH190-SerialPort"  
  ...
  
  You can check your port name in the right bottom of the Arduino window. This will help you to find the right number here.
  */
  String portName = Serial.list()[0];
    printArray(Serial.list());
  myPort = new Serial(this, portName, 200000);
try
  {
    robot = new Robot();
 
  }catch (AWTException e){
    throw new RuntimeException("Unable to Initialize", e);
  }
}
//MouseX
int mX = 0;
//MouseY
int mY = 0;

//Saved x
int sX = -1;
//Saved Y
int sY = -1;
int rX = 0;
int rY = 0;
int aX = 0;
int aY = 0;
int time = 0;
void draw(){
  //If no location is saved
  PImage c;
  Point mouse;
  mouse = MouseInfo.getPointerInfo().getLocation();
  mX = mouse.x;
  mY = mouse.y;
  if (sX == -1){
  Rectangle bounds = new Rectangle(mX, mY, screen_width, screen_height);
  BufferedImage test = robot.createScreenCapture(bounds);
  c = new PImage(test);
  }else{
  Rectangle bounds = new Rectangle(sX, sY, screen_width, screen_height);
  BufferedImage test = robot.createScreenCapture(bounds);
  c = new PImage(test);
  }
  //Since the OLED is only displaying Black/White, set a threshold filer to mymick this
  c.filter(THRESHOLD);
  scale(4);
  background(c.get(50,50));
  image(c,0,0);
  
    myPort.write((byte) 'R');
    myPort.write((byte) '.');
    //Brightness of the display
    myPort.write((byte) 255);
    //Send all the pixels in groups of bytes
    for(int j=0;j<screen_width;j++){
      for(int i=0;i<screen_height-1;){
        byte t = 0;
        for (int q = 0;q<8;q++){
        t += (brightness(c.get((i++),40+j))>128)?0:Math.pow(2,7-q);
        }
        print(hex(t));
        myPort.write((byte) t);
      }
      println();
    }
  
  }

/*
By pressing the UP key on the keyboard you can lock the screen to a specfic position 
//on your monitor, this is great for f.e. figma.com/mirror, where you can scale the window
//using the inspector tools (in Chrome(ium), Firefox...)
*/

void keyPressed(){
  if (keyCode == DOWN) {
    sX = mX;
    sY = mY;
  }
}

Credits

Geert Roumen

Geert Roumen

6 projects • 10 followers
I’m a maker and interaction designer, bridging the digital and physical world. and make prototypes and do research in a playful way.

Comments