| 背景 微雪 4.26inch 电子墨水屏介绍 各项参数 通信方式 硬件连接 GPIO 输入输出、中断测试 输入、输出相关的API 中断相关的API 测试代码 实测结果 SPI 通信测试 安装 spidev 库 测试代码 实测结果 电子墨水屏驱动移植 驱动分析 VisionFive2 类的实现 电子墨水屏效果展示 |
# -*- coding: utf-8 -*-
import VisionFive.gpio as GPIO
import time
import logging
import sys
# --- 配置 Logging ---
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler("gpio_official_test.log")
]
)
logger = logging.getLogger("VisionFive2_GPIO")
# --- 全局常量定义 (BCM 编号) ---
# 输出引脚:GPIO44 -> Board Pin 40
OUT_PIN = 44
# 扫描输入引脚:GPIO61 -> Board Pin 38
IN_SCAN_PIN = 61
# 中断输入引脚:GPIO36 -> Board Pin 36
IN_INT_PIN = 36
# 状态记录
out_state = GPIO.LOW
last_toggle_time = 0
last_scan_time = 0
def detect_callback(pin, edge_type):
"""
官方手册规范回调函数
edge_type: 1 为 Rising (上升沿), 2 为 Falling (下降沿)
"""
if edge_type == 1:
msg = "Rising edge is detected (上升沿)"
elif edge_type == 2:
msg = "Falling edge is detected (下降沿)"
else:
msg = f"Unknown edge type: {edge_type}"
logger.info("🔔 [INTERRUPT] Pin: GPIO%d | %s", pin, msg)
def setup():
"""
硬件初始化:完全遵循官方手册示例
"""
global last_toggle_time, last_scan_time
try:
# 设置编号模式
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# 1. 配置输出 (Board 40)
GPIO.setup(OUT_PIN, GPIO.OUT, initial=GPIO.LOW)
logger.info("✅ Setup: GPIO44 (Board 40) 输出已就绪")
# 2. 配置扫描输入 (Board 38)
GPIO.setup(IN_SCAN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
logger.info("✅ Setup: GPIO61 (Board 38) 扫描已就绪")
# 3. 配置中断输入 (Board 36)
GPIO.setup(IN_INT_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# 按照官方手册用法添加事件检测
# bouncetime=20 设置 20ms 的消抖时间
GPIO.add_event_detect(
IN_INT_PIN,
GPIO.BOTH,
callback=detect_callback,
bouncetime=20
)
logger.info("✅ Setup: GPIO36 (Board 36) 中断已就绪 (Mode: BOTH)")
last_toggle_time = time.time()
last_scan_time = time.time()
except Exception as e:
logger.error("❌ Setup 异常: %s", str(e))
sys.exit(1)
def loop():
"""
非阻塞任务调度循环
"""
global out_state, last_toggle_time, last_scan_time
current_time = time.time()
# 任务 1: GPIO44 每 2 秒切换一次输出
if current_time - last_toggle_time >= 2.0:
out_state = not out_state
GPIO.output(OUT_PIN, out_state)
logger.info("💡 [OUTPUT] GPIO44 -> %s", "HIGH" if out_state else "LOW")
last_toggle_time = current_time
# 任务 2: GPIO61 每 0.5 秒扫描一次输入
if current_time - last_scan_time >= 0.5:
val = GPIO.input(IN_SCAN_PIN)
logger.info("🔍 [INPUT ] GPIO61 -> %d", val)
last_scan_time = current_time
# 适当休眠,降低 CPU 占用
time.sleep(0.01)
def main():
"""
程序主入口
"""
logger.info("🚀 VisionFive 2 GPIO 官方规范测试程序启动...")
setup()
try:
while True:
loop()
except KeyboardInterrupt:
logger.info("⏹ 用户停止程序")
finally:
# 移除事件监听并清理
GPIO.remove_event_detect(IN_INT_PIN)
GPIO.cleanup()
logger.info("🧹 资源已清理并安全退出")
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
import spidev
import time
import logging
import sys
# --- 配置 Logging ---
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger("VF2_Spidev_Matrix")
# --- 测试矩阵配置 ---
BUS = 1
DEVICE = 0 # 对应 /dev/spidev1.0
SPEEDS = [100000, 500000, 1000000, 5000000, 10000000] # 100K 到 10M
MODES = [0, 1, 2, 3]
# 状态追踪
speed_idx = 0
mode_idx = 0
spi = None
def setup():
"""初始化 spidev 实例"""
global spi
try:
spi = spidev.SpiDev()
spi.open(BUS, DEVICE)
logger.info("✅ 已打开 SPI 设备: /dev/spidev%d.%d", BUS, DEVICE)
return True
except Exception as e:
logger.error("❌ 无法打开 SPI 设备: %s (请检查权限: sudo chmod 666 /dev/spidev1.0)", str(e))
return False
def loop():
"""主测试循环:遍历所有模式和速率"""
global speed_idx, mode_idx, spi
current_speed = SPEEDS[speed_idx]
current_mode = MODES[mode_idx]
# 1. 配置当前测试参数
spi.max_speed_hz = current_speed
spi.mode = current_mode
# 2. 生成动态测试数据
test_str = f"M{current_mode}-S{current_speed//1000}K"
test_data = [ord(c) for c in test_str]
logger.info("--- 测试配置: Mode %d | Speed %d Hz ---", current_mode, current_speed)
try:
# 3. 执行同步传输
# xfer2 会在传输期间保持 CS (片选) 信号有效,最适合回环测试
start_time = time.perf_counter()
resp = spi.xfer2(test_data)
duration = (time.perf_counter() - start_time) * 1000 # 毫秒
# 4. 校验数据
if resp == test_data:
recv_str = "".join([chr(b) for b in resp])
logger.info("✅ [成功] 耗时: %.2fms | 收到数据: '%s'", duration, recv_str)
else:
logger.error("❌ [失败] 数据不一致!")
logger.error("发送: %s", test_data)
logger.error("接收: %s", resp)
except Exception as e:
logger.error("❌ 传输过程中发生错误: %s", str(e))
# 5. 更新索引,遍历矩阵
speed_idx += 1
if speed_idx >= len(SPEEDS):
speed_idx = 0
mode_idx += 1
if mode_idx >= len(MODES):
mode_idx = 0
logger.info("🎉 所有测试矩阵已完成一遍。")
print("-" * 55)
time.sleep(1)
def main():
logger.info("🚀 开始星光 2 SPI 压力测试 (spidev 库)")
logger.info("测试范围: 模式 0-3, 速率 100K-10M")
if not setup():
sys.exit(1)
try:
while True:
loop()
except KeyboardInterrupt:
logger.info("⏹ 用户手动停止测试")
finally:
if spi:
spi.close()
logger.info("🧹 SPI 已关闭,测试程序退出")
if __name__ == "__main__":
main()
class VisionFive2:
# Pin definition
# GPIO 采用 BOARD 编号而非 BCM 编号
RST_PIN = 11 # GCM.GPIO42
DC_PIN = 22 # BCM.GPIO50
PWR_PIN = 12 # BCM.GPIO38
BUSY_PIN = 18 # BCM.GPIO51
# CS 采用硬件,不用 GPIO 模拟
CS_PIN = 24 # BCM.GPIO49, 实际上用SPI控制CS而非GPIO
def __init__(self):
import VisionFive.gpio as GPIO
import spidev
# 核心修改 1:在构造函数中创建 SPI 对象,并设置初始化状态位
self.GPIO = GPIO
self.SPI = spidev.SpiDev()
self._spi_opened = False
# 初始化 GPIO 资源
GPIO.setmode(GPIO.BOARD)
GPIO.setup(self.RST_PIN, GPIO.OUT)
GPIO.setup(self.DC_PIN, GPIO.OUT)
GPIO.setup(self.PWR_PIN, GPIO.OUT)
GPIO.setup(self.BUSY_PIN, GPIO.IN, GPIO.PUD_DOWN)
# GPIO.setup(self.CS_PIN, GPIO.OUT)
def digital_write(self, pin, value):
if pin == self.CS_PIN:
# CS 采用硬件,不用 GPIO 模拟
pass
else:
self.GPIO.output(pin, value)
def digital_read(self, pin):
return self.GPIO.input(pin)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(self, data):
self.SPI.writebytes(data)
def spi_writebyte2(self, data):
self.SPI.writebytes2(data)
def module_init(self, cleanup=False):
# 每次初始化都确保电源打开
self.GPIO.output(self.PWR_PIN, True)
if cleanup:
find_dirs = [
os.path.dirname(os.path.realpath(__file__)),
'/usr/local/lib',
'/usr/lib',
]
self.DEV_SPI = None
for find_dir in find_dirs:
val = int(os.popen('getconf LONG_BIT').read())
logging.debug("System is %d bit"%val)
if val == 64:
so_filename = os.path.join(find_dir, 'DEV_Config_64.so')
else:
so_filename = os.path.join(find_dir, 'DEV_Config_32.so')
if os.path.exists(so_filename):
from ctypes import CDLL
self.DEV_SPI = CDLL(so_filename)
break
if self.DEV_SPI is None:
raise RuntimeError('Cannot find DEV_Config.so')
self.DEV_SPI.DEV_Module_Init()
else:
# 核心修改 2:检查标记位,确保 /dev/spidev1.0 只 open 一次
if not self._spi_opened:
self.SPI.open(1, 0)
self.SPI.max_speed_hz = 4000000
self.SPI.mode = 0b00
self._spi_opened = True
# logging.debug("SPI Device opened and cached.")
return 0
def module_exit(self, cleanup=False):
# 正常刷新过程中的 exit 只关闭显示器电源,进入低功耗
self.GPIO.output(self.RST_PIN, False)
self.GPIO.output(self.DC_PIN, False)
self.GPIO.output(self.PWR_PIN, False)
# 核心修改 3:只有当 cleanup=True (线程退出) 或实例销毁时才真正关闭 SPI
if cleanup and self._spi_opened:
self.SPI.close()
self._spi_opened = False
# 释放 GPIO 资源
self.GPIO.cleanup(self.RST_PIN)
self.GPIO.cleanup(self.DC_PIN)
self.GPIO.cleanup(self.PWR_PIN)
self.GPIO.cleanup(self.BUSY_PIN)
# self.GPIO.cleanup(self.CS_PIN)
# logging.debug("SPI and GPIO resources fully released.")
def __del__(self):
"""
核心修改 4:析构函数。
如果应用层忘记调用 module_exit(cleanup=True),
Python 解释器在回收对象时会强制清理句柄。
"""
try:
self.module_exit(cleanup=True)
except:
pass
电子墨水屏效果展示