กลับไปหน้ารวมไฟล์
arduino-gui-library-ddeb70.md

RTT-GUI: เฟรมเวิร์กกราฟิกประสิทธิภาพสูงสำหรับระบบฝังตัวบน Arduino

RTT-GUI เป็นโอเพนซอร์ซ GUI Framework ที่ออกแบบมาเพื่อระบบฝังตัว (Embedded Systems) โดยเฉพาะ โปรเจกต์นี้ได้รับการพัฒนาต่อยอด (Fork) มาจาก RT-Thread Package -- GUI Engine และผ่านการปรับปรุงโครงสร้างใหม่ (Refactoring) อย่างหนักเพื่อให้สามารถใช้งานร่วมกับสภาพแวดล้อมของ Arduino ได้อย่างมีประสิทธิภาพสูงสุด โดยเน้นที่ความยืดหยุ่นและการจัดการทรัพยากรที่มีจำกัดบนไมโครคอนโทรลเลอร์

สถาปัตยกรรมของระบบ (Architecture)

Architecture

หัวใจสำคัญของ RTT-GUI คือการทำงานอยู่บนเลเยอร์ของ Arduino RT-Thread library ซึ่งเป็นระบบปฏิบัติการแบบเรียลไทม์ (RTOS) โดย RT-Thread จะทำหน้าที่จัดการคิวงานและทรัพยากรพื้นฐาน ในขณะที่ RTT-GUI จะรับหน้าที่จัดการส่วนติดต่อผู้ใช้ผ่านทาง Low-level Device Drivers ที่เชื่อมโยงกับฮาร์ดแวร์กราฟิก

โครงสร้างนี้ช่วยให้การพอร์ต (Porting) ไปยังหน้าจอหรือบอร์ดรุ่นต่างๆ ทำได้ง่ายผ่านการนิยามฟังก์ชันมาตรฐานสองส่วนหลัก คือ Common Device Operations และ Graphic Device Operations:

/* common device operations (RT-Thread: "rtdef.h") */
struct rt_device_ops {
  rt_err_t (*init)(rt_device_t dev);
  rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag);
  rt_err_t (*close)(rt_device_t dev);
  rt_size_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
  rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
  rt_err_t (*control)(rt_device_t dev, int cmd, void *args);
};

ในส่วนของ rt_device_ops จะเป็นอินเตอร์เฟซมาตรฐานของ RT-Thread สำหรับการเข้าถึงทรัพยากร เช่น การเปิด/ปิดหน้าจอ หรือการเขียนข้อมูลลงหน่วยความจำวิดีโอ (Frame Buffer)

/* graphic device operations (RTT-GUI: "driver.h") */
struct rtgui_graphic_driver_ops {
  void (*set_pixel)(rtgui_color_t *c, int x, int y);
  void (*get_pixel)(rtgui_color_t *c, int x, int y);
  void (*draw_hline)(rtgui_color_t *c, int x1, int x2, int y);
  void (*draw_vline)(rtgui_color_t *c, int x , int y1, int y2);
  void (*draw_raw_hline)(rt_uint8_t *pixels, int x1, int x2, int y);
};

ส่วน rtgui_graphic_driver_ops จะเน้นไปที่การควบคุมพิกเซลบนหน้าจอโดยเฉพาะ เช่น การวาดจุด (set_pixel) หรือการวาดเส้นตรงในแนวตั้งและแนวนอน ซึ่งเป็นพื้นฐานสำคัญในการเรนเดอร์รูปทรงที่ซับซ้อนขึ้น

ฟังก์ชันกราฟิกพื้นฐาน (Graphic Functions)

RTT-GUI มาพร้อมกับไลบรารีการวาดภาพที่ครอบคลุม ตั้งแต่จุดพื้นฐานไปจนถึงรูปทรงเรขาคณิตที่ซับซ้อน:

/* graphic functions (RTT-GUI: "dc.h") */
void rtgui_dc_draw_point(rtgui_dc_t *dc, int x, int y);

void rtgui_dc_draw_vline(rtgui_dc_t *dc, int x, int y1, int y2);
void rtgui_dc_draw_hline(rtgui_dc_t *dc, int x1, int x2, int y);
void rtgui_dc_draw_line(rtgui_dc_t *dc, int x1, int y1, int x2, int y2);

void rtgui_dc_draw_rect(rtgui_dc_t *dc, rtgui_rect_t *rect);
void rtgui_dc_fill_rect(rtgui_dc_t *dc, rtgui_rect_t *rect);

void rtgui_dc_draw_round_rect(rtgui_dc_t *dc, rtgui_rect_t *rect, int r);
void rtgui_dc_fill_round_rect(rtgui_dc_t *dc, rtgui_rect_t *rect, int r);

void rtgui_dc_draw_annulus(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r1, rt_int16_t r2, rt_int16_t start, rt_int16_t end);

void rtgui_dc_draw_pie(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r, rt_int16_t start, rt_int16_t end);
void rtgui_dc_fill_pie(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r, rt_int16_t start, rt_int16_t end);

void rtgui_dc_draw_polygon(rtgui_dc_t *dc, const int *vx, const int *vy, int count);
void rtgui_dc_fill_polygon(rtgui_dc_t *dc, const int *vx, const int *vy, int count);

void rtgui_dc_draw_circle(rtgui_dc_t *dc, int x, int y, int r);
void rtgui_dc_fill_circle(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r);

void rtgui_dc_draw_arc(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r, rt_int16_t start, rt_int16_t end);

void rtgui_dc_draw_ellipse(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t rx, rt_int16_t ry);
void rtgui_dc_fill_ellipse(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t rx, rt_int16_t ry);

ในการใช้งานฟังก์ชันเหล่านี้ พารามิเตอร์ตัวแรกคือ Device Context (DC) ซึ่งเปรียบเสมือน "บริบทของการวาด" ที่เก็บข้อมูลสถานะปัจจุบัน เช่น สี และขอบเขตการวาด คุณสามารถเริ่มการวาดได้ด้วย rtgui_dc_begin_drawing()

สำหรับการกำหนดสี สามารถทำได้ผ่านมาโคร RTGUI_DC_FC() ซึ่งเป็นการเปลี่ยนสี Foreground (สีหลัก) ของวิดเจ็ตเป้าหมาย:

RTGUI_DC_FC(dc) = GREEN;

สิ่งสำคัญที่สุดคือเมื่อทำการวาดเสร็จสิ้น จะต้องเรียกใช้ rtgui_dc_end_drawing() เพื่อคืนทรัพยากรและสั่งให้ระบบทำการเรนเดอร์ (Render) ข้อมูลลงหน้าจอจริง

/* device context functions (RTT-GUI: "dc.h") */
rtgui_dc_t *rtgui_dc_begin_drawing(rtgui_widget_t *owner);
void rtgui_dc_end_drawing(rtgui_dc_t *dc, rt_bool_t update);
  • rtgui_dc_begin_drawing: รับ Pointer ของวิดเจ็ตที่ต้องการวาดลงไป เพื่อสร้าง Context
  • rtgui_dc_end_drawing: รับ Pointer ของ DC และพารามิเตอร์ Boolean เพื่อระบุว่าต้องการ Force Update (บังคับรีเฟรชหน้าจอทันที) หรือไม่

การจัดการข้อความ (Text Functions)

RTT-GUI รองรับการแสดงผลตัวอักษร ASCII และอักษรจีนตัวย่อ (Unicode) โดยมีขนาดฟอนต์ให้เลือกสองขนาดคือ 12 และ 16 พิกเซล (asc12, asc16, hz12 และ hz16)

เพื่อให้การใช้งานหน่วยความจำ Flash ของไมโครคอนโทรลเลอร์เป็นไปอย่างคุ้มค่า หากโปรเจกต์ของคุณมีการเชื่อมต่อ SD Card แนะนำให้เก็บไฟล์ฟอนต์ไว้ใน SD Card (โดยการตั้งค่า CONFIG_USING_FONT_FILE == 1) โดยปกติ RTT-GUI จะมองหาฟอนต์ในโฟลเดอร์ /font/ โดยอัตโนมัติ

/* font config (RTT-GUI: "guiconfig.h") */
#define CONFIG_USING_FONT_12                (0)
#define CONFIG_USING_FONT_16                (1)
#define CONFIG_USING_FONT_HZ                (0)
#define CONFIG_USING_FONT_FILE              (1)

การวาดข้อความทำได้ง่ายๆ ผ่านฟังก์ชัน:

void rtgui_dc_draw_text(rtgui_dc_t *dc, const char *text, rtgui_rect_t *rect);

การจัดการรูปภาพ (Image Functions)

หนึ่งในจุดเด่นของ RTT-GUI คือความสามารถในการถอดรหัสรูปภาพยอดนิยมได้หลากหลายรูปแบบ โดยใช้ไลบรารีประสิทธิภาพสูงที่ปรับแต่งมาเพื่อ Embedded System:

  • BMP: รูปภาพบิตแมปมาตรฐาน
  • JPEG: รองรับผ่านโปรเจกต์ ChaN's TJpgDec ซึ่งใช้แรมในการถอดรหัสน้อยมาก
  • PNG: รองรับในขั้นทดลอง (Experimental) ผ่าน Lode Vandevenne's LodePNG
  • XPM: รูปแบบภายในของ RTT-GUI สำหรับไอคอนขนาดเล็ก

คุณสามารถเลือกเปิดหรือปิดการใช้งานตัวถอดรหัส (Decoder) แต่ละตัวได้ในไฟล์คอนฟิกเพื่อประหยัดพื้นที่โค้ด:

/* image decoder config (RTT-GUI: "guiconfig.h") */
#define CONFIG_USING_IMAGE_XPM              (1)
#define CONFIG_USING_IMAGE_BMP              (1)
#define CONFIG_USING_IMAGE_JPEG             (1)
#define CONFIG_USING_IMAGE_PNG              (0)

สำหรับการโหลดภาพ สามารถเลือกโหลดจากไฟล์ใน SD Card หรือโหลดจากหน่วยความจำโดยตรง (MemBuffer):

rtgui_image_t *rtgui_image_create_from_file(const char *type, const char *fn, rt_int32_t scale, rt_bool_t load);
rtgui_image_t *rtgui_image_create_from_mem(const char *type, const rt_uint8_t *data, rt_size_t size, rt_int32_t scale, rt_bool_t load);
void rtgui_image_blit(rtgui_image_t *image, rtgui_dc_t *dc, rtgui_rect_t *rect);

กลไกการทำงานของ GUI (GUI Functions)

RTT-GUI ใช้สถาปัตยกรรมแบบ Client-Server โดย Server Thread จะทำงานอยู่เบื้องหลังเพื่อทำหน้าที่ส่งต่อเหตุการณ์ (Dispatch Events) ไปยัง Client ซึ่งก็คือบรรดาวิดเจ็ตต่างๆ ระบบการทำงานจะเหมือนกับ UI Framework ระดับสูง ที่เหตุการณ์จะถูกส่งจากวิดเจ็ตชั้นบนสุด (เช่น Main Window) ลงไปยังวิดเจ็ตลูกที่ลึกที่สุด จนกว่าจะมีตัวใดตัวหนึ่งจัดการเหตุการณ์นั้น (Event Handling)

ขั้นตอนการสร้างแอปพลิเคชัน GUI เริ่มจากการประกาศอินสแตนซ์ของแอป:

CREATE_APP_INSTANCE(app, RT_NULL, "Demo App");

ฟังก์ชันนี้จะเชื่อมโยงแอปพลิเคชันเข้ากับ Thread ปัจจุบัน ข้อควรระวังคือ 1 Thread สามารถมีแอป GUI ได้เพียง 1 อินสแตนซ์เท่านั้น

จากนั้นสร้างหน้าต่างหลัก (Main Window):

main_win = CREATE_MAIN_WIN(win_event_handler, "Demo Window", RTGUI_WIN_STYLE_DEFAULT);

ในฟังก์ชัน win_event_handler เราจะจัดการเหตุการณ์ที่สนใจ เช่น PAINT (การวาดหน้าจอ) และปล่อยให้เหตุการณ์อื่นๆ ถูกจัดการโดย Default Handler:

rt_bool_t win_event_handler(void *obj, rtgui_evt_generic_t *evt) {
  rt_bool_t done = RT_FALSE;
  if (DEFAULT_HANDLER(obj)) {
    done = DEFAULT_HANDLER(obj)(obj, evt);
  }
  if (IS_EVENT_TYPE(evt, PAINT)) {
    done = show_demo(TO_WIN(obj));
  }
  return done;
} 

การเพิ่มวิดเจ็ตอย่าง Label หรือ Button สามารถทำได้ด้วยฟังก์ชันตระกูล CREATE_..._INSTANCE:

label = CREATE_LABEL_INSTANCE(main_win, RT_NULL, RT_NULL, "Label Demo");
WIDGET_TEXTALIGN_SET(label, CENTER_HORIZONTAL);
btn = CREATE_BUTTON_INSTANCE(main_win, RT_NULL, NORMAL, "Button Demo");

เพื่อให้การจัดวางองค์ประกอบง่ายขึ้น RTT-GUI มีระบบ Sizer (Box) เพื่อช่วยจัดตำแหน่งอัตโนมัติ ไม่ต้องกำหนดพิกเซลเองทุกตัว:

sizer = CREATE_BOX_INSTANCE(main_win, RTGUI_HORIZONTAL, 0);
WIDGET_ALIGN_SET(label, STRETCH);
rtgui_box_layout(sizer);

ขั้นตอนสุดท้ายคือการรันแอปพลิเคชัน:

rtgui_win_show(main_win, RT_FALSE);
rtgui_app_run(app);

ฟังก์ชัน rtgui_app_run จะรัน Event Loop และไม่ส่งคืนค่า (Block) จนกว่าหน้าต่างหลักจะถูกปิด

การออกแบบหน้าจอแบบไดนามิก (Dynamic Design)

นอกจากการเขียนโค้ดเพื่อสร้าง UI แล้ว RTT-GUI ยังรองรับการใช้สคริปต์ลักษณะคล้าย JSON เพื่อกำหนดโครงสร้างหน้าจอ ช่วยให้การแยกส่วน Logic และ Design ทำได้ง่ายขึ้น:

APP: {
  PARAM: { "Demo App", NULL },
  MWIN: {
    PARAM: { "Demo Window", hdl },
    BOX: { PARAM: { 1, 0 }, ID: 0 },
    LABEL: {
      PARAM: { NULL, NULL, "Label Demo" },
      TEXTALIGN: 0x01, ALIGN: 0x20
    }
  }
}

ในฝั่งของ Arduino Sketch เราสามารถใช้ rtgui_design_parse() เพื่อแปลงสคริปต์นี้ (ไม่ว่าจะเก็บใน String หรือไฟล์ใน SD Card) ให้กลายเป็นอ็อบเจกต์ GUI จริงๆ ซึ่งช่วยประหยัดเวลาในการแก้โค้ดและคอมไพล์ใหม่เมื่อต้องการเปลี่ยนเพียงแค่ตำแหน่งวิดเจ็ต

ฟังก์ชันการจับภาพหน้าจอ (Capture Screen)

หากคุณเปิดใช้งานการสนับสนุน BMP (CONFIG_USING_IMAGE_BMP == 1) คุณจะสามารถใช้ฟังก์ชัน screenshot() เพื่อบันทึกภาพหน้าจอที่แสดงผลอยู่ปัจจุบันลงใน SD Card ได้ทันที ซึ่งมีประโยชน์มากสำหรับการทำคู่มือหรือการดีบัก โดยสามารถสั่งการผ่าน Shell (FinSH หรือ MSH) ได้โดยตรง:

msh />prtscn demo.bmp 

ตัวอย่างภาพที่ได้จากการบันทึกหน้าจอ: Screenshot Example

ฮาร์ดแวร์ที่รองรับ (Supported Hardware)

ปัจจุบัน RTT-GUI รองรับฮาร์ดแวร์มาตรฐานที่นิยมใช้ในวงการ Maker ดังนี้:

1. Adafruit 2.8" TFT Touch Shield v2

  • หน้าจอ ILI9341 (SPI) และระบบสัมผัส FT6206 (I2C)
#define CONFIG_USING_ADAFRUIT_TFT_CAPACITIVE
#define CONFIG_GUI_DEVICE_NAME "ILI9341"
#define CONFIG_TOUCH_DEVICE_NAME "FT6206"

2. TinyCircuits Pocket Arcade

  • หน้าจอ SSD1331 OLED (SPI) พร้อมปุ่มกดในตัว
#define CONFIG_USING_TINYSCREEN
#define CONFIG_GUI_DEVICE_NAME "SSD1331"
#define CONFIG_KEY_DEVICE_NAME "BTN"

3. Generic 0.96" OLED module

  • หน้าจอ SSD1306 (SPI) สำหรับการแสดงผลขาวดำ (Monochrome)
#define CONFIG_USING_SSD1306_SPI4
#define CONFIG_GUI_DEVICE_NAME "SSD1306"
#define CONFIG_USING_MONO (1)

ตัวอย่างการใช้งาน (Examples)

นอกจากเดโมพื้นฐานแล้ว ยังมีโปรเจกต์ตัวอย่างที่ซับซ้อนขึ้น เช่น "Pic Show" สำหรับเปิดดูรูปภาพ และ "File Browser" สำหรับสำรวจไฟล์ใน SD Card ซึ่งมีให้ศึกษาทั้งแบบเขียนโค้ดสดและแบบใช้สคริปต์ Dynamic Design

ขั้นตอนต่อไป (Next Steps)

เพื่อความเข้าใจที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับการทำงานของระบบพื้นฐาน คุณสามารถศึกษาเพิ่มเติมได้จากหัวข้อเหล่านี้:

ข้อมูล Frontmatter ดั้งเดิม

title: "Arduino GUI Library"
description: "Need a simple GUI for your Arduino project? Check this out!"
author: "onelife"
category: "Screens & Displays"
tags:
  - "rt-thread"
  - "rtos"
  - "gui"
views: 29999
likes: 5
price: 99
difficulty: "Intermediate"
components:
  - "1x Adafruit 2.8\" TFT Touch Shield v2 (Capacitive)"
  - "1x Arduino Due"
tools: []
apps:
  - "1x Arduino IDE"
downloadableFiles:
  - "https://create.arduino.cc/editor/onelife/d0ef35f4-74aa-4211-91a1-21757501b47c/preview"
  - "https://create.arduino.cc/editor/onelife/4ccc6e2e-4811-464a-822b-d49782dde1c2/preview"
  - "https://create.arduino.cc/editor/onelife/4ccc6e2e-4811-464a-822b-d49782dde1c2/preview"
documentationLinks: []
passwordHash: "fd1b92a907b85705750e1d895ec0c20a665c13d80a3e767ee7a9b178c0b87329"
encryptedPayload: "U2FsdGVkX19nkt7EiX/PtXbBF3YJaa1a5D2bbP4cWvgQImgB0FliEOXlbz/eJ06Zjb5K7SHgTVHUpedw1Gv5NZNRoeEpt/aMDENDRKTDMNw="
seoDescription: "Create simple Arduino GUI easily with this Library. Perfect for your Arduino projects to build user interfaces quickly."
videoLinks: []
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/arduino-gui-library-ddeb70_cover.jpg"
lang: "en"