#!/usr/bin/python
__author__ = "D.Qendri"
__copyright__ = "Copyright 2015 Sensorian"
__license__ = "GPL V3"
__version__ = "1.0"
import numbers
import time
import Image
import ImageDraw
import RPi.GPIO as GPIO
import spidev as spi
spi = spi.SpiDev()
#Loosely based on some snippets from Adafruit display driver.
# Constants for interacting with display registers.
TFT_WIDTH = 128 # Screen Width
TFT_HEIGHT = 160 # Scree Height
NOP = 0x00
SWRESET = 0x01
RDDID = 0x04
RDDST = 0x09
RDDPM = 0x0A
RDDMADCTL = 0x0B # Read Display MADCTL
RDDCOLMOD = 0x0C # Read Display Pixel Format
RDDIM = 0x0D # Read Display Image Mode
RDDSM = 0x0E # Read Display Signal Mode
RDDSDR = 0x0F # Read Display Self-Diagnostic Result
SLEEP_IN = 0x10
SLEEP_OUT = 0x11
PTLON = 0x12 # Partial Display Mode On
NORON = 0x13 # Normal Display Mode On
INVOFF = 0x20
INVON = 0x21 # Display Inversion On
GAMSET = 0x26 # Gamma Set
DISPOFF = 0x28
DISPON = 0x29
CASET = 0x2A
RASET = 0x2B
RAMWR = 0x2C
RGBSET = 0x2D #Color set
RAMRD = 0x2E
PTLAR = 0x30
SCRLAR = 0x33 # Scroll Area Set
TEOFF = 0x34
TEON = 0x35
MADCTL = 0x36
VSCSAD = 0x37 # Vertical Scroll Start Address of RAM
IDLE_MODE_OFF = 0x38
IDLE_MODE_ON = 0x39
COLMOD = 0x3A # Interface Pixel Format
RDID1 = 0xDA
RDID2 = 0xDB
RDID3 = 0xDC # Read ID3 Value
FRMCTR1 = 0xB1 # Frame Rate Control (In normal mode/ Full colors)
FRMCTR2 = 0xB2 # Frame Rate Control (In Idle mode/ 8-colors)
FRMCTR3 = 0xB3 # Frame Rate Control (In Partial mode/ full colors)
INVCTR = 0xB4 # Display Inversion Control
DISSET5 = 0xB6
PWCTR1 = 0xC0
PWCTR2 = 0xC1
PWCTR3 = 0xC2
PWCTR4 = 0xC3
PWCTR5 = 0xC4
VMCTR1 = 0xC5
VMOFCTR = 0xC7
WRID2 = 0xD1
WRID3 = 0xD2
NVFCTR1 = 0xD9 # NVM Control Status
NVFCTR2 = 0xDE
NVFCTR3 = 0xDF
GMCTRP1 = 0xE0
GMCTRN1 = 0xE1
GCV = 0xFC
DUMMY = 0xff
#####################################################
BLACK = 0x0000
BLUE = 0x001F
RED = 0xF800
GREEN = 0x07E0
CYAN = 0x07FF
MAGENTA = 0xF81F
YELLOW = 0xFFE0
WHITE = 0xFFFF
#####################################################
MADCTL_MY = 0x80
MADCTL_MX = 0x40
MADCTL_MV = 0x20
MADCTL_ML = 0x10
MADCTL_MH = 0x04
MADCTL_M1 = 0x00
MADCTL_M2 = 0b10000000
MADCTL_M3 = 0b01000000
MADCTL_M4 = 0b11000000
MADCTL_M5 = 0b00100000
MADCTL_M6 = 0b10100000
MADCTL_M7 = 0b01100000
MADCTL_M8 = 0b11100000
MADCTL_RGB = 0x00
MADCTL_BGR = 0x08
GAMMA1 = 0x01
GAMMA2 = 0x02
GAMMA3 = 0x04
GAMMA4 = 0x08
PIXEL12BIT = 0x03
PIXEL16BIT = 0x05
PIXEL18BIT = 0x06
#####################################################
DC = 22
RST = 16
TFT_CS = 24
ON = 1
OFF = 0
[docs]def color565(r, g, b):
"""
Convert red, green, blue components to a 16-bit 565 RGB value.
Components should be values 0 to 255.
:param r: Red byte.
:param g: Green byte.
:param b: Blue byte.
:returns: pixel - 16-bit 565 RGB value
"""
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
[docs]def image_to_data(image):
"""Generator function to convert a PIL image to 16-bit 565 RGB bytes.
:param image: PIL image
:returns: imgArray
"""
pixels = image.convert('RGB').load()
width, height = image.size
for y in range(height):
for x in range(width):
r,g,b = pixels[(x,y)]
color = color565(r, g, b)
yield (color >> 8) & 0xFF
yield color & 0xFF
[docs]class TFT(object):
"""FW Driver for an ST7735S TFT controller."""
def __init__(self):
"""
Creates a TFT object and configures SPI bus and pins.
:param none:
:returns: none
"""
self.dc = DC
self.rst = RST
self.cs = TFT_CS
self.width = TFT_WIDTH
self.height = TFT_HEIGHT
spi.open(0,1) #will open bus 0, CE1.
#spi.max_speed_hz = 8000000
spi.max_speed_hz = int(16000000)
GPIO.setwarnings(True)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(self.cs, GPIO.OUT) #Set GPIO24 as output
GPIO.setup(self.dc, GPIO.OUT) # Set DC as output.
GPIO.setup(self.rst, GPIO.OUT)
self.CE_DESELECT()
self.buffer = Image.new('RGB', (self.width, self.height)) # Create an image buffer.
[docs] def CE_OUTPUT(self):
"""
Set TFT CS pin as output
:param none:
:returns: none
"""
GPIO.setup(self.cs, GPIO.OUT) #Set GPIO24 as output
[docs] def CE_SELECT(self):
"""
Select TFT CS pin.
:param none:
:returns: none
"""
GPIO.output(self.cs, 0)
[docs] def CE_DESELECT(self):
"""
Deselect TFT CS pin.
:param none:
:returns: none
"""
GPIO.output(self.cs, 1)
[docs] def send(self, data, dataOrCmd=True, length=4096):
"""
Writes a byte or array of bytes to the display.
dataOrCmd parameter controls if byte should be interpreted as display data (True)/ command data otherwise.
Length is an optional size of bytes to write in a single SPI transaction, with a default of 4096.
:param data: Single byte or an array of bytes.
:param dataOrCmd: Flag for command or data mode
:param length: size of array
:returns: none
"""
# Set DC low for command, high for data.
GPIO.output(self.dc, dataOrCmd)
# Convert scalar argument to list so either can be passed as parameter.
if isinstance(data, numbers.Number):
data = [data & 0xFF]
# Write data a chunk at a time.
for start in range(0, len(data), length):
end = min(start+length, len(data))
spi.writebytes(data[start:end])
[docs] def command(self, data):
"""
Write a byte or array of bytes to the display as command data.
:param data: Single byte command.
:returns: none
"""
self.CE_SELECT()
self.send(data, False)
self.CE_DESELECT()
[docs] def data(self, data):
"""
Write a byte or array of bytes to the display as display data.
:param data: Data byte
:returns: none
"""
self.CE_SELECT()
self.send(data, True)
self.CE_DESELECT()
[docs] def reset(self):
"""
Resets the display, (RST) reset pin must be connected.
:param none:
:returns: none
"""
if self._rst is not None:
GPIO.output(self.rst,1)
time.sleep(0.005)
GPIO.output(self.rst,0)
time.sleep(0.02)
GPIO.output(self.rst,1)
time.sleep(0.150)
[docs] def initialize(self):
"""
Intializes the display controller and prepares the it for any subsequent operations.
:param none:
:returns: none
"""
GPIO.setup(self.dc, GPIO.OUT)
GPIO.setup(self.rst, GPIO.OUT)
GPIO.setup(TFT_CS, GPIO.OUT)
GPIO.output(self.dc, 0)
GPIO.output(self.rst, 1)
self.command(SWRESET)
time.sleep(0.015)
self.command(SLEEP_OUT)
time.sleep(0.60)
self.command(FRMCTR1) # Set ST7735S Frame Rate
self.data(0x01)
self.data(0x2C)
self.data(0x2D)
self.command(FRMCTR2)
self.data(0x01)
self.data(0x2C)
self.data(0x2D)
self.command(FRMCTR3)
self.data(0x01)
self.data(0x2C)
self.data(0x2D)
self.data(0x01)
self.data(0x2C)
self.data(0x2D)
self.command(INVCTR) # Display Inversion Control
self.data(0x07)
self.command(PWCTR1)
self.data(0xA2)
self.data(0x02)
self.data(0x84)
self.command(PWCTR2)
self.data(0XC5)
self.command(PWCTR3) # Power Control 3 (in Normal mode/ Full colors)
self.data(0x0A)
self.data(0x00)
self.command(PWCTR4) # Power Control 4 (in Idle mode/ 8-colors)
self.data(0x8A)
self.data(0x2A)
self.command(PWCTR5)
self.data(0x8A)
self.data(0xEE)
self.command(VMCTR1) # ST7735S Power Sequence
self.data(0x0E)
self.command(INVOFF)
self.command(MADCTL) #MX, MY, RGB mode
self.data(0xC0)
self.command(COLMOD) #65k mode
self.data(0x05)
self.command(CASET)
self.data(0x00)
self.data(0x7F)
self.command(RASET)
self.data(0x00)
self.data(0x9F)
self.command(GMCTRP1) # ST7735S Gamma Sequence
self.data(0x02)
self.data(0x1c)
self.data(0x07)
self.data(0x12)
self.data(0x37)
self.data(0x32)
self.data(0x29)
self.data(0x2d)
self.data(0x29)
self.data(0x25)
self.data(0x2B)
self.data(0x39)
self.data(0x00)
self.data(0x01)
self.data(0x03)
self.data(0x10)
self.command(GMCTRN1)
self.data(0x03)
self.data(0x1d)
self.data(0x07)
self.data(0x06)
self.data(0x2E)
self.data(0x2C)
self.data(0x29)
self.data(0x2D)
self.data(0x2E)
self.data(0x2E)
self.data(0x37)
self.data(0x3F)
self.data(0x00)
self.data(0x00)
self.data(0x02)
self.data(0x10)
self.command(NORON) # Normal display on, no args, w/delay 10ms 0x13
time.sleep(0.100) # End ST7735S Gamma Sequence
self.command(DISPON) # Display on
[docs] def setAddrWindow(self, x0=0, y0=0, x1=None, y1=None):
"""
Set the pixel address window for proceeding drawing commands.
:param x0: x0 and x1 should define the minimum and maximum x pixel bounds.
:param y0:
:param x1: y0 and y1 should define the minimum and maximum y pixel bound.
:param y1:
:returns: none
"""
if x1 is None:
x1 = self.width-1
if y1 is None:
y1 = self.height-1
self.command(CASET) # Column addr set
self.data(x0 >> 8)
self.data(x0) # XSTART
self.data(x1 >> 8)
self.data(x1) # XEND
self.command(RASET) # Row addr set
self.data(y0 >> 8)
self.data(y0) # YSTART
self.data(y1 >> 8)
self.data(y1) # YEND
self.command(RAMWR) # write to RAM
[docs] def display(self, image=None):
"""
Write the provided image to the hardware. If no image parameter is provided the display buffer will be written to the hardware.
If an image is provided, it should be RGB format and the same dimensions as the display hardware.
:param image: picture image
:returns: none
"""
# By default write the internal buffer to the display.
if image is None:
image = self.buffer
# Set address bounds to entire display.
self.setAddrWindow()
# Convert image to array of 16bit 565 RGB data bytes.
# Unfortunate that this copy has to occur, but the SPI byte writing
# function needs to take an array of bytes and PIL doesn't natively
# store images in 16-bit 565 RGB format.
pixelbytes = list(image_to_data(image))
# Write data to hardware.
self.data(pixelbytes)
[docs] def clear(self, color=(0,0,0)):
"""
Clear the image buffer to the specified RGB color (default black).
:param color: Background color.
:returns: none
"""
width, height = self.buffer.size
self.buffer.putdata([color]*(width*height))
[docs] def draw(self):
"""
Return a PIL ImageDraw instance for 2D drawing on the image buffer.
:param none:
:returns: none
"""
return ImageDraw.Draw(self.buffer)
[docs] def invert(self,status):
"""Disables color inversion on the display.
:param status: Color inversion status.
:returns: none
"""
if (status == False):
self.command(INVOFF)
else:
self.command(INVON)
[docs] def setRotation(self,mode):
"""Sets the display text orientation. Mirrored modes are
also supported on top of portrait and landscape modes.
:param mode: orientation data
:returns: none
"""
self.command(MADCTL)
if (mode == 0x00):
self.data(MADCTL_MY | MADCTL_MX| MADCTL_BGR) #portrait
elif (mode == 0x01):
self.data(MADCTL_MV |MADCTL_ML| MADCTL_BGR) #Landscape mode reflected
elif (mode == 0x02):
self.data(MADCTL_MY | MADCTL_BGR) #Portrait mode reflected
elif (mode == 0x03):
self.data(MADCTL_MX | MADCTL_BGR) #Portarit mode inverted and reflected
elif (mode == 0x04):
self.data(MADCTL_MV | MADCTL_BGR | MADCTL_MX) #Landscape mode inverted
elif (mode == 0x05):
self.data(MADCTL_ML | MADCTL_BGR) #Portrait inverted
elif (mode == 0x06):
self.data(MADCTL_MV|MADCTL_MY | MADCTL_BGR) #Landscape mode
else :
self.data(MADCTL_M8 | MADCTL_BGR) #Landscape mode reflected and inverted
[docs] def setGamma(self,gamma):
"""
Sets the gamma mode of the display.
:param gamma: Is from 1 to 4.
:returns: none
"""
self.command(NORON)
if(gamma == 1):
self.data(GAMMA1)
elif(gamma == 2):
self.data(GAMMA2)
elif(gamma == 3):
self.data(GAMMA3)
else:
self.data(GAMMA4)
[docs] def setPartialArea(self,start,end):
"""
Sets the partial scroll area. This function is used in conjuction with the scrollArea function.
:param start: start of patial area
:param end: end of patial area
:returns: none
"""
self.command(PTLON)
self.data(start) # start row
self.data(start >> 8)
self.data(end) # end row
self.data(end >> 8)
self.command(PTLON)
[docs] def fullDisplay(self):
"""
Sets the display in full screen mode.
:param none:
:returns: none
"""
self.command(NORON)
[docs] def setColorMode(slef,mode):
"""
Sets the pixel color depth. This can be 12,16 or 18 bit.
:param mode: Constant number
:returns: none
"""
self.command(COLMOD)
self.data(mode)
[docs] def sleep(self):
"""
Return a PIL ImageDraw instance for 2D drawing on the image buffer.
:param none:
:returns: none
"""
self.command(SLEEP_IN)
time.sleep(0.005)
[docs] def wakeUp(self):
"""
Wakes the display from sleep mode.
:param none:
:returns: none
"""
self.command(SLEEP_OUT)
time.sleep(0.120)
[docs] def idleMode(self,onoff):
"""
Puts the display or takes it out of idle mode
:param onoff: idle mode status
:returns: none
"""
if(onoff == 0):
self.command(SIDLE_MODE_OFF)
else:
self.command(SIDLE_MODE_ON)
[docs] def turnOff(self):
"""
Blanks out the display.
:param none:
:returns: none
"""
self.command(DISPOFF)
[docs] def turnOn(self):
"""
This function turn on the display from idle mode.
:param none:
:returns: none
"""
self.command(DISPON)