ควบคุม RGB dot matrix ผ่าน I2C
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”- ส่งคำสั่งไปยัง DFR0522 RGB matrix 8x16 ที่ address 0x10 บน bus 3.3 V
- ผสม input จาก potentiometer กับ output บน matrix และจอในงานเดียว
บอร์ดฐาน QWA309 มีอะไรให้ฝึก
หัวข้อที่มีชื่อว่า “บอร์ดฐาน QWA309 มีอะไรให้ฝึก”บอร์ดฐาน QWA309 ของ TESAIoT Dev Kit มีอุปกรณ์จริงให้ฝึก ได้แก่ ปุ่มกด potentiometer 4 ตัว CAN transceiver และ header สำหรับต่ออุปกรณ์ภายนอก บทเรียนนี้ใช้แบบฝึกของ Developer Hub ที่เขียนไว้สำหรับบอร์ดนี้โดยตรง ทั้งสามแบบฝึกในบทเรียนนี้ควบคุมจอ RGB dot-matrix ตัวเดียวกัน (DFRobot DFR0522) ผ่านไดรเวอร์ I2C ระดับต่ำร่วมกันในไฟล์ rgb_panel.c แต่ต่างกันที่วิธีสั่งงาน: กดปุ่มสั่งตรง ๆ, แอนิเมชันอัตโนมัติ, และผสมสีจาก potentiometer
DFR0522 คืออะไร และคำสั่งที่ส่งไปมีอะไรบ้าง
หัวข้อที่มีชื่อว่า “DFR0522 คืออะไร และคำสั่งที่ส่งไปมีอะไรบ้าง”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()
ใช้บัส I2C ร่วมกับจอ/ทัช ไม่ใช่ sensor I2C ของ master
หัวข้อที่มีชื่อว่า “ใช้บัส I2C ร่วมกับจอ/ทัช ไม่ใช่ sensor I2C ของ master”ทั้งสามแบบฝึกสั่งงาน 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 แทน
ผสมสี R/G/B จาก potentiometer ด้วยการแพ็กบิต (Pot → RGB Mixer)
หัวข้อที่มีชื่อว่า “ผสมสี R/G/B จาก potentiometer ด้วยการแพ็กบิต (Pot → RGB Mixer)”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 เพื่อบังคับให้เขียนครั้งแรกเสมอ
แอนิเมชันด้วย state machine เฟรมนับถอยหลัง (RGB Matrix FX)
หัวข้อที่มีชื่อว่า “แอนิเมชันด้วย state machine เฟรมนับถอยหลัง (RGB Matrix FX)”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ก่อนส่งเสมอ — สีหรือพิกัดที่อยู่นอกช่วงจะถูกดักตั้งแต่ในไดรเวอร์ ไม่ถึงขั้นส่งข้อมูลผิดออกไปทางบัส
build และ flash
หัวข้อที่มีชื่อว่า “build และ flash”# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/make buildmake program # flash ผ่าน KitProg3- ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
- แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
- ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”- อุปกรณ์สองตัวบน I2C bus เดียวกันแยกกันด้วยอะไร
- ถ้า matrix ไม่ตอบ ต้องตรวจอะไรก่อน (สาย ไฟ address)
คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- แบบฝึกทั้งหมดของ TESAIoT Dev Kit · commit
e5c7722 - โค้ดเป็นของ Developer Hub และอ้างอิงด้วยลิงก์ ไม่ได้คัดลอกเข้าคลังนี้
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
DFR0522 ใช้ bus I2C เดียวกับ touch controller ของจอ อุปกรณ์สองตัวบน bus เดียวกันแยกกันด้วยอะไร (เป้าหมายข้อ 1)
- สาย chip-select แยกของแต่ละตัว
- ความเร็ว clock ที่ต่างกัน
- address 7 บิตที่ส่งหลัง START เฉพาะอุปกรณ์ที่ address ตรงกัน (0x10 สำหรับ matrix) จึงตอบ ACK และรับข้อมูล
- ลำดับการเสียบสาย
ดูเฉลย
คำตอบ: 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 ไม่ซ้ำกัน
-
กดปุ่ม Check 0x10 แล้วได้สถานะ “ADDRESS NACK” ควรตรวจอะไรก่อน (เป้าหมายข้อ 1)
- สาย SDA/SCL ไฟเลี้ยงของ matrix และ address ของโมดูลว่าเป็น 0x10 จริง เพราะไม่มีอุปกรณ์ใดตอบรับ address นี้เลย
- ค่า function byte ของคำสั่ง fill
- สีที่เลือกอยู่นอกช่วง
- ขนาด tx buffer 51 ไบต์
ดูเฉลย
คำตอบ: A. สาย SDA/SCL ไฟเลี้ยงของ matrix และ address ของโมดูลว่าเป็น 0x10 จริง เพราะไม่มีอุปกรณ์ใดตอบรับ address นี้เลย
check_panel_device() ส่งแค่ START พร้อม address แล้ว STOP ถ้าไม่มี ACK แปลว่าปัญหาอยู่ก่อนถึงเนื้อหาคำสั่ง คือสาย ไฟ หรือ address ข้อผิดพลาดของรูปแบบคำสั่งจะเกิดหลังอุปกรณ์ตอบ address แล้ว และสีที่ผิดช่วงถูกดักเป็น BAD PARAMETER ก่อนส่งด้วยซ้ำ
-
ทำไม DFR0522 จึงใช้ bus I2C ของจอ/ทัช (DISPLAY_I2C_CONTROLLER_HW) แทน sensor I2C bus ของ master (เป้าหมายข้อ 1)
- เพราะ sensor bus เร็วเกินไปสำหรับ matrix
- เพราะ sensor bus อยู่บน domain 1.8 V ส่วน matrix อยู่บน bus 3.3 V ร่วมกับจอ/ทัช ซึ่ง framework init ไว้แล้ว จึงใช้ context disp_touch_i2c_controller_context ได้เลย
- เพราะ sensor bus เป็น I3C ใช้กับอุปกรณ์ I2C ไม่ได้
- เพราะ 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 แยกกัน)
-
ใน Pot → RGB Mixer ถ้า pot ช่อง R = 70 %, G = 30 %, B = 60 % แผง matrix จะเป็นสีใด (เป้าหมายข้อ 2)
- YELLOW
- CYAN
- WHITE
- 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 แบบเดียวกัน
-
ทำไม mixer เรียก rgb_panel_fill() เฉพาะเมื่อสีเปลี่ยน ทั้งที่อ่าน pot ทุก 120 ms (เป้าหมายข้อ 2)
- แต่ละคำสั่งส่ง frame 51 ไบต์บน bus ที่ใช้ร่วมกับทัช และการเขียนบล็อก LVGL timer ระหว่างส่ง การเขียนเฉพาะตอนเปลี่ยนจึงลดภาระ bus และไม่ทำให้แผงกระพริบ
- เพราะ DFR0522 รับคำสั่งได้วินาทีละครั้ง
- เพราะ ADC ต้องพักระหว่างการเขียน I2C
- เพราะ 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 Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA