ESP-32 Tutorial: Display Using OLED
Bagaimana cara menampilkan pesan ataupun gambar dari ESP-32 kedalam sebuah tampilan atau display? Simak pembahasan berikut ini!
Halo teman-teman semua! Selamat datang kembali di serial blog “ESP-32 Tutorial”! Pada kesempatan kali ini, saya akan membahas terkait penggunaan display untuk menampilkan sebuah teks ataupun gambar yang disimpan oleh ESP-32. Untuk display pada percobaan kali ini, saya akan menggunakan OLED.
Tanpa basa-basi lagi, mari kita bahas secara lebih mendalam terkait penggunaan OLED dalam dunia ESP-32 ini!
Daftar Komponen dan Perangkat
Pada percobaan kali ini, saya akan menggunakan beberapa komponen/perangkat seperti berikut ini:
a. ESP-32 DOIT DEVKIT V1 Board
b. OLED
c. Jumper wires
d. Breadboard
Seperti biasa, nama komponen-komponen tersebut sudah seringkali kita lihat dan pahami fungsinya, hanya saja komponen bernama “OLED” ini yang belum pernah kita dengar ataupun pahami fungsinya, oleh karena itu mari kita bahas siapa sih dan apa yang dilakukan oleh teman baru kita ini.
OLED merupakan salah satu display yang ada dalam dunia ESP-32. Secara singkatan, OLED merupakan singkatan dari Organic Light-Emitting Diode yang berarti sebuah semikonduktor pemancar cahaya yang terbuat dari bahan organik. Dari namanya tersebut, tentunya kita dapat memahami bahwa fungsi utama dari OLED menampilkan suatu benda pada dirinya.
OLED ini sendiri memiliki display berukuran 128x64 pixels dan hanya berkomunikasi menggunakan jaringan I2C interface. OLED ini sendiri juga dapat digunakan pada ruangan yang memiliki suhu -40 sampai 80 derajat celcius sehingga cocok untuk digunakan dalam berbagai keadaan yang tidak terlalu ekstrem.
Untuk pinnya sendiri, OLED hanya memiliki 4 buah pin, yaitu VCC,GND,SCL dan SDA. Pin VCC sendiri berfungsi untuk menjadi power supply kepada OLED dengan range tegangan 2.8–5.2V, pin GND merupakan pin ground, pin SCL merupakan pin serial untuk clock input dan pin SDA merupakan pin serial untuk data input maupun output.
Skema Perangkat
Setelah kita melihat daftar komponen yang dibutuhkan, mari kita rangkai komponen-komponen tersebut menjadi sebuah rangkaian yang utuh! Nah tapi disclaimer terlebih dahulu teman-teman, karena adanya keterbatasan OLED yang tersedia pada website sketsa perangkat(saya menggunakan circuit.io), maka pada skema ini akan ditampilkan OLED dengan jumlah pin yang lebih dari empat, tapi secara sistematis, perangkaian/skemanya tetap sama
Setelah rangkaian berhasil dibuat, maka kita tinggal perlu membuat rangkaian codenya dan menguploadnya kedalam ESP-32. Bagaimana cara membuat codenya? Mari kita lihat di bagian “List Program”!
List Program
Seperti biasanya, saya akan menggunakan dua macam list program, yaitu list program untuk bawaan atau default case, lalu list program untuk perubahaan atau modified case.
Untuk Default case, program akan terlihat seperti berikut(Program diambil dari https://randomnerdtutorials.com/esp32-ssd1306-oled-display-arduino-ide/):
/*********
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 128x32 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);
}
}
}
}
Sementara untuk modified case, program akan terlihat seperti berikut(Template program diambil dari https://randomnerdtutorials.com/esp32-ssd1306-oled-display-arduino-ide/ tapi dengan perubahan sendiri):
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);void setup() {
Serial.begin(115200);if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F(“SSD1306 allocation failed”));
for(;;);
}
delay(2000);
display.clearDisplay();display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
// Display static text
display.println(“My Name Is Feihan”);
//Banner
display.drawRect(0,0,128,10,WHITE);
//Kepala dan Badan
display.drawCircle(64, 20, 8, WHITE);
display.fillCircle(64, 20, 8, WHITE);
display.drawRect(54, 28, 20, 15, WHITE);
//Tangan
display.drawLine(54, 28, 44, 10, WHITE);
display.drawLine(74, 28, 84, 10, WHITE);
display.display();
delay(100);
}void loop() {
// Scroll in various directions, pausing in-between:
display.startscrollright(0x00, 0x0F);
delay(500);
display.stopscroll();
delay(250);
display.startscrollleft(0x00, 0x0F);
delay(500);
display.stopscroll();
delay(250);
display.startscrolldiagright(0x00, 0x07);
delay(500);
display.startscrolldiagleft(0x00, 0x07);
delay(500);
display.stopscroll();
delay(250);
}
“Yaampun kak codenya banyak banget, gimana cara ngertinya ini T-T?” Tenang teman-teman! Kali ini akan saya bahas secara singkat apasih yang dilakukan oleh code/program ini.
Untuk penjelasan, saya akan menggunakan program pada modified case. Kenapa? karena secara struktur dan alur program, modified case merupakan program yang lebih mudah dipahami serta dibaca, yang membedakannya dengan default case hanyalah penambahan-penambahan object yang ingin digamabar(ada bentuk bintang, segitiga,dll).
Seperti biasanya, program akan dibagi menjadi tiga bagian, yaitu inisiasi, void setup dan void loop. Pada bagian inisiasi, program akan mengimport berbagai library yang diperlukan untuk menggunakan OLED serta mengatur konstanta seperti lebar layar,tingi layar,dll. Tujuan dari program ini adalah agar program yang kita tuliskan nantinya dapat dibaca dan dimengerti oleh compiler dan nantinya dapat diupload kedalam ESP-32.
Pada bagian void setup, program akan mempersiapkan berbagai hal, mulai dari menghubungkan ESP-32 dengan OLED itu sendiri. Penghubungan OLED dengan ESP-32 ini terjadi pada serial.begin dan apabila serial.begin tersebut tidak berjalan dengan baik, maka akan ditampilkan sebuah pesan error pada serial monitor(bukan di OLEDnya) dan program akan gagal menghubungkan ESP-32 dengan OLED.
Tetapi apabila program dapat berjalan dan menhubungkan OLED dengan ESP-32,maka pesan error tidak akan ditampilkan serta program akan membaca line-line code berikutnya, yaitu mempersiapkan objek yang ingin ditampilkan. Fungsi-fungsi ini ditulis dalam kode “display.” yang akan dilanjutkan dengan hal yang ingin dilakukan(misalnya ingin menggambar kotak, maka diperlukan fungsi display.drawRect() dengan berbagai parameternya).
Setelah berbagai persiapan tersebut selesai dibaca, maka program akan menampilkan display yang dibuat kedalam OLED dengan code display.display(). Tanpa fungsi ini, maka semua objek yang sudah kita buat akan sia-sia dan tidak ditampilkan pada OLED, oleh karena itu jangan sampai teman-teman melupakan satu fungsi simple tapi berguna tersebut.
Sementara untuk void loop sendiri, pada program kali ini sebenarnya merupakan add-on saja atau bukan hal yang wajib. Hal ini dikarenakan pada modul kali ini void loop hanya digunakan apabila kita ingin menampilkan sebuah animasi(misalnya menggerakan gambar dari kiri layar ke kanan layar secara terus menerus). Oleh karena itu, apabila teman-teman hanya ingin membuat gambar statik dan tidak bergerak, maka void loop ini tidak wajib untuk digunakan.
Demo Modul
Setelah kita pahami bagaimana cara merangkai OLED dengan ESP-32 serta memahami dan mengupload kode program yang ada, maka sekarang waktunya kita melakukan testing! Seperti biasanya, testing akan dibagi menjadi dua bagian, yaitu untuk default case dan modified case. Berikut ini video hasil demo yang saya lakukan.
Default Case
Modified Case
Analisis
Seusai dari demo rangkaian dan program yang berhasil, mari kita analisis bagaimana hal tersebut dapat terjadi! Untuk itu, mari kita lihat kembali pada bagian “List Program”. Pada bagian ini, seperti biasanya ada tiga tahap yaitu inisiasi/konstanta,void setup dan void loop. Dengan asumsi bahwa teman-teman sudah membaca dan memami masing-masing bagian program, maka saya akan menjelaskan bagaimana cara kerjanya.
Pertama-tama program akan melakukan pengaturan awal seperti yang dilakukan pada inisiasi/konstanta, program akan mengimport berbagai hal agar void setup dan void loop ini dapat dilakukan pada layar OLED. Setelah melakukan inisiasi, maka akan dilanjutkan dengan void setup. Pada void setup ini maka program akan menghubungkan koneksi antara ESP-32 dan OLED agar nantinya semua hal yang diproses pada ESP-32 dapat ditampilkan pada OLED.
Apabila kedua hal tersebut sudah saling terhubung, maka program akan mengirimkan berbagai objek ataupun teks yang disimpan pada ESP-32 untuk ditampilkan pada OLED dan dengan demikian maka objek-objek yang kita inginkan akan langsung muncul pada layar OLED. Apabila teman-teman menginginkan animasi, maka void loop akan menjalani hal tersebut dan akan menggerakan gambar serta teks teman-teman agar layar tidak menampilkan hal yang statis.
Setelah kita memahami bagaimana cara kerjanya, saya ingin sedikit bercerita ke teman-teman tentang masalah yang saya alami. Secara spesifik, saya memiliki 2 masalah yaitu tidak dapat menampilkan gambar(secara spesifik foto) pada OLED dan tidak dapat membuat kaki bagi figur orang-orangan saya.
Pada masalah pertama, hal tersebut terjadi karena untuk memindahkan foto kedalam OLED, diperlukan sebuah converter dari foto kedalam layar LCD(Pengubahan foto menjadi sebuah kode-kode pixel), namun sayangnya setelah mencari-cari saya tidak menemukan converter untuk hal tersebut bagi OS Mac. Mungkin kedepannya teman-teman bisa melakukan riset lebih dalam lagi untuk mencari converter yang sesuai
Untuk masalah yang kedua, hal tersebut terjadi karena keterbatasan tinggi layar. Pada saat saya menggambarkan badan,kepala,tangan,serta banner dari program modifikasi saya, saya lupa bahwa batas ketinggian untuk OLED hanyalah 64 pixel sehingga pada program awalnya saya membuat kaki pada titik 69 pixel dan hal tersebut tidak dapat digambarkan.
Setelah saya menyadari masalah tersebut, awalnya saya memiliki keiniginan untuk memperkecil gambar yang ada, namun melihat kembali bahwa orang-orangan yang saya buat sudah cukup kecil, maka saya memutuskan untuk membatalkan pembuatan kaki bagi orang tersebut dan membiarkan orang-orangan yang saya buat tidak memiliki kaki. Mungkin kedepannya teman-teman dalam membuat program dapat lebih berhati-hati dalam menentukan skala gambar yang ingin ditampilkan agar tidak mengalami hal yang sama.
Penutup
Sebagai penutup, saya merasa bahwa modul kali ini merupakan modul yang asik dan menyenangkan. Dengan terbukanya dunia display, maka kedepannya saya dapat melakukan berbagai pengaturan yang berhubungan langsung dengan display tanpa lagi bergantung pada serial monitor(contoh: pembuatan sensor yang langsung menampilkan hasil pembacaan pada OLED). Semoga dengan adanya modul ini saya dan teman-teman saya juga dapat semakin berkreasi dengan bebas sehingga dapat berkontribusi bagi dunia tutor ESP-32 ini :D