问题记录:树莓派3B+ RaspberryPi OS bookworm(Debian12)系统点亮SSD1306驱动OLED屏

背景

几年前买的树莓派3B+,吃灰了一段时间,拿出来玩一玩。把系统版本更新到了目前最新的 raspios bookworm(Debian 12)版本,旧版系统上有正常点亮的SSD1306驱动128x64 oled屏和python程序,准备恢复点亮的,结果遇到各种问题,就记录总结一下。

实验环境

时间:2024年8月25日
硬件:树莓派3B+
系统版本:Raspberry Pi OS Lite(32位) bookworm

问题记录

2019年,当时参考 树莓派实验室 - 在树莓派上使用 SSD1306 OLED 屏幕 这篇教程,在低版本系统上成功点亮。

这次,操作步骤还是参考上面的那片教程,但随着Debian系统和python版本更新,有些地方不再适用了。

1、sudo pip install xxx报错:error: externally-managed-environment

具体错误输出如下:

$ sudo python -m pip install --upgrade pip setuptools wheel
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.

    For more information visit http://rptl.io/venv

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

解决方法: Raspberry Pi OS系统版本升级后,包管理优化的一些改变。按照提示,配置 python 虚拟环境(venv)即可,上面错误信息中就有链接,教程网上也随便能搜到,不再赘述。
为了方便以后使用,我在.bashrc最后添加了激活 python 虚拟环境的命令,这样就每次 ssh 打开的远程终端都默认是虚拟环境:
~/.bashrc:

# 省略默认内容...
source /home/xxuser/rpi-py-env/bin/activate

2、sudo apt-get install python-smbus报错:E: Package ‘python-smbus’ has no installation candidate

解决方法: 新版本系统中,通过apt安装python模块的方式不再适用,通过pip install smbus方式安装,其他python中用到的依赖库同理。

pip install smbus
# python中用到的其他依赖模块...

3、pip install执行缓慢或中断失败

**解决方法:**更换国内pip镜像源,参考配置:
/home/xxuser/.config/pip/pip.conf

[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
extra-index-url=
        https://mirrors.aliyun.com/pypi/simple/
        https://pypi.mirrors.ustc.edu.cn/simple/

[install]
trusted-host=
        pypi.tuna.tsinghua.edu.cn
        mirrors.aliyun.com
        pypi.mirrors.ustc.edu.cn

4、使用Pillow库报错:ImportError: cannot import name ‘_imagingft’ from ‘PIL’

解决方法: 2024年8月这个时间,pip install Pillow默认安装的最新版本是pillow 10.4.0,pillow版本和python版本不兼容会报错,经过逐一验证,回退安装低版本Pillow-8.4.0,避免该报错,但是有其他错误,下面会说到。

pip uninstall Pillow
pip install "Pillow<9.0.0"

5、使用Pillow库报错:ImportError: The _imagingft C module is not installed

解决方法: 先卸载Pillow,安装必要的依赖库,再加上--no-cache-dir参数重新安装Pillow

pip uninstall Pillow
sudo apt-get install libjpeg-dev zlib1g-dev
pip install "Pillow<9.0.0" --no-cache-dir

点亮效果

我这里使用自制的OLED屏模块,扩展连接DHT11传感器采集温湿度。各种信息采集用到了psutilAdafruit_DHT这两个Python库,通过pip install安装即可。
在/etc/rc.local的exit之前添加上开机启动的执行命令:

# ...
# 使用Python虚拟环境,避免找不到依赖库
/home/shenyong/rpi-py-env/bin/python /home/shenyong/stats.py

exit 0

在这里插入图片描述

点亮脚本记录

基于 Adafruit_Python_SSD1306/example/stats.py 进行的定制修改。

# Copyright (c) 2017 Adafruit Industries
# Author: Tony DiCola & James DeVito
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# 指定虚拟环境的路径 
import sys
sys.path.insert(0, '/home/shenyong/rpi-py-env/lib/python3.11/site-packages')

import time

import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

import subprocess
import psutil

######## for DHT11 ########
import smbus
import sys
import threading
import Adafruit_DHT

DHT_PIN = 4
SAMPLE_INTERVAL_SEC = 5
init_ok = False
shared_lock = threading.Lock()
envStr = ""
######## for DHT11 end ########

# Raspberry Pi pin configuration:
RST = None     # on the PiOLED this pin isnt used
# Note the following are only used with SPI:
DC = 23
SPI_PORT = 0
SPI_DEVICE = 0

# Beaglebone Black pin configuration:
# RST = 'P9_12'
# Note the following are only used with SPI:
# DC = 'P9_15'
# SPI_PORT = 1
# SPI_DEVICE = 0

# 128x32 display with hardware I2C:
#disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST)

# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

# Note you can change the I2C address by passing an i2c_address parameter like:
# disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST, i2c_address=0x3C)

# Alternatively you can specify an explicit I2C bus number, for example
# with the 128x32 display you would use:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, i2c_bus=2)

# 128x32 display with hardware SPI:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))

# 128x64 display with hardware SPI:
# disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))

# Alternatively you can specify a software SPI implementation by providing
# digital GPIO pin numbers for all the required display pins.  For example
# on a Raspberry Pi with the 128x32 display you might use:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, dc=DC, sclk=18, din=25, cs=22)

def get_dht11_sensor():
    global init_ok
    global shared_lock
    global disp
    global envStr
    while True:
        humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, DHT_PIN)
        init_ok = True
        shared_lock.acquire()
        envStr = 'T:{:.0f}°C H:{:.0f}%  '.format(temperature, humidity)
        shared_lock.release()
        time.sleep(SAMPLE_INTERVAL_SEC)

if __name__ == '__main__':
    now = time.strftime('%m/%d %H:%M:%S', time.localtime(time.time()))

    # Initialize library.
    disp.begin()

    # Clear display.
    disp.clear()
    disp.display()

    # Create blank image for drawing.
    # Make sure to create image with mode '1' for 1-bit color.
    width = disp.width
    height = disp.height
    image = Image.new('1', (width, height))

    # 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)

    # Draw some shapes.
    # First define some constants to allow easy resizing of shapes.
    padding = 0
    top = padding
    bottom = height-padding
    # Move left to right keeping track of the current x position for drawing shapes.
    x = 0


    # Load default font.
    # font = ImageFont.load_default()
    font_size = 12
    font = ImageFont.truetype('/home/shenyong/Adafruit_Python_SSD1306/examples/JetBrainsMono-Thin.ttf', font_size)
    # font = ImageFont.truetype('/home/shenyong/Adafruit_Python_SSD1306/examples/JetBrainsMono-Light.ttf', font_size)
    # font = ImageFont.truetype('/home/shenyong/Adafruit_Python_SSD1306/examples/JetBrainsMono-Medium.ttf', font_size)

    # 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('Minecraftia.ttf', 8)

    rpi_logo = Image.open('/home/shenyong/Adafruit_Python_SSD1306/examples/rpi.ppm').convert('1')
    # draw.text((0, top), "Raspberry Pi 3B+",  font=font, fill=255)
    # draw.text((0, top + font_size + 1), "Initializing...",  font=font, fill=255)
    disp.image(rpi_logo)
    disp.display()
    try:
        threading.Thread(target=get_dht11_sensor).start()
    except:
        print("Error: unable to start thread")

    lastIdle = 0
    lastTotal = 0
    while True:
        if (init_ok):
            # 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"
            IPDesc = subprocess.check_output(cmd, shell = True )
            IP = "IP: " + str(IPDesc, encoding = "utf-8")

            cpu_percent = psutil.cpu_percent(interval=1)
            CPU = 'CPU: {:.1f}%'.format(cpu_percent)

            # cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%sMB %.2f%%\", $3,$2,$3*100/$2 }'"
            #cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%sMB %d%\", $3,$2,$3*100/$2 }'"
            #MemUsage = subprocess.check_output(cmd, shell = True )
            mem_info = psutil.virtual_memory()
            memTotalMB = mem_info.total/1024/1024
            memUsedMB = (mem_info.total - mem_info.available)/1024/1024
            MemUsage = 'Mem: {:d}M/{:d}M {:d}%'.format(int(memUsedMB), int(memTotalMB), int(mem_info.percent))
            
            cmd = "df -h / | awk 'NR==2{print $3\"/\"$2\" \"$5}'"
            diskDesc = subprocess.check_output(cmd, shell = True )
            Disk = 'Disk: {}'.format(str(diskDesc, encoding = "utf-8"))
            # disk_usage = psutil.disk_usage('/')
            # diskTotalGB = disk_usage.total/1024/1024/1024
            # diskUsedGB = disk_usage.used/1024/1024/1024
            # Disk = 'Disk: {:.1f}/{:d}GB {}%'.format(diskUsedGB, int(diskTotalGB), int(disk_usage.percent))

            nowTime = time.strftime('%H:%M', time.localtime(time.time()))
            
            # Write text.
            line = 0
            draw.text((x, top + font_size * line + 1),   IP,  font=font, fill=255)
            line += 1
            draw.text((x, top + font_size * line + 1),   CPU, font=font, fill=255)
            line += 1
            draw.text((x, top + font_size * line + 1),   MemUsage,  font=font, fill=255)
            line += 1
            draw.text((x, top + font_size * line + 1),   Disk,  font=font, fill=255)
            
            shared_lock.acquire()
            line += 1
            lineStr = '{} {}'.format(nowTime, envStr)
            draw.text((0, top + font_size * line + 1), lineStr,  font=font, fill=255)
            shared_lock.release()
            
            disp.image(image)
            disp.display()
        time.sleep(2)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值