Skip to content
58 changes: 51 additions & 7 deletions adafruit_rgb_display/rgb.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"""

import time
from micropython import const
try:
import struct
except ImportError:
Expand All @@ -41,9 +40,27 @@
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RGB_Display.git"

# This is the size of the buffer to be used for fill operations, in 16-bit
# units. We use 256, which is 512 bytes — size of the DMA buffer on SAMD21.
_BUFFER_SIZE = const(256)

# units.
try:
# If we're on CPython, try to set as large as possible
import platform
if "CPython" in platform.python_implementation():
# check for FT232H special case
try:
import os
if os.environ['BLINKA_FT232H']:
# we are limited by pyftdi's max SPI payload
from pyftdi.spi import SpiController
_BUFFER_SIZE = SpiController.PAYLOAD_MAX_LENGTH // 2 # max bytes / bytes per pixel
except KeyError:
# otherwise set it to blit the whole thing
_BUFFER_SIZE = 320 * 240
else:
# in case CircuitPython ever implements platform
_BUFFER_SIZE = 256
except ImportError:
# Otherwise set smaller MCU friendly size
_BUFFER_SIZE = 256

def color565(r, g=0, b=0):
"""Convert red, green and blue values (0-255) into a 16-bit 565 encoding. As
Expand Down Expand Up @@ -106,8 +123,8 @@ class Display: #pylint: disable-msg=no-member
_COLUMN_SET = None
_RAM_WRITE = None
_RAM_READ = None
_X_START = 0
_Y_START = 0
_X_START = 0 # pylint: disable=invalid-name
_Y_START = 0 # pylint: disable=invalid-name
_INIT = ()
_ENCODE_PIXEL = ">H"
_ENCODE_POS = ">HH"
Expand Down Expand Up @@ -157,6 +174,30 @@ def pixel(self, x, y, color=None):
self._block(x, y, x, y, self._encode_pixel(color))
return None

def image(self, img, rotation=0):
"""Set buffer to value of Python Imaging Library image. The image should
be in 1 bit mode and a size equal to the display size."""
if not img.mode in ('RGB', 'RGBA'):
raise ValueError('Image must be in mode RGB or RGBA')
if rotation not in (0, 90, 180, 270):
raise ValueError('Rotation must be 0/90/180/270')
if rotation != 0:
img = img.rotate(rotation, expand=True)
imwidth, imheight = img.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display ({0}x{1}).' \
.format(self.width, self.height))
pixels = bytearray(self.width * self.height * 2)
# Iterate through the pixels
for x in range(self.width): # yes this double loop is slow,
for y in range(self.height): # but these displays are small!
pix = color565(img.getpixel((x, y)))
pixels[2*(y * self.width + x)] = pix >> 8
pixels[2*(y * self.width + x) + 1] = pix & 0xFF

#print([hex(x) for x in pixels])
self._block(0, 0, self.width-1, self.height - 1, pixels)

#pylint: disable-msg=too-many-arguments
def fill_rectangle(self, x, y, width, height, color):
"""Draw a rectangle at specified position with specified width and
Expand Down Expand Up @@ -192,7 +233,8 @@ class DisplaySPI(Display):
"""Base class for SPI type devices"""
#pylint: disable-msg=too-many-arguments
def __init__(self, spi, dc, cs, rst=None, width=1, height=1,
baudrate=12000000, polarity=0, phase=0):
baudrate=12000000, polarity=0, phase=0, *,
x_offset=0, y_offset=0):
self.spi_device = spi_device.SPIDevice(spi, cs, baudrate=baudrate,
polarity=polarity, phase=phase)
self.dc_pin = dc
Expand All @@ -201,6 +243,8 @@ def __init__(self, spi, dc, cs, rst=None, width=1, height=1,
if self.rst:
self.rst.switch_to_output(value=0)
self.reset()
self._X_START = x_offset # pylint: disable=invalid-name
self._Y_START = y_offset # pylint: disable=invalid-name
super().__init__(width, height)
#pylint: enable-msg=too-many-arguments

Expand Down
13 changes: 7 additions & 6 deletions adafruit_rgb_display/st7789.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ class ST7789(DisplaySPI):
_PAGE_SET = _RASET
_RAM_WRITE = _RAMWR
_RAM_READ = _RAMRD
_Y_START = 80
_INIT = (
(_SWRESET, None),
(_SLPOUT, None),
Expand All @@ -113,14 +112,16 @@ class ST7789(DisplaySPI):
)

#pylint: disable-msg=useless-super-delegation, too-many-arguments
def __init__(self, spi, dc, cs, rst=None, width=240, height=240,
baudrate=16000000, polarity=0, phase=0):
def __init__(self, spi, dc, cs, rst=None, width=240, height=320,
baudrate=16000000, polarity=0, phase=0, *,
x_offset=0, y_offset=0):
super().__init__(spi, dc, cs, rst, width, height,
baudrate=baudrate, polarity=polarity, phase=phase)

baudrate=baudrate, polarity=polarity, phase=phase,
x_offset=x_offset, y_offset=y_offset)
def init(self):

super().init()
cols = struct.pack('>HH', 0, self.width)
cols = struct.pack('>HH', self._X_START, self.width + self._X_START)
rows = struct.pack('>HH', self._Y_START, self.height + self._Y_START)
for command, data in (
(_CASET, cols),
Expand Down
175 changes: 175 additions & 0 deletions examples/rgbdisplay_fbcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import time
import os
import fcntl
import mmap
import struct
import digitalio
import board
from PIL import Image, ImageDraw
import adafruit_rgb_display.st7789 as st7789

# definitions from linux/fb.h
FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611

FB_TYPE_PACKED_PIXELS = 0
FB_TYPE_PLANES = 1
FB_TYPE_INTERLEAVED_PLANES = 2
FB_TYPE_TEXT = 3
FB_TYPE_VGA_PLANES = 4
FB_TYPE_FOURCC = 5

FB_VISUAL_MONO01 = 0
FB_VISUAL_MONO10 = 1
FB_VISUAL_TRUECOLOR = 2
FB_VISUAL_PSEUDOCOLOR = 3
FB_VISUAL_DIRECTCOLOR = 4
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
FB_VISUAL_FOURCC = 6

FB_BLANK_UNBLANK = 0
FB_BLANK_POWERDOWN = 4



class Bitfield: # pylint: disable=too-few-public-methods
def __init__(self, offset, length, msb_right):
self.offset = offset
self.length = length
self.msb_right = msb_right

# Kind of like a pygame Surface object, or not!
# http://www.pygame.org/docs/ref/surface.html
class Framebuffer: # pylint: disable=too-many-instance-attributes

def __init__(self, dev):
self.dev = dev
self.fbfd = os.open(dev, os.O_RDWR)
vinfo = struct.unpack("8I12I16I4I",
fcntl.ioctl(self.fbfd,
FBIOGET_VSCREENINFO,
" "*((8+12+16+4)*4)))
finfo = struct.unpack("16cL4I3HI",
fcntl.ioctl(self.fbfd,
FBIOGET_FSCREENINFO, " "*48))

bytes_per_pixel = (vinfo[6] + 7) // 8
screensize = vinfo[0] * vinfo[1] * bytes_per_pixel

fbp = mmap.mmap(self.fbfd, screensize,
flags=mmap.MAP_SHARED, prot=mmap.PROT_READ)

self.fbp = fbp
self.xres = vinfo[0]
self.yres = vinfo[1]
self.xoffset = vinfo[4]
self.yoffset = vinfo[5]
self.bits_per_pixel = vinfo[6]
self.bytes_per_pixel = bytes_per_pixel
self.grayscale = vinfo[7]
self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
self.nonstd = vinfo[20]
self.name = b''.join([x for x in finfo[0:15] if x != b'\x00'])
self.type = finfo[18]
self.visual = finfo[20]
self.line_length = finfo[24]
self.screensize = screensize

def close(self):
self.fbp.close()
os.close(self.fbfd)

def blank(self, blank):
# Blanking is not supported by all drivers
try:
if blank:
fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
else:
fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
except IOError:
pass

def __str__(self):
visual_list = ['MONO01', 'MONO10', 'TRUECOLOR',
'PSEUDOCOLOR', 'DIRECTCOLOR',
'STATIC PSEUDOCOLOR', 'FOURCC']
type_list = ['PACKED_PIXELS', 'PLANES', 'INTERLEAVED_PLANES',
'TEXT', 'VGA_PLANES', 'FOURCC']
visual_name = 'unknown'
if self.visual < len(visual_list):
visual_name = visual_list[self.visual]
type_name = 'unknown'
if self.type < len(type_list):
type_name = type_list[self.type]

return \
"mode \"%sx%s\"\n" % (self.xres, self.yres) + \
" nonstd %s\n" % self.nonstd + \
" rgba %s/%s,%s/%s,%s/%s,%s/%s\n" % (self.red.length,
self.red.offset,
self.green.length,
self.green.offset,
self.blue.length,
self.blue.offset,
self.transp.length,
self.transp.offset) + \
"endmode\n" + \
"\n" + \
"Frame buffer device information:\n" + \
" Device : %s\n" % self.dev + \
" Name : %s\n" % self.name + \
" Size : (%d, %d)\n" % (self.xres, self.yres) + \
" Length : %s\n" % self.screensize + \
" BPP : %d\n" % self.bits_per_pixel + \
" Type : %s\n" % type_name + \
" Visual : %s\n" % visual_name + \
" LineLength : %s\n" % self.line_length

device = '/dev/fb0'
fb = Framebuffer(device)
print(fb)

# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

# Config for display baudrate (default max is 24mhz):
BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin,
baudrate=BAUDRATE,
width=135, height=240, x_offset=53, y_offset=40)

height = disp.width # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new('RGB', (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)

while True:
t = time.monotonic()
fb.fbp.seek(0)
b = fb.fbp.read(fb.screensize)
fbimage = Image.frombytes('RGBA', (fb.xres, fb.yres), b, 'raw')
b, g, r, a = fbimage.split()
fbimage = Image.merge("RGB", (r, g, b))
fbimage = fbimage.resize((width, height))

disp.image(fbimage, rotation)
print(1.0 / (time.monotonic()-t))
fb.close()
84 changes: 84 additions & 0 deletions examples/rgbdisplay_minipitftstats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-

import time
import subprocess
import digitalio
import board
from PIL import Image, ImageDraw, ImageFont
import adafruit_rgb_display.st7789 as st7789


# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

# Config for display baudrate (default max is 24mhz):
BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE,
width=135, height=240, x_offset=53, y_offset=40)

# Create blank image for drawing.
# Make sure to create image with mode 'RGB' for full color.
height = disp.width # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new('RGB', (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)
# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = -2
top = padding
bottom = height-padding
# Move left to right keeping track of the current x position for drawing shapes.
x = 0


# Alternatively load a TTF font. Make sure the .ttf font file is in the
# same directory as the python script!
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 24)

while True:
# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=0)

# Shell scripts for system monitoring from here:
# https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load
cmd = "hostname -I | cut -d\' \' -f1"
IP = "IP: "+subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB %.2f%%\", $3,$2,$3*100/$2 }'"
MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%d GB %s\", $3,$2,$5}'"
Disk = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "cat /sys/class/thermal/thermal_zone0/temp | awk \'{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}\'" # pylint: disable=line-too-long
Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")

# Write four lines of text.
y = top
draw.text((x, y), IP, font=font, fill="#FFFFFF")
y += font.getsize(IP)[1]
draw.text((x, y), CPU, font=font, fill="#FFFF00")
y += font.getsize(CPU)[1]
draw.text((x, y), MemUsage, font=font, fill="#00FF00")
y += font.getsize(MemUsage)[1]
draw.text((x, y), Disk, font=font, fill="#0000FF")
y += font.getsize(Disk)[1]
draw.text((x, y), Temp, font=font, fill="#FF00FF")

# Display image.
disp.image(image, rotation)
time.sleep(.1)
Loading