จากบทความการใช้ ESP8266 กับจอแสดงผลกราฟิกแบบ OLED ซึ่งเขียนด้วยภาษาไพธอนจะพบว่าการทำงานนั้นสะดวกรวดเร็วในระดับดี แต่เมื่อต้องใช้กับไมโครคอนโทรลเลอร์ตัวอื่นที่ไม่สามารถใช้ Micropython หรือ CircuitPython ได้นั้นจะต้องทำอย่างไร ซึ่งหนึ่งในหลายทางเลือกคือไลบรารี u8glib หรือ u8g2 (Universal 8 bit Graphics Library) ที่ออกแบบมาเพื่อทำงานกับกราฟิกแบบ 8 บิตแบบโมโนโครมทั้งผ่านการสื่อสารแบบ I2C หรือ SPI โดยบทความนี้ใช้อุปกรณ์ต่อเชื่อมกันดังภาพที่ 1 ด้วยการใช้ OLED แบบ I2C
อุปกรณ์
- บอร์ด STM32F401CCU6, STM32F103C หรือ STM32F411CEU6 หรือ
esp8266 หรือ esp32 โดยเชื่อมต่อกับขาที่เป็น I2C แบบฮาร์ดแวร์ซึ่งดูรายละเอียดของขาได้จากบทความก่อนหน้านี้ - โมดูล OLED สีฟ้า หรือสีขาว โดยพวกเรามีตัวสีฟ้า
- ไลบรารี u8g2 ที่ติดตั้งจากเมนู Sketch/Include Library แล้วเลือก Libraries Manager… หลังจากนั้นค้นหาและติดดั้ง u8g2 ดังภาพที่ 2
u8g2
u8g2 เป็นไลบรารีกราฟิกสำหรับใช้งานโมดูลแสดงผลกราฟิกของคอนโทรเลอร์ SSD1305, SSD1306, SSD1309, SSD1322, SSD1325, SSD1327, SSD1329, SSD1606, SSD1607, SH1106, SH1107, SH1108, SH1122, T6963, RA8835, LC7981, PCD8544, PCF8812, HX1230, UC1601, UC1604, UC1608, UC1610, UC1611, UC1701, ST7565, ST7567, ST7588, ST75256, NT7534, IST3020, ST7920, LD7032, KS0108, SED1520, SBN1661, IL3820, MAX7219
คำสั่งที่ใช้ได้กับ u8g2 ได้แก่ คำสั่งวาดเส้น วาดสี่เหลี่ยม และวงกลม พร้อมทั้งรองรับการแสดงผลด้วยตัวอักษรที่หลากหลาย และต้องการหน่วยความจำจากไมโครคอนโทรลเลอร์สำหรับการเรนเดอร์ผลลัพธ์
การเลือกประเภทของหน่วยแสดงผล
รายการโมดูลแสดงผลที่ u8g2 รองรับได้แก่ SSD1305, SSD1306, SSD1309, SSD1322, SSD1325, SSD1327, SSD1329, SSD1606, SSD1607, SH1106, SH1107, SH1108, SH1122, T6963, RA8835, LC7981, PCD8544, PCF8812, HX1230, UC1601, UC1604, UC1608, UC1610, UC1611, UC1701, ST7565, ST7567, ST7588, ST75256, NT7534, IST3020, ST7920, LD7032, KS0108, SED1520, SBN1661, IL3820, MAX7219 โดยรายละเอียดลงถึงความละเอียดในการแสดงผลและประเภทของบัสการเชื่อมต่อ เช่น
- U8G2_NULL u8g2(U8G2_R0); // null device, a 8×8 pixel display which does nothing
- U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=/ 13, / data=/ 11, / cs=/ 10, / dc=/ 9, / reset=/ 8);
- U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, / cs=/ 12, / dc=/ 4, / reset=/ 6); // Arduboy (Production, Kickstarter Edition)
- U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, / cs=/ 10, / dc=/ 9, / reset=/ 8);
- U8G2_SSD1306_128X64_NONAME_F_3W_SW_SPI u8g2(U8G2_R0, / clock=/ 13, / data=/ 11, / cs=/ 10, / reset=/ 8);
- U8G2_SSD1306_128X64_NONAME_F_3W_HW_SPI u8g2(U8G2_R0, / cs=/ 10, / reset=/ 8);
- U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, / reset=/ U8X8_PIN_NONE);
- U8G2_SSD1306_128X64_ALT0_F_HW_I2C u8g2(U8G2_R0, / reset=/ U8X8_PIN_NONE); // same as the NONAME variant, but may solve the “every 2nd line skipped” problem
- …เยอะมาก
- U8G2_LS013B7DH05_144X168_F_4W_HW_SPI u8g2(U8G2_R0, / cs=/ 10, / dc=/ U8X8_PIN_NONE, / reset=/ 8); // there is no DC line for this display
- U8G2_ST7511_AVD_320X240_F_8080 u8g2(U8G2_R0, 13, 11, 2, 3, 4, 5, 6, A4, /enable/WR=/ 7, /cs=/ 10, /dc=/ 9, /reset=*/ 8); // Enable U8g2 16Bit Mode and connect RD pin with 3.3V/5V
โดยให้เลือกรายการดังนี้ เนื่องจากเลือกใช้โมดูลแสดงผล I2C OLED ที่ใช้ชิพ SH1106
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
กรณีที่แสดงผลผิดพลาดให้เลือกเป็น SSD1306 ดังนี้
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
โปรแกรมสำหรับแสดงข้อความสวัสดี (Hello, World) เขียนดังนี้ ซึ่งได้ผลลัพธ์ดังตัวอย่างภาพที่ 3
#include <Arduino.h>
#include <U8g2lib.h>
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_etl14thai_t );
u8g2.drawStr(8,37, "Hello World!");
u8g2.sendBuffer();
delay(1000);
}
จากโค้ดจะพบว่าได้ทำการล้างบัฟเฟอร์ หลังจากนั้นวาดลงบัฟเฟอร์ และสั่งนำบัฟเฟอร์ไปแสดงผลด้วย sendBuffer() แล้วหน่วงเวลา 1 วินาที
#define U8G2_WITH_UNICODE
#include <Arduino.h>
#include <U8g2lib.h>
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_etl14thai_t );
u8g2.drawStr(8,37, "Hello World!");
} while ( u8g2.nextPage() );
delay(1000);
}
ทั้ง 2 วิธีให้ผลลัพธ์เหมือนกันแต่วิธีหลังเป็นการมองวิธีการสั่งงานแบบมีหลายหน้า โดยเริ่มจาก firstPage() หลังจากนั้นวาดข้อมูลลงไป และ nextPage() เป็นการนำข้อมูลที่วาดไปอัพเดตแล้วหน่วงเวลา 1 วินาที
คำสั่งที่ควรรู้จัก
คำสั่งที่ควรรู้จักเพื่อเป็นพื้นฐานการใช้งาน u8g2 ซึ่งเป็นเมธอดของวัตถุที่สืบทอดมาจาก u8g2 ด้วยรูปแบบต่อไปนี้ โดยในตัวอย่างก่อนหน้านี้ได้สร้างวัตถุของโมดูลแสดงผลประเภท U8G2_SH1106_128x64_NONAME_F_HW_I2C ภายใต้ชื่อ u8g2
ชื่อไดรเวอร์ ชื่อวัตถุ( อาร์กิวเมนต์ )
ซึ่งอาร์กิวเมนต์หรือพารามิเตอร์ที่ใช้มี 2 รายการ คือ
- รูปแบบของเลเอาต์ ซึ่งมีตัวเลือกดังนี้
- U8G2_R0 แสดงแบบแนวนอน
- U8G2_R1 หมุมจาก R0 ไป 90 องศา
- U8G2_R2 หมุนจาก R0 ไป 180 องศา
- u8G2_R3 หมุนจาก R0 ไป 270 องศา
- U8G2_MIRROR แสดงกลับด้านกับ U8G2_R0 ซึ่งสามารถสั่ง setFlipMode() ในโปรแกรมได้
- รายละเอียดขาที่เชื่อมต่อ ตอนนี้เป็น U8x8_PIN_NONE
คำสั่งต่าง ๆ มีดังนี้
- วัตถุ.displayRotation( ทิศการหมุน )
- วัตถุ.enableUTF8Print() สำหรับเปิดการแสดงผลตัวอักษรในแบบ UTF8
- วัตถุ.disableUTF8Print() ปิดการแสดงผลในลักษณะตัวอักษรแบบ UTF8
- วัตถุ.setFont( ชื่อฟอนต์ที่ต้องการใช้ ) สำหรับเลือกใช้รูปแบบตัวอักษรที่ใช้ในการแสดงผล
- u8g2_font_etl14thai_t
- u8g2_font_etl16thai_t
- u8g2_font_etl24thai_t
- u8g2_font_ncenB08_tr
- u8g2_font_ncenB14_tr
- อื่น ๆ
- วัตถุ.drawStr( x, y, “ข้อความ” ) สำหรับแสดงข้อความที่ตำแหน่ง (x,y)
- วัตถุ.drawUTF8( x, y, “ข้อความ” )
- วัตถุ.setDrawColor( ค่าสี ) เป็นการกำหนดค่าสีในการวาด โดยค่าสีเป็น 0 หรือ 1
- ความยาวข้อความ = วัตถุ.getStrWidth( ข้อความ )
- ความยาวข้อความ = วัตถุ.getUTF8Len( ข้อความ )
- วัตถุ.setFontDirection( ทิศทาง ) กำหนดทิศทางการวาดโดยทิศทางคือ
- 0 วาดจากซ้ายไปขวา
- 1 วาดจากบนลงล่าง
- 2 วาดจากขวาไปซ้าย
- 3 วาดจากล่างขึ้นบน
- วัตถุ.setFontMode( โหมดความโปร่งแสง )
- ขนาดบัฟเฟอร์ = วัตถุ.getBufferSize()
- วัตถุ.clearBuffer() สำหรับล้างค่าในบัฟเฟอร์ส่งผลให้ข้อมูลที่สั่งแสดงไว้ถูกลบออก หรือสั่ง
วัตถุ.firstPage() - วัตถุ.sendBuffer() นำข้อมูลจากบัฟเฟอร์ส่งไปแสดงผลที่โมดูลกราฟิก หรือใช้ในรูปแบบของ
do {
…สิ่งที่วาด…
} while (วัตถุ.nextPage()); - วัตถุ.initDisplay() ทำการรีเซ็ตค่าการแสดงผล
- วัตถุ.drawBitmap(x,y,cnt,h,บัฟเฟอร์ของภาพ) โดย cnt คือจำนวนไบต์ของภาพในแนวนอน (1 ไบต์มี 8 บิต หรือ 8 ชุด) ซึ่งความกว้างของภาพจะเท่ากับ cnt*8
- วัตถุ.setBitmapMode( โหมดความโปร่งแสง ) ถ้าโหมดความโปร่งแสง 0 จะวาดทับ แต่ถ้าเป็น 1 จะวาดซ้อนโดยไม่วาดข้อมูล 0 ทับข้อมูลก่อนหน้า
- วัตถุ.drawBox(x,y,w,h) วาดกล่องแบบทึบ
- วัตถุ.drawRBox(x,y,w,h,r) วาดกล่องทึบแบบขอบมน โดย r คือองศาของขอบ
- วัตถุ.drawFrame( x, y, w, h ) วาดกล่องแบบมีแต่เส้นขอบ
- วัตถุ.drawRFrame( x, y, w, h,r ) วาดเฟรมแบบขอบมน โดย r คือองศาของขอบ
- วัตถุ.drawCircle(x0, y0,rad) วาดวงกลมโดย opt มีค่าดังนี้
- U8G2_DRAW_UPPER_RIGHT
- U8G2_DRAW_UPPER_LEFT
- U8G2_DRAW_LOWER_LEFT
- U8G2_DRAW_LOWER_RIGHT
- U8G2_DRAW_ALL
- วัตถุ.drawDisc(x0, y0, rad, opt=U8G_DRAW_ALL) วาดวงกลมทึบ
- วัตถุ.drawEllipse( x0, y0, rx, ry, opt ) วาดวงรี โดย opt มีค่าดังนี้
- U8G2_DRAW_UPPER_RIGHT
- U8G2_DRAW_UPPER_LEFT
- U8G2_DRAW_LOWER_LEFT
- U8G2_DRAW_LOWER_RIGHT
- U8G2_DRAW_ALL
- วัตถุ.drawFilledEllipse( x0, y0, rx, ry, opt ) วาดวงรีทึบ
- วัตถุ.drawGlyph( x, y, อักขระ )
ตัวอย่างการวาดตุ๊กตาหิมะจาก Glyph เป็นดังนี้ โดยตัวอย่างเป็นดังภาพที่ 4 และตัวอย่างอื่น ๆ เป็นดังภาพที่ 5
#define U8G2_WITH_UNICODE
#include <Arduino.h>
#include <U8g2lib.h>
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_unifont_t_symbols);
u8g2.drawGlyph(5, 20, 0x2603); /* dec 9731/hex 2603 Snowman */
} while ( u8g2.nextPage() );
delay(1000);
}
ตัวอย่างการใช้ drawUTF8() เพื่อแสดงข้อความในแบบการเข้ารหัสยูนิโค้ด utf-8 เป็นดังนี้ และตัวอย่างผลลัพธ์เป็นดังภาพที่ 6
#define U8G2_WITH_UNICODE
#include <Arduino.h>
#include <U8g2lib.h>
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_etl14thai_t );
u8g2.drawUTF8(8,37, "สวัสดี");
} while ( u8g2.nextPage() );
delay(1000);
}
- วัตถุ.drawHLine(x,y,w) วาดเส้นแนวนอน
- วัตถุ.drawVLine(x,y,h) วาดเส้นแนวตั้ง
- วัตถุ.drawLine(x0, y0, x1, y1) วาดเส้นตรงจาก (x0,y0) ไปยัง (x1,y1)
- วัตถุ.drawPixel( x, y )
- วัตถุ.drawTriangle(x0,y0,x1,y1,x2,y2) วาดสามเหลี่ยม
- วัตถุ.drawXBM(x,y,w,h,บัฟเฟอร์) วาดบัฟเฟอร์ที่อยู่ในรูปแบบ XBM
- วัตถุ.drawXBMP(x,y,w,h,บัฟเฟอร์) วาดบัฟเฟอร์ที่อยู่ในรูปแบบ XBMP
- uint8_t = วัตถุ.getAscent() หาค่าความสูงของตัวอักษรโดยวัดระยะจาก base lineขึ้นมา
- uint8_t = วัตถุ.getDescent() หาค่าความสูงของวัตถุโดยวัดระยะตั้งแต่ base line ลงมา
- ความสูงของจอ = วัตถุ.getDisplayHeight()
- ความกว้างของจอ = วัตถุ.getDisplayWidth()
- ความสูงสูงสุดของตัวอักษร = วัตถุ.getMaxCharHeight()
- ความกว้างสูงสุดของตัวอักษร = วัตถุ.getMaxCharwidth()
- วัตถุ.setClipWindow(x0,y0,x1,y1) กำหนดกรอบหน้าต่าง ซึ่งป้องกันการวาดส่วนที่อยู่กรอบที่กำหนด
- วัตถุ.setContrast( ระดับความเปรียบต่าง ) ปรับระดับการเปรียบต่างของการแสดงโดยกำหนดเป็นค่าจำนวนเต็มในช่วง 0 ถึง 255
- วัตถุ.setCursor( x, y ) ย้ายตำแหน่งการแสดงผลไปที่แถวที่ y คอลัมน์ที่ x โดยคำนวนจากขนาดของตัวอักษร
- วัตถุ.print( ข้อมูล ) แสดงข้อมูลที่เคอร์เซอร์
- วัตถุ.updateDisplay() สั่งอัพเดตหน้าจอ
- วัตถุ.updateDisplayArea( tx, ty, tw, th ) อัพเดตเฉพาะในกรอบ (tx,ty) – (tx+tw, ty+th)
ตัวอย่างการใช้กับ SSD1306 และไมโครคอนโทรลเลอร์ STM32F103 ดังภาพที่ 7
#define U8G2_WITH_UNICODE
#include <Arduino.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
void setup(void) {
u8g2.begin();
}
void loop(void) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.setFontRefHeightAll(); /* this will add some extra space for the text inside the buttons */
u8g2.userInterfaceMessage("Title1", "Title2", "Title3", " Ok \n Cancel ");
} while ( u8g2.nextPage() );
delay(1000);
}
สรุป
จากบทความนี้จะพบว่า การใช้งาน u8g2 ในการควบคุมการแสดงผลของโมดูล OLED ทำให้การเขียนโปรแกรมมีความสะดวก และการทำงานรวดเร็ว นอกจากนี้ยังรองรับการทำงานกับไมโครคอนโทรลเลอร์หลากหลายรุ่นอีกด้วย แต่อย่างไรก็ดี ทางทีมงานไม่ได้กล่าวรายละเอียดในส่วนของการติดต่อกับผู้ใช้เนื่องจากต้องมีการต่อวงจรเพิ่มเติมเพื่อเป็นส่วนรำเข้าข้อมูลจากผู้ใช้ ซึ่งถ้ามีโอกาสจะดำเนินการปรับปรุงบทความนี้ต่อไป สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ
แหล่งอ้างอิง
- u8g2
- u8g2 reference
- kritsada arjchariyaphat, “มาทำให้ไมโครคอนโทรลเลอร์แสดงผลภาษาไทย (utf-8) ผ่านจอ LCD กันเถอะ“
(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-08-11, 2021-08-12, 2021-11-18