-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path07_display_basics.py
More file actions
457 lines (372 loc) · 22.7 KB
/
Copy path07_display_basics.py
File metadata and controls
457 lines (372 loc) · 22.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# ==============================================================================
# 07_display_basics.py - TFT Display for pH and Titration Monitoring
# ==============================================================================
# จอแสดงผล TFT ILI9341 สำหรับแสดงค่า pH และข้อมูลการไทเทรชัน
# TFT ILI9341 Display for pH Values and Titration Data Monitoring
# ==============================================================================
#
# ความสำคัญของจอแสดงผลในการไทเทรชัน (Display Importance in Titration):
# ======================================================================
# ในการไทเทรชันอัตโนมัติ จอแสดงผลมีบทบาทสำคัญ:
# In automated titration, the display plays a crucial role:
#
# 1. แสดงค่า pH แบบ real-time (Show real-time pH values)
# - นักศึกษาสังเกตการเปลี่ยนแปลง pH ได้ทันที
# - Students can observe pH changes immediately
#
# 2. แสดงอุณหภูมิสำหรับ compensation (Show temperature for compensation)
# - อุณหภูมิมีผลต่อความชันตามสมการ Nernst
# - Temperature affects slope per Nernst equation
#
# 3. แสดงปริมาตรสารละลายที่เติม (Show added solution volume)
# - ติดตามปริมาณสารไทแทรนต์ที่ใช้
# - Track titrant volume used
#
# 4. แสดงกราฟไทเทรชัน (Show titration curve) - Week 3
# - วาดกราฟ pH vs Volume แบบ real-time
# - Draw pH vs Volume graph in real-time
#
# Hardware Connection (การต่อวงจร):
# TFT_SCK = GPIO14 (SPI Clock)
# TFT_MOSI = GPIO13 (SPI Data)
# TFT_DC = GPIO27 (Data/Command select)
# TFT_CS = GPIO15 (Chip Select)
# TFT_RST = GPIO0 (Reset - also boot mode pin)
# Resolution: 320x240 pixels, 16-bit color (RGB565)
# ==============================================================================
from machine import Pin, SPI
from ili9341 import Display, color565
from xglcd_font import XglcdFont
import time
# ==============================================================================
# การทำงานร่วมกับเฟิร์มแวร์ SciLabPro MicroPad (Interop with MicroPad firmware)
# ==============================================================================
# บนบอร์ด TitraLab ที่ใช้เฟิร์มแวร์ SciLabPro MicroPad เฟิร์มแวร์จะ "ครอบครอง"
# บัส SPI ของจอ TFT ไว้ (ใช้วาดหน้า boot splash) จนกว่าสคริปต์ของนักศึกษา
# จะเรียก release_tft() เพื่อขอสิทธิ์ควบคุมจอเอง ถ้าไม่เรียก จะเกิดข้อผิดพลาด
# OSError: SPI host already in use ตอนสร้าง SPI(1, ...) ใน init_display()
#
# On a TitraLab board running SciLabPro MicroPad firmware, the firmware OWNS
# the TFT SPI bus (it draws the boot splash) until a student script calls
# release_tft() to take ownership. Without that call, SPI(1, ...) inside
# init_display() raises OSError: SPI host already in use.
#
# บน MicroPython มาตรฐาน (ไม่มีโมดูล scilabpro) เราตั้งค่าเป็น None แล้วข้าม
# ขั้นตอนนี้ไป โค้ดจึงทำงานได้ทั้งสองแพลตฟอร์ม
# On standard MicroPython (no scilabpro module) we fall back to None and skip
# the call, so this script runs unchanged on both platforms.
try:
from scilabpro import release_tft # ปลดจอ TFT จากเฟิร์มแวร์ (yield TFT)
except ImportError:
release_tft = None
try:
from scilabpro import stop_requested # ให้แอปสั่งหยุดได้ (cooperative stop)
except ImportError:
stop_requested = None
# === ค่าคงที่ขา GPIO สำหรับ TFT (TFT GPIO Pin Constants) ===
TFT_SCK = 14 # SPI Clock
TFT_MOSI = 13 # SPI Data (Master Out Slave In)
TFT_DC = 27 # Data/Command select
TFT_CS = 15 # Chip Select
TFT_RST = 0 # Reset pin (GPIO0 = boot-strap pin; ใช้เป็น reset หลังบูตเท่านั้น
# / GPIO0 is the boot strap pin — only driven as reset after boot)
# === สี RGB565 สำหรับการแสดงผล (RGB565 Colors for Display) ===
# RGB565 format: 5 bits red, 6 bits green, 5 bits blue
BLACK = color565(0, 0, 0)
WHITE = color565(255, 255, 255)
RED = color565(255, 0, 0)
GREEN = color565(0, 255, 0)
BLUE = color565(0, 0, 255)
YELLOW = color565(255, 255, 0)
CYAN = color565(0, 255, 255)
ORANGE = color565(255, 165, 0)
# === สีสำหรับแสดงค่า pH (pH Display Colors) ===
# สีตามช่วง pH (Colors based on pH range)
PH_ACIDIC = color565(255, 100, 100) # แดงอ่อน สำหรับกรด (Light red for acidic)
PH_NEUTRAL = color565(100, 255, 100) # เขียวอ่อน สำหรับกลาง (Light green for neutral)
PH_BASIC = color565(100, 100, 255) # น้ำเงินอ่อน สำหรับเบส (Light blue for basic)
def init_display(rotation=90):
"""
เริ่มต้นจอแสดงผล TFT ILI9341 (Initialize TFT ILI9341 Display)
การเชื่อมต่อ SPI ใช้ Bus 1 ของ ESP32 ที่ความเร็ว 40 MHz
SPI connection uses ESP32 Bus 1 at 40 MHz speed.
Args:
rotation: มุมหมุนจอ (display rotation) - 0, 90, 180, 270
Returns:
Display: ออบเจกต์จอแสดงผล (display object)
"""
# ปลดการครอบครองจอ TFT จากเฟิร์มแวร์ก่อนสร้างบัส SPI ของเราเอง
# Yield the TFT from the firmware BEFORE we create our own SPI bus.
# release_tft() ปลอดภัยและเรียกซ้ำได้ (idempotent) — เรียกได้เสมอ ไม่มีผลข้างเคียง
# release_tft() is safe and idempotent — always OK to call, even twice.
if release_tft is not None:
release_tft()
# สร้าง SPI bus สำหรับ TFT (Create SPI bus for TFT)
# หมายเหตุ: ถ้าจอแสดงผลเพี้ยน/มีสัญญาณรบกวน ลองลด baudrate เป็น 20_000_000
# หรือ 10_000_000 เพราะสายจัมเปอร์ยาวบนบอร์ด patch-panel อาจรับ 40 MHz ไม่ไหว
# Note: if the display is garbled/noisy, lower baudrate to 20_000_000 or
# 10_000_000 — long jumper wires on a patch-panel board may not handle 40 MHz.
spi = SPI(1, baudrate=40000000, sck=Pin(TFT_SCK), mosi=Pin(TFT_MOSI))
# สร้างออบเจกต์จอแสดงผล (Create display object)
display = Display(
spi,
dc=Pin(TFT_DC),
cs=Pin(TFT_CS),
rst=Pin(TFT_RST),
width=320,
height=240,
rotation=rotation
)
print(f"TFT Display พร้อมใช้งาน (ready) - 320x240 @ rotation={rotation}")
return display
def load_font(filename='fonts/EspressoDolce18x24.c'):
"""
โหลดฟอนต์สำหรับแสดงข้อความ (Load font for text display)
ฟอนต์ EspressoDolce ขนาด 18x24 pixels เหมาะสำหรับแสดงค่าตัวเลข
EspressoDolce font 18x24 pixels is suitable for displaying numeric values.
Args:
filename: path ไปยังไฟล์ฟอนต์ (path to font file)
Returns:
XglcdFont: ออบเจกต์ฟอนต์ (font object)
"""
try:
font = XglcdFont(filename, 18, 24)
print(f"โหลดฟอนต์สำเร็จ (Font loaded): {filename}")
return font
except Exception as e:
print(f"ข้อผิดพลาดโหลดฟอนต์ (Font load error): {e}")
return None
# ==============================================================================
# ฟังก์ชันพื้นฐาน (Basic Functions)
# ==============================================================================
def clear_screen(display, color=BLACK):
"""
ล้างหน้าจอด้วยสีที่กำหนด (Clear screen with specified color)
Args:
display: ออบเจกต์จอแสดงผล (display object)
color: สีพื้นหลัง (background color)
"""
display.clear(color)
def draw_text(display, font, x, y, text, color=WHITE):
"""
วาดข้อความบนจอ (Draw text on display)
Args:
display: ออบเจกต์จอแสดงผล (display object)
font: ออบเจกต์ฟอนต์ (font object)
x, y: ตำแหน่ง (position)
text: ข้อความ (text string)
color: สีข้อความ (text color)
"""
display.draw_text(x, y, text, font, color)
# ==============================================================================
# ฟังก์ชันสำหรับการไทเทรชัน (Titration-Specific Functions)
# ==============================================================================
def get_ph_color(ph_value):
"""
เลือกสีตามค่า pH (Select color based on pH value)
ใช้สีเพื่อให้นักศึกษาเข้าใจลักษณะของสารละลายได้ทันที
Use colors to help students immediately understand solution characteristics.
pH < 6.5 -> แดง (กรด/acidic)
pH 6.5-7.5 -> เขียว (กลาง/neutral)
pH > 7.5 -> น้ำเงิน (เบส/basic)
Args:
ph_value: ค่า pH (pH value)
Returns:
int: สี RGB565 (RGB565 color)
"""
if ph_value < 6.5:
return PH_ACIDIC
elif ph_value > 7.5:
return PH_BASIC
else:
return PH_NEUTRAL
def draw_ph_display(display, font, ph_value):
"""
แสดงค่า pH บนหน้าจอ พร้อมสีตามช่วง pH (Display pH value with color coding)
แสดงค่า pH ขนาดใหญ่ตรงกลางจอ เพื่อให้อ่านค่าได้ง่าย
Display large pH value in center for easy reading.
Args:
display: ออบเจกต์จอแสดงผล (display object)
font: ออบเจกต์ฟอนต์ (font object)
ph_value: ค่า pH ที่จะแสดง (pH value to display)
"""
# ล้างพื้นที่แสดง pH (Clear pH display area)
display.fill_rectangle(80, 80, 160, 50, BLACK)
# เลือกสีตามค่า pH (Select color based on pH)
ph_color = get_ph_color(ph_value)
# แสดงค่า pH (Display pH value)
ph_text = f"pH {ph_value:.2f}"
display.draw_text(80, 90, ph_text, font, ph_color)
def draw_temperature_display(display, font, temp_celsius):
"""
แสดงค่าอุณหภูมิบนหน้าจอ (Display temperature on screen)
อุณหภูมิสำคัญสำหรับ Nernst compensation
Temperature is important for Nernst compensation.
Args:
display: ออบเจกต์จอแสดงผล (display object)
font: ออบเจกต์ฟอนต์ (font object)
temp_celsius: อุณหภูมิเป็นเซลเซียส (temperature in Celsius)
"""
# ล้างพื้นที่แสดงอุณหภูมิ (Clear temperature display area)
display.fill_rectangle(80, 140, 160, 30, BLACK)
# แสดงค่าอุณหภูมิ (Display temperature)
temp_text = f"Temp {temp_celsius:.1f}C"
display.draw_text(80, 145, temp_text, font, CYAN)
def draw_volume_display(display, font, volume_ml):
"""
แสดงปริมาตรสารไทแทรนต์ที่เติม (Display added titrant volume)
ใช้ติดตามปริมาณสารละลายที่ใช้ในการไทเทรชัน
Used to track solution volume used in titration.
Args:
display: ออบเจกต์จอแสดงผล (display object)
font: ออบเจกต์ฟอนต์ (font object)
volume_ml: ปริมาตรเป็น mL (volume in mL)
"""
# ล้างพื้นที่แสดงปริมาตร (Clear volume display area)
display.fill_rectangle(80, 180, 160, 30, BLACK)
# แสดงค่าปริมาตร (Display volume)
vol_text = f"Vol {volume_ml:.2f}mL"
display.draw_text(80, 185, vol_text, font, YELLOW)
def draw_status_bar(display, font, status_text):
"""
แถบสถานะด้านบนของจอ (Status bar at top of screen)
แสดงสถานะการทำงานของระบบ เช่น "Calibrating", "Titrating", "Complete"
Shows system status like "Calibrating", "Titrating", "Complete"
Args:
display: ออบเจกต์จอแสดงผล (display object)
font: ออบเจกต์ฟอนต์ (font object)
status_text: ข้อความสถานะ (status message)
"""
# วาดพื้นหลังแถบสถานะ (Draw status bar background)
display.fill_rectangle(0, 0, 320, 30, BLUE)
# วาดข้อความสถานะ (Draw status text)
display.draw_text(10, 5, status_text, font, WHITE)
def draw_titration_screen(display, font, ph, temp, volume, status="TitraLab"):
"""
แสดงหน้าจอไทเทรชันแบบเต็ม (Display complete titration screen)
รวมข้อมูลทั้งหมดที่จำเป็นสำหรับการไทเทรชันในหน้าจอเดียว
Combines all necessary titration information in a single screen.
เชื่อมโยงกับ Week 3 (Connection to Week 3):
Week 3 จะเพิ่มกราฟไทเทรชันที่ด้านล่างของจอนี้
Week 3 will add titration graph at the bottom of this screen.
Args:
display: ออบเจกต์จอแสดงผล (display object)
font: ออบเจกต์ฟอนต์ (font object)
ph: ค่า pH (pH value)
temp: อุณหภูมิเป็น C (temperature in C)
volume: ปริมาตรเป็น mL (volume in mL)
status: ข้อความสถานะ (status message)
"""
# แถบสถานะ (Status bar)
draw_status_bar(display, font, status)
# ค่า pH (pH value)
draw_ph_display(display, font, ph)
# ค่าอุณหภูมิ (Temperature)
draw_temperature_display(display, font, temp)
# ปริมาตร (Volume)
draw_volume_display(display, font, volume)
def draw_equivalence_point(display, font, volume_ml, ph_value):
"""
แสดงจุดสมมูลที่ค้นพบ (Display detected equivalence point)
เมื่อระบบตรวจพบจุดสมมูล จะแสดงข้อมูลนี้บนหน้าจอ
When the system detects equivalence point, this information is displayed.
Args:
display: ออบเจกต์จอแสดงผล (display object)
font: ออบเจกต์ฟอนต์ (font object)
volume_ml: ปริมาตรที่จุดสมมูล (volume at equivalence point)
ph_value: pH ที่จุดสมมูล (pH at equivalence point)
"""
# แสดงข้อความ "Equivalence Point Found!" (Show message)
display.fill_rectangle(0, 200, 320, 40, GREEN)
display.draw_text(10, 205, "EQ POINT!", font, BLACK)
display.draw_text(120, 205, f"V={volume_ml:.2f}mL", font, BLACK)
# ==============================================================================
# Demo: จำลองการแสดงผลระหว่างไทเทรชัน (Simulated Titration Display)
# ==============================================================================
if __name__ == "__main__":
print("=" * 50)
print("TFT Display Demo - Titration Monitor")
print("จำลองการแสดงผลระหว่างไทเทรชัน")
print("=" * 50)
# เริ่มต้นจอแสดงผล (Initialize display)
try:
display = init_display(rotation=90)
except OSError as e:
# ข้อผิดพลาดที่พบบ่อยที่สุดคือ "SPI host already in use" เมื่อเฟิร์มแวร์
# MicroPad ยังครอบครองบัส SPI อยู่ (ดูหมายเหตุด้านบนไฟล์)
# The most common failure is "SPI host already in use" when the
# MicroPad firmware still owns the SPI bus (see note at top of file).
print("เริ่มต้นจอ TFT ไม่สำเร็จ (TFT init failed):", e)
print(" • ตรวจสอบว่าโมดูล scilabpro มี release_tft() "
"(check scilabpro.release_tft() is available)")
print(" • ตรวจสอบการต่อสาย SPI: SCK=14 MOSI=13 DC=27 CS=15 RST=0 "
"(verify SPI wiring)")
raise
font = load_font()
if font is None:
print("ไม่สามารถโหลดฟอนต์ได้ (Cannot load font)")
print("ตรวจสอบว่ามีไฟล์ fonts/EspressoDolce18x24.c")
else:
try:
# ล้างจอ (Clear screen)
clear_screen(display)
print("\n--- จำลองการไทเทรชัน (Simulating Titration) ---")
print("กด Ctrl+C เพื่อหยุด (Press Ctrl+C to stop)")
# ข้อมูลจำลอง: เส้นโค้งไทเทรชันกรดแก่-เบสแก่
# Simulated data: Strong acid - strong base titration curve
titration_data = [
# (volume, pH) - จำลองการเปลี่ยนแปลง pH
(0.0, 2.0),
(2.0, 2.3),
(4.0, 2.7),
(6.0, 3.2),
(8.0, 3.8),
(9.0, 4.5),
(9.5, 5.5),
(10.0, 7.0), # จุดสมมูล (equivalence point)
(10.5, 9.5),
(11.0, 10.5),
(12.0, 11.2),
(14.0, 11.8),
]
# อุณหภูมิจำลอง (Simulated temperature)
temp = 25.5
for volume, ph in titration_data:
# ตรวจคำสั่งหยุดจากแอป (check for an app stop request)
if stop_requested is not None and stop_requested():
print("ได้รับคำสั่งหยุดจากแอป (Stop requested by app)")
break
# แสดงหน้าจอไทเทรชัน (Display titration screen)
draw_titration_screen(display, font, ph, temp, volume, "Titrating...")
# แสดงข้อมูลใน console ด้วย (Also show in console)
ph_type = "acidic" if ph < 6.5 else "basic" if ph > 7.5 else "neutral"
print(f"Vol: {volume:5.1f} mL | pH: {ph:5.2f} ({ph_type})")
# ตรวจจับจุดสมมูล (Detect equivalence point)
if 6.5 <= ph <= 7.5 and volume > 0:
draw_equivalence_point(display, font, volume, ph)
print(f">>> จุดสมมูลที่ {volume:.2f} mL, pH {ph:.2f}")
time.sleep(1)
# สถานะเสร็จสิ้น (Complete status)
draw_status_bar(display, font, "Complete!")
print("\n--- การไทเทรชันเสร็จสิ้น (Titration Complete) ---")
print("\n" + "=" * 50)
print("ใน Week 3: จอนี้จะแสดงกราฟ pH vs Volume แบบ real-time")
print("In Week 3: This display will show real-time pH vs Volume graph")
print("=" * 50)
# รอจนกว่าจะกด Ctrl+C หรือแอปสั่งหยุด (Wait for Ctrl+C or app stop)
while True:
if stop_requested is not None and stop_requested():
print("ได้รับคำสั่งหยุดจากแอป (Stop requested by app)")
break
time.sleep(1)
except KeyboardInterrupt:
print("\n\nหยุดโปรแกรม (Program stopped)")
finally:
# ล้างจอก่อนจบ (Clear screen before exit)
# ครอบด้วย try เพื่อไม่ให้ข้อผิดพลาดตอนล้างจอบดบังข้อผิดพลาดจริง
# guard cleanup so a clear() failure can't mask the real error
try:
clear_screen(display)
print("ล้างจอแล้ว (Screen cleared)")
except Exception as e:
print("ล้างจอไม่สำเร็จ (Screen clear failed):", e)