import dsp
ema = dsp.EMA(alpha=0.2) # keyword-only — dsp.EMA(0.2) ขึ้น TypeError
smooth = ema.update(pct) # ป้อนค่าใหม่ ได้ค่ากรองแล้ว
ema.value() # ขอค่าล่าสุดซ้ำ ไม่ป้อนอะไรเข้าไป
ema.reset() # ล้างความจำ รอบหน้าเริ่มนับหนึ่งใหม่
dsp.EMA() # ไม่ใส่ alpha ได้ ค่าตั้งต้นคือ 0.1 ไม่ใช่ 0.5
ค่าใหม่มีน้ำหนัก ที่เหลือคือค่าเดิมทั้งก้อน อดีตไม่ได้หายไปไหน แต่ จางลงเรื่อย ๆ ทุกรอบ
คิดให้ดูจริง ๆ ค่าเดิม ค่าใหม่เข้ามา ที่ → ขยับแค่ 0.20 ทั้งที่อินพุตกระโดดไป 1.00 นั่นคือความนิ่งที่เราซื้อมาด้วยความหน่วง
ตัวอย่างแรกไม่ได้ถูกคูณ ค่าที่ป้อนเข้าไปครั้งแรกถูกใช้เป็นค่าตั้งต้นตรง ๆ ไม่งั้นทุกฟิลเตอร์จะเริ่มจากศูนย์แล้วต้องไต่ขึ้นมาก่อนหลายวินาที ทั้งที่ค่าจริงอยู่ตรงนั้นตั้งแต่แรก · .value() กับ .reset() มีเหมือนกันทุกตัวในตระกูลนี้
| alpha | ผลที่เห็นบนจอ | เหมาะกับ |
|---|---|---|
| 0.05 | นิ่งมาก แต่ตามมือช้าจนรู้สึกหน่วง | ค่าที่เปลี่ยนช้า เช่น อุณหภูมิ |
| 0.2 | นิ่งพอควรและยังตามทัน (ค่าที่เราใช้วันนี้) | ลูกบิด เกจทั่วไป |
| 0.8 | เกือบเท่าค่าดิบ กรองได้นิดเดียว | สัญญาณที่ต้องการความไวสูง |

06_ema_time_constant.py — ตารางข้างบนบอกว่า alpha ให้ "ความรู้สึก" แบบไหน ไฟล์นี้เปลี่ยนความรู้สึกนั้นให้เป็นวินาที · ภาพจับที่ขั้นแรกซึ่ง alpha = 1.00 คือไม่กรองเลย เส้นแดงจึงทับเส้นฟ้าสนิท และตัวเลขที่ตามมาคือ tau = 0 ms กับ "ถึง 63.2% ที่ตัวอย่างที่ 1" — ไล่กดเดินหน้าแล้ว alpha จะลดลง ทั้งสองค่านั้นจะโตขึ้นให้เห็นเป็นตัวเลข ไม่ใช่ความรู้สึก · ภาพหน้าจอจริงจากบอร์ด Eva Kit บันทึกโดยผู้สอนEMA ใช้หน่วยความจำแค่ตัวเลขเดียว จึงเป็นฟิลเตอร์ที่ระบบฝังตัวเล็ก ๆ เลือกใช้บ่อยที่สุด
med = dsp.Median(window=5) # window เป็น keyword-only เช่นเดียวกัน
clean = med.update(raw) # เก็บ 5 ค่าล่าสุด เรียงลำดับ แล้วคืนค่าตรงกลาง
dsp.Median(window=4) # ขอ 4 ได้ 5 — มันบังคับเป็นเลขคี่ให้เงียบ ๆ
dsp.Median(window=99) # ขอ 99 ได้ 15 — เพดานคือ 15 พื้นคือ 3
หนีบค่าเงียบ ๆ ไม่มี error เพราะบัฟเฟอร์ข้างในเป็นอาเรย์ขนาดคงที่ในภาษา C · ต้องเป็นเลขคี่เพราะ "ตัวกลาง" ของจำนวนคู่ไม่มีตัวเดียว · พิมพ์ print(med) แล้วมันบอกค่าที่ได้จริงมาให้ ไม่ต้องเดา

window=5 ในบรรทัดโค้ดบนสุดของสไลด์นี้คือความกว้างของกรอบที่กำลังเลื่อนอยู่นี้ และเหตุผลที่มันหน่วง ก็เพราะคำตอบของแต่ละรอบต้องรอให้กรอบเต็มก่อนไทย: เรียงค่าล่าสุด N ตัวแล้วหยิบตัวกลาง — ค่าที่กระโดดโดดเดี่ยวถูกดันไปอยู่ริมสุด ไม่มีใครหยิบมันขึ้นมา
คิดให้ดูจริง ๆ จากชุดตัวเลขในสไลด์ก่อน Median คืน 42 เหมือน spike ไม่เคยเกิด · ส่วน EMA() ที่ค่าเดิม 42 เจอ 95 ได้ คือกระเด็นตาม · ที่ลูป 200 ms หน้าต่าง 5 ค่า = 1.0 วินาที หน่วงราว 0.4 วินาที
dsp.EMA |
dsp.Median |
|
|---|---|---|
| เก่งเรื่อง | สั่นเล็ก ๆ ต่อเนื่อง | spike กระโดดเดี่ยว |
| วิธีคิด | ถ่วงน้ำหนักเก่า/ใหม่ | เรียงแล้วเลือกตัวกลาง |
| spike เดี่ยว | ถูกเฉลี่ยเข้าไปบางส่วน | หายไปทั้งก้อน |
| ความหน่วง | จางลงต่อเนื่อง | ราวครึ่งหนึ่งของ window |
| หน่วยความจำ | 1 ค่า | เท่ากับ window |
ใช้ร่วมกันได้: Median เก็บกวาด spike ก่อน แล้วส่งต่อให้ EMA · เลือกฟิลเตอร์จาก หน้าตาของ noise ไม่ใช่จากชื่อ
dsp ทั้งโมดูล (1/3) — 8 คลาส 8 ฟังก์ชัน ใช้ได้ครบทุกตัวทั้งสองบอร์ดบทเรียน 1.1–1.3 เราเรียก dsp.EMA dsp.Median และ dsp.tilt ไปแล้วอย่างละครั้ง ที่ 13_raw_and_filtered.py — สามชื่อจากสิบหก นี่คืออีกสิบสามชื่อที่เหลือ
คลาส — สร้างครั้งเดียวนอกลูป เพราะทุกตัวมีความจำ ทุกตัวมี .update() และ .reset()
| คลาส | อาร์กิวเมนต์ (keyword-only) | ค่าตั้งต้น | มีเมธอดอะไรอีก |
|---|---|---|---|
EMA |
alpha= |
0.1 | .value() |
SMA |
window= (หนีบ 2-64) |
10 | .value() |
LPF |
cutoff= fs= |
5.0 Hz, 100 Hz | .value() |
HPF |
cutoff= fs= |
0.5 Hz, 100 Hz | .value() |
Median |
window= (หนีบ 3-15 บังคับคี่) |
5 | .value() |
Kalman1D |
q= r= |
0.01, 0.1 | .value() |
Madgwick |
beta= fs= |
0.1, 100 Hz | .quaternion() — ไม่มี .value() |
Pedometer |
threshold= min_interval= |
1.5, 300 ms | ไม่มี .value() |
dsp ทั้งโมดูล (2/3) — 8 ฟังก์ชัน ไม่มีความจำ เรียกตรงได้เลย| ฟังก์ชัน | รับ | คืน | บทเรียนไหน |
|---|---|---|---|
tilt(ax,ay,az) |
ความเร่ง 3 แกน | (roll, pitch) องศา |
บทเรียน 3.1–3.3 |
compass(mx,my,mz) |
สนามแม่เหล็ก 3 แกน | ทิศ 0-360 องศา | บทเรียน 3.1–3.3 |
altitude(hpa, sea=1013.25) |
ความดัน hPa (อาร์กิวเมนต์เรียงตำแหน่ง ไม่ใช่ keyword) | ความสูงเป็นเมตร | อ้างอิง |
dew_point(t, rh) |
องศา C, %RH | จุดน้ำค้าง องศา C | อ้างอิง |
heat_index(t, rh) |
องศา C, %RH | อุณหภูมิที่รู้สึก องศา C | อ้างอิง |
comfort_zone(t, rh) |
องศา C, %RH | สตริง: cold hot dry humid comfortable acceptable |
อ้างอิง |
fft_mag(ชุด, n=256, window=True, demean=True) |
list หรือ buffer int16 ทั้งชุด | ขนาดสเปกตรัม n/2 ค่า สเกล 2/N | บทเรียน 3.4–3.6 |
s16(บัฟเฟอร์, step=1) |
buffer int16 LE (เช่น mic.raw()) |
list ของจำนวนเต็ม | บทเรียน 3.4–3.6 |
สองแถวล่างเพิ่มเข้าโมดูลเมื่อ 2026-08-20 — firmware รุ่นก่อนหน้ายังไม่มี ใช้ในห้องต้องแฟลชรุ่นนั้นขึ้นไป
dsp ทั้งโมดูล (3/3) — สี่ฟังก์ชันอ้างอิง และกฎ "ป้อนทีละค่า"สี่ฟังก์ชันอ้างอิง (altitude dew_point heat_index comfort_zone) เป็นคณิตศาสตร์ล้วน เรียกได้ปกติทุกเมื่อ แต่ Eva Kit ไม่มี DPS368 (ความดัน) และไม่มี SHT40 (อุณหภูมิ/ความชื้น) จึง ไม่มีแหล่งข้อมูลบนบอร์ดให้ป้อน ป้อนตัวเลขที่พิมพ์เองหรือรับมาจากเครือข่ายได้ · Dev Kit มีชิปทั้งสองตัว (sensors.dps368 / sensors.sht40) จึงป้อนค่าจริงเข้าสี่ฟังก์ชันนี้ได้ — แต่ทั้งสี่ตัวยังอยู่ในตารางในฐานะแถวอ้างอิง ไม่ใช่งานลงมือของชุดบทเรียนนี้ ไม่ว่าบอร์ดไหน
ตัวกรองทุกตัวรับทีละค่า ป้อนทีละตัวอย่าง — ใครมาจาก numpy ต้องเปลี่ยนวิธีคิดตรงนี้ก่อน · ข้อยกเว้นเดียวของโมดูลคือคู่
fft_magกับs16ในตารางก่อนหน้า ที่รับทั้งชุด เพราะสเปกตรัมคำนวณจากตัวอย่างทั้งก้อนพร้อมกัน — บทเรียน 3.4–3.6 ได้ใช้จริง
| ตัวกรอง | วิธีคิด | เก่งเรื่อง | ราคาที่จ่าย |
|---|---|---|---|
SMA(window=8) |
เฉลี่ยตรง ๆ ของ N ค่าล่าสุด | เข้าใจง่าย อธิบายให้ใครก็ได้ฟัง | จำ N ค่า และหน่วงราวครึ่ง window |
LPF(cutoff=, fs=) |
EMA ที่ตั้งด้วย ความถี่ แทนน้ำหนัก | บอกเป็น Hz ได้ว่าตัดอะไรทิ้ง | ต้องรู้คาบลูปจริง ไม่งั้น fs โกหก |
HPF(cutoff=, fs=) |
เก็บเฉพาะส่วนที่ เปลี่ยนเร็ว | จับการสั่น การเคาะ การกระแทก | ทิ้งระดับของสัญญาณไปหมด |
Kalman1D(q=, r=) |
ชั่งน้ำหนักระหว่าง "เชื่อการวัด" กับ "เชื่อค่าเดิม" ทุกรอบ | ปรับตัวเองได้ นิ่งกว่า EMA ที่ความไวเท่ากัน | ต้องจูนสองตัวเลข ไม่ใช่ตัวเดียว |
LPF กับ EMA เป็นสมการเดียวกัน ต่างแค่ทางเข้า — LPF คำนวณ ให้จากค่า cutoff และ fs ที่เราบอก ส่วน EMA ให้เราใส่ เอง เลือกทางไหนก็ได้ แต่ ถ้าบอก fs=100 ทั้งที่ลูปเดินจริงที่ 5 Hz ตัวเลข cutoff ที่ตั้งไว้จะไม่เป็นความจริงเลย
HPF คืน 0.0 เสมอในรอบแรก ไม่ใช่บั๊ก — มันตอบว่า "เปลี่ยนไปเท่าไร" และรอบแรกยังไม่มีค่าก่อนหน้าให้ลบ · Kalman1D ที่ r สูงแปลว่าไม่ค่อยเชื่อเซนเซอร์ จึงนิ่งมากแต่ตามช้า ส่วน q สูงแปลว่าคิดว่าโลกเปลี่ยนเร็ว จึงไวขึ้น
ลงมือ:
08_six_filters_one_signal.pyป้อนสัญญาณเส้นเดียวกันเข้าทั้งหกตัวพร้อมกัน แล้วโชว์.value()ของทุกตัวเรียงกัน — สัญญาณสร้างเองในไฟล์ จึงรันซ้ำได้ผลเดิมทุกครั้ง เถียงกันด้วยตัวเลขได้
ก่อนจะมีไมโครคอนโทรลเลอร์ราคาถูก การทำสัญญาณให้เรียบทำด้วยของจริงสองชิ้น คือตัวต้านทานต่อกับตัวเก็บประจุ เรียกว่าวงจร RC low-pass filter ความถี่สูง ๆ ถูกกลืนหายไปกับตัวเก็บประจุ เหลือแต่ส่วนที่เปลี่ยนช้า ๆ ออกมา
ถ้าเขียนสมการของวงจร RC ออกมาในรูปดิจิทัล จะได้หน้าตาเหมือน EMA ทุกประการ โดยที่
โมดูล dsp บนบอร์ดนี้มี dsp.LPF(cutoff=, fs=) ที่คำนวณ จากสูตรนี้ให้เลย — พูดง่าย ๆ คือ LPF กับ EMA เป็นตัวเดียวกัน ต่างกันแค่เราตั้งค่าด้วยความถี่ตัดหรือด้วยน้ำหนักโดยตรง
เชื่อมกับวันนี้: ตอนที่ผู้เรียนปรับ alpha แล้วรู้สึกว่า "หน่วงขึ้น" นั่นคือการหมุนลูกบิดเสมือนของตัวเก็บประจุที่ไม่มีอยู่จริง วิศวกรรุ่นก่อนต้องบัดกรีเปลี่ยนตัว C เพื่อทำสิ่งเดียวกับที่เราแก้ตัวเลขบรรทัดเดียว
สิ่งที่เฟิร์มแวร์ทำให้แล้ว (70%)
ตั้งค่า SAR ADC และอ่านค่าจากขาลูกบิด (Eva: P15[1] · Dev Kit: VR1 บนฐาน) · สเกลค่า 12 บิตที่วัดได้ให้เป็นช่วง 0-65535 และคิดเปอร์เซ็นต์/โวลต์ให้ · คุย I2C กับชิป 4000T และหักลบ baseline ให้เรียบร้อย · ฟิลเตอร์ทั้งชุดใน dsp เขียนเป็นภาษา C มาแล้ว · การวาดทุกอย่างบนจอ
สิ่งที่เป็นงานของเรา (30%)
เลือกว่าจะแสดงค่าไหนด้วย widget อะไร · ตั้งค่า alpha ให้เหมาะกับงาน · จัดจังหวะลูปให้จอตามทันและเซนเซอร์ไม่ถูกอ่านถี่เกินจำเป็น · ตัดสินใจว่าค่าที่เห็นเชื่อถือได้หรือยัง
โจทย์ของวิศวกรวันนี้ไม่ใช่ "เขียนฟิลเตอร์" แต่คือ เลือกฟิลเตอร์และปกป้องตัวเลือกนั้นได้
import ui
ui.screen() # ล้าง widget เดิมทั้งหมด เริ่มจากจอว่างที่รู้แน่
import time
import sensors
import dsp
ui.clear()
time.sleep_ms(200)
...
# ไม่มี sensors.init() ทั้งสองบอร์ด (Eva: ขึ้น OSError / Dev Kit: ไม่จำเป็น)
try:
sensors.pot.read() # อุ่นเครื่อง รอบแรกหลังรีเซ็ตอาจต้องรอคอร์จอตอบ
except OSError:
print("อ่านเซนเซอร์รอบแรกยังไม่ได้ - ลองใหม่ในลูป")
ui.screen() มาก่อนเสมอ เพราะการใช้ ui.* ครั้งแรกจะสั่งหยุด sensor auto-task ของเฟิร์มแวร์ หลังจากนี้ เราต้องอ่านค่าเองทุกรอบในลูป · อุ่นเครื่องใน try/except เพราะรอบแรกหลังรีเซ็ตอาจต้องรอ (Eva วัดได้ถึง 16 วินาที) ยังไม่ตอบก็พิมพ์บอกแล้วลองใหม่ในลูป
บน Eva Kit บัส I2C ของเซนเซอร์เป็นของคอร์จอ (CM55) sensors.init()/scan() จึงถูก ปฏิเสธด้วย OSError (ขับบัสจากสองคอร์เคยทำให้บอร์ดค้างถาวร) · บน Dev Kit สองคำสั่งนั้นผ่าน เพราะบัส I2C ของ IMU เป็นของ CM33 เอง แต่เฟิร์มแวร์ปลุกให้ตั้งแต่บูต โค้ดของคอร์สจึง ไม่เรียกทั้งสองบอร์ด
เส้นทางของค่า — Eva: ทุกตัวมาจาก snapshot ของ CM55 · Dev Kit: pot.read() อ่าน SAR ตรง (ลูกบิดไม่อยู่บนบัส) IMU อ่านสดจาก CM33 ส่วนแถบสัมผัส 4000T และช่อง pot ใน snapshot() มาจากคอร์จอทุก 200 ms
pot_bar = ui.Bar(x=28, y=80, w=428, h=16, color=0x4A9EFF, min=0, max=100, value=0)
pot_scale = ui.Scale(x=28, y=92, w=428, h=44, color=COL_TEXT, min=0, max=100)
pot_scale.ticks(11, 2) # 11 ขีด ใส่เลขทุกขีดที่สอง
...
lbl_pct = ui.Label("0.0 %", x=28, y=180, color=COL_TEXT, value=28)
...
sp_th = ui.Spinbox(x=508, y=76, w=256, h=88, color=COL_TEXT,
min=TH_MIN, max=TH_MAX, value=th)
sp_th.digits(2, 0) # ไม่บอกจะเห็น 0070
btn_up = ui.Button("เพิ่ม", x=644, y=172, w=120, h=88, color=0x3A4150, value=16)
btn_dn = ui.Button("ลด", x=508, y=172, w=120, h=88, color=0x3A4150, value=16)
...
led_ok = ui.Led(x=492, y=304, w=48, h=48, color=COL_OK, value=1)
...
led_bad = ui.Led(x=492, y=348, w=48, h=48, color=COL_BAD, value=0)
ตัวเลข 55.4 % ลอย ๆ ตอบไม่ได้ว่าสูงหรือต่ำ ui.Scale คือไม้บรรทัดที่พาพิสัยมาอยู่บนจอด้วยกัน และเพราะเป็นไม้บรรทัด มันจึง ไม่รับ .value() ตัวที่ขยับคือ ui.Bar ที่วางทับ (หน้าปัดวงกลมมีเข็มจริง — บทเรียน 3.1–3.3) · .ticks(ทั้งหมด, ใส่เลขทุกกี่ขีด) คุมความหนาแน่น ที่นี่ 11 ขีดใส่เลขทุกขีดที่สอง ได้ 0 20 40 60 80 100 พอดี
เกณฑ์เตือนไม่ควรเป็นค่าคงที่ในโค้ด คนหน้างานคือคนรู้ว่างานนี้ยอมได้แค่ไหน ui.Spinbox หนีบค่าในพิสัยให้เอง แต่บนจอสัมผัส spinbox เปล่า ๆ นิ้วเปลี่ยนค่าไม่ได้ ตัวที่เพิ่มลดค่าจริงคือ ui.Button สองปุ่มข้าง ๆ (สูง 88 px ตามขนาดเป้าสัมผัสของหลักสูตร)
ไฟสองดวงแทนสถานะด้วยสีตัวอักษร เพราะไฟมี รูปทรงและความสว่าง ถ่ายจอขาวดำแล้วยังแยกออก และ .value(0) คือ หรี่ ไม่ใช่หาย โดยตั้งใจ · จำกฎบทเรียน 2.4–2.6: value= ของ ui.Label คือ ขนาดฟอนต์ ส่วน Bar/Scale ใช้ min/max เป็นพิสัยจริง — ตัวอย่างประกอบที่ 09_scale_led_spinbox.py
ค่าดิบกับโวลต์ยังอยู่ครบ แต่ลดเป็นบรรทัดกำกับ เพราะคนเดินมาดูจอถามคำถามเดียวก่อน: ตอนนี้สูงเกินหรือยัง
touch_bar = ui.Bar(x=28, y=284, w=428, h=16, color=0x4A9EFF, min=0, max=100, value=0)
touch_scale = ui.Scale(x=28, y=300, w=428, h=40, color=COL_TEXT, min=0, max=100)
touch_scale.ticks(11, 2)
...
led_b0 = ui.Led(x=28, y=344, w=48, h=48, color=COL_OK, value=0)
...
led_b1 = ui.Led(x=160, y=344, w=48, h=48, color=COL_OK, value=0)
...
slider = sensors.capsense.slider() # 0 - 100 อยู่แล้ว ไม่ต้องแปลงหน่วย
b0, b1 = sensors.capsense.buttons() # คืน tuple สองช่อง แกะพร้อมกันได้เลย
...
touch_bar.value(max(0, min(100, slider)))
...
led_b0.value(1 if b0 else 0)
led_b1.value(1 if b1 else 0)
ui.Bar ไม่รับการแตะและไม่ส่ง event มันเป็น จอแสดงผลอย่างเดียว ซึ่งตรงกับที่ต้องการพอดี เพราะตัวสั่งงานคือนิ้วบนแถบทองแดง ไม่ใช่นิ้วบนกระจก และไม้บรรทัดชุดเดิมถูกใช้ซ้ำด้วยเหตุผลเดียวกับการ์ดบน: 62 ไม่มีความหมายจนกว่าจะรู้ว่าเต็มสเกลคือเท่าไร
สถานะปุ่มทองแดงเคยเขียนเป็นข้อความ BTN0 ON BTN1 - ซึ่ง ไม่ผ่านการทดสอบขาวดำ — เป็นเกรย์สเกลแล้ว ON สีม่วงกับขีดสีเทาแยกกันไม่ออก ไฟสองดวงแยกออกทันทีเพราะดวงที่ติดสว่างกว่าดวงที่หรี่
ครอบด้วย max(0, min(100, ...)) ไม่ใช่เพราะไม่เชื่อไดรเวอร์ แต่ถ้าชิป 4000T ไม่พร้อม ไบต์ที่ได้อาจเป็น 255 การครอบทำให้จอยังแสดงผลได้แทนที่จะพังทั้งหน้า
ค่าที่มาจากภายนอกโปรแกรมเราเสมอ ๆ ควรถูก ตรวจขอบเขตก่อนใช้ นี่คือนิสัยที่ติดตัวไปทุกภาษา
lbl_health = ui.Label("อ่านค่าปกติ", x=292, y=364, color=COL_TEXT, value=16)
...
ema = dsp.EMA(alpha=0.2) # สร้างครั้งเดียว นอกลูป
...
ema_pct = ema.update(pct) # ป้อนค่าใหม่ ได้ค่าที่กรองแล้วกลับมาทันที
...
raw_line.text("ดิบ {:.2f} %".format(pct))
ema_line.text("กรอง {:.2f} %".format(ema_pct))
...
if not fresh: # รอบนี้อ่านเซนเซอร์ไม่ได้
lbl_health.color(COL_WARN)
health = "ค่าค้าง - เลขคือค่าล่าสุด"
...
lbl_health.text(health) # นอกประตูหนึ่งวินาที - ส่งซ้ำทุกรอบโดยตั้งใจ
จุดที่พลาดบ่อยที่สุดคือ เผลอสร้างฟิลเตอร์ไว้ในลูป — ทุกรอบได้ฟิลเตอร์ใหม่ที่ความจำว่าง ค่าที่ออกมาเท่าค่าดิบเป๊ะ แล้วสรุปผิดว่า "ฟิลเตอร์ไม่ทำงาน" · ทศนิยมสองตำแหน่งทั้งสองบรรทัดเป็นเรื่องจงใจ ปัดเหลือจำนวนเต็มเมื่อไร ความสั่นถูกซ่อน บทเรียนทั้งบทเรียนหายไปด้วย
บรรทัด คุณภาพของค่า แก้ปัญหาที่ไม่มีใครเห็นจนกว่าจะเกิด: อ่านรอบนี้ขึ้น OSError โปรแกรมใช้ค่าเดิมต่อ จอจึงยังโชว์ตัวเลขสวยงามทั้งที่ไม่มีค่าใหม่แล้ว ค่าที่ค้างจึงต้องเขียนกำกับว่าไม่ใช่ค่าปัจจุบัน · สังเกตลำดับ ตั้งสีก่อน แล้วค่อยเขียนข้อความ — สองคำสั่งข้ามคอร์คนละครั้ง สลับกันจะมีเสี้ยววินาทีที่ข้อความใหม่ใส่สีของสถานะเก่า
lbl_health.text(health) อยู่ นอก ประตูหนึ่งวินาทีโดยตั้งใจ — ฝั่งจออยู่ โหมดเร็ว ต่อไปอีก 500 ms ทุกครั้งที่ได้คำสั่งเขียนข้อความ แต่ .value() ของแถบกับไฟ ไม่ปลุก โหมดนั้น ลูปที่มีแต่แถบกับไฟจึงถอยไปโหมดช้า ภาพกระตุก · ข้อความเท่าเดิมเฟิร์มแวร์ไม่วาดซ้ำ เราจ่ายแค่ค่าส่ง
อยากเห็นฟิลเตอร์ทำงาน ต้อง แสดงคู่กันบนจอเดียว ถ้าดูทีละอันจะไม่มีวันรู้ว่ามันช่วยจริงหรือแค่รู้สึกไปเอง
while True:
fresh = True
try:
raw = sensors.pot.read()
volts = sensors.pot.voltage()
pct = sensors.pot.percent()
slider = sensors.capsense.slider()
b0, b1 = sensors.capsense.buttons()
except OSError:
fresh = False # ใช้ค่าเดิมต่อ แต่ต้องบอกคนดู
b0, b1 = 0, 0
pot_bar.value(int(max(0, min(100, pct))))
# ... ไฟกับแถบอัปเดตทุกรอบ ...
for ev in ui.poll(): # ต้องมีทุกรอบ ปุ่มถึงจะกดติด
...
sec = time.ticks_ms() // 1000
if sec != last_sec: # ตัวเลขเปลี่ยนวินาทีละครั้ง
last_sec = sec
lbl_pct.text("{:.1f} %".format(pct))
time.sleep_ms(200) # จังหวะที่ผ่านการทดสอบ
ui.poll() มีหน้าที่มากกว่ารับ event จากการแตะจอ มันคือจังหวะที่ฝั่ง Python เปิดโอกาสให้ระบบ UI จัดการคิวของตัวเอง ถ้าไม่เรียก widget บางตัวจะถูกซ่อนไว้นานถึงสองวินาที ชุดบทเรียนนี้ใช้ event แค่ปุ่มเพิ่ม/ลดเกณฑ์ แต่ต่อให้ไม่มี event เลยก็ยังต้องเรียกทุกรอบอยู่ดี
ทำไม 200 ms ไม่ใช่ 20 ms — เพราะลูปที่เร็วเกินไปจะยิงคำสั่งวาดข้ามคอร์ถี่กว่าที่ CM55 วาดทัน เฟรมส่วนเกินจะถูกทิ้งเงียบ ๆ ไม่มี error ให้เห็น ผลคือจอกระตุกโดยที่โค้ดดู "ถูกต้อง" ทุกบรรทัด
อีกมุมหนึ่ง 200 ms = อ่านเซนเซอร์ 5 ครั้งต่อวินาที ซึ่งเร็วกว่าที่ตาคนแยกออกอยู่แล้วสำหรับการหมุนลูกบิดด้วยมือ
แต่แถบกับตัวเลขไม่ได้เดินจังหวะเดียวกัน และนี่คือรายละเอียดที่แยกหน้าจอควบคุมออกจากหน้าจอสาธิต แถบและไฟอัปเดตทุกรอบ คือ 5 ครั้งต่อวินาที เพราะตาคนอ่าน "ตำแหน่ง" ได้โดยไม่ต้องหยุดอ่าน ส่วน ตัวเลขที่ต้องอ่านเป็นตัวเลข เขียนใหม่ไม่เกินวินาทีละครั้ง ตัวเลขทศนิยมที่วิ่งห้าครั้งต่อวินาทีคือตัวเลขที่อ่านไม่ทัน แล้วคนจะเลิกอ่านมันไปเลย ประตูที่ใช้กั้นคือบรรทัด if sec != last_sec: ซึ่งเทียบแค่ว่า "วินาทีเปลี่ยนแล้วหรือยัง"
for ev in ui.poll(): ทำสองอย่างพร้อมกันในบรรทัดเดียว คือเรียก poll ตามกฎ และรับ event ของปุ่มเพิ่ม/ลดเกณฑ์ ถ้าลืมบรรทัดนี้ อาการที่เห็นจะเป็น "จอกระตุกและปุ่มกดไม่ติด" ซึ่งดูเหมือนของสองเรื่องแต่มีสาเหตุเดียว
เร็วกว่าที่จำเป็นไม่ได้แปลว่าดีกว่า ในระบบฝังตัวมันมักแปลว่า เปลืองพลังงานและได้ภาพที่แย่ลง
สังเกตว่าสองเส้นทางต้นทางต่างกันคนละแบบ (ADC กับ I2C) แต่มาบรรจบเป็นโค้ด Python บรรทัดเดียวกันที่ CM33
s05_pot_capsense.py ใน BENTO IDEpass ให้ครบทั้ง 6 จุดตามคำใบ้ # เติม: — เติมทีละจุด รันดูทีละครั้งหมายเหตุผู้สอน: ขึ้น OSError: CapSense: CM55 did not answer... ในไม่กี่บรรทัดแรก ให้รอสักครู่แล้วรันซ้ำก่อน (หลังรีเซ็ต คอร์จอต้องใช้เวลาก่อนตอบสายเซนเซอร์ — Eva วัดได้ถึง 16 วินาที) ยังขึ้นซ้ำค่อยตรวจว่าชิป PSoC 4000T ถูก flash แล้วหรือยัง · ขึ้น OSError ที่ sensors.init() = ลอกโค้ดรุ่นเก่ามา ให้ลบบรรทัดนั้นทิ้ง (บน Dev Kit ผ่านเงียบ ๆ ก็ลบเหมือนกัน) · ลูกบิดและ CapSense มีทั้ง Eva Kit และ Dev Kit — AI Kit เปล่า ๆ ที่ไม่มีฐาน QWA309 ไม่มีทั้งสองอย่าง · เจอ error ตอนอ่าน CapSense ให้เรียกผู้สอนก่อน อย่าเสียเวลาทั้งบทเรียนแก้โค้ดที่ไม่ได้ผิด
โน้ตผู้สอน: การอ่านครั้งแรกหลังรีเซ็ตอาจรอคอร์จอนาน (Eva วัดได้ถึง 16 วินาที · Dev Kit ยังไม่ได้วัด) ไฟล์จึงห่ออุ่นเครื่องใน try/except — ยังไม่ตอบก็พิมพ์บอกแล้วไปลองใหม่ในลูป ไม่หยุดทั้งสคริปต์