把闲置的树莓派变成一个桌面监视器

把闲置的树莓派变成一个桌面监视器

本文是树莓派板块下的第一篇文章,所以先介绍一下树莓派吧~

前言——树莓派是什么?

树莓派(Raspberry Pi) 是基于Linux的单板电脑 (Single Board Computer,简称SBC),由英国 树莓派基金会 开发,目的是以低价硬件及自由软件促进学校的基本计算机科学教育
当然,他也不仅仅只用来做教育,树莓派也可以成为广大爱好者玩家手中的diy利器,原因在于以下几点:

  1. 树莓派的生态十分丰富,有大量公司、团体和个人为其完善了从硬件到软件的生态链
  2. 树莓派体积小,使用方便,资料全,新人容易上手
  3. 基于庞大的生态链,你可以想象到的所有关于嵌入式的项目,基本上都有人用树莓派做过,这些项目也就成为了重要参考学习资料~

树莓派能做什么?

我们拿最新版的树莓派4B举例子。

  • 硬件

    树莓派的主CPU是博通公司的BCM系列SoC(System on Chip),最新版的树莓派4B使用的是 BCM2711,具有4核心1.5GHz A72(Quad-core Cortex-A72 (ARM v8) 64-bit SoC @ 1.5 GHz),这颗SoC具有丰富的外设比如GPIO、SPI、I2C、PWM、UART、PCIe总线、HDMI等,在树莓派4B上外设接口如下:

    • 2 x Micro HDMI接口(4k@60fps)
    • 1000M Ethernet接口
    • 2xUSB3.0(通过PCIe总线连接USB控制器)、2xUSB2.0
    • 40Pin 向下兼容 GPIO(实际上只有26Pin可用)
    • 2-Lane MIPI CSI 相机接口
    • 2-Lane MIPI DSI 显示器接口
    • SD卡槽(用于加载操作系统和存储)
    • PoE(Power Over Ethernet)供电接口
    • 音频与复合视频接口(3.5MM音频口,可以输出音频或AV视频信号)

interface

  • 软件

    树莓派能运行Raspbian(基于Debian的树莓派定制操作系统)、Ubuntu等嵌入式Linux操作系统,当然也有Windows IoT等奇怪的系统,甚至还有人在上面移植了RTOS…

    当你已经可以运行Linux on ARM之后,你就可以把它当作一台标准计算机使用了~你可以在上面运行gcc、python解释器,配合使用各种库与软件包,能解锁这片SBC的全部潜力~

os

  • 应用

    具体用来做什么?这个问题我也研究了很久23333(因为我有至少3台树莓派在吃灰orz),我就说说我见过的~

    • 运行网页服务器(LAMP/LNMP等)
    • 当作联网监控使用(配合mjpg-streamer作视频推流)
    • 当作直播工具使用(与上一条类似)
    • 可以在上面跑OpenCV做些人脸识别、实时图像识别
    • 运行Minecraft服务器(我试过很多次,树莓派4B的话,原版带几个玩家不成问题)
    • 做成家庭云存储服务器(SMB、FTP等,可以提供媒体推流服务)
    • 做成网络音乐播放器(树莓派版网抑云?)
    • 当作无人机的主控(可以,但是功耗有点…(电池亲你还好吗?))
    • 接上屏幕,当作开发工具(可以运行VsCode的设备都是开发工具#滑稽)
    • 使用GPIO驱动多种外设、实现物联网控制等(这个玩法就太多了…)

国外的树莓派开源社区,有非常多的开源项目分享,是个找灵感的好地方

oshw

总之树莓派是个非常强大的开发平台,虽然也有同类产品的性能比他更优秀,但树莓派仍然是我认为的最好的入门嵌入式、Linux开发平台之一。为什么?因为别的产品的生态远没有树莓派丰富,非专业人员拿到手根本不会用啊喂。。

EmoeMonitor

我为什么要做这个项目呢…说来话长。首先有如下场景:

  • 我有一台闲置的树莓派4B,但我不知道用它来做什么好…
  • Emoe的网站服务器经常卡死,我百思不得其解…同时又必须时不时登录后台来一次硬重启
  • 我想做个能实时显示服务器的资源使用情况的监视器,使远端数据可视化,就像钟表一样。
  • 额外的,我想玩玩IoT,搭建一个MQTT Broker并跑一套前后端来实现室内的电器控制与环境感测

基于以上需求,我就有了这个项目的原动力~
那么我需要做什么呢?怎么做?

原理图 & PCB

我想的是使用一块外接TFT作为数据可视化的窗口。但是网上卖的3.5寸左右TFT全都是与树莓派平行的,我要它能够直立起来,免得我伸脖子去看(

既然在淘宝上找不到类似的产品,那就只能自己造轮子咯——
我之前做 EmoePower 的时候买了3倍的物料,所以还剩下2块2.8寸 320×240分辨率的TFT屏幕,所以我直接用这块屏了~
老样子,使用KiCad绘制原理图与PCB,非常简单,用一个40pin排母夹住板子边缘就可以让PCB直立起来。
当然,我发挥了传统艺能,在板子后面贴了2个传感器。

  1. 原理图

sch

  1. PCB

pcb

pcb_front

pcb_back

式姐好好看!

软件

服务器资源监控

首先是服务器的数据监控问题,最开始我使用了lnmp提供的php探针,具体说来就是从client端GET服务器上的php文件,那个文件中包含了服务器的实时运行状态和各种参数…但这种方法很快就被我弃用了。原因如下:

  • 首先是有潜在安全隐患(好吧其实哪都有…)
  • 其次会在accesslog里产生大量GET请求我看得好不爽啊555

这时候闲着无聊的宋学姐贴了过来,了解了我的需求之后,他用java写了一个server~
github链接如下

ServerResourceProbe-Github

它会获取服务器的CPU与内存使用情况,还有服务器的运行时间(他偷了个懒,获取的是Java Server运行的时间…),将这些数据打包发给Client。

在客户端(Client),跟服务器的接口对接上后,获取到的数据将被保存到一个本地的json文件中以待使用。

点屏方案

说到这个我可就不困了啊)
点屏算是我的传统艺能之一了…但是这次我却犯了难。

在树莓派上想要驱动外置LCD-TFT,无非就是通过SPI(我用的SPI接口屏)推屏。那么有以下几种控制spi的方法,同时他们各有优劣,我做了些取舍

Method Description 弃用/启用原因
BCM2835 Library 纯C语言,如果用了它屏幕驱动要手写…(虽然我也写了) 呜呜呜好麻烦 我不想用C写字符串解析与JSON解析什么的
Python 使用adafruit提供的底层接口库blinka将标准python与MicroPython对接起来 Python真香 嫖代码真香vv
Linux Device Tree 在Linux设备树上注册新SPI设备,并将Framebuffer映射过去 非常高效,而且这样就可以使用需要X11的GUI库了,但是我懒…

人生苦短,我用一次Python好了。
那么重点介绍一下这套框架吧——

Blinka与MicroPython

标准的Python是没有底层硬件的API的,也就是说使用标准Python不能控制底层硬件的行为。

而MicroPython是专为嵌入式处理器量身剪裁的轻量级Python解释器框架,通常是针对特定的处理器平台进行定制化,在MicroPython中就可以使用能直接控制硬件的API和函数。MicroPython可以在很多硬件平台上运行,比如STM32、AVR单片机(Arduino)、ESP32、K210等处理器上。

这是MicroPython的官方网站,还挺好玩的(主要是方便偷懒): https://micropython.org/

但是我们使用的是运行在树莓派上的标准Python,就像在普通的电脑上的Python一样,是不具有MicroPython的直通底层API的。这时候有许多第三方库就站出来了——比如 RPi.GPIO库,它就提供了所有直通树莓派底层硬件接口的Python函数。当然还有别的库,比如 GPIO Zero

那么为什么还要Blinka呢…
简单来说,Blinka是 "套娃之娃"。它相当于一个翻译官,将Python代码翻译成底层硬件操作代码。(当然这个过程貌似是调用RPi.GPIO实现的)使用Blinka的好处是,它将所有与硬件操作相关的Python语法规范化了,这个规范当然就是CircuitPython的规范咯(CircuitPython是一种MicroPython的变体)。Blinka是Adafruit开发出来用于在不同硬件平台上快速移植CircuitPython代码的。(Linux小企鹅比蟒蛇还是可爱点)

Py&Linux

比如说,我有3块开发板,STM32F4、树莓派4B、ESP32,我如果想要点亮一个LED,需要写3套不同框架下的代码。但如果使用MicroPython框架,我只需要一份代码就可以了~
以下是MicroPython支持的处理器平台(图源Adafruit)

cPy

而我为什么不用别的Python库呢?因为别的Python库没有 ili9341(这块LCD屏幕的控制器) 的驱动库,而Adafruit家有一个库叫 adafruit_rgb_display ,这个库中包含了多达十几种屏幕控制器,其中就包含了ili9341。

这是Adafruit编写的Blinka的安装过程,请参考: https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/installing-circuitpython-on-raspberry-pi

我还是简要概括1下Setup吧…

Install Blinka on Raspberry Pi

我运行的OS是 树莓派爱好者基地 编译的基于Debian的64位发行版,当然推荐使用官方Raspbian系统。

  1. 事前工作——当然是装好Raspbian系统辣

  2. 升级树莓派与Python3(注意,python2不支持)
    在升级之前,记得换一下软件源,我推荐把apt的源和pip的源都换掉,可以清华源也可以阿里源。

sudo apt-get update

sudo apt-get upgrade

//  如果没装过pip,使用这条安装,装过就跳过这条
sudo apt-get install python3-pip

sudo pip3 install --upgrade setuptools
  1. 下载Adafruit提供的安装脚本进行安装
//  进入用户根目录
cd ~

sudo pip3 install --upgrade adafruit-python-shell

wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py

sudo python3 raspi-blinka.py
  1. 检查I2C与SPI是否启用
ls /dev/i2c* /dev/spi*

//  如果没问题,你应该看到这些内容:
/dev/i2c-1 /dev/spidev0.0 /dev/spidev0.1

如果需要更改使用的SPI port,请参考原文进行操作。

  1. 测试Blinka

新建一个 blinkatest.py,然后抄代码:

import board
import digitalio
import busio

print("Hello blinka!")

# Try to great a Digital input
pin = digitalio.DigitalInOut(board.D4)
print("Digital IO ok!")

# Try to create an I2C device
i2c = busio.I2C(board.SCL, board.SDA)
print("I2C ok!")

# Try to create an SPI device
spi = busio.SPI(board.SCLK, board.MOSI, board.MISO)
print("SPI ok!")

print("done!")

保存,然后run一下:

python3 blinkatest.py

看看输出信息如何~

Coding…

其实用了Python之后,就几乎没有什么代码量了:)
我不打算将所有代码开放,(毕竟我不希望我的服务器也被别人盯着…),放个删减版吧#手动和谐

import os
import time
import subprocess
import digitalio
import board
from PIL import Image, ImageDraw, ImageFont
import adafruit_rgb_display.ili9341 as ili9341
from adafruit_rgb_display.rgb import color565
import adafruit_bmp280

# 注册传感器,这里只用了BMP280,SHT30D没用
i2c = board.I2C()
bmp280 = adafruit_bmp280.Adafruit_BMP280_I2C(i2c)
bmp280.sea_level_pressure = 1013.25

# 屏幕接口配置
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D27)
reset_pin = digitalio.DigitalInOut(board.D22)
backlight = digitalio.DigitalInOut(board.D17)
backlight.switch_to_output()
backlight.value = True

BAUDRATE = 64000000  # SPI速率配置,使用PCB的话上到50MHz应该是没问题的,实测也不会花屏啥的
spi = board.SPI()

# Create the ST7789 display:
display = ili9341.ILI9341(
    spi,
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    #width=320,
    #height=240,
    #x_offset=0,
    #y_offset=0,
)

# 这里使用了PIL库新建了个320x240的图像对象
# Create blank image for drawing.
width = 320
height = 240
img = Image.new("RGB", (width, height))

rotation = 270
# Get drawing object to draw on image.
draw = ImageDraw.Draw(img)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
display.image(img, rotation)
padding = -2
top = padding
bottom = height - padding
x = 0

while True:
    draw.rectangle((0, 0, width, height), outline=0, fill=0)

    # 获取树莓派的信息
    cmd = "hostname -I | cut -d' ' -f1"
    IP = "IP: " + subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
    CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB  %.2f%%\", $3,$2,$3*100/$2 }'"
    MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = 'df -h | awk \'$NF=="/"{printf "Disk: %d/%d GB  %s", $3,$2,$5}\''
    Disk = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "cat /sys/class/thermal/thermal_zone0/temp |  awk '{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}'"  # pylint: disable=line-too-long
    Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")

    # 获取传感器(BMP280)的信息
    tmp = "Temp(board): %0.1f C" % bmp280.temperature
    pres = "Pressure: %0.1f hPa" % bmp280.pressure
    alti = "Altitude = %0.2f meters" % bmp280.altitude

    # 获取服务器信息...诶,被FLoydfish吃了![]~( ̄▽ ̄)~*

    # Set font to draw
    font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)

    y = top
    draw.text((x, y), "RPI local " + IP, font=font, fill="#FFFFFF")
    y += font.getsize(IP)[1]
    draw.text((x, y), "RPI's " + CPU, font=font, fill="#FFFF00")
    y += font.getsize(CPU)[1]
    draw.text((x, y), "RPI's " + MemUsage, font=font, fill="#00FF00")
    y += font.getsize(MemUsage)[1]
    draw.text((x, y), "RPI's " + Disk, font=font, fill="#0000FF")
    y += font.getsize(Disk)[1]
    draw.text((x, y), "RPI's " + Temp, font=font, fill="#FF00FF")

    # Set font to draw
    font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 16)
    y += 18
    draw.text((x, y), tmp, font=font, fill="#FF0000")
    y += 16
    draw.text((x, y), pres, font=font, fill="#FF0000")
    y += 16
    draw.text((x, y), alti, font=font, fill="#FF0000")
    #y += 16

    # 显示
    display.image(img, rotation)
    # 1秒1刷新
    time.sleep(1)

运行效果

看我使用柔焦滤镜拍出来的——
(焦锐奶化,毒德大学(bushi))

monitor

Fin

如果你对这个项目感兴趣,或者想要看看我的PCB设计文件,请联系我(因为不是什么大项目所以懒得开个repo了…)

树莓派还有很多的玩法,我们以后还会继续探索的~
下一期——MQTTIoT!

发表回复