【进迭时空 K1 MUSE Pi Pro】扩展板设计
本文介绍了进迭时空 K1 MUSE Pi Pro 的扩展板 PCB 设计,包括原理图、资源参数、环境搭建 、工程测试等。

项目介绍
扩展板设计:PCB、资源分布、参数特点、原理图等;
环境搭建:安装所需 python 库;
工程测试:驱动 OLED、RTC 时钟、AHT10温湿度计、ADC 呼吸灯、心率传感器等。
扩展板设计

资源
DHT11 温湿度传感器
4 路独立按键
4 颗单色 LED
6 颗 WS2812 彩色 LED
HX1838 红外接收
GL5528 光敏电阻
旋转电位器
无源蜂鸣器
接口
硬件 I2C 接口
0.96 寸 I2C OLED 接口
超声波模块接口
CH340E 芯片 USB 转 TTL 串口
预留数字拓展引脚
模拟拓展通道
原理图

PCB

实物图

硬件连接
使用 HDMI 数据线连接显示屏或 SSH 远程登录;
USB 接口连接鼠标键盘;
连接 LAN 有线网或连接 Wi-Fi 无线网;
使用 5V 直流电源 Type-C 供电;

若 CPU 温度过高,需安装散热片、风扇等主动制冷装置;
环境搭建
终端执行如下指令,搭建虚拟环境并激活
sudo apt update
sudo apt install python3-venv # 安装虚拟环境软件包
sudo apt install i2c-tools fonts-dejavu-core libgpiod-dev
python3 -m venv .venv # 创建虚拟环境
pip config set global.extra-index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple # 更改pip镜像源
source .venv/bin/activate # 激活虚拟环境安装所需软件包
pip install smbus2 # iic library
pip install luma.oled # oled library
pip install periphery # gpio libraryMINI 副屏
根据扩展板 OLED 接口,设计迷你副屏,显示系统运行参数状态等信息。

代码
终端执行 touch sys_oled.py 创建文件,添加如下代码
import time
import psutil
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont
# 加载字体
font_big = ImageFont.load_default(size=14)
# I2C4 总线,i2cdetect -y 4, OLED地址0x3C
serial = i2c(port=4, address=0x3C)
device = ssd1306(serial, width=128, height=64)
# 获取CPU温度
def get_cpu_temp():
try:
with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
temp = int(f.read()) / 1000
return f"{temp:.1f}C"
except:
return "N/A"
# 获取内存占用
def get_mem():
mem = psutil.virtual_memory()
used = mem.used / 1024 / 1024 / 1024
total = mem.total / 1024 / 1024 / 1024
percent = mem.percent
return f"RAM:{used:.0f}/{total:.0f}GB {percent}%"
# CPU使用率
def get_cpu():
cpu_pct = psutil.cpu_percent(interval=0.2)
return f"CPU:{cpu_pct}%"
# 系统运行时间
def get_uptime():
upt = time.time() - psutil.boot_time()
h = int(upt // 3600)
m = int((upt % 3600) // 60)
return f"RUN:{h}h{m:02d}m"
if __name__ == "__main__":
print("Board: SpacemiT K1 MUSE Pi Pro")
print("System Resource Monitor")
print("OLED update every 2 second")
try:
while True:
cpu_txt = get_cpu()
temp_txt = get_cpu_temp()
mem_txt = get_mem()
up_txt = get_uptime()
with canvas(device) as draw:
# 清屏
draw.rectangle(device.bounding_box, fill="black")
# TEXT Display
draw.text((0, 16), f"{cpu_txt} T:{temp_txt}", font=font_big, fill="white")
draw.text((0, 32), mem_txt, font=font_big, fill="white")
draw.text((0, 48), up_txt, font=font_big, fill="white")
draw.text((0, 0), "MUSE Pi Pro", font=font_big, fill="white")
time.sleep(2)
except KeyboardInterrupt:
print("\n退出OLED显示")保存代码。
效果
终端执行
python sys_oled.py运行程序;OLED 显示系统运行信息;

RTC 时钟
结合扩展板 OLED 和开发板 RTC 实现桌面动态时钟设计。

代码
终端执行 touch rtc_oled.py 创建文件,添加如下代码
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import Image, ImageFont
import os
import time
import datetime
# I2C总线4,OLED地址0x3C
serial = i2c(port=4, address=0x3C)
device = ssd1306(serial)
# 字体
font_fg = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 22)
font_date = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)
# 加载帧
frame_list = []
for i in range(1, 21):
img_path = os.path.join("frame", f"{i}.jpg")
img = Image.open(img_path).convert("1") # 转为OLED单色位图
frame_list.append(img)
frame_index = 0
week_map = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
try:
while True:
now = datetime.datetime.now()
date_str = now.strftime("%Y-%m-%d")
time_str = now.strftime("%H:%M")
week_str = week_map[now.weekday()]
with canvas(device) as draw:
# 动画
draw.bitmap((48, 0), frame_list[frame_index], fill=1)
# Week
draw.text((1, 2), week_str, font=font_date, fill=1)
# 时分
draw.text((1, 20), time_str, font=font_fg, fill=1)
# 年月日
draw.text((1, 48), date_str, font=font_date, fill=1)
# 循环切换帧
frame_index = (frame_index + 1) % 20
time.sleep(0.1)
except KeyboardInterrupt:
# Ctrl+C 退出清屏
device.clear()保存代码。
效果
终端执行
python rtc_oled.py运行程序;
OLED 显示日期、时间、动画等;

动态演示

温湿度计
使用杜邦线连接 AHT10 模块和扩展板板载 IIC 接口。

终端执行指令 sudo i2cdetect -y -r 4 显示 IIC4 总线挂载设备的地址;

其中 0x38 为 AHT10 对应地址,0x3c 是 OLED 的地址,0x48 是 PCF8591 的地址。
代码
终端执行 touch aht_oled.py 创建文件,添加如下代码
#!/usr/bin/env python3
# AHT10 温湿度 + SSD1306 OLED 显示 - MUSE PI Pro I2C4
import time
import sys
from PIL import ImageFont
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
try:
import smbus2 as smbus
except ImportError:
import smbus
# ---------------------- 硬件配置 ----------------------
I2C_BUS = 4
AHT10_ADDR = 0x38
OLED_ADDR = 0x3C
# AHT10 命令定义
CMD_INIT = [0xE1, 0x08, 0x00] # 初始化/校准
CMD_MEASURE = [0xAC, 0x33, 0x00] # 触发测量
CMD_SOFT_RESET = 0xBA # 软复位
CMD_STATUS = 0x71 # 读取状态
# OLED初始化
serial = i2c(port=I2C_BUS, address=OLED_ADDR)
device = ssd1306(serial, width=128, height=64)
# 加载粗体字体
try:
big_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
except:
big_font = ImageFont.load_default()
# ---------------------- AHT10 驱动类 ----------------------
class AHT10:
def __init__(self, bus_num=I2C_BUS, addr=AHT10_ADDR):
self.bus = smbus.SMBus(bus_num)
self.addr = addr
self._init_sensor()
def _init_sensor(self):
"""初始化 AHT10"""
# 软复位
self.bus.write_byte(self.addr, CMD_SOFT_RESET)
time.sleep(0.02)
# 发送初始化命令
self.bus.write_i2c_block_data(self.addr, CMD_INIT[0], CMD_INIT[1:])
time.sleep(0.01)
# 检查状态,确认校准标志位
status = self.bus.read_byte(self.addr)
if not (status & 0x08):
print("警告: AHT10 校准未完成,数据可能不准确")
def read(self):
"""
读取温湿度
返回: (temperature_C, humidity_RH)
"""
# 触发测量
self.bus.write_i2c_block_data(self.addr, CMD_MEASURE[0], CMD_MEASURE[1:])
time.sleep(0.08) # 测量等待时间
# 读取 6 字节数据
data = self.bus.read_i2c_block_data(self.addr, 0x00, 6)
# 检查状态位 bit7,1=忙,0=完成
if data[0] & 0x80:
time.sleep(0.01)
data = self.bus.read_i2c_block_data(self.addr, 0x00, 6)
# 解析湿度 (20bit)
humidity_raw = (data[1] << 12) | (data[2] << 4) | (data[3] >> 4)
humidity = (humidity_raw / 1048576.0) * 100.0
# 解析温度 (20bit)
temp_raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]
temperature = (temp_raw / 1048576.0) * 200.0 - 50.0
# 限幅
humidity = max(0, min(100, humidity))
return round(temperature, 2), round(humidity, 2)
def close(self):
self.bus.close()
# ---------------------- 主函数:终端打印 + OLED显示 ----------------------
def main():
print("=" * 40)
print(" AHT10 温湿度传感器 + OLED显示")
print(" I2C Bus: 4, AHT10:0x38 OLED:0x3C")
print("=" * 40)
try:
sensor = AHT10()
while True:
temp, humi = sensor.read()
# 终端打印温湿度
print(f"温度: {temp:>6.2f} °C 湿度: {humi:>6.2f} %RH")
# OLED Display
with canvas(device) as draw:
draw.rectangle(device.bounding_box, fill="black")
draw.text((5, 8), f"TEMP: {temp} °C", font=big_font, fill="white")
draw.text((5, 32), f"HUMI: {humi} %RH", font=big_font, fill="white")
time.sleep(2)
except KeyboardInterrupt:
print("\n程序已退出")
except Exception as e:
print(f"错误: {e}")
finally:
try:
sensor.close()
except:
pass
if __name__ == "__main__":
main()保存代码。
效果
终端执行
python aht_oled.py运行程序;

OLED 显示 AHT10 传感器采集的温湿度信息;

呼吸灯
通过 PCF8591 芯片的 AOUT 引脚,实现 DAC 控制 LED 亮度周期变化的呼吸灯效果。

代码
终端执行 touch pcf8591_breath_oled.py 创建文件,添加如下代码
#!/usr/bin/env python3
# PCF8591 AOUT 呼吸灯 + OLED动态DAC电压波形 (MUSE PI Pro I2C4)
import time
import math
import sys
from PIL import ImageFont, ImageDraw, Image
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
try:
import smbus2 as smbus
except ImportError:
import smbus
# ===================== 硬件配置 =====================
I2C_BUS = 4
PCF8591_ADDR = 0x48
DAC_ENABLE = 0x40
OLED_ADDR = 0x3C
OLED_WIDTH = 128
OLED_HEIGHT = 64
VCC = 3.3
V_LED = 2.3 # LED 点亮阈值电压
# DAC有效范围
DAC_MIN = int((V_LED / VCC) * 255)
DAC_MAX = 255
DAC_RANGE = DAC_MAX - DAC_MIN
# OLED初始化
serial = i2c(port=I2C_BUS, address=OLED_ADDR)
device = ssd1306(serial, width=OLED_WIDTH, height=OLED_HEIGHT)
# 小字用于标题
try:
font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 10)
except:
font_small = ImageFont.load_default()
# 波形缓冲区:保存历史DAC电压点,长度等于屏幕宽度
wave_buffer = [0.0] * OLED_WIDTH
def dac_to_volt(dac):
"""DAC数值转输出电压 0~3.3V"""
return round((dac / 255.0) * VCC, 2)
def draw_wave(draw, current_volt):
"""绘制动态电压波形,滚动刷新"""
global wave_buffer
# 缓冲区左移,丢弃最早数据
wave_buffer = wave_buffer[1:]
wave_buffer.append(current_volt)
# 波形绘图区域:上下留边,顶部文字占用12像素
top_margin = 12
plot_h = OLED_HEIGHT - top_margin
volt_min = V_LED - 0.1
volt_max = VCC + 0.1
# 绘制标题文字
draw.rectangle((0, 0, OLED_WIDTH, top_margin), fill="black")
draw.text((2, 0), f"AOUT Voltage: {current_volt:.2f}V", font=font_small, fill="white")
# 绘制坐标轴底线
draw.line([0, OLED_HEIGHT/2+7, OLED_WIDTH, OLED_HEIGHT/2+7], fill="white")
# 逐点绘制波形曲线
for x in range(OLED_WIDTH):
v = wave_buffer[x]
# 电压映射到屏幕Y坐标:电压越高Y越小(向上)
ratio = (v - volt_min) / (volt_max - volt_min)
y = OLED_HEIGHT - int(ratio * plot_h)
# 绘制竖线连接基线到波形点
draw.point((x, y), fill="white")
if x > 0:
prev_v = wave_buffer[x-1]
prev_ratio = (prev_v - volt_min) / (volt_max - volt_min)
prev_y = OLED_HEIGHT - int(prev_ratio * plot_h)
draw.line([x-1, prev_y, x, y], fill="white")
def main():
bus = smbus.SMBus(I2C_BUS)
print(f"PCF8591 @ I2C-{I2C_BUS}")
print(f"LED 阈值: {V_LED}V | 有效 DAC: {DAC_MIN}~{DAC_MAX} ({DAC_RANGE} 级)")
print("OLED实时显示AOUT电压波形,按 Ctrl+C 停止\n")
try:
period = 1.0 # 呼吸周期
steps = 30
delay = period / steps
while True:
for i in range(steps):
t = i / steps
# 正弦波生成
sin_val = (math.sin(t * 2 * math.pi - math.pi / 2) + 1) / 2
# Gamma校正
gamma = 1.0
brightness = math.pow(sin_val, gamma)
# 映射DAC值
dac_val = DAC_MIN + int(brightness * DAC_RANGE)
dac_val = min(DAC_MAX, max(DAC_MIN, dac_val))
# 输出DAC
bus.write_i2c_block_data(PCF8591_ADDR, DAC_ENABLE, [dac_val])
# 计算当前输出电压
volt = dac_to_volt(dac_val)
# OLED刷新波形
with canvas(device) as draw:
draw_wave(draw, volt)
time.sleep(delay)
except KeyboardInterrupt:
print("\n停止,熄灭 LED")
bus.write_i2c_block_data(PCF8591_ADDR, DAC_ENABLE, [0])
# 清屏
with canvas(device) as draw:
draw.rectangle(device.bounding_box, fill="black")
finally:
bus.close()
if __name__ == "__main__":
main()保存代码。
效果
终端执行 python pcf8591_breath_oled.py 运行程序;

不同阶段 LED 亮度变化

动态演示

心率监测
心率传感器,采用光电容积脉搏波描记法,通过测量血液中血红蛋白随心脏跳动而对氧气吸收的变化量来测量人体心率参数。

具有响应快,性能稳定,适应性强等特点。
模块拥有方波和脉搏波两种信号输出模式,可以通过板载开关去自由切换输出信号。
脉搏波将输出一个连续的心率波形,而方波将根据心率的变化输出对应的方波。
心率传感器体积小巧,可以佩戴于手指、手腕等检测位置。
硬件连接
使用杜邦线将数据引脚与扩展板 PCF8591 的 A2 引脚连接,通过 ADC 实现心率计算、脉搏读取等。
代码
终端执行 touch hr_oled.py 创建文件,添加如下代码
import time
import threading
import os
from collections import deque
from periphery import GPIO
# ========== OLED library ==========
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import Image, ImageFont
# ========= 硬件配置 =========
GPIO_CHIP = "/dev/gpiochip0"
GPIO_LINE = 35 # 心率GPIO
BEAT_WINDOW = 5
DEBOUNCE_MS = 300
TIMEOUT_MS = 3000
# OLED配置
OLED_PORT = 4
OLED_ADDR = 0x3C
FRAME_COUNT = 20
# 字体
font_fg = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 22)
font_date = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 16)
# 星期
week_map = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
# ==================== OLED全局对象 ====================
serial = i2c(port=OLED_PORT, address=OLED_ADDR)
device = ssd1306(serial)
frame_list = []
frame_index = 0
# 加载动画帧
for i in range(1, FRAME_COUNT + 1):
img_path = os.path.join("frame_hr", f"{i}.png")
img = Image.open(img_path).convert("1")
frame_list.append(img)
# ==================== 心率监测器 ====================
class HeartRateMonitor:
def __init__(self, chip, line):
self.chip_path = chip
self.line_num = line
self.gpio = GPIO(chip, line, "in", edge="rising")
self.beat_times = deque(maxlen=BEAT_WINDOW)
self.bpm = 0
self.running = False
self._thread = None
self._lock = threading.Lock()
def start(self):
self.running = True
self._thread = threading.Thread(target=self._monitor_loop, daemon=True)
self._thread.start()
print(f"[INFO] 心率监测启动: {self.chip_path} line {self.line_num}")
def _monitor_loop(self):
last_beat_ns = 0
while self.running:
if not self.gpio.poll(timeout=TIMEOUT_MS / 1000.0):
with self._lock:
self.bpm = 0
continue
try:
edge, timestamp_ns = self.gpio.read_event()
except Exception:
continue
if last_beat_ns > 0:
interval_ms = (timestamp_ns - last_beat_ns) / 1_000_000
if interval_ms < DEBOUNCE_MS:
continue
last_beat_ns = timestamp_ns
self.beat_times.append(timestamp_ns)
if len(self.beat_times) >= 2:
intervals = []
times = list(self.beat_times)
for i in range(1, len(times)):
intervals.append((times[i] - times[i-1]) / 1_000_000_000.0)
intervals.sort()
median_interval = intervals[len(intervals) // 2]
bpm = int(60.0 / median_interval)
bpm = max(30, min(220, bpm))
with self._lock:
self.bpm = bpm
def get_bpm(self):
with self._lock:
return self.bpm
def get_beat_count(self):
return len(self.beat_times)
def stop(self):
self.running = False
if self._thread:
self._thread.join(timeout=1.0)
self.gpio.close()
# ==================== 主程序(仅增加OLED渲染逻辑) ====================
def main():
print("=" * 50)
print(" 心率监测仪 ")
print("=" * 50)
print("请将传感器拨到 'D' 档")
print("按 Ctrl+C 停止\n")
monitor = HeartRateMonitor(GPIO_CHIP, GPIO_LINE)
monitor.start()
global frame_index
try:
while True:
bpm = monitor.get_bpm()
beat_cnt = monitor.get_beat_count()
# 控制台打印
if bpm > 0:
bar = "█" * (bpm // 10) + "░" * ((220 - bpm) // 20)
print(f"\r♥ 心率: {bpm:3d} BPM [{bar}] 脉搏数: {beat_cnt:2d}", end="", flush=True)
else:
print("\r♥ 等待心跳信号... ", end="", flush=True)
# ========== OLED刷新 ==========
with canvas(device) as draw:
# 帧动画
draw.bitmap((0, 0), frame_list[frame_index], fill=1)
# 时间日期星期
now = time.localtime()
date_str = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d}"
time_str = f"{now.tm_hour:02d}:{now.tm_min:02d}"
week_str = week_map[now.tm_wday]
#draw.text((2, 2), date_str, font=font_date, fill=1)
#draw.text((2, 18), week_str, font=font_date, fill=1)
#draw.text((2, 34), time_str, font=font_fg, fill=1)
# 心率BPM
draw.text((80, 8), "BPM", font=font_date, fill=1)
if bpm > 0:
draw.text((75, 32), f"{bpm}", font=font_fg, fill=1)
else:
draw.text((67, 32), "Waiting Pulse", font=font_date, fill=1)
# 动画帧自增循环
frame_index = (frame_index + 1) % FRAME_COUNT
time.sleep(0.1)
except KeyboardInterrupt:
print("\n\n[INFO] 停止监测")
device.clear() # OLED清屏
finally:
monitor.stop()
if __name__ == "__main__":
main()保存代码。
效果
终端执行
python hr_oled.py运行程序;终端显示心率 BPM 数据和动态条;

OLED 显示心跳动画和 BPM 心率数值;

动态演示

串口打印模拟输出,并绘制心率曲线

总结
本文介绍了进迭时空 K1 MUSE Pi Pro 的扩展板 PCB 设计,包括原理图、资源参数、环境搭建 、工程测试等,为相关产品的快速开发和应用设计提供了参考。
