จากบทความสร้างนาฬิกาที่แสดงผลแบบแอนาล็อกแสดงผ่านจอแสดงผลแบบสี ครั้งนี้นำมาปรับปรุงแก้ไขเพื่อสร้างการทำงานเป็นเครื่องจับเวลาหรือนาฬิกาจับเวลา (Stopwatch) โดยใช้บอร์ด ESP32-CAM เชื่อมต่อกับจอ TFT และใช้สวิตช์จากขา GPIO0 ที่ใช้เป็นสวิตช์เลือกโหมดทำงานหรือโปรแกรมชิพเมื่อตอนบูตระบบหรือจ่ายไฟเข้าบอร์ด ESP32-CAM ดังภาพที่ 1 และการเขียนโปรแกรมยังคงใช้ภาษาไพธอนกับตัว MicroPython เช่นเคย
วาดนาฬิกา
สิ่งที่ปรับปรุงจากบทความก่อนหน้านี้คือ เปลี่ยนรูปแบบของหมุดบนหน้าปัดนาฬิกา โดยการวาดเปลี่ยนจากการวาดวงกลมจากการคำนวณจุดกึ่งกลางของแต่ละตำแหน่ง เป็นวาดเป้นเส้นตรง โดยขั้นตอนการทำงานของส่วนนี้คือ
- คำนวณหา newX, newY
- คำนวณ newX2 และ newY2
- วาดเส้นตรงจาก (newX, newY) ไป (newX2, newY2)
ผลลัพธ์ที่ได้เป็นดังภาพที่ 2 และโค้ดสำหรับวาดหน้าปัดนาฬิกาเป็นดังนี้
screenCenterX = 40
screenCenterY = 40
...
def drawClock():
midPointCircleDraw(screenCenterX, screenCenterY, 39, tft.color(232,232,232))
n12x = screenCenterX
n12y = 2
n12y2 = 8
for i in range(12):
newX,newY = rotate( n12x, n12y, 30*i)
newX2,newY2 = rotate( n12x, n12y2, 30*i)
tft.line((newX,newY),(newX2, newY2),tft.color(232, 232, 64))
ตัวอย่างโปรแกรม
ผังวงจร
อุปกรณ์ที่ต้องใช้
- จอแสดงผล ST7735 GREENTAB80x160
- บอร์ดไมโครคอนโทรลเลอร์ ESP32 โดยในบทความนี้ใช้ ESP32CAM
การเชื่อมต่อจากจอแสดงผลกับ ESP32 โดยใช้บัส SPI ช่องสัญญาณ 2
- SCK ของจอแสดงผลเชื่อมต่อกับ GPIO14 ของ ESP32
- MOSI ของจอแสดงผลเชื่อมต่อกับ GPIO12 ของ ESP32
- DC ของจอแสดงผลเชื่อมต่อกับ GPIO15 ของ ESP32
- RST ของจอแสดงผลเชื่อมต่อกับ GPIO13 ของ ESP32
- CS ของจอแสดงผลเชื่อมต่อกับ GPIO2 ของ ESP32
การเชื่อมต่อสวิตช์ต่อเข้ากับขา GPIO0
โค้ดโปรแกรม
หลักการทำงานของโปรแกรมตัวอย่าง คือ
- กำหนดโหลดทำงานเป็น 0
- วาดหน้าปัด
- ถ้ามีการกด switch
- ถ้า state = 0
- เริ่มต้นตัวตั้งเวลา
- เปลี่ยน state เป็น 1
- ถ้า state = 1
- หยุดการทำงานของตัวตั้งเวลา
- แสดงค่าชั่วโมง นาที และวินาที
- ล้างค่าตัวแปรต่าง ๆ ให้เป็นค่าเริ่มต้น
- ถ้า state = 0
โค้ดโปรแกรมเป็นดังนี้
from ST7735 import TFT
import machine as mc
from machine import SPI,Pin, Timer
import time
import math
#################################################################
###### setting ##################################################
#################################################################
mc.freq(240000000)
spi = SPI(2, baudrate=26000000,
sck=Pin(14), mosi=Pin(12),
polarity=0, phase=0)
sw = Pin(0, Pin.IN, Pin.PULL_UP)
# dc, rst, cs
tft=TFT(spi,15,13,2)
tft.fill(tft.BLACK)
tft.swap()
screenCenterX = 40
screenCenterY = 40
second = 0
minute = 0
hour = 0
state = 0
#################################################################
###### sub modules ##############################################
#################################################################
def midPointCircleDraw(x_centre, y_centre, r, c):
x = r
y = 0
tft.setPixel(x + x_centre,y + y_centre, c)
# When radius is zero only a single
# point be printed
if (r > 0) :
tft.setPixel(x + x_centre,-y + y_centre, c)
tft.setPixel(y + x_centre,x + y_centre, c)
tft.setPixel(-y + x_centre,x + y_centre, c)
# Initialising the value of P
P = 1 - r
while x > y:
y += 1
# Mid-point inside or on the perimeter
if P <= 0:
P = P + 2 * y + 1
# Mid-point outside the perimeter
else:
x -= 1
P = P + 2 * y - 2 * x + 1
# All the perimeter points have
# already been printed
if (x < y):
break
# Printing the generated point its reflection
# in the other octants after translation
tft.setPixel(x + x_centre,y + y_centre, c)
tft.setPixel(-x + x_centre, y + y_centre, c)
tft.setPixel( x + x_centre,-y + y_centre, c)
tft.setPixel( -x + x_centre,-y + y_centre, c)
# If the generated point on the line x = y then
# the perimeter points have already been printed
if x != y:
tft.setPixel(y + x_centre, x + y_centre, c)
tft.setPixel(-y + x_centre, x + y_centre, c)
tft.setPixel(y + x_centre, -x + y_centre, c)
tft.setPixel(-y + x_centre, -x + y_centre, c)
def rotate(pX,pY,angle):
rad = math.radians(angle)
pX -= screenCenterX
pY -= screenCenterY
xCos = pX*math.cos(rad)
ySin = pY*math.sin(rad)
xSin = pX*math.sin(rad)
yCos = pY*math.cos(rad)
newX = xCos - ySin + screenCenterX
newY = xSin + yCos + screenCenterY
return (int(newX), int(newY))
def drawClock():
midPointCircleDraw(screenCenterX, screenCenterY, 39, tft.color(232,232,232))
n12x = screenCenterX
n12y = 2
n12y2 = 8
for i in range(12):
newX,newY = rotate( n12x, n12y, 30*i)
newX2,newY2 = rotate( n12x, n12y2, 30*i)
tft.line((newX,newY),(newX2, newY2),tft.color(232, 232, 64))
def drawSecond( sec ):
deg = sec*6
n12x = screenCenterX
n12y = 10
newX,newY = rotate( n12x, n12y, deg)
tft.line((screenCenterX, screenCenterY), (newX, newY), tft.color(232, 64, 232))
def cbSecond(x):
global second, minute, hour
second += 1
if (second == 60):
second = 0
minute += 1
if (minute == 60):
minute = 0
hour += 1
if (hour == 12):
hour = 0
tft.fill(tft.BLACK)
drawClock()
drawSecond(second)
tft.swap()
#################################################################
###### main program #############################################
#################################################################
tft.fill(tft.BLACK)
drawClock()
drawSecond(0)
tft.swap()
secTmr = Timer(0)
while True:
if (sw.value()==0):
if state == 0:
secTmr.init( period=1000, mode=Timer.PERIODIC, callback=cbSecond)
state = 1 # start
else:
state = 0
secTmr.deinit()
tft.fill(tft.BLACK)
drawClock()
drawSecond(0)
tft.text("H:{}".format(hour),(96,24),tft.color(232, 192, 194))
tft.text("M:{}".format(minute),(96,34),tft.color(192, 232, 194))
tft.text("S:{}".format(second),(96,44),tft.color(194, 192, 232))
tft.swap()
second = 0
minute = 0
hour = 0
time.sleep(0.25)
ตัวอย่างผลลัพธ์เป็นดังภาพที่ 3
สรุป
จากบทควานนี้จะพบว่าเราสามารถปรับปรุงการทำงานของนาฬิกาให้กลายเป็นตัวจับเวลาได้โดยการแก้ไขขั้นตอนการทำงานใหม่ ทำให้อุปกรณ์เดิมมีความสามมรถมากยิ่งขึ้น นั่นหมายความว่า ฮาร์ดแวร์ที่เราใช้นั้นจะทำงานได้มากหรือดีนอกจากการออกแบบฮาร์ดแวร์ได้ดีแล้วยังขึ้นกับซอฟต์แวร์ที่เราเขียนอีกด้วย โดยพวกเราหวังว่า ตัวอย่างในบทความนี้คงพอเป็นแนวทางสำหรับการปรับปรุงการทำงาน หรือนำไปรวมกับบทความก่อนหน้านี้เพื่อให้รองรับการทำงานที่มากขึ้น และ สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ
(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-11-05