Python 应用开发
本文档讲在 RK3588 出厂 Debian 12 上用 Python 3 做应用开发:环境配置、GPIO / I2C / SPI / UART 外设访问、Flask Web 服务,最后是把程序部署到板子上长期运行。
适用范围: RK3588 Linux 6.1 Debian 12 / Ubuntu 22.04 系统(C-3588LQ V1.0 + SHIMETAPI-M5-R2A V1.0 底板),Python 3.11(Ubuntu 镜像为 3.10)。
1. 概念基础
1.1 应用场景
- 外设控制(GPIO / I2C / SPI / UART)
- 网络通信(TCP/UDP、HTTP、MQTT)
- 数据处理(NumPy / Pandas)
- Web 服务(Flask / FastAPI)
- AI 推理(RKNN Lite)
1.2 软件栈
Python 应用层(用户编写)
↓ 标准库 + 第三方包(pip)
CPython 3.11 解释器
↓ 系统调用(syscall)
内核(Linux 6.1 + 驱动)
↓
硬件(UART / I2C / SPI / GPIO / NPU)从上到下四层:你写的 Python 代码;下面的解释器和各种包;再往下通过系统调用进内核;最底下是硬件。后面几章的外设编程,本质上都是 Python 通过库去读写 /dev/i2c-、/dev/ttyS 这类设备文件。
2. 准备工作
2.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | C-3588LQ 核心板 + SHIMETAPI-M5-R2A 底板 | 1 | Debian 出厂 |
| 调试串口线 | USB 转 TTL(3.3V) | 1 | 波特率 1500000 |
| 网线 | 标准 RJ45 | 1 | 网络 / SSH |
2.2 硬件确认
在开发板上跑下面几条命令,确认 Python 环境:
python3 --version
# Python 3.11.2
pip3 --version
# pip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11)
uname -m
# 输出:aarch64python3 --version 输出 Python 3.11.2,解释器就绪;uname -m 输出 aarch64,系统架构对得上。
注意:出厂镜像没有预装 pip3(Python 的包管理器)。第一次用之前必须执行 sudo apt install -y python3-pip,或者用 python3 -m ensurepip 装一个。
3. 软件准备
3.1 Python 环境安装
sudo apt update
sudo apt install -y python3 python3-pip python3-venv python3-dev
# 升级 pip
pip3 install --upgrade pip这里的 python3-venv 是用来创建虚拟环境的工具:虚拟环境给每个项目一份独立的包目录,装多少包都不会弄乱系统 Python。用法见第 6 节。
3.2 常用外设库
下面按外设类型给出常用库的安装命令(串口、I2C、SPI、GPIO、Web 框架、数据处理、摄像头):
# 串口
pip3 install pyserial
# I2C
pip3 install smbus2
# SPI(系统包)
sudo apt install -y python3-spidev
# GPIO(RK3588 走 sysfs,无专用 Python 库)
sudo apt install -y python3-gpiozero
# Web 框架
pip3 install flask
# 数据处理
pip3 install numpy pandas
# 摄像头
pip3 install opencv-python出厂镜像没有预装 numpy / opencv-python / smbus2 / flask,要先
apt或pip3 install --break-system-packages之后才能用。推荐用apt装系统包(路径标准化):sudo apt install -y python3-pip python3-serial python3-smbus \ python3-flask python3-numpy python3-spidev python3-gpiozero # smbus2(v2 API 支持 with 上下文)需 pip 装: sudo pip3 install --break-system-packages smbus2装完用 import 验证,两条命令都有输出说明库全部导入成功:
python3 -c "import serial, smbus, flask, numpy, spidev, gpiozero; print('all ok')" # OK python3 -c "import smbus2; print('smbus2', smbus2.__version__)" # smbus2 0.6.1
3.3 启用外设访问
Linux 把外设暴露成 /dev 下面的文件(设备节点),程序打开、读写这些文件就是在操作硬件。I2C 和 SPI 的节点由内核自动创建,前提是设备树(DTS,向内核描述板子硬件配置的文件)里 enable 了对应控制器:
# I2C / SPI 设备节点由内核自动创建,前提是设备树 enable 了控制器
ls /dev/i2c-* 2>&1 # 本机有 i2c-0/1/3/5/6/7/9
ls /dev/spidev* 2>&1 # 通常空(需在 DTS 中挂 spidev 节点)
# 用户权限
sudo usermod -aG i2c,gpio,dialout $USER # 注销重新登录生效下面的输出取自在设备上执行这些命令:
$ ls /dev/i2c-*
/dev/i2c-0 /dev/i2c-1 /dev/i2c-3 /dev/i2c-5 /dev/i2c-6 /dev/i2c-7 /dev/i2c-9
$ ls /dev/spidev*
ls: cannot access '/dev/spidev*': No such file or directory
$ groups | grep -E "i2c|gpio"
dialout注意:
- I2C 设备节点对非 root 用户默认不可读写(需
sudo或设 udev 规则),Debian 12 默认也没有i2c/gpio组。- SPI 控制器虽然在 DTS 启用(kernel 有 spidev 模块),但没有任何 spidev 节点,示例代码的
spi.open()在本机会失败。- 调通 SPI 需在板级 dts 加
&spiN { status = "okay"; spidev@xx {...} };后重新编译烧录。
4. GPIO / I2C / SPI / UART 编程示例
4.1 GPIO(sysfs 方式,跨平台通用)
sysfs 方式就是通过读写 /sys/class/gpio 下的文件来控制 GPIO,不需要额外库,代码在任何 Linux 上都能跑:
#!/usr/bin/env python3
"""GPIO LED 闪烁示例 — RK3588"""
import os
import time
class RK3588GPIO:
"""RK3588 GPIO 控制(通过 sysfs)"""
BASE = "/sys/class/gpio"
def __init__(self, gpio_num):
self.gpio_num = gpio_num
self.path = f"{self.BASE}/gpio{gpio_num}"
def export(self):
try:
with open(f"{self.BASE}/export", "w") as f:
f.write(str(self.gpio_num))
time.sleep(0.1)
except FileExistsError:
pass # 已导出
def unexport(self):
try:
with open(f"{self.BASE}/unexport", "w") as f:
f.write(str(self.gpio_num))
except FileNotFoundError:
pass
def set_direction(self, dir_):
"""dir_: 'in' | 'out'"""
with open(f"{self.path}/direction", "w") as f:
f.write(dir_)
def set_value(self, v):
with open(f"{self.path}/value", "w") as f:
f.write("1" if v else "0")
def get_value(self):
with open(f"{self.path}/value", "r") as f:
return int(f.read().strip())
def blink(gpio_num, count=5, interval=0.5):
led = RK3588GPIO(gpio_num)
led.export()
led.set_direction("out")
try:
for i in range(count):
led.set_value(1)
print(f"LED ON ({i + 1}/{count})")
time.sleep(interval)
led.set_value(0)
print(f"LED OFF ({i + 1}/{count})")
time.sleep(interval)
finally:
led.unexport()
if __name__ == "__main__":
# GPIO 编号 = bank × 32 + pin
# 例:GPIO4_A3 = 4*32 + 3 = 131
blink(gpio_num=131, count=5, interval=0.5)运行要用 sudo,因为 /sys/class/gpio/export 默认只对 root 可写:
$ sudo python3 gpio_test.py
Available gpiochips:
gpiochip0: label=gpio0 base=0 count=32
gpiochip32: label=gpio1 base=32 count=32
gpiochip64: label=gpio2 base=64 count=32
gpiochip96: label=gpio3 base=96 count=32
gpiochip128: label=gpio4 base=128 count=32
gpiochip256: label=nca9555 base=256 count=16 # GPIO 扩展芯片
gpiochip509: label=rk806-gpio base=509 count=3 # PMIC GPIO
Export gpio13...
gpio13 state: direction=in value=1
toggle: 1->1, 0->0
OK: GPIO export/control/unexport passed输出先列出板子上可用的 gpiochip 控制器(含 nca9555 扩展芯片和 rk806 PMIC 的 GPIO),最后的 OK: GPIO export/control/unexport passed 说明导出、电平控制、释放都正常。GPIO 编号按 bank × 32 + pin 换算,比如 GPIO4_A3 是 4×32+3 = 131(见代码注释)。
详细 GPIO 编号说明参考 peripheral-drivers/ch01 GPIO 驱动与控制。
4.2 I2C(smbus2)
smbus2 是 Linux 上常用的 I2C 库。示例从从机地址 0x44 的 0x00 寄存器起读 6 个字节:
#!/usr/bin/env python3
"""I2C 读取示例 — 假设外接从机 0x44"""
from smbus2 import SMBus
import time
ADDR = 0x44
BUS = 1 # /dev/i2c-1
def read_reg(bus, reg, length=1):
return bus.read_i2c_block_data(ADDR, reg, length)
def main():
with SMBus(BUS) as bus:
for _ in range(3):
data = read_reg(bus, 0x00, 6)
print("data:", [hex(b) for b in data])
time.sleep(1)
if __name__ == "__main__":
main()下面是在板端扫描全部 I2C 总线找设备的结果(本机没接 0x44 从机):
$ sudo python3 i2c_scan.py
i2c busses: [0, 1, 3, 5, 6, 7, 9]
/dev/i2c-0: 0 device(s) []
/dev/i2c-1: 0 device(s) []
/dev/i2c-3: 0 device(s) []
/dev/i2c-5: 0 device(s) []
/dev/i2c-6: 0 device(s) []
/dev/i2c-7: 0 device(s) []
/dev/i2c-9: 116 device(s) ['0x3', '0x4', '0x5', ..., '0x77'] # 全部 ACK(异常)从扫描结果能得出几条结论:
- i2c-0/1/3/5/6/7 无任何从机(板级默认不挂外部 I2C 设备)。
- i2c-9 对所有地址 0x03-0x77 全部 ACK,这是 RK3588 内部 HDMI/Type-C 控制器接的伪总线,不是真的外设,不能信任
read_byte()返回。- 真正接 SHT40(0x44)等传感器时,需先用
i2cdetect -y 1(用真从机所在总线)确认。
详细总线编号参考 peripheral-drivers/ch02 I2C 驱动开发。
4.3 UART(pyserial)
pyserial 是常用的串口库。示例打开 /dev/ttyS9,发一行、收一行:
#!/usr/bin/env python3
"""UART 收发示例"""
import serial
import time
PORT = "/dev/ttyS9"
BAUD = 1500000 # ttyS9 默认 1500000 baud
def main():
with serial.Serial(PORT, BAUD, timeout=1) as ser:
print(f"opened {PORT} @ {BAUD}")
ser.write(b"hello from python\n")
time.sleep(0.1)
line = ser.readline()
if line:
print("got:", line.decode(errors="ignore"))
else:
print("(no echo / timeout)")
if __name__ == "__main__":
main()注意:/dev/ttyS9 被 brcm_patc(蓝牙 HCI 进程)占用,不能直接当普通串口用。下面的输出也印证了这一点:lsof 能看到占用进程,写串口则超时:
$ sudo lsof /dev/ttyS9
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
brcm_patc 1976 root 3u CHR 4,73 0t0 93 /dev/ttyS9
$ sudo python3 uart_test.py
opening /dev/ttyS9 @ 1500000...
opened: /dev/ttyS9 @ 1500000
Traceback (most recent call last):
...
File ".../serialposix.py", line 636, in write
serial.serialutil.SerialTimeoutException: Write timeout排查思路和出路:
# 1) 确认是 BT 在用
sudo fuser /dev/ttyS9 # 输出 "1976"(brcm_patc 的 PID)
stty -a < /dev/ttyS9 | head # speed 1500000 baud, -crtscts
# 2) 板子只有 ttyS9 一个 UART(ttyS0~8/10 都不存在),要真用 UART 必须:
# a) 停 BT: sudo systemctl stop bluetooth (破坏 BT 功能)
# b) 或重定向 BT 到其他接口 (DTS 改)
# c) 或 USB-TTL 接 ttyUSBx (需外接)把注释归纳一下:板子上物理可用的 UART 只有 ttyS9 一个(ttyS0~8/10 都不存在),而且被蓝牙占了。真要用串口,要么停掉蓝牙服务(会破坏 BT 功能),要么改 DTS 把蓝牙挪走,要么外接 USB 转 TTL 用 ttyUSBx。
详细 UART 信息(console=ttyFIQ0,ttyS9@1500000,被 BT 占用)见 peripheral-drivers/ch04 UART 串口驱动。
4.4 SPI(spidev)
spidev 库通过 /dev/spidevX.Y 设备节点访问 SPI。示例假设 /dev/spidev1.0 已存在,读一片 Flash 的 JEDEC ID:
#!/usr/bin/env python3
"""SPI 收发示例 — 假设 /dev/spidev1.0 已挂载"""
import spidev
spi = spidev.SpiDev()
spi.open(1, 0) # bus=1, device=0
spi.max_speed_hz = 1_000_000
spi.mode = 0
# 写读
resp = spi.xfer2([0x9F, 0x00, 0x00, 0x00]) # 读 JEDEC ID
print("resp:", [hex(x) for x in resp])
spi.close()结果:板子没有任何 /dev/spidev* 节点,Python 的 spidev 模块装了但跑不通:
$ ls /dev/spidev*
ls: cannot access '/dev/spidev*': No such file or directory
$ python3 -c "import spidev; s=spidev.SpiDev(); s.open(1,0)"
Traceback (most recent call last):
...
FileNotFoundError: [Errno 2] No such file or directory: '/dev/spidev1.0'解决:需在板级 dts 给
&spi1加 spidev 子节点(如spidev@00 { compatible="rockchip,spidev"; reg=<0>; spi-max-frequency=<1000000>; };),重新编译烧录才会有节点。本镜像默认未配。
5. Flask Web 服务(系统监控)
Flask 是一个轻量 Web 框架,几十行代码就能把板子的信息通过 HTTP 接口暴露出来。示例提供三个接口:/api/system 读内核和内存,/api/temperature 遍历所有温度传感器,/api/cpu 返回负载:
#!/usr/bin/env python3
"""RK3588 实时监控(所用接口)"""
from flask import Flask, jsonify
import os
import subprocess
app = Flask(__name__)
@app.route("/api/system")
def system_info():
info = {
"kernel": os.uname().release,
"machine": os.uname().machine,
"hostname": os.uname().nodename,
}
with open("/proc/meminfo") as f:
for line in f:
if line.startswith("MemTotal"):
info["mem_total_kb"] = int(line.split()[1])
return jsonify(info)
@app.route("/api/temperature")
def temperature():
# 本机有 7 个 thermal_zone
zones = []
for z in sorted(os.listdir("/sys/class/thermal")):
if not z.startswith("thermal_zone"):
continue
tp = f"/sys/class/thermal/{z}"
with open(f"{tp}/type") as f:
tp_name = f.read().strip()
with open(f"{tp}/temp") as f:
t = int(f.read().strip()) / 1000.0
zones.append({"zone": z, "type": tp_name, "temp_c": t})
return jsonify(zones)
@app.route("/api/cpu")
def cpu_usage():
# 简单读取 load average
load1, load5, load15 = os.getloadavg()
return jsonify({"load1": load1, "load5": load5, "load15": load15})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False)pip3 install flask
python3 web_monitor.py &
# 主机浏览器访问
# http://192.168.49.60:5000/api/system
# http://192.168.49.60:5000/api/temperature下面的输出取自 apt 装 python3-flask 2.2.2 后,在板上启动 Flask 并用 curl 依次请求三个接口,全部返回 200:
$ nohup python3 web_monitor.py > /tmp/web.log 2>&1 &
$ cat /tmp/web.log
* Serving Flask app 'web_monitor'
* Running on http://127.0.0.1:5000
* Running on http://192.168.49.60:5000
$ curl -s http://127.0.0.1:5000/api/system
{"hostname":"linaro-alip","kernel":"6.1.141","machine":"aarch64","mem_total_kb":3989628}
$ curl -s http://127.0.0.1:5000/api/temperature
[{"temp_c":40.692,"type":"soc-thermal","zone":"thermal_zone0"},
{"temp_c":40.692,"type":"bigcore0-thermal","zone":"thermal_zone1"},
{"temp_c":40.692,"type":"bigcore1-thermal","zone":"thermal_zone2"},
{"temp_c":41.615,"type":"littlecore-thermal","zone":"thermal_zone3"},
{"temp_c":39.769,"type":"center-thermal","zone":"thermal_zone4"},
{"temp_c":40.692,"type":"gpu-thermal","zone":"thermal_zone5"},
{"temp_c":40.692,"type":"npu-thermal","zone":"thermal_zone6"}]
$ curl -s http://127.0.0.1:5000/api/cpu
{"load1":5.0,"load5":5.06,"load15":5.07}7 个 thermal_zone 全部读到了,温度 39.769~41.615°C,小核略高。同一套接口在局域网主机上通过 http://192.168.49.60:5000/... 也能访问。
6. 部署 / 远程运行
通常的开发节奏是:PC 上写好程序,把项目目录 scp 到板子,再 SSH 上去运行。下面创建 venv 那两步是建立并激活虚拟环境,之后的 pip 安装都落在项目目录里,不动系统 Python:
# 把项目推到开发板
scp -r my_project/ root@192.168.49.60:/root/
# SSH 上去
ssh root@192.168.49.60
# 跑起来
cd /root/my_project
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
nohup python3 app.py > app.log 2>&1 &要开机自启的话,写一个 systemd 服务(systemd 是 Linux 现在的标准服务管理器,下面配置里的 Restart=on-failure 表示程序异常退出时自动拉起):
cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Python App
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/my_project
ExecStart=/usr/bin/python3 /root/my_project/app.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now myapp下面的输出取自用上面的 service 文件实际启动 web_monitor 的过程。Active: active (running) 加上 5000 端口处于监听,服务就算起来了:
$ sudo cp myapp.service /etc/systemd/system/myapp.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable myapp
$ sudo systemctl start myapp
$ sudo systemctl status myapp --no-pager -l
● myapp.service - My Python App
Loaded: loaded (/etc/systemd/system/myapp.service; enabled; ...)
Active: active (running) since Thu 2026-10-15 09:04:12 UTC; 152ms ago
Main PID: 31269 (python3)
Tasks: 1 (limit: 4318)
Memory: 15.1M
$ ss -tln | grep 5000
LISTEN 0 0 0.0.0.0:5000 0.0.0.0:*
$ curl -s http://127.0.0.1:5000/api/system
{"hostname":"linaro-alip","kernel":"6.1.141","machine":"aarch64","mem_total_kb":3989628}
# 清理
$ sudo systemctl stop myapp && sudo systemctl disable myapp
$ sudo rm /etc/systemd/system/myapp.service
$ sudo systemctl daemon-reload7. 故障排除
7.1 smbus2 安装失败
sudo apt install -y python3-dev libi2c-dev
pip3 install smbus2 --no-cache-dir7.2 I2C 扫不到设备
i2cdetect -y 1
dmesg | grep -i i2c
# 检查设备树:cat /proc/device-tree/soc/i2c@fe5a0000/status7.3 权限问题
sudo usermod -aG i2c,gpio,dialout $USER
# 重新登录生效