/ examples / rgb_display_fbcp.py
rgb_display_fbcp.py
  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  class Bitfield:  # pylint: disable=too-few-public-methods
 36      def __init__(self, offset, length, msb_right):
 37          self.offset = offset
 38          self.length = length
 39          self.msb_right = msb_right
 40  
 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      def __init__(self, dev):
 46          self.dev = dev
 47          self.fbfd = os.open(dev, os.O_RDWR)
 48          vinfo = struct.unpack(
 49              "8I12I16I4I",
 50              fcntl.ioctl(self.fbfd, FBIOGET_VSCREENINFO, " " * ((8 + 12 + 16 + 4) * 4)),
 51          )
 52          finfo = struct.unpack(
 53              "16cL4I3HI", fcntl.ioctl(self.fbfd, FBIOGET_FSCREENINFO, " " * 48)
 54          )
 55  
 56          bytes_per_pixel = (vinfo[6] + 7) // 8
 57          screensize = vinfo[0] * vinfo[1] * bytes_per_pixel
 58  
 59          fbp = mmap.mmap(
 60              self.fbfd, screensize, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ
 61          )
 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 = [
 98              "MONO01",
 99              "MONO10",
100              "TRUECOLOR",
101              "PSEUDOCOLOR",
102              "DIRECTCOLOR",
103              "STATIC PSEUDOCOLOR",
104              "FOURCC",
105          ]
106          type_list = [
107              "PACKED_PIXELS",
108              "PLANES",
109              "INTERLEAVED_PLANES",
110              "TEXT",
111              "VGA_PLANES",
112              "FOURCC",
113          ]
114          visual_name = "unknown"
115          if self.visual < len(visual_list):
116              visual_name = visual_list[self.visual]
117          type_name = "unknown"
118          if self.type < len(type_list):
119              type_name = type_list[self.type]
120  
121          return (
122              'mode "%sx%s"\n' % (self.xres, self.yres)
123              + "    nonstd %s\n" % self.nonstd
124              + "    rgba %s/%s,%s/%s,%s/%s,%s/%s\n"
125              % (
126                  self.red.length,
127                  self.red.offset,
128                  self.green.length,
129                  self.green.offset,
130                  self.blue.length,
131                  self.blue.offset,
132                  self.transp.length,
133                  self.transp.offset,
134              )
135              + "endmode\n"
136              + "\n"
137              + "Frame buffer device information:\n"
138              + "    Device      : %s\n" % self.dev
139              + "    Name        : %s\n" % self.name
140              + "    Size        : (%d, %d)\n" % (self.xres, self.yres)
141              + "    Length      : %s\n" % self.screensize
142              + "    BPP         : %d\n" % self.bits_per_pixel
143              + "    Type        : %s\n" % type_name
144              + "    Visual      : %s\n" % visual_name
145              + "    LineLength  : %s\n" % self.line_length
146          )
147  
148  
149  device = "/dev/fb0"
150  fb = Framebuffer(device)
151  print(fb)
152  
153  # Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
154  cs_pin = digitalio.DigitalInOut(board.CE0)
155  dc_pin = digitalio.DigitalInOut(board.D25)
156  reset_pin = None
157  
158  # Config for display baudrate (default max is 24mhz):
159  BAUDRATE = 64000000
160  
161  # Setup SPI bus using hardware SPI:
162  spi = board.SPI()
163  
164  # Create the ST7789 display:
165  disp = st7789.ST7789(
166      spi,
167      cs=cs_pin,
168      dc=dc_pin,
169      rst=reset_pin,
170      baudrate=BAUDRATE,
171      width=135,
172      height=240,
173      x_offset=53,
174      y_offset=40,
175  )
176  
177  height = disp.width  # we swap height/width to rotate it to landscape!
178  width = disp.height
179  image = Image.new("RGB", (width, height))
180  rotation = 90
181  
182  # Get drawing object to draw on image.
183  draw = ImageDraw.Draw(image)
184  
185  # Draw a black filled box to clear the image.
186  draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
187  disp.image(image, rotation)
188  
189  while True:
190      t = time.monotonic()
191      fb.fbp.seek(0)
192      b = fb.fbp.read(fb.screensize)
193      fbimage = Image.frombytes("RGBA", (fb.xres, fb.yres), b, "raw")
194      b, g, r, a = fbimage.split()
195      fbimage = Image.merge("RGB", (r, g, b))
196      fbimage = fbimage.resize((width, height))
197  
198      disp.image(fbimage, rotation)
199      print(1.0 / (time.monotonic() - t))
200  fb.close()