กลับไปหน้ารวมไฟล์
good-enough-menu-gem-73bb5a.md


ว่าไงทุกคน! สบายดีกันมั้ยวัยรุ่น?

รุ่นพี่เขียนไลบรารีสำหรับทำเมนูกราฟิกบน Arduino อีกอันนึงเข้าไป (มีตั้งเยอะแยะแล้วไม่ใช่เหรอ?) แต่สำหรับพี่มัน "พอใช้ได้" ก็เลยคิดว่าน่าจะมีประโยชน์กับใครบางคนเหมือนกัน =)

GEM (หรือที่รู้จักกันในนาม Good Enough Menu) - เป็นไลบรารี Arduino สำหรับสร้างเมนูกราฟิกหลายระดับ ที่มีไอเท็มเมนูแบบแก้ไขได้ เช่น ตัวแปร (รองรับประเภทข้อมูล int, byte, float, double, boolean, char[17]) และตัวเลือกแบบเลือกได้ (option selects) นอกจากนี้ยังสามารถกำหนดฟังก์ชัน callback ที่เขียนเองให้ทำงานตอนที่บันทึกค่าเมนูไอเท็มได้อีกด้วย

รองรับปุ่มที่สามารถเรียกใช้การกระทำที่ผู้ใช้กำหนดเอง และสร้าง context เฉพาะสำหรับการกระทำนั้นๆ ซึ่ง context นั้นก็สามารถมีฟังก์ชัน callback สำหรับตอนเข้า (setup) และออก (exit) รวมถึงฟังก์ชัน loop เป็นของตัวเองได้อีกด้วย งานจัดเต็ม!

รองรับไลบรารีกราฟิกหลักๆ คือ AltSerialGraphicLCD, U8g2 และ Adafruit GFX เลือกใช้ตามใจชอบได้เลย

ไลบรารีนี้มาพร้อมกับ เอกสารประกอบ และ ตัวอย่างโค้ดพร้อมคำอธิบาย ที่จำเป็นทั้งหมด รวมถึงยังมี Wiki บน GitHub ที่มีคำแนะนำเพิ่มเติมสำหรับตัวอย่างและวิธีใช้งานต่างๆ อีกด้วย ไปหาอ่านกันได้

ควรใช้เมื่อไหร่?

ถ้าน้องอยากติดตั้งจอ LCD กราฟิกให้โปรเจคของน้อง และให้ผู้ใช้สามารถเลือกตัวเลือกหรือตั้งค่าต่างๆ เพื่อกำหนดการทำงานของมันได้ ไม่ว่าจะเป็นคอนโทรลพาเนลสำหรับสมาร์ทโฮม หรือแค่สาย LED ธรรมดาที่อยากให้ตั้งค่าได้ GEM ก็จะเตรียมเครื่องมือควบคุมทั้งหมดที่จำเป็นสำหรับการแก้ไขตัวแปร การเลื่อนไปมาระหว่างซับเมนู รวมถึงการรันฟังก์ชันที่ผู้ใช้กำหนดเองไว้ให้แล้ว จัดไปวัยรุ่น!

โครงสร้าง

เมนูที่สร้างด้วยไลบรารี GEM ประกอบด้วยองค์ประกอบพื้นฐาน 3 อย่าง:

  • เมนูไอเท็ม (คลาส GEMItem) - เป็นตัวแทนของตัวแปร, ปุ่ม, หรือลิงก์ไปยังระดับเมนูถัดไป (เมนูเพจ);
  • เมนูเพจ (คลาส GEMPage) - ประกอบด้วยรายการของเมนูไอเท็ม และเป็นตัวแทนของระดับเมนูหนึ่งๆ;
  • ตัวเมนูเอง (คลาส GEM, หรือ GEM_u8g2, หรือ GEM_adafruit_gfx) - สามารถมีเมนูเพจได้หลายอัน (เชื่อมโยงถึงกัน) ซึ่งแต่ละเพจก็มีเมนูไอเท็มได้หลายอัน

Deep UI Software Architecture: Good Enough Menu (GEM)

การทำให้จอ LCD แสดงอุณหภูมิเนี่ย ง่ายจะตายไป แต่การทำ "เมนูตั้งค่า" แบบละเอียดยิบที่มีหน้าเมนูซ้อนกัน 12 ชั้น (เช่น Settings -> Advanced -> Calibrate -> Confirm) โดยใช้ logic if/else แบบธรรมดาเนี่ย มันคือฝันร้ายที่แท้จริงที่จะทำให้โค้ดพันกว่าบรรทัดพังไม่เป็นท่า! โปรเจค Good Enough Menu (GEM) นี้เปลี่ยนโฉมการทำ UI ไปเลย! มันแนะนำ Data Structures แบบ C++ ที่ชัดเจน (Linked Lists และ Pointers) ที่ออกแบบมาโดยเฉพาะเพื่อจัดการกับการสร้างเมนูหลายระดับสำหรับฮาร์ดแวร์แบบโหดๆ ให้เป็นไปโดยอัตโนมัติ! ห้ามช็อตนะตัวนี้

Instantiating Page Structs (`GEM_Adafruit_GFX`)

ไลบรารี GEM ใช้หลักการ Object-Oriented ของ C++ แบบลึกซึ้งขั้นสูง

  1. น้องไม่ต้องมานั่งเขียน if (button == UP) { lcd.print("Settings"); } แบบมือเปล่าอีกแล้ว!
  2. น้องสร้าง object โครงสร้าง UI ขนาดใหญ่ระดับโลก (global) เลย!
    // Create the explicit Menu Objects!
    GEMPage menuPageMain("Main Menu"); // The Root!
    GEMItem menuItemTemp("Set Temp", tempVariable); // Creates an editable number UI physically linked to the CPU variable!
    

GEMItem menuItemRun("Start Motor", triggerMotorFunction); // เชื่อมโยงไปยังฟังก์ชัน!

// เอามาเชื่อมกันซะ! menuPageMain.addMenuItem(menuItemTemp); menuPageMain.addMenuItem(menuItemRun);

  • ไลบรารี GEM จะคำนวณตำแหน่งที่มันจะถูกวาดบนหน้าจอ OLED ให้อัตโนมัติ, จัดการลูกศรเลือก `->` ให้เลื่อนได้, และจัดการคณิตศาสตร์ของ "หน้าถัดไป" ได้อย่างราบรื่นโดยที่เราไม่ต้องไปยุ่ง!
  • เชื่อมต่อ Rotary Encoder ได้แบบเนียนๆ

    การเลื่อนเมนูในลิสต์ยาวเหยียด 50 รายการด้วยปุ่มกดธรรมดา `(ขึ้น/ลง)` มันเหนื่อยมือชัดๆ

    • โครงสร้างซอฟต์แวร์ของมันถูกออกแบบมาให้เชื่อมต่อกับ **Rotary Encoders** ได้ทันที
    • มันตรวจจับตรรกะอินเตอร์รัพต์แบบ Quadrature จากขา `A และ B` ของ Encoder ที่หมุนได้ทั้งซ้ายและขวาไม่สิ้นสุด!
      if (encoderSpunRight) { gem.setMenuDirection(GEM_KEY_DOWN); }
      if (buttonPressed) { gem.setMenuDirection(GEM_KEY_OK); }
      
    • แค่กดปุ่มบน Encoder ก็จะเรียกฟังก์ชันคอลแบ็ก `triggerMotorFunction()` ทันที, เรียกใช้สคริปต์ C++ ได้เหมือนกับ UI แบบกราฟิกในเครื่องพิมพ์ 3D ระดับมืออาชีพเลยทีเดียว!

    คลังแสงสำหรับสร้าง UI

    • **Arduino Uno/Mega** (แนะนำ Mega อย่างแรงถ้าเมนูเยอะมากๆ เพราะโครงสร้างข้อมูล C++ พวก Struct กินพื้นที่ SRAM เยอะ ซึ่ง Uno อาจจะไม่พอ)
    • **จอ OLED ขนาดใหญ่ 128x64 แบบ I2C (SSD1306)** หรือ **จอ SPI TFT ขนาด 2.8"**
    • **Rotary Encoder (KY-040) แบบมีปุ่มกดในตัว** (จำเป็นมากถ้าอยากได้ประสบการณ์ UI ที่ลื่นไหลและเท่กว่าการใช้ปุ่มกดธรรมดา)
    • *ติดตั้งไลบรารี GEM ผ่าน Library Manager ใน Arduino IDE โดยตรง*

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

    มาลองดูตัวอย่างหนึ่งที่มากับไลบรารีกันดีกว่า อย่าง **Example 03: Party Hard!** (หรือจะลองดูตัวอย่างอื่นๆ ที่มากับไลบรารี GEM ก็ได้นะ)

    การติดตั้ง

    รูปแบบไลบรารีนี้เข้ากันได้กับ Arduino IDE 1.5.x ขึ้นไป มีสองวิธีในการติดตั้ง:

    • ดาวน์โหลดไฟล์ ZIP ตรงจากส่วน Releases ของ repository บน GitHub แล้วแตกไฟล์ลงในโฟลเดอร์ชื่อ GEM ภายในโฟลเดอร์ Library ของคุณ
    • ใช้ Library Manager (ตั้งแต่ Arduino IDE 1.6.2): ไปที่เมนู `Sketch > Include Library > Manage Libraries` ภายใน Arduino IDE แล้วค้นหาไลบรารี GEM จากนั้นกด `Install` (หรือจะเพิ่มไฟล์ ZIP ที่ดาวน์โหลดมาก่อนหน้าแล้วผ่านเมนู `Sketch > Include Library > Add .ZIP Library` ก็ได้)

    ไม่ว่าจะเลือกวิธีไหน คุณอาจต้องรีสตาร์ท Arduino IDE หลังจากติดตั้งเสร็จ

    ไลบรารีพื้นฐานอย่าง AltSerialGraphicLCD, U8g2 และ Adafruit GFX จำเป็นต้องติดตั้งไว้ด้วยเช่นกัน อย่างไรก็ตาม คุณสามารถตั้งค่าไม่ให้รวมไลบรารีที่ไม่ได้ใช้ได้ ดูรายละเอียดเพิ่มเติมได้ในส่วน Configuration ของไฟล์ Readme

    การตั้งค่าฮาร์ดแวร์

    ชุดทดสอบสำหรับเวอร์ชัน U8g2 ของไลบรารีประกอบด้วยจอ Graphic LCD ขนาด 128x64 (พร้อม potentiometer หนึ่งตัวสำหรับปรับคอนทราสต์ของหน้าจอ) และปุ่มกด 6 ปุ่ม (สวิตช์แบบกดค้าง) สำหรับนำทางเมนู: ปุ่มควบคุมทิศทางสี่ปุ่ม, ปุ่ม Cancel หนึ่งปุ่ม และปุ่ม Ok หนึ่งปุ่ม

    แผนผังวงจร

    แต่ละสวิตช์เชื่อมต่อกับพินอินพุต 2 ถึง 7 ของ Arduino โดยใช้ตัวต้านทานแบบ pull-up (ดังนั้นสถานะ LOW หมายถึงปุ่มถูกกด) คุณอาจต้องการเพิ่มวงจรกรองสัญญาณ debounce เพื่อลดโอกาสการอ่านค่าผิดพลาดในขณะกดปุ่ม

    บอร์ดทดลอง (Breadboard)

    ต่อขาหนึ่งของสวิตช์กดค้างไว้ (momentary switch) แต่ละตัว เข้ากับพินอินพุตของ Arduino (พิน 2, 3, 4, 5, 6, 7) ที่กำหนดไว้ และต่อเข้ากับไฟ 5V ผ่านตัวต้านทานดึงขึ้น (pullup resistor) 10kOhm อย่าลืม! ส่วนขาอีกข้างของสวิตช์ให้ต่อลงกราวด์ (ground) เรียบร้อย

    ต่อหน้าจอ LCD เข้ากับบอร์ด Arduino ตามแผนภาพด้านบนเลย (โดยต่อขาหนึ่งของโพเทนชิโอมิเตอร์ 10kOhm เข้ากับขา Vo ของ LCD และขาอีกสองขาไปที่ Vee กับกราวด์)

    วิธีใช้

    เจ้าแท่นทดสอบนี้ทำงานร่วมกับ U8g2 examples ที่มากับไลบรารี GEM ได้เลย (ถ้าไม่ได้ระบุไว้เป็นอย่างอื่น) หลังจากคอมไพล์และอัปโหลดสเก็ตช์ลง Arduino แล้ว รอให้หน้าจอ LCD บูตและเมนูถูกเตรียมพร้อมและวาดลงบนหน้าจอ จากนั้นก็กดปุ่มต่างๆ เพื่อเลื่อนและโต้ตอบกับเมนูได้เลย ดูรายละเอียดเพิ่มเติมได้จากคำอธิบายของตัวอย่าง Party Hard! ด้านล่างนี้

    เกี่ยวกับตัวอย่าง Party Hard!

    ตัวอย่างนี้จะสาธิตการสร้างเมนูหนึ่งหน้า ซึ่งประกอบด้วยไอเท็มเมนูที่แก้ไขได้ 1 รายการที่เชื่อมกับตัวแปรประเภท int, อีก 1 รายการเชื่อมกับตัวแปรประเภท boolean, มีตัวเลือกแบบ option select 1 รายการ และปุ่มกด 1 ปุ่ม ซึ่งเมื่อกดแล้วจะทำให้เกิดลำดับอนิเมชันบนหน้าจอ ความล่าช้าระหว่างเฟรมถูกกำหนดโดยค่าของตัวแปร int ถ้าตั้งค่าเป็น 0 จะทำให้สามารถควบคุมเฟรมด้วยปุ่มเลื่อนเมนูได้แบบแมนนวลเลย

    มีการแนบฟังก์ชัน callback ไว้กับ option select เพื่อควบคุมตัวแปรอื่นๆ ตามตัวเลือกที่ถูกเลือก (เหมือนเป็นการสร้างตัวเลือกพรีเซ็ต)

    และยังมีฟังก์ชัน callback อีกตัวแนบมากับไอเท็มเมนูที่เชื่อมกับตัวแปร int เพื่อให้แน่ใจว่าค่าของตัวแปรจะอยู่ในช่วงที่กำหนดไว้

    ไฮไลท์เด็ด

    ในตัวอย่างนี้จะแสดงให้เห็นวิธีการ:

    • สร้างไอเท็มเมนูที่แก้ไขได้และเชื่อมกับตัวแปรประเภท int;
    • สร้างไอเท็มเมนูที่แก้ไขได้และเชื่อมกับตัวแปรประเภท boolean;
    • สร้างตัวเลือกแบบ option select ประเภท byte;
    • เพิ่ม callback ให้กับไอเท็มเมนู;
    • ทำให้ไอเท็มเมนูเป็นแบบ readonly (อ่านอย่างเดียว);
    • สร้างปุ่มที่มีการทำงานแบบขึ้นกับ context (บริบท)

    สเก็ตช์ (Sketch)

    สเก็ตช์ที่มีคำอธิบายประกอบ (รวมถึงไฟล์เพิ่มเติมที่มีสไปรต์สำหรับอนิเมชัน) มาพร้อมกับไลบรารีอยู่แล้ว และแนบไว้ที่นี่ด้วย

    รันเลย!

    หลังจากคอมไพล์และอัปโหลดสเก็ตช์ลง Arduino แล้ว รอให้หน้าจอ LCD บูตและเมนูถูกเตรียมพร้อมและวาดลงบนหน้าจอ จากนั้นก็เริ่มกดปุ่มต่างๆ เพื่อเลื่อนไปมาในเมนูได้เลย การกดปุ่ม "Ok" (ซึ่งต่อกับพิน 7) จะทำให้เข้าสู่โหมดแก้ไขของตัวเลือกพรีเซ็ต "Tempo", หรือตัวแปร "Interval", หรือเปลี่ยนสถานะของตัวเลือก "Strobe", หรือเรียกการทำงานที่เกี่ยวข้องกับปุ่มเมนู "Let's Rock!" มาจัดไปวัยรุ่น!

    การตั้งค่า "Tempo" ให้เลือกเป็นหนึ่งในตัวเลือกที่มีอยู่ จะทำให้ตัวแปรอื่นๆ อัปเดตตามไปด้วย (ทั้งค่าที่ตั้งและโหมด readonly ของตัวแปร "Interval" รวมถึงสถานะของช่องทำเครื่องหมาย "Strobe") ถ้าเลือกตัวเลือก "Custom" โหมด readonly ของ "Interval" จะถูกปิดใช้งาน ทำให้ผู้ใช้สามารถปรับค่าของมันได้โดยตรง ส่วนโหมด "Manual" จะตั้งค่า "Interval" เป็น 0 ซึ่งจะเปิดใช้งานการเลื่อนเฟรมภาพเคลื่อนไหวด้วยมือ (ดูด้านล่าง)

    การตั้งค่าตัวแปร "Interval" เป็นค่าลบ (ในโหมด "Custom" ของการเลือก "Tempo") จะทำให้มันถูกตั้งค่าเป็น 0 โดยอัตโนมัติ

    การกดปุ่ม "Let's Rock!" จะเปิดลูปภาพเคลื่อนไหว ซึ่งประกอบด้วยสไปรต์ 5 ตัวที่ถูกวาดลงบนหน้าจอต่อเนื่องกัน โดยมีดีเลย์ตามค่าของตัวแปร "Interval" (หน่วยเป็น ms) การตั้งค่า "Interval" เป็น 0 (หรือเลือกตัวเลือก "Manual" จาก "Tempo") จะเปิดใช้งานการเลื่อนเฟรมภาพเคลื่อนไหวด้วยมือโดยใช้ปุ่มกด "Left" และ "Right" (ซึ่งเชื่อมต่อกับพิน 3 และ 4 ตามลำดับ)

    การเปิดใช้งานโหมด "Strobe" จะทำให้เฟรมที่ 2 และ 4 ของภาพเคลื่อนไหวถูกวาดในโหมดสีกลับกัน (inversed mode)

    หากต้องการออกจากลูปภาพเคลื่อนไหว ให้กดปุ่ม "Cancel" (ซึ่งเชื่อมต่อกับพิน 6)

    Sketch run

    ข้อมูลเพิ่มเติม

    อยากรู้ลึก อยากรู้จริง อย่าลืมแวะไปดูที่ GitHub และ Wiki ของ GEM เพื่ออ่านเอกสารและดูตัวอย่างเพิ่มเติมนะครับ หวังว่าน้องๆ จะพบว่าคุณสมบัติของไลบรารีนี้มีประโยชน์!

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

    apps:
      - "1x GEM Library"
      - "1x U8g2 Library"
      - "1x Arduino IDE"
    author: "Spirik"
    category: "Screens & Displays"
    components:
      - "1x Solderless Breadboard Half Size"
      - "1x Single Turn Potentiometer- 10k ohms"
      - "1x Arduino UNO"
      - "6x PTS 645 Series Switch"
      - "6x Resistor 10k ohm"
      - "1x Graphic LCD 128x64 STN LED Backlight"
    description: "เป็น Arduino Library ไว้สร้างเมนูกราฟิกหลายเลเวล งานง่ายแต่หล่อ ตึงๆ เอาไปทำโปรเจคได้แบบจัดไป"
    difficulty: "Intermediate"
    documentationLinks: []
    downloadableFiles:
      - "https://github.com/Spirik/GEM/blob/master/examples/U8g2/Example-03_Party-Hard/frames.h"
      - "https://github.com/Spirik/GEM/blob/master/examples/U8g2/Example-03_Party-Hard/Example-03_Party-Hard.ino"
      - "https://projects.arduinocontent.cc/2436b254-9d98-4c12-a003-28b6bcc23691.h"
      - "https://projects.arduinocontent.cc/984c17fe-f8e6-4bb2-8709-f63b28fa0ad2.ino"
      - "https://github.com/Spirik/GEM/blob/master/examples/U8g2/Example-03_Party-Hard/frames.h"
      - "https://github.com/Spirik/GEM/blob/master/examples/U8g2/Example-03_Party-Hard/Example-03_Party-Hard.ino"
      - "https://projects.arduinocontent.cc/984c17fe-f8e6-4bb2-8709-f63b28fa0ad2.ino"
      - "https://github.com/Spirik/GEM"
      - "https://projects.arduinocontent.cc/2436b254-9d98-4c12-a003-28b6bcc23691.h"
    encryptedPayload: "U2FsdGVkX19SPvcq3qFTp9WdXPahm0x7N2hHuC3fWZCkCQjo0CQmOjULVkcOeuoruKkwRmJkbYjigSE9Aw9nt+tTGawJFR9HJ5jKhRiCuW0aMYlnQLnPtmcZpSx8UN9ZvK/e7/X7we8NzQua39lg3A=="
    heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/good-enough-menu-gem-73bb5a_cover.gif"
    lang: "en"
    likes: 3
    passwordHash: "512b870d3d71c5dd28325dae5aef732be16dbda2cebb0a068e266cd4bbb2db0e"
    price: 99
    seoDescription: "Arduino library for creating graphic multi-level menus. Easy to use and customizable for various Arduino displays and projects."
    tags:
      - "display"
      - "screen"
      - "menu"
      - "lcd"
    title: "Good Enough Menu (GEM) - เมนูเทพๆ ไว้จัดหน้าจอ Arduino"
    tools: []
    videoLinks:
      - "https://www.youtube.com/embed/3mEslt_9iKE"
    views: 9562