บทความนี้แนะนำ TFT LCD แบบ IPS ขนาด 0.96″ ที่มีความละเอียด 80×160 จุด ให้สี RGB565 หรือ 16 บิต โดยควบคุมการทำงานของโมดูล LCD ด้วยชิพ ST7735s ผ่านทางบัส SPI โดยตัวอย่างการใช้งานใช้กับ TTGO T8 ESP32 ที่เรียกใช้ไลบรารี ST7735 ด้วยภาษาไพธอน

ภาพที่ 1

แนะนำ TFT LCD

จอ TFT LCD รุ่นนี้มีขนาดเล็กเพียง 0.96″ แต่ให้ความละเอียดการแสดงผลเป็น 80×160 จุด ให้แสงเป็นแบบ IPS ทำให้สามารถมองได้จากหลายมุมมอง และแสดงผลสีได้ 16 บิต ตามคุณลักษณะของสีแบบ RGB565 จึงสามารถแสดงสีได้ 216 หรือ 65,536 สี ทำงานที่แรงดัน 2.5VDC-4.8VDC

ภาพที่ 2 ด้านหน้า
ภาพที่ 2 ด้านหลัง
ภาพที่ 4 บล็อกไดอะแกรมของ ST7735s
ที่มา
ภาพที่ 5 ขาเชื่อมต่อ

ไอซีควบคุมการแสดงผลของจอแอลซีดีเป็นชิพ ST7735s ที่มีบล็อกไดอะแกรมภายในดังภาพที่ 4 และมีชื่อรุ่นที่ใช้เรียกในการเขียนโปรแกรมว่า GRENTAB80x160 ทำงานผ่านการสั่งงานผ่านบัส SPI มีพื้นที่แสดงผล (ความกว้างและความสูงของจอแสดงผล) เป็น 0.108 x 0.21696 เซ็นติเมตร มีขาเชื่อมต่อภายนอก 8 ขา ดังภาพที่ 5 ซึ่งใช้ 4 ขาในการควบคุมการทำงานทำงาน 4 ขา คือ SCL (SCK), SDA (MOSI), DC และ CS (ดังภาพที่ 6 และ 7) และรายละเอียดของทั้ง 8 ขาเป็นดังนี้

  1. GND สำหรับต่อกับกราวนด์ของวงจรทำงาน
  2. VCC สำหรับต่อไฟฟ้ากระแสตรงแรงดัน 3.3V (3V3)
  3. SCL สำหรับต่อกับขา SCK ของบัส SPI ใช้เป็นขาสัญญาณนาฬิกาในการสื่อสารระหว่างไมโครคอนโทรลเลอร์กับโมดูลแสดงผล
  4. SDA สำหรับต่อกับขา MOSI ของบัส SPI ใช้เป็นขาส่งข้อมูลจากไมโครคอนโทรลเลอร์ไปยัง ST7735s
  5. RES สำหรับต่อสัญญาณ reset เพื่อให้โมดูลแสดงผลรีเซ็ตตัวเอง
  6. DC สำหรับใช้เป็นตัวบอกประเภทของข้อมูลที่กำลังส่งไปใน SDA ซึ่งเป็น 1 หมายถึง ข้อมูลสำหรับแสดงผลหรือพารามิเตอร์ และเป็น 0 หมายถึง ข้อมูลคำสั่ง
  7. CS สำหรับต่อกับขาควบคุมการเลือกให้โมดูลทำงานหรือไม่ทำงาน
  8. BLK สำหรับต่อกับวงจรแสงหลัง (back light)
ภาพที่ 6 4-line Serial Interface Timing (ที่มา)
ภาพที่ 7 ภาพแสดงคุณลักษณะของ 4-line Serial Interface (ที่มา)

เชื่อมต่อกับ TTGO T8 ESP32

การเชื่อมต่อระหว่างโมดูลแสดงผลกับบอร์ด TTGO T8 ESP32 สามารถทำได้ตามตารางต่อไปนี้

TFT LCD ModuleTTGO T8 ESP32
GNDGND
VCC3V3
SCL14
SDA13
RSTRST
DC2
CS15
BLK3V3
ภาพที่ 8
ภาพที่ 9
ภาพที่ 10
ภาพที่ 11
ภาพที่ 12
ภาพที่ 13

ไลบรารีและตัวอย่าง

ไลบรารีที่พวกเราเลือกใช้เป็นไลบรารีที่เขียนโดย Billy Cheung (เข้าถึงเมื่อ 2020-10-13) ที่เผยแพร่บน github ซึ่งเป็นไลบรารีที่ปรับปรุงมาจาก Guy Caver จนรองรับ ST7735s โดยไลบรารีที่ต้องใช้งานประกอบด้วย st7735.py และ sysfont.py โดยให้อัพโหลดทั้ง 2 ไฟล์เข้าไปเก็บใน TTGO T8 ESP32

ตัวอย่างโปรแกรม code9-1 เป็นการปรับปรุงตัวอย่าง test80x160.py ให้ใช้กับบอร์ด TTGO T8 ESP32 และเลือกใช้ความถี่สัญญาณนาฬิกาของการทำงานเป็น 240MHz พร้อมกำหนดความเร็วในการสื่อสารกับบัส SPI เป็น 20MHz เพื่อให้ได้ความเร็วสูงสุดเท่าที่จะทำได้

# code9-1
from st7735 import TFT
from sysfont import sysfont
from machine import SPI,Pin
import machine as mc
import time
import math
mc.freq(240000000)
spi = SPI(1, baudrate=20000000,
          sck=Pin(14), mosi=Pin(13),
          polarity=0, phase=0)
# dc, rst, cs
tft=TFT(spi,2,None,15)
tft.init_7735(tft.GREENTAB80x160)

def testlines(color):
    tft.fill(TFT.BLACK)
    for x in range(0, tft.size()[0], 6):
        tft.line((0,0),(x, tft.size()[1] - 1), color)
    for y in range(0, tft.size()[1], 6):
        tft.line((0,0),(tft.size()[0] - 1, y), color)

    tft.fill(TFT.BLACK)
    for x in range(0, tft.size()[0], 6):
        tft.line((tft.size()[0] - 1, 0), (x, tft.size()[1] - 1), color)
    for y in range(0, tft.size()[1], 6):
        tft.line((tft.size()[0] - 1, 0), (0, y), color)

    tft.fill(TFT.BLACK)
    for x in range(0, tft.size()[0], 6):
        tft.line((0, tft.size()[1] - 1), (x, 0), color)
    for y in range(0, tft.size()[1], 6):
        tft.line((0, tft.size()[1] - 1), (tft.size()[0] - 1,y), color)

    tft.fill(TFT.BLACK)
    for x in range(0, tft.size()[0], 6):
        tft.line((tft.size()[0] - 1, tft.size()[1] - 1), (x, 0), color)
    for y in range(0, tft.size()[1], 6):
        tft.line((tft.size()[0] - 1, tft.size()[1] - 1), (0, y), color)

def testfastlines(color1, color2):
    tft.fill(TFT.BLACK)
    for y in range(0, tft.size()[1], 5):
        tft.hline((0,y), tft.size()[0], color1)
    for x in range(0, tft.size()[0], 5):
        tft.vline((x,0), tft.size()[1], color2)

def testdrawrects(color):
    tft.fill(TFT.BLACK);
    for x in range(0,tft.size()[0],6):
        tft.rect((tft.size()[0]//2 - x//2, tft.size()[1]//2 - x/2), (x, x), color)

def testfillrects(color1, color2):
    tft.fill(TFT.BLACK);
    for x in range(tft.size()[0],0,-6):
        tft.fillrect((tft.size()[0]//2 - x//2, tft.size()[1]//2 - x/2), (x, x), color1)
        tft.rect((tft.size()[0]//2 - x//2, tft.size()[1]//2 - x/2), (x, x), color2)


def testfillcircles(radius, color):
    for x in range(radius, tft.size()[0], radius * 2):
        for y in range(radius, tft.size()[1], radius * 2):
            tft.fillcircle((x, y), radius, color)

def testdrawcircles(radius, color):
    for x in range(0, tft.size()[0] + radius, radius * 2):
        for y in range(0, tft.size()[1] + radius, radius * 2):
            tft.circle((x, y), radius, color)

def testtriangles():
    tft.fill(TFT.BLACK);
    color = 0xF800
    w = tft.size()[0] // 2
    x = tft.size()[1] - 1
    y = 0
    z = tft.size()[0]
    for t in range(0, 15):
        tft.line((w, y), (y, x), color)
        tft.line((y, x), (z, x), color)
        tft.line((z, x), (w, y), color)
        x -= 4
        y += 4
        z -= 4
        color += 100

def testroundrects():
    tft.fill(TFT.BLACK);
    color = 100
    for t in range(5):
        x = 0
        y = 0
        w = tft.size()[0] - 2
        h = tft.size()[1] - 2
        for i in range(17):
            tft.rect((x, y), (w, h), color)
            x += 2
            y += 3
            w -= 4
            h -= 6
            color += 1100
        color += 100

def tftprinttest():
    tft.fill(TFT.BLACK);
    v = 30
    tft.text((0, v), "Hello World!", TFT.RED, sysfont, 1, nowrap=True)
    v += sysfont["Height"]
    tft.text((0, v), "Hello World!", TFT.YELLOW, sysfont, 2, nowrap=True)
    v += sysfont["Height"] * 2
    tft.text((0, v), "Hello World!", TFT.GREEN, sysfont, 3, nowrap=True)
    v += sysfont["Height"] * 3
    tft.text((0, v), str(1234.567), TFT.BLUE, sysfont, 4, nowrap=True)
    time.sleep_ms(1500)
    tft.fill(TFT.BLACK);
    v = 0
    tft.text((0, v), "Hello World!", TFT.RED, sysfont)
    v += sysfont["Height"]
    tft.text((0, v), str(math.pi), TFT.GREEN, sysfont)
    v += sysfont["Height"]
    tft.text((0, v), " Want pi?", TFT.GREEN, sysfont)
    v += sysfont["Height"] * 2
    tft.text((0, v), hex(8675309), TFT.GREEN, sysfont)
    v += sysfont["Height"]
    tft.text((0, v), " Print HEX!", TFT.GREEN, sysfont)
    v += sysfont["Height"] * 2
    tft.text((0, v), "Sketch has been", TFT.WHITE, sysfont)
    v += sysfont["Height"]
    tft.text((0, v), "running for: ", TFT.WHITE, sysfont)
    v += sysfont["Height"]
    tft.text((0, v), str(time.ticks_ms() / 1000), TFT.PURPLE, sysfont)
    v += sysfont["Height"]
    tft.text((0, v), " seconds.", TFT.WHITE, sysfont)

def testMain() :
  while True :
    tft.fill(TFT.BLACK)

    while True :
        tft.fill(TFT.BLACK)
        tft.text((0, 0), "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur adipiscing ante sed nibh tincidunt feugiat. Maecenas enim massa, fringilla sed malesuada et, malesuada sit amet turpis. Sed porttitor neque ut ante pretium vitae malesuada nunc bibendum. Nullam aliquet ultrices massa eu hendrerit. Ut sed nisi lorem. In vestibulum purus a tortor imperdiet posuere. ", TFT.WHITE, sysfont, 1)
        time.sleep_ms(1000)

        tftprinttest()
        time.sleep_ms(2000)

        testlines(TFT.YELLOW)
        time.sleep_ms(500)

        testfastlines(TFT.RED, TFT.BLUE)
        time.sleep_ms(500)

        testdrawrects(TFT.GREEN)
        time.sleep_ms(500)

        testfillrects(TFT.YELLOW, TFT.PURPLE)
        time.sleep_ms(500)

        tft.fill(TFT.BLACK)
        testfillcircles(10, TFT.BLUE)
        testdrawcircles(10, TFT.WHITE)
        time.sleep_ms(500)

        testroundrects()
        time.sleep_ms(500)

        testtriangles()
        time.sleep_ms(500)

def testRotation () :
    i = tft.rotate
    for x in range (0, 4) :
        tft.rotation(i)
        tft.fill(TFT.BLACK)
        tft.rect((0,0),tft.size(), TFT.WHITE)
        tft.rect((2,2),(tft.size()[0]-4, tft.size()[1]-4), TFT.YELLOW)
        tft.rect((4,4),(tft.size()[0]-8, tft.size()[1]-8), TFT.RED)
        tft.fillrect((0,0),(8,8),TFT.GREEN)
        tft.text((20, 20), "TEST", TFT.RED, sysfont, 2)
        print ('{} {}x{} {}:{}'.format(i, tft.size()[0],tft.size()[1] , tft.offset()[0],tft.offset()[1]))
        time.sleep_ms(3000)
        i = i + 1 if i < 3 else 0
    tft.rotation(1)
testRotation()
testMain()

สรุป

จากบทความนี้จะพบว่าเราสามารถใช้การสื่อสารบัส SPI ผ่านฮาร์ดแวร์ของ ESP32 ด้วยภาษาไพธอนได้ โดยความเร็วในการทำงานอาจจะไม่เท่ากับการเขียนด้วยภาษา C/C++ ผ่านทาง Arduino Framework หรือ ESP-IDF แต่ได้ความสะดวกในการเขียนโค้ด และบริหารจัดการโค้ด และเช่นเดิมถ้าผู้อ่านสนใจ TFT LCD 0.96″ ISP 80×160 ตัวนี้ ทางเรายังพอมีเหลืออยู่ (อย่างที่ได้อธิบายไว้ก่อนหน้านี้ การทดลองมักต้องสั่งมาเผื่อ) สามารถติดต่อมาได้ หรือรอให้ทางเราทำส่วนของร้านค้าเสร็จเรียบร้อยค่อยสนับสนุนพวกเราเพื่อหาทุนรอนมาเขียนบทความกันต่อไปครับ

สุดท้ายนี้ขอให้สนุกกับการเขียนโปรแกรมครับ

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