[II2260 Embedded System] Project 5: How about some OLED?

Raden Dizi Assyafadi Putra
12 min readMar 2, 2023

--

Hy selamat datang lagi yh di blowg q

Setelah minggu lalu kita mengerjakan projek yang cukup menarik menggunakan Sensor BME/BMP280 (Refer ke [II2260 Embedded System] Project 4: Mengutilisasi External Sensor dengan BMP280). Sekarang kita akan naik level lagi yaitu mencoba mengoutput menggunakan D I S P L A Y.

Dalam sistem embedded, display atau tampilan merupakan salah satu komponen yang penting untuk menampilkan informasi kepada pengguna. Display dapat berupa layar LCD (Liquid Crystal Display), LED (Light Emitting Diode), OLED (Organic Light Emitting Diode), atau jenis tampilan lainnya. Disini aku menggunakan OLED karena dulu pernah ada pengalaman buruk dengan LCD (huek). Dan juga OLED konfigurasinya lebih mudah karena sudah ada library dari Adafruit yang mudah untuk digunakan. Pada percobaan ini, aku menggunakan OLED Display SSD1306 0.96 inch monocolor white dengan resolusi 128×64 pixels.

+ What is the project about?

Pada project kali ini, kita akan mengoutputkan text, gambar, dan animasi di OLED Display. Disini kita bisa berkreasi sekreatif mungkin seperti menciptakan gambar yang dapat bergerak dan sebagainya.

+ What tools do you need?

Beberapa tools yang akan kita gunakan adalah sebagai berikut

  1. ESP32 -> Yep, not your first rodeo and you should already know la how to use it. Kalo belum, refer to this [II2260 Embedded System] Project 1: Pengenalan ESP32 dengan Program LED Blink
  2. Kabel USB
  3. Kabel Jumper male to male
  4. OLED Display SSD1306 128x64
  5. Laptop km mz ((yang ada arduino ide yh))

+ What are the steps?

Installation

Sebelum memulai project, kamu harus menginstall beberapa libraries, yaitu Adafruit SSD 1306 sesuai dengan display OLED yang kita gunakan. Libraries ini butuh 2 dependency library yaitu Adafruit GFX Library dan Adafruit BusIO.

OLED Display

Untuk susunan rangkaiannya kita akan buat seperti gambar diatas. Pin-Pin tersebut dapat dilihat ditabel dibawah ini

Lalu setelah dirangkai, kita akan masukkan kode ini untuk menampilkan teks dasar di OLED Display dengan menggunakan kode berikut

/*********
Rui Santos
Complete project details at https://randomnerdtutorials.com
*********/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
Serial.begin(115200);

if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
delay(2000);
display.clearDisplay();

display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 10);
// Display static text
display.println("Hello, world!");
display.display();
}

void loop() {

}

Dan ini adalah hasil dari output yang harusnya akan kita dapatkan

Sebenarnya Adafruit memiliki example untuk SSD1306 namun kode cukup rumit untuk dimengerti. Ya mangga dicobakeun

/*********
Complete project details at https://randomnerdtutorials.com

This is an example for our Monochrome OLEDs based on SSD1306 drivers. Pick one up today in the adafruit shop! ------> http://www.adafruit.com/category/63_98
This example is for a 128x64 pixel display using I2C to communicate 3 pins are required to interface (two I2C and one reset).
Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries, with contributions from the open source community. BSD license, check license.txt for more information All text above, and the splash screen below must be included in any redistribution.
*********/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES 10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT 16
#define LOGO_WIDTH 16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
B00000001, B11000000,
B00000001, B11000000,
B00000011, B11100000,
B11110011, B11100000,
B11111110, B11111000,
B01111110, B11111111,
B00110011, B10011111,
B00011111, B11111100,
B00001101, B01110000,
B00011011, B10100000,
B00111111, B11100000,
B00111111, B11110000,
B01111100, B11110000,
B01110000, B01110000,
B00000000, B00110000 };

void setup() {
Serial.begin(115200);

// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}

// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.display();
delay(2000); // Pause for 2 seconds

// Clear the buffer
display.clearDisplay();

// Draw a single pixel in white
display.drawPixel(10, 10, WHITE);

// Show the display buffer on the screen. You MUST call display() after
// drawing commands to make them visible on screen!
display.display();
delay(2000);
// display.display() is NOT necessary after every single drawing command,
// unless that's what you want...rather, you can batch up a bunch of
// drawing operations and then update the screen all at once by calling
// display.display(). These examples demonstrate both approaches...

testdrawline(); // Draw many lines

testdrawrect(); // Draw rectangles (outlines)

testfillrect(); // Draw rectangles (filled)

testdrawcircle(); // Draw circles (outlines)

testfillcircle(); // Draw circles (filled)

testdrawroundrect(); // Draw rounded rectangles (outlines)

testfillroundrect(); // Draw rounded rectangles (filled)

testdrawtriangle(); // Draw triangles (outlines)

testfilltriangle(); // Draw triangles (filled)

testdrawchar(); // Draw characters of the default font

testdrawstyles(); // Draw 'stylized' characters

testscrolltext(); // Draw scrolling text

testdrawbitmap(); // Draw a small bitmap image

// Invert and restore display, pausing in-between
display.invertDisplay(true);
delay(1000);
display.invertDisplay(false);
delay(1000);

testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
}

void testdrawline() {
int16_t i;

display.clearDisplay(); // Clear display buffer

for(i=0; i<display.width(); i+=4) {
display.drawLine(0, 0, i, display.height()-1, WHITE);
display.display(); // Update screen with each newly-drawn line
delay(1);
}
for(i=0; i<display.height(); i+=4) {
display.drawLine(0, 0, display.width()-1, i, WHITE);
display.display();
delay(1);
}
delay(250);

display.clearDisplay();

for(i=0; i<display.width(); i+=4) {
display.drawLine(0, display.height()-1, i, 0, WHITE);
display.display();
delay(1);
}
for(i=display.height()-1; i>=0; i-=4) {
display.drawLine(0, display.height()-1, display.width()-1, i, WHITE);
display.display();
delay(1);
}
delay(250);

display.clearDisplay();

for(i=display.width()-1; i>=0; i-=4) {
display.drawLine(display.width()-1, display.height()-1, i, 0, WHITE);
display.display();
delay(1);
}
for(i=display.height()-1; i>=0; i-=4) {
display.drawLine(display.width()-1, display.height()-1, 0, i, WHITE);
display.display();
delay(1);
}
delay(250);

display.clearDisplay();

for(i=0; i<display.height(); i+=4) {
display.drawLine(display.width()-1, 0, 0, i, WHITE);
display.display();
delay(1);
}
for(i=0; i<display.width(); i+=4) {
display.drawLine(display.width()-1, 0, i, display.height()-1, WHITE);
display.display();
delay(1);
}

delay(2000); // Pause for 2 seconds
}

void testdrawrect(void) {
display.clearDisplay();

for(int16_t i=0; i<display.height()/2; i+=2) {
display.drawRect(i, i, display.width()-2*i, display.height()-2*i, WHITE);
display.display(); // Update screen with each newly-drawn rectangle
delay(1);
}

delay(2000);
}

void testfillrect(void) {
display.clearDisplay();

for(int16_t i=0; i<display.height()/2; i+=3) {
// The INVERSE color is used so rectangles alternate white/black
display.fillRect(i, i, display.width()-i*2, display.height()-i*2, INVERSE);
display.display(); // Update screen with each newly-drawn rectangle
delay(1);
}

delay(2000);
}

void testdrawcircle(void) {
display.clearDisplay();

for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
display.drawCircle(display.width()/2, display.height()/2, i, WHITE);
display.display();
delay(1);
}

delay(2000);
}

void testfillcircle(void) {
display.clearDisplay();

for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
// The INVERSE color is used so circles alternate white/black
display.fillCircle(display.width() / 2, display.height() / 2, i, INVERSE);
display.display(); // Update screen with each newly-drawn circle
delay(1);
}

delay(2000);
}

void testdrawroundrect(void) {
display.clearDisplay();

for(int16_t i=0; i<display.height()/2-2; i+=2) {
display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
display.height()/4, WHITE);
display.display();
delay(1);
}

delay(2000);
}

void testfillroundrect(void) {
display.clearDisplay();

for(int16_t i=0; i<display.height()/2-2; i+=2) {
// The INVERSE color is used so round-rects alternate white/black
display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
display.height()/4, INVERSE);
display.display();
delay(1);
}

delay(2000);
}

void testdrawtriangle(void) {
display.clearDisplay();

for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
display.drawTriangle(
display.width()/2 , display.height()/2-i,
display.width()/2-i, display.height()/2+i,
display.width()/2+i, display.height()/2+i, WHITE);
display.display();
delay(1);
}

delay(2000);
}

void testfilltriangle(void) {
display.clearDisplay();

for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
// The INVERSE color is used so triangles alternate white/black
display.fillTriangle(
display.width()/2 , display.height()/2-i,
display.width()/2-i, display.height()/2+i,
display.width()/2+i, display.height()/2+i, INVERSE);
display.display();
delay(1);
}

delay(2000);
}

void testdrawchar(void) {
display.clearDisplay();

display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.cp437(true); // Use full 256 char 'Code Page 437' font

// Not all the characters will fit on the display. This is normal.
// Library will draw what it can and the rest will be clipped.
for(int16_t i=0; i<256; i++) {
if(i == '\n') display.write(' ');
else display.write(i);
}

display.display();
delay(2000);
}

void testdrawstyles(void) {
display.clearDisplay();

display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(WHITE); // Draw white text
display.setCursor(0,0); // Start at top-left corner
display.println(F("Hello, world!"));

display.setTextColor(BLACK, WHITE); // Draw 'inverse' text
display.println(3.141592);

display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(WHITE);
display.print(F("0x")); display.println(0xDEADBEEF, HEX);

display.display();
delay(2000);
}

void testscrolltext(void) {
display.clearDisplay();

display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(WHITE);
display.setCursor(10, 0);
display.println(F("scroll"));
display.display(); // Show initial text
delay(100);

// Scroll in various directions, pausing in-between:
display.startscrollright(0x00, 0x0F);
delay(2000);
display.stopscroll();
delay(1000);
display.startscrollleft(0x00, 0x0F);
delay(2000);
display.stopscroll();
delay(1000);
display.startscrolldiagright(0x00, 0x07);
delay(2000);
display.startscrolldiagleft(0x00, 0x07);
delay(2000);
display.stopscroll();
delay(1000);
}

void testdrawbitmap(void) {
display.clearDisplay();

display.drawBitmap(
(display.width() - LOGO_WIDTH ) / 2,
(display.height() - LOGO_HEIGHT) / 2,
logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
display.display();
delay(1000);
}

#define XPOS 0 // Indexes into the 'icons' array in function below
#define YPOS 1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
int8_t f, icons[NUMFLAKES][3];

// Initialize 'snowflake' positions
for(f=0; f< NUMFLAKES; f++) {
icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
icons[f][YPOS] = -LOGO_HEIGHT;
icons[f][DELTAY] = random(1, 6);
Serial.print(F("x: "));
Serial.print(icons[f][XPOS], DEC);
Serial.print(F(" y: "));
Serial.print(icons[f][YPOS], DEC);
Serial.print(F(" dy: "));
Serial.println(icons[f][DELTAY], DEC);
}

for(;;) { // Loop forever...
display.clearDisplay(); // Clear the display buffer

// Draw each snowflake:
for(f=0; f< NUMFLAKES; f++) {
display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, WHITE);
}

display.display(); // Show the display buffer on the screen
delay(200); // Pause for 1/10 second

// Then update coordinates of each flake...
for(f=0; f< NUMFLAKES; f++) {
icons[f][YPOS] += icons[f][DELTAY];
// If snowflake is off the bottom of the screen...
if (icons[f][YPOS] >= display.height()) {
// Reinitialize to a random position, just off the top
icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
icons[f][YPOS] = -LOGO_HEIGHT;
icons[f][DELTAY] = random(1, 6);
}
}
}
}

Tapi hasilnya keren bro, nih liat.

Ga gini doang sih. Tapi kalo divideoin semuanya gabisa jadi GIF :(

+ Code Breakdown!!!

Disini yang akan aku breakdown hanya yang menampilakan teks saja ya.


#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

Bagian ini adalah inisialisasi dari OLED dimana Width dan Heightnya merupakan resolusi dari layar yaitu 128x64 dan disini kita memanggil kelas Adafruit_SSD1306 untuk inisiasi display dengan I2C.

void setup() {
Serial.begin(115200);

if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
delay(2000);
display.clearDisplay();

display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 10);
// Display static text
display.println("Hello, world!");
display.display();
}

Display.begin() adalah method untuk menginisiasi layar dan membaca apakah ada layar yang terkoneksi di esp32. Bila tidak ada, maka sistem akan menampilkan “SSD1306 allocation failed” dan kita harus mengecek wiring kita. clearDisplay() adalah method wajib ketika kita ingin membersihkan layar OLED sehingga tidak ada tampilan dari hasil sebelumnya. Setter pada method-method selanjutnya berfungsi untuk memodifikasi text seperti ukurannya, warnanya, dan posisinya. println() berfungsi sama seperti I/O Stream arduino sebagai output stream dari text yang kita inginkan. display() berfungsi untuk menampilkan hasil tersebut ke layar.

— Challenge time

“Yeyy, it’s challenge time diziiii”

Sekarang untuk ide challenge, aku akan mencoba untuk membuat Muybridge Animation yang aku temukan di suatu forum arduino. Proyek ini sangat menarik karena disini kita harus bermain dengan point-point dan pixels untuk dapat membuat animasi yang dapat bergerak dalam interval tertentu.

Yak tanpa berlama-lama ini adalah kodenya

#include <Wire.h>                     // requried to run I2C SSD106
#include <SPI.h> // requried to run I2C SSD106
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_SSD1306.h> // https://github.com/adafruit/Adafruit_SSD1306

#define OLED_RESET 4 // reset required for SSD1306

Adafruit_SSD1306 display(OLED_RESET); // reset required for SSD1306

int framecount = 0;

static const unsigned char PROGMEM horse00[] = {
...
};

static const unsigned char PROGMEM horse01[] = {
...
};

static const unsigned char PROGMEM horse02[] = {
..
};

static const unsigned char PROGMEM horse03[] = {
...
};

static const unsigned char PROGMEM horse04[] = {
...
};

static const unsigned char PROGMEM horse05[] = {
...
};

static const unsigned char PROGMEM horse06[] = {
...
};

static const unsigned char PROGMEM horse07[] = {
...
};

static const unsigned char PROGMEM horse10[] = {
...
};

static const unsigned char PROGMEM horse11[] = {
...
};

static const unsigned char PROGMEM horse12[] = {
...
};

static const unsigned char PROGMEM horse13[] = {
...
};

static const unsigned char PROGMEM horse14[] = {
...
};

void setup(){

display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // required to run SSD1306
display.clearDisplay();
}

void loop(){

display.fillRect(0, 0, 128, 64, WHITE);
framecount = framecount + 1;

if(framecount > 14){
framecount = 0;
}

if(framecount == 0){
display.drawBitmap(0, 0, horse00, 128, 64, BLACK);
}

if(framecount == 1){
display.drawBitmap(0, 0, horse01, 128, 64, BLACK);
}

if(framecount == 2){
display.drawBitmap(0, 0, horse02, 128, 64, BLACK);
}

if(framecount == 3){
display.drawBitmap(0, 0, horse03, 128, 64, BLACK);
}

if(framecount == 4){
display.drawBitmap(0, 0, horse04, 128, 64, BLACK);
}

if(framecount == 5){
display.drawBitmap(0, 0, horse05, 128, 64, BLACK);
}

if(framecount == 6){
display.drawBitmap(0, 0, horse06, 128, 64, BLACK);
}

if(framecount == 7){
display.drawBitmap(0, 0, horse07, 128, 64, BLACK);
}

if(framecount == 8){
display.drawBitmap(0, 0, horse08, 128, 64, BLACK);
}

if(framecount == 9){
display.drawBitmap(0, 0, horse09, 128, 64, BLACK);
}

if(framecount == 10){
display.drawBitmap(0, 0, horse10, 128, 64, BLACK);
}

if(framecount == 11){
display.drawBitmap(0, 0, horse11, 128, 64, BLACK);
}

if(framecount == 12){
display.drawBitmap(0, 0, horse12, 128, 64, BLACK);
}

if(framecount == 13){
display.drawBitmap(0, 0, horse13, 128, 64, BLACK);
}

if(framecount == 14){
display.drawBitmap(0, 0, horse14, 128, 64, BLACK);
}

display.display();
}

wow sebuah kode yang sangat panjang sekali bukan? Untuk bagian progmem sengaja aku hilangkan karena kode sebanyak itu sangat memakan buffer dari chrome sehingga menyulitkan aku untuk mengetik blog ini :D. Hasilnya???

Yeehaw!

Kren kan? Aku cuma bisa bilang “imma throw some shade”.

— Experiment with PWM

Sedikit tentang PWM. PWM (Pulse Width Modulation) adalah sebuah teknik pengendalian sinyal analog dengan mengatur lebar pulsa sinyal digital periodik. Teknik ini sering digunakan dalam aplikasi kontrol kecepatan motor, dimming lampu, kontrol posisi servomotor, dan berbagai aplikasi lainnya yang memerlukan pengendalian sinyal analog dengan presisi tinggi. Keuntungan menggunakan modul PWM adalah mengurangi beban pada mikrokontroler atau mikroprosesor karena tugas menghasilkan sinyal PWM ditangani oleh modul PWM.

Nah kebetulan banget kebetulan, ESP32 punya PWM yang sudah terembed didalamnya (emang keren dah).

Jadi cus langsung aja brow.

Susun rangkaian seperti gambar diatas. Dan masukkan code berikut dan upload ke ESP32.

// the number of the LED pin
const int ledPin = 21; // 16 corresponds to GPIO16
const int ledPin2 = 19; // 17 corresponds to GPIO17
const int ledPin3 = 18; // 5 corresponds to GPIO5

// setting PWM properties
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;

void setup(){
// configure LED PWM functionalitites
ledcSetup(ledChannel, freq, resolution);

// attach the channel to the GPIO to be controlled
ledcAttachPin(ledPin, ledChannel);
ledcAttachPin(ledPin2, ledChannel);
ledcAttachPin(ledPin3, ledChannel);
}

void loop(){
// increase the LED brightness
for(int dutyCycle = 0; dutyCycle <= 255; dutyCycle++){
// changing the LED brightness with PWM
ledcWrite(ledChannel, dutyCycle);
delay(15);
}

// decrease the LED brightness
for(int dutyCycle = 255; dutyCycle >= 0; dutyCycle--){
// changing the LED brightness with PWM
ledcWrite(ledChannel, dutyCycle);
delay(15);
}
}

Hasilnya?

And that’s PWM in 5 minutes!

+ Summary

Kita sudah berhasil mengoutput animasi dan teks di OLED Display . Dengan demikian maka project ini akhirnya selesai juga juga juga juga juga jugaaaaaa. Sampai bertemu di project selanjutnya yawww teman-teman ❤

+ References

--

--