ESP32-C3平台MicroPython _thread引脚读取致LED显示卡顿问题
ESP32-C3 MicroPython多线程下七段LED刷新缓慢问题排查与解决
问题背景
- 硬件平台:Espressif ESP32-C3
- 语言:MicroPython
- 现象:使用
_thread模块开启两个线程后,驱动多位七段LED的displaytime()函数运行极慢,单个LED刷新约需1秒;但将detec_btn()中的pin_button.value()改为True后,displaytime()恢复正常。
问题代码
# define a button pin_button = Pin(6, Pin.IN) Buttpushflag = 1 btn_value = 0 def detec_btn(): global btn_value global gpio_value while True: if pin_button.value() == Buttpushflag: time.sleep_ms(1000) if pin_button.value() == Buttpushflag: #print("button push valid") btn_value = 1 gpio_value = "1" else: btn_value = 0 #print("button push no valid") gpio_value = "0" # sacn muti 7 segments LED def displaytime(): while True: #current_time = time.localtime() current_time = ds.get_time() #print(current_time) time_hour = current_time[3] time_min = current_time[4] time_sed = current_time[5] sed_h, sed_l = divid_2num(time_sed) display1num(2, sed_l, "0") time.sleep(0.001) display1num(3, sed_h, "0") time.sleep(0.001) min_h, min_l = divid_2num(time_min) display1num(4, min_l, "1") time.sleep(0.001) display1num(5, min_h, "0") time.sleep(0.001) #display1num(6, "-", "0") hour_h, hour_l = divid_2num(time_hour) display1num(6, hour_l, "1") time.sleep(0.001) display1num(7, hour_h, "0") time.sleep(0.001) _thread.start_new_thread(displaytime,()) _thread.start_new_thread(detec_btn,())
原因分析
- MicroPython线程调度机制限制:MicroPython的
_thread模块实现的是协作式多线程,而非抢占式。线程之间需要主动释放CPU资源(比如通过time.sleep()这类触发调度的操作),其他线程才能获得执行机会。 - 按钮检测线程的阻塞问题:
- 当按钮未被按下时,
detec_btn()线程会进入else分支,无延迟地循环执行赋值操作,持续占用CPU,完全不给displaytime()线程执行机会。 - 只有按钮被按下时,线程执行
time.sleep_ms(1000)才会触发调度,但这1秒的延迟直接导致LED刷新变慢。
- 当按钮未被按下时,
- 修改为
True后的变化:将pin_button.value()改为True后,detec_btn()会一直进入if分支并执行time.sleep_ms(1000),每次睡眠都会触发调度,displaytime()就能正常获取CPU时间,LED因此恢复正常刷新。
解决办法
针对协作式多线程的特点,需让按钮检测线程主动释放CPU,避免独占资源:
方法1:在按钮检测循环中添加短延迟
修改detec_btn()函数,在循环末尾添加短延迟,主动触发线程调度:
def detec_btn(): global btn_value global gpio_value while True: if pin_button.value() == Buttpushflag: time.sleep_ms(1000) if pin_button.value() == Buttpushflag: btn_value = 1 gpio_value = "1" else: btn_value = 0 gpio_value = "0" # 添加短延迟,释放CPU给其他线程 time.sleep_ms(10)
方法2:使用GPIO中断替代轮询
避免轮询占用CPU,改用GPIO外部中断检测按钮按下,无需持续循环,效率更高:
# 定义中断回调函数 def btn_callback(pin): global btn_value, gpio_value # 防抖处理 time.sleep_ms(100) if pin.value() == Buttpushflag: btn_value = 1 gpio_value = "1" else: btn_value = 0 gpio_value = "0" # 初始化按钮并注册中断 pin_button = Pin(6, Pin.IN) # 根据按钮接线选择触发方式,示例为按下时触发上升沿 pin_button.irq(trigger=Pin.IRQ_RISING, handler=btn_callback) # 仅保留displaytime线程 _thread.start_new_thread(displaytime,())
方法3:优化轮询逻辑
若必须使用轮询,可只在按钮状态变化时更新值,同时保留短延迟:
def detec_btn(): global btn_value, gpio_value last_state = pin_button.value() while True: current_state = pin_button.value() if current_state != last_state: time.sleep_ms(100) # 防抖 current_state = pin_button.value() if current_state == Buttpushflag: btn_value = 1 gpio_value = "1" else: btn_value = 0 gpio_value = "0" last_state = current_state time.sleep_ms(10) # 释放CPU
内容的提问来源于stack exchange,提问作者bruce
相关产品推荐
相关产品推荐

