[TH] How to make the stopwatch?

จากบทความสร้างนาฬิกาที่แสดงผลแบบแอนาล็อกแสดงผ่านจอแสดงผลแบบสี ครั้งนี้นำมาปรับปรุงแก้ไขเพื่อสร้างการทำงานเป็นเครื่องจับเวลาหรือนาฬิกาจับเวลา (Stopwatch) โดยใช้บอร์ด ESP32-CAM เชื่อมต่อกับจอ TFT และใช้สวิตช์จากขา GPIO0 ที่ใช้เป็นสวิตช์เลือกโหมดทำงานหรือโปรแกรมชิพเมื่อตอนบูตระบบหรือจ่ายไฟเข้าบอร์ด ESP32-CAM ดังภาพที่ 1 และการเขียนโปรแกรมยังคงใช้ภาษาไพธอนกับตัว MicroPython เช่นเคย

ภาพที่ 1 บอร์ด dCore RED บอร์ด esp32cam+REDTAB

วาดนาฬิกา

สิ่งที่ปรับปรุงจากบทความก่อนหน้านี้คือ เปลี่ยนรูปแบบของหมุดบนหน้าปัดนาฬิกา โดยการวาดเปลี่ยนจากการวาดวงกลมจากการคำนวณจุดกึ่งกลางของแต่ละตำแหน่ง เป็นวาดเป้นเส้นตรง โดยขั้นตอนการทำงานของส่วนนี้คือ

  • คำนวณหา 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))
ภาพที่ 2 ตัวอย่างหน้าปัดที่เริ่มจับเวลา

ตัวอย่างโปรแกรม

ผังวงจร

อุปกรณ์ที่ต้องใช้

  1. จอแสดงผล ST7735 GREENTAB80x160
  2. บอร์ดไมโครคอนโทรลเลอร์ ESP32 โดยในบทความนี้ใช้ ESP32CAM

การเชื่อมต่อจากจอแสดงผลกับ ESP32 โดยใช้บัส SPI ช่องสัญญาณ 2

  1. SCK ของจอแสดงผลเชื่อมต่อกับ GPIO14 ของ ESP32
  2. MOSI ของจอแสดงผลเชื่อมต่อกับ GPIO12 ของ ESP32
  3. DC ของจอแสดงผลเชื่อมต่อกับ GPIO15 ของ ESP32
  4. RST ของจอแสดงผลเชื่อมต่อกับ GPIO13 ของ ESP32
  5. CS ของจอแสดงผลเชื่อมต่อกับ GPIO2 ของ ESP32

การเชื่อมต่อสวิตช์ต่อเข้ากับขา GPIO0

โค้ดโปรแกรม

หลักการทำงานของโปรแกรมตัวอย่าง คือ

  • กำหนดโหลดทำงานเป็น 0
  • วาดหน้าปัด
  • ถ้ามีการกด switch
    • ถ้า state = 0
      • เริ่มต้นตัวตั้งเวลา
      • เปลี่ยน state เป็น 1
    • ถ้า state = 1
      • หยุดการทำงานของตัวตั้งเวลา
      • แสดงค่าชั่วโมง นาที และวินาที
      • ล้างค่าตัวแปรต่าง ๆ ให้เป็นค่าเริ่มต้น

โค้ดโปรแกรมเป็นดังนี้

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

ภาพที่ 3 ผลลัพธ์ของการทำงาน

สรุป

จากบทควานนี้จะพบว่าเราสามารถปรับปรุงการทำงานของนาฬิกาให้กลายเป็นตัวจับเวลาได้โดยการแก้ไขขั้นตอนการทำงานใหม่ ทำให้อุปกรณ์เดิมมีความสามมรถมากยิ่งขึ้น นั่นหมายความว่า ฮาร์ดแวร์ที่เราใช้นั้นจะทำงานได้มากหรือดีนอกจากการออกแบบฮาร์ดแวร์ได้ดีแล้วยังขึ้นกับซอฟต์แวร์ที่เราเขียนอีกด้วย โดยพวกเราหวังว่า ตัวอย่างในบทความนี้คงพอเป็นแนวทางสำหรับการปรับปรุงการทำงาน หรือนำไปรวมกับบทความก่อนหน้านี้เพื่อให้รองรับการทำงานที่มากขึ้น และ สุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ

(C) 2020-2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-11-05