Python pygame 无法在 Raspberry Pi + TFT 屏幕上输出到/dev/fb1 [英] Python pygame fails to output to /dev/fb1 on a Raspberry Pi + TFT screen

查看:53
本文介绍了Python pygame 无法在 Raspberry Pi + TFT 屏幕上输出到/dev/fb1的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在摆弄 Raspberry Pi 2 和连接到 Pi GPIO 的 2.8" TFT 触摸屏.Pi 还连接到 HDMI 监视器.
我的问题是我的 Python3 pygame 脚本无法使用 TFT 屏幕,但总是显示在我的 HDMI 屏幕上.

I am fiddling with a Raspberry Pi 2 and a 2.8" TFT touch screen attached to the Pi's GPIO. The Pi is also connected to a HDMI monitor.
My issue is that my Python3 pygame script is not able to use the TFT screen, but always displays on my HDMI screen instead.

我已经安装了最新的 vanilla Raspbian 即用型发行版,并按照 TFT 屏幕安装步骤操作,一切正常:TFT 可以毫无问题地显示控制台和 X.触摸屏已校准并正确移动光标.我还可以看到一个新的 framebuffer 设备为 /dev/fb1.

I've installed the latest vanilla Raspbian ready-to-use distro and followed the TFT screen installation steps, everything works well: the TFT can display the console and X without issue. The touchscreen is calibrated and moves the cursor correctly. I can also see a new framebuffer device as /dev/fb1.

我尝试了以下方法来测试这个新设备:

I've tried the following to test this new device:

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

=> 这样就可以在 TFT 屏幕上成功显示图片了

=> This successfully displays the pic on the TFT screen

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

=> 这成功地在 TFT 屏幕上显示静态

=> This successfully displays statics on the TFT screen

然而,当我运行这个 Python3/pygame 脚本时,结果始终出现在 HDMI 屏幕上,而不是在 TFT 屏幕上:

However, when I run this Python3/pygame script, the result appears in the HDMI screen consistently and not on the TFT screen:

#!/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

我尝试了不同的 drivers(fbcon、directfb、svgalib...)但没有成功.

I have tried different drivers (fbcon, directfb, svgalib...) without success.

任何帮助或想法将不胜感激,我已经阅读了大量文档、手册和示例,但线索已经用完了:/此外,似乎很多人已经成功地将 Python3/pygame通过 /dev/fb1 输出到他们的 TFT 屏幕.

Any help or idea would be greatly appreciated, I've been through a lot of doc, manuals and samples and just ran out of leads :/ Furthermore, it appears that a lot of people have succeeded in getting Python3/pygame to output to their TFT screen via /dev/fb1.

推荐答案

我已经折腾了太多小时了,但至少我找到了我称之为体面的解决方法,如果不是解决方案的话.

I have been fiddling around that for far too many hours now, but at least I have found what I'd call a decent workaround, if not a solution.

我一直使用 pygame 来构建我的图形/GUI,并切换到 evdev 来处理 TFT 触摸事件.下一节将解释使用 evdev 而不是 pygame 的内置输入管理(或 pymouse,或任何其他高级内容)的原因.

I've kept using pygame for building my graphics/GUI, and switched to evdev for handling the TFT touch events. The reason for using evdev rather than pygame's built-in input management (or pymouse, or any other high level stuff) is explained in the next section.

简而言之,该程序使用 pygame 在内存(RAM,而不是图形)中构建一些图形,并将构建的图形作为字节直接推送到 TFT 屏幕帧缓冲区.这绕过了任何驱动程序,因此它实际上与通过帧缓冲区访问的任何屏幕兼容,但是它也绕过了任何潜在的优化,因为它是一个好的驱动程序.

In a nutshell, this program builds some graphics in memory (RAM, not graphic) using pygame, and pushes the built graphics as bytes into the TFT screen framebuffer directly. This bypasses any driver so it is virtually compatible with any screen accessible through a framebuffer, however it also bypasses any potential optimizations coming along what would be a good driver.

这是一个让奇迹发生的代码示例:

Here is a code sample that makes the magic happen:

#!/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 显示器上显示内容:

More details

A quick recap on what I wanted to achieve: my goal is to display content onto a TFT display with the following constraints:

  1. 能够在不受干扰的情况下在 HDMI 显示器上显示其他内容(例如 HDMI 上的 X,TFT 上图形应用程序的输出);
  2. 能够利用 TFT 显示屏的触摸功能为图形应用程序带来好处;
  3. 确保上述要点不会干扰 HDMI 显示器上的鼠标指针;
  4. 利用 Python 和 Pygame 轻松构建我喜欢的任何图形/GUI;
  5. 保持一个不太合适但对我来说足够的帧率,例如10 帧/秒.

为什么不按照许多论坛和 adafruit TFT 手册中的说明使用 pygame/SDL1.2.x?

首先,它根本不起作用.我已经尝试了无数版本的 libsdl 及其依赖项,但它们始终都失败了.我尝试强制一些 libsdl 版本降级,与 pygame 版本相同,只是为了尝试回到我的 TFT 屏幕发布时软件的状态(~2014).然后我也尝试切换到 C 并直接处理 SDL2 原语.

Why not using pygame/SDL1.2.x as instructed in many forums and the adafruit TFT manual?

First, it doesn't work, at all. I have tried a gazillion versions of libsdl and its dependencies and they all failed consistently. I've tried forcing some libsdl versions downgrades, same with pygame version, just to try to get back to what the software was when my TFT screen was released (~2014). Then I aslo tried switching to C and handle SDL2 primitives directly.

此外,SDL1.2 已经过时了,我认为在旧代码之上构建新代码是不好的做法.也就是说,我仍在使用 pygame-1.9.4...

Furthermore, SDL1.2 is getting old and I believe it is bad practice to build new code on top of old one. That said, I am still using pygame-1.9.4...

那为什么不是 SDL2?好吧,他们已经停止(或即将停止)支持帧缓冲区.我还没有尝试过他们替代 framebuffers,EGL,因为它变得更复杂,我进一步挖掘和 它看起来不太吸引人(太旧了,感觉像死灵浏览).顺便说一句,任何新的帮助或建议都将不胜感激.

So why not SDL2? Well, they have stopped (or are about to stop) supporting framebuffers. I have not tried their alternative to framebuffers, EGL, as it got more complex the further I digged and it did not look too engaging (so old it felt like necro-browsing). Any fresh help or advice on that would be greatly appreciated BTW.

在传统环境中工作的所有高级解决方案都嵌入了显示器.我已经尝试过 pygame 事件、pymouse 和其他一些在我的情况下不起作用的事件,因为我故意摆脱了显示的概念.这就是为什么我不得不回到通用和低级解决方案的原因,互联网将我介绍给 evdev,有关更多详细信息,请参阅上面的注释代码.

All the high level solutions that work in a conventional context are embedding a display. I've tried pygame events, pymouse and a couple others that would not work in my case as I got rid of the notion of display on purpose. That's why I had to go back to a generic and low level solution, and the internet introduced my to evdev, see the commented code above for more details.

对上述内容的任何评论将不胜感激,这是我使用 Raspbian、Python 和 TFT 屏幕的第一步,我想我很可能在此过程中错过了一些非常明显的东西.

Any comment on the above would be greatly appreciated, these are my first step with Raspbian, Python and TFT screens, I reckon I most probably have missed some pretty obvious stuff along the way.

这篇关于Python pygame 无法在 Raspberry Pi + TFT 屏幕上输出到/dev/fb1的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆