จอสัมผัสและ widget ตัวแรก
โมดูล 2 — จากจอสู่ฮาร์ดแวร์ · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เข้าใจว่านิ้วที่แตะกระจกเดินทางไปถึงโค้ด Python ผ่านคิวเหตุการณ์ของอีกคอร์ได้อย่างไร แล้วสร้าง widget ตัวแรกให้ถูกตำแหน่ง ถูกขนาดตัวอักษร และจำเบอร์ประจำตัวของมันไว้แยกว่าใครถูกแตะ
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- อธิบายเส้นทางสี่ทอดจากนิ้วแตะกระจกถึงโค้ด Python (ตัวตรวจจับสัมผัส → CM55 ที่รู้ว่าโดน widget ไหน → คิวเหตุการณ์ → ui.poll() บน CM33) และบอกได้ว่าต่างจากการ polling ปุ่มจริงอย่างไร รวมทั้งเกิดอะไรขึ้นถ้าโค้ดไม่เรียก ui.poll() เลย
- สร้าง ui.Label, ui.Button และ ui.Switch หลัง ui.screen() ด้วย x, y, w, h และ color ที่กำหนดเอง บอกได้ว่าเมื่อไรจึงปล่อยให้จอจัดวางเอง (x=-1) และอ่าน value= ของ Label/Button/Dropdown/Textarea ว่าเป็นขนาดฟอนต์ที่รับแค่ 14/16/20/24/28 ไม่ใช่ค่าที่วัดได้
- เก็บ .id() ของปุ่มหลายตัวไว้ใน list แล้วแปลงเหตุการณ์ที่บอกมาแค่เบอร์ handle กับชนิด ‘clicked’ กลับเป็นแถวของปุ่มด้วย .index() ตามห้าขั้นของการสร้าง widget ได้ถูกต้อง
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ชุดบทเรียน 2.4–2.6 ต่อยอดจากลูปของบทเรียน 2.1–2.3 (อ่าน → ตัดสินใจ → สั่ง → หน่วง → วนใหม่) เปลี่ยนแค่ว่าอินพุตมาจากนิ้วบนกระจก
ทีมที่ยังไม่ได้ลอง 07_button_to_broker.py ของบทเรียน 2.3 ไม่เสียอะไร เนื้อหาหลักไม่พึ่งมัน
เตรียมบันทึกการเรียนไว้จดขนาดฟอนต์ที่ลองและเบอร์ .id() ที่ได้จากการลองมือท้ายบทเรียน
- อุปกรณ์: บอร์ด Eva Kit หรือ TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE
- เรียนมาก่อน: บทเรียน 2.3 — ลงมือทำ: ไฟวิ่งกับปุ่ม แล้วส่งขึ้น broker
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”บน Eva Kit เปิดเมนู Controls ที่เคยเล่นในบทเรียน 1.1 อีกครั้ง แตะวงกลมสีแล้วสังเกตสามอย่างที่ชุดบทเรียนนี้ต้องทำให้ได้เอง: ปุ่มรู้ว่าถูกแตะ · หลอดจริงเปลี่ยนสถานะ · ตัวหนังสือบนจอเปลี่ยนตาม ทีมที่ถือ Dev Kit ไม่มีการ์ดนี้ ซึ่งยิ่งเป็นเหตุผลให้สร้างเอง ให้ดูภาพปลายทางของเฉลยในสไลด์แทน หน้าที่ดูธรรมดานี้ข้างในคือวงจรรับเหตุการณ์ที่เราจะเขียนเอง
คาถาของชุดบทเรียนนี้คือ จอไม่ใช่ของจริง จอคือรายงานของจริง และสองอย่างนี้ต้องตรงกันเสมอ เหตุผลที่ต้องมีปุ่มบนจอทั้งที่ปุ่มจริงก็สั่งไฟได้ คือเครื่องที่ส่งมอบไปแล้วคนอื่นเป็นคนใช้และเขาเห็นแค่จอ บอร์ดมีปุ่มจริงให้ Python แตะได้ปุ่มเดียว แต่เครื่องหนึ่งเครื่องมีคำสั่งมากกว่าหนึ่ง และแผงที่รายงานไม่ตรงกับของจริงคือแผงที่หลอกคนคุมเครื่อง ถึงอย่างนั้นปุ่มจริงก็ยังมีข้อดีที่จอไม่มี คือนิ้วรู้ตำแหน่งได้โดยไม่ต้องมอง
ปุ่มบนจอไม่ใช่ปุ่มบนบอร์ด ปุ่มจริงต่อสายตรงเข้าขาชิป เราไป “อ่าน” เองเมื่อไรก็ได้ ส่วนปุ่มบนจอเป็นภาพที่ CM55 วาด
จอสัมผัสไม่ได้วัดแรงกด มันวัด ความใกล้ของตัวนำ (นิ้วเพิ่มความจุไฟฟ้าให้จุดนั้น) และตัวตรวจจับอยู่ฝั่ง CM55 ทั้งหมด
ขณะที่ Python รันบน CM33 คนละคอร์ CM55 จึงจดเหตุการณ์ใส่คิวไว้แล้วรอให้เราไป “รับของที่ฝากไว้” ด้วย ui.poll()
ถ้าเราไม่ถาม คิวก็ค้าง เหตุการณ์ไม่หาย แต่ก็ไม่มีอะไรเกิดขึ้น
ห้าขั้นของ widget หนึ่งตัว (ลำดับเดียวกับ LVGL ในภาษา C): 1 สร้าง ui.Button("แดง") · 2 วางที่ x, y, w, h
(หรือ .pos(x, y)) · 3 หน้าตา text color · 4 จำเบอร์ btn.id() · 5 กรอง ev['type'] == 'clicked'
ขั้น 1–3 คือของที่เห็น ขั้น 4–5 คือของที่ตอบสนอง ขั้น 4 ต่างจาก C มากที่สุด เพราะ C ฝาก callback ให้ระบบเรียก
ส่วนเราเก็บหมายเลขประจำตัวไว้เช็กเองในลูป เริ่มหน้าใหม่ด้วย ui.screen() เสมอเพื่อล้างของเก่า x, y คือมุมซ้ายบนเป็นพิกเซล
ไม่ใส่ w, h จอเลือกขนาดพอดีข้อความ สีเป็น 0xRRGGBB และ x=-1 (ค่าเริ่มต้น) คือให้จอเรียงตามลำดับที่สร้าง
เหมาะกับการลองของเร็ว ๆ แต่แผงควบคุมจริงต้องกำหนดตำแหน่งเอง เพราะ “ปุ่มอยู่ตรงไหน” คือการออกแบบ
กับดักที่พลาดกันทุกรุ่น: บน Label, Button, Dropdown และ Textarea value= คือ ขนาดฟอนต์ รับแค่ 14 / 16 / 20 / 24 / 28
ui.Label("อุณหภูมิ", value=24) จึงแปลว่าตัวอักษรสูง 24 พิกเซล ไม่ใช่ 24 องศา ส่วน min max value ที่เป็นค่าจริงตามชื่อ
ใช้กับ Slider, Arc และ Bar เท่านั้น อยากเปลี่ยนข้อความของ Label ให้ใช้ .text("ข้อความใหม่") เสมอ
handle คือบัตรประจำตัว สิ่งที่ได้กลับมาตอนสร้างคือตัวจัดการที่ผูกกับ widget จริงบน CM55 เหตุการณ์จาก ui.poll()
ไม่ได้บอกว่า “ปุ่มแดงถูกกด” มันบอกแค่เบอร์ เช่น {'handle': 4, 'type': 'clicked'} ไม่มีชื่อ ไม่มีสี ไม่มีข้อความ
เมื่อมีหลายปุ่ม วิธีที่สะอาดที่สุดคือ on_ids = [b.id() for b in btn_on] แล้ว on_ids.index(ev['handle']) ได้เลขแถวกลับมา
ถ้าไม่เก็บ .id() ไว้ ตอนเหตุการณ์เข้ามาเราจะแยกไม่ออกเลยว่าใครส่ง
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นด้วย:
- m02-ui-to-hardware/l03-led-button-lab/examples/07_button_to_broker.py — ปุ่มกับไฟบนโต๊ะเรา ขึ้นไปอยู่บน broker ให้หน้าเว็บอ่านได้
- m02-ui-to-hardware/l03-led-button-lab/solution/s03_led_button.py — ไฟวิ่งทุกดวง + ปุ่มนับครั้งแบบกันเด้ง
- shared/web/mqtt_dashboard.html — แดชบอร์ดทั้งห้อง
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
ทีมสร้างปุ่มสามปุ่มแล้วเขียนลูปที่หลับรออย่างเดียว ไม่มี ui.poll() เลย ผู้ใช้แตะปุ่มแดงสามครั้ง จะเกิดอะไรขึ้น (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)
- ก) หลอดแดงติดเอง เพราะ CM55 รู้อยู่แล้วว่าปุ่มนี้ชื่อแดง
- ข) เหตุการณ์ถูกจดไว้ในคิวฝั่ง CM55 และรออยู่ แต่โค้ด Python ไม่ทำอะไรเลย เพราะไม่มีใครมาถาม
- ค) โปรแกรมหยุดด้วย error เพราะคิวเต็ม
- ง) CM33 อ่านตำแหน่งนิ้วจากจอเองโดยตรง จึงไม่จำเป็นต้องมี ui.poll()
เฉลย
ข — ตัวตรวจจับสัมผัสอยู่ฝั่ง CM55 ทั้งหมด ส่วน Python รันบน CM33 CM55 จึงจดเหตุการณ์ใส่คิวแล้วรอให้เรามาถามด้วย ui.poll() ถ้าไม่ถาม เหตุการณ์ไม่หาย แต่ก็ไม่มีอะไรเกิดขึ้น
-
ข้อใดอธิบายว่าจอสัมผัสของบอร์ดรู้ว่ามีนิ้วมาแตะได้ถูกต้อง (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)
- ก) วัดแรงกด ยิ่งกดแรงยิ่งรับรู้ได้ชัด
- ข) มีสวิตช์กลขนาดเล็กอยู่ใต้กระจกทุกจุด
- ค) วัดความใกล้ของตัวนำ นิ้วคนที่เข้าใกล้กระจกเพิ่มความจุไฟฟ้าให้จุดนั้น
- ง) โค้ด Python บน CM33 อ่านขาของจอทีละจุดแบบ polling
เฉลย
ค — จอสัมผัสไม่ได้วัดแรงกด มันวัดความใกล้ของตัวนำ นิ้วคนเป็นตัวนำจึงเปลี่ยนความจุไฟฟ้าที่จุดนั้นโดยไม่ต้องมีสวิตช์กล หลักการเดียวกันนี้กลับมาอีกในบทเรียน 2.7–2.9 ตอนอ่าน CapSense
-
บรรทัด
ui.Label("อุณหภูมิ", value=24)หมายความว่าอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)- ก) แสดงค่าอุณหภูมิ 24 องศาบนป้าย
- ข) ตัวอักษรบนป้ายสูง 24 พิกเซล
- ค) วางป้ายห่างจากขอบซ้าย 24 พิกเซล
- ง) ป้ายจะอัปเดตทุก 24 ms
เฉลย
ข — บน Label, Button, Dropdown และ Textarea value= คือขนาดฟอนต์ และรับแค่ 14, 16, 20, 24, 28 ส่วน value ที่เป็นค่าจริงใช้กับ Slider, Arc และ Bar เท่านั้น อยากเปลี่ยนข้อความให้ใช้ .text()
-
ข้อใดถูกต้องเกี่ยวกับการวาง widget บนจอ เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 2)
- ก) x=-1 (ค่าเริ่มต้น) ให้จอเรียงตามลำดับที่สร้าง เหมาะกับการลองของเร็ว ๆ แต่คุมตำแหน่งไม่ได้
- ข) แผงควบคุมจริงควรกำหนด x, y เอง เพราะตำแหน่งของปุ่มเป็นส่วนหนึ่งของการออกแบบ
- ค) ถ้าไม่ใส่ w, h จอเลือกขนาดพอดีข้อความให้เอง
- ง) x, y วัดจากมุมขวาล่างของจอ
- จ) ต้องสร้าง widget ใหม่ทุกครั้งที่อยากเปลี่ยนข้อความบน Label
เฉลย
ก, ข, ค — x, y คือมุมซ้ายบนเป็นพิกเซล และเปลี่ยนข้อความด้วย .text() บน widget เดิม ไม่ต้องสร้างใหม่ ส่วน x=-1 ปล่อยให้จอเรียงเอง ซึ่งไม่เหมาะกับแผงควบคุมที่ต้องออกแบบตำแหน่ง
-
สมมติ
on_ids = [3, 4, 5]เก็บเบอร์ของปุ่ม แดง เขียว น้ำเงิน ตามลำดับ แล้ว ui.poll() คืน{'handle': 4, 'type': 'clicked'}บรรทัดon_ids.index(ev['handle'])ให้ผลอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)- ก) 4 คือเบอร์ของปุ่ม
- ข) 1 คือแถวของปุ่มเขียว
- ค) “เขียว” เพราะเหตุการณ์บอกชื่อปุ่มมาด้วย
- ง) 2 คือแถวของปุ่มน้ำเงิน
เฉลย
ข — เหตุการณ์บอกแค่เบอร์ ไม่มีชื่อ ไม่มีสี .index() หาตำแหน่งของเบอร์ 4 ใน list ซึ่งอยู่ลำดับที่ 1 (นับจาก 0) คือแถวของปุ่มเขียว ถ้าไม่ได้เก็บ .id() ไว้ เราจะแยกไม่ออกว่าใครส่งมา
ลองมือ (ราว 20 นาที) เขียนใน BENTO IDE แล้วส่งขึ้นบอร์ดหรือ Emulator ที่เปิด BENTO Playground ค้างไว้
ยังไม่ต้องมี event loop บทเรียน 2.5 จะทำให้ปุ่มตอบสนอง widget ที่สร้างแล้วค้างบนจอจนกว่าจะมีใครเรียก ui.screen()
แต่ให้ปิดท้ายสคริปต์ด้วย ui.poll() หนึ่งครั้ง เพราะ CM55 ซ่อน widget ทั้งชุดไว้จนกว่า ui.poll() ครั้งแรกจะมาถึง
ถ้าไม่เรียกเลย จอจะว่างอยู่ราว 2 วินาทีก่อนของจะโผล่ (กฎข้อ 1 ของโมดูล ui ในบทเรียน 2.5)
- เริ่มด้วย
ui.screen()ตามด้วยtime.sleep_ms(200)แล้วสร้างสามบรรทัดจากสไลด์ (titlebtnsw) ตรวจว่าแต่ละตัวอยู่ตรง x, y ที่สั่ง - ลบ
x=y=ออกจากปุ่ม แล้วรันใหม่ จดว่าปุ่มไปอยู่ตรงไหนเมื่อปล่อยให้จอเรียงเอง - สร้าง Label ห้าบรรทัดด้วย
value=14, 16, 20, 24 และ 28 แล้วเลือกขนาดที่อ่านออกจากระยะยืน ลองค่าที่อยู่นอกห้าค่านี้หนึ่งครั้ง แล้วจดว่าเกิดอะไรขึ้น - เปลี่ยนข้อความของ Label หนึ่งตัวด้วย
.text()แทนการสร้างใหม่ - สร้างปุ่มสามปุ่ม แดง เขียว น้ำเงิน เก็บ
.id()ลง list แล้วlcd.printออกมา จดเบอร์ลงบันทึกการเรียน สังเกตว่าเป็นเลขล้วน ไม่มีชื่อหรือสี
บทเรียน 2.5 เอาเบอร์ที่เก็บไว้มาใช้จริง: เขียน event loop ที่เรียก ui.poll() ทุกรอบ แยกเหตุการณ์ตาม handle แล้วสั่ง gpio.led()
ให้หลอดจริงติดพร้อมกับที่จอบอกสถานะตรงกัน
บทเรียนถัดไป: บทเรียน 2.5 — event loop: แตะจอแล้วไฟจริงติด
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- รถที่ย้ายทุกปุ่มขึ้นจอสัมผัสจอเดียวได้อะไรและเสียอะไร ถ้าเป็นแผงของเครื่องจักรในโรงงาน คุณจะเก็บปุ่มจริงไว้ปุ่มไหน
- ถ้าเหตุการณ์บอกแค่เบอร์ แล้ววันหนึ่งคุณเพิ่มปุ่มใหม่แทรกกลาง list โค้ดส่วนไหนต้องระวังที่สุด
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
ทีมสร้างปุ่มสามปุ่มแล้วเขียนลูปที่หลับรออย่างเดียว ไม่มี ui.poll() เลย ผู้ใช้แตะปุ่มแดงสามครั้ง จะเกิดอะไรขึ้น (เป้าหมายข้อ 1)
- หลอดแดงติดเอง เพราะ CM55 รู้อยู่แล้วว่าปุ่มนี้ชื่อแดง
- เหตุการณ์ถูกจดไว้ในคิวฝั่ง CM55 และรออยู่ แต่โค้ด Python ไม่ทำอะไรเลย เพราะไม่มีใครมาถาม
- โปรแกรมหยุดด้วย error เพราะคิวเต็ม
- CM33 อ่านตำแหน่งนิ้วจากจอเองโดยตรง จึงไม่จำเป็นต้องมี ui.poll()
ดูเฉลย
คำตอบ: B. เหตุการณ์ถูกจดไว้ในคิวฝั่ง CM55 และรออยู่ แต่โค้ด Python ไม่ทำอะไรเลย เพราะไม่มีใครมาถาม
ตัวตรวจจับสัมผัสอยู่ฝั่ง CM55 ทั้งหมด ส่วน Python รันบน CM33 CM55 จึงจดเหตุการณ์ใส่คิวแล้วรอให้เรามาถามด้วย ui.poll() ถ้าไม่ถาม เหตุการณ์ไม่หาย แต่ก็ไม่มีอะไรเกิดขึ้น
-
ข้อใดอธิบายว่าจอสัมผัสของบอร์ดรู้ว่ามีนิ้วมาแตะได้ถูกต้อง (เป้าหมายข้อ 1)
- วัดแรงกด ยิ่งกดแรงยิ่งรับรู้ได้ชัด
- มีสวิตช์กลขนาดเล็กอยู่ใต้กระจกทุกจุด
- วัดความใกล้ของตัวนำ นิ้วคนที่เข้าใกล้กระจกเพิ่มความจุไฟฟ้าให้จุดนั้น
- โค้ด Python บน CM33 อ่านขาของจอทีละจุดแบบ polling
ดูเฉลย
คำตอบ: C. วัดความใกล้ของตัวนำ นิ้วคนที่เข้าใกล้กระจกเพิ่มความจุไฟฟ้าให้จุดนั้น
จอสัมผัสไม่ได้วัดแรงกด มันวัดความใกล้ของตัวนำ นิ้วคนเป็นตัวนำจึงเปลี่ยนความจุไฟฟ้าที่จุดนั้นโดยไม่ต้องมีสวิตช์กล หลักการเดียวกันนี้กลับมาอีกในบทเรียน 2.7–2.9 ตอนอ่าน CapSense
-
บรรทัด `ui.Label("อุณหภูมิ", value=24)` หมายความว่าอะไร (เป้าหมายข้อ 2)
- แสดงค่าอุณหภูมิ 24 องศาบนป้าย
- ตัวอักษรบนป้ายสูง 24 พิกเซล
- วางป้ายห่างจากขอบซ้าย 24 พิกเซล
- ป้ายจะอัปเดตทุก 24 ms
ดูเฉลย
คำตอบ: B. ตัวอักษรบนป้ายสูง 24 พิกเซล
บน Label, Button, Dropdown และ Textarea value= คือขนาดฟอนต์ และรับแค่ 14, 16, 20, 24, 28 ส่วน value ที่เป็นค่าจริงใช้กับ Slider, Arc และ Bar เท่านั้น อยากเปลี่ยนข้อความให้ใช้ .text()
-
ข้อใดถูกต้องเกี่ยวกับการวาง widget บนจอ เลือกทุกข้อที่ถูก (เป้าหมายข้อ 2)
- x=-1 (ค่าเริ่มต้น) ให้จอเรียงตามลำดับที่สร้าง เหมาะกับการลองของเร็ว ๆ แต่คุมตำแหน่งไม่ได้
- แผงควบคุมจริงควรกำหนด x, y เอง เพราะตำแหน่งของปุ่มเป็นส่วนหนึ่งของการออกแบบ
- ถ้าไม่ใส่ w, h จอเลือกขนาดพอดีข้อความให้เอง
- x, y วัดจากมุมขวาล่างของจอ
- ต้องสร้าง widget ใหม่ทุกครั้งที่อยากเปลี่ยนข้อความบน Label
ดูเฉลย
คำตอบ: A. x=-1 (ค่าเริ่มต้น) ให้จอเรียงตามลำดับที่สร้าง เหมาะกับการลองของเร็ว ๆ แต่คุมตำแหน่งไม่ได้ · B. แผงควบคุมจริงควรกำหนด x, y เอง เพราะตำแหน่งของปุ่มเป็นส่วนหนึ่งของการออกแบบ · C. ถ้าไม่ใส่ w, h จอเลือกขนาดพอดีข้อความให้เอง
x, y คือมุมซ้ายบนเป็นพิกเซล และเปลี่ยนข้อความด้วย .text() บน widget เดิม ไม่ต้องสร้างใหม่ ส่วน x=-1 ปล่อยให้จอเรียงเอง ซึ่งไม่เหมาะกับแผงควบคุมที่ต้องออกแบบตำแหน่ง
-
สมมติ `on_ids = [3, 4, 5]` เก็บเบอร์ของปุ่ม แดง เขียว น้ำเงิน ตามลำดับ แล้ว ui.poll() คืน `{'handle': 4, 'type': 'clicked'}` บรรทัด `on_ids.index(ev['handle'])` ให้ผลอะไร (เป้าหมายข้อ 3)
- 4 คือเบอร์ของปุ่ม
- 1 คือแถวของปุ่มเขียว
- "เขียว" เพราะเหตุการณ์บอกชื่อปุ่มมาด้วย
- 2 คือแถวของปุ่มน้ำเงิน
ดูเฉลย
คำตอบ: B. 1 คือแถวของปุ่มเขียว
เหตุการณ์บอกแค่เบอร์ ไม่มีชื่อ ไม่มีสี .index() หาตำแหน่งของเบอร์ 4 ใน list ซึ่งอยู่ลำดับที่ 1 (นับจาก 0) คือแถวของปุ่มเขียว ถ้าไม่ได้เก็บ .id() ไว้ เราจะแยกไม่ออกว่าใครส่งมา
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"จอสัมผัสและ widget ตัวแรก" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "The touch screen and your first widgets" 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/aiot-micropython/m02-ui-to-hardware/l04-touch-widgets/
บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/Advance-Innovation-Centre-AIC/embedded-systems-for-aiot-developer/blob/a80bbe88a34bcb9bb8d991f42f9252b77cdab079/session-04.html (slides 1–14)
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA