This article is an update of the st7735 library file for Micropython by Billy Cheung (accessed 2021-09-07) published on github. It is a library that has been updated by Guy Caver to support ST7735s. The required libraries include st7735.py and sysfont.py Guy Carver implements esp8266 and esp32 to provide better display speed through the principle of display buffering for pixel storage and additional instructions for sending data from buffer to TFT module via SPI bus.
Original library
From the original library written by Guy Carver for use with the 1.8″ ST7735, someone has updated it to work with the 0.96″ ST7735 with a display resolution of 80×160 pixels as mentioned in both previous articles. For use with ESP32 and ESP8266, the source code is as follows.
#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)
From the source code, it can be seen that the drawings are executed one by one. When the command is received, the data will be sent to the display module immediately. This action results in the frequent display, frequent I/O connection, and followed by operational bottlenecks as the SPI buses are significantly slower than microcontrollers: around 20MHz, while esp8266 is running at 160MHz and esp32 is running at 240MHz.
If create a buffer for storing pixel data and take it out once, we have already tested to draw Thai characters in the article on i2c bus OLED, which has the same performance. Drawing in memory and exporting once Reduces the time wasted to turn on work mode / transmit data / turn off work mode. And when looking at the pixel() command’s code, it is found that the data is sent to the SPI bus every time it is called.
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)
In the char() command for drawing characters, pixel() is called every time a point of each character is drawn. This means that data is always being sent to the SPI bus.
# @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]
And when considering the image() command that sends the bitmap data of the whole image at once as follows:
def image( self, x0, y0, x1, y1, data ) :
self._setwindowloc((x0, y0), (x1, y1))
self._writedata(data)
We will find that the command _serwindowloc() is the same but different from _pushcolor() and _writedata(), the working code is as follows.
#@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)
As you’ll notice, _pushcolor() calls _writedata() as well, so if you want to draw a character of 8×8 pixels, char() has to send pixels to the display module as follows:
- Number of times to send data = 8×8 = 64 times
- Each time using instructions = 2 commands
- 64 times, it takes operations = 2*64 = 128 times.
But if we store the characters in a buffer or array of 8×8 members or 64 characters, it will look like this:
- Number of times to send data = 1 time
- Each time using instructions = 2 commands
- 1 time for operation = 1+ number of data = 1+64 = 65 times
From a rough comparison above excluding that the command pixel() calls _writedata(), which increases the number of operations, it is found that the command to draw a letter size of 8×8 pixels, the normal drawing method uses 128 operations (namely, positioning and sending color values) with another method that takes data from memory to the display module, 65 operations indicating where to send and then another 64 sets will find that the use of instructions was significantly reduced.
Therefore, when applied to a large number of draws on each display, the difference in render speed will be even greater. However Double buffering requires a large amount of memory to hold the display buffer. When used with Blue tab size 160×80 pixels, there must be approximately the amount of memory as follows.
- Amount of memory = Width x Height x 2 bytes = 160 x 80 x 2 = 25,600 bytes.
The esp8266 and esp32 microcontrollers have enough memory of 25,600 bytes for display storage.
Note
We compare the number of times, not the time to show the number of times. It doesn’t measure the speed of each command.
Buffer
One of Micropython’s major libraries is framebuf, which serves as a drawing board. By drawing into memory according to the format of the pixels that can be defined as follows
- RGB565
- MONO_VLSB
- GS2_HMSB
- GS4_HMSB
- GS8
- MONO_HLSB
- MONO_HMSB
Additionally, when we choose to use buffers from the framebuf class, we can use the following command to draw pixels into memory. The in-memory draw is faster than TFT modules as SPIRAM supports 80MHz.
- fill()
- fill_rect()
- pixel()
- hline()
- vline()
- rect()
- line()
- blit()
- scroll()
- text()
From the instructions provided, it will be found that it is sufficient to use and can be written more without difficulty and we have improved the code.
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)
In addition, a set of instructions for reading color values and entering color values in memory have been added as follows:
def setPixel( self, aX, aY, aColor ) :
self.buffer.pixel(aX, aY, aColor)
def readPixel( self, aX, aY ):
return self.buffer.pixel(aX, aY)
The part that must be done when drawing things into memory is delivering this buffer to the display module via the SPI bus with the following code.
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)
Improved library
The idea of making a double buffer and using it with ST7735 BLUE TAB 0.96 inch size 80×160 has a library of work as follows.
#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)
The implementation relies on the swap() command as the final command to bring the data from the buffer to display in the TFT module. Therefore, the programmer must design the code to suit the double-buffer operation as well.
Code
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()
The result is shown in Figure 2.
From the modified code, the result is shown in Figure 3.
For testing with old libraries write the following code: and the result is as shown in Figure 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()
Modifications for use with REDTAB80x160
Code for 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)
Conclusion
From this article, we hope that you will be stuck in the matter of improving the efficiency of the code studied from the Internet. And it gets even better when we roll out our improvements so others can spot bugs and improve the code of our predecessors, including us who pushed it for better service life and performance.
Finally, have fun with programming.
If you want to talk with us, feel free to leave comments below!!
(C) 2020-2021, By Jarut Busarathid and Danai Jedsadathitikul
Updated 2021-12-15