当前位置: 首页 > article >正文

玩转树莓派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()    

总结

        虽然看起来白折腾,但是没有这个折腾过程,怎么会有这个结果呢?我还体验了使用多线程,对定时器的也有更深的理解。


http://www.kler.cn/a/463587.html

相关文章:

  • 路由基本配置实验
  • STM32 和 ESP32
  • C# 设计模式(结构型模式):组合模式
  • Linux之ARM(MX6U)裸机篇----5.仿stm32的LED驱动实验
  • 全新免押租赁系统助力商品流通高效安全
  • Nacos服务注册和发现
  • 基于SSM(Spring + Spring MVC + MyBatis)框架的旅游资源网站
  • git reset --hard(重置到当前提交,所有未提交的更改都会被永久丢弃)
  • ubuntu中zlib安装的步骤是什么
  • 运维人员的Go语言学习路线
  • 初学stm32---高级定时器输出n个pwm波
  • 无人机无法返航紧急处理方式!
  • Redis - 1 ( 11000 字 Redis 入门级教程 )
  • Linux性能优化-网络篇-NAT详解
  • 基于Docker+模拟器的Appium自动化测试(二)
  • 如何使用网络工具进行网络性能评估
  • 【Rust自学】8.4. String类型 Pt.2:字节、标量值、字形簇以及字符串的各类操作
  • Android Opengl(三)绘制三角形
  • Python 数据可视化的完整指南
  • LLaMA详解
  • springboot520基于Spring Boot的民宿租赁系统的设计与实现(论文+源码)_kaic
  • 安卓入门四 Application Component
  • ubuntu2204 gpu 没接显示器,如何连接vnc
  • JnetPcap抓取数据包IP数据包
  • 3、redis的集群模式
  • selenium 安装Chrome驱动