Python Pygame не может выводить в / dev / fb1 на экране Raspberry Pi + TFT - PullRequest
0 голосов
/ 20 февраля 2019

TL; DR

Я играю с Raspberry Pi 2 и 2,8 "TFT сенсорным экраном, подключенным к GPIO Pi. Pi также подключен к монитору HDMI.
Моя проблема в том, чтоМой скрипт Python3 pygame не может использовать экран TFT, но вместо этого всегда отображается на экране HDMI.

Некоторый фон

Я установил последний ванильный Raspbianготовый к использованию дистрибутив и следуя инструкциям по установке экрана TFT, все работает хорошо: TFT может отображать консоль и X без проблем. Сенсорный экран откалиброван и правильно перемещает курсор. Я также вижу новый кадровый буфер устройство как /dev/fb1.

Я попробовал следующее для тестирования этого нового устройства:

sudo fbi -T 2 -d /dev/fb1 -noverbose -a my_picture.jpg

=> Это успешно отображает изображение на экране TFT

while true; do sudo cat /dev/urandom > /dev/fb1; sleep .01; done

=> Это успешно отображает статику на экране TFT

Однако, когда я запускаю этот скрипт Python3 / pygame, результат отображается последовательно на экране HDMI, а не на экране TFTn:

#!/usr/bin/python3

import os, pygame, time

def setSDLVariables():
    print("Setting SDL variables...")
    os.environ["SDL_FBDEV"] = "/dev/fb1"
    os.environ["SDL_VIDEODRIVER"] = driver
    print("...done") 

def printSDLVariables():
    print("Checking current env variables...")
    print("SDL_VIDEODRIVER = {0}".format(os.getenv("SDL_VIDEODRIVER")))
    print("SDL_FBDEV = {0}".format(os.getenv("SDL_FBDEV")))

def runHW5():
    print("Running HW5...")
    try:
        pygame.init()
    except pygame.error:
        print("Driver '{0}' failed!".format(driver))
    size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
    print("Detected screen size: {0}".format(size))
    lcd = pygame.display.set_mode(size)
    lcd.fill((10,50,100))
    pygame.display.update()
    time.sleep(sleepTime)
    print("...done")

driver = 'fbcon'
sleepTime= 0.1

printSDLVariables()
setSDLVariables()
printSDLVariables()
runHW5()

Сценарий, приведенный выше, работает следующим образом:

pi@raspberrypi:~/Documents/Python_HW_GUI $ ./hw5-ThorPy-fb1.py
Checking current env variables...
SDL_VIDEODRIVER = None
SDL_FBDEV = None
Setting SDL variables...
...done
Checking current env variables...
SDL_VIDEODRIVER = fbcon
SDL_FBDEV = /dev/fb1
Running HW5...
Detected screen size: (1920, 1080)
...done

Я пробовал разные driver s (fbcon, directfb, svgalib ...) без успеха.

Буду очень признателен за любую помощь или идею, я прошел много документации, руководств и примеров и просто исчерпал список потенциальных клиентов: / Кроме того, похоже, что многим людям удалось получить Python3 / pygameвыводить на экран TFT через /dev/fb1.

1 Ответ

0 голосов
/ 04 марта 2019

Я слишком долго возился с этим, но, по крайней мере, я нашел то, что я назвал бы достойным обходным путем, если не решением.

TL; DR

Я продолжал использовать pygame для создания моей графики / графического интерфейса и переключился на evdev для обработки событий касания TFT.Причина использования evdev, а не встроенного управления вводом pygame (или pymouse, или любого другого высокоуровневого материала), объясняется в следующем разделе.

В двух словах, эта программа создает некоторую графику в памяти (RAM)(не графический), используя pygame, и вставляет встроенную графику в виде байтов непосредственно в кадровый буфер экрана TFT.Это обходит любой драйвер, поэтому он практически совместим с любым экраном, доступным через фрейм-буфер, однако он также обходит любые потенциальные оптимизации, которые могут привести к тому, что будет хорошим драйвером.

Вот пример кода, который заставляет волшебство произойти:

#!/usr/bin/python3

##
# Prerequisites:
# A Touchscreen properly installed on your system:
# - a device to output to it, e.g. /dev/fb1
# - a device to get input from it, e.g. /dev/input/touchscreen
##

import pygame, time, evdev, select, math

# Very important: the exact pixel size of the TFT screen must be known so we can build graphics at this exact format
surfaceSize = (320, 240)

# Note that we don't instantiate any display!
pygame.init()

# The pygame surface we are going to draw onto. 
# /!\ It must be the exact same size of the target display /!\
lcd = pygame.Surface(surfaceSize)

# This is the important bit
def refresh():
    # We open the TFT screen's framebuffer as a binary file. Note that we will write bytes into it, hence the "wb" operator
    f = open("/dev/fb1","wb")
    # According to the TFT screen specs, it supports only 16bits pixels depth
    # Pygame surfaces use 24bits pixels depth by default, but the surface itself provides a very handy method to convert it.
    # once converted, we write the full byte buffer of the pygame surface into the TFT screen framebuffer like we would in a plain file:
    f.write(lcd.convert(16,0).get_buffer())
    # We can then close our access to the framebuffer
    f.close()
    time.sleep(0.1)

# Now we've got a function that can get the bytes from a pygame surface to the TFT framebuffer, 
# we can use the usual pygame primitives to draw on our surface before calling the refresh function.

# Here we just blink the screen background in a few colors with the "Hello World!" text
pygame.font.init()
defaultFont = pygame.font.SysFont(None,30)

lcd.fill((255,0,0))
lcd.blit(defaultFont.render("Hello World!", False, (0, 0, 0)),(0, 0))
refresh()

lcd.fill((0, 255, 0))
lcd.blit(defaultFont.render("Hello World!", False, (0, 0, 0)),(0, 0))
refresh()

lcd.fill((0,0,255))
lcd.blit(defaultFont.render("Hello World!", False, (0, 0, 0)),(0, 0))
refresh()

lcd.fill((128, 128, 128))
lcd.blit(defaultFont.render("Hello World!", False, (0, 0, 0)),(0, 0))
refresh()

##
# Everything that follows is for handling the touchscreen touch events via evdev
##

# Used to map touch event from the screen hardware to the pygame surface pixels. 
# (Those values have been found empirically, but I'm working on a simple interactive calibration tool
tftOrig = (3750, 180)
tftEnd = (150, 3750)
tftDelta = (tftEnd [0] - tftOrig [0], tftEnd [1] - tftOrig [1])
tftAbsDelta = (abs(tftEnd [0] - tftOrig [0]), abs(tftEnd [1] - tftOrig [1]))

# We use evdev to read events from our touchscreen
# (The device must exist and be properly installed for this to work)
touch = evdev.InputDevice('/dev/input/touchscreen')

# We make sure the events from the touchscreen will be handled only by this program
# (so the mouse pointer won't move on X when we touch the TFT screen)
touch.grab()
# Prints some info on how evdev sees our input device
print(touch)
# Even more info for curious people
#print(touch.capabilities())

# Here we convert the evdev "hardware" touch coordinates into pygame surface pixel coordinates
def getPixelsFromCoordinates(coords):
    # TODO check divide by 0!
    if tftDelta [0] < 0:
        x = float(tftAbsDelta [0] - coords [0] + tftEnd [0]) / float(tftAbsDelta [0]) * float(surfaceSize [0])
    else:    
        x = float(coords [0] - tftOrig [0]) / float(tftAbsDelta [0]) * float(surfaceSize [0])
    if tftDelta [1] < 0:
        y = float(tftAbsDelta [1] - coords [1] + tftEnd [1]) / float(tftAbsDelta [1]) * float(surfaceSize [1])
    else:        
        y = float(coords [1] - tftOrig [1]) / float(tftAbsDelta [1]) * float(surfaceSize [1])
    return (int(x), int(y))

# Was useful to see what pieces I would need from the evdev events
def printEvent(event):
    print(evdev.categorize(event))
    print("Value: {0}".format(event.value))
    print("Type: {0}".format(event.type))
    print("Code: {0}".format(event.code))

# This loop allows us to write red dots on the screen where we touch it 
while True:
    # TODO get the right ecodes instead of int
    r,w,x = select.select([touch], [], [])
    for event in touch.read():
        if event.type == evdev.ecodes.EV_ABS:
            if event.code == 1:
                X = event.value
            elif event.code == 0:
                Y = event.value
        elif event.type == evdev.ecodes.EV_KEY:
            if event.code == 330 and event.value == 1:
                printEvent(event)
                p = getPixelsFromCoordinates((X, Y))
                print("TFT: {0}:{1} | Pixels: {2}:{3}".format(X, Y, p [0], p [1]))
                pygame.draw.circle(lcd, (255, 0, 0), p , 2, 2)
                refresh()

exit()

Подробнее

Краткий обзор того, чего я хотел достичь: моя цель - вывести контент на TFT-дисплей со следующими ограничениями:

  1. Иметь возможность отображать другой контент на дисплее HDMI без помех (например, X на HDMI, вывод графического приложения на TFT);
  2. иметь возможность использовать сенсорные возможности TFT-дисплея для пользыграфического приложения;
  3. убедитесь, что вышеприведенный пункт не будет мешать указателю мыши на дисплее HDMI;
  4. использует Python и Pygame, чтобы было очень легко создавать любую графику / графический интерфейс пользователя I'fancy;
  5. держите менее чем приличную, но достаточную для меня частоту кадров, например, 10 FPS.

Почему бы и нетиспользовать pygame / SDL1.2.x, как указано на многих форумах и в руководстве по TFT adafruit?

Во-первых, это вообще не работает.Я пробовал gazillion версий libsdl и его зависимостей, и все они постоянно терпели неудачу.Я пытался принудительно понизить некоторые версии libsdl , то же самое с версией pygame , просто чтобы попытаться вернуться к тому, что было программное обеспечение, когда был выпущен мой экран TFT (~ 2014).Затем я также попытался переключиться на C и напрямую работать с примитивами SDL2.

Кроме того, SDL1.2 стареет, и я считаю, что создавать новый код поверх старого - плохая практика.Тем не менее, я все еще использую Pygame-1.9.4 ...

Так почему бы не SDL2?Ну, они прекратили (или собираются остановить) поддержку кадровых буферов.Я не пробовал их альтернативу кадровым буферам, EGL, поскольку чем дальше я копал, тем сложнее становилось, и не выглядело слишком привлекательно (настолько старым, что казалось некро-просмотром).BTW.

А как насчет ввода с сенсорным экраном?

. Все высокоуровневые решения, работающие в обычном контексте, имеют встроенный дисплей.Я пробовал события Pygame, Pymouse и пару других, которые не будут работать в моем случае, так как я специально избавился от понятия отображения.Вот почему мне пришлось вернуться к общему и низкоуровневому решению, и интернет представил мой номер evdev , для получения более подробной информации см. Приведенный выше код.

Любой комментарий по поводу вышес благодарностью, это мой первый шаг с экранами Raspbian, Python и TFT, я считаю, что, скорее всего, я пропустил некоторые довольно очевидные вещи на этом пути.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...