[TH] ปรับแต่งไลบรารี st7735 blue-tab/red-tab 0.96 นิ้ว

บทความนี้เป็นการปรับปรุงไฟล์ไลบรารี st7735 สำหรับ Micropythonโดย Billy Cheung (เข้าถึงเมื่อ 2021-09-07) ที่เผยแพร่บน github ซึ่งเป็นไลบรารีที่ปรับปรุงมาจาก Guy Caver จนรองรับ ST7735s โดยไลบรารีที่ต้องใช้งานประกอบด้วย st7735.py และ sysfont.py Guy Carver ใช้งานกับ esp8266 และ esp32 เพื่อให้ความเร็วในการแสดงผลที่ดีขึ้นโดยอาศัยหลักการสร้างบัฟเฟอร์ของการแสดงผลสำหรับเก็บข้อมูลพิกเซลและเพิ่มเติมคำสั่งสำหรับส่งข้อมูลจากบัฟเฟอร์ไปยังโมดูล TFT ผ่านบัสแบบ SPI

ภาพที่ 1 บอร์ด dCore-miniML ที่ติดตั้งโมดูลแสดงผล ST7735 0.96 นิ้ว

ไลบรารีต้นฉบับ

จากไลบรารีต้นฉบับที่เขียนโดย Guy Carver เพื่อใช้กับ ST7735 ขนาด 1.8 นิ้ว และมีผู้คนปรับปรุงจนใช้งานได้กับ ST7735 ขยาด 0.96 นิ้วที่มีความละเอียดในการแสดงผล 80×160 พิกเซลดังที่เคยกล่าวถึงในบทความก่อนหน้านี้ทั้งการใช้กับ ESP32 และ ESP8266 นั้น ซึ่งโค้ดต้นฉบับเป็นดังนี้

#Added 1.44 inch 128x128 and 0.96 inch 80x160 SST7735 to the
# original 1.8 inch drivers developed by Guy Carver
# fixed the issue of different start Row and start col addresses of different OLED types
# fixed the RGB special equence in 80x160 ST7735B

import machine
import time
from math import sqrt

#TFTRotations and TFTRGB are bits to set
# on MADCTL to control display rotation/color layout
#Looking at display with pins on top.
#00 = upper left printing right
#10 = does nothing (MADCTL_ML)
#20 = upper left printing down (backwards) (Vertical flip)
#40 = upper right printing left (backwards) (X Flip)
#80 = lower left printing right (backwards) (Y Flip)
#04 = (MADCTL_MH)

#60 = 90 right rotation
#C0 = 180 right rotation
#A0 = 270 right rotation
TFTRotations = [0x00, 0x60, 0xC0, 0xA0]
TFTBGR = 0x08 # for 1.8 and 1.44 inch display
TFTRGB = 0x00

#@micropython.native
def clamp( aValue, aMin, aMax ) :
  return max(aMin, min(aMax, aValue))

#@micropython.native
def TFTColor( aR, aG, aB ) :
  '''Create a 16 bit rgb value from the given R,G,B from 0-255.
     This assumes rgb 565 layout and will be incorrect for bgr.'''
  return ((aR & 0xF8) << 8) | ((aG & 0xFC) << 3) | (aB >> 3)

#ScreenSize = (128, 160)

class TFT(object) :
  """ define different model of ST7735, circuit board color or types (tabcolor)."""
  GREENTAB        = 0x0 # 128x160 , start col 2, start row 1, rgb
  REDTAB          = 0x1 # 128x160 , start col 0, start row 0, rgb
  BLACKTAB        = 0x2 # 128x160 , start col 0, start row 0, bgr
  GREENTAB2       = 0x3 # 128x160 , start col 2, start row 1, bgr
  GREENTAB3       = 0x4 # 128x160 , start col 2, start row 3, rgb
  GREENTAB128x128 = 0x5 # 128x128 1.44 inches, bgr, start col 2
                              #         if rotation = 0, or 1, start row 1
                              #         if rotation = 2, or 3, start row 3
  GREENTAB80x160  = 0x6 # 80x160 0.96 inch, start col 26, start row 1, bgr, inverted
  REDTAB80x160    = 0x7 # 80x160 0.96 inch, start col 24, start row 0, rgb
  BLUETAB         = 0xB # 128x160 , start col 2, start row 1, rgb

  NOP = 0x0
  SWRESET = 0x01
  RDDID = 0x04
  RDDST = 0x09

  SLPIN  = 0x10
  SLPOUT  = 0x11
  PTLON  = 0x12
  NORON  = 0x13

  INVOFF = 0x20
  INVON = 0x21
  DISPOFF = 0x28
  DISPON = 0x29
  CASET = 0x2A
  RASET = 0x2B
  RAMWR = 0x2C
  RAMRD = 0x2E

  COLMOD = 0x3A
  MADCTL = 0x36

  FRMCTR1 = 0xB1
  FRMCTR2 = 0xB2
  FRMCTR3 = 0xB3
  INVCTR = 0xB4
  DISSET5 = 0xB6

  PWCTR1 = 0xC0
  PWCTR2 = 0xC1
  PWCTR3 = 0xC2
  PWCTR4 = 0xC3
  PWCTR5 = 0xC4
  VMCTR1 = 0xC5

  RDID1 = 0xDA
  RDID2 = 0xDB
  RDID3 = 0xDC
  RDID4 = 0xDD

  PWCTR6 = 0xFC

  GMCTRP1 = 0xE0
  GMCTRN1 = 0xE1

  BLACK = 0
  RED = TFTColor(0xFF, 0x00, 0x00)
  MAROON = TFTColor(0x80, 0x00, 0x00)
  GREEN = TFTColor(0x00, 0xFF, 0x00)
  FOREST = TFTColor(0x00, 0x80, 0x80)
  BLUE = TFTColor(0x00, 0x00, 0xFF)
  NAVY = TFTColor(0x00, 0x00, 0x80)
  CYAN = TFTColor(0x00, 0xFF, 0xFF)
  YELLOW = TFTColor(0xFF, 0xFF, 0x00)
  PURPLE = TFTColor(0xFF, 0x00, 0xFF)
  WHITE = TFTColor(0xFF, 0xFF, 0xFF)
  GRAY = TFTColor(0x80, 0x80, 0x80)

  @staticmethod
  def color( aR, aG, aB ) :
    '''Create a 565 rgb TFTColor value'''
    return TFTColor(aR, aG, aB)

  def __init__( self, spi, aDC, aReset=None, aCS=None) :
    """aLoc SPI pin location is either 1 for 'X' or 2 for 'Y'.
       aDC is the DC pin and aReset is the reset pin."""
    self.tabcolor = 0 # default
    self._size = (128,128)
    self.rotate = 2                    #Vertical with top toward pins.
    self._offset=(2,3)
    self._rgb = False                   #color order of rgb.
    self.dc  = machine.Pin(aDC, machine.Pin.OUT)

    if aReset == None :
        self.useReset = False
    else :
        self.useReset = True
        self.reset = machine.Pin(aReset, machine.Pin.OUT)
    if aCS == None :
        self.useCS = False
    else :
        self.useCS = True
        self.csPin = machine.Pin(aCS, machine.Pin.OUT)
    self.cs(1)
    self.spi = spi
    self.colorData = bytearray(2)
    self.windowLocData = bytearray(4)


  def cs (self, iologic) :
      if self.useCS :
          self.csPin(iologic)

  def size( self ) :
    return self._size

  def offset ( self ) :
    return self._offset

#   @micropython.native
  def on( self, aTF = True ) :
    '''Turn display on or off.'''
    self._writecommand(TFT.DISPON if aTF else TFT.DISPOFF)

#   @micropython.native
  def invertcolor( self, aBool ) :
    '''Invert the color data IE: Black = White.'''
    self._writecommand(TFT.INVON if aBool else TFT.INVOFF)

#   @micropython.native
  def rgb( self, aTF = True ) :
    '''True = rgb else bgr'''
    self._rgb = aTF
    self._setMADCTL()

#   @micropython.native
  def rotation( self, aRot ) :
    '''0 - 3. Starts vertical with top toward pins and rotates 90 deg
       clockwise each step.'''
    if (0 <= aRot < 4):
      rotchange = self.rotate ^ aRot
      self.rotate = aRot
      # If switching from vertical to horizontal (indicated by bit 0 changing).
      # swap screen size rows and columns and their start addresses offset

    if (rotchange & 1):
      self._size =(self._size[1], self._size[0])
      self._offset=(self._offset[1], self._offset[0])

    if self.tabcolor == self.GREENTAB128x128 :
      # special handling of 128x128 with different offsets during rotations
      if aRot == 0 :
        self._offset=(2,1)
      elif aRot == 1 :
        self._offset=(1,2)
      elif aRot == 2 :
        self._offset=(2,3)
      elif aRot == 3 :
        self._offset=(3,2)
    self._setMADCTL()

#  @micropython.native
  def pixel( self, aPos, aColor ) :
    '''Draw a pixel at the given position'''
    if 0 <= aPos[0] < self._size[0] and 0 <= aPos[1] < self._size[1]:
      self._setwindowpoint(aPos)
      self._pushcolor(aColor)

#   @micropython.native
  def text( self, aPos, aString, aColor, aFont, aSize = 1, nowrap = False ) :
    '''Draw a text at the given position.  If the string reaches the end of the
       display it is wrapped to aPos[0] on the next line.  aSize may be an integer
       which will size the font uniformly on w,h or a or any type that may be
       indexed with [0] or [1].'''

    if aFont == None:
      return

    #Make a size either from single value or 2 elements.
    if (type(aSize) == int) or (type(aSize) == float):
      wh = (aSize, aSize)
    else:
      wh = aSize

    px, py = aPos
    width = wh[0] * aFont["Width"] + 1
    for c in aString:
      self.char((px, py), c, aColor, aFont, wh)
      px += width
      #We check > rather than >= to let the right (blank) edge of the
      # character print off the right of the screen.
      if px + width > self._size[0]:
        if nowrap:
          break
        else:
          py += aFont["Height"] * wh[1] + 1
          px = aPos[0]

#   @micropython.native
  def char( self, aPos, aChar, aColor, aFont, aSizes ) :
    '''Draw a character at the given position using the given font and color.
       aSizes is a tuple with x, y as integer scales indicating the
       # of pixels to draw for each pixel in the character.'''

    if aFont == None:
      return

    startchar = aFont['Start']
    endchar = aFont['End']

    ci = ord(aChar)
    if (startchar <= ci <= endchar):
      fontw = aFont['Width']
      fonth = aFont['Height']
      ci = (ci - startchar) * fontw

      charA = aFont["Data"][ci:ci + fontw]
      px = aPos[0]
      if aSizes[0] <= 1 and aSizes[1] <= 1 :
        for c in charA :
          py = aPos[1]
          for r in range(fonth) :
            if c & 0x01 :
              self.pixel((px, py), aColor)
            py += 1
            c >>= 1
          px += 1
      else:
        for c in charA :
          py = aPos[1]
          for r in range(fonth) :
            if c & 0x01 :
              self.fillrect((px, py), aSizes, aColor)
            py += aSizes[1]
            c >>= 1
          px += aSizes[0]

#   @micropython.native
  def line( self, aStart, aEnd, aColor ) :
    '''Draws a line from aStart to aEnd in the given color.  Vertical or horizontal
       lines are forwarded to vline and hline.'''
    if aStart[0] == aEnd[0]:
      #Make sure we use the smallest y.
      pnt = aEnd if (aEnd[1] < aStart[1]) else aStart
      self.vline(pnt, abs(aEnd[1] - aStart[1]) + 1, aColor)
    elif aStart[1] == aEnd[1]:
      #Make sure we use the smallest x.
      pnt = aEnd if aEnd[0] < aStart[0] else aStart
      self.hline(pnt, abs(aEnd[0] - aStart[0]) + 1, aColor)
    else:
      px, py = aStart
      ex, ey = aEnd
      dx = ex - px
      dy = ey - py
      inx = 1 if dx > 0 else -1
      iny = 1 if dy > 0 else -1

      dx = abs(dx)
      dy = abs(dy)
      if (dx >= dy):
        dy <<= 1
        e = dy - dx
        dx <<= 1
        while (px != ex):
          self.pixel((px, py), aColor)
          if (e >= 0):
            py += iny
            e -= dx
          e += dy
          px += inx
      else:
        dx <<= 1
        e = dx - dy
        dy <<= 1
        while (py != ey):
          self.pixel((px, py), aColor)
          if (e >= 0):
            px += inx
            e -= dy
          e += dx
          py += iny

#   @micropython.native
  def vline( self, aStart, aLen, aColor ) :
    '''Draw a vertical line from aStart for aLen. aLen may be negative.'''
    start = (clamp(aStart[0], 0, self._size[0]), clamp(aStart[1], 0, self._size[1]))
    stop = (start[0], clamp(start[1] + aLen, 0, self._size[1]))
    #Make sure smallest y 1st.
    if (stop[1] < start[1]):
      start, stop = stop, start
    self._setwindowloc(start, stop)
    self._setColor(aColor)
    self._draw(aLen)

#   @micropython.native
  def hline( self, aStart, aLen, aColor ) :
    '''Draw a horizontal line from aStart for aLen. aLen may be negative.'''
    start = (clamp(aStart[0], 0, self._size[0]), clamp(aStart[1], 0, self._size[1]))
    stop = (clamp(start[0] + aLen, 0, self._size[0]), start[1])
    #Make sure smallest x 1st.
    if (stop[0] < start[0]):
      start, stop = stop, start
    self._setwindowloc(start, stop)
    self._setColor(aColor)
    self._draw(aLen)

#   @micropython.native
  def rect( self, aStart, aSize, aColor ) :
    '''Draw a hollow rectangle.  aStart is the smallest coordinate corner
       and aSize is a tuple indicating width, height.'''
    self.hline(aStart, aSize[0], aColor)
    self.hline((aStart[0], aStart[1] + aSize[1] - 1), aSize[0], aColor)
    self.vline(aStart, aSize[1], aColor)
    self.vline((aStart[0] + aSize[0] - 1, aStart[1]), aSize[1], aColor)

#   @micropython.native
  def fillrect( self, aStart, aSize, aColor ) :
    '''Draw a filled rectangle.  aStart is the smallest coordinate corner
       and aSize is a tuple indicating width, height.'''
    start = (clamp(aStart[0], 0, self._size[0]), clamp(aStart[1], 0, self._size[1]))
    end = (clamp(start[0] + aSize[0] - 1, 0, self._size[0]), clamp(start[1] + aSize[1] - 1, 0, self._size[1]))

    if (end[0] < start[0]):
      tmp = end[0]
      end = (start[0], end[1])
      start = (tmp, start[1])
    if (end[1] < start[1]):
      tmp = end[1]
      end = (end[0], start[1])
      start = (start[0], tmp)

    self._setwindowloc(start, end)
    numPixels = (end[0] - start[0] + 1) * (end[1] - start[1] + 1)
    self._setColor(aColor)
    self._draw(numPixels)

#   @micropython.native
  def circle( self, aPos, aRadius, aColor ) :
    '''Draw a hollow circle with the given radius and color with aPos as center.'''
    self.colorData[0] = aColor >> 8
    self.colorData[1] = aColor
    xend = int(0.7071 * aRadius) + 1
    rsq = aRadius * aRadius
    for x in range(xend) :
      y = int(sqrt(rsq - x * x))
      xp = aPos[0] + x
      yp = aPos[1] + y
      xn = aPos[0] - x
      yn = aPos[1] - y
      xyp = aPos[0] + y
      yxp = aPos[1] + x
      xyn = aPos[0] - y
      yxn = aPos[1] - x

      self._setwindowpoint((xp, yp))
      self._writedata(self.colorData)
      self._setwindowpoint((xp, yn))
      self._writedata(self.colorData)
      self._setwindowpoint((xn, yp))
      self._writedata(self.colorData)
      self._setwindowpoint((xn, yn))
      self._writedata(self.colorData)
      self._setwindowpoint((xyp, yxp))
      self._writedata(self.colorData)
      self._setwindowpoint((xyp, yxn))
      self._writedata(self.colorData)
      self._setwindowpoint((xyn, yxp))
      self._writedata(self.colorData)
      self._setwindowpoint((xyn, yxn))
      self._writedata(self.colorData)

#   @micropython.native
  def fillcircle( self, aPos, aRadius, aColor ) :
    '''Draw a filled circle with given radius and color with aPos as center'''
    rsq = aRadius * aRadius
    for x in range(aRadius) :
      y = int(sqrt(rsq - x * x))
      y0 = aPos[1] - y
      ey = y0 + y * 2
      y0 = clamp(y0, 0, self._size[1])
      ln = abs(ey - y0) + 1;

      self.vline((aPos[0] + x, y0), ln, aColor)
      self.vline((aPos[0] - x, y0), ln, aColor)

  def fill( self, aColor = BLACK ) :
    '''Fill screen with the given color.'''
    self.fillrect((0, 0), self._size, aColor)

  def image( self, x0, y0, x1, y1, data ) :
    self._setwindowloc((x0, y0), (x1, y1))
    self._writedata(data)

#   @micropython.native
  def _setColor( self, aColor ) :
    self.colorData[0] = aColor >> 8
    self.colorData[1] = aColor
    self.buf = bytes(self.colorData) * 32

#   @micropython.native
  def _draw( self, aPixels ) :
    '''Send given color to the device aPixels times.'''

    self.dc(1)
    self.cs(0)
    for i in range(aPixels//32):
      self.spi.write(self.buf)
    rest = (int(aPixels) % 32)
    if rest > 0:
        buf2 = bytes(self.colorData) * rest
        self.spi.write(buf2)
    self.cs(1)

#   @micropython.native
  def _setwindowpoint( self, aPos ) :
    '''Set a single point for drawing a color to.'''
    x = self._offset[0] + int(aPos[0])
    y = self._offset[1] + int(aPos[1])
    self._writecommand(TFT.CASET)            #Column address set.
    self.windowLocData[0] = self._offset[0]
    self.windowLocData[1] = x
    self.windowLocData[2] = self._offset[0]
    self.windowLocData[3] = x
    self._writedata(self.windowLocData)

    self._writecommand(TFT.RASET)            #Row address set.
    self.windowLocData[0] = self._offset[1]
    self.windowLocData[1] = y
    self.windowLocData[2] = self._offset[1]
    self.windowLocData[3] = y
    self._writedata(self.windowLocData)
    self._writecommand(TFT.RAMWR)            #Write to RAM.

#   @micropython.native
  def _setwindowloc( self, aPos0, aPos1 ) :
    '''Set a rectangular area for drawing a color to.'''
    self._writecommand(TFT.CASET)            #Column address set.
    self.windowLocData[0] = self._offset[0]
    self.windowLocData[1] = self._offset[0] + int(aPos0[0])
    self.windowLocData[2] = self._offset[0]
    self.windowLocData[3] = self._offset[0] + int(aPos1[0])
    self._writedata(self.windowLocData)

    self._writecommand(TFT.RASET)            #Row address set.
    self.windowLocData[0] = self._offset[1]
    self.windowLocData[1] = self._offset[1] + int(aPos0[1])
    self.windowLocData[2] = self._offset[1]
    self.windowLocData[3] = self._offset[1] + int(aPos1[1])
    self._writedata(self.windowLocData)

    self._writecommand(TFT.RAMWR)            #Write to RAM.

  #@micropython.native
  def _writecommand( self, aCommand ) :
    '''Write given command to the device.'''
    self.dc(0)
    self.cs(0)
    self.spi.write(bytearray([aCommand]))
    self.cs(1)

  #@micropython.native
  def _writedata( self, aData ) :
    '''Write given data to the device.  This may be
       either a single int or a bytearray of values.'''
    self.dc(1)
    self.cs(0)
    self.spi.write(aData)
    self.cs(1)

  #@micropython.native
  def _pushcolor( self, aColor ) :
    '''Push given color to the device.'''
    self.colorData[0] = aColor >> 8
    self.colorData[1] = aColor
    self._writedata(self.colorData)

  #@micropython.native
  def _setMADCTL( self ) :
    '''Set screen rotation and RGB/BGR format.'''
    self._writecommand(TFT.MADCTL)
    rgb = TFTRGB if self._rgb else TFTBGR
    self._writedata(bytearray([TFTRotations[self.rotate] | rgb]))

  #@micropython.native
  def _reset( self ) :
    '''Reset the device.'''
    self.dc(0)
    if self.useReset :
        self.reset(1)
        time.sleep_us(500)
        self.reset(0)
        time.sleep_us(500)
        self.reset(1)

    time.sleep_us(500)

  def init_7735 ( self, Tabcolor) :
    self.tabcolor = Tabcolor
    if self.tabcolor == self.BLUETAB :
        # Initialize blue tab version.
        self._size = (128, 160)
        self._offset = (2,1)
        self._rgb = True

        self._reset()
        self._writecommand(TFT.SWRESET)              #Software reset.
        time.sleep_us(50)
        self._writecommand(TFT.SLPOUT)               #out of sleep mode.
        time.sleep_us(500)

        data1 = bytearray(1)
        self._writecommand(TFT.COLMOD)               #Set color mode.
        data1[0] = 0x05                             #16 bit color.
        self._writedata(data1)
        time.sleep_us(10)

        data3 = bytearray([0x00, 0x06, 0x03])       #fastest refresh, 6 lines front, 3 lines back.
        self._writecommand(TFT.FRMCTR1)              #Frame rate control.
        self._writedata(data3)
        time.sleep_us(10)

        self._writecommand(TFT.MADCTL)
        data1[0] = 0x08                             #row address/col address, bottom to top refresh
        self._writedata(data1)

        data2 = bytearray(2)
        self._writecommand(TFT.DISSET5)              #Display settings
        data2[0] = 0x15                             #1 clock cycle nonoverlap, 2 cycle gate rise, 3 cycle oscil, equalize
        data2[1] = 0x02                             #fix on VTL
        self._writedata(data2)

        self._writecommand(TFT.INVCTR)               #Display inversion control
        data1[0] = 0x00                             #Line inversion.
        self._writedata(data1)

        self._writecommand(TFT.PWCTR1)               #Power control
        data2[0] = 0x02   #GVDD = 4.7V
        data2[1] = 0x70   #1.0uA
        self._writedata(data2)
        time.sleep_us(10)

        self._writecommand(TFT.PWCTR2)               #Power control
        data1[0] = 0x05                             #VGH = 14.7V, VGL = -7.35V
        self._writedata(data1)

        self._writecommand(TFT.PWCTR3)           #Power control
        data2[0] = 0x01   #Opamp current small
        data2[1] = 0x02   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.VMCTR1)               #Power control
        data2[0] = 0x3C   #VCOMH = 4V
        data2[1] = 0x38   #VCOML = -1.1V
        self._writedata(data2)
        time.sleep_us(10)

        self._writecommand(TFT.PWCTR6)               #Power control
        data2[0] = 0x11
        data2[1] = 0x15
        self._writedata(data2)

        #These different values don't seem to make a difference.
    #     dataGMCTRP = bytearray([0x0f, 0x1a, 0x0f, 0x18, 0x2f, 0x28, 0x20, 0x22, 0x1f,
    #                             0x1b, 0x23, 0x37, 0x00, 0x07, 0x02, 0x10])
        dataGMCTRP = bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29,
                                0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])
        self._writecommand(TFT.GMCTRP1)
        self._writedata(dataGMCTRP)

    #     dataGMCTRN = bytearray([0x0f, 0x1b, 0x0f, 0x17, 0x33, 0x2c, 0x29, 0x2e, 0x30,
    #                             0x30, 0x39, 0x3f, 0x00, 0x07, 0x03, 0x10])
        dataGMCTRN = bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e,
                                0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])
        self._writecommand(TFT.GMCTRN1)
        self._writedata(dataGMCTRN)
        time.sleep_us(10)

        self._writecommand(TFT.CASET)                #Column address set.
        self.windowLocData[0] = 0x00
        self.windowLocData[1] = self._offset[0]                   #Start at column 2
        self.windowLocData[2] = 0x00
        self.windowLocData[3] = self._size[0] + self._offset[0]
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)                #Row address set.
        self.windowLocData[1] = self._offset[1]                   #Start at row 1.
        self.windowLocData[3] = self._size[1] + self._offset[1]
        self._writedata(self.windowLocData)

        self._writecommand(TFT.NORON)                #Normal display on.
        time.sleep_us(10)

        self._writecommand(TFT.RAMWR)
        time.sleep_us(500)

        self._writecommand(TFT.DISPON)
        self.cs(1)
        time.sleep_us(500)
    else :
        # Initialize a green tab version.
        self._reset()
        self._writecommand(TFT.SWRESET)              #Software reset.
        time.sleep_us(150)
        self._writecommand(TFT.SLPOUT)               #out of sleep mode.
        time.sleep_us(255)

        data3 = bytearray([0x01, 0x2C, 0x2D])       #fastest refresh, 6 lines front, 3 lines back.
        self._writecommand(TFT.FRMCTR1)              #Frame rate control.
        self._writedata(data3)

        self._writecommand(TFT.FRMCTR2)              #Frame rate control.
        self._writedata(data3)

        data6 = bytearray([0x01, 0x2c, 0x2d, 0x01, 0x2c, 0x2d])
        self._writecommand(TFT.FRMCTR3)              #Frame rate control.
        self._writedata(data6)
        time.sleep_us(10)

        self._writecommand(TFT.INVCTR)               #Display inversion control
        self._writedata(bytearray([0x07]))
        self._writecommand(TFT.PWCTR1)               #Power control
        data3[0] = 0xA2
        data3[1] = 0x02
        data3[2] = 0x84
        self._writedata(data3)

        self._writecommand(TFT.PWCTR2)               #Power control
        self._writedata(bytearray([0xC5]))

        data2 = bytearray(2)
        self._writecommand(TFT.PWCTR3)               #Power control
        data2[0] = 0x0A   #Opamp current small
        data2[1] = 0x00   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.PWCTR4)               #Power control
        data2[0] = 0x8A   #Opamp current small
        data2[1] = 0x2A   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.PWCTR5)               #Power control
        data2[0] = 0x8A   #Opamp current small
        data2[1] = 0xEE   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.VMCTR1)               #Power control
        self._writedata(bytearray([0x0E]))

        self._writecommand(TFT.INVOFF)

        if self.tabcolor == self.GREENTAB :
            self._offset = (2,1)
        if self.tabcolor == self.REDTAB :
            self._offset = (0,0)
        if self.tabcolor == self.BLACKTAB :
            self._offset = (0,0)
            self._rgb = False
        elif self.tabcolor == self.GREENTAB2 :
            self._offset = (2,1)
            self._rgb = False
        elif self.tabcolor == self.GREENTAB3 :
            self._offset = (2,3)
        elif self.tabcolor == self.GREENTAB128x128 :
            self._size = (128,128)
            self._offset = (2,1)
            self._rgb = False
        elif self.tabcolor == self.GREENTAB80x160 :
            self._size = (80,160)
            self._offset = (26,1)
            self._rgb = False
            self._writecommand(TFT.INVON)
        elif self.tabcolor == self.REDTAB80x160 :
            self._size = (80,160)
            self._offset = (24,0)

        # rotate to the same orientation with the Pins on the boards at the top
        if self.tabcolor == self.GREENTAB80x160 or self.tabcolor == self.REDTAB80x160 :
            self.rotation(1)
        else :
            self.rotation(2)

        # set the color mapping of  RGB or GBR
        self._setMADCTL()

        self._writecommand(TFT.COLMOD)
        self._writedata(bytearray([0x05]))

        self._writecommand(TFT.CASET)                #Column address set.

        self.windowLocData[0] = 0x00
        self.windowLocData[1] = self._offset[0]
        self.windowLocData[2] = 0x00
        self.windowLocData[3] = self._size[0]+self._offset[0]
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)                #Row address set.
        self.windowLocData[1] = self._offset[1]
        self.windowLocData[3] = self._size[1]+self._offset[1]
        self._writedata(self.windowLocData)

        dataGMCTRP = bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29,
                                0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])
        self._writecommand(TFT.GMCTRP1)
        self._writedata(dataGMCTRP)

        dataGMCTRN = bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d, 0x2e,
                                0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])
        self._writecommand(TFT.GMCTRN1)
        self._writedata(dataGMCTRN)

        self._writecommand(TFT.NORON)                #Normal display on.
        time.sleep_us(10)

        self._writecommand(TFT.DISPON)
        time.sleep_us(100)

        self.cs(1)

จากโค้ดต้นฉบับจะพบว่าการสั่งวาดนั้นกระทำทีละคำสั่ง เมื่อได้รับคำสั่งจะส่งข้อมูลไปให้โมดูลแสดงผลในทันที ซึ่งการกระทำนี้ส่งผลให้การแสดงผลเกิดขึ้นบ่อย มีการเชื่อมต่อกับ I/O บ่อย และตามด้วยเกิดคอขวดในการทำงานเนื่องจากบัส SPI ที่รับส่งนั้นความเร็วน้อยกว่าไมโครคอนโทรลเลอร์อย่างมาก คือ ทำงานได้ประมาณ 20MHz ขณะที่ esp8266 ทำงานที่ 160MHz และ esp32 ทำงานที่ 240MHz

แต่ถ้าสร้างบัฟเฟอร์สำหรับเก็บข้อมูลพิกเซลและนำออกไปครั้งเดียวซึ่งทีมงานเราได้ทดสอบไปแล้วในการวาดอักขระภาษาไทยในบทความของ OLED ที่เป็นบัสแบบ I2C ซึ่งมีผลการทำงานเหมือนกันคือ การวาดในหน่วยความจำและส่งออกครั้งเดียว ช่วยลดเวลาที่สูญเสียในการเปิดโหมดทำงาน/ส่งข้อมูล/ปิดโหมดการทำงานได้ และเมื่อดูจากโค้ดการทำงานของคำสั่ง pixel() จะพบว่ามีการส่งข้อมูลลงในบัส SPI ทุกครั้งที่เรียกใช้

  def pixel( self, aPos, aColor ) :
    '''Draw a pixel at the given position'''
    if 0 <= aPos[0] < self._size[0] and 0 <= aPos[1] < self._size[1]:
      self._setwindowpoint(aPos)
      self._pushcolor(aColor)

ในคำสั่ง char() สำหรับวาดตัวอักษรมีการเรียก pixel() ทุกครั้งที่มีการวาดจุดของตัวอักษรในแต่ละจุด นั่นหมายความว่าเกิดการส่งข้อมูลไปยังบัส SPI อยู่เสมอ

#   @micropython.native
  def char( self, aPos, aChar, aColor, aFont, aSizes ) :
    '''Draw a character at the given position using the given font and color.
       aSizes is a tuple with x, y as integer scales indicating the
       # of pixels to draw for each pixel in the character.'''

    if aFont == None:
      return

    startchar = aFont['Start']
    endchar = aFont['End']

    ci = ord(aChar)
    if (startchar <= ci <= endchar):
      fontw = aFont['Width']
      fonth = aFont['Height']
      ci = (ci - startchar) * fontw

      charA = aFont["Data"][ci:ci + fontw]
      px = aPos[0]
      if aSizes[0] <= 1 and aSizes[1] <= 1 :
        for c in charA :
          py = aPos[1]
          for r in range(fonth) :
            if c & 0x01 :
              self.pixel((px, py), aColor)
            py += 1
            c >>= 1
          px += 1
      else:
        for c in charA :
          py = aPos[1]
          for r in range(fonth) :
            if c & 0x01 :
              self.fillrect((px, py), aSizes, aColor)
            py += aSizes[1]
            c >>= 1
          px += aSizes[0]

และเมื่อพิจารณาจากคำสั่ง image() ที่เป็นการส่งข้อมูลบิตแมพของภาพทั้งก้อนในครั้งเดียวดังนี้

  def image( self, x0, y0, x1, y1, data ) :
    self._setwindowloc((x0, y0), (x1, y1))
    self._writedata(data)

จะพบว่ามีการเรียกใช้คำสั่ง _serwindowloc() เหมือนกัน แต่แตกต่างที่คำสั่ง _pushcolor() และ _writedata() ซึ่งโค้ดการทำงานเป็นดังนี้

  #@micropython.native
  def _pushcolor( self, aColor ) :
    '''Push given color to the device.'''
    self.colorData[0] = aColor >> 8
    self.colorData[1] = aColor
    self._writedata(self.colorData)

  def _writedata( self, aData ) :
    '''Write given data to the device.  This may be
       either a single int or a bytearray of values.'''
    self.dc(1)
    self.cs(0)
    self.spi.write(aData)
    self.cs(1)

และเมื่อสังเกตดี ๆ จะพบว่า คำสั่ง _pushcolor() เองก็เรียกใช้ _writedata() เช่นเดียวกัน ดังนั้น ถ้าต้องวาดตัวอักษรขนาด 8×8 พิกเซล จะพบว่า คำสั่ง char() จะต้องส่งพิกเซลไปที่โมดูลแสดงผลดังนี้

  • จำนวนครั้งที่ส่งข้อมูล = 8×8 = 64 ครั้ง
  • แต่ละครั้งใช้คำสั่งในการทำงาน = 2 คำสัง
  • 64 ครั้ง จะต้องใช้การทำงาน = 2*64 = 128 ครั้ง

แต่ถ้าเราเก็บตัวอักษรในลักษณะบัฟเฟอร์หรือแถวลำดับขนาด 8×8 สมาชิก หรือ 64 ตัว จะเป็นดังนี้

  • จำนวนครั้งที่ส่งข้อมูล = 1 ครั้ง
  • แต่ละครั้งใช้คำสั่งในการทำงาน = 2 คำสั่ง
  • 1 ครั้ง จะต้องใช้การทำงาน = 1+จำนวนข้อมูล = 1+64 = 65 ครั้ง

จากการเปรียบเทียบคร่าว ๆ ด้านบน (ไม่รวมว่าคำาสั่ง pixel() เรียกใช้ _writedata() ซึ่งยิ่งทำให้การทำงานมีจำนวนครั้งของการทำงานที่มากขึ้น) ซึ่งจะพบว่า การสั่งวาดตัวอักษรขนาด 8×8 พิกเซลที่เท่ากัน วิธีการวาดปกติใช้การทำงาน 128 ครั้ง (คือ กำหนดตำแหน่ง และส่งค่าสี) กับอีกวิธีที่นำข้อมูลจากหน่วยความจำส่งไปยังโมดูลแสดงผลที่ทำงาน 65 ครั้งที่เป็นการระบุตำแหน่งที่จะส่งและตามด้วยข้อมูลอีก 64 ชุด จะพบว่าการทำงานนั้น มีจำนวนครั้งที่ลดลงมากอย่างเห็นได้ชัด

ดังนั้น เมื่อใช้กับการวาดปริมาณมากในแต่ละการแสดงผลแต่ละครั้งจะส่งผลให้ความเร็วในการแสดงผลยิ่งแตกต่างกันออกไป แต่อย่างไรก็ดี การทำดับเบิลบัฟเฟอร์ (double buffer) จะต้องมีปริมาณหน่วยความจำที่มากพอสำหรับเก็บบัฟเฟอร์ของจอแสดงผล ซึ่งเมื่อใช้กับ Blue tab ขนาด 160×80 พิกเซล จะต้องมีประมาณหน่วยความจำดังนี้

  • จำนวนหน่วยความจำ = ความกว้าง x สูง x 2 ไบต์ = 160 x 80 x 2 = 25,600 ไบต์

ซึ่งไมโครคอนโทรลเลอร์ esp8266 และ esp32มีหน่วยความจำมากพอสำหรับกันเอาไว้ 25,600 ไบต์ สำหรับการเก็บข้อมูลของจอแสดงผล

หมายเหตุ ที่ผู้เขียนเทียบเป็นจำนวนครั้งไม่ได้เทียบเป็นเวลาเพื่อให้เห็นจำนวนครั้งของการทำงาน ซึ่งไม่ได้วัดประสิทธิภาพเรื่องของความเร็วของแต่ละคำสั่ง

บัฟเฟอร์

ตัวไลบรารีสำคัญของ Micropython ตัวหนึ่งคือ framebuf ซึ่งมีหน้าที่สำหรับเป็นกระดานวาดภาพ โดยวาดลงหน่วยความจำตามรูปแบบของพิกเซลที่กำหนดได้ดังนี้

  • RGB565
  • MONO_VLSB
  • GS2_HMSB
  • GS4_HMSB
  • GS8
  • MONO_HLSB
  • MONO_HMSB

นอกจากนี้เมื่อเราเลือกใช้บัฟเฟอร์จากคลาส framebuf จะทำให้เราสามารถใช้งานชุดคำสั่งต่อไปนี้สำหรับวาดพิกเซลลงในหน่วยความจำ ซึ่งการวาดในหน่วยความจำนั้นมีความเร็วที่ไวกว่าโมดูล TFT เนื่องจาก SPIRAM รองรับที่ความเร็วการทำงาน 80MHz

  • fill()
  • fill_rect()
  • pixel()
  • hline()
  • vline()
  • rect()
  • line()
  • blit()
  • scroll()
  • text()

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

    def __init__( self, spi, aDC, aReset, aCS):
        self.dcPin  = machine.Pin(aDC, machine.Pin.OUT)
        if aReset == None :
            self.useReset = False
        else:
            self.useReset = True
            self.resetPin = machine.Pin(aReset, machine.Pin.OUT)
        if aCS == None :
            self.useCS = False
        else :
            self.useCS = True
            self.csPin = machine.Pin(aCS, machine.Pin.OUT)
        self.csPin(1)
        self.spi = spi
        self.colorData = bytearray(2)
        self.windowLocData = bytearray(4)
        self.buffer = framebuf.FrameBuffer(bytearray(80*160*2),160,80,framebuf.RGB565)
        
        self._reset()
        self._writecommand(TFT.SWRESET)              #Software reset.
        ...

        self.csPin(1)

นอกจากนี้ได้เพิ่มชุดคำสั่งสำหรับอ่านค่าสีและลงค่าสีในหน่วยความจำดังนี้

    def setPixel( self, aX, aY, aColor ) :
        self.buffer.pixel(aX, aY, aColor)
    
    def readPixel( self, aX, aY ):
       return self.buffer.pixel(aX, aY)

ส่วนที่ต้องกระทำเมื่อสั่งงานวาดสิ่งต่าง ๆ ลงหน่วยความจำแล้วก็คือ การนำบัฟเฟอร์นี้ไปส่งให้กับโมดูลแสดงผลผ่านทางบัส SPI ซึ่งมีโค้ดดังต่อไปนี้


    def swap( self ):
        self._setwindowloc((0,0), (self._size[0]-1, self._size[1]-1))
        self.dcPin(1)
        self.csPin(0)
        self.spi.write(self.buffer)
        self.csPin(1)

ไลบรารีที่ปรับปรุงแล้ว

จากแนวคิดในการทำดับเบิลบัฟเฟอร์ และเจาะจงให้ใช้งานกับ ST7735 BLUE TAB 0.96 นิ้ว ขนาด 80×160 จึงได้ไลบรารีของการทำงานเป็นดังนี้

#Added 1.44 inch 128x128 and 0.96 inch 80x160 SST7735 to the
# original 1.8 inch drivers developed by Guy Carver
# fixed the issue of different start Row and start col addresses of different OLED types
# fixed the RGB special equence in 80x160 ST7735B
#
# ปรับปรุงเพื่อใช้กับ ST7735S GREENTAB80x160 (เท่านั้น)
# by JarutEx (www.jarutex.com)
# Update 2021-09-07
from machine import Pin
import time
import machine
import framebuf

TFTRotations = [0x00, 0x60, 0xC0, 0xA0]
TFTBGR = 0x08 # for 1.8 and 1.44 inch display

#@micropython.native
def clamp( aValue, aMin, aMax ):
    return max(aMin, min(aMax, aValue))

#@micropython.native
def TFTColor( aR, aG, aB ):
    # Convert to RGB5655
    aColor = ((aR & 0xF8) << 8) | ((aG & 0xFC) << 3) | (aB >> 3)
    # Swap
    lColor = aColor >> 8
    hColor = aColor << 8
    return (lColor | hColor)

class TFT(object):
    NOP = 0x0
    SWRESET = 0x01
    RDDID = 0x04
    RDDST = 0x09
    
    SLPIN  = 0x10
    SLPOUT = 0x11
    PTLON  = 0x12
    NORON  = 0x13

    INVOFF = 0x20
    INVON = 0x21
    DISPOFF = 0x28
    DISPON = 0x29
    CASET = 0x2A
    RASET = 0x2B
    RAMWR = 0x2C
    RAMRD = 0x2E

    COLMOD = 0x3A
    MADCTL = 0x36

    FRMCTR1 = 0xB1
    FRMCTR2 = 0xB2
    FRMCTR3 = 0xB3
    INVCTR = 0xB4
    DISSET5 = 0xB6

    PWCTR1 = 0xC0
    PWCTR2 = 0xC1
    PWCTR3 = 0xC2
    PWCTR4 = 0xC3
    PWCTR5 = 0xC4
    VMCTR1 = 0xC5

    RDID1 = 0xDA
    RDID2 = 0xDB
    RDID3 = 0xDC
    RDID4 = 0xDD

    PWCTR6 = 0xFC

    GMCTRP1 = 0xE0
    GMCTRN1 = 0xE1

    BLACK = 0
    RED = TFTColor(0xFF, 0x00, 0x00)
    MAROON = TFTColor(0x80, 0x00, 0x00)
    GREEN = TFTColor(0x00, 0xFF, 0x00)
    FOREST = TFTColor(0x00, 0x80, 0x80)
    BLUE = TFTColor(0x00, 0x00, 0xFF)
    NAVY = TFTColor(0x00, 0x00, 0x80)
    CYAN = TFTColor(0x00, 0xFF, 0xFF)
    YELLOW = TFTColor(0xFF, 0xFF, 0x00)
    PURPLE = TFTColor(0xFF, 0x00, 0xFF)
    WHITE = TFTColor(0xFF, 0xFF, 0xFF)
    GRAY = TFTColor(0x80, 0x80, 0x80)

    @staticmethod
    def color( aR, aG, aB ):
        return TFTColor(aR, aG, aB)

    def __init__( self, spi, aDC, aReset, aCS):
        self.dcPin  = machine.Pin(aDC, machine.Pin.OUT)
        if aReset == None :
            self.useReset = False
        else:
            self.useReset = True
            self.resetPin = machine.Pin(aReset, machine.Pin.OUT)
        if aCS == None :
            self.useCS = False
        else :
            self.useCS = True
            self.csPin = machine.Pin(aCS, machine.Pin.OUT)
        self.csPin(1)
        self.spi = spi
        self.colorData = bytearray(2)
        self.windowLocData = bytearray(4)
        self.buffer = framebuf.FrameBuffer(bytearray(80*160*2),160,80,framebuf.RGB565)
        
        self._reset()
        self._writecommand(TFT.SWRESET)              #Software reset.
        time.sleep_us(150)
        self._writecommand(TFT.SLPOUT)               #out of sleep mode.
        time.sleep_us(255)

        data3 = bytearray([0x01, 0x2C, 0x2D])       #fastest refresh, 6 lines front, 3 lines back.
        self._writecommand(TFT.FRMCTR1)              #Frame rate control.
        self._writedata(data3)

        self._writecommand(TFT.FRMCTR2)              #Frame rate control.
        self._writedata(data3)

        data6 = bytearray([0x01, 0x2c, 0x2d, 0x01, 0x2c, 0x2d])
        self._writecommand(TFT.FRMCTR3)              #Frame rate control.
        self._writedata(data6)
        time.sleep_us(10)

        self._writecommand(TFT.INVCTR)               #Display inversion control
        self._writedata(bytearray([0x07]))
        self._writecommand(TFT.PWCTR1)               #Power control
        data3[0] = 0xA2
        data3[1] = 0x02
        data3[2] = 0x84
        self._writedata(data3)

        self._writecommand(TFT.PWCTR2)               #Power control
        self._writedata(bytearray([0xC5]))

        data2 = bytearray(2)
        self._writecommand(TFT.PWCTR3)               #Power control
        data2[0] = 0x0A   #Opamp current small
        data2[1] = 0x00   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.PWCTR4)               #Power control
        data2[0] = 0x8A   #Opamp current small
        data2[1] = 0x2A   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.PWCTR5)               #Power control
        data2[0] = 0x8A   #Opamp current small
        data2[1] = 0xEE   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.VMCTR1)               #Power control
        self._writedata(bytearray([0x0E]))

        self._writecommand(TFT.INVOFF)

        self._size = (80,160)
        self._offset = (26,1)
        self._rgb = False
        self._writecommand(TFT.INVON)

        self.rotate = 2
        self.rotation(1)
        # set the color mapping of  RGB or GBR
        self._setMADCTL()

        self._writecommand(TFT.COLMOD)
        self._writedata(bytearray([0x05]))

        self._writecommand(TFT.CASET)                #Column address set.

        self.windowLocData[0] = 0x00
        self.windowLocData[1] = self._offset[0]
        self.windowLocData[2] = 0x00
        self.windowLocData[3] = self._size[0]+self._offset[0]
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)                #Row address set.
        self.windowLocData[1] = self._offset[1]
        self.windowLocData[3] = self._size[1]+self._offset[1]
        self._writedata(self.windowLocData)

        dataGMCTRP = bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d,
                                0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])
        self._writecommand(TFT.GMCTRP1)
        self._writedata(dataGMCTRP)

        dataGMCTRN = bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d,
                                0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])
        self._writecommand(TFT.GMCTRN1)
        self._writedata(dataGMCTRN)

        self._writecommand(TFT.NORON)                #Normal display on.
        time.sleep_us(10)

        self._writecommand(TFT.DISPON)
        time.sleep_us(100)

        self.csPin(1)

    #@micropython.native
    def setPixel( self, aX, aY, aColor ) :
        self.buffer.pixel(aX, aY, aColor)
    
    #@micropython.native
    def clear(self):
        self.buffer.fill(0)

    #@micropython.native
    def readPixel( self, aX, aY ):
       return self.buffer.pixel(aX, aY)
    
    #@micropython.native
    def swap( self ):
        self._setwindowloc((0,0), (self._size[0]-1, self._size[1]-1))
        self.dcPin(1)
        self.csPin(0)
        self.spi.write(self.buffer)
        self.csPin(1)
        
    #@micropython.native
    def on( self, aTF = True ) :
        self._writecommand(TFT.DISPON if aTF else TFT.DISPOFF)
        
    #@micropython.native
    def rgb( self, aTF = True ) :
        self._rgb = aTF
        self._setMADCTL()
        
    #@micropython.native
    def size( self ) :
        return self._size

    #@micropython.native
    def rotation( self, aRot ) :
        if (0 <= aRot < 4):
            rotchange = self.rotate ^ aRot
            self.rotate = aRot
        if (rotchange & 1):
            self._size =(self._size[1], self._size[0])
            self._offset=(self._offset[1], self._offset[0])
        self._setMADCTL()

    #@micropython.native
    def fillrect( self, aStart, aSize, aColor ) :
        self.buffer.fill_rect(aStart[0], aStart[1], aSize[0], aSize[1], aColor)

    def fill( self, aColor = BLACK ) :
        self.buffer.fill(aColor)

    def image( self, x0, y0, x1, y1, data ) :
        self._setwindowloc((x0, y0), (x1, y1))
        self._writedata(data)

    #@micropython.native
    def line( self, aStart, aEnd, aColor ) :
        self.buffer.line(aStart[0],aStart[1],aEnd[0],aEnd[1],aColor)

    #@micropython.native
    def hline( self, aStart, aW, aColor ) :
        self.buffer.hline(aStart[0],aStart[1],aW,aColor)

    #@micropython.native
    def vline( self, aStart, aH, aColor ) :
        self.buffer.vline(aStart[0],aStart[1],aH,aColor)

    #@micropython.native
    def text( self, aText, aStart, aColor ) :
        self.buffer.text(aText,aStart[0],aStart[1],aColor)

    #@micropython.native
    def scroll( self, xStep, yStep ) :
        self.buffer.scroll(xStep, yStep)

    #@micropython.native
    def rect( self, aStart, aSize, aColor ) :
        self.buffer.rect(aStart[0],aStart[1],aSize[0],aSize[1],aColor)

    #@micropython.native
    def _setColor( self, aColor ) :
        self.colorData[0] = aColor >> 8
        self.colorData[1] = aColor
        self.buf = bytes(self.colorData) * 32

    #@micropython.native
    def _draw( self, aPixels ) :
        self.dcPin(1)
        self.csPin(0)
        for i in range(aPixels//32):
            self.spi.write(self.buf)
        rest = (int(aPixels) % 32)
        if rest > 0:
            buf2 = bytes(self.colorData) * rest
            self.spi.write(buf2)
        self.csPin(1)

    #@micropython.native
    def _setwindowpoint( self, aPos ) :
        x = self._offset[0] + int(aPos[0])
        y = self._offset[1] + int(aPos[1])
        self._writecommand(TFT.CASET)            #Column address set.
        self.windowLocData[0] = self._offset[0]
        self.windowLocData[1] = x
        self.windowLocData[2] = self._offset[0]
        self.windowLocData[3] = x
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)            #Row address set.
        self.windowLocData[0] = self._offset[1]
        self.windowLocData[1] = y
        self.windowLocData[2] = self._offset[1]
        self.windowLocData[3] = y
        self._writedata(self.windowLocData)
        self._writecommand(TFT.RAMWR)            #Write to RAM.

    #@micropython.native
    def _setwindowloc( self, aPos0, aPos1 ) :
        self._writecommand(TFT.CASET)            #Column address set.
        self.windowLocData[0] = self._offset[0]
        self.windowLocData[1] = self._offset[0] + int(aPos0[0])
        self.windowLocData[2] = self._offset[0]
        self.windowLocData[3] = self._offset[0] + int(aPos1[0])
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)            #Row address set.
        self.windowLocData[0] = self._offset[1]
        self.windowLocData[1] = self._offset[1] + int(aPos0[1])
        self.windowLocData[2] = self._offset[1]
        self.windowLocData[3] = self._offset[1] + int(aPos1[1])
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RAMWR)            #Write to RAM.

    #@micropython.native
    def _writecommand( self, aCommand ) :
        self.dcPin(0)
        self.csPin(0)
        self.spi.write(bytearray([aCommand]))
        self.csPin(1)

    #@micropython.native
    def _writedata( self, aData ) :
        self.dcPin(1)
        self.csPin(0)
        self.spi.write(aData)
        self.csPin(1)

    #@micropython.native
    def _pushcolor( self, aColor ) :
        self.colorData[0] = aColor >> 8
        self.colorData[1] = aColor
        self._writedata(self.colorData)

    #@micropython.native
    def _setMADCTL( self ) :
        self._writecommand(TFT.MADCTL)
        rgb = TFTRGB if self._rgb else TFTBGR
        self._writedata(bytearray([TFTRotations[self.rotate] | rgb]))

    #@micropython.native
    def _reset( self ) :
        self.dcPin(0)
        if self.useReset :
            self.resetPin(1)
            time.sleep_us(500)
            self.resetPin(0)
            time.sleep_us(500)
            self.resetPin(1)
        time.sleep_us(500)

โดยการใช้งานจะอาศัยคำสั่ง swap() เป็นคำสั่งสุดท้ายของการนำข้อมูลจากบัฟเฟอร์ไปแสดงผลที่โมดูล TFT ดังนั้น ผู้เขียนโปรแกรมจะต้องออกแบบการเขียนโค้ดให้เหมาะสมกับการทำงานแบบดับเบิลบัฟเฟอร์ด้วยเช่นกัน

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

from st7735x import TFT
from sysfont import sysfont
from machine import SPI,Pin
import machine as mc
import time
import math
mc.freq(240000000)
spi = SPI(2, baudrate=33000000,
          sck=Pin(14), mosi=Pin(12),
          polarity=0, phase=0)
# dc, rst, cs
tft=TFT(spi,15,13,2)

tft.fill(tft.BLACK)
tft.text("(C)2020-21",(10,36),tft.YELLOW)
tft.text("JarutEx",(92,36),tft.WHITE)
tft.text("JarutEx",(93,36),tft.WHITE)
tft.swap()
time.sleep_ms(2000)
tft.fill(tft.BLACK)

t0 = time.ticks_ms()
for row in range(tft._size[1]):
    color = tft.color(int(row*(256.0/tft._size[1])),32,32)
    for col in range(tft._size[0]):
        tft.setPixel(col, row, color)
tft.swap()
print("done setPixel() in {} msec".format(time.ticks_ms()-t0))

color = tft.color(192,32,64)

t0 = time.ticks_ms()
tft.fill(tft.BLACK)
for x in range(0, tft.size()[0], 6):
    tft.line((0,0),(x, tft.size()[1] - 1), color)
    tft.swap()
for y in range(0, tft.size()[1], 6):
    tft.line((0,0),(tft.size()[0] - 1, y), color)
    tft.swap()
print("done test line() #1 in {} msec".format(time.ticks_ms()-t0))

t0 = time.ticks_ms()
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)
    tft.swap()
for y in range(0, tft.size()[1], 6):
    tft.line((tft.size()[0] - 1, 0), (0, y), color)
    tft.swap()
print("done test line() #2 in {} msec".format(time.ticks_ms()-t0))

t0 = time.ticks_ms()
tft.fill(tft.BLACK)
for x in range(0, tft.size()[0], 6):
    tft.line((0, tft.size()[1] - 1), (x, 0), color)
    tft.swap()
for y in range(0, tft.size()[1], 6):
    tft.line((0, tft.size()[1] - 1), (tft.size()[0] - 1,y), color)
    tft.swap()
print("done test line() #3 in {} msec".format(time.ticks_ms()-t0))

t0 = time.ticks_ms()
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)
    tft.swap()
for y in range(0, tft.size()[1], 6):
    tft.line((tft.size()[0] - 1, tft.size()[1] - 1), (0, y), color)
    tft.swap()
print("done test line() #4 in {} msec".format(time.ticks_ms()-t0))

tft.fill(tft.BLACK)
tft.text("(C)2020-21",(10,36),tft.YELLOW)
tft.text("JarutEx",(92,36),tft.WHITE)
tft.text("JarutEx",(93,36),tft.WHITE)
tft.swap()
time.sleep_ms(10000)
tft.fill(0)
tft.on(False)
spi.deinit()

ตัวอย่างของการทำงานเป็นดังภาพที่ 2

ภาพที่ 2 ตัวอย่างการแสดงผลจากการทำงานของการวาดเส้น

จากโค้ดที่ได้จากการปรับปรุงแล้วมีผลลัพธ์ของเวลาการทำงานเป็นดังภาพที่ 3

ภาพที่ 3 ตัวอย่างผลลัพธ์จากโค้ดที่ได้จากไลบรารีที่ปรับปรุงแล้ว

ส่วนการทดสอบกับไลบรารีเก่าเขียนโค้ดดังนี้ และได้ผลลัพธ์ดังภาพที่ 4

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(2, baudrate=27000000,
          sck=Pin(14), mosi=Pin(12),
          polarity=0, phase=0)
# dc, rst, cs
tft=TFT(spi,15,13,2)
tft.init_7735(tft.GREENTAB80x160)

tft.fill(tft.BLACK)
tft.text((10,36),"(C)2020-21",tft.YELLOW, sysfont)
tft.text((92,36),"JarutEx",tft.WHITE, sysfont)
tft.text((93,36),"JarutEx",tft.WHITE, sysfont)
time.sleep_ms(2000)
tft.fill(tft.BLACK)

t0 = time.ticks_ms()
for row in range(tft._size[1]):
    color = tft.color(int(row*(256.0/tft._size[1])),32,32)
    for col in range(tft._size[0]):
        tft.pixel((col,row), color)
print("done setPixel() in {} msec".format(time.ticks_ms()-t0))

color = tft.color(192,32,64)

t0 = time.ticks_ms()
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)
print("done test line() #1 in {} msec".format(time.ticks_ms()-t0))

t0 = time.ticks_ms()
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)
print("done test line() #2 in {} msec".format(time.ticks_ms()-t0))

t0 = time.ticks_ms()
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)
print("done test line() #3 in {} msec".format(time.ticks_ms()-t0))

t0 = time.ticks_ms()
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)
print("done test line() #4 in {} msec".format(time.ticks_ms()-t0))

tft.fill(tft.BLACK)
tft.text((10,36),"(C)2020-21",tft.YELLOW, sysfont)
tft.text((92,36),"JarutEx",tft.WHITE, sysfont)
tft.text((93,36),"JarutEx",tft.WHITE, sysfont)

time.sleep_ms(10000)
tft.fill(0)
tft.on(False)
spi.deinit()


ภาพที่ 4 ตัวอย่างผลลัพธ์ที่ได้จากไลบรารีก่อนปรับปรุง

การปรับแต่งเพื่อใช้กับ REDTAB80x160

โค้ดสำหรับ REDTAB80x160 เป็นดังนี้

#Added 1.44 inch 128x128 and 0.96 inch 80x160 SST7735 to the
# original 1.8 inch drivers developed by Guy Carver
# fixed the issue of different start Row and start col addresses of different OLED types
# fixed the RGB special equence in 80x160 ST7735B
#
# ปรับปรุงเพื่อใช้กับ ST7735S GREENTAB80x160 (เท่านั้น)
# by JarutEx (www.jarutex.com)
# Update 2021-09-07
# REDTAB80x160 - 2021-09-27
from machine import Pin
import time
import machine
import framebuf

#TFTRotations and TFTRGB are bits to set
# on MADCTL to control display rotation/color layout
#Looking at display with pins on top.
#00 = upper left printing right
#10 = does nothing (MADCTL_ML)
#20 = upper left printing down (backwards) (Vertical flip)
#40 = upper right printing left (backwards) (X Flip)
#80 = lower left printing right (backwards) (Y Flip)
#04 = (MADCTL_MH)

#60 = 90 right rotation
#C0 = 180 right rotation
#A0 = 270 right rotation
TFTRotations = [0x00, 0x60, 0xC0, 0xA0]
TFTBGR = 0x08 # for 1.8 and 1.44 inch display
TFTRGB = 0x00

#@micropython.native
def clamp( aValue, aMin, aMax ):
    return max(aMin, min(aMax, aValue))

#@micropython.native
def TFTColor( aR, aG, aB ):
    # Convert to RGB5655
    aColor = ((aR & 0xF8) << 8) | ((aG & 0xFC) << 3) | (aB >> 3)
    # Swap
    lColor = aColor >> 8
    hColor = aColor << 8
    return (lColor | hColor)

class TFT(object):
    NOP = 0x0
    SWRESET = 0x01
    RDDID = 0x04
    RDDST = 0x09
    
    SLPIN  = 0x10
    SLPOUT = 0x11
    PTLON  = 0x12
    NORON  = 0x13

    INVOFF = 0x20
    INVON = 0x21
    DISPOFF = 0x28
    DISPON = 0x29
    CASET = 0x2A
    RASET = 0x2B
    RAMWR = 0x2C
    RAMRD = 0x2E

    COLMOD = 0x3A
    MADCTL = 0x36

    FRMCTR1 = 0xB1
    FRMCTR2 = 0xB2
    FRMCTR3 = 0xB3
    INVCTR = 0xB4
    DISSET5 = 0xB6

    PWCTR1 = 0xC0
    PWCTR2 = 0xC1
    PWCTR3 = 0xC2
    PWCTR4 = 0xC3
    PWCTR5 = 0xC4
    VMCTR1 = 0xC5

    RDID1 = 0xDA
    RDID2 = 0xDB
    RDID3 = 0xDC
    RDID4 = 0xDD

    PWCTR6 = 0xFC

    GMCTRP1 = 0xE0
    GMCTRN1 = 0xE1

    BLACK = 0
    RED = TFTColor(0xFF, 0x00, 0x00)
    MAROON = TFTColor(0x80, 0x00, 0x00)
    GREEN = TFTColor(0x00, 0xFF, 0x00)
    FOREST = TFTColor(0x00, 0x80, 0x80)
    BLUE = TFTColor(0x00, 0x00, 0xFF)
    NAVY = TFTColor(0x00, 0x00, 0x80)
    CYAN = TFTColor(0x00, 0xFF, 0xFF)
    YELLOW = TFTColor(0xFF, 0xFF, 0x00)
    PURPLE = TFTColor(0xFF, 0x00, 0xFF)
    WHITE = TFTColor(0xFF, 0xFF, 0xFF)
    GRAY = TFTColor(0x80, 0x80, 0x80)

    @staticmethod
    def color( aR, aG, aB ):
        return TFTColor(aR, aG, aB)

    def __init__( self, spi, aDC, aReset, aCS):
        self.dcPin  = machine.Pin(aDC, machine.Pin.OUT)
        if aReset == None :
            self.useReset = False
        else:
            self.useReset = True
            self.resetPin = machine.Pin(aReset, machine.Pin.OUT)
        if aCS == None :
            self.useCS = False
        else :
            self.useCS = True
            self.csPin = machine.Pin(aCS, machine.Pin.OUT)
        self.csPin(1)
        self.spi = spi
        self.colorData = bytearray(2)
        self.windowLocData = bytearray(4)
        self.buffer = framebuf.FrameBuffer(bytearray(80*160*2),160,80,framebuf.RGB565)
        
        self._reset()
        self._writecommand(TFT.SWRESET)              #Software reset.
        time.sleep_us(150)
        self._writecommand(TFT.SLPOUT)               #out of sleep mode.
        time.sleep_us(255)

        data3 = bytearray([0x01, 0x2C, 0x2D])       #fastest refresh, 6 lines front, 3 lines back.
        self._writecommand(TFT.FRMCTR1)              #Frame rate control.
        self._writedata(data3)

        self._writecommand(TFT.FRMCTR2)              #Frame rate control.
        self._writedata(data3)

        data6 = bytearray([0x01, 0x2c, 0x2d, 0x01, 0x2c, 0x2d])
        self._writecommand(TFT.FRMCTR3)              #Frame rate control.
        self._writedata(data6)
        time.sleep_us(10)

        self._writecommand(TFT.INVCTR)               #Display inversion control
        self._writedata(bytearray([0x07]))
        self._writecommand(TFT.PWCTR1)               #Power control
        data3[0] = 0xA2
        data3[1] = 0x02
        data3[2] = 0x84
        self._writedata(data3)

        self._writecommand(TFT.PWCTR2)               #Power control
        self._writedata(bytearray([0xC5]))

        data2 = bytearray(2)
        self._writecommand(TFT.PWCTR3)               #Power control
        data2[0] = 0x0A   #Opamp current small
        data2[1] = 0x00   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.PWCTR4)               #Power control
        data2[0] = 0x8A   #Opamp current small
        data2[1] = 0x2A   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.PWCTR5)               #Power control
        data2[0] = 0x8A   #Opamp current small
        data2[1] = 0xEE   #Boost frequency
        self._writedata(data2)

        self._writecommand(TFT.VMCTR1)               #Power control
        self._writedata(bytearray([0x0E]))

        self._writecommand(TFT.INVOFF)

        self._size = (80,160)
        self._offset = (24,0)
        #self._rgb = False
        self._rgb = True
        self._writecommand(TFT.INVON)

        self.rotate = 2
        self.rotation(1)
        # set the color mapping of  RGB or GBR
        self._setMADCTL()

        self._writecommand(TFT.COLMOD)
        self._writedata(bytearray([0x05]))

        self._writecommand(TFT.CASET)                #Column address set.

        self.windowLocData[0] = 0x00
        self.windowLocData[1] = self._offset[0]
        self.windowLocData[2] = 0x00
        self.windowLocData[3] = self._size[0]+self._offset[0]
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)                #Row address set.
        self.windowLocData[1] = self._offset[1]
        self.windowLocData[3] = self._size[1]+self._offset[1]
        self._writedata(self.windowLocData)

        dataGMCTRP = bytearray([0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d,
                                0x29, 0x25, 0x2b, 0x39, 0x00, 0x01, 0x03, 0x10])
        self._writecommand(TFT.GMCTRP1)
        self._writedata(dataGMCTRP)

        dataGMCTRN = bytearray([0x03, 0x1d, 0x07, 0x06, 0x2e, 0x2c, 0x29, 0x2d,
                                0x2e, 0x2e, 0x37, 0x3f, 0x00, 0x00, 0x02, 0x10])
        self._writecommand(TFT.GMCTRN1)
        self._writedata(dataGMCTRN)

        self._writecommand(TFT.NORON)                #Normal display on.
        time.sleep_us(10)

        self._writecommand(TFT.DISPON)
        time.sleep_us(100)

        self.csPin(1)

    #@micropython.native
    def setPixel( self, aX, aY, aColor ) :
        self.buffer.pixel(aX, aY, aColor)
    
    #@micropython.native
    def clear(self):
        self.buffer.fill(0)

    #@micropython.native
    def readPixel( self, aX, aY ):
       return self.buffer.pixel(aX, aY)
    
    #@micropython.native
    def swap( self ):
        self._setwindowloc((0,0), (self._size[0]-1, self._size[1]-1))
        self.dcPin(1)
        self.csPin(0)
        self.spi.write(self.buffer)
        self.csPin(1)
        
    #@micropython.native
    def on( self, aTF = True ) :
        self._writecommand(TFT.DISPON if aTF else TFT.DISPOFF)
        
    #@micropython.native
    def rgb( self, aTF = True ) :
        self._rgb = aTF
        self._setMADCTL()
        
    #@micropython.native
    def size( self ) :
        return self._size

    #@micropython.native
    def rotation( self, aRot ) :
        if (0 <= aRot < 4):
            rotchange = self.rotate ^ aRot
            self.rotate = aRot
        if (rotchange & 1):
            self._size =(self._size[1], self._size[0])
            self._offset=(self._offset[1], self._offset[0])
        self._setMADCTL()

    #@micropython.native
    def fillrect( self, aStart, aSize, aColor ) :
        self.buffer.fill_rect(aStart[0], aStart[1], aSize[0], aSize[1], aColor)

    def fill( self, aColor = BLACK ) :
        self.buffer.fill(aColor)

    def image( self, x0, y0, x1, y1, data ) :
        self._setwindowloc((x0, y0), (x1, y1))
        self._writedata(data)

    #@micropython.native
    def line( self, aStart, aEnd, aColor ) :
        self.buffer.line(aStart[0],aStart[1],aEnd[0],aEnd[1],aColor)

    #@micropython.native
    def hline( self, aStart, aW, aColor ) :
        self.buffer.hline(aStart[0],aStart[1],aW,aColor)

    #@micropython.native
    def vline( self, aStart, aH, aColor ) :
        self.buffer.vline(aStart[0],aStart[1],aH,aColor)

    #@micropython.native
    def text( self, aText, aStart, aColor ) :
        self.buffer.text(aText,aStart[0],aStart[1],aColor)

    #@micropython.native
    def scroll( self, xStep, yStep ) :
        self.buffer.scroll(xStep, yStep)



    #@micropython.native
    def rect( self, aStart, aSize, aColor ) :
        self.buffer.rect(aStart[0],aStart[1],aSize[0],aSize[1],aColor)

    #@micropython.native
    def _setColor( self, aColor ) :
        self.colorData[0] = aColor >> 8
        self.colorData[1] = aColor
        self.buf = bytes(self.colorData) * 32

    #@micropython.native
    def _draw( self, aPixels ) :
        self.dcPin(1)
        self.csPin(0)
        for i in range(aPixels//32):
            self.spi.write(self.buf)
        rest = (int(aPixels) % 32)
        if rest > 0:
            buf2 = bytes(self.colorData) * rest
            self.spi.write(buf2)
        self.csPin(1)

    #@micropython.native
    def _setwindowpoint( self, aPos ) :
        x = self._offset[0] + int(aPos[0])
        y = self._offset[1] + int(aPos[1])
        self._writecommand(TFT.CASET)            #Column address set.
        self.windowLocData[0] = self._offset[0]
        self.windowLocData[1] = x
        self.windowLocData[2] = self._offset[0]
        self.windowLocData[3] = x
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)            #Row address set.
        self.windowLocData[0] = self._offset[1]
        self.windowLocData[1] = y
        self.windowLocData[2] = self._offset[1]
        self.windowLocData[3] = y
        self._writedata(self.windowLocData)
        self._writecommand(TFT.RAMWR)            #Write to RAM.

    #@micropython.native
    def _setwindowloc( self, aPos0, aPos1 ) :
        self._writecommand(TFT.CASET)            #Column address set.
        self.windowLocData[0] = self._offset[0]
        self.windowLocData[1] = self._offset[0] + int(aPos0[0])
        self.windowLocData[2] = self._offset[0]
        self.windowLocData[3] = self._offset[0] + int(aPos1[0])
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RASET)            #Row address set.
        self.windowLocData[0] = self._offset[1]
        self.windowLocData[1] = self._offset[1] + int(aPos0[1])
        self.windowLocData[2] = self._offset[1]
        self.windowLocData[3] = self._offset[1] + int(aPos1[1])
        self._writedata(self.windowLocData)

        self._writecommand(TFT.RAMWR)            #Write to RAM.

    #@micropython.native
    def _writecommand( self, aCommand ) :
        self.dcPin(0)
        self.csPin(0)
        self.spi.write(bytearray([aCommand]))
        self.csPin(1)

    #@micropython.native
    def _writedata( self, aData ) :
        self.dcPin(1)
        self.csPin(0)
        self.spi.write(aData)
        self.csPin(1)

    #@micropython.native
    def _pushcolor( self, aColor ) :
        self.colorData[0] = aColor >> 8
        self.colorData[1] = aColor
        self._writedata(self.colorData)

    #@micropython.native
    def _setMADCTL( self ) :
        self._writecommand(TFT.MADCTL)
        rgb = TFTRGB if self._rgb else TFTBGR
        self._writedata(bytearray([TFTRotations[self.rotate] | rgb]))

    #@micropython.native
    def _reset( self ) :
        self.dcPin(0)
        if self.useReset :
            self.resetPin(1)
            time.sleep_us(500)
            self.resetPin(0)
            time.sleep_us(500)
            self.resetPin(1)
        time.sleep_us(500)

สรุป

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

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

ท่านใดต้องการพูดคุยสามารถคอมเมนท์ได้เลยครับ

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