ข้ามไปยังเนื้อหา

ควบคุม RGB dot matrix ผ่าน I2C

  1. ส่งคำสั่งไปยัง DFR0522 RGB matrix 8x16 ที่ address 0x10 บน bus 3.3 V
  2. ผสม input จาก potentiometer กับ output บน matrix และจอในงานเดียว

บอร์ดฐาน QWA309 ของ TESAIoT Dev Kit มีอุปกรณ์จริงให้ฝึก ได้แก่ ปุ่มกด potentiometer 4 ตัว CAN transceiver และ header สำหรับต่ออุปกรณ์ภายนอก บทเรียนนี้ใช้แบบฝึกของ Developer Hub ที่เขียนไว้สำหรับบอร์ดนี้โดยตรง ทั้งสามแบบฝึกในบทเรียนนี้ควบคุมจอ RGB dot-matrix ตัวเดียวกัน (DFRobot DFR0522) ผ่านไดรเวอร์ I2C ระดับต่ำร่วมกันในไฟล์ rgb_panel.c แต่ต่างกันที่วิธีสั่งงาน: กดปุ่มสั่งตรง ๆ, แอนิเมชันอัตโนมัติ, และผสมสีจาก potentiometer

DFR0522 เป็นจอ RGB dot-matrix ขนาด 16×8 พิกเซล (RGB_PANEL_WIDTH = 16, RGB_PANEL_HEIGHT = 8) สั่งงานผ่าน I2C ที่ address 0x10 (RGB_PANEL_I2C_ADDRESS) รองรับ 8 สี (RGB_PANEL_COLOR_OFF ถึง RGB_PANEL_COLOR_WHITE ค่า 0–7) คำสั่งแต่ละครั้งประกอบเป็นเฟรม: byte แรกเป็น command register คงที่ 0x02 ตามด้วย function byte ที่บอกว่าจะ clear (0x01), fill ทั้งแผง (0x09) หรือจุดพิกเซลเดียว (0x08) แล้วตามด้วยค่าสี พิกัด x และ y โดยเฟรมทั้งหมดถูก pad ให้มีขนาดคงที่ RGB_PANEL_TX_SIZE = 51 ไบต์ (payload 50 ไบต์ + 1 ไบต์ command register) ไม่ว่าจะสั่งอะไรก็ส่งความยาวเท่ากันทุกครั้ง

เขียนไดรเวอร์ I2C ระดับไบต์เอง ไม่ผ่าน wrapper สำเร็จรูป

หัวข้อที่มีชื่อว่า “เขียนไดรเวอร์ I2C ระดับไบต์เอง ไม่ผ่าน wrapper สำเร็จรูป”

rgb_panel_write() ใน rgb_panel.c ควบคุม I2C ด้วย PDL ระดับต่ำสุดสามฟังก์ชัน: Cy_SCB_I2C_MasterSendStart() ส่ง address พร้อมโหมดเขียน ตามด้วยลูป Cy_SCB_I2C_MasterWriteByte() ส่งทีละไบต์จนครบ RGB_PANEL_TX_SIZE แล้วปิดท้ายด้วย Cy_SCB_I2C_MasterSendStop() เสมอ ไม่ว่าการเขียนไบต์จะสำเร็จหรือไม่ (เพื่อคืนบัสให้อยู่ในสถานะปกติ ไม่ค้าง) แต่ละไบต์มี timeout RGB_PANEL_BYTE_TIMEOUT_MS = 5 ms ทั้งสามแบบฝึกเรียกใช้ไดรเวอร์ตัวเดียวกันนี้ผ่านฟังก์ชันสาธารณะสามตัวคือ rgb_panel_clear(), rgb_panel_fill() และ rgb_panel_pixel()

ทั้งสามแบบฝึกสั่งงาน DFR0522 ผ่าน DISPLAY_I2C_CONTROLLER_HW และ context disp_touch_i2c_controller_context ซึ่งเป็น SCB I2C ตัวเดียวกับที่ใช้กับจอแสดงผลและทัชสกรีน ไม่ใช่ sensor I2C ที่ master template เปิดไว้ให้ (ดูบทเรียน 3.1) เหตุผลคือ sensor I2C อยู่บน 1.8 V domain ขณะที่ DFR0522 อยู่บน bus 3.3 V ร่วมกับจอ/ทัช การต่อผิด domain แรงดันอาจสื่อสารไม่ได้หรือทำให้ขาเสียหาย และเพราะบัสนี้ framework init ไว้ให้แล้วตั้งแต่ก่อน example_main() จึงเรียกใช้ context ที่มีอยู่แล้วได้ทันทีโดยไม่ต้อง init ซ้ำ (การ init ซ้ำอาจทำให้ทัชสกรีนเสียการทำงาน)

ปุ่ม “Check 0x10” ใน DFR0522 RGB Dot Matrix เรียก check_panel_device() ซึ่งส่งแค่ START พร้อม address แล้ว STOP ทันที ไม่ส่งข้อมูลใด ๆ เป็นการทดสอบว่ามีอุปกรณ์ตอบ ACK ที่ address นั้นหรือไม่ ผลลัพธ์เป็นค่า enum cy_en_scb_i2c_status_t ที่ถูกแปลเป็นข้อความอ่านง่ายด้วย i2c_status_to_text() เช่น “ADDRESS NACK” (ไม่มีอุปกรณ์ตอบเลย), “DATA NACK” (มีอุปกรณ์ตอบ address แต่ปฏิเสธข้อมูล), “TIMEOUT” หรือ “ARBITRATION LOST” การแยกแยะแบบนี้สำคัญ เพราะ “ADDRESS NACK” ชี้ไปที่ปัญหาสาย ไฟเลี้ยง หรือ address ผิด ในขณะที่ error หลัง address ตอบแล้วชี้ไปที่ปัญหาระดับคำสั่งหรือ timing แทน

Pot → RGB Mixer อ่าน potentiometer 3 ตัวแรก (channel index 0–2 บน P15.4–P15.6) ทุก MIX_REFRESH_MS = 120 ms แต่ละช่องถ้าค่าดิบ ≥ MIX_THRESHOLD = 2048 (ประมาณ 50%) จะเปิดบิตของตัวเอง: bits |= (1U << i) โดย i = 0 คือ R, i = 1 คือ G, i = 2 คือ B บิตทั้งสามรวมกันแล้ว cast ตรงเป็น rgb_panel_color_t ได้เลย เพราะ enum สีของ DFR0522 แพ็กบิตในลำดับเดียวกันพอดี (เช่น R+B เปิดพร้อมกัน = บิต 0b101 = 5 = RGB_PANEL_COLOR_PURPLE) โค้ดเขียนแผงใหม่เฉพาะเมื่อสีเปลี่ยนจากรอบก่อน (if (color != s_last_color)) เพื่อไม่ให้ส่งเฟรม 51 ไบต์ซ้ำ ๆ บนบัสที่ใช้ร่วมกับทัชทุก 120 ms โดยไม่จำเป็น s_last_color เริ่มต้นที่ RGB_PANEL_COLOR_WHITE เพื่อบังคับให้เขียนครั้งแรกเสมอ

RGB Matrix FX วนเอฟเฟกต์ 3 แบบอัตโนมัติด้วย lv_timer ที่ FX_PERIOD_MS = 140 ms ตัวแปร s_frame นับตั้งแต่ 0 ถึง FX_FRAMES_PER_EFFECT - 1 (24 เฟรม) ต่อหนึ่งเอฟเฟกต์ แล้วสลับ s_effect ไปตัวถัดไปด้วยมอดุโล 3 fx_step() เลือกการทำงานตาม s_effect: เอฟเฟกต์ 0 (Colour Cycle) เรียก rgb_panel_fill() ทั้งแผงด้วยสีจากอาร์เรย์ s_cycle[7] วนตาม s_frame % 7, เอฟเฟกต์ 1 (Pixel Sweep) เคลียร์แผงแล้วจุดพิกเซลเดียวที่ตำแหน่ง x = (s_frame * 2) % FX_COLS, y = (s_frame / 2) % FX_ROWS, เอฟเฟกต์ 2 (Row Wipe) เติมสีทั้งแผงที่เปลี่ยนช้าลง (s_frame / 4) ทุกเอฟเฟกต์เรียกไดรเวอร์ rgb_panel ตัวเดียวกับอีกสองแบบฝึก

แบบฝึกชุด QWA309 ของ Developer Hub (อ้างอิงที่ commit e5c7722) รันบน TESAIoT Dev Kit เท่านั้น เพราะใช้อุปกรณ์บนบอร์ดฐาน

  • QWA309 — DFR0522 RGB Dot Matrix — ควบคุม DFRobot DFR0522 RGB matrix 8x16 (I2C 0x10) บน bus 3.3V ร่วมกับ display แสดง clear/fill/pixel/pattern ผ่าน LVGL UI README · โค้ด · Developer Hub
  • QWA309 — RGB Matrix FX — เอฟเฟกต์แอนิเมชันบน DFR0522 8x16 (color cycle / pixel sweep / row wipe) auto-cycle + สถานะบน LCD README · โค้ด · Developer Hub
  • QWA309 — Pot → RGB Mixer — 3 potentiometers เป็น R/G/B channel (>50% = เปิดสีนั้น) ผสมเป็น 1 ใน 8 สีของ DFR0522 matrix + แสดงบน LCD — รวม SAR pots + RGB I2C README · โค้ด · Developer Hub

โค้ดตัวอย่างด้านล่างคัดลอกจากไฟล์จริงที่ commit เดียวกัน (Apache-2.0, tesaiot/developer-hub)

rgb_panel.c — ไดรเวอร์ I2C ระดับไบต์ที่ทั้งสามแบบฝึกเรียกใช้ร่วมกัน:

status = Cy_SCB_I2C_MasterSendStart(base,
RGB_PANEL_I2C_ADDRESS,
CY_SCB_I2C_WRITE_XFER,
RGB_PANEL_BYTE_TIMEOUT_MS,
context);
if (status == CY_SCB_I2C_SUCCESS)
{
for (uint32_t i = 0U; i < RGB_PANEL_TX_SIZE; i++)
{
status = Cy_SCB_I2C_MasterWriteByte(base,
tx_buffer[i],
RGB_PANEL_BYTE_TIMEOUT_MS,
context);
if (status != CY_SCB_I2C_SUCCESS) { break; }
}
}
stop_status = Cy_SCB_I2C_MasterSendStop(base,
RGB_PANEL_BYTE_TIMEOUT_MS,
context);

rgb_matrix_ui.c — ตรวจว่ามีอุปกรณ์ตอบ ACK ที่ address 0x10 หรือไม่ ก่อนส่งคำสั่งจริง:

static cy_en_scb_i2c_status_t check_panel_device(void)
{
cy_en_scb_i2c_status_t status;
status = Cy_SCB_I2C_MasterSendStart(DISPLAY_I2C_CONTROLLER_HW,
RGB_PANEL_I2C_ADDRESS,
CY_SCB_I2C_WRITE_XFER,
DEVICE_CHECK_TIMEOUT_MS,
&disp_touch_i2c_controller_context);
(void)Cy_SCB_I2C_MasterSendStop(DISPLAY_I2C_CONTROLLER_HW,
DEVICE_CHECK_TIMEOUT_MS,
&disp_touch_i2c_controller_context);
return status;
}

pot_rgb_mixer_ui.c — แพ็กบิต R/G/B จาก threshold แล้วเขียนแผงเฉพาะตอนสีเปลี่ยน:

uint8_t bits = 0U;
for (uint8_t i = 0U; i < 3U; i++) {
uint16_t raw = mix_read(s_ch[i].ch);
/* ... update bar/label ... */
if (raw >= MIX_THRESHOLD) { bits |= (uint8_t)(1U << i); }
}
/* bits: b0=R b1=G b2=B -> DFR0522 colour enum is the same RGB packing. */
rgb_panel_color_t color = (rgb_panel_color_t)bits;
if (color != s_last_color) {
s_last_color = color;
(void)rgb_panel_fill(DISPLAY_I2C_CONTROLLER_HW,
&disp_touch_i2c_controller_context, color);
}

rgb_fx_ui.c — เลือกเอฟเฟกต์ตาม s_effect แล้ววาดหนึ่งเฟรม:

switch (s_effect) {
case 0U: /* colour cycle — one fill per frame */
(void)rgb_panel_fill(FX_HW, FX_CTX, s_cycle[s_frame % 7U]);
break;
case 1U: { /* pixel sweep — clear then light one pixel */
(void)rgb_panel_clear(FX_HW, FX_CTX);
uint8_t x = (uint8_t)((s_frame * 2U) % FX_COLS);
uint8_t y = (uint8_t)((s_frame / 2U) % FX_ROWS);
(void)rgb_panel_pixel(FX_HW, FX_CTX, x, y, s_cycle[s_frame % 7U]);
break;
}
  • ต่อ DFR0522 เข้ากับ sensor I2C ของ master แทนบัส 3.3 V ของจอ/ทัช — sensor I2C อยู่บน 1.8 V domain ต่อผิดแรงดันอาจสื่อสารไม่ได้หรือทำให้ขาเสียหาย ต้องใช้ DISPLAY_I2C_CONTROLLER_HW เสมอ
  • สรุปว่า “ADDRESS NACK” กับ error หลังจากนั้นคือปัญหาเดียวกัน — ADDRESS NACK แปลว่าไม่มีอุปกรณ์ตอบเลย (ตรวจสาย ไฟ address) ส่วน error ที่เกิดหลัง address ตอบแล้วเป็นปัญหาระดับคำสั่งหรือ timing คนละสาเหตุกัน
  • เขียนแผงทุกรอบโพลโดยไม่เช็กว่าสีเปลี่ยนหรือไม่ — Pot → RGB Mixer เขียนเฉพาะตอนสีเปลี่ยนเพื่อลดภาระบัสที่ใช้ร่วมกับทัช ถ้าสีแรกที่คำนวณได้ตรงกับค่าเริ่มต้น s_last_color (WHITE) พอดี แผงจะยังไม่ถูกเขียนจนกว่าสีจะเปลี่ยนจริง
  • ลืมว่า rgb_panel_fill()/rgb_panel_pixel() ตรวจขอบเขตพารามิเตอร์และคืน CY_SCB_I2C_BAD_PARAM ก่อนส่งเสมอ — สีหรือพิกัดที่อยู่นอกช่วงจะถูกดักตั้งแต่ในไดรเวอร์ ไม่ถึงขั้นส่งข้อมูลผิดออกไปทางบัส
Terminal window
# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)
# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/
# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/
make build
make program # flash ผ่าน KitProg3
  1. ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
  2. แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
  3. ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
  • อุปกรณ์สองตัวบน I2C bus เดียวกันแยกกันด้วยอะไร
  • ถ้า matrix ไม่ตอบ ต้องตรวจอะไรก่อน (สาย ไฟ address)

คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. DFR0522 ใช้ bus I2C เดียวกับ touch controller ของจอ อุปกรณ์สองตัวบน bus เดียวกันแยกกันด้วยอะไร (เป้าหมายข้อ 1)

    1. สาย chip-select แยกของแต่ละตัว
    2. ความเร็ว clock ที่ต่างกัน
    3. address 7 บิตที่ส่งหลัง START เฉพาะอุปกรณ์ที่ address ตรงกัน (0x10 สำหรับ matrix) จึงตอบ ACK และรับข้อมูล
    4. ลำดับการเสียบสาย
    ดูเฉลย

    คำตอบ: C. address 7 บิตที่ส่งหลัง START เฉพาะอุปกรณ์ที่ address ตรงกัน (0x10 สำหรับ matrix) จึงตอบ ACK และรับข้อมูล

    rgb_panel_write() เรียก Cy_SCB_I2C_MasterSendStart() ด้วย RGB_PANEL_I2C_ADDRESS (0x10) อุปกรณ์อื่นบน bus เห็นว่า address ไม่ใช่ของตนจึงไม่ตอบ สาย chip-select เป็นวิธีของ SPI ไม่ใช่ I2C และอุปกรณ์บน bus เดียวกันต้องมี address ไม่ซ้ำกัน

  2. กดปุ่ม Check 0x10 แล้วได้สถานะ “ADDRESS NACK” ควรตรวจอะไรก่อน (เป้าหมายข้อ 1)

    1. สาย SDA/SCL ไฟเลี้ยงของ matrix และ address ของโมดูลว่าเป็น 0x10 จริง เพราะไม่มีอุปกรณ์ใดตอบรับ address นี้เลย
    2. ค่า function byte ของคำสั่ง fill
    3. สีที่เลือกอยู่นอกช่วง
    4. ขนาด tx buffer 51 ไบต์
    ดูเฉลย

    คำตอบ: A. สาย SDA/SCL ไฟเลี้ยงของ matrix และ address ของโมดูลว่าเป็น 0x10 จริง เพราะไม่มีอุปกรณ์ใดตอบรับ address นี้เลย

    check_panel_device() ส่งแค่ START พร้อม address แล้ว STOP ถ้าไม่มี ACK แปลว่าปัญหาอยู่ก่อนถึงเนื้อหาคำสั่ง คือสาย ไฟ หรือ address ข้อผิดพลาดของรูปแบบคำสั่งจะเกิดหลังอุปกรณ์ตอบ address แล้ว และสีที่ผิดช่วงถูกดักเป็น BAD PARAMETER ก่อนส่งด้วยซ้ำ

  3. ทำไม DFR0522 จึงใช้ bus I2C ของจอ/ทัช (DISPLAY_I2C_CONTROLLER_HW) แทน sensor I2C bus ของ master (เป้าหมายข้อ 1)

    1. เพราะ sensor bus เร็วเกินไปสำหรับ matrix
    2. เพราะ sensor bus อยู่บน domain 1.8 V ส่วน matrix อยู่บน bus 3.3 V ร่วมกับจอ/ทัช ซึ่ง framework init ไว้แล้ว จึงใช้ context disp_touch_i2c_controller_context ได้เลย
    3. เพราะ sensor bus เป็น I3C ใช้กับอุปกรณ์ I2C ไม่ได้
    4. เพราะ matrix ต้องใช้ interrupt ของจอ
    ดูเฉลย

    คำตอบ: B. เพราะ sensor bus อยู่บน domain 1.8 V ส่วน matrix อยู่บน bus 3.3 V ร่วมกับจอ/ทัช ซึ่ง framework init ไว้แล้ว จึงใช้ context disp_touch_i2c_controller_context ได้เลย

    main.c ของ master ระบุว่า sensor I2C (SCB0) อยู่บน 1.8 V domain ส่วน README ของตัวอย่างบอกว่า matrix อยู่บน bus 3.3 V ที่ใช้ร่วมกับจอ การต่ออุปกรณ์ผิดระดับแรงดันอาจสื่อสารไม่ได้หรือทำให้ขาเสียหาย และการใช้ SCB ที่ถูก init แล้วร่วมกันต้องไม่ init ซ้ำจนทัชเสีย (master มีทั้ง sensor I2C และ I3C แยกกัน)

  4. ใน Pot → RGB Mixer ถ้า pot ช่อง R = 70 %, G = 30 %, B = 60 % แผง matrix จะเป็นสีใด (เป้าหมายข้อ 2)

    1. YELLOW
    2. CYAN
    3. WHITE
    4. PURPLE
    ดูเฉลย

    คำตอบ: D. PURPLE

    แต่ละช่องที่ raw ≥ MIX_THRESHOLD (2048 ≈ 50 %) เปิดบิตของตัวเอง b0 = R, b1 = G, b2 = B ได้ bits = 1 + 4 = 5 ซึ่งตรงกับ RGB_PANEL_COLOR_PURPLE เพราะ enum สีของ DFR0522 ใช้การแพ็กบิต RGB แบบเดียวกัน

  5. ทำไม mixer เรียก rgb_panel_fill() เฉพาะเมื่อสีเปลี่ยน ทั้งที่อ่าน pot ทุก 120 ms (เป้าหมายข้อ 2)

    1. แต่ละคำสั่งส่ง frame 51 ไบต์บน bus ที่ใช้ร่วมกับทัช และการเขียนบล็อก LVGL timer ระหว่างส่ง การเขียนเฉพาะตอนเปลี่ยนจึงลดภาระ bus และไม่ทำให้แผงกระพริบ
    2. เพราะ DFR0522 รับคำสั่งได้วินาทีละครั้ง
    3. เพราะ ADC ต้องพักระหว่างการเขียน I2C
    4. เพราะ LVGL ห้ามเรียก I2C ใน timer
    ดูเฉลย

    คำตอบ: A. แต่ละคำสั่งส่ง frame 51 ไบต์บน bus ที่ใช้ร่วมกับทัช และการเขียนบล็อก LVGL timer ระหว่างส่ง การเขียนเฉพาะตอนเปลี่ยนจึงลดภาระ bus และไม่ทำให้แผงกระพริบ

    rgb_panel_write() ส่ง RGB_PANEL_TX_SIZE = 51 ไบต์ทุกคำสั่ง โดยเขียนทีละไบต์แบบรอผล ซึ่งทำงานใน mix_timer_cb() ของ LVGL การเทียบกับ s_last_color จึงประหยัดทั้งเวลาและ bus ข้อสังเกต: s_last_color เริ่มที่ WHITE เพื่อบังคับเขียนครั้งแรก แต่ถ้าสีแรกเป็น WHITE พอดีจะยังไม่ถูกเขียนจนกว่าสีเปลี่ยน

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"ควบคุม RGB dot matrix ผ่าน I2C" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Driving an RGB dot matrix over I2C" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/tesaiot-firmware-stack/m04-qwa309-hardware/l03-rgb-matrix-i2c/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/developer-hub/blob/e5c772252e7d20f715463e0d27df9ece4e569c38/prac_qwa309_rgb_matrix · Code stays in the Developer Hub and is linked at pinned commits, never copied: the episodes, practice codes and main-branch examples are Apache-2.0; the master template and the OPTIGA client carry Infineon/Cypress EULAs.

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA