Skip to content

Commit 87bf59c

Browse files
authored
Merge pull request #28 from caternuson/ladyada_image
Add image() support, big buffer on CPython and some linux demos pt.2
2 parents 608dcf4 + c052914 commit 87bf59c

File tree

5 files changed

+352
-13
lines changed

5 files changed

+352
-13
lines changed

adafruit_rgb_display/rgb.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
"""
3030

3131
import time
32-
from micropython import const
3332
try:
3433
import struct
3534
except ImportError:
@@ -41,9 +40,27 @@
4140
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RGB_Display.git"
4241

4342
# This is the size of the buffer to be used for fill operations, in 16-bit
44-
# units. We use 256, which is 512 bytes — size of the DMA buffer on SAMD21.
45-
_BUFFER_SIZE = const(256)
46-
43+
# units.
44+
try:
45+
# If we're on CPython, try to set as large as possible
46+
import platform
47+
if "CPython" in platform.python_implementation():
48+
# check for FT232H special case
49+
try:
50+
import os
51+
if os.environ['BLINKA_FT232H']:
52+
# we are limited by pyftdi's max SPI payload
53+
from pyftdi.spi import SpiController
54+
_BUFFER_SIZE = SpiController.PAYLOAD_MAX_LENGTH // 2 # max bytes / bytes per pixel
55+
except KeyError:
56+
# otherwise set it to blit the whole thing
57+
_BUFFER_SIZE = 320 * 240
58+
else:
59+
# in case CircuitPython ever implements platform
60+
_BUFFER_SIZE = 256
61+
except ImportError:
62+
# Otherwise set smaller MCU friendly size
63+
_BUFFER_SIZE = 256
4764

4865
def color565(r, g=0, b=0):
4966
"""Convert red, green and blue values (0-255) into a 16-bit 565 encoding. As
@@ -106,8 +123,8 @@ class Display: #pylint: disable-msg=no-member
106123
_COLUMN_SET = None
107124
_RAM_WRITE = None
108125
_RAM_READ = None
109-
_X_START = 0
110-
_Y_START = 0
126+
_X_START = 0 # pylint: disable=invalid-name
127+
_Y_START = 0 # pylint: disable=invalid-name
111128
_INIT = ()
112129
_ENCODE_PIXEL = ">H"
113130
_ENCODE_POS = ">HH"
@@ -157,6 +174,30 @@ def pixel(self, x, y, color=None):
157174
self._block(x, y, x, y, self._encode_pixel(color))
158175
return None
159176

177+
def image(self, img, rotation=0):
178+
"""Set buffer to value of Python Imaging Library image. The image should
179+
be in 1 bit mode and a size equal to the display size."""
180+
if not img.mode in ('RGB', 'RGBA'):
181+
raise ValueError('Image must be in mode RGB or RGBA')
182+
if rotation not in (0, 90, 180, 270):
183+
raise ValueError('Rotation must be 0/90/180/270')
184+
if rotation != 0:
185+
img = img.rotate(rotation, expand=True)
186+
imwidth, imheight = img.size
187+
if imwidth != self.width or imheight != self.height:
188+
raise ValueError('Image must be same dimensions as display ({0}x{1}).' \
189+
.format(self.width, self.height))
190+
pixels = bytearray(self.width * self.height * 2)
191+
# Iterate through the pixels
192+
for x in range(self.width): # yes this double loop is slow,
193+
for y in range(self.height): # but these displays are small!
194+
pix = color565(img.getpixel((x, y)))
195+
pixels[2*(y * self.width + x)] = pix >> 8
196+
pixels[2*(y * self.width + x) + 1] = pix & 0xFF
197+
198+
#print([hex(x) for x in pixels])
199+
self._block(0, 0, self.width-1, self.height - 1, pixels)
200+
160201
#pylint: disable-msg=too-many-arguments
161202
def fill_rectangle(self, x, y, width, height, color):
162203
"""Draw a rectangle at specified position with specified width and
@@ -192,7 +233,8 @@ class DisplaySPI(Display):
192233
"""Base class for SPI type devices"""
193234
#pylint: disable-msg=too-many-arguments
194235
def __init__(self, spi, dc, cs, rst=None, width=1, height=1,
195-
baudrate=12000000, polarity=0, phase=0):
236+
baudrate=12000000, polarity=0, phase=0, *,
237+
x_offset=0, y_offset=0):
196238
self.spi_device = spi_device.SPIDevice(spi, cs, baudrate=baudrate,
197239
polarity=polarity, phase=phase)
198240
self.dc_pin = dc
@@ -201,6 +243,8 @@ def __init__(self, spi, dc, cs, rst=None, width=1, height=1,
201243
if self.rst:
202244
self.rst.switch_to_output(value=0)
203245
self.reset()
246+
self._X_START = x_offset # pylint: disable=invalid-name
247+
self._Y_START = y_offset # pylint: disable=invalid-name
204248
super().__init__(width, height)
205249
#pylint: enable-msg=too-many-arguments
206250

adafruit_rgb_display/st7789.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ class ST7789(DisplaySPI):
104104
_PAGE_SET = _RASET
105105
_RAM_WRITE = _RAMWR
106106
_RAM_READ = _RAMRD
107-
_Y_START = 80
108107
_INIT = (
109108
(_SWRESET, None),
110109
(_SLPOUT, None),
@@ -113,14 +112,16 @@ class ST7789(DisplaySPI):
113112
)
114113

115114
#pylint: disable-msg=useless-super-delegation, too-many-arguments
116-
def __init__(self, spi, dc, cs, rst=None, width=240, height=240,
117-
baudrate=16000000, polarity=0, phase=0):
115+
def __init__(self, spi, dc, cs, rst=None, width=240, height=320,
116+
baudrate=16000000, polarity=0, phase=0, *,
117+
x_offset=0, y_offset=0):
118118
super().__init__(spi, dc, cs, rst, width, height,
119-
baudrate=baudrate, polarity=polarity, phase=phase)
120-
119+
baudrate=baudrate, polarity=polarity, phase=phase,
120+
x_offset=x_offset, y_offset=y_offset)
121121
def init(self):
122+
122123
super().init()
123-
cols = struct.pack('>HH', 0, self.width)
124+
cols = struct.pack('>HH', self._X_START, self.width + self._X_START)
124125
rows = struct.pack('>HH', self._Y_START, self.height + self._Y_START)
125126
for command, data in (
126127
(_CASET, cols),

examples/rgbdisplay_fbcp.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import time
2+
import os
3+
import fcntl
4+
import mmap
5+
import struct
6+
import digitalio
7+
import board
8+
from PIL import Image, ImageDraw
9+
import adafruit_rgb_display.st7789 as st7789
10+
11+
# definitions from linux/fb.h
12+
FBIOGET_VSCREENINFO = 0x4600
13+
FBIOGET_FSCREENINFO = 0x4602
14+
FBIOBLANK = 0x4611
15+
16+
FB_TYPE_PACKED_PIXELS = 0
17+
FB_TYPE_PLANES = 1
18+
FB_TYPE_INTERLEAVED_PLANES = 2
19+
FB_TYPE_TEXT = 3
20+
FB_TYPE_VGA_PLANES = 4
21+
FB_TYPE_FOURCC = 5
22+
23+
FB_VISUAL_MONO01 = 0
24+
FB_VISUAL_MONO10 = 1
25+
FB_VISUAL_TRUECOLOR = 2
26+
FB_VISUAL_PSEUDOCOLOR = 3
27+
FB_VISUAL_DIRECTCOLOR = 4
28+
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
29+
FB_VISUAL_FOURCC = 6
30+
31+
FB_BLANK_UNBLANK = 0
32+
FB_BLANK_POWERDOWN = 4
33+
34+
35+
36+
class Bitfield: # pylint: disable=too-few-public-methods
37+
def __init__(self, offset, length, msb_right):
38+
self.offset = offset
39+
self.length = length
40+
self.msb_right = msb_right
41+
42+
# Kind of like a pygame Surface object, or not!
43+
# http://www.pygame.org/docs/ref/surface.html
44+
class Framebuffer: # pylint: disable=too-many-instance-attributes
45+
46+
def __init__(self, dev):
47+
self.dev = dev
48+
self.fbfd = os.open(dev, os.O_RDWR)
49+
vinfo = struct.unpack("8I12I16I4I",
50+
fcntl.ioctl(self.fbfd,
51+
FBIOGET_VSCREENINFO,
52+
" "*((8+12+16+4)*4)))
53+
finfo = struct.unpack("16cL4I3HI",
54+
fcntl.ioctl(self.fbfd,
55+
FBIOGET_FSCREENINFO, " "*48))
56+
57+
bytes_per_pixel = (vinfo[6] + 7) // 8
58+
screensize = vinfo[0] * vinfo[1] * bytes_per_pixel
59+
60+
fbp = mmap.mmap(self.fbfd, screensize,
61+
flags=mmap.MAP_SHARED, prot=mmap.PROT_READ)
62+
63+
self.fbp = fbp
64+
self.xres = vinfo[0]
65+
self.yres = vinfo[1]
66+
self.xoffset = vinfo[4]
67+
self.yoffset = vinfo[5]
68+
self.bits_per_pixel = vinfo[6]
69+
self.bytes_per_pixel = bytes_per_pixel
70+
self.grayscale = vinfo[7]
71+
self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
72+
self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
73+
self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
74+
self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
75+
self.nonstd = vinfo[20]
76+
self.name = b''.join([x for x in finfo[0:15] if x != b'\x00'])
77+
self.type = finfo[18]
78+
self.visual = finfo[20]
79+
self.line_length = finfo[24]
80+
self.screensize = screensize
81+
82+
def close(self):
83+
self.fbp.close()
84+
os.close(self.fbfd)
85+
86+
def blank(self, blank):
87+
# Blanking is not supported by all drivers
88+
try:
89+
if blank:
90+
fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
91+
else:
92+
fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
93+
except IOError:
94+
pass
95+
96+
def __str__(self):
97+
visual_list = ['MONO01', 'MONO10', 'TRUECOLOR',
98+
'PSEUDOCOLOR', 'DIRECTCOLOR',
99+
'STATIC PSEUDOCOLOR', 'FOURCC']
100+
type_list = ['PACKED_PIXELS', 'PLANES', 'INTERLEAVED_PLANES',
101+
'TEXT', 'VGA_PLANES', 'FOURCC']
102+
visual_name = 'unknown'
103+
if self.visual < len(visual_list):
104+
visual_name = visual_list[self.visual]
105+
type_name = 'unknown'
106+
if self.type < len(type_list):
107+
type_name = type_list[self.type]
108+
109+
return \
110+
"mode \"%sx%s\"\n" % (self.xres, self.yres) + \
111+
" nonstd %s\n" % self.nonstd + \
112+
" rgba %s/%s,%s/%s,%s/%s,%s/%s\n" % (self.red.length,
113+
self.red.offset,
114+
self.green.length,
115+
self.green.offset,
116+
self.blue.length,
117+
self.blue.offset,
118+
self.transp.length,
119+
self.transp.offset) + \
120+
"endmode\n" + \
121+
"\n" + \
122+
"Frame buffer device information:\n" + \
123+
" Device : %s\n" % self.dev + \
124+
" Name : %s\n" % self.name + \
125+
" Size : (%d, %d)\n" % (self.xres, self.yres) + \
126+
" Length : %s\n" % self.screensize + \
127+
" BPP : %d\n" % self.bits_per_pixel + \
128+
" Type : %s\n" % type_name + \
129+
" Visual : %s\n" % visual_name + \
130+
" LineLength : %s\n" % self.line_length
131+
132+
device = '/dev/fb0'
133+
fb = Framebuffer(device)
134+
print(fb)
135+
136+
# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
137+
cs_pin = digitalio.DigitalInOut(board.CE0)
138+
dc_pin = digitalio.DigitalInOut(board.D25)
139+
reset_pin = None
140+
141+
# Config for display baudrate (default max is 24mhz):
142+
BAUDRATE = 64000000
143+
144+
# Setup SPI bus using hardware SPI:
145+
spi = board.SPI()
146+
147+
# Create the ST7789 display:
148+
disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin,
149+
baudrate=BAUDRATE,
150+
width=135, height=240, x_offset=53, y_offset=40)
151+
152+
height = disp.width # we swap height/width to rotate it to landscape!
153+
width = disp.height
154+
image = Image.new('RGB', (width, height))
155+
rotation = 90
156+
157+
# Get drawing object to draw on image.
158+
draw = ImageDraw.Draw(image)
159+
160+
# Draw a black filled box to clear the image.
161+
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
162+
disp.image(image, rotation)
163+
164+
while True:
165+
t = time.monotonic()
166+
fb.fbp.seek(0)
167+
b = fb.fbp.read(fb.screensize)
168+
fbimage = Image.frombytes('RGBA', (fb.xres, fb.yres), b, 'raw')
169+
b, g, r, a = fbimage.split()
170+
fbimage = Image.merge("RGB", (r, g, b))
171+
fbimage = fbimage.resize((width, height))
172+
173+
disp.image(fbimage, rotation)
174+
print(1.0 / (time.monotonic()-t))
175+
fb.close()

examples/rgbdisplay_minipitftstats.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import time
4+
import subprocess
5+
import digitalio
6+
import board
7+
from PIL import Image, ImageDraw, ImageFont
8+
import adafruit_rgb_display.st7789 as st7789
9+
10+
11+
# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
12+
cs_pin = digitalio.DigitalInOut(board.CE0)
13+
dc_pin = digitalio.DigitalInOut(board.D25)
14+
reset_pin = None
15+
16+
# Config for display baudrate (default max is 24mhz):
17+
BAUDRATE = 64000000
18+
19+
# Setup SPI bus using hardware SPI:
20+
spi = board.SPI()
21+
22+
# Create the ST7789 display:
23+
disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE,
24+
width=135, height=240, x_offset=53, y_offset=40)
25+
26+
# Create blank image for drawing.
27+
# Make sure to create image with mode 'RGB' for full color.
28+
height = disp.width # we swap height/width to rotate it to landscape!
29+
width = disp.height
30+
image = Image.new('RGB', (width, height))
31+
rotation = 90
32+
33+
# Get drawing object to draw on image.
34+
draw = ImageDraw.Draw(image)
35+
36+
# Draw a black filled box to clear the image.
37+
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
38+
disp.image(image, rotation)
39+
# Draw some shapes.
40+
# First define some constants to allow easy resizing of shapes.
41+
padding = -2
42+
top = padding
43+
bottom = height-padding
44+
# Move left to right keeping track of the current x position for drawing shapes.
45+
x = 0
46+
47+
48+
# Alternatively load a TTF font. Make sure the .ttf font file is in the
49+
# same directory as the python script!
50+
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
51+
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 24)
52+
53+
while True:
54+
# Draw a black filled box to clear the image.
55+
draw.rectangle((0, 0, width, height), outline=0, fill=0)
56+
57+
# Shell scripts for system monitoring from here:
58+
# https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load
59+
cmd = "hostname -I | cut -d\' \' -f1"
60+
IP = "IP: "+subprocess.check_output(cmd, shell=True).decode("utf-8")
61+
cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
62+
CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
63+
cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB %.2f%%\", $3,$2,$3*100/$2 }'"
64+
MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
65+
cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%d GB %s\", $3,$2,$5}'"
66+
Disk = subprocess.check_output(cmd, shell=True).decode("utf-8")
67+
cmd = "cat /sys/class/thermal/thermal_zone0/temp | awk \'{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}\'" # pylint: disable=line-too-long
68+
Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")
69+
70+
# Write four lines of text.
71+
y = top
72+
draw.text((x, y), IP, font=font, fill="#FFFFFF")
73+
y += font.getsize(IP)[1]
74+
draw.text((x, y), CPU, font=font, fill="#FFFF00")
75+
y += font.getsize(CPU)[1]
76+
draw.text((x, y), MemUsage, font=font, fill="#00FF00")
77+
y += font.getsize(MemUsage)[1]
78+
draw.text((x, y), Disk, font=font, fill="#0000FF")
79+
y += font.getsize(Disk)[1]
80+
draw.text((x, y), Temp, font=font, fill="#FF00FF")
81+
82+
# Display image.
83+
disp.image(image, rotation)
84+
time.sleep(.1)

0 commit comments

Comments
 (0)