#5 Playing with OLED Display and PWM Signals
Hi! Hello! Selamat datang kembali di #SEJourney!
Kali ini, kita akan mencoba bermain dan bereksperimen menggunakan OLED Display dan juga PWM Signals!
First thing first, Apa itu OLED Display?
OLED merupakan singkatan dari Organic Light-Emitting Diode. Sesuai dengan namanya, OLED terbentuk dari bahan organic untuk memancarkan cahaya ketika kontak dengan listrik. Nah, untuk eksperimen kali ini, saya akan menggunakan OLED model SSD1306 (0.96 inch, 128x64 px). Nah,OLED Display hemat energi karena lampu latar tidak perlu diberi daya. Refresh rate pada layar OLED juga lebih cepat karena tidak adanya pixel shutters. Dan juga, warna hitam yang ditampilkan juga benar-benar hitam, karena pixel dimatikan sepenuhnya. Model SSD1306 ini memiliki 4 pin dan akan berkomunikasi dengan microcontroller apapun menggunakan I2C communication protocol.
Untuk project kali ini, kita akan lebih banyak bermain dengan code daripada dengan rangkaiannya. Langsung saja, mari kita siapkan rangkaiannya terlebih dahulu.
Alat yang diperlukan:
- Laptop
- ESP32
- Micro USB Cable
- Breadboard
- Kabel jumper Male to Male
- OLED Display (SSD 1306)
Mari kita merangkai!
Rangkaian pada project ini cukup simple, kita hanya perlu menghubungkan OLED dengan ESP32 dengan detail sebagai berikut:
- SCL -> GPIO 22
- SDA -> GPIO 21
- VCC -> 3V3
- GND -> GND
Maka, rangkaian akan terlihat seperti gambar di bawah!
Displaying Various Shapes
Untuk displaying yang pertama, mari kita coba dari template yang ada di Library Adafruit yang bisa kalian akses dengan cara File > Examples > Adafruit SSD1306 > SSD1306_128x64_i2c atau kalian bisa copy paste dari code di bawah ini!
/*********
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);
}
}
}
}
Jika kalian Upload code, akan menampilkan hasil seperti ini!
Displaying “Hello, World!” , Playing with Another Functions!
Selanjutnya, mari kita menuliskan “Hello, World!”.
Kalian bisa menggunakan code di bawah ini.
/*********
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() {
}
Kalian akan mendapatkan output pada OLED seperti gambar di bawah!
Variation!
Disini kalian bisa juga menambahkan variasi seperti mengatur ukuran size dengan metode setTextSize( ):
display.setTextSize(1);
Atau, bisa juga kalian ganti font menggunakan metode setFont():
#include <Fonts/FreeSerif12pt7b.h>
display.setFont(&FreeSerif12pt7b);
Dengan catatan, size diset oleh font aslinya, sehingga penggunaan setTextSize() tidak bisa diterapkan pada fonts yang diatur manual.
Bagaimana dengan membuat text agar bisa scrolling?
Untuk hal itu, kalian bisa menambahkan method dari Library Adafruit, diantaranya:
startscrollright(0x00, 0x0F): kiri ke kanan
startscrollleft(0x00, 0x0F): kanan ke kiri
startscrolldiagright(0x00, 0x07): kiri bawah ke kanan atas
startscrolldiagleft(0x00, 0x07): kanan bawah ke kiri atas
Mari kita eksperimen dengan mengganti font, menambahkan tulisan lain di bawah “Hello, World!”, dan membuat kedua tulisan tersebut bergerak dari kanan ke kiri!
Kalian bisa lihat code di bawah sebagai contoh ya!
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSerif12pt7b.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.setFont(&FreeSerif12pt7b);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 20);
display.println("Hello, world! ");
display.println(" I'm Trista!");
display.display();
}
void loop() {
// scroll to right, pause, then to left
display.startscrollleft(0x00, 0x0F);
delay(2000);
}
Outputnya akan terlihat seperti ini!
Creating a Moving Animations (This is the fun part)!
Sedikit cerita, sebelum memulai project ini, sempat terlintas dalam benak saya untuk membuat display karakter anime favorit saya. Tetapi, saya tidak ingin hanya gambar yang diam, bagaimana ya agar bisa membuat moving animations? 🤔🤔🤔
Let me show you how!
- Yang pertama, kalian harus mencari gambar dalam format gif yang akan kita pecah menjadi frame-frame foto.
- Gunakan website ezgif.com/resize untuk resize gif sesuai ukuran OLED Display (128px by 64px). [not sponsored]
- Setelah menyesuaikan ukuran, (masih) gunakan website ezgif.com/split untuk memecah gif menjadi frame-frame foto dan simpan dalam bentuk zip.
- Selanjutnya, kita perlu mengubah foto-foto tadi menjadi array bytes. Disini saya menggunakan website https://javl.github.io/image2cpp/, pilih dan susun file foto sesuai dengan urutan pada gif ya!
- Atur dalam bentuk Arduino code (bisa coba yang lain jika nanti output gagal), lalu atur identifier/prefix untuk memudahkan kalian identifikasi file.
- Generate code!
Nah, kalian akan mendapatkan code untuk animasi gifnya.
Karena code untuk displaying terlalu panjang, saya akan melampirkan link github untuk acuan set-up dan sesuaikan dengan generated code yang kalian dapatkan.
https://github.com/reswaratrista/Project-Embedded-System.git
Jangan lupa untuk mengganti bagian-bagian yang merupakan nama byte array kalian ya, disini punya aku adalah “epd_bitmap_xx”.
Misalnya, pada bagian di bawah ini.
void loop() {
// Display Animation
// epd_bitmap_1
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_01, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_2
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_02, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_3
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_03, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_4
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_04, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_5
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_05, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_6
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_06, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_7
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_07, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_8
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_08, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_9
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_09, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_10
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_10, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_11
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_11, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_12
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_12, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_13
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_13, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_14
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_14, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_15
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_15, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_16
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_16, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_17
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_17, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_18
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_18, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_19
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_19, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_20
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_20, 128, 64, 1);
display.display();
delay(frame_delay);
// epd_bitmap_21
display.clearDisplay();
display.drawBitmap(0,0,epd_bitmap_21, 128, 64, 1);
display.display();
delay(frame_delay);
Setelah itu, bisa coba upload dan untuk code yang saya buat akan menghasilkan output seperti ini:
Bonus!
Disini, saya akan mencoba untuk menggunakan sinyal PWM dengan ESP32 menggunakan Arduino IDE untuk mengatur nyala-mati dan tingkat kecerahan LED.
Disini, kita membutuhkan beberapa komponen tambahan, diantaranya:
- Resistor 330k Ohm
- LED 5mm
Selanjutnya, mari kita merangkai!
Hubungkan komponen komponen sesuai dengan gambar di bawah, disini saya menggunakan 3 buah LED, maka dibutuhkan pula 3 buah resistor.
Lalu, upload code di bawah untuk menjalankan rangkaian.
// the number of the LED pin
const int ledPin = 18; // 18 corresponds to GPIO18
const int ledPin2 = 21; // 21 corresponds to GPIO21
const int ledPin3 = 22; // 22 corresponds to GPIO22
// 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);
}
}
Jika tidak ada trouble, kalian bakan mendapatkan output seperti video di bawah.
Review Time!
Sejauh ini, project ini merupakan project favorit saya karena saya bisa bermain dengan code untuk menghasilkan output yang berbeda-beda.
⭐100/10
Sampai bertemu di project selanjutnya!