ETT :: Article :: >ESP8266/ESP32 :: ET-ESP8266-RS485 ตอนที่ 2 ใช้งาน RTC DS3231 และ NTP

© 2022,จารุต บุศราทิจ, กอบกิจ เติมผาติ
ปรับปรุงเมื่อ 2022-04-10

บทความนี้เป็นการใช้งานโมดูล RTC DS3231 (ภาพที่ 1) และการเชื่อมต่อเครือข่ายอินเทอร์เน็ตเพื่อใช้งานบริการ NTP (Network Time Protocol) ด้วยบอร์ด ET-ESP8266-RS485 เพื่อเรียนรู้และเข้าใจหลักการสื่อสารของระบบบัสแบบ I2C, การทำงานของ DS3231, การเปิดการทำงานระบบสื่อสารไร้สายแบบ IEEE802.11 และการใช้งานบริการ NTP ด้วย MicroPython ที่ได้ติดตั้งเป็นเฟิร์มแวร์ของบอร์ด โดยบทความได้กล่าวถึงเรื่องของบัส I2C, คลาส RTC, คลาส network, ไอซี DS3231, คลาส time และการใช้งาน NTP พร้อมยกตัวอย่างประกอบ


ภาพที่ 1 ภาค RTC บนบอร์ด ET-ESP8266-RS485

เป้าหมาย

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

คลาส RTC

RTC หรือ Real-Time Clock เป็นโมดูลนาฬิกาแบบเวลาจริง นั่นคือ เมื่อเริ่มทำงานจะมีการนับค่าเวลาเพิ่มขึ้นไปเรื่อย ๆ ตามที่โมดูลนั้นรองรับ โดยในไมโครคอนโทรลเลอร์ ESP8266 มีหน่วยความจำสำหรับเก็บค่า RTC เอาไว้ในตัว และสามารถใช้งานได้ผ่านทางคลาส RTC ซึ่งเป็นคลาสย่อยของ machine อันมีรายละเอียดดังนี้

การสร้างวัตถุสำหรับเข้าถึงการทำงาน RTC ของ esp8266 กระทำได้ดังนี้


    import  machine
    วัตถุ = machine.RTC( id=0 )

สิ่งที่เก็บในคลาส RTC เป็นข้อมูลแบบ Tuple จำนวน 8 สมาชิกเพื่อเก็บค่าของปี เดือน วัน วันในสัปดาห์ ชั่วโมง นาที วินาที และส่วนของวินาทีตามลำดับ โดยรูปแบบของคำสั่งกำหนดค่าเป็นดังนี้


    วัตถุ.datetime( ( ปี, เดือน, วัน, วันในสัปดาห์, ชั่วโมง, นาที, วินาที, ส่วนของวินาที ) )

สำหรับการอ่านค่าจะสามารถใช้งานตามรูปแบบดังนี้


    ข้อมูล = วัตถุ.datetime()

การตั้งการเตือนหรือ Alarm ต้องเรียกใช้เมธอด alarm โดยระบุค่า id ของ RTC พร้อมทั้งกำหนดค่า time เป็นจำนวนมิลลิวินาทีถัดไปที่จะให้แจ้งเตือน


    วัตถุ.alarm( id, time )

กรณีที่ต้องการทราบจำนวนมิลลิวินาทีที่เหลือก่อนจะมีการแจ้งเตือนสามารถใช้เมธอดต่อไปนี้ในการอ่ารค่า


    จำนวนมิลลิวินาที = วัตถุ.alarm_left()

ตัวอย่างการใช้งานเป็นดังนี้


    import machine
    rtc = machine.RTC()
    rtc.alarm( 0, 1000)
    while (rtc.alarm_left() > 0):
        print(".")
    print("!!!")

หมายเหตุ

เมธอด irq ยังไม่ถูกสนับสนุนการใช้งานในไมโครคอนโทรลเลอร์ esp8266

คลาส time

การเข้าถึงการทำงานเกี่ยวกับวันที่และเวลาของไมโครคอนโทรลเลอร์ esp8266 ผ่านทางไมโคนไพธอนจะต้องทำผ่านทางคลาส time ที่รองรับการใช้งานตามเมธอดดังต่อไปนี้

จากรายการเมธอดข้างต้นต้องเรียกนำเข้าไลบรารี time ด้วยคำสั่งต่อไปนี้


    import time

การอ่านค่าของเวลาปัจจุบันใช้งานเมธอด localtime() ดังต่อไปนี้


    ผลลัพธ์ = time.localtime( )

โดยผลลัพธ์ที่ได้เป็นข้อมูลแบบ tuple จำนวน 8 สมาชิกตามลพดับต่อไปนี้

ถ้าต้องการตั้งเวลาผ่านทางคลาส time จะต้องระบุค่าตามลำดับสมาชิพ tuple ดังนี้


    (year, month, mday, hour, minute, second, weekday, yearday)

ผ่านทางเมธอด mktime ดังนี้


    time.mktime( ตัวแปรเก็บวันที่และเวลา )

หรือ


    time.mktime( ( ปีค.ศ. , เดือน, วัน, วันในสัปดาห์, ชั่วโมง, นาที, วินาที, เศษวินาที ) )

นอกจากนี้ คลาส time ทำหน้าที่หน่วงเวลาในหลายรูปแบบการใช้งานดังนี้

ตัวอย่างการใช้คำสั่งเพื่อหน่วงเวลา 2 วินาที สามารถเขียนได้ดังนี้


    time.sleep(2)

หรือ


    time.sleep_ms( 2000 )

หรือ


    time.sleep_us( 20000000 )

กลุ่มคำสั่งที่ทำหน้าที่อ่านค่าเวลาปัจจุบันในหน่วยของมิลลิวินาที ไมโครวินาที หรือไซเคิลของไมโครคอนโ?รลเลอร์ เป็นตามเมธอดต่อไปนี้

และสามารถทำการบวกค่าของค่าเวลาและค่าความต่างด้งยเมธอด add และ diff ตามรูปแบบการใช้งานต่อไปนี้

คลาส network

เมื่อได้เข้าใจการใช้งานคลาสเกี่ยวกับ RTC และ time เรียบร้อย สิ่งที่จะต้อทำความรู้จักต่อไปคือการเชื่อมต่อเครือข่ายไร้สายเพื่ออ่านค่าเวลาจากเครื่องให้บริการเวลาในอินเทอร์เน็ตมาเก็บใน RTC เพื่อให้ไมโครคอนโทรลเลอร์ esp8266 ได้มีค่าเวลาตรงกับเวลาโลก

การใช้เครือข่ายอินเทอร์เน็ตเป็นจุดแข็งแกร่งของไมโครคอนโทรลเลอร์ตัวนี้เนื่องจากมีโมดูลเชื่อมต่อเครือข่ายร้ายมาให้อยู่ในตัว และสามารถเข้าถึงการทำงานได้ผ่านทางคลาส networkดังนี้

จากรายการคุณสมบัติของคลาส network ข้างต้นจะพบว่าไมโครคอนโทรลเลอร์ esp8266 รองรับการทำงานเป็น STA_IFหรือเป็นลูกข่ายในเครือข่ายไร้สาย และเป็น AP_IF สำหรับทำตัวเองเป็น AP หรือ Access Point ให้อุปกรณ์อื่นเข้าถึงตัวเอง ทำให้สามารถประยุกต์ให้เป็นผู้ให้บริการเว็บ หรือกระจาย IP Address ได้ โดยรองรับการเชื่อมต่อตามมาตรฐาน IEEE 802.11b, IEEE 802.11g และ IEEE 802.11n ที่ทำงานบนความถี่ 2.4GHz ด้วยอัตราความเร็ว 11Mbps, 36-54Mbps และ 300-450Mbps ตามลำดับ

นอกจากโหมดทำงานทั้ง 2 โหมดแล้วจะพบว่า มีโหมดการทำงานทั้งแบบ Open, WEP, WPA_PSK และ WPA2_PSK ทำให้รองรับการสื่อสารไร้สายทั้งแบบเปิดและมีการเข้ารหัสในแบบ WEP, WPA_PSK และ WPA2_PSK

ตัวอย่างการเปิดให้เป็น AP เขียนดังนี้


    import network
    import time
    if_ap = network.WLAN( network.AP_IF )
    while (if_ap.active() == False):
       if_ap.active(True)
       time.sleep(0.3)
      print(".")
    print(if_ap.ifconfig())
    if_ap.active(False)

ผลลัพธ์จากตัวอย่างการเป็น AP เป็นดังนี้


    ('192.168.4.1', '255.255.255.0', '192.168.4.1', '208.67.222.222')

จากตัวอย่างจะได้ว่า การเลือกโหมดทำงานใช้เมธอกด WLAN() ที่ระบุโหมดเป็น network.AP_IF และตรวจสอบสถานะการทำงานของโหมดด้วยคำสั่ง active() ที่เป็น False เมื่อยังไม่พร้อมเป็น AP โหมด และเป็น True เมื่อพร้อมทำงาน ส่วนคำสั่ง ifconfig() ใช้สำหรับอ่านรายละเอียดเกี่ยวค่า IP ของไมโครคอนโทรลเลอร์ โดยข้อมูลที่ได้จะเ)น tuple จำนวน 4 สมาชิก สำหรับเป็นตัวแทนของค่า ip-address ของชิพ, netmask, gateway และ DNS

การเปิดหรือปิดการทำงานของโมดูลไร้สายจะอาศัยคำสั่ง active() ที่กำหนดพารามิเตอร์เป็น True หรือ False สำหรับเปิดและปิดการทำงานของโมดูลไร้สาย

ตัวอย่างในกรณีที่ต้องการกำหนดชื่อ AP และรหัสผ่านในการเข้าใช้งานสามารถทำได้ดังนี้


    import network
    import time

    ssid = 'ETT8266_AP'
    password = '123456789'

    if_ap = network.WLAN( network.AP_IF )
    if_ap.active(True)
    if_ap.config(essid=ssid, password=password)

    while (if_ap.active() == False):
        if_ap.active(True)
        time.sleep(0.3)
        print(".")
    print(if_ap.ifconfig())
    if_ap.active(False)

ส่วนการทำงานในโหมด STA หรือเป็นลูกข่ายของเครือข่ายไร้สายที่มีอยู่แล้ว ผู้ใช้งานต้องทรายชื่อของ AP และรหัสในการเข้าใช้จึงจะทำการเชื่อมต่อได้ดังตัวอย่างโปรแกรมต่อไปนี้


    import network
    import time

    ssid = 'ETT8266_AP'
    password = '123456789'

    if_sta = network.WLAN(network.STA_IF)
    if (if_sta.active() == False):
        if_sta.active(True)
        if_sta.connect(ssid, password)
        while not if_sta.isconnected():
            time.sleep(0.3)
            print(".")
        print("network configuration:\n{}".format(if_sta.ifconfig()))
    if_sta.active(False)

จากตัวอย่างจะพบว่ามีความแตกต่างจากการเป็น AP ตรงที่ต้องระบุโหมดเป็น network.STA_IF และต้องเรียกใช้เมธอด connect() ที่ระบุชื่อของ AP ที่จะขอใช้งาน และรหัสผ่านสำหรับการเข้าใช้งาน

หมายเหตุ

ในการใช้งานตัวอย่างโปรแกรมในโหมด STA_IF จะต้องกำหนดชื่อของ AP และรหัสผ่านของการเข้าใช้ AP ให้ถูกต้อง ซึ่งผู้อ่านต้องตั้งให้ตรงกับการใช้งานของตนเอง

คลาส NTP

หลังจากที่สามารถเข้าใช้เครือข่ายอินเทอร์เน็ตได้จากการเรียกใช้คลาส network ในโหมด STA_IF ทำให้ไมโครคอนโทรลเลอร์ esp8266 นั้นเรียกใช้บริการอินเทอร์ได้ตามหลักการเขียนโปรแกรมสื่อสารอินเทอร์เตภายใต่โพรโทคอลที่กำหนดไว้ อย่างไรก็ดี ไมฑครไพธอนได้เตรียมคลาสสำหรับใช้งานให้อยู่หลากหลายบริการ และที่ใช้งานในบทความนี้คือบริการ NTP หรือ Network Time Protocol เพื่อร้องขอค่าวันและเวลาปัจจุบันมาจากเครื่องให้บริการทำให้อุปกรณ์ได้ค่าที่ถูกต้องและมีความแท่นยำใกล้เคียงเวลาจริงมากที่สุด

การใช้ NTP ต้องดำเนินการผ่านคล่า ntptime ที่มีคุณสมบัติและเมธอดให้ใช้งานดังนี้

ตัวอย่างโปรแกรมสำหรับแสดงค่าวันที่และเวลา โดยทำการอัพเดต RTC เป็นดังนี้


    import network
    import time
    import ntptime
    from machine import RTC

    ssid = 'ETT8266_AP'
    password = '123456789'

    if_sta = network.WLAN(network.STA_IF)
    if (if_sta.active() == False):
        if_sta.active(True)
        if_sta.connect(ssid, password)
        while not if_sta.isconnected():
            time.sleep(0.3)
            print(".")
        print("network configuration:\n{}".format(if_sta.ifconfig()))
        print("Before sync. : {}".format(time.localtime()))
        rtc = RTC()
        ntptime.settime()
        print("After : {}".format(time.localtime()))
    if_sta.active(False)

จากตัวอย่างจะทำให้ผู้อ่านสามารถสร้างระบบที่มีค่าวันที่และเวลาตรงตามเวลาของโลกทุกครั้งที่สามารถเข้าสู่เครือข่ายอินเทอร์ แต่สำหรับในบางกรณีที่ไม่สามารถเข้าถึงเครือข่ายได้ตลอดเวลา แต่ระบบที่ออกแบบนั้นต้องการใช้ค่าวันที่และเวลาที่ถูกต้องแม่นยำแม้ในช่วงที่ไม่มีแรงดันไฟฟ้าเลี้ยงบอร์ดไมโครคอนโทรลเลอร์ ด้วยเหตุนี้จึงต้องมรโมดูล RTC ภายนอกผนวกเข้ามา ซึ่งบนบอร์ด ET-ESP8266-RS485 มีโมดูล RTC พร้อมแบตเตอร์รีสำรองไฟเพื่อให้โมดูลทำงานได้ตลอดเวลาตราบที่แบตเตอร์รียังคงมีไฟเลี้ยงโมดูล ดังนั้น ในหัวข้อถัดไปจะเป็นเรื่องของการใช้งานบัส I2C ซึ่งเป็นบัสสื่อสารระหว่างไมโครคอนโทรลเลอร์กับโมดูล RTC และตามด้วยการทำความเข้าใจการทำงานของไอซี DS3231 อันเป็นไอซีโมดูล RTC บนบอร์ด ET-ESP8266-RS485 เป็นลำดับสุดท้ายของบทความนี้

บัส I2C

บัส I2C หรือ IIC มาจากคำว่า Inter-Integrated Circuit (ที่มา https://en.wikipedia.org/wiki/I2C) เป็นระบบเชื่อมต่อระหว่างอุปกรณ์แบบอนุกรมที่คิดค้นโดยบริษัท Phillips Semiconductors เมื่อปี ค.ศ. 1982 โดยการสื่อสารในระบบนี้อาศัยการสื่อสารผ่านสายที่ทำหน้าที่เป็น SCL สำหรับเป็นสายสัญญาณนาฬิการะหว่างอุปกรณ์และ SDA สำหรับสายสัญญาณสำหรับรับ/ส่งข้อมูลระหว่างกันครั้งละ 1 บิต ทำให้สามารถส่งข้อมูลระหว่างกันได้ด้วยสายสัญญาณเพียง 2 เส้น

ระบบสื่อสารของ I2C มีพัฒนาการต่อเนื่องมาตั้งแต่ ค.ศ. 1981 จนถึงปี 2021 ซึ่งเป็นรุ่นที่ 7 และได้ปรับเปลี่ยนรายละเอียดการทำงาน พร้อมทั้งเปลี่ยนคำเรียกจาก “master/slave” มาเป็น “controller/target” เพื่อลดทอนความแบ่งแยกชนชั้น

โหมดการทำงานของ I2C มีทั้งสิ้น 6 โหมดดังนี้

การสื่อสารในระบบบัสได้ใช้หลักการกำหนดค่าตำแหน่งของอุปกรณ์เป็นตัวระบุตัวตนของการสื่อสาร ซึ่งอุปกณณ์จะมีค่าตำแหน่งเป็นค่าตัวเลขฐาน 2 จำนวน 7 หลัก หรือ 7 บิต และการทำงานของฝั่งควบคุมการสื่อสารและอุปกรณ์ปลายทางมีการทำงานจำแนกได้ 4 แบบ คือ

จากหลักการนี้ทำให้การเชื่อมต่ออุปกรณ์เข้ากับไมโครคอนโทรลเลอร์ผ่านบัส I2C จึงสามารถเชื่อมต่อได้หลายอุปกรณ์ด้วยการจำแนกอุปกรณ์แต่ละตัวตามค่าตำแหน่งของอุปกรณ์นั้น เช่น โมดูล RTC บนบอร์ด ET-ESP8266-RS485 นั้นเป็นชิพ DS3231 ซึ่งถูกกำหนดตำแหน่งบนบัสไว้ที่ 104 เป็นต้น

คลาส I2C ของไมโครไพธอนเป็นคลาสย่อยของคลาส machine ที่ถูกออกแบบมาเพื่อใช้สำหรับดำเนินการสื่อสารบนระบบบัส I2C โดยมีรายละเอียดของคุณสมบัติและเมธอดดังนี้

การสร้างวัตถุสำหรับเป็นตัวทำงาน I2C จะต้องสร้างดังรูปแบบต่อไปนี้


    import machine
    วัตถุ = machine.I2C( sda=ขาSDA, scl=ขาSCL, freq=ความถี่ในการสื่อสาร )

โดย ESP8266 กำหนดไว้ให้ขา GPIO4 ทำหน้าที่ SCA และ GPIO5 ทำหน้าที่ SDL หรือกรณีที่ต้องการใช้ขาอื่นสามารถทำได้โดยการใช้ SoftI2C ตามรูปแบบต่อไปนี้


    วัตถุ = machine.SoftI2C( 
            sda=ขาSDA, 
            scl=ขาSCL, 
            freq=ความถี่ในการสื่อสาร, 
            timeout=ค่าไทม์เอาต์ 
    )

การกำหนดขาต้องกำหนดจากคลาส Pin ซึ่งเป็นคลาสย่อยของ machine ดังนี้


    ขา = machine.Pin( หมายเลขGPIO )

เมธอดหรือฟังก์ชันของ I2C มีดังนี้

หมายเหตุ

memaddr เป็นค่าตัวเลขที่ตัวควบคุมกับอุปกรณ์ปลายทางกำหนดร่วมกันว่า ตำแหน่งใดมีหน้าที่ใด ดังนั้น ต้องศึกษารายละเอียดของไอซีเพิ่มเติม

DS3231

ไอซี DS3231 เป็นไอซีที่ทำหน้าที่เป็น RTC ผ่านทางบัส I2C มีค่าตำแหน่งของอุปกรณ์อยู่ที่ 104 โดยการเขียนโปรแกรมเพื่อใช้งานภาษาไพธอนของไมโครไพธอนนั้นมีตัวอย่างการเขียนโปรแกรมหลายโมดูล แต่เพื่อความสะดวกในการใช้งานและการนำไปใช้ต่อภายใต้ข้อตกลงการใช้โค้ดแบบ MIT ทางผู้เขียนจึงเลือกใช้ ds3231_port ที่พัฒนาโดย Peter Hinch และสามารถเข้าไปดาวน์โหลดมาใช้งานได้จาก github

ใน github จะพบโค้ดโปรแกรม ds3231_port.py ที่เป็นไลบรารีสำหรับใช้งานในตัวอย่างโปรแกรม ดังนั้น ให้ผู้อ่านทำการดาวน์โหลดโค้ดมาไว้ในเครื่องและใช้ Thonny อัพโหลดไฟล์ ds3231_port.py ไปเก็บไว้ในชิพ esp8266 เพื่อจะได้ถูกเรียกใช้ต่อไป

ณ ตอนที่ผู้เขียนได้เขียนบทความตัวโค้ดโปรแกรมของ ds3231_port.py เป็นดังนี้


# ds3231_port.py Portable driver for DS3231 precison real time clock.
# Adapted from WiPy driver at https://github.com/scudderfish/uDS3231

# Author: Peter Hinch
# Copyright Peter Hinch 2018 Released under the MIT license.

import utime
import machine
import sys
DS3231_I2C_ADDR = 104

try:
    rtc = machine.RTC()
except:
    print('Warning: machine module does not support the RTC.')
    rtc = None

def bcd2dec(bcd):
    return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f))

def dec2bcd(dec):
    tens, units = divmod(dec, 10)
    return (tens << 4) + units

def tobytes(num):
    return num.to_bytes(1, 'little')

class DS3231:
    def __init__(self, i2c):
        self.ds3231 = i2c
        self.timebuf = bytearray(7)
        if DS3231_I2C_ADDR not in self.ds3231.scan():
            raise RuntimeError("DS3231 not found on I2C bus at %d" % DS3231_I2C_ADDR)

    def get_time(self, set_rtc=False):
        if set_rtc:
            self.await_transition()  # For accuracy set RTC immediately after a seconds transition
        else:
            self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf) # don't wait
        return self.convert(set_rtc)

    def convert(self, set_rtc=False):  # Return a tuple in localtime() format (less yday)
        data = self.timebuf
        ss = bcd2dec(data[0])
        mm = bcd2dec(data[1])
        if data[2] & 0x40:
            hh = bcd2dec(data[2] & 0x1f)
            if data[2] & 0x20:
                hh += 12
        else:
            hh = bcd2dec(data[2])
        wday = data[3]
        DD = bcd2dec(data[4])
        MM = bcd2dec(data[5] & 0x1f)
        YY = bcd2dec(data[6])
        if data[5] & 0x80:
            YY += 2000
        else:
            YY += 1900
        # Time from DS3231 in time.localtime() format (less yday)
        result = YY, MM, DD, hh, mm, ss, wday -1, 0
        if set_rtc:
            if rtc is None:
                # Best we can do is to set local time
                secs = utime.mktime(result)
                utime.localtime(secs)
            else:
                rtc.datetime((YY, MM, DD, wday, hh, mm, ss, 0))
        return result

    def save_time(self):
        (YY, MM, mday, hh, mm, ss, wday, yday) = utime.localtime()  # Based on RTC
        self.ds3231.writeto_mem(DS3231_I2C_ADDR, 0, tobytes(dec2bcd(ss)))
        self.ds3231.writeto_mem(DS3231_I2C_ADDR, 1, tobytes(dec2bcd(mm)))
        self.ds3231.writeto_mem(DS3231_I2C_ADDR, 2, tobytes(dec2bcd(hh)))  # Sets to 24hr mode
        self.ds3231.writeto_mem(DS3231_I2C_ADDR, 3, tobytes(dec2bcd(wday + 1)))  # 1 == Monday, 7 == Sunday
        self.ds3231.writeto_mem(DS3231_I2C_ADDR, 4, tobytes(dec2bcd(mday)))  # Day of month
        if YY >= 2000:
            self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM) | 0b10000000))  # Century bit
            self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-2000)))
        else:
            self.ds3231.writeto_mem(DS3231_I2C_ADDR, 5, tobytes(dec2bcd(MM)))
            self.ds3231.writeto_mem(DS3231_I2C_ADDR, 6, tobytes(dec2bcd(YY-1900)))

    # Wait until DS3231 seconds value changes before reading and returning data
    def await_transition(self):
        self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
        ss = self.timebuf[0]
        while ss == self.timebuf[0]:
            self.ds3231.readfrom_mem_into(DS3231_I2C_ADDR, 0, self.timebuf)
        return self.timebuf

    # Test hardware RTC against DS3231. Default runtime 10 min. Return amount
    # by which DS3231 clock leads RTC in PPM or seconds per year.
    # Precision is achieved by starting and ending the measurement on DS3231
    # one-seond boundaries and using ticks_ms() to time the RTC.
    # For a 10 minute measurement +-1ms corresponds to 1.7ppm or 53s/yr. Longer
    # runtimes improve this, but the DS3231 is "only" good for +-2ppm over 0-40C.
    def rtc_test(self, runtime=600, ppm=False, verbose=True):
        if rtc is None:
            raise RuntimeError('machine.RTC does not exist')
        verbose and print('Waiting {} minutes for result'.format(runtime//60))
        factor = 1_000_000 if ppm else 114_155_200  # seconds per year

        self.await_transition()  # Start on transition of DS3231. Record time in .timebuf
        t = utime.ticks_ms()  # Get system time now
        ss = rtc.datetime()[6]  # Seconds from system RTC
        while ss == rtc.datetime()[6]:
            pass
        ds = utime.ticks_diff(utime.ticks_ms(), t)  # ms to transition of RTC
        ds3231_start = utime.mktime(self.convert())  # Time when transition occurred
        t = rtc.datetime()
        rtc_start = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0))  # y m d h m s wday 0

        utime.sleep(runtime)  # Wait a while (precision doesn't matter)

        self.await_transition()  # of DS3231 and record the time
        t = utime.ticks_ms()  # and get system time now
        ss = rtc.datetime()[6]  # Seconds from system RTC
        while ss == rtc.datetime()[6]:
            pass
        de = utime.ticks_diff(utime.ticks_ms(), t)  # ms to transition of RTC
        ds3231_end = utime.mktime(self.convert())  # Time when transition occurred
        t = rtc.datetime()
        rtc_end = utime.mktime((t[0], t[1], t[2], t[4], t[5], t[6], t[3] - 1, 0))  # y m d h m s wday 0

        d_rtc = 1000 * (rtc_end - rtc_start) + de - ds  # ms recorded by RTC
        d_ds3231 = 1000 * (ds3231_end - ds3231_start)  # ms recorded by DS3231
        ratio = (d_ds3231 - d_rtc) / d_ds3231
        ppm = ratio * 1_000_000
        verbose and print('DS3231 leads RTC by {:4.1f}ppm {:4.1f}mins/yr'.format(ppm, ppm*1.903))
        return ratio * factor


    def _twos_complement(self, input_value: int, num_bits: int) -> int:
        mask = 2 ** (num_bits - 1)
        return -(input_value & mask) + (input_value & ~mask)


    def get_temperature(self):
        t = self.ds3231.readfrom_mem(DS3231_I2C_ADDR, 0x11, 2)
        i = t[0] << 8 | t[1]
        return self._twos_complement(i >> 6, 10) * 0.25

วงจรสำหรับภาค RTC บนบอร์ด ET-ESP8266-RS485 เป็นดังภาพที่ 2 ซึ่งจะพบว่าบนบอร์ดมีวงจรสำหรับทำ Pull-up เพื่อรักษาสภาพสัญญาณของ SDA และ SCL ทำให้ไม่มีปัญหาเรื่องสัญญาณหายระหว่างการสื่อสาร และมีการออกแบบให้รองรับกับการใช้งานกับโมดูลที่ใช้แรงดันไฟฟ้ากระแสตรง 3V3 และ 5V จึงช่วยลดภาระในการออกแบบวงจรเมื่อต้องเชื่อมต่อกับโมดูลที่มีความแตกต่างเรื่องของแรงดัน


ภาพที่ 2 วงจรเชื่อมต่อกับ DS3231 ของบอร์ด ET-ESP8266-RS485
ที่มา sch-et-esp8266-rs485-2-of-2.pdf

ตัวอย่างโปรแกรมสำหรับเชื่อมต่อกับ NTP เพื่ออัพเดตเวลาของ RTC หลังจากนั้นสั่ง save_time() เพื่อนำค่า RTC ไปตั้งค่าวันที่และเวลาของ DS3231 และทำการอ่านค่าเวลาจากชิพด้วยเมธอด get_time() เป็นดังนี้


    import network
    import time
    import ntptime
    import ds3231_port

    ssid = 'ETT8266_AP'
    password = '123456789'

    i2c = machine.I2C(sda=machine.Pin(4), scl=machine.Pin(5))
    ds3231 = ds3231_port.DS3231(i2c)

    if_sta = network.WLAN(network.STA_IF)
    if (if_sta.active() == False):
        if_sta.active(True)
        if_sta.connect(ssid, password)
        while not if_sta.isconnected():
            time.sleep(0.3)
            print(".")
        print("network configuration:\n{}".format(if_sta.ifconfig()))
        rtc = machine.RTC()
        ntptime.settime()
        print("Before : {}".format(ds3231.get_time()))
        ds3231.save_time()
        print("After : {}".format(ds3231.get_time()))
    if_sta.active(False)

สรุป

จากบทความนี้จะพบว่าผู้อ่านได้เรียนรู้การใช้งานไมโครคอนโทรลเลอร์ esp8266 ในด้านของคลาส machine.RTC, time, network, ntptime, machine.Pin และ machine.I2C พร้อมทั้งศึกษาการเขียนโปรแกรมเพื่อใช้งาน DS3231 บนบอร์ด ET-ESP8266-RS485 โดยทีมผู้เขียนหวังว่าจะเป็นแนวทางสำหรับการนำไปใช้ที่หลากหลายต่อไปในอนาคต

ส่วนบทความในตอนถัดไปเป็นการเปิด/ปิดด้วยภาครีเลย์บนบอร์ดผ่านการสั่งงานบนเว็บบราวเซอร์ด้วยการให้บอร์ดไมโครคอนโทรลเลอร์ทำงานเป็น AP (Access Point) ทำให้ผู้อ่านได้เข้าใจหลักการทำงานของการให้บริการแก่อุปกรณ์ที่เชื่อมต่อมายัง ESP8266 และการสร้างเว็บเพื่อตอบสนองกับอุปกรณ์ที่เชื่อมต่อผ่านทางบราวเซอร์อันเป็นพื้นซานสำหรับการทำระบบ IoT (Internet-of-Things) ต่อไป

ตอนที่ 1 :: ตอนที่ 2 :: ตอนที่ 3

--ไม่สามารถเข้าถึงฐานข้อมูลได้--