You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python含异常处理仍挂起:树莓派+MCP3008 ADC测温问题

解决树莓派MCP3008 ADC读取挂起的问题

我之前在做树莓派+MCP3008测热敏电阻的项目时,也碰到过类似的长时间运行后读取操作挂起的情况,结合你的问题,给你几个可行的解决方案:

核心问题分析

你用Signal设置超时但还是挂起,大概率是因为GPIOZero封装的MCP3008在底层调用SPI硬件接口时卡住了——Signal信号没法中断内核级的硬件IO操作,所以抛出异常后,底层的阻塞还是没法解除。

解决方案1:用进程封装ADC读取(最有效)

进程和主线程是独立的资源空间,超时后可以强制终止进程,彻底解决底层阻塞的问题。这里给你一个用multiprocessing实现的示例:

from multiprocessing import Process, Queue
from gpiozero import MCP3008

def _read_adc_avg(queue, channel=0):
    # 子进程中执行ADC读取,避免阻塞主线程
    try:
        adc = MCP3008(channel=channel)
        samples = [adc.value for _ in range(5)]
        avg_value = sum(samples) / 5
        queue.put(avg_value)
    except Exception as e:
        queue.put(e)

def get_temp_percent(timeout=1):
    result_queue = Queue()
    # 启动子进程执行读取操作
    read_process = Process(target=_read_adc_avg, args=(result_queue,))
    read_process.start()
    # 等待进程执行完成,超时则强制终止
    read_process.join(timeout=timeout)
    
    if read_process.is_alive():
        read_process.terminate()
        read_process.join()  # 确保进程完全退出
        raise TimeoutError("ADC读取超时,已终止阻塞进程")
    
    # 从队列获取结果
    result = result_queue.get()
    if isinstance(result, Exception):
        raise result
    return result

这个方法的优势在于,哪怕底层SPI调用彻底卡住,我们也能通过terminate()直接杀死子进程,不会影响主线程的运行。

解决方案2:改用底层spidev库,手动控制SPI通信

有时候GPIOZero的高层封装会隐藏一些细节,改用spidev直接操作SPI总线,能更灵活地处理异常和重启设备:

import spidev
import threading

# 初始化SPI设备
spi = spidev.SpiDev()
spi.open(0, 0)  # bus=0, device=0对应CE0引脚
spi.max_speed_hz = 1000000  # 设置SPI速率,匹配MCP3008的规格

def _read_single_adc(channel):
    # MCP3008的原始读取逻辑
    adc_data = spi.xfer2([1, (8 + channel) << 4, 0])
    raw_value = ((adc_data[1] & 3) << 8) + adc_data[2]
    return raw_value / 1023.0  # 转换为0-1的百分比值

def get_temp_percent(timeout=1):
    samples = []
    
    def _read_task():
        nonlocal samples
        samples = [_read_single_adc(0) for _ in range(5)]
    
    # 用线程执行读取操作
    read_thread = threading.Thread(target=_read_task)
    read_thread.start()
    read_thread.join(timeout=timeout)
    
    if read_thread.is_alive():
        # 线程卡住时,重启SPI设备恢复通信
        spi.close()
        spi.open(0, 0)
        raise TimeoutError("ADC读取超时,已重启SPI设备")
    
    return sum(samples) / 5

如果碰到SPI总线卡住,关闭再重新打开设备通常能恢复通信,比GPIOZero的封装更可控。

解决方案3:排查硬件与系统问题

除了代码层面,你也可以检查这两个点:

  • 硬件连接:长时间运行后,SPI引脚(SCK、MOSI、MISO、CE0)或者热敏电阻的接线可能松动,导致通信异常。可以用万用表测一下引脚电压,或者重新插拔接线加固。
  • 系统与驱动更新:树莓派的SPI内核驱动偶尔会有bug,执行sudo apt update && sudo apt upgrade -y更新系统,或者用sudo rmmod spi_bcm2835 && sudo modprobe spi_bcm2835重新加载SPI模块,可能解决底层驱动的阻塞问题。

内容的提问来源于stack exchange,提问作者Aryeh Gielchinsky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:26:41