树莓派GPIO回调事件中终止while循环问题求助
问题分析与修复方案
嘿,刚看完你的代码,发现两个核心问题导致你没法终止OLED程序,咱们一步步来解决:
问题1:局部变量搞砸了状态控制
你在oledfunc里写了var = var,但这个var是函数内部的局部变量——也就是说,你在offbutton回调里调用oledfunc(1),其实是新开了一个oledfunc的实例,完全碰不到之前启动的那个循环里的var值。原来的循环还在拿着最初的var=2一直跑,自然不会停下来。
问题2:主线程被阻塞,GPIO回调根本跑不起来
当你按下启动按钮,button_callback调用oledfunc(2),这个函数里的while循环会直接占满主线程。而Python的GPIO事件回调是在主线程里处理的,主线程被OLED循环卡死了,就算你按下停止按钮,offbutton回调也没机会执行,程序自然没反应。
修复方案:用线程分离任务+全局状态控制
我们需要把OLED的循环放到单独的线程里,让主线程能专心监听GPIO事件;同时用一个全局变量(或者线程安全的事件)来统一控制启停。
第一步:重构主脚本,用线程管理OLED
把主脚本改成这样,核心是用线程跑OLED,主线程留着处理GPIO:
from oledscriptwithfunc import start_oled_loop, stop_oled_loop from reset import rstfunc import RPi.GPIO as GPIO import threading # 全局状态变量:控制OLED是否运行 oled_running = False oled_thread = None def button_callback(channel): global oled_running, oled_thread if not oled_running: print("Initializing..") oled_running = True # 新开线程运行OLED循环,不阻塞主线程 oled_thread = threading.Thread(target=start_oled_loop) oled_thread.start() def offbutton(channel): global oled_running if oled_running: print("Ending program..") oled_running = False # 等待OLED线程完全退出 if oled_thread is not None: oled_thread.join() rstfunc() # 重置OLED屏幕 GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(10, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.setup(9, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # 加上防抖,避免开关抖动触发多次 GPIO.add_event_detect(10, GPIO.RISING, callback=button_callback, bouncetime=300) GPIO.add_event_detect(9, GPIO.RISING, callback=offbutton, bouncetime=300) message = input("Press enter to quit\n") # 退出前确保OLED线程停止 if oled_running: oled_running = False oled_thread.join() GPIO.cleanup()
第二步:重构oledscriptwithfunc.py,用全局状态控制循环
把原来的oledfunc改成依赖全局状态的循环,而且OLED初始化只做一次(原来每次调用都初始化太浪费资源):
import digitalio import busio import board from PIL import Image, ImageDraw, ImageFont import requests import adafruit_ssd1306 import datetime import time # 全局状态变量,和主脚本共享 oled_running = False # 只初始化一次OLED设备,避免重复初始化 i2c = busio.I2C(board.SCL, board.SDA) oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, addr=0x3c) oled.fill(0) oled.show() image = Image.new("1", (128, 32)) draw = ImageDraw.Draw(image) font = ImageFont.load_default() def start_oled_loop(): global oled_running timer1 = time.perf_counter() # 循环判断全局状态 while oled_running: timer2 = time.perf_counter() # 每30分钟更新一次天气数据 if int(timer2 - timer1) < 1 or int(timer2 - timer1) % 1800 == 0: # 这里可以加个异常处理,避免网络挂了程序崩掉 try: response = requests.get("https://api.openweathermap.org/data/2.5/weather?lat=xxxx&lon=xxxxx&lang=de&units=metric&appid=xxxxxxxxxxxxxxxxxx") response.raise_for_status() data = response.json() status = data['weather'][0]['description'] temp = data['main']['temp'] temptext = f"{temp}°C" timer1 = time.perf_counter() # 重置计时器 except Exception as e: print(f"获取天气失败: {e}") time.sleep(60) # 失败了等1分钟再试 continue # 显示时间10秒,期间也要检查是否要停止 t_end = time.time() + 10 while time.time() < t_end and oled_running: now = datetime.datetime.now() date_str = now.strftime("%d-%m-%Y") time_str = now.strftime("%M:%S") hour = int(now.strftime("%H")) + 2 text = f"{date_str} {hour}:{time_str}" draw.rectangle((0, 0, 128, 32), outline=255, fill=255) draw.rectangle((6, 6, 122, 26), outline=0, fill=0) font_width, font_height = font.getsize(text) draw.text((oled.width//2 - font_width//2, oled.height//2 - font_height//2), text, font=font, fill=255) oled.image(image) oled.show() time.sleep(0.1) # 加个小延迟,降低CPU占用 if not oled_running: break # 显示温度5秒 draw.rectangle((0, 0, 128, 32), outline=0, fill=0) font_width, font_height = font.getsize(temptext) draw.text((oled.width//2 - font_width//2, oled.height//2 - font_height//2), temptext, font=font, fill=255) oled.image(image) oled.show() time.sleep(5) if not oled_running: break # 显示天气状态5秒 draw.rectangle((0, 0, 128, 32), outline=0, fill=0) font_width, font_height = font.getsize(status) draw.text((oled.width//2 - font_width//2, oled.height//2 - font_height//2), status, font=font, fill=255) oled.image(image) oled.show() time.sleep(5) # 停止后清空OLED屏幕 oled.fill(0) oled.show() def stop_oled_loop(): global oled_running oled_running = False
关键修改点说明
- 线程分离:OLED循环在单独线程运行,主线程可以正常响应GPIO事件,不会被阻塞。
- 全局状态变量:
oled_running在主脚本和OLED模块之间共享,修改它就能直接控制循环启停。 - 循环内状态检查:在每个子循环(显示时间、温度、状态)里都检查
oled_running,确保按下停止按钮后能立刻退出,不用等当前子循环结束。 - 防抖处理:给GPIO事件加了
bouncetime=300,避免开关抖动导致多次触发。 - 异常处理:天气API请求加了异常捕获,避免网络问题导致程序崩溃。
额外优化建议
- 更推荐用
threading.Event代替全局变量,线程安全更高:比如定义stop_event = threading.Event(),用stop_event.wait(0.1)代替time.sleep(0.1),用stop_event.set()触发停止,这样不会有全局变量的竞态问题。 - 可以把天气API的参数(lat、lon、appid)放到配置文件里,方便修改。
内容的提问来源于stack exchange,提问作者aperturelabs
相关产品推荐
相关产品推荐

