玩转树莓派Pico(21): 迷你气象站7——软件整合改进2
前言
改进是无止境的,我迟迟没有装配是因为我一直对我的代码不满意,一改再改。
改进日志类
这个是在别人的基础上,根据自己的需要稍微改动,难度不大。代码放在gitcode上,详见这里。
改进状态指示灯类
这是根据自己的想法,在AI的辅助下,经过多次迭代而成。然而我今天又有了新想法,而且很简单,经过测试可行,彻底将以前的结果推翻。那我前几天算不算白折腾?
起初我是用的嵌套函数,执行外层函数传入Pin对象,返回内层函数,这样就得到该led的专属对象,然后可以传入频率去执行。
以前测试led的闪烁时都是在主程序里。而我这个项目设计有3个指示灯,我认为需要在另一个线程中循环控制闪烁,于是使用了_thread类。而该类并不完善,使用中出现一个问题就是连接电脑调试时,当点击停止按钮,子线程仍在运行,导致与pico失去联系,只能重新插拔usb。为了避免这个问题又增加了代码的复杂性,最后弄成这样:
"""
功能: 状态指示灯的类, 使灯可以在另一个线程闪烁
最后更新: 241229
"""
import utime
import _thread
class StatusLed():
_mainthread_heart_beat_data = 0 # 主线程心跳数据(时间戳)
_mainthread_heart_beat_alive = 6 # 超过该时间算主线程退出,秒
def __init__(self, led, frequency = 1):
self.led = led # Pin对象
self.blink_frequency = frequency # 闪烁频率,单位Hz
self.cycle_accuracy = 10 # 循环精度,毫秒(每次循环的时间)
self.cycle_times = 0 # 改变led状态需要的循环次数
self.cycle_count = 0 # 循环计次
self.running = False # 控制是否闪烁的指针
self.lock = _thread.allocate_lock() # 线程锁
self.thread_id = None # 保存线程id
self.frequency_to_cycle_times()
# 根据频率计算循环次数
def frequency_to_cycle_times(self):
self.cycle_times = int((1000 / self.blink_frequency)/10)
# 心跳数据
@classmethod
def heart_beat(cls):
cls._mainthread_heart_beat_data = utime.time()
# 检测主线程是否存活
@property
def is_mainthread_alive(self):
if utime.time() - self.__class__._mainthread_heart_beat_data <= self.__class__._mainthread_heart_beat_alive:
return True
else:
return False
# 闪烁
def blink(self):
while self.running and self.is_mainthread_alive:
if self.cycle_count >= self.cycle_times: # 达到次数,才改变led的状态
self.led.toggle()
self.cycle_count = 0
else:
self.cycle_count += 1
utime.sleep_ms(self.cycle_accuracy)
self.thread_id = None
self.led.off() # 退出循环时,关闭灯
# 停止闪烁
def stop_blink(self):
with self.lock:
self.running = False
# 等待子线程结束,但是最大等待时间为循环精度
start_time = utime.ticks_ms() # 起始时间
while self.thread_id is not None:
if utime.ticks_diff(utime.ticks_ms(), start_time) > self.cycle_accuracy:
break
# 启动闪烁
def start_blink(self, frequency=None):
if frequency and (0.1 <= frequency <= 100):
with self.lock:
self.blink_frequency = frequency
self.frequency_to_cycle_times()
self.running = True
if self.thread_id is None:
self.thread_id = _thread.start_new_thread(self.blink, ())
else: # 频率为0或设置不符合要求,停止
self.stop_blink()
# 按档位闪烁
def blink_adjust_speed_with_position(self, position):
if position == 0:
frequency = 0
elif position == 1:
frequency = 0.25
elif position == 2:
frequency = 0.5
elif position == 3:
frequency = 1
elif position == 4:
frequency = 2
elif position == 5:
frequency = 4
elif position == 6:
frequency = 8
elif position == 7:
frequency = 16
elif position == 8:
frequency = 32
else:
frequency = 0
self.start_blink(frequency)
# 调试代码
if __name__ == '__main__':
from machine import Pin
led = StatusLed(Pin(25, Pin.OUT))
led.blink_adjust_speed_with_position(8)
utime.sleep(20)
led.blink_adjust_speed_with_position(0)
今天我又有新的想法,上面的代码是一个指示灯生成一个实例对象,就要新开一个线程。我可以让多个指示灯共用一个线程,节省资源。
然而还没等这个想法实施,我又来灵感。项目中我使用Timer类来定时执行读取气象数据并发布到MQTT服务器和校时任务, 为啥我不能用来控制led的闪烁呢? 只要将运行周期设为要闪烁的周期啊。这样就非常简单,根本无需使用_thread,代码如下:
"""
功能: 状态指示灯的类,封装了Timer类,用来来控制led的闪烁
最后更新: 250101
"""
import utime
from machine import Timer, Pin
class StatusLed():
def __init__(self, pin_no, period=1000):
self.led = Pin(pin_no, Pin.OUT) # Pin对象
self.default_period = period # 闪烁周期,毫秒
self.timer = Timer()
# 执行闪烁, 传入的参数是Timer的要求
def do_blink(self, timer):
self.led.toggle()
# 启动闪烁,传入周期
def start_blink(self, period):
if period > 0:
if period > 10000 or period < 10: # 超出范围,按默认周期闪烁
period = self.default_period
self.timer.init(period=period, mode=Timer.PERIODIC, callback=self.do_blink)
else: # 其他情况表明停止
self.stop_blink()
# 停止闪烁
def stop_blink(self):
self.timer.deinit()
self.led.off() # 停止后指示灯熄灭
# 按档位闪烁
def blink_with_position(self, position):
if position == 1:
period = 4000
elif position == 2:
period = 2000
elif position == 3:
period = 1000
elif position == 4:
period = 500
elif position == 5:
period = 250
elif position == 6:
period = 125
elif position == 7:
period = 62
elif position == 8:
period = 31
else:
period = 0
self.start_blink(period)
if __name__ == '__main__':
green_led = StatusLed(25)
for i in range(9):
print(f"档位:{i}")
green_led.blink_with_position(i)
utime.sleep(10)
green_led.stop_blink()
总结
虽然看起来白折腾,但是没有这个折腾过程,怎么会有这个结果呢?我还体验了使用多线程,对定时器的也有更深的理解。