mirror of
https://github.com/pimoroni/st7735-python.git
synced 2025-01-05 22:40:25 +03:00
Support for non 160x80 displays
This commit is contained in:
@@ -20,7 +20,6 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import signal
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import ST7735 as ST7735
|
import ST7735 as ST7735
|
||||||
@@ -31,18 +30,21 @@ if len(sys.argv) < 2:
|
|||||||
|
|
||||||
image_file = sys.argv[1]
|
image_file = sys.argv[1]
|
||||||
|
|
||||||
WIDTH = ST7735.ST7735_TFTWIDTH
|
|
||||||
HEIGHT = ST7735.ST7735_TFTHEIGHT
|
|
||||||
|
|
||||||
# Create ST7735 LCD display class.
|
# Create ST7735 LCD display class.
|
||||||
disp = ST7735.ST7735(
|
disp = ST7735.ST7735(
|
||||||
port=0,
|
port=0,
|
||||||
cs=0,
|
cs=0,
|
||||||
dc=24,
|
dc=24,
|
||||||
backlight=18,
|
backlight=18,
|
||||||
|
rotation=90,
|
||||||
spi_speed_hz=4000000
|
spi_speed_hz=4000000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
WIDTH = disp.width
|
||||||
|
HEIGHT = disp.height
|
||||||
|
|
||||||
|
print(WIDTH, HEIGHT)
|
||||||
|
|
||||||
# Initialize display.
|
# Initialize display.
|
||||||
disp.begin()
|
disp.begin()
|
||||||
|
|
||||||
@@ -50,14 +52,10 @@ disp.begin()
|
|||||||
print('Loading image: {}...'.format(image_file))
|
print('Loading image: {}...'.format(image_file))
|
||||||
image = Image.open(image_file)
|
image = Image.open(image_file)
|
||||||
|
|
||||||
# Resize the image and rotate it so matches the display.
|
# Resize the image
|
||||||
# image = image.rotate(90).resize((WIDTH, HEIGHT))
|
|
||||||
image = image.resize((WIDTH, HEIGHT))
|
image = image.resize((WIDTH, HEIGHT))
|
||||||
|
|
||||||
# Draw the image on the display hardware.
|
# Draw the image on the display hardware.
|
||||||
print('Drawing image')
|
print('Drawing image')
|
||||||
|
|
||||||
disp.display(image)
|
disp.display(image)
|
||||||
|
|
||||||
print("Press Ctl+C to exit!")
|
|
||||||
signal.pause()
|
|
||||||
|
|||||||
@@ -18,15 +18,12 @@
|
|||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
import signal
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL import ImageDraw
|
from PIL import ImageDraw
|
||||||
from PIL import ImageFont
|
from PIL import ImageFont
|
||||||
|
|
||||||
import ST7735
|
import ST7735
|
||||||
|
|
||||||
WIDTH = ST7735.ST7735_TFTWIDTH
|
|
||||||
HEIGHT = ST7735.ST7735_TFTHEIGHT
|
|
||||||
|
|
||||||
# Create ST7735 LCD display class.
|
# Create ST7735 LCD display class.
|
||||||
disp = ST7735.ST7735(
|
disp = ST7735.ST7735(
|
||||||
@@ -34,21 +31,23 @@ disp = ST7735.ST7735(
|
|||||||
cs=0,
|
cs=0,
|
||||||
dc=24,
|
dc=24,
|
||||||
backlight=18,
|
backlight=18,
|
||||||
|
rotation=90,
|
||||||
spi_speed_hz=4000000
|
spi_speed_hz=4000000
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize display.
|
# Initialize display.
|
||||||
disp.begin()
|
disp.begin()
|
||||||
|
|
||||||
|
WIDTH = disp.width
|
||||||
|
HEIGHT = disp.height
|
||||||
|
|
||||||
|
|
||||||
# Clear the display to a red background.
|
# Clear the display to a red background.
|
||||||
# Can pass any tuple of red, green, blue values (from 0 to 255 each).
|
# Can pass any tuple of red, green, blue values (from 0 to 255 each).
|
||||||
disp.clear((255, 0, 0))
|
|
||||||
|
|
||||||
# Alternatively can clear to a black screen by calling:
|
|
||||||
# disp.clear()
|
|
||||||
|
|
||||||
# Get a PIL Draw object to start drawing on the display buffer.
|
# Get a PIL Draw object to start drawing on the display buffer.
|
||||||
draw = disp.draw()
|
img = Image.new('RGB', (WIDTH, HEIGHT), color=(255, 0, 0))
|
||||||
|
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
# Draw a purple rectangle with yellow outline.
|
# Draw a purple rectangle with yellow outline.
|
||||||
draw.rectangle((10, 10, WIDTH-10, HEIGHT-10), outline=(255,255,0), fill=(255,0,255))
|
draw.rectangle((10, 10, WIDTH-10, HEIGHT-10), outline=(255,255,0), fill=(255,0,255))
|
||||||
@@ -89,12 +88,9 @@ def draw_rotated_text(image, text, position, angle, font, fill=(255,255,255)):
|
|||||||
image.paste(rotated, position, rotated)
|
image.paste(rotated, position, rotated)
|
||||||
|
|
||||||
# Write two lines of white text on the buffer, rotated 90 degrees counter clockwise.
|
# Write two lines of white text on the buffer, rotated 90 degrees counter clockwise.
|
||||||
draw_rotated_text(disp.buffer, 'Hello World!', (0, 0), 90, font, fill=(255,255,255))
|
draw_rotated_text(img, 'Hello World!', (0, 0), 90, font, fill=(255,255,255))
|
||||||
draw_rotated_text(disp.buffer, 'This is a line of text.', (10, HEIGHT-10), 0, font, fill=(255,255,255))
|
draw_rotated_text(img, 'This is a line of text.', (10, HEIGHT-10), 0, font, fill=(255,255,255))
|
||||||
|
|
||||||
# Write buffer to display hardware, must be called to make things visible on the
|
# Write buffer to display hardware, must be called to make things visible on the
|
||||||
# display!
|
# display!
|
||||||
disp.display()
|
disp.display(img)
|
||||||
|
|
||||||
print("Press Ctl+C to exit!")
|
|
||||||
signal.pause()
|
|
||||||
|
|||||||
@@ -21,22 +21,19 @@
|
|||||||
import numbers
|
import numbers
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import atexit
|
|
||||||
|
|
||||||
import spidev
|
import spidev
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from PIL import ImageDraw
|
|
||||||
|
|
||||||
GPIO.setwarnings(False)
|
|
||||||
GPIO.setmode(GPIO.BCM)
|
|
||||||
|
|
||||||
SPI_CLOCK_HZ = 16000000 # 4 MHz
|
SPI_CLOCK_HZ = 16000000 # 4 MHz
|
||||||
|
|
||||||
# Constants for interacting with display registers.
|
# Constants for interacting with display registers.
|
||||||
ST7735_TFTWIDTH = 160
|
ST7735_TFTWIDTH = 80
|
||||||
ST7735_TFTHEIGHT = 80
|
ST7735_TFTHEIGHT = 160
|
||||||
|
|
||||||
|
ST7735_COLS = 132
|
||||||
|
ST7735_ROWS = 162
|
||||||
|
|
||||||
ST7735_NOP = 0x00
|
ST7735_NOP = 0x00
|
||||||
ST7735_SWRESET = 0x01
|
ST7735_SWRESET = 0x01
|
||||||
@@ -113,11 +110,11 @@ def color565(r, g, b):
|
|||||||
"""
|
"""
|
||||||
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
|
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
|
||||||
|
|
||||||
def image_to_data(image):
|
def image_to_data(image, rotation=0):
|
||||||
"""Generator function to convert a PIL image to 16-bit 565 RGB bytes."""
|
"""Generator function to convert a PIL image to 16-bit 565 RGB bytes."""
|
||||||
# NumPy is much faster at doing this. NumPy code provided by:
|
# NumPy is much faster at doing this. NumPy code provided by:
|
||||||
# Keith (https://www.blogger.com/profile/02555547344016007163)
|
# Keith (https://www.blogger.com/profile/02555547344016007163)
|
||||||
pb = np.rot90(np.array(image.convert('RGB')), 1).astype('uint16')
|
pb = np.rot90(np.array(image.convert('RGB')), rotation // 90).astype('uint16')
|
||||||
color = ((pb[:,:,0] & 0xF8) << 8) | ((pb[:,:,1] & 0xFC) << 3) | (pb[:,:,2] >> 3)
|
color = ((pb[:,:,0] & 0xF8) << 8) | ((pb[:,:,1] & 0xFC) << 3) | (pb[:,:,2] >> 3)
|
||||||
return np.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
|
return np.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
|
||||||
|
|
||||||
@@ -125,15 +122,29 @@ class ST7735(object):
|
|||||||
"""Representation of an ST7735 TFT LCD."""
|
"""Representation of an ST7735 TFT LCD."""
|
||||||
|
|
||||||
def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH,
|
def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH,
|
||||||
height=ST7735_TFTHEIGHT, spi_speed_hz=4000000):
|
height=ST7735_TFTHEIGHT, rotation=90, offset_left=None, offset_top=None, invert=True, spi_speed_hz=4000000):
|
||||||
"""Create an instance of the display using SPI communication. Must
|
"""Create an instance of the display using SPI communication. Must
|
||||||
provide the GPIO pin number for the D/C pin and the SPI driver. Can
|
provide the GPIO pin number for the D/C pin and the SPI driver. Can
|
||||||
optionally provide the GPIO pin number for the reset pin as the rst
|
optionally provide the GPIO pin number for the reset pin as the rst
|
||||||
parameter.
|
parameter.
|
||||||
|
|
||||||
:param port: SPI port number
|
:param port: SPI port number
|
||||||
:param cs: SPI CS number (0 or 1 for BCM
|
:param cs: SPI chip-select number (0 or 1 for BCM
|
||||||
|
:param backlight: Pin for controlling backlight
|
||||||
|
:param rst: Reset pin for ST7735
|
||||||
|
:param width: Width of display connected to ST7735
|
||||||
|
:param height: Height of display connected to ST7735
|
||||||
|
:param rotation: Rotation of display connected to ST7735
|
||||||
|
:param offset_left: COL offset in ST7735 memory
|
||||||
|
:param offset_top: ROW offset in ST7735 memory
|
||||||
|
:param invert: Invert display
|
||||||
|
:param spi_speed_hz: SPI speed (in Hz)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
|
||||||
self._spi = spidev.SpiDev(port, cs)
|
self._spi = spidev.SpiDev(port, cs)
|
||||||
self._spi.mode = 0
|
self._spi.mode = 0
|
||||||
self._spi.lsbfirst = False
|
self._spi.lsbfirst = False
|
||||||
@@ -141,8 +152,22 @@ class ST7735(object):
|
|||||||
|
|
||||||
self._dc = dc
|
self._dc = dc
|
||||||
self._rst = rst
|
self._rst = rst
|
||||||
self.width = width
|
self._width = width
|
||||||
self.height = height
|
self._height = height
|
||||||
|
self._rotation = rotation
|
||||||
|
self._invert = invert
|
||||||
|
|
||||||
|
# Default left offset to center display
|
||||||
|
if offset_left is None:
|
||||||
|
offset_left = (ST7735_COLS - width) // 2
|
||||||
|
|
||||||
|
self._offset_left = offset_left
|
||||||
|
|
||||||
|
# Default top offset to center display
|
||||||
|
if offset_top is None:
|
||||||
|
offset_top = (ST7735_ROWS - height) // 2
|
||||||
|
|
||||||
|
self._offset_top = offset_top
|
||||||
|
|
||||||
# Set DC as output.
|
# Set DC as output.
|
||||||
GPIO.setup(dc, GPIO.OUT)
|
GPIO.setup(dc, GPIO.OUT)
|
||||||
@@ -155,8 +180,6 @@ class ST7735(object):
|
|||||||
if rst is not None:
|
if rst is not None:
|
||||||
GPIO.setup(rst, GPIO.OUT)
|
GPIO.setup(rst, GPIO.OUT)
|
||||||
|
|
||||||
# Create an image buffer.
|
|
||||||
self.buffer = Image.new('RGB', (width, height))
|
|
||||||
|
|
||||||
def send(self, data, is_data=True, chunk_size=4096):
|
def send(self, data, is_data=True, chunk_size=4096):
|
||||||
"""Write a byte or array of bytes to the display. Is_data parameter
|
"""Write a byte or array of bytes to the display. Is_data parameter
|
||||||
@@ -174,6 +197,14 @@ class ST7735(object):
|
|||||||
end = min(start+chunk_size, len(data))
|
end = min(start+chunk_size, len(data))
|
||||||
self._spi.xfer(data[start:end])
|
self._spi.xfer(data[start:end])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self._width if self._rotation == 0 or self._rotation == 180 else self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self._height if self._rotation == 0 or self._rotation == 180 else self._width
|
||||||
|
|
||||||
def command(self, data):
|
def command(self, data):
|
||||||
"""Write a byte or array of bytes to the display as command data."""
|
"""Write a byte or array of bytes to the display as command data."""
|
||||||
self.send(data, False)
|
self.send(data, False)
|
||||||
@@ -243,7 +274,10 @@ class ST7735(object):
|
|||||||
self.command(ST7735_VMCTR1) # Power control
|
self.command(ST7735_VMCTR1) # Power control
|
||||||
self.data(0x0E)
|
self.data(0x0E)
|
||||||
|
|
||||||
self.command(ST7735_INVON) # Don't invert display
|
if self._invert:
|
||||||
|
self.command(ST7735_INVON) # Invert display
|
||||||
|
else:
|
||||||
|
self.command(ST7735_INVOFF) # Don't invert display
|
||||||
|
|
||||||
self.command(ST7735_MADCTL) # Memory access control (directions)
|
self.command(ST7735_MADCTL) # Memory access control (directions)
|
||||||
self.data(0xC8) # row addr/col addr, bottom to top refresh
|
self.data(0xC8) # row addr/col addr, bottom to top refresh
|
||||||
@@ -254,15 +288,15 @@ class ST7735(object):
|
|||||||
|
|
||||||
self.command(ST7735_CASET) # Column addr set
|
self.command(ST7735_CASET) # Column addr set
|
||||||
self.data(0x00) # XSTART = 0
|
self.data(0x00) # XSTART = 0
|
||||||
self.data(132 - ST7735_TFTHEIGHT)
|
self.data(self._offset_left)
|
||||||
self.data(0x00) # XEND = 127
|
self.data(0x00) # XEND = ROWS - height
|
||||||
self.data(ST7735_TFTHEIGHT - 1)
|
self.data(self._width + self._offset_left - 1)
|
||||||
|
|
||||||
self.command(ST7735_RASET) # Row addr set
|
self.command(ST7735_RASET) # Row addr set
|
||||||
self.data(0x00) # XSTART = 0
|
self.data(0x00) # XSTART = 0
|
||||||
self.data(161 - ST7735_TFTWIDTH)
|
self.data(self._offset_top)
|
||||||
self.data(0x00) # XEND = 159
|
self.data(0x00) # XEND = COLS - width
|
||||||
self.data(ST7735_TFTWIDTH - 1)
|
self.data(self._height + self._offset_top - 1)
|
||||||
|
|
||||||
|
|
||||||
self.command(ST7735_GMCTRP1) # Set Gamma
|
self.command(ST7735_GMCTRP1) # Set Gamma
|
||||||
@@ -313,7 +347,6 @@ class ST7735(object):
|
|||||||
"""
|
"""
|
||||||
self.reset()
|
self.reset()
|
||||||
self._init()
|
self._init()
|
||||||
atexit.register(self.clear_on_exit)
|
|
||||||
|
|
||||||
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
def set_window(self, x0=0, y0=0, x1=None, y1=None):
|
||||||
"""Set the pixel address window for proceeding drawing commands. x0 and
|
"""Set the pixel address window for proceeding drawing commands. x0 and
|
||||||
@@ -323,59 +356,42 @@ class ST7735(object):
|
|||||||
to width-1,height-1.
|
to width-1,height-1.
|
||||||
"""
|
"""
|
||||||
if x1 is None:
|
if x1 is None:
|
||||||
x1 = self.width-1
|
x1 = self._width-1
|
||||||
|
|
||||||
if y1 is None:
|
if y1 is None:
|
||||||
y1 = self.height-1
|
y1 = self._height-1
|
||||||
|
|
||||||
y0 += 26
|
y0 += self._offset_top
|
||||||
y1 += 26
|
y1 += self._offset_top
|
||||||
|
|
||||||
x0 += 1
|
x0 += self._offset_left
|
||||||
x1 += 1
|
x1 += self._offset_left
|
||||||
|
|
||||||
self.command(ST7735_CASET) # Column addr set
|
self.command(ST7735_CASET) # Column addr set
|
||||||
self.data(y0 >> 8)
|
|
||||||
self.data(y0) # XSTART
|
|
||||||
self.data(y1 >> 8)
|
|
||||||
self.data(y1) # XEND
|
|
||||||
self.command(ST7735_RASET) # Row addr set
|
|
||||||
self.data(x0 >> 8)
|
self.data(x0 >> 8)
|
||||||
self.data(x0) # YSTART
|
self.data(x0) # XSTART
|
||||||
self.data(x1 >> 8)
|
self.data(x1 >> 8)
|
||||||
self.data(x1) # YEND
|
self.data(x1) # XEND
|
||||||
|
self.command(ST7735_RASET) # Row addr set
|
||||||
|
self.data(y0 >> 8)
|
||||||
|
self.data(y0) # YSTART
|
||||||
|
self.data(y1 >> 8)
|
||||||
|
self.data(y1) # YEND
|
||||||
self.command(ST7735_RAMWR) # write to RAM
|
self.command(ST7735_RAMWR) # write to RAM
|
||||||
|
|
||||||
def display(self, image=None):
|
def display(self, image):
|
||||||
"""Write the display buffer or provided image to the hardware. If no
|
"""Write the provided image to the hardware.
|
||||||
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
|
:param image: Should be RGB format and the same dimensions as the display hardware.
|
||||||
same dimensions as the display hardware.
|
|
||||||
"""
|
"""
|
||||||
# By default write the internal buffer to the display.
|
|
||||||
if image is None:
|
|
||||||
image = self.buffer
|
|
||||||
# Set address bounds to entire display.
|
# Set address bounds to entire display.
|
||||||
self.set_window()
|
self.set_window()
|
||||||
# Convert image to array of 16bit 565 RGB data bytes.
|
# Convert image to array of 16bit 565 RGB data bytes.
|
||||||
# Unfortunate that this copy has to occur, but the SPI byte writing
|
# 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
|
# function needs to take an array of bytes and PIL doesn't natively
|
||||||
# store images in 16-bit 565 RGB format.
|
# store images in 16-bit 565 RGB format.
|
||||||
pixelbytes = list(image_to_data(image))
|
pixelbytes = list(image_to_data(image, self._rotation))
|
||||||
# Write data to hardware.
|
# Write data to hardware.
|
||||||
self.data(pixelbytes)
|
self.data(pixelbytes)
|
||||||
|
|
||||||
def clear(self, color=(0,0,0)):
|
|
||||||
"""Clear the image buffer to the specified RGB color (default black)."""
|
|
||||||
width, height = self.buffer.size
|
|
||||||
self.buffer.putdata([color]*(width*height))
|
|
||||||
|
|
||||||
def draw(self):
|
|
||||||
"""Return a PIL ImageDraw instance for 2D drawing on the image buffer."""
|
|
||||||
return ImageDraw.Draw(self.buffer)
|
|
||||||
|
|
||||||
def clear_on_exit(self):
|
|
||||||
self.clear()
|
|
||||||
self.display()
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user